Vier Bugs in einem Aufwasch:
1. HF-Cache als Bind-Mount zurueck
xtts/hf-cache:/root/.cache/huggingface fuer beide Bridges. War vorher
raus, dadurch jedes Container-Restart = ~3GB Whisper-Download +
~1GB F5-TTS-Download. User dachte 5min ist einmalig — ist aber bei
jedem Restart. Jetzt: einmal pro Maschine geladen, fertig.
2. Banner zeigte stale "ready"
whisper-bridge sendete beim Connect nur dann Status wenn Modell schon
geladen war. Sonst blieb der App/Diagnostic Banner auf dem alten
"ready" State von vor dem Restart haengen — User sah "bereit" obwohl
gerade gar nichts geladen war. Jetzt wird IMMER ein Status broadcast:
ready oder loading.
3. config_request Pattern
aria-bridge wusste nicht wann Gamebox-Bridges sich (re)connecten.
Wenn die nach aria-bridge kamen, verpassten sie den Config-Broadcast
und blieben mit Hard-Defaults stehen.
Jetzt: whisper- und f5tts-bridge senden beim Connect ein
config_request, aria-bridge antwortet mit der persistierten Config
(whisperModel, xttsVoice, f5tts*-Felder).
4. RVS ALLOWED_TYPES um config_request erweitert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: aria-whisper-bridge bekam beim ersten internen stt_request
(via voice_upload mit WAV als base64, ~2.4MB) den Frame zu Gesicht,
default ws-max ist 1MB → mit Close-Code 1009 abgewiesen → Verbindung
tot → naechster stt_request lief in Timeout → lokales Fallback.
Fixes:
- whisper-bridge: max_size=50*1024*1024 in websockets.connect()
(gleicher Wert wie f5tts-bridge schon hat)
- RVS-Server: maxPayload=50*1024*1024 in WebSocketServer-Optionen,
damit der Server die Frames nicht selbst auf 1MB cappt bevor er
sie an die Bridge weiterleitet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan akzeptiert die ~5min Modell-Download-Zeit nach jedem Container-
Start, dafuer keine 50GB Cache-Bloat mehr und kein Bind-Mount-Verzeichnis
zu pflegen.
- xtts/docker-compose.yml: hf-cache Bind-Mount entfernt fuer beide
Bridges. Modelle werden im writable Container-Layer abgelegt und mit
jedem `docker compose down` automatisch weggeraeumt.
- xtts/.gitignore: hf-cache/ Eintrag raus
- RVS ALLOWED_TYPES: service_status hinzu
Bridges broadcasten Lade-Status:
- f5tts-bridge: bei Connect 'loading' -> ensure_loaded -> 'ready'.
Auch bei config-getriggertem Modell-Wechsel: erst 'loading' Broadcast,
dann reload, dann 'ready'.
- whisper-bridge: gleiches Pattern. Modell wird jetzt erst nach
RVS-Connect geladen damit der loading-Broadcast tatsaechlich rausgeht.
Diagnostic:
- server.js: service_status wird an Browser durchgereicht
- index.html: neues Banner unten rechts (fixed position) zeigt Status
fuer beide Services. Aggregiert: Icon ist Lupe waehrend Loading,
Check wenn alles ready, X bei Error.
- Wenn alles ready: X-Button erscheint (manuell schliessen) +
nach 8s automatisches Fade-Out.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer Container aria-whisper-bridge auf der Gamebox — faster-whisper
CUDA mit float16. Der Container verbindet sich per WebSocket an den RVS,
nimmt stt_request entgegen, laeuft ffmpeg+Whisper, antwortet mit
stt_response. Hoert zusaetzlich auf config-Broadcasts und lädt das
Modell hot-swap bei Diagnostic-Wechsel.
aria-bridge ruft jetzt primaer die Gamebox an; nur wenn die nicht binnen
45s antwortet, faellt auf lokales Whisper (CPU) zurueck. Das lokale
Modell wird lazy geladen, spart RAM auf der VM.
RVS: stt_request/stt_response zur ALLOWED_TYPES-Liste.
Diagnostic-Voice-Config (whisperModel-Feld) bleibt unveraendert —
die Auswahl wird an die Gamebox durchgereicht.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XTTS-Bridge:
- empfaengt neuen voice_preload Type, rendert stumm "ja." fuer die Stimme
via TTS-Queue (damit kein Konflikt mit echtem TTS)
- horcht zusaetzlich auf config-Broadcasts: wenn Diagnostic global die
Stimme wechselt, wird auto-preloaded
- broadcastet voice_ready mit Dauer (loadMs) oder error
RVS: voice_preload + voice_ready zur ALLOWED_TYPES-Liste.
App (SettingsScreen): beim Wechsel senden wir voice_preload, zeigen einen
Spinner in der Voice-Row und einen Toast mit "Stimme X bereit (Ns)".
App (ChatScreen): Toast auch hier — falls User gerade nicht in Settings ist.
Diagnostic (server+UI): voice_ready wird an Browser durchgereicht, ein
Status-Text unter dem Voice-Dropdown zeigt "wird geladen" → "bereit".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug-Fix: Voice-Auswahl verschwand nach Page-Load
- xtts_voices_list Handler rebuildet das Dropdown — vorheriger select.value
ging dabei verloren. Jetzt wird der Wert gemerkt und nach Rebuild
wiederhergestellt (falls die Stimme noch existiert).
Feature: Stimmen loeschen (Diagnostic)
- XTTS-Bridge: neuer handleDeleteVoice — entfernt /voices/<name>.wav
und schickt aktualisierte Liste per xtts_voices_list
- RVS: xtts_delete_voice in ALLOWED_TYPES
- Diagnostic Server: Action xtts_delete_voice forwarded via RVS
- Diagnostic UI: renderVoiceList zeigt alle Custom-Voices mit X-Button
Bei Loeschen der gerade aktiven Stimme: auf Default zuruecksetzen
Feature: Voice-per-Request in Bridge
- App kann mit jedem Chat ein voice-Feld mitschicken
- Bridge merkt sich _next_voice_override, nutzt es fuer die NAECHSTE
ARIA-Antwort (einmalig, dann reset)
- tts_request (Play-Button) akzeptiert voice im Payload als Override
- Fallback: globale xtts_voice aus voice_config.json
- So kann jedes Geraet seine eigene Stimme haben ohne den globalen
Default zu aendern
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pipeline: XTTS-Server → xtts-bridge → aria-bridge → RVS → App AudioTrack
XTTS-Bridge (Gaming-PC):
- streamXTTSAsPCM(): liest /tts_to_audio/ Response inkrementell,
parst WAV-Header (samplerate/channels), teilt PCM in 8KB-Chunks
(~170ms bei 24kHz s16 mono) und sendet jeden als audio_pcm.
- Finaler Chunk mit final=true nach letztem Text-Chunk
aria-bridge:
- audio_pcm Handler leitet payload 1:1 weiter, filled messageId aus
requestId → messageId Map falls XTTS-Bridge messageId nicht hatte
- Alter xtts_response Pfad bleibt als Legacy-Fallback (WAV)
RVS: audio_pcm in ALLOWED_TYPES
Android Native:
- PcmStreamPlayerModule (Kotlin): AudioTrack MODE_STREAM mit
Writer-Thread und BlockingQueue. start(rate, ch) / writeChunk(b64)
/ end() / stop()
- 8x MinBufferSize grosszuegig dimensioniert, glatt auch bei
Netz-Aussetzern
- Registered im MainApplication via PcmStreamPlayerPackage
App JS:
- audioService.handlePcmChunk(): erkennt neue Session (messageId-Wechsel),
started nativen Stream, cached PCM-Bytes pro Message. Bei final=true
Stream sauber schliessen + _savePcmBufferAsWav → WAV-File im
tts_cache/<messageId>.wav
- _savePcmBufferAsWav: baut 44-byte WAV-Header (PCM s16le, korrekte
samplerate/channels), haengt alle gesammelten base64-PCM-Chunks an
- stopPlayback beendet auch aktiven PCM-Stream
- ChatScreen routet type=audio_pcm an handlePcmChunk, bei final
setzt audioPath in der Message
Play-Button: falls messageId einen audioPath hat → WAV aus Cache
(Sound-basiert), egal ob Original-TTS Piper oder XTTS war.
Audio-Focus:
- requestDuck() beim Stream-Start, release() bei Stream-Ende
- Andere Apps (Spotify etc.) werden leiser waehrend ARIA spricht
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Bridge: _emit_activity() spiegelt OpenClaw agent events als agent_activity
an RVS, dedupliziert State-Wechsel. chat:final/error senden idle.
- Bridge: Neuer cancel_request-Handler ruft Diagnostic /api/cancel per HTTP.
- Diagnostic: Neuer POST /api/cancel Endpoint (gleiche Logik wie WS-Cancel).
- RVS: agent_activity + cancel_request in ALLOWED_TYPES.
- App: Gelber Indicator ueber der Input-Bar mit Text je nach Activity,
roter Abbrechen-Button. Cancel sendet cancel_request via RVS.
- issue.md: Erledigte Bugfixes + Features konsolidiert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>