import os import sys from .config import GATEWAY_URL, AGENT, log from .audio import record from .tts import 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 _conversation_loop(agent_id: str, agent_name: str = "Cosmo"): """Основной цикл диалога — слушает и отвечает пока пользователь говорит. Выходит когда в течение MAX_DURATION не было речи.""" conv = _get_session(agent_id) while True: text = record() if not text: print(f"😴 Тишина, жду активации...\n") return print(f"📝 Ты → {agent_name}: {text}") if _handle_reset(text, agent_id): conv = _get_session(agent_id) continue response = ask_agent_stream(text, conv=conv, agent_id=agent_id) print(f"🤖 {agent_name}: {response}\n") # после ответа — следующая итерация с новым record() # record() сам гасит эхо через ECHO_WARMUP 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 _conversation_loop("cosmo", "Cosmo") 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}'!") # отпускаем микрофон на время диалога stream.stop_stream() _conversation_loop(agent_id, agent_name) stream.start_stream() 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()