feat(mcp): Rust-owned speaking pill with self-contained audio playback

The pill window now surfaces for agent-initiated speech without main-window
involvement. Rust subscribes to /events/speak via a tokio task + reqwest
streaming body (speak_monitor.rs), shows the pill, and forwards events to
the dictate webview over Tauri's event bus. The pill plays audio via a
plain HTMLAudioElement and emits dictate:hide when playback ends. The
pill stays hidden through the ~1 s generation wait and only surfaces when
audio actually starts, with the counter armed at that moment.

Fixes a shared-dict mutation in mcp_server/events.publish() that caused
the second subscriber (Rust speak_monitor) to receive `event: message`
instead of named speak-start/speak-end frames. Also teaches the speak_monitor
parser to handle CRLF framing (sse-starlette default). Main-window
AudioPlayer now skips autoplay for source in {mcp, rest} to avoid
double-play when both windows are alive.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
James Pine
2026-04-23 01:46:17 -07:00
co-authored by Claude Opus 4.7
parent 0cef2c9fe1
commit 6b75e097e1
14 changed files with 387 additions and 192 deletions
+3 -4
View File
@@ -60,12 +60,11 @@ export default function CapturePage() {
<div className="grid md:grid-cols-3 gap-6">
<div className="rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6">
<h3 className="text-[15px] font-semibold text-foreground mb-2">
Four STT engines, one picker
Whisper, sized for every machine
</h3>
<p className="text-sm leading-relaxed text-muted-foreground">
Whisper, Whisper Turbo, Parakeet v3, Qwen3-ASR. Pick per-capture — broad
multilingual, speed, non-English quality, or cross-platform coverage. All local,
all downloadable from inside the app.
Base, Small, Medium, Large, and Turbo. Pick per-capture — 99 languages at every
tier, all local, all downloadable from inside the app.
</p>
</div>
<div className="rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6">