mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(dictation): scope STT shutdown drain to dictation + stop PTT on blur
Two Codex P2 findings on PR #1511: - stt_engine.rs: the shutdown drain + final flush ran for every SttEngine, including the huddle wrapper. Leaving a huddle / disabling transcription calls shutdown() then drops the pipeline (Drop joins the worker), so draining the queue and decoding the trailing buffer could block huddle teardown even though the huddle generation guard discards the transcript. Gate the drain + final flush behind a new SttEngineConfig.flush_on_shutdown flag: true for dictation (preserves the last-words fix), false for huddle (fast no-final-flush teardown, as before). - AppShell.tsx: push-to-talk dictation only stopped on keyup, so if the WebView lost focus before \u2318D was released (Cmd-Tab, clicking another app, OS focus change), the key-up event never fired and the mic kept recording. Add blur + visibilitychange handlers that dispatch buzz:dictation-key-up when the key is still held.
This commit is contained in:
parent
8e046c0ed3
commit
18f7a373d0
@@ -79,6 +79,7 @@ pub async fn start_dictation(state: State<'_, AppState>) -> Result<u64, String>
|
||||
tts_active: None,
|
||||
tts_cancel: None,
|
||||
ptt_active: None,
|
||||
flush_on_shutdown: true,
|
||||
};
|
||||
|
||||
let (engine, text_rx) = SttEngine::new(config)?;
|
||||
|
||||
@@ -71,6 +71,7 @@ impl SttPipeline {
|
||||
tts_active: Some(tts_active),
|
||||
tts_cancel,
|
||||
ptt_active,
|
||||
flush_on_shutdown: false,
|
||||
};
|
||||
|
||||
let (engine, text_rx) = SttEngine::new(config)?;
|
||||
|
||||
@@ -72,6 +72,15 @@ pub struct SttEngineConfig {
|
||||
/// Optional: push-to-talk flag — when `Some`, speech is only accumulated
|
||||
/// while the flag is true.
|
||||
pub ptt_active: Option<Arc<AtomicBool>>,
|
||||
/// When `true`, the worker drains any audio still queued at shutdown and
|
||||
/// transcribes the trailing speech buffer before exiting. Dictation needs
|
||||
/// this so the last words the user spoke before releasing the key still
|
||||
/// land in the transcript. Set to `false` for huddle: leaving a huddle (or
|
||||
/// disabling transcription) calls `shutdown()` then drops the pipeline,
|
||||
/// whose `Drop` joins this worker — running the full drain + final decode
|
||||
/// there would block huddle teardown, and the huddle generation guard
|
||||
/// discards any transcript produced during teardown anyway.
|
||||
pub flush_on_shutdown: bool,
|
||||
}
|
||||
|
||||
// ── Public engine handle ──────────────────────────────────────────────────────
|
||||
@@ -265,6 +274,7 @@ fn stt_worker(
|
||||
|
||||
// ── 5. Main loop ──────────────────────────────────────────────────────────
|
||||
let has_tts = config.tts_active.is_some();
|
||||
let flush_on_shutdown = config.flush_on_shutdown;
|
||||
let tts_active_flag = config
|
||||
.tts_active
|
||||
.unwrap_or_else(|| Arc::new(AtomicBool::new(false)));
|
||||
@@ -351,6 +361,15 @@ fn stt_worker(
|
||||
// enqueued batch, so the tail of speech would never reach `speech_buf` and
|
||||
// the final flush below would only transcribe older audio — dropping the
|
||||
// last words. Process everything still in the channel before flushing.
|
||||
//
|
||||
// Only dictation opts into this (`flush_on_shutdown`). Huddle shutdown must
|
||||
// stay fast: its `Drop` joins this worker, so draining the queue + decoding
|
||||
// the final buffer here would block huddle teardown, and any transcript
|
||||
// produced during teardown is discarded by the huddle generation guard.
|
||||
if !flush_on_shutdown {
|
||||
return;
|
||||
}
|
||||
|
||||
while let Ok(bytes) = audio_rx.try_recv() {
|
||||
let samples_48k = bytes_to_f32(&bytes);
|
||||
input_buf_48k.extend_from_slice(&samples_48k);
|
||||
|
||||
@@ -621,11 +621,32 @@ export function AppShell() {
|
||||
}
|
||||
}
|
||||
|
||||
// If the WebView loses focus (Cmd-Tab, clicking another app, an OS focus
|
||||
// change) or is hidden before ⌘D is released, the keyup never fires and the
|
||||
// mic would keep recording. Release the push-to-talk key on blur/hide so
|
||||
// the recording stops instead of running unattended.
|
||||
function releaseDictationKey() {
|
||||
if (dictationKeyHeld) {
|
||||
dictationKeyHeld = false;
|
||||
window.dispatchEvent(new CustomEvent("buzz:dictation-key-up"));
|
||||
}
|
||||
}
|
||||
|
||||
function handleVisibilityChange() {
|
||||
if (document.hidden) {
|
||||
releaseDictationKey();
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
window.addEventListener("keyup", handleKeyUp);
|
||||
window.addEventListener("blur", releaseDictationKey);
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
window.removeEventListener("keyup", handleKeyUp);
|
||||
window.removeEventListener("blur", releaseDictationKey);
|
||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
};
|
||||
}, [
|
||||
handleOpenBrowseChannels,
|
||||
|
||||
Reference in New Issue
Block a user