"""Тест М5.3: свободный режим с ПРОБЕЛОМ-тумблером диалога. Запуск: python modules/assistant/test_free.py python modules/assistant/test_free.py --silence 1.5 # короче пауза конца фразы Схема (целевая для отца): ПРОБЕЛ (в консоли: d + Enter) — НАЧАТЬ диалог: куи «Ассистент готов к диалогу» → просто говори, пауза 2 c = фраза закончена → ответ. ПРОБЕЛ в диалоге — ЗАВЕРШИТЬ: куи «Спасибо. До новых встреч». Или скажи «до свидания» — то же самое + запомнит разговор. Вне диалога микрофон заглушен. Выход: q + Enter. """ from __future__ import annotations import argparse import sys import threading from pathlib import Path try: from modules.assistant import Assistant from modules.audio_io import HandsFreeRecorder, Player from modules.brain import get_provider as get_brain from modules.hotkeys import KeyEvents, get_backend from modules.stt import get_provider as get_stt from modules.tts import make_tts except ImportError: sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from modules.assistant import Assistant from modules.audio_io import HandsFreeRecorder, Player from modules.brain import get_provider as get_brain from modules.hotkeys import KeyEvents, get_backend from modules.stt import get_provider as get_stt from modules.tts import make_tts VAD_STATE_RU = { "listening": "👂 слушаю тебя", "speech_start": "🎙 начало фразы", "speech_end": "✅ фраза собрана, обрабатываю", "too_short": "⚠ слишком коротко — пропускаю", "paused": "⏸ не слушаю (сама говорю)", } ASSISTANT_STATE_RU = { "idle": "⏸ ожидание", "listening": "🎙 запись", "thinking": "🧠 думаю…", "speaking": "🔊 говорю", } def main() -> int: p = argparse.ArgumentParser(description="Свободный режим ассистента (VAD)") p.add_argument("--silence", type=float, default=2.0, help="пауза тишины, считающаяся концом фразы (сек)") p.add_argument("--threshold", type=float, default=0.5, help="порог VAD: больше — строже, меньше — чувствительнее") p.add_argument("--min-speech", type=float, default=0.3, help="минимальная доля голоса во фразе, сек") p.add_argument("--stt-model", default="small") args = p.parse_args() print("Загружаю модули…") stt = get_stt("faster-whisper", model_size=args.stt_model) brain = get_brain("openrouter") tts = make_tts() # провайдер/скорость из .env (TTS_PROVIDER, TTS_RATE) player = Player() quit_event = threading.Event() def on_vad_state(state: str) -> None: print(f"\n [микрофон] {VAD_STATE_RU.get(state, state)}") def on_assistant_state(state: str, note: str) -> None: print(f"\n[{ASSISTANT_STATE_RU.get(state, state)}]" + (f" {note}" if note else "")) def on_log(msg: str) -> None: print(f" {msg}") recorder = HandsFreeRecorder( silence_sec=args.silence, speech_threshold=args.threshold, min_speech_sec=args.min_speech, on_state=on_vad_state, on_error=lambda m: print(f" [ОШИБКА] {m}"), ) assistant = Assistant(stt, brain, tts, recorder, player, on_state_change=on_assistant_state, on_log=on_log) print(f"\nУПРАВЛЕНИЕ (голосом, без клавиатуры):") print(f" «{assistant.wake_word.wake_word.capitalize()}» — начать диалог (куи «готова к диалогу»)") print(" «До свидания» — завершить диалог (куи «до новых встреч»)") print(" Вне диалога ассистент слушает тихо и отвечает только на wake-word.") print(" Консоль: s — «Замолчи», r — «Повтори», ПРОБЕЛ — тоже тумблер диалога, q — выход.\n") quit_event = threading.Event() events = KeyEvents( on_talk_down=lambda: None, on_talk_up=lambda: None, on_stop=assistant.on_stop, on_repeat=assistant.on_repeat, on_quit=lambda: quit_event.set(), on_dialog_toggle=assistant.on_dialog_toggle, ) backend = get_backend("console", events) backend.start() recorder.start() # микрофон открыт, но заглушен до начала диалога try: while not quit_event.wait(timeout=0.2): pass except KeyboardInterrupt: print("\n(Ctrl+C)") finally: backend.stop() recorder.stop() try: player.stop() except Exception: pass print("Выход.") return 0 if __name__ == "__main__": sys.exit(main())