Skip to content

feat: add browser-native text-to-speech for assistant replies - #113

Merged
kahme247 merged 4 commits into
kahme247:mainfrom
andrebrait:feat/text-to-speech
Sep 19, 2026
Merged

kahme247 merged 4 commits into
kahme247:mainfrom
andrebrait:feat/text-to-speech

Conversation

@andrebrait

Copy link
Copy Markdown
Contributor

Summary

Adds browser-native text-to-speech (TTS) playback for assistant replies in ompweb using the standard Web Speech API (window.speechSynthesis).

Features

  • Speaker Action Button: Added to completed assistant messages in MessageView, matching sibling Copy and Fork button styling (.message-copy-action, var(--text-muted)).
  • Audio Sanitizer (lib/speech-sanitizer.ts): Strips fenced code blocks, inline code syntax, raw URLs, and markdown formatting before speech synthesis so spoken output sounds natural.
  • Speech Synthesis Hook (hooks/useSpeechSynthesis.ts): Handles client lifecycle, Chromium SpeechSynthesisUtterance garbage-collection retention, cross-component state synchronization via custom events, and immediate cancellation on Escape or clicking stop.
  • User Settings (components/SettingsConfig.tsx):
    • Auto-read assistant responses: Toggle in Settings → General to automatically read aloud new replies when completed.
    • Speech Voice: Dropdown in Settings → General to select any installed browser/system voice.
  • Auto-read on Complete (components/ChatWindow.tsx): Automatically triggers playback for completed turns when auto-read is enabled.
  • Localization: Full translations for English (en.json), Japanese (ja.json), and Simplified Chinese (zh-CN.json).

Verification

  • Unit tests for text sanitization in lib/speech-sanitizer.test.mjs (6/6 passing).
  • tsc --noEmit and npm run lint clean (0 errors).
  • Tested in browser: click-to-speak, stop, voice selection, and auto-read toggle.

Allows hearing assistant messages aloud using browser-native SpeechSynthesis:
- Speaker action button on completed assistant replies matching sibling action buttons
- Client-side SpeechSynthesis hook with garbage-collection retention and cross-component state sync
- Markdown/code-fence sanitization so code blocks and syntax are not read aloud
- Auto-read toggle and speech voice selector in Settings -> General
- Full i18n localization for English, Japanese, and Simplified Chinese
- Use addEventListener for voiceschanged so per-hook mounts stop clobbering the shared handler
- Mount the speech hook once via SpeechSynthesisProvider; transcript rows consume useSpeechContext
- Autoplay reads streamState.streamingMessage with assistant-role narrowing instead of stale messagesRef
- TTS settings render disabled with an explanation on unsupported browsers instead of hiding
- Add settingsConfig.ttsNotSupported localization (en/ja/zh-CN)
- speech sanitizer strips tag-shaped HTML only, so "x < y and z > 0" survives
- utterance lifecycle callbacks identity-check the active utterance before
  mutating shared speech state
- autoplay speaks from the render that commits the finished reply instead of
  refs that lag onAgentEnd
- the provider owns the single speech controller; useSpeechContext no longer
  mounts one fallback controller per assistant row
@kahme247
kahme247 merged commit f852ad0 into kahme247:main Sep 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants