diff --git a/components/VoiceController.tsx b/components/VoiceController.tsx index bd9efc8..3b6e429 100644 --- a/components/VoiceController.tsx +++ b/components/VoiceController.tsx @@ -68,7 +68,23 @@ export default function VoiceController() { useEffect(() => { console.log('[VoiceController] mounted, state=idle, ждём тап на микрофон') + + // Кнопка X в overlay шлёт voice-cancel → прерываем активную запись фразы, + // но wake-word оставляем слушать в фоне (если он включён). + const onCancel = () => { + console.log('[voice] cancel — прерываю запись') + try { vadRef.current?.pause?.() } catch {} + try { vadRef.current?.destroy?.() } catch {} + vadRef.current = null + busyRef.current = false + try { wakeRef.current?.resume?.() } catch {} + setState((s) => (wakeRef.current ? 'listening' : 'idle')) + emitLocal('idle', AGENT) + } + window.addEventListener('voice-cancel', onCancel) + return () => { + window.removeEventListener('voice-cancel', onCancel) try { vadRef.current?.destroy?.() } catch {} try { wakeRef.current?.stop?.() } catch {} vadRef.current = null diff --git a/components/VoiceOverlay.tsx b/components/VoiceOverlay.tsx index 9dcc848..5a503ed 100644 --- a/components/VoiceOverlay.tsx +++ b/components/VoiceOverlay.tsx @@ -1,6 +1,7 @@ 'use client' import { useEffect, useRef, useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' +import { X } from 'lucide-react' type VoiceState = 'idle' | 'wake' | 'listening' | 'command' | 'response' | 'error' type Agent = 'cosmo' | 'lusya' @@ -206,6 +207,31 @@ export default function VoiceOverlay() { pointerEvents: 'none', }} > + {/* Закрыть прослушивание. Просим VoiceController прервать активный + VAD/recording через voice-cancel, и сами закрываем UI. */} + + {/* Subtle status (только "слушаю" — для остальных текст сам говорит за себя) */}