20 KiB
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+AudioWorkletoderMediaRecorder - 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,mutedorthogonal)
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-whisperals Runtime (GPU empfohlen, CPU möglich)- Sprachen konfigurierbar (z. B.
de,en) - Ausgabe:
interimEvents (optional, je nach Latenzbudget)finalSegmente- 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
SpeechRecognitiondirekt. - Auto-Restart bei
onendmitREC_AUTORESTART_DELAY_MS = 300. SILENCE_TIMEOUT_MS = 1000finalisiert User-Text.- Mobile-Problem entsteht hier durch Browser-
onend-Zwang (alle ~5-7s).
- Nutzt Browser
-
src/pages/views/commcoach/CommcoachDossierView.tsx- Bindet Voice-State-Machine an UI/TTS.
voice.liveTranscriptwird 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.
- Text läuft über
-
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.pyPOST .../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.pyprocessAudioMessage(...): STT auf gesamtem Blob, danachprocessMessage(...).- 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
recognizeauf Einzeldateien (nicht Streaming). - Damit strukturell ungeeignet für kontinuierliche Mobile-Spracheingabe.
- Verwendet
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
-
Neue API-Funktion in
src/api/commcoachApi.tsopenSttStreamApi(instanceId, sessionId, handlers, options)via WebSocket.- Handler:
onStatus,onInterim,onFinal,onError,onClose.
-
Neues Hook
src/hooks/useAudioStreamTranscription.ts- Mikrofon aufnehmen (
MediaStream+AudioWorkletoderMediaRecorder). - Audio in 100-250ms Chunks an WS senden.
- Event-Rückkanal verarbeiten (interim/final/status/error).
- Reconnect-Mechanismus für Mobile-Netzwechsel.
- Mikrofon aufnehmen (
-
src/pages/views/commcoach/useVoiceController.tsrefactor- Provider-Interface einführen:
browserSpeech(legacy)streamedStt(neu)
transcriptPartsRefundliveTranscriptaus Serverevents speisen.SILENCE_TIMEOUT_MSnur noch als optionales Guard-Rail, nicht als Kernsegmentierung.
- Provider-Interface einführen:
-
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
-
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.
- Beispiel:
-
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.
-
Anpassung
modules/features/commcoach/serviceCommcoach.pyprocessAudioMessagebleibt für Legacy/Upload kompatibel.- Neue Streaming-Nutzung erfolgt über den neuen STT-Stream-Service.
-
Erweiterung
modules/interfaces/interfaceVoiceObjects.py- Neue Methoden ergänzen:
startSttStream(...)pushSttAudioChunk(...)stopSttStream(...)
- Bestehende Methoden (
speechToText,textToSpeech) unverändert lassen.
- Neue Methoden ergänzen:
C) STT Worker (self-hosted)
-
Neuer Connector (z. B.)
modules/connectors/connectorVoiceWhisper.pyfaster-whisperIntegration.- Modell, Sprache, VAD-Parameter konfigurierbar.
-
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": "<base64>" }
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)
-
Feature-Flag
commcoachVoiceProviderauf Instanzebene- Werte:
browserSpeech|streamedStt.
- Werte:
-
Rolloutpfad
- Schritt 1: intern auf Mobile aktivieren.
- Schritt 2: nach Stabilisierung global aktivieren.
- Schritt 3: Browser-STT-Code entfernen.
-
Expliziter Fehlerpfad
- Kein stiller Fallback auf Browser-STT, wenn
streamedSttaktiv ist. - Fehler wird sichtbar im UI (Banner + Debuglog).
- Kein stiller Fallback auf Browser-STT, wenn
Konkrete Taskliste pro Repository
Repo frontend_nyla
commcoachApi.ts:openSttStreamApiergänzen.- Neues Hook
useAudioStreamTranscription.tsimplementieren. useVoiceController.ts: Provider-Abstraktion + streamed Provider integrieren.CommcoachDossierView.tsx: Provider-Init + Debug-Events anzeigen.- Tests:
- unit: Segment-Assembler
- integration: state transitions bei interim/final/error
Repo gateway
- WS-Route für STT-Stream in
routeFeatureCommcoach.py. - Neuer Stream-Service
serviceCommcoachSttStream.py. interfaceVoiceObjects.pyum Streaming-Methoden erweitern.- Neuer Whisper-Connector
connectorVoiceWhisper.py. - 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
- STT-Worker als eigener Service im Gateway-Umfeld bereitstellen
- Streaming-Protokoll definieren (Events:
audio,interim,final,status,error,close) - WS-Route im Gateway für CommCoach implementieren
- Test-Client mit Beispielaudio aufbauen (ohne UI) zur Last-/Latenzprüfung
Phase 2: Frontend Integration
- Neues Modul
useAudioStreamTranscriptioneinführen useVoiceControllerauf Provider-Abstraktion umstellen:- Provider
browserSpeech(bestehend) - Provider
streamedStt(neu)
- Provider
- Transcript-Handling auf Serverevents umstellen:
- Interim in
liveTranscript - Final in
transcriptParts
- Interim in
- State-Transitions unverändert belassen, nur STT-Quelle ersetzen
Phase 3: Segmentierung und Qualität
- Serverseitige VAD aktivieren
- Segment-Ende sauber in
onMessageüberführen - Doppelungen/Fragmentverluste mit Session-IDs und Segment-Counter verhindern
- Mobile Netzwechsel/WS-Reconnect robust behandeln
Phase 4: Rollout
- Feature-Flag: zuerst intern, dann Pilotmandanten
- Optionale Stufen:
- Stufe 1: Nur Mobile
- Stufe 2: Alle Plattformen
- 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 (
ffmpegLaufzeit)
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
-
Scope
- Umsetzung als Variante B (ein Stack fuer Mobile + Desktop).
-
Transport
- STT-Eingang erfolgt ueber WebSocket.
- Text-/Assistant-Antworten bleiben vorerst auf bestehendem SSE-Pfad.
-
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.
- Self-hosted Whisper-Pfad (
-
Fehlerbehandlung
- Keine stillen Fallbacks.
- Bei aktivem
streamedSttwird ein STT-Fehler sichtbar ins UI gemeldet.
-
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
open
{
"type": "open",
"sessionId": "string",
"language": "de-DE",
"codec": "pcm16",
"sampleRate": 16000,
"channels": 1
}
audio
{
"type": "audio",
"seq": 1,
"chunk": "base64-encoded-audio-bytes",
"durationMs": 200
}
commit
{
"type": "commit",
"reason": "silence"
}
close
{
"type": "close"
}
Server -> Client Nachrichten
status
{
"type": "status",
"label": "Sprache wird erkannt..."
}
ack
{
"type": "ack",
"seq": 1
}
interim
{
"type": "interim",
"segmentId": "seg-uuid",
"text": "teiltranskript"
}
final
{
"type": "final",
"segmentId": "seg-uuid",
"text": "finales segment",
"confidence": 0.91
}
error
{
"type": "error",
"code": "stt_failed",
"message": "Transkription fehlgeschlagen"
}
closed
{
"type": "closed",
"reason": "server"
}
Orchestrierungsregeln (verbindlich)
useVoiceControllerbleibt Owner der Zustandsmaschine.streamedSttliefert nur Transkript-Ereignisse, steuert keine State-Transitions direkt.- Nur
finalTexte duerfenonMessage(...)triggern. interimaktualisiert nurliveTranscript.- Bei
ttsPlayingwird STT-Stream pausiert/geschlossen. - Bei
ttsEndedwird STT-Stream wieder geoeffnet. muted=trueblockiert Senden vonaudioChunks.
Konkrete Datei- und Funktionsaenderungen
Frontend frontend_nyla
-
src/api/commcoachApi.ts- Neue Funktion
openSttStreamApi(instanceId, sessionId, handlers, options). - Rueckgabeobjekt mit
sendAudioChunk,sendCommit,close.
- Neue Funktion
-
src/hooks/useAudioStreamTranscription.ts(neu)- Intern:
_startMicCapture()_stopMicCapture()_encodePcm16Chunk()_pushChunkToWs()
- Extern:
startStream()stopStream()commitSegment(reason)
- Intern:
-
src/pages/views/commcoach/useVoiceController.ts- Provider-Layer einfuegen:
browserSpeechProviderstreamedSttProvider
streamedSttProviderauf neues Hook mappen.- Bestehende State-Transitionen unveraendert lassen.
- Provider-Layer einfuegen:
-
src/pages/views/commcoach/CommcoachDossierView.tsx- Debugpanel um STT-WS Events erweitern (
STT-OPEN,STT-INTERIM,STT-FINAL,STT-ERR,STT-CLOSE).
- Debugpanel um STT-WS Events erweitern (
Gateway gateway
-
modules/features/commcoach/routeFeatureCommcoach.py- Neue WS-Route
.../stt/stream. - Auth/Ownership-Pruefung identisch zu bestehenden Session-Endpunkten.
- Neue WS-Route
-
modules/features/commcoach/serviceCommcoachSttStream.py(neu)- Session-Stream-Manager:
_openSessionStream()_handleAudioChunk()_flushSegment()_closeSessionStream()
- Final-Segmente an
CommcoachService.processMessage(...)uebergeben.
- Session-Stream-Manager:
-
modules/interfaces/interfaceVoiceObjects.py- Streaming API erweitern:
startSttStream(...)pushSttAudioChunk(...)finalizeSttSegment(...)stopSttStream(...)
- Streaming API erweitern:
-
modules/connectors/connectorVoiceWhisper.py(neu)- Whisper-basierte Implementierung fuer Streaming-Segmente.
- Config fuer Modellgroesse, Sprache, VAD.
Reihenfolge fuer die Umsetzung
- Gateway WS Route + Dummy-Events (ohne echte STT).
- Frontend WS Client + Mikrofondaten senden.
- Connector/Whisper Integration im Gateway.
- Segmentierung und
final -> processMessage. - Feature-Flag Integration und Pilot-Rollout.
- Legacy-Bereinigung.
Abnahmekriterien (Definition of Done)
-
Mobile Stabilitaet
- 60 Sekunden durchgehendes Sprechen ohne erzwungenen 5-Sekunden-Reset.
-
Textintegritaet
- Keine abgeschnittenen Saetze zwischen Segmenten.
- Keine Duplikate bei finalen Segmenten.
-
State Machine Integritaet
- Keine User-Transkripte waehrend
botSpeaking. mutedblockiert Audio-Chunks sofort.
- Keine User-Transkripte waehrend
-
Fehlertransparenz
- STT- oder WS-Fehler werden im UI sichtbar angezeigt.
- Kein stiller Fallback auf Browser-STT im
streamedSttModus.
-
Performance
- Time to first interim <= 1200 ms (WLAN Referenz).
- Time to first final <= 2500 ms fuer kurze Saetze.
Testplan (verbindlich)
-
Unit Tests
- Segment-Assembler und Seq-Handling.
- State Transition Tests fuer
ttsPlaying,ttsEnded,muted.
-
Integration Tests
- WS Open -> Audio -> Interim -> Final -> Close.
- Reconnect nach Netzunterbruch.
-
Manuelle Geraetetests
- iOS Safari (aktuell + 1 Vorversion).
- Android Chrome (aktuell + 1 Vorversion).
- Desktop Chrome/Edge.
-
Regression
- Bestehende Text-SSE Flows unveraendert funktionsfaehig.
- TTS Wiedergabe und Stop/Resume weiterhin stabil.
Rollout und Backout
-
Rollout
- Feature-Flag pilotweise pro Instanz aktivieren.
- Metriken mindestens 3 Tage beobachten.
-
Backout
- Bei kritischen Fehlern Flag auf
browserSpeechzuruecksetzen. - Keine DB-Migrationen erforderlich.
- Bei kritischen Fehlern Flag auf
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:
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 (
<div>
<button onClick={startMic}>Mic Start</button>
<button onClick={stopMic}>Mic Stop</button>
<button onClick={() => setIsMuted(v => !v)}>{isMuted ? 'Unmute' : 'Mute'}</button>
<div>{speech.liveTranscript || 'Mikrofon bereit'}</div>
</div>
);
}
Minimalanforderungen fuer Reuse:
isCaptureAllowedmuss den lokalen UI-State korrekt abbilden.onChunkundonSegmentmuessen auf den Ziel-Transport (WS/SSE/API) gemappt werden.stopCapture(..., true)soll bei View-Wechsel oder Unmount aufgerufen werden, um Mic sauber freizugeben.