From 9583c84e27287bc11fde22408ef40686ab7d08e0 Mon Sep 17 00:00:00 2001 From: Cosmo Date: Mon, 27 Apr 2026 10:25:21 +0000 Subject: [PATCH] =?UTF-8?q?feat(voice):=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA?= =?UTF-8?q?=D0=B0=20X=20=D0=B2=20overlay=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE=D1=81=D0=BB=D1=83?= =?UTF-8?q?=D1=88=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В overlay появляется крестик в правом верхнем углу. Тап = эмитит voice-cancel → VoiceController прерывает активный VAD-захват и сам overlay закрывается. Wake-word, если был активен, продолжает слушать в фоне. --- components/VoiceController.tsx | 16 ++++++++++++++++ components/VoiceOverlay.tsx | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) 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 (только "слушаю" — для остальных текст сам говорит за себя) */}