type TranscriptionWord = { word: string; start: number; end: number; }; type TranscriptionSegment = { id: number; start: number; end: number; text: string; }; export type TranscriptionResult = { text: string; language?: string; duration?: number; segments?: TranscriptionSegment[] | null; words?: TranscriptionWord[] | null; }; type TranscriptionRequest = { endpointUrl: string; blob: Blob; signal?: AbortSignal; }; type ApiErrorPayload = { error?: { message?: string; }; }; function extensionFromMimeType(mimeType: string) { const match = mimeType.match(/audio\/([^;]+)/); return match?.[1] || "webm"; } async function parseErrorMessage(response: Response) { const payload = (await response.json().catch(() => ({}))) as ApiErrorPayload; return payload?.error?.message; } export async function transcribeWithBackend({ endpointUrl, blob, signal, }: TranscriptionRequest): Promise { const extension = extensionFromMimeType(blob.type || "audio/webm"); const file = new File([blob], `recording.${extension}`, { type: blob.type || "audio/webm", }); const formData = new FormData(); formData.append("file", file); const response = await fetch(endpointUrl, { method: "POST", body: formData, signal, }); if (!response.ok) { const message = await parseErrorMessage(response); throw new Error(message || `Transcription request failed (${response.status}).`); } const payload = (await response.json()) as TranscriptionResult; if (!payload || typeof payload.text !== "string") { throw new Error("Backend did not return a transcription."); } return payload; }