From ba8a9c40389c961f2bc5ec05d1e10dbdf53620e9 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Wed, 5 Aug 2026 21:19:00 -0700 Subject: [PATCH] [eric] voice: scratch-that discards the take, multilingual Small with auto language detect joins the catalog --- electron/voice/whisperModels.js | 1 + electron/voice/whisperService.js | 5 ++++- frontend/src/shared/voice/useVoiceDictation.ts | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/electron/voice/whisperModels.js b/electron/voice/whisperModels.js index 57bf09e5..d9e9844b 100644 --- a/electron/voice/whisperModels.js +++ b/electron/voice/whisperModels.js @@ -21,6 +21,7 @@ const MODELS = [ { id: 'base.en-q5_1', file: 'ggml-base.en-q5_1.bin', label: 'Base (compact)', note: 'Base quality and speed, 90MB less to download and hold', bytes: 59721011, sha256: '4baf70dd0d7c4247ba2b81fafd9c01005ac77c2f9ef064e00dcf195d0e2fdd2f' }, { id: 'base.en', file: 'ggml-base.en.bin', label: 'Base', note: 'Fast and light; the instant fallback while Small downloads', bytes: 147964211, sha256: 'a03779c86df3323075f5e796cb2ce5029f00ec8869eee3fdfb897afe36c6d002' }, { id: 'small.en-q5_1', file: 'ggml-small.en-q5_1.bin', label: 'Small', note: 'Most accurate, the default; steadier on accents and noise', bytes: 190098681, sha256: 'bfdff4894dcb76bbf647d56263ea2a96645423f1669176f4844a1bf8e478ad30' }, + { id: 'small-q5_1', file: 'ggml-small-q5_1.bin', label: 'Small (multilingual)', note: 'Auto-detects the spoken language; slightly less sharp on English', bytes: 190085487, sha256: 'ae85e4a935d7a567bd102fe55afc16bb595bdb618e11b2fc7591bc08120411bb' }, ]; // Measured on an M2 (quiet machine, 3.5s/8.1s/26.4s utterances, median of 5): // tiny 174/154/241ms base-q5_1 212/414/656ms base 208/398/734ms small 865/1108/1869ms diff --git a/electron/voice/whisperService.js b/electron/voice/whisperService.js index d9f91b18..e5dc7fdc 100644 --- a/electron/voice/whisperService.js +++ b/electron/voice/whisperService.js @@ -145,7 +145,10 @@ async function p_bootServer(resourceDir, userDataDir) { p_sweepStrays(bin); const p = await freePort(); // No --convert: our WAV is already 16kHz mono, and the flag makes whisper demand ffmpeg on PATH at boot; a Finder-launched app has no brew PATH, so it exited before ever binding the port. - const child = spawn(bin, ['-m', model, '--port', String(p), '-nt'], { + const args = ['-m', model, '--port', String(p), '-nt']; + // A multilingual model (no .en in the filename) auto-detects the spoken language per utterance. + if (!path.basename(model).includes('.en')) args.push('-l', 'auto'); + const child = spawn(bin, args, { cwd: p_privateCwd(userDataDir), // a writable, EMPTY dir: whisper writes temp files beside cwd, and ggml scans cwd at boot (see p_privateCwd) // BOTH pipes: whisper writes its fatal reasons to STDOUT and then exits 0, so an ignored stdout // turns "ffmpeg is missing" into an unexplained failure. Draining also stops the pipe buffer diff --git a/frontend/src/shared/voice/useVoiceDictation.ts b/frontend/src/shared/voice/useVoiceDictation.ts index 3d07784a..dd2a7063 100644 --- a/frontend/src/shared/voice/useVoiceDictation.ts +++ b/frontend/src/shared/voice/useVoiceDictation.ts @@ -223,6 +223,12 @@ export function useVoiceDictation() { if (res?.ok && res.text) res = { ok: true, text: res.text.replace(/\[[^\]]*\]|\([^)]*\)|\*[^*]*\*|(?:^|\s)>>\s?/g, ' ').replace(/\s+/g, ' ').trim() }; // A transcript with no letter or digit in it is a hallucination artifact (the lone comma), not dictation. if (res?.ok && res.text && !/[\p{L}\p{N}]/u.test(res.text)) res = { ok: true, text: '' }; + // Wispr's command grammar, v1: saying only "scratch that" (or "delete/cancel that") throws the take away. + if (res?.ok && res.text && /^(scratch|delete|cancel) that[.!?]?$/i.test(res.text.trim())) { + setFeedback({ tone: 'ok', icon: 'check', text: 'Scratched.', at: Date.now() }); + setState('idle'); + return; + } if (res?.ok && res.text) { // WhisperFlow-style cleanup: punctuation + filler words via the cheap aux tier, fail-open to // the raw transcript on any error/timeout so dictation never breaks with the aux down.