From d9b1e1bed48de6641be947ee6bca915159a98037 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 27 Dec 2025 18:54:24 +0100 Subject: [PATCH] bridge liest nur nachrichten vor wo claude sagt: drin vorkommt und merkt sich welche schon vorgelesen wurden beim bridge neustart --- python_bridge/chat_audio_bridge.py | 97 +++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 9 deletions(-) diff --git a/python_bridge/chat_audio_bridge.py b/python_bridge/chat_audio_bridge.py index 3b082af..f803d42 100755 --- a/python_bridge/chat_audio_bridge.py +++ b/python_bridge/chat_audio_bridge.py @@ -40,6 +40,7 @@ try: except: pass # Kein ALSA - kein Problem +import json import yaml import click from rich.console import Console @@ -107,6 +108,7 @@ class ClaudesEyesAudioBridge: # State self.last_assistant_message_id: Optional[str] = None + self._current_chat_id: Optional[str] = None # Aktuelle Chat-ID für TTS-State self._lock = threading.Lock() # Ready-Flag: Heartbeat UND TTS warten bis Claude [READY] gesendet hat @@ -150,6 +152,9 @@ class ClaudesEyesAudioBridge: # Merke den tatsächlichen Pfad für späteres Speichern self._actual_config_path = path + # Pfad für TTS-State (vorgelesene Nachrichten) + self._tts_state_path = path.parent / ".tts_state.json" + if not path.exists(): logger.error(f"Config nicht gefunden: {path}") sys.exit(1) @@ -157,6 +162,57 @@ class ClaudesEyesAudioBridge: with open(path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) + def _load_tts_state(self, chat_id: str) -> Optional[str]: + """ + Lädt die zuletzt vorgelesene Nachricht-ID für einen Chat. + + Args: + chat_id: Die Chat-ID (aus der URL) + + Returns: + Die zuletzt vorgelesene Nachricht-ID oder None + """ + try: + if self._tts_state_path.exists(): + with open(self._tts_state_path, 'r', encoding='utf-8') as f: + state = json.load(f) + last_id = state.get(chat_id) + if last_id: + logger.info(f"TTS-State geladen für Chat {chat_id[:20]}...: {last_id[:30]}...") + return last_id + except Exception as e: + logger.debug(f"Konnte TTS-State nicht laden: {e}") + return None + + def _save_tts_state(self, chat_id: str, message_id: str): + """ + Speichert die zuletzt vorgelesene Nachricht-ID für einen Chat. + + Args: + chat_id: Die Chat-ID (aus der URL) + message_id: Die Nachricht-ID die zuletzt vorgelesen wurde + """ + try: + # Existierenden State laden oder leeren dict + state = {} + if self._tts_state_path.exists(): + try: + with open(self._tts_state_path, 'r', encoding='utf-8') as f: + state = json.load(f) + except: + pass + + # State aktualisieren + state[chat_id] = message_id + + # Speichern + with open(self._tts_state_path, 'w', encoding='utf-8') as f: + json.dump(state, f, indent=2) + + logger.debug(f"TTS-State gespeichert: {chat_id[:20]}... -> {message_id[:30]}...") + except Exception as e: + logger.debug(f"Konnte TTS-State nicht speichern: {e}") + def _save_chat_url_to_config(self, new_url: str): """ Speichert die neue Chat-URL in der config.yaml. @@ -472,17 +528,36 @@ Erst dann starten die automatischen TICKs mit Bildern!""" # Heartbeat startet NUR wenn Claude wirklich [READY] sendet if self.chat.wait_for_ready_signal(timeout=300): # 5 Minuten max # ════════════════════════════════════════════════════════════════ - # WICHTIG: Markiere die letzte Nachricht BEVOR TTS aktiviert wird - # So werden alle bisherigen Nachrichten (inkl. [READY]) ignoriert + # Chat-ID ermitteln und gespeicherten TTS-State laden # ════════════════════════════════════════════════════════════════ - last_msg = self.chat.get_last_assistant_message() - if last_msg: - self.last_assistant_message_id = last_msg.id - logger.info(f"TTS startet nach Nachricht: {last_msg.id[:30]}...") + current_url = self.chat.get_current_chat_url() + self._current_chat_id = self.chat.extract_chat_id(current_url) + + if self._current_chat_id: + # Versuche gespeicherten State zu laden + saved_id = self._load_tts_state(self._current_chat_id) + if saved_id: + self.last_assistant_message_id = saved_id + console.print(f"[green]TTS: Setze fort ab gespeicherter Position[/green]") + logger.info(f"TTS setzt fort nach: {saved_id[:30]}...") + else: + # Kein gespeicherter State - starte nach [READY] Nachricht + last_msg = self.chat.get_last_assistant_message() + if last_msg: + self.last_assistant_message_id = last_msg.id + self._save_tts_state(self._current_chat_id, last_msg.id) + logger.info(f"TTS startet nach READY-Nachricht: {last_msg.id[:30]}...") + else: + self.last_assistant_message_id = f"init_{time.time()}" + logger.info("Keine letzte Nachricht gefunden, TTS startet frisch") else: - # Fallback: Generiere eine Dummy-ID die niemals matcht - self.last_assistant_message_id = f"init_{time.time()}" - logger.info("Keine letzte Nachricht gefunden, TTS startet frisch") + # Keine Chat-ID (neuer Chat?) - nutze letzte Nachricht + last_msg = self.chat.get_last_assistant_message() + if last_msg: + self.last_assistant_message_id = last_msg.id + else: + self.last_assistant_message_id = f"init_{time.time()}" + logger.info("Keine Chat-ID, TTS startet frisch") # Jetzt TTS und Heartbeat aktivieren self._tts_active.set() @@ -668,6 +743,10 @@ Erst dann starten die automatischen TICKs mit Bildern!""" if msg.is_from_assistant: self.last_assistant_message_id = msg.id + # State speichern (persistent) + if self._current_chat_id: + self._save_tts_state(self._current_chat_id, msg.id) + # Text für Sprache aufbereiten speech_text = self._clean_for_speech(msg.text) logger.debug(f"TTS: Nach Bereinigung: {len(speech_text) if speech_text else 0} Zeichen")