nachricjhten parsen debug
This commit is contained in:
parent
d09feeffd5
commit
aab93958f8
|
|
@ -109,9 +109,13 @@ class ClaudesEyesAudioBridge:
|
||||||
self.last_assistant_message_id: Optional[str] = None
|
self.last_assistant_message_id: Optional[str] = None
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
# Ready-Flag: Heartbeat wartet bis Claude [READY] gesendet hat
|
# Ready-Flag: Heartbeat UND TTS warten bis Claude [READY] gesendet hat
|
||||||
self._claude_ready = threading.Event()
|
self._claude_ready = threading.Event()
|
||||||
|
|
||||||
|
# TTS-Active-Flag: TTS wird erst aktiviert nachdem [READY] empfangen UND
|
||||||
|
# die letzte Nachricht-ID gesetzt wurde (um alte Nachrichten zu ignorieren)
|
||||||
|
self._tts_active = threading.Event()
|
||||||
|
|
||||||
# Stefan-Buffer: Sammelt Spracheingaben während Claude tippt
|
# Stefan-Buffer: Sammelt Spracheingaben während Claude tippt
|
||||||
self._stefan_buffer: list = []
|
self._stefan_buffer: list = []
|
||||||
self._stefan_buffer_lock = threading.Lock()
|
self._stefan_buffer_lock = threading.Lock()
|
||||||
|
|
@ -467,8 +471,23 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
# Warte auf [READY] - KEIN Timeout-Fallback!
|
# Warte auf [READY] - KEIN Timeout-Fallback!
|
||||||
# Heartbeat startet NUR wenn Claude wirklich [READY] sendet
|
# Heartbeat startet NUR wenn Claude wirklich [READY] sendet
|
||||||
if self.chat.wait_for_ready_signal(timeout=300): # 5 Minuten max
|
if self.chat.wait_for_ready_signal(timeout=300): # 5 Minuten max
|
||||||
# Signal für Heartbeat dass es losgehen kann
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
# WICHTIG: Markiere die letzte Nachricht BEVOR TTS aktiviert wird
|
||||||
|
# So werden alle bisherigen Nachrichten (inkl. [READY]) ignoriert
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
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]}...")
|
||||||
|
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")
|
||||||
|
|
||||||
|
# Jetzt TTS und Heartbeat aktivieren
|
||||||
|
self._tts_active.set()
|
||||||
self._claude_ready.set()
|
self._claude_ready.set()
|
||||||
|
console.print("[green]TTS aktiviert - ab jetzt werden neue Nachrichten vorgelesen[/green]")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# KEIN Fallback - Heartbeat bleibt blockiert
|
# KEIN Fallback - Heartbeat bleibt blockiert
|
||||||
|
|
@ -619,12 +638,21 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
|
|
||||||
Filtert dabei [BEFEHLE] und technische Teile raus,
|
Filtert dabei [BEFEHLE] und technische Teile raus,
|
||||||
sodass nur der "menschliche" Text gesprochen wird.
|
sodass nur der "menschliche" Text gesprochen wird.
|
||||||
|
|
||||||
|
WICHTIG: Wartet auf _tts_active bevor Nachrichten vorgelesen werden!
|
||||||
|
So werden Init-Nachrichten und UI-Texte ignoriert.
|
||||||
"""
|
"""
|
||||||
if not self.tts:
|
if not self.tts:
|
||||||
logger.warning("TTS nicht verfügbar")
|
logger.warning("TTS nicht verfügbar")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info("TTS-Loop gestartet")
|
logger.info("TTS-Loop gestartet, warte auf READY...")
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
# WICHTIG: Warte bis TTS aktiviert wird (nach [READY])
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
self._tts_active.wait()
|
||||||
|
logger.info("TTS aktiviert - beginne mit Vorlesen neuer Nachrichten")
|
||||||
|
|
||||||
while self.running:
|
while self.running:
|
||||||
try:
|
try:
|
||||||
|
|
@ -818,8 +846,9 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
"""Startet einen neuen Chat und sendet die Instruktionen erneut"""
|
"""Startet einen neuen Chat und sendet die Instruktionen erneut"""
|
||||||
console.print("\n[yellow]Starte neuen Chat...[/yellow]")
|
console.print("\n[yellow]Starte neuen Chat...[/yellow]")
|
||||||
|
|
||||||
# Heartbeat pausieren
|
# Heartbeat UND TTS pausieren
|
||||||
self._claude_ready.clear()
|
self._claude_ready.clear()
|
||||||
|
self._tts_active.clear()
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
new_url, old_chat_id = self.chat.start_new_chat()
|
new_url, old_chat_id = self.chat.start_new_chat()
|
||||||
|
|
|
||||||
|
|
@ -526,142 +526,122 @@ class ClaudeChatInterface:
|
||||||
return new_messages
|
return new_messages
|
||||||
|
|
||||||
def _get_all_messages(self) -> List[ChatMessage]:
|
def _get_all_messages(self) -> List[ChatMessage]:
|
||||||
"""Holt alle Nachrichten aus dem Chat"""
|
"""
|
||||||
|
Holt alle Nachrichten aus dem Chat.
|
||||||
|
|
||||||
|
Claude.ai verwendet unterschiedliche data-testid für User und Assistant:
|
||||||
|
- data-testid="user-message" für User
|
||||||
|
- Für Claude: Wir suchen nach Nachrichten die NICHT user-message sind
|
||||||
|
"""
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Versuche verschiedene Selektoren
|
# ═══════════════════════════════════════════════════════════════
|
||||||
elements = []
|
# STRATEGIE: Hole User UND Assistant Nachrichten SEPARAT
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
# Claude.ai spezifische Selektoren (2024/2025)
|
# 1. User-Nachrichten (haben data-testid="user-message")
|
||||||
message_selectors = [
|
user_elements = []
|
||||||
# Neuere Claude.ai Versionen
|
try:
|
||||||
"[data-testid='user-message']",
|
user_elements = self.driver.find_elements(
|
||||||
"[data-testid='assistant-message']",
|
By.CSS_SELECTOR,
|
||||||
# Ältere Varianten
|
"[data-testid='user-message']"
|
||||||
"div[data-is-streaming='false']",
|
)
|
||||||
# Container für Nachrichten-Inhalt
|
logger.debug(f"User-Nachrichten gefunden: {len(user_elements)}")
|
||||||
".font-claude-message",
|
except Exception as e:
|
||||||
".prose",
|
logger.debug(f"User-Nachrichten Suche fehlgeschlagen: {e}")
|
||||||
# Generische Fallbacks
|
|
||||||
"div[class*='message']",
|
|
||||||
"div[class*='Message']",
|
|
||||||
]
|
|
||||||
|
|
||||||
for selector in message_selectors:
|
# 2. Claude-Nachrichten suchen
|
||||||
|
# Claude.ai nutzt verschiedene Klassen, aber NICHT user-message
|
||||||
|
claude_elements = []
|
||||||
|
|
||||||
|
# Methode A: font-claude-message Klasse
|
||||||
|
try:
|
||||||
|
found = self.driver.find_elements(
|
||||||
|
By.CSS_SELECTOR,
|
||||||
|
"[class*='font-claude-message']"
|
||||||
|
)
|
||||||
|
if found:
|
||||||
|
claude_elements = found
|
||||||
|
logger.debug(f"Claude-Nachrichten via font-claude-message: {len(found)}")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Methode B: Prose-Container die NICHT in user-message sind
|
||||||
|
if not claude_elements:
|
||||||
try:
|
try:
|
||||||
found = self.driver.find_elements(By.CSS_SELECTOR, selector)
|
# Alle prose Elemente die nicht innerhalb von user-message sind
|
||||||
if found:
|
found = self.driver.execute_script("""
|
||||||
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.execute_script("""
|
|
||||||
// Suche nach Nachrichten-Containern
|
|
||||||
const msgs = [];
|
const msgs = [];
|
||||||
|
document.querySelectorAll('.prose').forEach(e => {
|
||||||
// Methode 1: data-testid
|
// Prüfe ob dieses Element NICHT in einem user-message Container ist
|
||||||
document.querySelectorAll('[data-testid*="message"]').forEach(e => msgs.push(e));
|
let parent = e;
|
||||||
|
let isUserMessage = false;
|
||||||
// Methode 2: Prose-Container (Markdown-Inhalt)
|
while (parent && parent !== document.body) {
|
||||||
if (msgs.length === 0) {
|
if (parent.getAttribute('data-testid') === 'user-message') {
|
||||||
document.querySelectorAll('.prose').forEach(e => msgs.push(e));
|
isUserMessage = true;
|
||||||
}
|
break;
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
});
|
parent = parent.parentElement;
|
||||||
}
|
}
|
||||||
|
if (!isUserMessage) {
|
||||||
|
msgs.push(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
return msgs;
|
return msgs;
|
||||||
""") or []
|
""") or []
|
||||||
logger.debug(f"JavaScript fand {len(elements)} Nachrichten")
|
if found:
|
||||||
|
claude_elements = found
|
||||||
|
logger.debug(f"Claude-Nachrichten via prose (nicht-user): {len(found)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"JavaScript-Suche fehlgeschlagen: {e}")
|
logger.debug(f"Prose-Suche fehlgeschlagen: {e}")
|
||||||
|
|
||||||
for i, elem in enumerate(elements):
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# Nachrichten verarbeiten
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
# User-Nachrichten hinzufügen
|
||||||
|
for i, elem in enumerate(user_elements):
|
||||||
|
try:
|
||||||
|
text = elem.text.strip()
|
||||||
|
if not text or len(text) < 3:
|
||||||
|
continue
|
||||||
|
|
||||||
|
msg_id = elem.get_attribute("data-message-id")
|
||||||
|
if not msg_id:
|
||||||
|
msg_id = f"user_{i}_{hash(text[:100])}"
|
||||||
|
|
||||||
|
messages.append(ChatMessage(
|
||||||
|
id=msg_id,
|
||||||
|
text=text,
|
||||||
|
is_from_assistant=False,
|
||||||
|
timestamp=time.time()
|
||||||
|
))
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Fehler bei User-Nachricht {i}: {e}")
|
||||||
|
|
||||||
|
# Claude-Nachrichten hinzufügen
|
||||||
|
for i, elem in enumerate(claude_elements):
|
||||||
try:
|
try:
|
||||||
text = elem.text.strip()
|
text = elem.text.strip()
|
||||||
if not text or len(text) < 5:
|
if not text or len(text) < 5:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 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 ""
|
|
||||||
|
|
||||||
# Prüfe Parent-Elemente auf Hinweise
|
|
||||||
parent_hints = ""
|
|
||||||
try:
|
|
||||||
parent = elem.find_element(By.XPATH, "..")
|
|
||||||
parent_class = parent.get_attribute("class") or ""
|
|
||||||
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_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
|
|
||||||
"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")
|
msg_id = elem.get_attribute("data-message-id")
|
||||||
if not msg_id:
|
if not msg_id:
|
||||||
msg_id = f"msg_{i}_{hash(text[:100])}"
|
msg_id = f"claude_{i}_{hash(text[:100])}"
|
||||||
|
|
||||||
messages.append(ChatMessage(
|
messages.append(ChatMessage(
|
||||||
id=msg_id,
|
id=msg_id,
|
||||||
text=text,
|
text=text,
|
||||||
is_from_assistant=final_is_assistant,
|
is_from_assistant=True,
|
||||||
timestamp=time.time()
|
timestamp=time.time()
|
||||||
))
|
))
|
||||||
|
logger.debug(f"Claude-Nachricht {i}: '{text[:50]}...'")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Fehler bei Nachricht {i}: {e}")
|
logger.debug(f"Fehler bei Claude-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")
|
logger.debug(f"Gesamt: {len(messages)} Nachrichten, davon {sum(1 for m in messages if m.is_from_assistant)} von Claude")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Lesen der Nachrichten: {e}")
|
logger.error(f"Fehler beim Lesen der Nachrichten: {e}")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue