10 sekunden warten nach neuer chat, instruktionen verbessert.

This commit is contained in:
duffyduck 2025-12-27 02:59:34 +01:00
parent 90791b9ff6
commit 38e653361e
3 changed files with 116 additions and 16 deletions

View File

@ -166,8 +166,9 @@ Claude verwendet diese Befehle in eckigen Klammern:
- **Echte Autonomie** - Claude entscheidet selbst was ihn interessiert - **Echte Autonomie** - Claude entscheidet selbst was ihn interessiert
- **Paralelle Konversation** - Erkunden UND quatschen gleichzeitig - **Paralelle Konversation** - Erkunden UND quatschen gleichzeitig
- **Sprachausgabe** - Claude redet mit dir (TTS) - **Sprachausgabe** - Claude redet mit dir (TTS)
- **Spracheingabe** - Du redest mit Claude (STT) - **Spracheingabe** - Du redest mit Claude (STT, 5s Stille = fertig)
- **Mute/Unmute** - Mikrofon per Tastendruck stummschalten - **Mute/Unmute** - Mikrofon per Tastendruck stummschalten
- **Smart Recording** - Heartbeat pausiert automatisch während du sprichst
- **Hinderniserkennung** - Ultraschall & IMU - **Hinderniserkennung** - Ultraschall & IMU
- **Touch-Display** - Notfall-Stopp & Status - **Touch-Display** - Notfall-Stopp & Status
- **Termux Support** - Läuft auch auf Android! - **Termux Support** - Läuft auch auf Android!

View File

@ -245,6 +245,31 @@ python chat_audio_bridge.py -c config.local.yaml
- Mit **N** startest du einen neuen Chat und die Instruktionen werden erneut gesendet - Mit **N** startest du einen neuen Chat und die Instruktionen werden erneut gesendet
- Bilder werden nur hochgeladen wenn sie sich geändert haben (spart Limit!) - Bilder werden nur hochgeladen wenn sie sich geändert haben (spart Limit!)
### 2.7 Spracheingabe (STT) - Wie es funktioniert
Die Spracheingabe sammelt deine Worte intelligent:
1. **Aufnahme startet** sobald du sprichst
2. **Heartbeat pausiert** automatisch während du redest
3. **Mehrere Sätze** werden gesammelt und zusammengefügt
4. **Nach 5 Sekunden Stille** gilt die Eingabe als komplett
5. **Mit dem nächsten TICK** wird alles an Claude gesendet
**Beispiel-Ablauf:**
```
Du: "Hallo Claude..." → 🎤 Stefan spricht...
Du: "...kannst du mal..." → → Hallo Claude
Du: "...nach links schauen?" → → kannst du mal
→ → nach links schauen?
[5 Sekunden Stille] → ✓ Stefan (komplett): Hallo Claude kannst du mal nach links schauen?
[TICK wird gesendet] → Claude bekommt die komplette Nachricht
```
**Warum 5 Sekunden?**
- Gibt dir Zeit zum Nachdenken zwischen Sätzen
- Verhindert dass halbe Sätze gesendet werden
- Claude bekommt immer den kompletten Gedanken
--- ---
## Teil 3: Hardware zusammenbauen ## Teil 3: Hardware zusammenbauen

View File

