'use client' import { useEffect, useRef, useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' type VoiceState = 'idle' | 'wake' | 'command' | 'response' | 'error' type Agent = 'cosmo' | 'lusya' interface VoiceEvent { event: VoiceState agent?: Agent text?: string timestamp: string } const AGENT_STYLE: Record = { cosmo: { primary: '#818cf8', secondary: '#a855f7', name: 'Cosmo', emoji: '🦞' }, lusya: { primary: '#ec4899', secondary: '#f43f5e', name: 'Люся', emoji: '👩' }, } export default function VoiceOverlay() { const [state, setState] = useState('idle') const [agent, setAgent] = useState('cosmo') const [text, setText] = useState('') const dismissTimer = useRef | null>(null) const clearDismiss = () => { if (dismissTimer.current) { clearTimeout(dismissTimer.current) dismissTimer.current = null } } const scheduleDismiss = (ms: number) => { clearDismiss() dismissTimer.current = setTimeout(() => setState('idle'), ms) } useEffect(() => { let es: EventSource | null = null let retry: ReturnType | null = null let closedByUs = false const connect = () => { es = new EventSource('/api/voice/stream') es.onmessage = (e) => { try { const evt: VoiceEvent = JSON.parse(e.data) if (evt.agent) setAgent(evt.agent) if (evt.event === 'wake') { setState('wake') setText('') scheduleDismiss(20000) // safety net: 20s max without command } else if (evt.event === 'command') { setState('command') setText(evt.text || '') scheduleDismiss(30000) } else if (evt.event === 'response') { setState('response') setText(evt.text || '') scheduleDismiss(6000) } else if (evt.event === 'error') { setState('error') setText(evt.text || 'Ошибка') scheduleDismiss(4000) } else if (evt.event === 'idle') { clearDismiss() setState('idle') } } catch {} } es.onerror = () => { if (closedByUs) return es?.close() retry = setTimeout(connect, 3000) } } connect() return () => { closedByUs = true clearDismiss() if (retry) clearTimeout(retry) es?.close() } }, []) const isActive = state !== 'idle' const style = AGENT_STYLE[agent] return ( {isActive && (
{style.emoji} {style.name} {state !== 'wake' && ( )} {state === 'wake' ? '· слушает' : state === 'command' ? '· распознал' : state === 'response' ? '· отвечает' : state === 'error' ? '· ошибка' : ''}
{state === 'wake' ? 'Слушаю…' : (text || '…')}
)}
) } function SiriBlob({ color, color2, state }: { color: string; color2: string; state: VoiceState }) { const isIntense = state === 'wake' return (
{/* Outer pulsing ring */} {/* Inner core */} {/* Bright center dot */}
) }