import os import sys from .config import GATEWAY_URL, AGENT, FOLLOWUP_TIMEOUT, log from .audio import record, record_with_timeout from .tts import play_activation_sound, speak, stop_speaking from .llm import ask_agent_stream, Conversation, is_reset_command # Персистентные сессии — одна на день для каждого агента _sessions: dict[str, Conversation] = {} def _get_session(agent_id: str) -> Conversation: """Возвращает текущую сессию, создаёт новую если день сменился""" conv = _sessions.get(agent_id) if conv is None or conv.is_expired(): conv = Conversation(agent_id=agent_id) _sessions[agent_id] = conv print(f"🆕 Новая сессия для {agent_id}") return conv def _handle_reset(text: str, agent_id: str) -> bool: """Проверяет команду сброса. Возвращает True если сброс произошёл.""" if is_reset_command(text): _sessions[agent_id] = Conversation(agent_id=agent_id) msg = "Начинаю новую сессию." print(f"🔄 {msg}") speak(msg, agent_id) return True return False def run_with_enter(): print("\n🦞 Cosmo Satellite запущен (режим: Enter для активации)") print(f" Gateway : {GATEWAY_URL}") print(f" Агент : {AGENT}") print("\nНажми Enter → говори → получи ответ. Ctrl+C для выхода.\n") while True: try: input("⏎ Нажми Enter и говори...") stop_speaking() # barge-in: прервать если ещё говорит play_activation_sound() conv = _get_session("cosmo") while True: text = record() if not text: print("⚠️ Ничего не распознано") break print(f"📝 Ты: {text}") if _handle_reset(text, "cosmo"): conv = _get_session("cosmo") break response = ask_agent_stream(text, conv=conv) print(f"🤖 Cosmo: {response}\n") print(f"👂 Слушаю продолжение ({int(FOLLOWUP_TIMEOUT)} сек)...") followup = record_with_timeout(timeout=FOLLOWUP_TIMEOUT) if not followup: print("😴 Нет продолжения, жду активации...\n") break text = followup except KeyboardInterrupt: print("\n👋 Выход") break except Exception as e: log.exception("Непредвиденная ошибка в цикле Enter") print(f"⚠️ Ошибка: {e} — продолжаю работу...\n") def run_with_porcupine(): """Режим продакшн — два wake word через Porcupine (для Pi)""" import pvporcupine import struct from .config import AGENTS porcupine_key = os.getenv("PORCUPINE_KEY") wake_word_cosmo = os.getenv("WAKE_WORD_COSMO") wake_word_lusya = os.getenv("WAKE_WORD_LUSYA") if not porcupine_key: print("❌ PORCUPINE_KEY не задан в .env") sys.exit(1) keyword_paths = [] wake_word_map = [] if wake_word_cosmo: keyword_paths.append(wake_word_cosmo) wake_word_map.append("cosmo") if wake_word_lusya: keyword_paths.append(wake_word_lusya) wake_word_map.append("lusya") if not keyword_paths: print("❌ WAKE_WORD_COSMO или WAKE_WORD_LUSYA не заданы в .env") sys.exit(1) import pyaudio porcupine = pvporcupine.create( access_key=porcupine_key, keyword_paths=keyword_paths, ) audio = pyaudio.PyAudio() stream = audio.open( rate=porcupine.sample_rate, channels=1, format=pyaudio.paInt16, input=True, frames_per_buffer=porcupine.frame_length, ) print("\n🦞 Cosmo Satellite запущен (режим: wake word)") for agent_id in wake_word_map: cfg = AGENTS[agent_id] print(f" {cfg['name']:6s} : {cfg['gateway_url']} → {cfg['agent']}") print(f"\nСкажи 'Космо' или 'Люся'...\n") try: while True: try: pcm = stream.read(porcupine.frame_length) pcm = struct.unpack_from("h" * porcupine.frame_length, pcm) keyword_index = porcupine.process(pcm) if keyword_index >= 0: agent_id = wake_word_map[keyword_index] agent_name = AGENTS[agent_id]["name"] stop_speaking() # barge-in: прервать если ещё говорит print(f"✅ Услышал '{agent_name}'!") play_activation_sound() conv = _get_session(agent_id) text = record() if not text: continue print(f"📝 Ты → {agent_name}: {text}") if _handle_reset(text, agent_id): continue response = ask_agent_stream(text, conv=conv, agent_id=agent_id) print(f"🤖 {agent_name}: {response}\n") except KeyboardInterrupt: raise except Exception as e: log.exception("Непредвиденная ошибка в цикле Porcupine") print(f"⚠️ Ошибка: {e} — продолжаю слушать...\n") except KeyboardInterrupt: print("\n👋 Выход") finally: stream.stop_stream() audio.terminate() porcupine.delete()