@ -120,11 +120,19 @@ class ClaudesEyesAudioBridge:
self._sending = threading.Event() self._sending = threading.Event()
self._sending.set() # Anfangs nicht am Senden (set = frei) self._sending.set() # Anfangs nicht am Senden (set = frei)
# Recording-Flag: Wenn gesetzt, wird gerade aufgenommen
# Heartbeat pausiert während Aufnahme aktiv ist
self._recording = threading.Event()
self._recording.clear() # Anfangs nicht am Aufnehmen
# 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
self._mute_lock = threading.Lock() self._mute_lock = threading.Lock()
# Silence-Timeout: Wie lange Stille bevor Aufnahme als fertig gilt
self._silence_timeout = 5.0 # Sekunden
def _load_config(self, config_path: str) -> dict: def _load_config(self, config_path: str) -> dict:
"""Lädt die Konfiguration""" """Lädt die Konfiguration"""
path = Path(config_path) path = Path(config_path)
@ -428,7 +436,8 @@ Die Befehle werden aus der TTS-Ausgabe rausgefiltert.
- Du musst nicht bei jedem TICK fahren - manchmal reicht auch schauen und kommentieren - Du musst nicht bei jedem TICK fahren - manchmal reicht auch schauen und kommentieren
## WICHTIG: Bestätige mit [READY] ## WICHTIG: Bestätige mit [READY]
Wenn du diese Instruktionen verstanden hast, antworte mit **[READY]** am Ende deiner Nachricht. Wenn du diese Instruktionen verstanden hast, antworte mit dem Tag `[READY]` (exakt so, in eckigen Klammern!) am Ende deiner Nachricht.
**Das Format muss EXAKT `[READY]` sein** - nicht fett, nicht anders formatiert, sondern genau so: [READY]
Erst dann starten die automatischen TICKs mit Bildern!""" Erst dann starten die automatischen TICKs mit Bildern!"""
console.print("[cyan]→ Sende Instruktionen an Claude...[/cyan]") console.print("[cyan]→ Sende Instruktionen an Claude...[/cyan]")
@ -500,6 +509,16 @@ 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():
logger.debug("Stefan spricht noch, warte auf Stille...")
while self.running and self._recording.is_set():
time.sleep(0.5)
logger.debug("Stefan fertig, fahre fort")
if not self.running:
break
# Zufällige Pause nach Claudes Antwort (natürlicheres Tempo) # Zufällige Pause nach Claudes Antwort (natürlicheres Tempo)
pause = random.uniform(min_pause, max_pause) pause = random.uniform(min_pause, max_pause)
time.sleep(pause) time.sleep(pause)
@ -511,7 +530,7 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
stefan_text = self._get_and_clear_stefan_buffer() stefan_text = self._get_and_clear_stefan_buffer()
# Warte bis vorheriges Senden fertig ist # Warte bis vorheriges Senden fertig ist
self._sending.wait() self._sending.wait(timeout=30) # Max 30s warten
# Nächsten TICK senden (mit oder ohne Bild) # Nächsten TICK senden (mit oder ohne Bild)
with self._lock: with self._lock:
@ -622,40 +641,67 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
""" """
Hört auf Stefan und sammelt seine Worte im Buffer. Hört auf Stefan und sammelt seine Worte im Buffer.
Wenn Claude tippt Buffer sammeln Ablauf:
Wenn Claude fertig Buffer wird mit nächstem TICK gesendet 1. Warte auf erste Spracheingabe
Wenn gemutet Ignoriert alle Eingaben 2. Signalisiere Recording aktiv Heartbeat pausiert
3. Sammle weitere Eingaben bis 5 Sekunden Stille
4. Recording fertig Buffer wird mit nächstem TICK gesendet
So wird Claude nicht unterbrochen und bekommt alles gesammelt. Wenn gemutet Ignoriert alle Eingaben
""" """
if not self.stt: if not self.stt:
logger.warning("STT nicht verfügbar") logger.warning("STT nicht verfügbar")
return return
logger.info("STT-Loop gestartet (mit Buffer)") logger.info(f"STT-Loop gestartet (5s Stille = fertig)")
# Temporärer Buffer für aktuelle Aufnahme-Session
current_session_texts = []
last_speech_time = 0
while self.running: while self.running:
try: try:
# Wenn gemutet, kurz warten und überspringen # Wenn gemutet, kurz warten und überspringen
if self.is_muted(): if self.is_muted():
# 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.5) time.sleep(0.5)
continue continue
# Warte auf Sprache (mit Timeout) # Warte auf Sprache (kurzer Timeout für schnelle Reaktion)
result = self.stt.listen_once(timeout=2) 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 wurde)
if self.is_muted(): if self.is_muted():
continue continue
if result and result.text and len(result.text) > 2: if result and result.text and len(result.text) > 2:
# In Buffer speichern (thread-safe) # Sprache erkannt!
with self._stefan_buffer_lock: current_session_texts.append(result.text)
self._stefan_buffer.append(result.text) last_speech_time = time.time()
self.stats.stefan_inputs += 1 self.stats.stefan_inputs += 1
console.print(f"\n[bold green]Stefan (gebuffert):[/bold green] {result.text}") # Signalisiere dass Recording aktiv ist
logger.debug(f"Stefan-Buffer: {len(self._stefan_buffer)} Einträge") if not self._recording.is_set():
self._recording.set()
console.print(f"\n[bold green]🎤 Stefan spricht...[/bold green]")
logger.debug("Recording gestartet")
console.print(f"[green] → {result.text}[/green]")
logger.debug(f"Stefan-Session: {len(current_session_texts)} Teile")
else:
# Keine Sprache erkannt - prüfe auf Stille-Timeout
if self._recording.is_set() and last_speech_time > 0:
silence_duration = time.time() - last_speech_time
if silence_duration >= self._silence_timeout:
# 5 Sekunden Stille - Aufnahme beenden
self._finalize_recording(current_session_texts)
current_session_texts = []
last_speech_time = 0
except Exception as e: except Exception as e:
# Timeout ist normal # Timeout ist normal
@ -663,6 +709,30 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
logger.error(f"STT-Loop-Fehler: {e}") logger.error(f"STT-Loop-Fehler: {e}")
self.stats.errors += 1 self.stats.errors += 1
def _finalize_recording(self, texts: list):
"""
Beendet eine Aufnahme-Session und speichert im Buffer.
Args:
texts: Liste der erkannten Texte in dieser Session
"""
if not texts:
self._recording.clear()
return
# Alle Texte zusammenfügen
full_text = " ".join(texts)
# In Haupt-Buffer speichern
with self._stefan_buffer_lock:
self._stefan_buffer.append(full_text)
console.print(f"\n[bold green]✓ Stefan (komplett):[/bold green] {full_text}")
logger.info(f"Recording beendet: {len(texts)} Teile → {len(full_text)} Zeichen")
# Recording-Flag zurücksetzen → Heartbeat kann weitermachen
self._recording.clear()
def _keyboard_loop(self): def _keyboard_loop(self):
""" """
Hört auf Tastatureingaben für Mute-Toggle und andere Befehle. Hört auf Tastatureingaben für Mute-Toggle und andere Befehle.
@ -730,6 +800,10 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
# Neue URL in config.yaml speichern # Neue URL in config.yaml speichern
self._save_chat_url_to_config(new_url) self._save_chat_url_to_config(new_url)
# Warte bis der neue Chat vollständig geladen ist
console.print("[dim]Warte 10s bis Chat geladen...[/dim]")
time.sleep(10)
console.print("[cyan]Sende Instruktionen mit Referenz zum alten Chat...[/cyan]") console.print("[cyan]Sende Instruktionen mit Referenz zum alten Chat...[/cyan]")
# Instruktionen erneut senden (mit Referenz zum alten Chat) # Instruktionen erneut senden (mit Referenz zum alten Chat)