Built for people who think better out loud.
at main 74 lines 1.7 kB view raw
1type TranscriptionWord = { 2 word: string; 3 start: number; 4 end: number; 5}; 6 7type TranscriptionSegment = { 8 id: number; 9 start: number; 10 end: number; 11 text: string; 12}; 13 14export type TranscriptionResult = { 15 text: string; 16 language?: string; 17 duration?: number; 18 segments?: TranscriptionSegment[] | null; 19 words?: TranscriptionWord[] | null; 20}; 21 22type TranscriptionRequest = { 23 endpointUrl: string; 24 blob: Blob; 25 signal?: AbortSignal; 26}; 27 28type ApiErrorPayload = { 29 error?: { 30 message?: string; 31 }; 32}; 33 34function extensionFromMimeType(mimeType: string) { 35 const match = mimeType.match(/audio\/([^;]+)/); 36 return match?.[1] || "webm"; 37} 38 39async function parseErrorMessage(response: Response) { 40 const payload = (await response.json().catch(() => ({}))) as ApiErrorPayload; 41 return payload?.error?.message; 42} 43 44export async function transcribeWithBackend({ 45 endpointUrl, 46 blob, 47 signal, 48}: TranscriptionRequest): Promise<TranscriptionResult> { 49 const extension = extensionFromMimeType(blob.type || "audio/webm"); 50 const file = new File([blob], `recording.${extension}`, { 51 type: blob.type || "audio/webm", 52 }); 53 54 const formData = new FormData(); 55 formData.append("file", file); 56 57 const response = await fetch(endpointUrl, { 58 method: "POST", 59 body: formData, 60 signal, 61 }); 62 63 if (!response.ok) { 64 const message = await parseErrorMessage(response); 65 throw new Error(message || `Transcription request failed (${response.status}).`); 66 } 67 68 const payload = (await response.json()) as TranscriptionResult; 69 if (!payload || typeof payload.text !== "string") { 70 throw new Error("Backend did not return a transcription."); 71 } 72 73 return payload; 74}