Files
2026-09-11 19:47:15 +03:00

128 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Тест М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())