feat(voice): SSE bridge + Siri-blob overlay for wake-word script
All checks were successful
Deploy / deploy (push) Successful in 3m12s

Adds the tablet side of voice assistant integration. External Python
script (openWakeWord + Groq STT + OpenClaw) will POST state transitions
to /api/voice/event with a bearer token, and the tablet shows a
fullscreen overlay with Siri-style animated blob + current agent +
recognized text / response text.

- lib/voice-bus.ts — in-process EventEmitter singleton, preserved
  across hot reloads via globalThis
- app/api/voice/event — POST, bearer-auth via VOICE_API_KEY env,
  validates event kind, broadcasts on voiceBus
- app/api/voice/stream — GET, SSE endpoint, per-connection listener
  with 15s keep-alive ping and abort-signal cleanup
- components/VoiceOverlay — full-screen overlay, 3-layer pulsing
  Siri blob, per-agent palette (cosmo indigo/violet, lusya pink/rose),
  auto-dismiss timeouts (wake=20s safety, response=6s, error=4s),
  auto-reconnect on SSE drop
- middleware bypasses /api/voice/event so the script does not need
  a user auth cookie
- VoiceOverlay mounted in HomePageInner outside tab routing so it
  appears on every view
This commit is contained in:
Cosmo
2026-04-23 12:36:26 +00:00
parent 9fec9bca99
commit 51c3d6016a
6 changed files with 307 additions and 2 deletions

14
lib/voice-bus.ts Normal file
View File

@@ -0,0 +1,14 @@
import { EventEmitter } from 'events'
export type VoiceEventPayload = {
event: 'wake' | 'command' | 'response' | 'idle' | 'error'
agent?: 'cosmo' | 'lusya'
text?: string
timestamp: string
}
// Singleton across hot-reloads in dev, preserved via global.
const globalForBus = globalThis as unknown as { __voiceBus?: EventEmitter }
export const voiceBus: EventEmitter = globalForBus.__voiceBus ?? new EventEmitter()
voiceBus.setMaxListeners(32)
if (!globalForBus.__voiceBus) globalForBus.__voiceBus = voiceBus