feat: follow-up dialog window — VAD stays active 8s after agent response
All checks were successful
Deploy / deploy (push) Successful in 1m37s
All checks were successful
Deploy / deploy (push) Successful in 1m37s
This commit is contained in:
@@ -38,6 +38,8 @@ export default function VoiceController() {
|
||||
const wakeRef = useRef<WakeWordDetector | null>(null)
|
||||
const vadRef = useRef<any>(null)
|
||||
const busyRef = useRef(false)
|
||||
const followUpRef = useRef(false)
|
||||
const followUpTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
vlog('[VoiceController] mounted, state=idle, ждём тап на микрофон')
|
||||
@@ -46,6 +48,8 @@ export default function VoiceController() {
|
||||
// (НЕ destroy — иначе следующий wake снова будет ждать 1-2с на инициализацию).
|
||||
const onCancel = () => {
|
||||
vlog('[voice] cancel — пауза VAD')
|
||||
followUpRef.current = false
|
||||
if (followUpTimerRef.current) { clearTimeout(followUpTimerRef.current); followUpTimerRef.current = null }
|
||||
try { vadRef.current?.pause?.() } catch {}
|
||||
busyRef.current = false
|
||||
try { wakeRef.current?.resume?.() } catch {}
|
||||
@@ -54,8 +58,36 @@ export default function VoiceController() {
|
||||
}
|
||||
window.addEventListener('voice-cancel', onCancel)
|
||||
|
||||
const onFollowUp = () => {
|
||||
if (!wakeRef.current) return // ассистент выключен
|
||||
vlog('[voice] follow-up window открыто (8s)')
|
||||
followUpRef.current = true
|
||||
// Очистить предыдущий таймер если был
|
||||
if (followUpTimerRef.current) clearTimeout(followUpTimerRef.current)
|
||||
// Запустить VAD сразу без wake word
|
||||
if (vadRef.current) {
|
||||
try { vadRef.current.start() } catch {}
|
||||
}
|
||||
setState('recording')
|
||||
emitLocal('wake', AGENT)
|
||||
// Через 8 секунд — если никто не заговорил — вернуться к wake
|
||||
followUpTimerRef.current = setTimeout(() => {
|
||||
if (followUpRef.current) {
|
||||
followUpRef.current = false
|
||||
vlog('[voice] follow-up timeout — возврат к wake word')
|
||||
try { vadRef.current?.pause?.() } catch {}
|
||||
try { wakeRef.current?.resume?.() } catch {}
|
||||
setState('listening')
|
||||
emitLocal('idle', AGENT)
|
||||
}
|
||||
}, 8000)
|
||||
}
|
||||
window.addEventListener('voice-follow-up', onFollowUp)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('voice-cancel', onCancel)
|
||||
window.removeEventListener('voice-follow-up', onFollowUp)
|
||||
if (followUpTimerRef.current) clearTimeout(followUpTimerRef.current)
|
||||
try { vadRef.current?.destroy?.() } catch {}
|
||||
try { wakeRef.current?.stop?.() } catch {}
|
||||
vadRef.current = null
|
||||
@@ -96,12 +128,19 @@ export default function VoiceController() {
|
||||
emitLocal('error', AGENT, 'Не получилось')
|
||||
} finally {
|
||||
busyRef.current = false
|
||||
// VAD на паузу — переиспользуем при следующем wake (без re-init).
|
||||
// Если активен follow-up режим — НЕ переключаемся на wake, ответ сам откроет новое окно
|
||||
if (!followUpRef.current) {
|
||||
try { vadRef.current?.pause?.() } catch {}
|
||||
// Wake возобновляем — снова слушаем фоном.
|
||||
try { wakeRef.current?.resume?.() } catch {}
|
||||
setState((s) => (s === 'busy' ? 'listening' : s))
|
||||
}
|
||||
// Сбросить follow-up флаг — новое окно откроется после TTS
|
||||
followUpRef.current = false
|
||||
if (followUpTimerRef.current) {
|
||||
clearTimeout(followUpTimerRef.current)
|
||||
followUpTimerRef.current = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Однократная инициализация VAD. Создаётся в paused-состоянии и переиспользуется
|
||||
|
||||
@@ -172,7 +172,11 @@ export default function VoiceOverlay() {
|
||||
setText(evt.text || '')
|
||||
clearDismiss()
|
||||
if (evt.text) {
|
||||
playTTS(evt.text, currentAgent, () => scheduleDismiss(4000))
|
||||
playTTS(evt.text, currentAgent, () => {
|
||||
// Сигнализируем VoiceController что можно слушать follow-up
|
||||
window.dispatchEvent(new CustomEvent('voice-follow-up'))
|
||||
scheduleDismiss(9000)
|
||||
})
|
||||
} else {
|
||||
scheduleDismiss(4000)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user