10 sekunden warten nach neuer chat, instruktionen verbessert.
This commit is contained in:
parent
90791b9ff6
commit
38e653361e
|
|
@ -166,8 +166,9 @@ Claude verwendet diese Befehle in eckigen Klammern:
|
|||
- **Echte Autonomie** - Claude entscheidet selbst was ihn interessiert
|
||||
- **Paralelle Konversation** - Erkunden UND quatschen gleichzeitig
|
||||
- **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
|
||||
- **Smart Recording** - Heartbeat pausiert automatisch während du sprichst
|
||||
- **Hinderniserkennung** - Ultraschall & IMU
|
||||
- **Touch-Display** - Notfall-Stopp & Status
|
||||
- **Termux Support** - Läuft auch auf Android!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -120,11 +120,19 @@ class ClaudesEyesAudioBridge:
|
|||
self._sending = threading.Event()
|
||||
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
|
||||
# Startet gemutet um ungewollte Aufnahmen zu vermeiden
|
||||
self._muted = True
|
||||
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:
|
||||
"""Lädt die Konfiguration"""
|
||||
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
|
||||
|
||||
## 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!"""
|
||||
|
||||
console.print("[cyan]→ Sende Instruktionen an Claude...[/cyan]")
|
||||
|
|
@ -500,6 +509,16 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
|||
if not self.running:
|
||||
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)
|
||||
pause = random.uniform(min_pause, max_pause)
|
||||
time.sleep(pause)
|
||||
|
|
@ -511,7 +530,7 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
|||
stefan_text = self._get_and_clear_stefan_buffer()
|
||||
|
||||
# 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)
|
||||
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.
|
||||
|
||||
Wenn Claude tippt → Buffer sammeln
|
||||
Wenn Claude fertig → Buffer wird mit nächstem TICK gesendet
|
||||
Wenn gemutet → Ignoriert alle Eingaben
|
||||
Ablauf:
|
||||
1. Warte auf erste Spracheingabe
|
||||
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:
|
||||
logger.warning("STT nicht verfügbar")
|
||||
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:
|
||||
try:
|
||||
# Wenn gemutet, kurz warten und überspringen
|
||||
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)
|
||||
continue
|
||||
|
||||
# Warte auf Sprache (mit Timeout)
|
||||
result = self.stt.listen_once(timeout=2)
|
||||
# Warte auf Sprache (kurzer Timeout für schnelle Reaktion)
|
||||
result = self.stt.listen_once(timeout=1)
|
||||
|
||||
# Nochmal prüfen nach dem Hören (falls zwischendurch gemutet wurde)
|
||||
if self.is_muted():
|
||||
continue
|
||||
|
||||
if result and result.text and len(result.text) > 2:
|
||||
# In Buffer speichern (thread-safe)
|
||||
with self._stefan_buffer_lock:
|
||||
self._stefan_buffer.append(result.text)
|
||||
self.stats.stefan_inputs += 1
|
||||
# Sprache erkannt!
|
||||
current_session_texts.append(result.text)
|
||||
last_speech_time = time.time()
|
||||
self.stats.stefan_inputs += 1
|
||||
|
||||
console.print(f"\n[bold green]Stefan (gebuffert):[/bold green] {result.text}")
|
||||
logger.debug(f"Stefan-Buffer: {len(self._stefan_buffer)} Einträge")
|
||||
# Signalisiere dass Recording aktiv ist
|
||||
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:
|
||||
# Timeout ist normal
|
||||
|
|
@ -663,6 +709,30 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
|||
logger.error(f"STT-Loop-Fehler: {e}")
|
||||
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):
|
||||
"""
|
||||
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
|
||||
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]")
|
||||
|
||||
# Instruktionen erneut senden (mit Referenz zum alten Chat)
|
||||
|
|
|
|||
Loading…
Reference in New Issue