feat(web): add Read aloud for stored bot replies - #194
Conversation
Users could only hear a bot during a live ChatGPT call. This adds Read aloud on settled replies, playback controls, and an opt-in automatic readout preference, without starting a call or generating another turn. Stored-reply synthesis is still reported as unavailable until the shared speech operation lands. Made-with: grok-4.6-auto in T3 Code / Grok
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Isolated landing tests call components as functions with a partial React mock, so real useContext and environment connection hooks crashed. Mock the playback hooks the same way the tests already mock live-call controls. Made-with: grok-4.6-auto in T3 Code / Grok
Greptile SummaryThis PR adds shared stored-reply read-aloud support across web and mobile, including playback controls, automatic readout preferences, browser and native audio adapters, and focused test coverage. It also ensures playback is stopped or released when a thread exits, connectivity is lost, content changes, live calls begin, or audio resources are disposed. Confidence Score: 5/5Safe to merge. The unresolved playback-on-thread-exit issue is fully fixed by ownership-aware context cleanup. The connection-state, disposal-cleanup, and revealed-thread-context threads were resolved by greptile-apps[bot] without explanation; the current implementation forwards mobile connection state, contains disposal failures before resetting playback state, and restores thread context through navigation focus. Reviews (4): Last reviewed commit: "Merge branch 'main' into t3code/read-alo..." | Re-trigger Greptile |
| useEffect(() => { | ||
| if (!session) return; | ||
| if (!options.environmentId || !options.threadId) { | ||
| session.setContext(null); | ||
| return; | ||
| } | ||
| session.setContext({ | ||
| environmentId: options.environmentId, | ||
| threadId: options.threadId, | ||
| provider: session.synthesis.provider, | ||
| voice: session.synthesis.voice, | ||
| connected: !voiceEnvironmentConnectionLost(connection.data), | ||
| mediaBlocked: options.mediaBlocked, | ||
| }); | ||
| session.observe(options.messages); |
There was a problem hiding this comment.
Unmounting a thread does not clear the root-scoped playback context. Audio that was playing for the former thread remains playing and available after navigating away, because the effect sets context only while mounted and returns no cleanup. Clear this hook's owned context during cleanup so leaving a chat stops its playback; the mobile thread hook has the same omission.
Artifacts
- The executable React lifecycle test mounts a thread, starts playback, unmounts it, and asserts the observed retained state; it is the exact source used for the proof.
- Captured `cat` output records the exact executed test source and command metadata; it shows the lifecycle assertions used to reproduce the defect.
- Verbose test output records active playback before unmount and retained action, playing status, and undisposed audio after unmount; it demonstrates the lifecycle defect.
- A second executed run of the same lifecycle repro completed successfully and reproduced the retained playback behavior; it confirms the observation is repeatable.
Leaving a chat, disconnecting mobile, or a failed native dispose could leave playback running. Clear thread context on unmount, treat only a connected environment as playable, and still go idle if dispose throws. Made-with: grok-4.6-auto in T3 Code / Grok
Clearing playback on every unmount left the still-mounted chat without context after a nested thread was popped. Cleanup now only releases the owning chat, and mobile restores context when the revealed route is focused. Made-with: grok-4.6-auto in T3 Code / Grok
Users could only hear a bot during a live ChatGPT call. This adds Read aloud on settled replies, playback controls, and an opt-in automatic readout preference that stays off by default.
Playback speaks the stored reply. It does not start a call or generate another turn. Markdown is converted to spoken text, code blocks and images are skipped, and one playback owner stops on navigation, disconnect, edit/delete, or a live call. Stored-reply synthesis is still reported as unavailable until the shared speech operation lands.
Verification
100.84.34.117:58402). Desktop 1280×900 and narrow 390×844. Time advanced, analyser RMS was 0.088 while playing, and pause/stop/switch/retry/call/navigation behaved as specified.auto_null.adbmissing, XcodeBuildMCP timed out). Electron shell was not launched separately; the renderer is shared with web.Screenshots
Idle, playing, paused, error, and narrow states from the isolated fixture (local test audio, not provider speech).