# CommCoach Voice Recording Streaming Konzept ## Ausgangslage Die aktuelle CommCoach-Spracheingabe basiert auf Browser `SpeechRecognition` (`webkitSpeechRecognition`). Auf Mobile-Browsern wird die Aufnahme typischerweise alle ca. 5-7 Sekunden automatisch beendet und neu gestartet. Das ist kein sauber behebbarer App-Bug, sondern eine Browser/API-Limitation. ## Zielbild Die Spracheingabe soll auf Mobile und Desktop stabil laufen, ohne erzwungene 5-Sekunden-Resets, ohne Textverlust zwischen Restart-Gaps und ohne MSFT-Abhängigkeiten. ## Antwort auf die Kernfragen ### 1) Ist Option 3 ein genereller Umbau oder nur Mobile? Beides ist möglich: - **Variante A (Mobile-only Umbau):** Neue Audio-Streaming-Pipeline nur auf Mobile aktivieren, Desktop bleibt vorerst bei Browser STT. - **Variante B (Genereller Umbau):** Neue Pipeline für alle Clients (Mobile + Desktop), einheitliches Verhalten und weniger Wartung. Empfehlung: **Variante B**, da nur ein Stack gepflegt werden muss und CommCoach-Logik konsistent bleibt. ### 2) Kann das komplett in unserer Plattform umgesetzt werden? Ja. Vollständig in eigener Plattform ist möglich. Notwendig sind: - Eigene UI- und Gateway-Implementierung (WebSocket Streaming) - Eigener STT-Service (self-hosted), z. B. `faster-whisper` - Optional eigene VAD (Voice Activity Detection), z. B. `webrtcvad` Nicht zwingend notwendig: - Externe SaaS-STT Provider - MSFT/Azure Speech ## Zielarchitektur (MSFT-frei) ### UI (Frontend CommCoach) - Mikrofonaufnahme via `MediaStream` + `AudioWorklet` oder `MediaRecorder` - Chunking (z. B. 100-250 ms) - Streaming per WebSocket an Gateway - Lokaler Live-Preview-Text kommt nicht mehr aus Browser-STT, sondern aus Server-Interims - Bestehende State-Machine bleibt als Steuerlogik erhalten (`idle`, `listening`, `botSpeaking`, `interrupted`, `muted` orthogonal) ### Gateway - Neuer Endpoint, z. B. `ws /api/feature/commcoach/{instanceId}/stt/stream` - Sessiongebundene Stream-Verarbeitung - Weiterleitung der Audio-Chunks an STT-Worker - Rückkanal von Interims + Final-Text zum UI - Serverseitige Segmentierung (Silence / VAD) statt Browser `onend` ### STT-Service (self-hosted) - `faster-whisper` als Runtime (GPU empfohlen, CPU möglich) - Sprachen konfigurierbar (z. B. `de`, `en`) - Ausgabe: - `interim` Events (optional, je nach Latenzbudget) - `final` Segmente - Zeitmarken für Debug und Nachvollziehbarkeit ### Optional: VAD-Service - Entweder im Gateway oder STT-Worker - Trigger für Segment-Ende statt fester 1s-Timer - Stabiler als browserseitige `onspeechstart/onspeechend` ## Ist-Analyse der bestehenden Codebase (UI + Gateway) ### Frontend (`frontend_nyla`) - `src/pages/views/commcoach/useVoiceController.ts` - Nutzt Browser `SpeechRecognition` direkt. - Auto-Restart bei `onend` mit `REC_AUTORESTART_DELAY_MS = 300`. - `SILENCE_TIMEOUT_MS = 1000` finalisiert User-Text. - Mobile-Problem entsteht hier durch Browser-`onend`-Zwang (alle ~5-7s). - `src/pages/views/commcoach/CommcoachDossierView.tsx` - Bindet Voice-State-Machine an UI/TTS. - `voice.liveTranscript` wird als Live-User-Bubble angezeigt. - Debuglog existiert bereits via `window.__dlog`. - `src/hooks/useCommcoach.ts` - Text läuft über `sendMessageStreamApi` (SSE). - Audio läuft derzeit nur als One-Shot Blob über `sendAudioStreamApi` (SSE). - Kein echtes bidirektionales Low-Latency Audio-Streaming. - `src/api/commcoachApi.ts` - Bestehende Endpunkte sind HTTP + SSE. - Es gibt noch keinen WebSocket-Endpunkt für chunkweises Audio. ### Gateway (`gateway`) - `modules/features/commcoach/routeFeatureCommcoach.py` - `POST .../message/stream` (SSE) für Text. - `POST .../audio/stream` (SSE) für Audio-One-Shot. - Audio-Endpoint liest `await request.body()` komplett und startet danach STT. - `modules/features/commcoach/serviceCommcoach.py` - `processAudioMessage(...)`: STT auf gesamtem Blob, danach `processMessage(...)`. - Keine Segment-/Chunk-Logik für laufende Erkennung. - `modules/interfaces/interfaceVoiceObjects.py` - Kapselt STT/TTS aktuell über Google Connector. - API ist bereits zentralisiert und damit gut erweiterbar. - `modules/connectors/connectorVoiceGoogle.py` - Verwendet `recognize` auf Einzeldateien (nicht Streaming). - Damit strukturell ungeeignet für kontinuierliche Mobile-Spracheingabe. ## Implementierung im bestehenden System (konkret) ### Ziel: bestehende CommCoach-Pipeline beibehalten, nur STT-Eingang ersetzen Unverändert bleiben: - Session- und Message-Processing in `serviceCommcoach.py` (`processMessage`, Events, TTS). - UI-State-Machine-Fachlogik (`idle`, `listening`, `botSpeaking`, `interrupted`, `muted`). Ersetzt wird: - Browser-STT (`SpeechRecognition`) durch Streaming-STT Provider. - One-Shot Audio-Upload durch WebSocket-Audio-Stream. ### A) Frontend Änderungen 1. Neue API-Funktion in `src/api/commcoachApi.ts` - `openSttStreamApi(instanceId, sessionId, handlers, options)` via WebSocket. - Handler: `onStatus`, `onInterim`, `onFinal`, `onError`, `onClose`. 2. Neues Hook `src/hooks/useAudioStreamTranscription.ts` - Mikrofon aufnehmen (`MediaStream` + `AudioWorklet` oder `MediaRecorder`). - Audio in 100-250ms Chunks an WS senden. - Event-Rückkanal verarbeiten (interim/final/status/error). - Reconnect-Mechanismus für Mobile-Netzwechsel. 3. `src/pages/views/commcoach/useVoiceController.ts` refactor - Provider-Interface einführen: - `browserSpeech` (legacy) - `streamedStt` (neu) - `transcriptPartsRef` und `liveTranscript` aus Serverevents speisen. - `SILENCE_TIMEOUT_MS` nur noch als optionales Guard-Rail, nicht als Kernsegmentierung. 4. `src/pages/views/commcoach/CommcoachDossierView.tsx` - Keine Verhaltensänderung in Tabs/State nötig. - Nur Provider initialisieren und vorhandene Debuganzeige um STT-WS Events erweitern. ### B) Gateway Änderungen 1. Neue WS-Route in `modules/features/commcoach/routeFeatureCommcoach.py` - Beispiel: `ws /api/commcoach/{instanceId}/sessions/{sessionId}/stt/stream` - Ownership-/Session-Checks analog zu `message/stream`. - WebSocket akzeptieren, Audio-Chunks entgegennehmen, Events zurücksenden. 2. Neuer Service `modules/features/commcoach/serviceCommcoachSttStream.py` - Sessiongebundene Streamverwaltung. - Chunk-Buffer, VAD/Silence-Regeln, Segmentbildung. - Für jedes finale Segment: `processMessage(sessionId, contextId, finalText, interface)` aufrufen. 3. Anpassung `modules/features/commcoach/serviceCommcoach.py` - `processAudioMessage` bleibt für Legacy/Upload kompatibel. - Neue Streaming-Nutzung erfolgt über den neuen STT-Stream-Service. 4. Erweiterung `modules/interfaces/interfaceVoiceObjects.py` - Neue Methoden ergänzen: - `startSttStream(...)` - `pushSttAudioChunk(...)` - `stopSttStream(...)` - Bestehende Methoden (`speechToText`, `textToSpeech`) unverändert lassen. ### C) STT Worker (self-hosted) 1. Neuer Connector (z. B.) `modules/connectors/connectorVoiceWhisper.py` - `faster-whisper` Integration. - Modell, Sprache, VAD-Parameter konfigurierbar. 2. Optional separater Worker-Prozess - Entkoppelt Gateway-Latenz von STT-Rechenlast. - Kommunikation intern über Queue/IPC oder internen WS. ## WS Event Contract (verbindlich) ### Client -> Server - `open` - `{ "type": "open", "sessionId": "...", "language": "de-DE", "codec": "pcm16" }` - `audio` - `{ "type": "audio", "seq": 123, "chunk": "" }` - `commit` - `{ "type": "commit", "reason": "silence|manual|stateChange" }` - `close` - `{ "type": "close" }` ### Server -> Client - `status` - `{ "type": "status", "label": "Sprache wird erkannt..." }` - `interim` - `{ "type": "interim", "text": "...", "segmentId": "..." }` - `final` - `{ "type": "final", "text": "...", "segmentId": "...", "confidence": 0.0 }` - `ack` - `{ "type": "ack", "seq": 123 }` - `error` - `{ "type": "error", "code": "stt_failed", "message": "..." }` - `closed` - `{ "type": "closed", "reason": "client|server|timeout" }` ## Migrationsstrategie (ohne doppelte Logikfalle) 1. Feature-Flag `commcoachVoiceProvider` auf Instanzebene - Werte: `browserSpeech` | `streamedStt`. 2. Rolloutpfad - Schritt 1: intern auf Mobile aktivieren. - Schritt 2: nach Stabilisierung global aktivieren. - Schritt 3: Browser-STT-Code entfernen. 3. Expliziter Fehlerpfad - Kein stiller Fallback auf Browser-STT, wenn `streamedStt` aktiv ist. - Fehler wird sichtbar im UI (Banner + Debuglog). ## Konkrete Taskliste pro Repository ### Repo `frontend_nyla` 1. `commcoachApi.ts`: `openSttStreamApi` ergänzen. 2. Neues Hook `useAudioStreamTranscription.ts` implementieren. 3. `useVoiceController.ts`: Provider-Abstraktion + streamed Provider integrieren. 4. `CommcoachDossierView.tsx`: Provider-Init + Debug-Events anzeigen. 5. Tests: - unit: Segment-Assembler - integration: state transitions bei interim/final/error ### Repo `gateway` 1. WS-Route für STT-Stream in `routeFeatureCommcoach.py`. 2. Neuer Stream-Service `serviceCommcoachSttStream.py`. 3. `interfaceVoiceObjects.py` um Streaming-Methoden erweitern. 4. Neuer Whisper-Connector `connectorVoiceWhisper.py`. 5. Telemetrie + Logs: - stream open/close - first interim latency - first final latency - reconnect count ## Variante A vs. Variante B | Kriterium | Variante A: Nur Mobile | Variante B: Alle Plattformen | |---|---|---| | Time-to-first-fix | schneller | mittel | | Komplexität gesamt | höher langfristig (2 Stacks) | niedriger langfristig (1 Stack) | | UX-Konsistenz | unterschiedlich je Device | einheitlich | | Wartung | doppelt | einfach | | Risiko | mittelhoch (Divergenz) | mittel (einmaliger Umbau) | **Empfehlung:** Variante B. ## Implementierungsplan ### Phase 1: Infrastruktur und Prototyp 1. STT-Worker als eigener Service im Gateway-Umfeld bereitstellen 2. Streaming-Protokoll definieren (Events: `audio`, `interim`, `final`, `status`, `error`, `close`) 3. WS-Route im Gateway für CommCoach implementieren 4. Test-Client mit Beispielaudio aufbauen (ohne UI) zur Last-/Latenzprüfung ### Phase 2: Frontend Integration 1. Neues Modul `useAudioStreamTranscription` einführen 2. `useVoiceController` auf Provider-Abstraktion umstellen: - Provider `browserSpeech` (bestehend) - Provider `streamedStt` (neu) 3. Transcript-Handling auf Serverevents umstellen: - Interim in `liveTranscript` - Final in `transcriptParts` 4. State-Transitions unverändert belassen, nur STT-Quelle ersetzen ### Phase 3: Segmentierung und Qualität 1. Serverseitige VAD aktivieren 2. Segment-Ende sauber in `onMessage` überführen 3. Doppelungen/Fragmentverluste mit Session-IDs und Segment-Counter verhindern 4. Mobile Netzwechsel/WS-Reconnect robust behandeln ### Phase 4: Rollout 1. Feature-Flag: zuerst intern, dann Pilotmandanten 2. Optionale Stufen: - Stufe 1: Nur Mobile - Stufe 2: Alle Plattformen 3. Browser-STT nach Stabilitätsphase vollständig entfernen ## Technische Leitplanken - Keine stillen Fallbacks, die Fehler verdecken - Explizite Fehlerzustände im UI (Mikrofon, Netzwerk, STT nicht verfügbar) - Klares Telemetrie-Set: - Time to first interim - Time to first final - Segment-Länge - Abbruchgründe - WS-Reconnect-Rate ## Drittkomponenten ### Minimal erforderlich - Python Packages (self-hosted): - `faster-whisper` - optional `webrtcvad` - Audio-Decode (`ffmpeg` Laufzeit) ### Nicht erforderlich - MSFT/Azure Speech - Externe STT-SaaS ## Risiken und Gegenmassnahmen - **GPU-Kapazität fehlt:** zunächst kleines Modell und Queueing, später GPU-Skalierung - **Latenz zu hoch:** Chunk-Grösse reduzieren, VAD feinjustieren, Modellwahl anpassen - **Mobile Netz instabil:** robustes Reconnect-Handling + Segment-ACKs - **Drift zwischen UI und Backend:** eindeutige Stream-/Segment-IDs und Idempotenzregeln ## Fazit Der Umbau ist vollständig in der eigenen Plattform machbar und löst das Mobile-5-Sekunden-Problem an der Wurzel. Für Wartbarkeit und konsistentes Verhalten ist ein **genereller Umbau (Variante B)** sinnvoll. Ein stufenweiser Rollout mit Feature-Flag minimiert Risiko. --- ## Implementierungsfreigabe (Ready for Build) Dieser Abschnitt ist die verbindliche Umsetzungsvorlage. ### Verbindliche Entscheidungen 1. **Scope** - Umsetzung als **Variante B** (ein Stack fuer Mobile + Desktop). 2. **Transport** - STT-Eingang erfolgt ueber **WebSocket**. - Text-/Assistant-Antworten bleiben vorerst auf bestehendem **SSE**-Pfad. 3. **STT-Engine** - Self-hosted Whisper-Pfad (`faster-whisper`) wird als neue Connector-Linie eingefuehrt. - Bestehender Google-One-Shot-Pfad bleibt nur als Legacy fuer `audio/stream`. 4. **Fehlerbehandlung** - Keine stillen Fallbacks. - Bei aktivem `streamedStt` wird ein STT-Fehler sichtbar ins UI gemeldet. 5. **Feature Flag** - `commcoachVoiceProvider = browserSpeech | streamedStt`. - Default initial: `browserSpeech`, Pilot: `streamedStt`. ### Verbindliche API Spezifikation #### WS Endpoint - `GET ws /api/commcoach/{instanceId}/sessions/{sessionId}/stt/stream` #### Client -> Server Nachrichten 1. `open` ```json { "type": "open", "sessionId": "string", "language": "de-DE", "codec": "pcm16", "sampleRate": 16000, "channels": 1 } ``` 2. `audio` ```json { "type": "audio", "seq": 1, "chunk": "base64-encoded-audio-bytes", "durationMs": 200 } ``` 3. `commit` ```json { "type": "commit", "reason": "silence" } ``` 4. `close` ```json { "type": "close" } ``` #### Server -> Client Nachrichten 1. `status` ```json { "type": "status", "label": "Sprache wird erkannt..." } ``` 2. `ack` ```json { "type": "ack", "seq": 1 } ``` 3. `interim` ```json { "type": "interim", "segmentId": "seg-uuid", "text": "teiltranskript" } ``` 4. `final` ```json { "type": "final", "segmentId": "seg-uuid", "text": "finales segment", "confidence": 0.91 } ``` 5. `error` ```json { "type": "error", "code": "stt_failed", "message": "Transkription fehlgeschlagen" } ``` 6. `closed` ```json { "type": "closed", "reason": "server" } ``` ### Orchestrierungsregeln (verbindlich) 1. `useVoiceController` bleibt Owner der Zustandsmaschine. 2. `streamedStt` liefert nur Transkript-Ereignisse, steuert keine State-Transitions direkt. 3. Nur `final` Texte duerfen `onMessage(...)` triggern. 4. `interim` aktualisiert nur `liveTranscript`. 5. Bei `ttsPlaying` wird STT-Stream pausiert/geschlossen. 6. Bei `ttsEnded` wird STT-Stream wieder geoeffnet. 7. `muted=true` blockiert Senden von `audio` Chunks. ### Konkrete Datei- und Funktionsaenderungen #### Frontend `frontend_nyla` 1. `src/api/commcoachApi.ts` - Neue Funktion `openSttStreamApi(instanceId, sessionId, handlers, options)`. - Rueckgabeobjekt mit `sendAudioChunk`, `sendCommit`, `close`. 2. `src/hooks/useAudioStreamTranscription.ts` (neu) - Intern: - `_startMicCapture()` - `_stopMicCapture()` - `_encodePcm16Chunk()` - `_pushChunkToWs()` - Extern: - `startStream()` - `stopStream()` - `commitSegment(reason)` 3. `src/pages/views/commcoach/useVoiceController.ts` - Provider-Layer einfuegen: - `browserSpeechProvider` - `streamedSttProvider` - `streamedSttProvider` auf neues Hook mappen. - Bestehende State-Transitionen unveraendert lassen. 4. `src/pages/views/commcoach/CommcoachDossierView.tsx` - Debugpanel um STT-WS Events erweitern (`STT-OPEN`, `STT-INTERIM`, `STT-FINAL`, `STT-ERR`, `STT-CLOSE`). #### Gateway `gateway` 1. `modules/features/commcoach/routeFeatureCommcoach.py` - Neue WS-Route `.../stt/stream`. - Auth/Ownership-Pruefung identisch zu bestehenden Session-Endpunkten. 2. `modules/features/commcoach/serviceCommcoachSttStream.py` (neu) - Session-Stream-Manager: - `_openSessionStream()` - `_handleAudioChunk()` - `_flushSegment()` - `_closeSessionStream()` - Final-Segmente an `CommcoachService.processMessage(...)` uebergeben. 3. `modules/interfaces/interfaceVoiceObjects.py` - Streaming API erweitern: - `startSttStream(...)` - `pushSttAudioChunk(...)` - `finalizeSttSegment(...)` - `stopSttStream(...)` 4. `modules/connectors/connectorVoiceWhisper.py` (neu) - Whisper-basierte Implementierung fuer Streaming-Segmente. - Config fuer Modellgroesse, Sprache, VAD. ### Reihenfolge fuer die Umsetzung 1. Gateway WS Route + Dummy-Events (ohne echte STT). 2. Frontend WS Client + Mikrofondaten senden. 3. Connector/Whisper Integration im Gateway. 4. Segmentierung und `final -> processMessage`. 5. Feature-Flag Integration und Pilot-Rollout. 6. Legacy-Bereinigung. ### Abnahmekriterien (Definition of Done) 1. **Mobile Stabilitaet** - 60 Sekunden durchgehendes Sprechen ohne erzwungenen 5-Sekunden-Reset. 2. **Textintegritaet** - Keine abgeschnittenen Saetze zwischen Segmenten. - Keine Duplikate bei finalen Segmenten. 3. **State Machine Integritaet** - Keine User-Transkripte waehrend `botSpeaking`. - `muted` blockiert Audio-Chunks sofort. 4. **Fehlertransparenz** - STT- oder WS-Fehler werden im UI sichtbar angezeigt. - Kein stiller Fallback auf Browser-STT im `streamedStt` Modus. 5. **Performance** - Time to first interim <= 1200 ms (WLAN Referenz). - Time to first final <= 2500 ms fuer kurze Saetze. ### Testplan (verbindlich) 1. Unit Tests - Segment-Assembler und Seq-Handling. - State Transition Tests fuer `ttsPlaying`, `ttsEnded`, `muted`. 2. Integration Tests - WS Open -> Audio -> Interim -> Final -> Close. - Reconnect nach Netzunterbruch. 3. Manuelle Geraetetests - iOS Safari (aktuell + 1 Vorversion). - Android Chrome (aktuell + 1 Vorversion). - Desktop Chrome/Edge. 4. Regression - Bestehende Text-SSE Flows unveraendert funktionsfaehig. - TTS Wiedergabe und Stop/Resume weiterhin stabil. ### Rollout und Backout 1. Rollout - Feature-Flag pilotweise pro Instanz aktivieren. - Metriken mindestens 3 Tage beobachten. 2. Backout - Bei kritischen Fehlern Flag auf `browserSpeech` zuruecksetzen. - Keine DB-Migrationen erforderlich. ### Aufwandsschaetzung - Gateway WS + Stream Service: 2-3 Tage - Whisper Connector + Tuning: 2-4 Tage - Frontend Hook + Provider Refactor: 2-3 Tage - Tests + Pilot-Hardening: 2-3 Tage Gesamt: **8-13 Arbeitstage** fuer produktionsreife Erstversion. ## Reuse Snippet (AI Workspace und weitere Views) Der Hook `useSpeechAudioCapture` ist generisch und kann ausserhalb von CommCoach wiederverwendet werden. Er kapselt Mikrofonzugriff, VAD, Segmentierung und Chunk-Emission. Beispielintegration in einer anderen View: ```tsx import React, { useState } from 'react'; import { useSpeechAudioCapture } from '../../hooks/useSpeechAudioCapture'; export default function WorkspaceVoiceInput() { const [isMicActive, setIsMicActive] = useState(false); const [isMuted, setIsMuted] = useState(false); const speech = useSpeechAudioCapture( { // Controller-Bedingung: nur aufnehmen wenn aktiv und nicht stumm isCaptureAllowed: () => isMicActive && !isMuted, // Laufende Audio-Chunks an den eigenen Stream senden onChunk: async (audioChunk) => { await wsSendChunk(audioChunk); // eigene WS/API Implementierung }, // Segmentabschluss bei Stille oder manuellem Stop onSegment: async ({ reason }) => { wsSendCommit(reason); // z. B. 'silence' oder 'manual' }, // Optionales Debugging onDebug: (tag, info) => console.log(tag, info), onError: (error) => console.error('Speech capture error', error), }, { silenceTimeoutMs: 1200, vadRmsThreshold: 0.03, vadIntervalMs: 120, minSegmentDurationMs: 450, recordingChunkMs: 250, }, ); const startMic = async () => { setIsMicActive(true); await speech.startCapture(); }; const stopMic = () => { setIsMicActive(false); speech.stopCapture('manual', true); }; return (
{speech.liveTranscript || 'Mikrofon bereit'}
); } ``` Minimalanforderungen fuer Reuse: - `isCaptureAllowed` muss den lokalen UI-State korrekt abbilden. - `onChunk` und `onSegment` muessen auf den Ziel-Transport (WS/SSE/API) gemappt werden. - `stopCapture(..., true)` soll bei View-Wechsel oder Unmount aufgerufen werden, um Mic sauber freizugeben.