diff --git a/python_bridge/chat_audio_bridge.py b/python_bridge/chat_audio_bridge.py index d720802..bbfc4b3 100755 --- a/python_bridge/chat_audio_bridge.py +++ b/python_bridge/chat_audio_bridge.py @@ -650,9 +650,12 @@ Erst dann starten die automatischen TICKs mit Bildern!""" if len(speech_text) > 200: console.print(f"[dim]...({len(speech_text)} Zeichen)[/dim]") + # Mit "Claude sagt:" Prefix vorlesen (analog zu "Stefan sagt:") + tts_text = f"Claude sagt: {speech_text}" + # Vorlesen - logger.info(f"TTS: Spreche {len(speech_text)} Zeichen...") - self.tts.speak(speech_text) + logger.info(f"TTS: Spreche {len(tts_text)} Zeichen...") + self.tts.speak(tts_text) self.stats.messages_spoken += 1 logger.debug("TTS: Sprechen beendet") else: diff --git a/python_bridge/chat_web_interface.py b/python_bridge/chat_web_interface.py index 7b2f2f3..0318bec 100644 --- a/python_bridge/chat_web_interface.py +++ b/python_bridge/chat_web_interface.py @@ -533,56 +533,118 @@ class ClaudeChatInterface: # Versuche verschiedene Selektoren elements = [] - # Methode 1: Nach data-is-streaming Attribut - try: - elements = self.driver.find_elements( - By.CSS_SELECTOR, - "div[data-is-streaming='false']" - ) - except: - pass + # Claude.ai spezifische Selektoren (2024/2025) + message_selectors = [ + # Neuere Claude.ai Versionen + "[data-testid='user-message']", + "[data-testid='assistant-message']", + # Ältere Varianten + "div[data-is-streaming='false']", + # Container für Nachrichten-Inhalt + ".font-claude-message", + ".prose", + # Generische Fallbacks + "div[class*='message']", + "div[class*='Message']", + ] - # Methode 2: Generischer Message-Selektor + for selector in message_selectors: + try: + found = self.driver.find_elements(By.CSS_SELECTOR, selector) + if found: + logger.debug(f"Selektor '{selector}' fand {len(found)} Elemente") + if not elements or len(found) > len(elements): + elements = found + except Exception as e: + logger.debug(f"Selektor '{selector}' fehlgeschlagen: {e}") + + logger.debug(f"Gesamt gefundene Nachrichten-Elemente: {len(elements)}") + + # Wenn immer noch nichts gefunden, versuche via JavaScript if not elements: try: - elements = self.driver.find_elements( - By.CSS_SELECTOR, - self.SELECTORS["message_any"] - ) - except: - pass + elements = self.driver.execute_script(""" + // Suche nach Nachrichten-Containern + const msgs = []; + + // Methode 1: data-testid + document.querySelectorAll('[data-testid*="message"]').forEach(e => msgs.push(e)); + + // Methode 2: Prose-Container (Markdown-Inhalt) + if (msgs.length === 0) { + document.querySelectorAll('.prose').forEach(e => msgs.push(e)); + } + + // Methode 3: Alle großen Text-Container im Hauptbereich + if (msgs.length === 0) { + document.querySelectorAll('main div').forEach(e => { + if (e.innerText && e.innerText.length > 50 && e.children.length < 10) { + msgs.push(e); + } + }); + } + + return msgs; + """) or [] + logger.debug(f"JavaScript fand {len(elements)} Nachrichten") + except Exception as e: + logger.debug(f"JavaScript-Suche fehlgeschlagen: {e}") for i, elem in enumerate(elements): try: text = elem.text.strip() - if not text: + if not text or len(text) < 5: continue - # Bestimme ob Human oder Assistant - # Mehrere Methoden probieren: + # Bestimme ob Human oder Assistant mit mehreren Methoden class_name = elem.get_attribute("class") or "" + data_testid = elem.get_attribute("data-testid") or "" data_role = elem.get_attribute("data-role") or "" - outer_html = "" + + # Prüfe Parent-Elemente auf Hinweise + parent_hints = "" try: - # Prüfe Parent-Element auf Hinweise parent = elem.find_element(By.XPATH, "..") parent_class = parent.get_attribute("class") or "" - outer_html = parent_class + parent_testid = parent.get_attribute("data-testid") or "" + grandparent = parent.find_element(By.XPATH, "..") + grandparent_class = grandparent.get_attribute("class") or "" + parent_hints = f"{parent_class} {parent_testid} {grandparent_class}" except: pass # Kombiniere alle Hinweise - all_hints = (class_name + " " + data_role + " " + outer_html).lower() + all_hints = (class_name + " " + data_testid + " " + data_role + " " + parent_hints).lower() + + # Prüfe explizit auf "user" oder "human" für User-Nachrichten + is_human = ( + "user" in all_hints or + "human" in all_hints or + "user-message" in data_testid + ) is_assistant = ( "assistant" in all_hints or "claude" in all_hints or "ai-message" in all_hints or - "response" in all_hints or - # Claude.ai spezifisch: Nachrichten ohne "human" sind von Claude - ("message" in all_hints and "human" not in all_hints and "user" not in all_hints) + "assistant-message" in data_testid or + "response" in all_hints ) + # Wenn weder user noch assistant explizit, nutze Heuristik + if not is_human and not is_assistant: + # Prüfe ob Text typische User-Marker hat + if text.startswith("[TICK]") or text.startswith("[START]") or "Stefan sagt:" in text: + is_human = True + else: + # Fallback: Nachrichten ohne User-Marker sind von Claude + is_assistant = True + + # Final: is_assistant ist True wenn nicht explizit Human + final_is_assistant = is_assistant and not is_human + + logger.debug(f"Nachricht {i}: is_human={is_human}, is_assistant={is_assistant}, final={final_is_assistant}, hints='{all_hints[:80]}...', text='{text[:40]}...'") + # Generiere ID msg_id = elem.get_attribute("data-message-id") if not msg_id: @@ -591,7 +653,7 @@ class ClaudeChatInterface: messages.append(ChatMessage( id=msg_id, text=text, - is_from_assistant=is_assistant, + is_from_assistant=final_is_assistant, timestamp=time.time() )) @@ -599,6 +661,8 @@ class ClaudeChatInterface: logger.debug(f"Fehler bei Nachricht {i}: {e}") continue + logger.debug(f"Parsed {len(messages)} Nachrichten, davon {sum(1 for m in messages if m.is_from_assistant)} von Claude") + except Exception as e: logger.error(f"Fehler beim Lesen der Nachrichten: {e}")