""" Hermes Voice Client Wake Word → STT → Screenshot → Hermes (MC2) → TTS Läuft als System-Tray-App im Hintergrund. Aktivierung: "Hey Jarvis" (konfigurierbar in config.py) """ import threading import sys from PIL import Image, ImageDraw import pystray from audio_input import wait_for_wake_word, record_speech, transcribe from audio_output import speak, play_ding, stop_speaking from screen_capture import capture_screen from ai_client import ask from config import SCREENSHOT_ON_QUERY # ── Status ─────────────────────────────────────────────────────────────── class State: IDLE = "idle" # wartet auf Wake Word LISTENING = "listen" # nimmt Sprache auf THINKING = "think" # wartet auf MC2-Antwort SPEAKING = "speak" # gibt Antwort aus _state = State.IDLE _stop_event = threading.Event() _tray_icon: pystray.Icon | None = None # ── Tray Icon ──────────────────────────────────────────────────────────── COLORS = { State.IDLE: "#22c55e", # grün State.LISTENING: "#eab308", # gelb State.THINKING: "#3b82f6", # blau State.SPEAKING: "#a855f7", # lila } def _make_icon(color: str) -> Image.Image: img = Image.new("RGBA", (64, 64), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) draw.ellipse([4, 4, 60, 60], fill=color) return img def _set_state(state: str): global _state _state = state if _tray_icon: _tray_icon.icon = _make_icon(COLORS[state]) labels = { State.IDLE: "Hermes — bereit (Hey Jarvis)", State.LISTENING: "Hermes — hört zu…", State.THINKING: "Hermes — denkt…", State.SPEAKING: "Hermes — spricht", } _tray_icon.title = labels[state] # ── Hauptloop ──────────────────────────────────────────────────────────── def _voice_loop(): print("[Hermes] Bereit. Sage 'Hey Jarvis' zum Aktivieren.") while not _stop_event.is_set(): _set_state(State.IDLE) # 1. Auf Wake Word warten detected = wait_for_wake_word(_stop_event) if not detected: break # 2. Aktivierungs-Ding + Aufnahme starten play_ding() _set_state(State.LISTENING) audio = record_speech() # 3. Transkribieren text = transcribe(audio) if not text: continue print(f"[STT] {text!r}") # 4. Screenshot machen screenshot = capture_screen() if SCREENSHOT_ON_QUERY else None # 5. MC2 fragen _set_state(State.THINKING) response = ask(text, screenshot) print(f"[Hermes] {response!r}") # 6. Antwort sprechen _set_state(State.SPEAKING) speak(response) print("[Hermes] Beendet.") # ── System Tray ────────────────────────────────────────────────────────── def _on_quit(icon, item): _stop_event.set() stop_speaking() icon.stop() def _on_mute(icon, item): stop_speaking() def main(): global _tray_icon loop_thread = threading.Thread(target=_voice_loop, daemon=True) loop_thread.start() menu = pystray.Menu( pystray.MenuItem("Hermes Voice", None, enabled=False), pystray.Menu.SEPARATOR, pystray.MenuItem("Sprechen stoppen", _on_mute), pystray.MenuItem("Beenden", _on_quit), ) _tray_icon = pystray.Icon( "hermes-voice", icon=_make_icon(COLORS[State.IDLE]), title="Hermes — bereit (Hey Jarvis)", menu=menu, ) _tray_icon.run() if __name__ == "__main__": main()