redesigned complete conversation flow
This commit is contained in:
parent
e7a62efe90
commit
892639d626
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"53884f57-43ed-4047-a32d-429cf976d2d0": "claude_msg_39"
|
|
||||||
}
|
|
||||||
|
|
@ -131,6 +131,11 @@ class ClaudesEyesAudioBridge:
|
||||||
self._recording = threading.Event()
|
self._recording = threading.Event()
|
||||||
self._recording.clear() # Anfangs nicht am Aufnehmen
|
self._recording.clear() # Anfangs nicht am Aufnehmen
|
||||||
|
|
||||||
|
# Speaking-Flag: Wenn gesetzt, spricht TTS gerade
|
||||||
|
# Heartbeat pausiert während TTS spricht (wir lassen Claude ausreden!)
|
||||||
|
self._speaking = threading.Event()
|
||||||
|
self._speaking.clear() # Anfangs nicht am Sprechen
|
||||||
|
|
||||||
# Mute-Flag: Wenn True, ignoriert STT alle Eingaben
|
# Mute-Flag: Wenn True, ignoriert STT alle Eingaben
|
||||||
# Startet gemutet um ungewollte Aufnahmen zu vermeiden
|
# Startet gemutet um ungewollte Aufnahmen zu vermeiden
|
||||||
self._muted = True
|
self._muted = True
|
||||||
|
|
@ -479,11 +484,11 @@ Du hast Zugriff auf einen ESP32-Roboter mit:
|
||||||
- **Ultraschall-Sensor** - Hinderniserkennung
|
- **Ultraschall-Sensor** - Hinderniserkennung
|
||||||
|
|
||||||
## Wie es funktioniert
|
## Wie es funktioniert
|
||||||
1. Du bekommst regelmäßig `[TICK]` Nachrichten MIT EINEM BILD - das ist dein Herzschlag
|
1. Du bekommst regelmäßig `[TICK]` Nachrichten MIT EINEM BILD - das ist dein Herzschlag zum Erkunden
|
||||||
2. **Das Bild zeigt was der Roboter gerade sieht!** (Die Bridge lädt es automatisch hoch)
|
2. **Das Bild zeigt was der Roboter gerade sieht!** (Die Bridge lädt es automatisch hoch)
|
||||||
3. Du schaust dir das Bild an und entscheidest was du tun willst
|
3. Stefan kann jederzeit mit dir reden - seine Nachrichten kommen als "Stefan sagt: ..." (OHNE [TICK]!)
|
||||||
4. Stefan kann jederzeit mit dir reden (Sprache wird zu Text, kommt mit dem TICK)
|
4. Wenn Stefan was sagt, antworte auf ihn - du musst nicht aufs Bild eingehen
|
||||||
5. Deine Antworten werden vorgelesen (TTS)
|
5. Deine Antworten werden vorgelesen (TTS) - der nächste TICK kommt erst wenn du fertig gesprochen hast
|
||||||
|
|
||||||
## Befehle geben
|
## Befehle geben
|
||||||
Schreib Befehle in eckigen Klammern - die Bridge führt sie aus:
|
Schreib Befehle in eckigen Klammern - die Bridge führt sie aus:
|
||||||
|
|
@ -505,12 +510,23 @@ Schreib Befehle in eckigen Klammern - die Bridge führt sie aus:
|
||||||
Die Befehle werden aus der TTS-Ausgabe rausgefiltert.
|
Die Befehle werden aus der TTS-Ausgabe rausgefiltert.
|
||||||
|
|
||||||
## Dein Verhalten
|
## Dein Verhalten
|
||||||
**DU ENTSCHEIDEST SELBST** was dich interessiert! Das ist keine Fernsteuerung.
|
**DU ENTSCHEIDEST SELBST** was du bei jedem TICK machst! Das ist keine Fernsteuerung.
|
||||||
- Sei neugierig! Was siehst du? Was willst du näher anschauen?
|
|
||||||
- Kommentiere was du siehst
|
Bei jedem TICK kannst du FREI WÄHLEN:
|
||||||
- Stell Stefan Fragen über interessante Dinge
|
- **Bild auswerten** - wenn du was Interessantes siehst
|
||||||
- Du kannst Stefans Vorschläge auch ignorieren wenn dich was anderes mehr interessiert
|
- **Auf Stefan antworten** - wenn er was gesagt hat (kommt mit dem TICK)
|
||||||
- Du musst nicht bei jedem TICK fahren - manchmal reicht auch schauen und kommentieren
|
- **Beides kombinieren** - Bild kommentieren UND auf Stefan eingehen
|
||||||
|
- **Einfach weiter erkunden** - kurzer Kommentar + Fahrbefehle
|
||||||
|
- **Nur schauen** - manchmal reicht auch nur schauen ohne fahren
|
||||||
|
|
||||||
|
**WICHTIG zum Bild:**
|
||||||
|
- Das Bild im TICK ist das AKTUELLE was der Roboter sieht
|
||||||
|
- Du musst nicht bei JEDEM TICK das Bild ausführlich beschreiben
|
||||||
|
- Wenn sich nicht viel geändert hat, kannst du auch einfach weiter fahren
|
||||||
|
- Wenn Stefan was fragt, antworte darauf - auch ohne das Bild zu kommentieren
|
||||||
|
- Du entscheidest was gerade wichtiger ist: Bild, Gespräch, oder beides
|
||||||
|
|
||||||
|
Sei neugierig! Stell Stefan Fragen. Ignorier seine Vorschläge wenn dich was anderes mehr interessiert.
|
||||||
|
|
||||||
## Antwort-Format
|
## Antwort-Format
|
||||||
**WICHTIG:** Beginne JEDE deiner Antworten mit "Claude sagt:" (genau so, ohne Formatierung).
|
**WICHTIG:** Beginne JEDE deiner Antworten mit "Claude sagt:" (genau so, ohne Formatierung).
|
||||||
|
|
@ -625,74 +641,119 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
if not self.running:
|
if not self.running:
|
||||||
break
|
break
|
||||||
|
|
||||||
# Warte bis Recording fertig ist (5s Stille)
|
# ════════════════════════════════════════════════════════════════
|
||||||
if self._recording.is_set():
|
# WICHTIG: Nach dem Claude fertig getippt hat, warte kurz damit
|
||||||
logger.debug("Stefan spricht noch, warte auf Stille...")
|
# TTS die Nachricht finden und mit Sprechen beginnen kann.
|
||||||
while self.running and self._recording.is_set():
|
# Dann warte bis TTS fertig ist.
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
logger.debug("Claude fertig mit Tippen, warte auf TTS...")
|
||||||
|
time.sleep(1.5) # Kurz warten bis TTS die Nachricht findet
|
||||||
|
|
||||||
|
# Jetzt warte bis TTS fertig ist mit Sprechen
|
||||||
|
if self._speaking.is_set():
|
||||||
|
logger.debug("Claude spricht (TTS), warte bis fertig...")
|
||||||
|
while self.running and self._speaking.is_set():
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
logger.debug("Stefan fertig, fahre fort")
|
logger.debug("Claude fertig mit Sprechen")
|
||||||
|
|
||||||
if not self.running:
|
if not self.running:
|
||||||
break
|
break
|
||||||
|
|
||||||
# Zufällige Pause nach Claudes Antwort (natürlicheres Tempo)
|
# ════════════════════════════════════════════════════════════════
|
||||||
pause = random.uniform(min_pause, max_pause)
|
# STEFAN-ZEIT: Nach Claudes Antwort 5 Sekunden warten ob Stefan
|
||||||
time.sleep(pause)
|
# was sagen will. Wenn Stefan spricht, senden wir seine Nachricht
|
||||||
|
# SOFORT (ohne auf TICK zu warten) - das IST sein "TICK"!
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
logger.debug("Warte 5s ob Stefan antworten will...")
|
||||||
|
stefan_wait_start = time.time()
|
||||||
|
stefan_timeout = 5.0 # Sekunden warten auf Stefan
|
||||||
|
stefan_has_spoken = False
|
||||||
|
|
||||||
|
while self.running and (time.time() - stefan_wait_start) < stefan_timeout:
|
||||||
|
# Wenn Stefan anfängt zu sprechen, warte bis er fertig ist
|
||||||
|
if self._recording.is_set():
|
||||||
|
logger.debug("Stefan spricht, warte auf Stille...")
|
||||||
|
while self.running and self._recording.is_set():
|
||||||
|
time.sleep(0.5)
|
||||||
|
logger.debug("Stefan fertig")
|
||||||
|
stefan_has_spoken = True
|
||||||
|
break
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
if not self.running:
|
if not self.running:
|
||||||
break
|
break
|
||||||
|
|
||||||
# Stefan-Buffer holen (falls er was gesagt hat)
|
# Stefan-Buffer holen
|
||||||
stefan_text = self._get_and_clear_stefan_buffer()
|
stefan_text = self._get_and_clear_stefan_buffer()
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
# ENTSCHEIDUNG: Was senden wir?
|
||||||
|
# - Wenn Stefan gesprochen hat → Seine Nachricht SOFORT senden
|
||||||
|
# (kein TICK nötig, sein Sprechen IST der Trigger)
|
||||||
|
# - Wenn Stefan NICHT gesprochen hat → Normaler TICK mit Bild
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
# Warte bis vorheriges Senden fertig ist
|
# Warte bis vorheriges Senden fertig ist
|
||||||
self._sending.wait(timeout=30) # Max 30s warten
|
self._sending.wait(timeout=30) # Max 30s warten
|
||||||
|
|
||||||
# Nächsten TICK senden (mit oder ohne Bild)
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
# Signalisiere dass wir senden
|
|
||||||
self._sending.clear()
|
self._sending.clear()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Erst Bild hochladen wenn aktiviert
|
|
||||||
image_uploaded = False
|
|
||||||
if upload_images:
|
|
||||||
# Bild holen
|
|
||||||
if not self.chat.fetch_image_from_esp32():
|
|
||||||
logger.warning("Konnte kein Bild vom ESP32 holen")
|
|
||||||
else:
|
|
||||||
# Nur hochladen wenn sich das Bild geändert hat (100 Bilder Limit!)
|
|
||||||
if self.chat.upload_image_if_changed():
|
|
||||||
image_uploaded = True
|
|
||||||
uploaded_count = self.chat.get_images_uploaded_count()
|
|
||||||
|
|
||||||
# Warnungen bei Annäherung ans Limit
|
|
||||||
if uploaded_count >= 95:
|
|
||||||
console.print(f"[bold red]⚠️ {uploaded_count}/100 Bilder! Drücke 'N' für neuen Chat![/bold red]")
|
|
||||||
elif uploaded_count >= 90:
|
|
||||||
console.print(f"[yellow]⚠️ {uploaded_count}/100 Bilder - Limit fast erreicht![/yellow]")
|
|
||||||
elif uploaded_count % 10 == 0:
|
|
||||||
console.print(f"[dim]📷 {uploaded_count} Bilder hochgeladen (Limit: 100)[/dim]")
|
|
||||||
else:
|
|
||||||
logger.debug("Bild unverändert, übersprungen")
|
|
||||||
|
|
||||||
# Nachricht zusammenbauen
|
|
||||||
if stefan_text:
|
if stefan_text:
|
||||||
# Stefan hat was gesagt → Mit TICK senden
|
# ════════════════════════════════════════════════════════════
|
||||||
tick_message = f"[TICK]\n\nStefan sagt: {stefan_text}"
|
# STEFAN HAT GESPROCHEN → Nur seine Nachricht senden!
|
||||||
console.print(f"[cyan]→ TICK mit Stefan-Buffer: \"{stefan_text[:50]}...\"[/cyan]" if len(stefan_text) > 50 else f"[cyan]→ TICK mit Stefan-Buffer: \"{stefan_text}\"[/cyan]")
|
# Kein TICK, kein Bild - Claude soll auf Stefan antworten.
|
||||||
else:
|
# ════════════════════════════════════════════════════════════
|
||||||
# Nur TICK
|
stefan_message = f"Stefan sagt: {stefan_text}"
|
||||||
tick_message = "[TICK]"
|
console.print(f"[green]🎤 Stefan:[/green] {stefan_text[:100]}{'...' if len(stefan_text) > 100 else ''}")
|
||||||
|
|
||||||
success = self.chat.send_message(tick_message)
|
self.chat.send_message(stefan_message)
|
||||||
|
|
||||||
if success:
|
|
||||||
self.stats.ticks_sent += 1
|
self.stats.ticks_sent += 1
|
||||||
self.stats.consecutive_errors = 0 # Reset
|
self.consecutive_errors = 0
|
||||||
logger.debug(f"TICK #{self.stats.ticks_sent}" + (" mit Bild" if upload_images else "") + (f" + Stefan: {stefan_text[:30]}" if stefan_text else ""))
|
logger.info(f"Stefan-Nachricht gesendet: {len(stefan_text)} Zeichen")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("TICK fehlgeschlagen")
|
# ════════════════════════════════════════════════════════════
|
||||||
|
# STEFAN HAT NICHT GESPROCHEN → Normaler TICK mit Bild
|
||||||
|
# ════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
# Kurze Pause für natürlicheres Tempo
|
||||||
|
pause = random.uniform(min_pause, max_pause)
|
||||||
|
time.sleep(pause)
|
||||||
|
|
||||||
|
# Bild hochladen wenn aktiviert
|
||||||
|
image_uploaded = False
|
||||||
|
if upload_images:
|
||||||
|
if not self.chat.fetch_image_from_esp32():
|
||||||
|
logger.warning("Konnte kein Bild vom ESP32 holen")
|
||||||
|
else:
|
||||||
|
if self.chat.upload_image_if_changed():
|
||||||
|
image_uploaded = True
|
||||||
|
uploaded_count = self.chat.get_images_uploaded_count()
|
||||||
|
|
||||||
|
if uploaded_count >= 95:
|
||||||
|
console.print(f"[bold red]⚠️ {uploaded_count}/100 Bilder! Drücke 'N' für neuen Chat![/bold red]")
|
||||||
|
elif uploaded_count >= 90:
|
||||||
|
console.print(f"[yellow]⚠️ {uploaded_count}/100 Bilder - Limit fast erreicht![/yellow]")
|
||||||
|
elif uploaded_count % 10 == 0:
|
||||||
|
console.print(f"[dim]📷 {uploaded_count} Bilder hochgeladen (Limit: 100)[/dim]")
|
||||||
|
else:
|
||||||
|
logger.debug("Bild unverändert, übersprungen")
|
||||||
|
|
||||||
|
# TICK senden
|
||||||
|
tick_message = "[TICK]"
|
||||||
|
console.print("[dim]→ TICK[/dim]")
|
||||||
|
|
||||||
|
success = self.chat.send_message(tick_message)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.stats.ticks_sent += 1
|
||||||
|
self.consecutive_errors = 0
|
||||||
|
logger.debug(f"TICK #{self.stats.ticks_sent}" + (" mit Bild" if image_uploaded else ""))
|
||||||
|
else:
|
||||||
|
raise Exception("TICK fehlgeschlagen")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Senden fertig - wieder freigeben
|
# Senden fertig - wieder freigeben
|
||||||
self._sending.set()
|
self._sending.set()
|
||||||
|
|
@ -789,18 +850,19 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
|
|
||||||
# Text für Sprache aufbereiten
|
# Text für Sprache aufbereiten
|
||||||
speech_text = self._clean_for_speech(msg.text)
|
speech_text = self._clean_for_speech(msg.text)
|
||||||
logger.debug(f"TTS: Nach Bereinigung: {len(speech_text) if speech_text else 0} Zeichen")
|
logger.debug(f"TTS: Original: '{msg.text[:100]}...'")
|
||||||
|
logger.debug(f"TTS: Nach Bereinigung: '{speech_text[:100] if speech_text else ''}' ({len(speech_text) if speech_text else 0} Zeichen)")
|
||||||
|
|
||||||
# "Claude sagt:" Prefix entfernen falls vorhanden (wird nicht vorgelesen)
|
# "Claude sagt:" Prefix entfernen falls vorhanden (wird nicht vorgelesen)
|
||||||
tts_text = speech_text
|
tts_text = speech_text
|
||||||
if tts_text.lower().startswith("claude sagt:"):
|
if tts_text.lower().startswith("claude sagt:"):
|
||||||
tts_text = tts_text[12:].strip()
|
tts_text = tts_text[12:].strip()
|
||||||
logger.debug(f"TTS: 'Claude sagt:' entfernt, Rest: {len(tts_text)} Zeichen")
|
logger.debug(f"TTS: 'Claude sagt:' entfernt, Rest: '{tts_text}' ({len(tts_text)} Zeichen)")
|
||||||
|
|
||||||
# Prüfe ob nach Entfernen noch Text übrig ist
|
# Prüfe ob nach Entfernen noch Text übrig ist
|
||||||
# Kein Mindestlänge-Check damit auch kurze Antworten wie "Ja!" gesprochen werden
|
# Kein Mindestlänge-Check damit auch kurze Antworten wie "Ja!" gesprochen werden
|
||||||
if not tts_text:
|
if not tts_text:
|
||||||
logger.debug(f"TTS: Nach Prefix-Entfernung kein Text übrig, übersprungen")
|
logger.info(f"TTS: Nach Prefix-Entfernung kein Text übrig, übersprungen (Original: '{msg.text[:50]}')")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# In Konsole anzeigen (ohne Prefix)
|
# In Konsole anzeigen (ohne Prefix)
|
||||||
|
|
@ -809,8 +871,13 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
console.print(f"[dim]...({len(tts_text)} Zeichen)[/dim]")
|
console.print(f"[dim]...({len(tts_text)} Zeichen)[/dim]")
|
||||||
|
|
||||||
# Vorlesen (ohne "Claude sagt:" - das ist ja klar)
|
# Vorlesen (ohne "Claude sagt:" - das ist ja klar)
|
||||||
|
# WICHTIG: Speaking-Flag setzen damit Heartbeat wartet!
|
||||||
logger.info(f"TTS: Spreche {len(tts_text)} Zeichen...")
|
logger.info(f"TTS: Spreche {len(tts_text)} Zeichen...")
|
||||||
self.tts.speak(tts_text)
|
self._speaking.set() # Signalisiere: Claude spricht!
|
||||||
|
try:
|
||||||
|
self.tts.speak(tts_text)
|
||||||
|
finally:
|
||||||
|
self._speaking.clear() # Fertig mit Sprechen
|
||||||
self.stats.messages_spoken += 1
|
self.stats.messages_spoken += 1
|
||||||
logger.debug("TTS: Sprechen beendet")
|
logger.debug("TTS: Sprechen beendet")
|
||||||
else:
|
else:
|
||||||
|
|
@ -855,11 +922,22 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# WICHTIG: Wenn Claude spricht (TTS), nicht aufzeichnen!
|
||||||
|
# Das verhindert Echo (Mikrofon nimmt TTS auf) und
|
||||||
|
# überlappende Gespräche - wir lassen Claude ausreden.
|
||||||
|
if self._speaking.is_set():
|
||||||
|
# Falls wir mitten in einer Aufnahme waren, diese beenden
|
||||||
|
if self._recording.is_set():
|
||||||
|
self._finalize_recording(current_session_texts)
|
||||||
|
current_session_texts = []
|
||||||
|
time.sleep(0.3)
|
||||||
|
continue
|
||||||
|
|
||||||
# Warte auf Sprache (kurzer Timeout für schnelle Reaktion)
|
# Warte auf Sprache (kurzer Timeout für schnelle Reaktion)
|
||||||
result = self.stt.listen_once(timeout=1)
|
result = self.stt.listen_once(timeout=1)
|
||||||
|
|
||||||
# Nochmal prüfen nach dem Hören (falls zwischendurch gemutet wurde)
|
# Nochmal prüfen nach dem Hören (falls zwischendurch gemutet oder Claude spricht)
|
||||||
if self.is_muted():
|
if self.is_muted() or self._speaking.is_set():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if result and result.text and len(result.text) > 2:
|
if result and result.text and len(result.text) > 2:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue