fix
This commit is contained in:
parent
7f5f31db30
commit
ebaaf77942
2 changed files with 327 additions and 0 deletions
|
|
@ -332,3 +332,330 @@ Ersetzt wird:
|
||||||
Der Umbau ist vollständig in der eigenen Plattform machbar und löst das Mobile-5-Sekunden-Problem an der Wurzel.
|
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.
|
Für Wartbarkeit und konsistentes Verhalten ist ein **genereller Umbau (Variante B)** sinnvoll.
|
||||||
Ein stufenweiser Rollout mit Feature-Flag minimiert Risiko.
|
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 (
|
||||||
|
<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:
|
||||||
|
- `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.
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in a new issue