mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(dictation): ignore stale transcript events after stop/send
When the user manually sends (Enter/click) while recording, the submit flow already calls stopRecording(). However, queued data channel messages could still fire handleRealtimeEvent after the peer connection teardown began, writing stale transcript text back into the now-empty composer. Fix: pass the run ID captured at startRecording into the data channel message handler closure. handleRealtimeEvent now checks activeRunIdRef against the captured run ID and drops events from stale runs. This guarantees that once stopRecording() increments the run counter, no further transcript events from that session can mutate composer state.
This commit is contained in:
@@ -104,29 +104,36 @@ export function useRealtimeDictation({
|
||||
|
||||
useEffect(() => cleanupResources, [cleanupResources]);
|
||||
|
||||
const handleRealtimeEvent = useCallback((event: TranscriptEvent) => {
|
||||
if (event.type === "error") {
|
||||
console.error("OpenAI realtime server error", event);
|
||||
toast.error(event.error?.message ?? "Voice input error");
|
||||
return;
|
||||
}
|
||||
const handleRealtimeEvent = useCallback(
|
||||
(runId: number, event: TranscriptEvent) => {
|
||||
// Ignore events from a stale run (e.g. user sent/stopped while
|
||||
// transcripts were still in-flight from the data channel).
|
||||
if (activeRunIdRef.current !== runId) return;
|
||||
|
||||
if (
|
||||
event.type !== TRANSCRIPT_DELTA_EVENT &&
|
||||
event.type !== TRANSCRIPT_COMPLETED_EVENT &&
|
||||
event.type !== BUFFER_COMMITTED_EVENT
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (event.type === "error") {
|
||||
console.error("OpenAI realtime server error", event);
|
||||
toast.error(event.error?.message ?? "Voice input error");
|
||||
return;
|
||||
}
|
||||
|
||||
const prevText = getTranscriptText(segmentStateRef.current);
|
||||
const merged = mergeTranscriptEvent(segmentStateRef.current, event);
|
||||
if (
|
||||
event.type !== TRANSCRIPT_DELTA_EVENT &&
|
||||
event.type !== TRANSCRIPT_COMPLETED_EVENT &&
|
||||
event.type !== BUFFER_COMMITTED_EVENT
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (merged === prevText) return;
|
||||
const prevText = getTranscriptText(segmentStateRef.current);
|
||||
const merged = mergeTranscriptEvent(segmentStateRef.current, event);
|
||||
|
||||
onTranscriptTextRef.current(merged);
|
||||
setIsTranscribing(event.type !== TRANSCRIPT_COMPLETED_EVENT);
|
||||
}, []);
|
||||
if (merged === prevText) return;
|
||||
|
||||
onTranscriptTextRef.current(merged);
|
||||
setIsTranscribing(event.type !== TRANSCRIPT_COMPLETED_EVENT);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const startRecording = useCallback(async () => {
|
||||
if (!isEnabled || isStarting || isRecording) return;
|
||||
@@ -187,7 +194,7 @@ export function useRealtimeDictation({
|
||||
dataChannelRef.current = dataChannel;
|
||||
dataChannel.addEventListener("message", (message) => {
|
||||
try {
|
||||
handleRealtimeEvent(JSON.parse(String(message.data)));
|
||||
handleRealtimeEvent(runId, JSON.parse(String(message.data)));
|
||||
} catch {
|
||||
// Ignore non-JSON events
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user