""" Smart Speaker - Main Application Голосовой ассистент с wake word detection, STT, AI и TTS. Flow: 1. Wait for wake word ("Alexandr") 2. Listen to user speech (STT) 3. Send query to AI (Perplexity) 4. Clean response from markdown 5. Speak response (TTS) 6. Loop back to step 1 """ import signal import sys from collections import deque from wakeword import wait_for_wakeword, cleanup as cleanup_wakeword, check_wakeword_once from stt import listen, cleanup as cleanup_stt, get_recognizer from ai import ask_ai from cleaner import clean_response from tts import speak, initialize as init_tts from sound_level import set_volume, parse_volume_text def signal_handler(sig, frame): """Handle Ctrl+C gracefully.""" print("\n\n👋 Завершение работы...") cleanup_wakeword() cleanup_stt() sys.exit(0) def main(): """Main application loop.""" print("=" * 50) print("🔊 УМНАЯ КОЛОНКА") print("=" * 50) print("Скажите 'Alexandr' для активации") print("Нажмите Ctrl+C для выхода") print("=" * 50) print() # Setup signal handler for graceful exit signal.signal(signal.SIGINT, signal_handler) # Pre-initialize models (takes a few seconds) print("⏳ Инициализация моделей...") get_recognizer().initialize() # Initialize STT model first init_tts() # Then initialize TTS model print() # Initialize chat history (last 10 exchanges = 20 messages) chat_history = deque(maxlen=20) # Main loop skip_wakeword = False while True: try: # Step 1: Wait for wake word or Follow-up listen if not skip_wakeword: wait_for_wakeword() # Standard listen after activation user_text = listen(timeout_seconds=7.0) else: # Follow-up listen (wait 2.0s for start, then listen long) print("👂 Слушаю продолжение диалога...") user_text = listen(timeout_seconds=20.0, detection_timeout=2.0) if not user_text: # User didn't continue conversation, go back to sleep skip_wakeword = False continue # Reset flag for now (will be set to True if we speak successfully) skip_wakeword = False # Step 2: Check if speech was recognized if not user_text: speak("Извините, я вас не расслышал. Попробуйте ещё раз.") continue # Check for stop commands user_text_lower = user_text.lower().strip() if user_text_lower in ["стоп", "александр", "стоп александр"]: print("_" * 50) print("💤 Жду 'Alexandr' для активации...") skip_wakeword = False continue # Check for volume command if user_text.lower().startswith("громкость"): try: # Remove "громкость" prefix and strip whitespace vol_str = user_text.lower().replace("громкость", "", 1).strip() # Try to parse the number level = parse_volume_text(vol_str) if level is not None: if set_volume(level): speak(f"Громкость установлена на {level}") else: speak("Не удалось установить громкость.") else: speak( "Я не понял число громкости. Скажите число от одного до десяти." ) continue except Exception as e: print(f"❌ Ошибка громкости: {e}") speak("Не удалось изменить громкость.") continue # Step 3: Send to AI # Add user message to history chat_history.append({"role": "user", "content": user_text}) # Get response using history ai_response = ask_ai(list(chat_history)) # Add AI response to history chat_history.append({"role": "assistant", "content": ai_response}) # Step 4: Clean response clean_text = clean_response(ai_response) # Step 5: Speak response (with wake word interrupt support) completed = speak(clean_text, check_interrupt=check_wakeword_once) # Enable follow-up mode for next iteration skip_wakeword = True # If interrupted by wake word, we still want to skip_wakeword (which is set above) # but we can print a message if not completed: print("⏹️ Ответ прерван - слушаю следующий вопрос") continue print() print("-" * 30) print() # Step 6: Loop continues with skip_wakeword=True except KeyboardInterrupt: signal_handler(None, None) except Exception as e: print(f"❌ Ошибка: {e}") speak("Произошла ошибка. Попробуйте ещё раз.") if __name__ == "__main__": main()