Skip to content

fix(desktop): stream sync TTS by sentence - #75532

Open
XtremXpert wants to merge 1 commit into
NousResearch:mainfrom
XtremXpert:fix/desktop-voice-conversation
Open

fix(desktop): stream sync TTS by sentence#75532
XtremXpert wants to merge 1 commit into
NousResearch:mainfrom
XtremXpert:fix/desktop-voice-conversation

Conversation

@XtremXpert

Copy link
Copy Markdown

Summary

  • stream synchronous TTS providers such as Edge sentence-by-sentence instead of waiting for the complete assistant reply
  • advertise the WebSocket audio encoding and decode encoded chunks sequentially in Desktop while preserving the existing raw PCM path
  • negotiate encoded-audio protocol v2 so older Desktop clients safely fall back instead of treating MP3 bytes as PCM
  • fall back to whole-text speech when synchronous synthesis fails before producing any audio
  • stop an encoded session cleanly on a decode error while draining audio already scheduled
  • wait for queued decoding/playback before settling the speech session, while retaining cancellation and fallback behavior
  • add a regression test proving the first synchronous-TTS sentence is emitted before the reply finishes

Why

Edge TTS produces encoded MP3 rather than native PCM. The previous fallback waited for the entire generated reply before synthesizing it, which created a long silent delay in voice conversation mode. This change provides application-level streaming without changing the behavior of providers that already support PCM streaming.

Related work

PR #74301 pipelines synthesis with local speaker playback in tools/tts_tool.py. This PR is complementary: it covers the Desktop WebSocket path in hermes_cli/web_server.py and encoded-audio decoding in the Electron renderer; the changed files and runtime paths do not overlap.

Testing

  • scripts/run_tests.sh tests/hermes_cli/test_web_server_speak_stream.py — 7 passed
  • npm run test:ui -- src/lib/voice-playback.test.ts src/app/chat/composer/hooks/use-voice-conversation.test.tsx src/app/chat/composer/hooks/use-voice-conversation-rearm.test.tsx — 12 passed
  • npm run typecheck
  • venv/bin/ruff check hermes_cli/web_server.py tests/hermes_cli/test_web_server_speak_stream.py
  • npx eslint src/lib/voice-playback.ts
  • npx prettier --check src/lib/voice-playback.ts
  • npm run build
  • npm run builder -- --dir --mac

Manual verification

  • verified Edge TTS with fr-CA-SylvieNeural starts speaking before a long response finishes generating
  • verified sentence chunks play in order
  • verified speaking over playback cancels the queued audio and submits the interruption
  • verified the microphone returns to listening after normal playback

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for addressing the synchronous-provider gap. Current main still closes /api/audio/speak-stream with fallback when no PCM streamer resolves (hermes_cli/web_server.py:4539), while the streaming-provider contract explicitly expects a per-sentence synchronous path for that case (tools/tts_streaming.py:196-199).

The PR adds that path at hermes_cli/web_server.py:4571-4596, preserves old-client behavior with the protocol-v2 gate at hermes_cli/web_server.py:4548-4554, and serializes encoded-clip decoding before draining at apps/desktop/src/lib/voice-playback.ts:272-299 and 342-365. The added backend tests cover early sentence emission and both pre- and post-audio failure behavior (tests/hermes_cli/test_web_server_speak_stream.py:94-156). No blocking issue identified from source review.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/streaming Streaming responses: gateway delivery, provider wire labels Jul 31, 2026
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/desktop Electron desktop app (apps/desktop/*) tool/tts Text-to-speech and transcription labels Jul 31, 2026
@andrexibiza

Copy link
Copy Markdown
Contributor

Vox Lockin lane 09 verification receipt — mergeable, no desktop-class gaps found.

  • Merge-clean against current main (70db671): no conflicts in apps/desktop/src/lib/voice-playback.ts or hermes_cli/web_server.py.
  • Desktop delta reviewed: protocol-v2 opt-in (audio_protocol=2) keeps old clients on the PCM path (no misinterpreting MP3 bytes as raw PCM), encoded sentence frames schedule through the existing nextStartAt queue, and decode failure falls back to whole-text playback. Consistent with the current hook's SpeechStreamSession contract (append/finish/done → 'fallback').
  • Tests: apps/desktop/src/lib/voice-playback.test.ts passes on PR head; the web-server speak-stream tests are outside the desktop lane but additive.
  • Cross-checked against fix(desktop): harden read aloud synthesis and playback #76757: both touch voice-playback.ts but the changes are orthogonal (context hardening vs sentence frames); both merge cleanly.

Recommend merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/streaming Streaming responses: gateway delivery, provider wire comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages tool/tts Text-to-speech and transcription type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants