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 (только "слушаю" — для остальных текст сам говорит за себя) */}