From 23928d342ce14c3e25b6b65c5109b4a1556a124a Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sat, 22 Aug 2026 00:36:25 +0300 Subject: [PATCH] feat(dictation): transcribe after recording instead of live Parakeet is an offline model trained on whole utterances, so re-decoding the growing buffer to animate a live transcript cost O(n^2) work for a result the final decode replaced. Sessions now decode once per committed segment, and the composer shows a scrolling waveform of the mic level instead of running text. Long dictations split at a pause once past 60s (hard cap 90s) instead of on a blind 15s timer, so cuts no longer land mid-word. Committed segments decode while the user is still speaking: a 185s dictation returns 4.1s after stop instead of 11.0s, with identical text (816 vs 817 words). Also fixes two ways the stream manager could silently drop transcribed audio. It now counts the commits it issued instead of trusting the session's echoed events, so a commit still in flight when the client finishes can no longer be left out of the final text. And segment byte/peak accounting is reset where the commit is issued rather than when the event arrives, which could mistake the tail of a dictation for silence and clear it. --- CHANGELOG.md | 1 + .../dictation/ComposerDictation.tsx | 34 ++--- .../dictation/DictationWaveform.tsx | 130 ++++++++++++++++++ packages/ui/src/hooks/useDictation.ts | 23 ++-- .../dictation/use-dictation-audio-source.ts | 38 +++-- .../web/server/lib/dictation/DOCUMENTATION.md | 40 ++++-- .../lib/dictation/local/sherpa-recognizer.js | 113 +++++---------- .../lib/dictation/local/worker-process.js | 4 +- .../dictation/openai-compatible-session.js | 5 +- .../server/lib/dictation/stream-manager.js | 111 ++++++++++----- .../lib/dictation/stream-manager.test.js | 62 ++++++++- 11 files changed, 393 insertions(+), 168 deletions(-) create mode 100644 packages/ui/src/components/dictation/DictationWaveform.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 65659752..03cf8954 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- **Dictation:** speech is now transcribed after you stop talking, instead of being re-guessed word by word while you speak. The offline models OpenChamber runs are built to read a whole utterance at once, so the running transcript was consistently worse than the final one. While recording, the composer shows a live waveform of your voice and a timer, then Transcribing while the text is produced. Long recordings are split at pauses in your speech rather than on a timer, so a three-minute dictation still returns a few seconds after you stop, and words are no longer cut in half at the split. - Chat: if OpenCode restarts while a response is still running, the chat now stops with an interrupted state and a notification to continue instead of hanging silently (thanks to @sum117). ## [1.19.0] - 2026-08-19 diff --git a/packages/ui/src/components/dictation/ComposerDictation.tsx b/packages/ui/src/components/dictation/ComposerDictation.tsx index c458e5d1..c66bc804 100644 --- a/packages/ui/src/components/dictation/ComposerDictation.tsx +++ b/packages/ui/src/components/dictation/ComposerDictation.tsx @@ -5,6 +5,10 @@ * area uses the same paddings/typography as the textarea and the action row * reuses the footer icon-button styling — so toggling dictation causes no * vertical shift. + * + * No text appears while recording. The server transcribes the audio once the + * user stops, so the overlay shows the recording state and then Transcribing. + * The only transcript rendered here is the salvage text of a failed dictation. */ import React from 'react'; @@ -15,6 +19,7 @@ import { useThemeSystem } from '@/contexts/useThemeSystem'; import { cn } from '@/lib/utils'; import { runtimeFetch } from '@/lib/runtime-fetch'; import { useDictation } from '@/hooks/useDictation'; +import { DictationWaveform } from '@/components/dictation/DictationWaveform'; import { isDictationCaptureSupported } from '@/lib/dictation/use-dictation-audio-source'; import { isVSCodeRuntime } from '@/lib/desktop'; import { useConfigStore } from '@/stores/useConfigStore'; @@ -50,25 +55,6 @@ const formatDuration = (seconds: number): string => { return `${mins}:${String(secs).padStart(2, '0')}`; }; -const VolumeMeter: React.FC<{ volume: number }> = ({ volume }) => { - const { currentTheme } = useThemeSystem(); - return ( -