From 10accc60922f1c2b7a3dd2470af5f40bca6f5454 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 24 Feb 2026 18:11:51 -0800 Subject: [PATCH] fix: use live transcript directly instead of batch API re-transcription The batch Whisper API was producing worse results than the live Web Speech API transcript. Now the live text is used as-is when recording stops. Audio still uploads for the AUDIO note attachment. Parakeet.js offline fallback only triggers if no live transcript was captured. Co-Authored-By: Claude Opus 4.6 --- src/app/voice/page.tsx | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/src/app/voice/page.tsx b/src/app/voice/page.tsx index 90c892a..65f9260 100644 --- a/src/app/voice/page.tsx +++ b/src/app/voice/page.tsx @@ -339,16 +339,13 @@ export default function VoicePage() { const url = URL.createObjectURL(blob); setAudioUrl(url); - // --- Three-tier transcription cascade --- - - // Show immediate live text while we process - const immediateLive = wsFullText || (segmentsRef.current.length > 0 + // Use live transcript immediately (best quality in practice) + const liveResult = wsFullText || (segmentsRef.current.length > 0 ? segmentsRef.current.map(s => s.text).join(' ') : capturedLive); - if (immediateLive) setFinalTranscript(immediateLive); + setFinalTranscript(liveResult || ''); - // Tier 1: Upload + batch API - let bestTranscript = ''; + // Upload audio file in background (needed for AUDIO note, not for transcript) try { setStatus({ message: 'Uploading recording...', type: 'loading' }); const uploadForm = new FormData(); @@ -360,36 +357,24 @@ export default function VoicePage() { setUploadedFileUrl(uploadResult.url); setUploadedMimeType(uploadResult.mimeType); setUploadedFileSize(uploadResult.size); - - setStatus({ message: 'Transcribing...', type: 'loading' }); - const tForm = new FormData(); - tForm.append('audio', blob, 'voice-note.webm'); - const tRes = await authFetch('/api/voice/transcribe', { method: 'POST', body: tForm }); - if (tRes.ok) { - const tResult = await tRes.json(); - bestTranscript = tResult.text || ''; - } } } catch { - console.warn('Tier 1 (batch API) failed'); + console.warn('Audio upload failed'); } - // Tier 2: WebSocket / Web Speech API (already captured) - if (!bestTranscript) bestTranscript = immediateLive || ''; - - // Tier 3: Offline Parakeet.js - if (!bestTranscript) { + // If no live transcript at all, try offline Parakeet as last resort + if (!liveResult) { try { setStatus({ message: 'Loading offline model...', type: 'loading' }); const { transcribeOffline } = await import('@/lib/parakeetOffline'); - bestTranscript = await transcribeOffline(blob, (p) => setOfflineProgress(p)); + const offlineText = await transcribeOffline(blob, (p) => setOfflineProgress(p)); + setFinalTranscript(offlineText); setOfflineProgress(null); } catch { setOfflineProgress(null); } } - setFinalTranscript(bestTranscript); setStatus(null); setState('done'); }, [audioUrl, stopSpeechRecognition, cleanupStreaming]);