fix(dictation): invalidate run before delayed teardown, remove unwrap

Address two review comments:

1. (useRealtimeDictation.ts) Increment activeRunIdRef immediately in the
   manual-commit cleanup path so late transcript events from the 3s grace
   window are rejected by handleRealtimeEvent. Previously, the run stayed
   valid during the timeout, allowing transcripts to write back into the
   composer after a send, edit-save, or navigation.

2. (transcribe.rs) Replace as_object_mut().unwrap() with a branch that
   builds the correct JSON literal directly. Avoids introducing an unwrap
   in a production path per AGENTS.md rules.
This commit is contained in:
klopez4212
2026-07-06 10:21:24 +01:00
committed by npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5
parent 625037e35d
commit cd6a707fc0
2 changed files with 19 additions and 18 deletions

View File

@@ -189,18 +189,18 @@ async fn can_mint_session(
fn build_session_payload(model: &str) -> Value {
let uses_manual_commit = model.contains("realtime-whisper");
let mut audio_input = serde_json::json!({
"transcription": {
"model": model,
}
});
if !uses_manual_commit {
audio_input.as_object_mut().unwrap().insert(
"turn_detection".to_string(),
serde_json::json!({ "type": "server_vad" }),
);
}
let audio_input = if uses_manual_commit {
// Manual commit mode — omit turn_detection entirely.
serde_json::json!({
"transcription": { "model": model }
})
} else {
// Server VAD mode — include turn_detection.
serde_json::json!({
"transcription": { "model": model },
"turn_detection": { "type": "server_vad" }
})
};
serde_json::json!({
"session": {

View File

@@ -102,6 +102,11 @@ export function useRealtimeDictation({
if (needsCommit && dc) {
commitAudioBuffer(dc);
// Mark the run stale immediately so late transcript events from the
// data channel are rejected by handleRealtimeEvent. This prevents
// transcripts from writing back into the composer after a send,
// edit-save, or navigation that triggered this cleanup.
activeRunIdRef.current += 1;
// Stop the mic immediately so no new audio is sent after commit.
for (const track of streamRef.current?.getTracks() ?? []) {
track.stop();
@@ -109,16 +114,12 @@ export function useRealtimeDictation({
streamRef.current = null;
audioCaptureRef.current?.close();
audioCaptureRef.current = null;
// Delay full teardown to allow the final transcript to arrive.
// Delay WebRTC teardown briefly for clean protocol shutdown (the
// commit message needs to reach OpenAI before the channel closes).
const pc = peerConnectionRef.current;
peerConnectionRef.current = null;
dataChannelRef.current = null;
const runId = activeRunIdRef.current;
setTimeout(() => {
// Only tear down if no new run started in the meantime.
if (activeRunIdRef.current === runId) {
activeRunIdRef.current += 1;
}
dc.close();
pc?.close();
}, 3000);