Gesprächsaufnahmen mit lokalem Whisper und LLM verarbeiten
This commit is contained in:
1 parent
373081d33d
commit
55e5d34e83
39 files changed
+2931
-74
No files matched your search
@@ -48,13 +48,15 @@ Mit der Tab-Verwaltung meldete die App im `hello` `protocol: 6`, mit der Adresss
|
||||
unverändert geblieben. Implementiert in [protocol.ts](frontend/src/swyx/protocol.ts) und
|
||||
[SwyxTrayClient.ts](frontend/src/swyx/SwyxTrayClient.ts).
|
||||
|
||||
Ein Anruf wird über die **1-basierte Leitungsnummer `line`** identifiziert – so, wie sie auch
|
||||
in SwyxIt! erscheint. Eine Anruf-Id gibt es nicht.
|
||||
Leitungen werden über die **1-basierte Leitungsnummer `line`** adressiert.
|
||||
Seit Protokoll **10** tragen Snapshots und Anrufereignisse außerdem `callId` aus
|
||||
dem SDK. Aufnahmebefehle verlangen beide Werte, damit verspätete Befehle
|
||||
keinen Folgeanruf auf derselben Leitung treffen.
|
||||
|
||||
> **Die App schickt keine Ereignisnachrichten.** Jede Zustandsänderung – auch ein eingehender
|
||||
> Anruf – kommt als **vollständiger `snapshot`** über alle vier Leitungen. Die Anrufmeldung der
|
||||
> Startseite entsteht deshalb aus dem Vergleich zweier Snapshots
|
||||
> ([mergeSnapshot](frontend/src/swyx/protocol.ts)).
|
||||
Die App schickt vollständige `snapshot`-Nachrichten und zusätzlich `call`-Ereignisse
|
||||
(`incoming`, `outgoing`, `connected`, `ended`). SwyxWeb verarbeitet auch ältere
|
||||
Versionen ausschließlich über die Snapshots. Gesprächsenden werden bei beiden
|
||||
Nachrichtenreihenfolgen nur einmal in den Verlauf übernommen.
|
||||
|
||||
### Begrüßung (App → Seite, beim Verbinden)
|
||||
|
||||
@@ -526,24 +528,71 @@ Um das Frontend aus dem Backend auszuliefern, `frontend/dist/*` nach
|
||||
|
||||
## Deployment als Docker-Container
|
||||
|
||||
Das [Dockerfile](Dockerfile) im Wurzelverzeichnis erledigt genau das in drei Stufen: Frontend
|
||||
mit Node bauen, das Ergebnis in `backend/src/main/resources/static/` legen und mit Maven
|
||||
zusammen zum Jar packen, dieses in ein schlankes JRE-Image kopieren. Herauskommt **ein**
|
||||
Container, der Startseite und REST-API über Port 8080 ausliefert – als Benutzer `swyx`, nicht
|
||||
als `root`, mit einem Healthcheck gegen `/api/config`.
|
||||
Das [Dockerfile](Dockerfile) baut das React-Frontend und das Java-Backend in ein
|
||||
Image mit Java, Python und Whisper. Der Container liefert die Oberfläche und
|
||||
REST-API über Port **8080** aus und läuft als Benutzer `swyx` (UID 10001).
|
||||
Der Healthcheck prüft sowohl `/api/config` als auch den lokalen Whisper-Worker.
|
||||
|
||||
Die Verarbeitung erfolgt über **WAV → SwyxWeb → Whisper im selben Container →
|
||||
Transkript in SwyxWeb**. Verwendet wird `faster-whisper` mit `large-v3-turbo`,
|
||||
CPU und INT8. Das Modell wird beim Image-Build heruntergeladen und mitgeliefert;
|
||||
zur Laufzeit sind weder Modelldownload noch externer Transkriptionsdienst nötig.
|
||||
Der Worker lauscht ausschließlich auf `127.0.0.1:8765` innerhalb des Containers.
|
||||
Das Java-Backend verwendet dafür `APP_TRANSCRIPTION_BASE_URL=http://127.0.0.1:8765`.
|
||||
WAV-Dateien und Aufträge liegen dauerhaft im Volume unter `/data/transcriptions`.
|
||||
Ein Worker verarbeitet die Aufnahmen nacheinander; unterbrochene Aufträge werden
|
||||
beim nächsten Start wieder aufgenommen. Ist ein LLM konfiguriert, erstellt der
|
||||
Worker anschließend eine Zusammenfassung über dessen OpenAI-kompatible API.
|
||||
Die WAV bleibt lokal; an das konfigurierte LLM wird ausschließlich der Text gesendet.
|
||||
Eine Sprecherzuordnung ist nicht enthalten.
|
||||
|
||||
Mit [Docker Compose](compose.yaml):
|
||||
|
||||
```bash
|
||||
# Einmalig bei einem frischen Checkout; vorhandene .env bleibt erhalten.
|
||||
test -f .env || cp .env.example .env
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Die lokale `.env` enthält die interne Worker-Adresse, `WHISPER_THREADS`
|
||||
(Standard: 4), die LLM-Einstellungen, den SwyxWeb-Port und die SwyxTray-Adresse. Compose liest sie
|
||||
automatisch; auch die beiden VS-Code-
|
||||
Backend-Startkonfigurationen laden diese Datei. `.env` ist aus Git und
|
||||
Docker-Build-Kontext ausgeschlossen; [.env.example](.env.example) enthält
|
||||
nur öffentliche Standardwerte.
|
||||
|
||||
Oder direkt:
|
||||
|
||||
```bash
|
||||
docker build -t swyxweb .
|
||||
docker run -d -p 8080:8080 \
|
||||
--env-file .env \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
-e APP_WEBSOCKET_HOST=127.0.0.1 \
|
||||
-e APP_WEBSOCKET_PORT=17654 \
|
||||
-v swyxweb-transcriptions:/data/transcriptions \
|
||||
--name swyxweb swyxweb
|
||||
```
|
||||
|
||||
Die interne Worker-Adresse lautet `APP_TRANSCRIPTION_BASE_URL=http://127.0.0.1:8765`.
|
||||
Für den Container zunächst mindestens 4 GB RAM einplanen; der tatsächliche
|
||||
Bedarf hängt auch von Länge und Format der Aufnahmen ab. Das Image ist durch
|
||||
das mitgelieferte Modell größer und der erste Build benötigt einen Download.
|
||||
Ein anderes CTranslate2-Whisper-Modell kann über die beiden Build-Argumente
|
||||
`WHISPER_MODEL_REPO` und `WHISPER_MODEL_REVISION` ausgewählt werden.
|
||||
|
||||
Bei einer Unterbrechung bleibt die WAV in der SwyxTray-Warteschlange, bis der
|
||||
Worker die dauerhafte Speicherung mit einer Auftragskennung bestätigt.
|
||||
Das Volume enthält WAV-Dateien, Transkripte und Zusammenfassungen ohne automatische Löschfrist;
|
||||
`docker compose down` behält es, `docker compose down -v` löscht es.
|
||||
Beide Prozesse werden gemeinsam beendet; beendet sich einer unerwartet, endet
|
||||
auch der Container und Compose kann ihn neu starten.
|
||||
|
||||
Alle `app.websocket.*`-Werte sind zur Laufzeit über Umgebungsvariablen setzbar
|
||||
(`APP_WEBSOCKET_HOST`, `_PORT`, `_PATH`, `_SECURE`), das Bundle muss dafür nicht neu gebaut
|
||||
werden. Der Heap wächst über `-XX:MaxRAMPercentage=75` mit dem Speicherlimit des Containers
|
||||
mit; überschreiben lässt sich das mit `-e JAVA_OPTS=…`. Soll die WebSocket-Adresse ausnahmsweise
|
||||
(`APP_WEBSOCKET_HOST`, `_PORT`, `_PATH`, `_SECURE`), das Bundle muss dafür nicht
|
||||
neu gebaut werden. Der Java-Heap verwendet `-XX:MaxRAMPercentage=25.0`, damit
|
||||
Speicher für Whisper verfügbar bleibt;
|
||||
überschreibbar mit `-e JAVA_OPTS=…`. Soll die WebSocket-Adresse ausnahmsweise
|
||||
fest ins Bundle, geht das beim Bauen mit `--build-arg VITE_WS_URL=ws://…/ws`.
|
||||
|
||||
In die Registry schieben erledigt [docker_push.sh](docker_push.sh) – es baut für `linux/amd64`
|
||||
@@ -561,8 +610,9 @@ Ohne Argument bricht das Skript mit der Kurzhilfe ab. Zulässig ist das Format `
|
||||
|
||||
Ein lokaler Maven- oder npm-Lauf ist vorher **nicht** nötig; beides passiert im Image-Build.
|
||||
Die beiden Build-Stufen laufen dabei nativ auf der Architektur des bauenden Rechners
|
||||
(`--platform=$BUILDPLATFORM`) – JS-Bundle und Jar sind architekturunabhängig, nur das
|
||||
Laufzeit-Image wird für `linux/amd64` gezogen. Auf einem ARM-Mac spart das die Emulation.
|
||||
(`--platform=$BUILDPLATFORM`) – JS-Bundle und Jar sind architekturunabhängig.
|
||||
Das Laufzeit-Image samt Python-Abhängigkeiten wird für die Zielarchitektur gebaut;
|
||||
der Modelldownload läuft ebenfalls auf der Architektur des bauenden Rechners.
|
||||
|
||||
**Der SwyxTray-Mock ist nicht dabei**: `SPRING_PROFILES_ACTIVE` bleibt leer, also fehlen `/ws`
|
||||
und `/api/mock/**` (siehe [Ohne echte Telefonanlage testen](#ohne-echte-telefonanlage-testen)).
|
||||
@@ -590,3 +640,229 @@ Zwei Dinge, die der Container nicht lösen kann:
|
||||
aussagekräftige Browser-Fehlermeldung.
|
||||
- **System-Benachrichtigungen** verlangen eine Benutzerinteraktion zur Erlaubniserteilung und
|
||||
funktionieren nur über `https://` oder `localhost`.
|
||||
|
||||
|
||||
## Gesprächsaufnahme (Protokoll 10)
|
||||
|
||||
**Annehmen** in der Leitungsliste oder im Kundenpopup startet nach bestätigter
|
||||
Sprechverbindung automatisch eine Aufnahme. SwyxWeb sendet dafür zuerst `answer`
|
||||
und danach `recording.start` für dieselbe `line` und `callId`. Während eines
|
||||
Gesprächs stehen **Aufnahme starten**, **Aufnahme stoppen** und **Auflegen** auch
|
||||
im Dashboard zur Verfügung. Das Ende erscheint als Hinweis und im Ereignisverlauf.
|
||||
|
||||
```json
|
||||
{"id":21,"cmd":"recording.start","line":1,"callId":4711}
|
||||
{"id":22,"cmd":"recording.stop","line":1,"callId":4711}
|
||||
{"type":"call","event":"ended","line":1,"callId":4711,"peerNumber":"123"}
|
||||
```
|
||||
|
||||
Ein Ende, ein anderer Anruf auf derselben Leitung, eine unterbrochene Verbindung
|
||||
oder 15 Sekunden ohne Sprechverbindung brechen den automatischen Start ab.
|
||||
Eine abgewiesene Annahme startet keine Aufnahme. Ein Aufnahmefehler wird angezeigt,
|
||||
ohne die erfolgreiche Annahme als fehlgeschlagen auszugeben. Mit Tray-Versionen
|
||||
vor Protokoll 10 bleibt die Annahme nutzbar; die fehlende Aufnahmefunktion wird
|
||||
angezeigt. Die Tray-App muss nach ihrem Update neu gestartet werden.
|
||||
|
||||
`recordingRequested` zeigt den zuletzt vom SDK angenommenen Aufnahmeauftrag der
|
||||
Tray-App. Deshalb lautet die Anzeige **Aufnahme angefordert**: Das SDK liefert
|
||||
keinen lesbaren Aufnahmestatus oder Nachweis einer geschriebenen Datei. Änderungen
|
||||
direkt in SwyxIt! sind damit nicht nachweisbar. Dateiname und Speicherort bleiben
|
||||
in der Aufnahmekonfiguration von SwyxIt!; es wird kein Mikrofon des Browsers
|
||||
verwendet. Grundlage sind `IClientLineDisp.DispStartRecording()` und
|
||||
`DispStopRecording()` aus dem [Swyx Client SDK](https://www.swyxdownload.com/download/kb3373_SwyxClientSDKManual.pdf).
|
||||
|
||||
Der Backend-Mock im Profil `mock` bildet Protokoll 10 mit Anrufkennungen,
|
||||
Aufnahmebefehlen und Ende-Ereignissen ab. Er erzeugt keine Audiodateien.
|
||||
|
||||
Geprüft: Frontendtests einschließlich asynchroner Annahme, Fehler, Timeout,
|
||||
Leitungswiederverwendung und Verbindungsabbruch; Backend-Protokolltest für
|
||||
Annehmen → Start → Stop → Ende. Ein echter Audio-Mitschnitt muss auf dem
|
||||
Windows-Arbeitsplatz mit eingerichteter SwyxIt!-Aufnahme geprüft werden.
|
||||
|
||||
## Automatische WAV-Übergabe nach Gesprächsende
|
||||
|
||||
Mit SwyxTray **Protokoll 11** läuft die Übergabe automatisch:
|
||||
|
||||
1. SwyxTray ordnet dem über `recording.start` gestarteten Aufnahmeabschnitt eine UUID zu.
|
||||
2. Nach bestätigtem `recording.stop` oder Gesprächsende sucht die App die neue WAV im
|
||||
von SwyxIt! gemeldeten Aufnahmeordner (`IClientConfig.RecordedConversationsPath`).
|
||||
Ein Aufnahmestopp bereitet die Datei nur lokal vor und gibt sie noch nicht frei.
|
||||
3. Erst nach stabiler Dateigröße, abgeschlossenem RIFF-Dateikopf und exklusivem
|
||||
Lesezugriff erstellt sie eine dauerhafte Kopie in `%LOCALAPPDATA%\SwyxTray\recordings`.
|
||||
4. Erst nach **bestätigtem Gesprächsende** wird die fertige WAV **über die bereits
|
||||
offene WebSocket-Verbindung** in Blöcken an den Browser geschickt.
|
||||
Es gibt dafür keinen zusätzlichen HTTP-Dateiserver
|
||||
und keine Upload-Zieladresse in SwyxTray. Telefonbefehle bleiben währenddessen möglich.
|
||||
5. Der Browser übergibt sie an `POST /api/transcriptions` im SwyxWeb-Backend.
|
||||
Dieses leitet sie an `POST /api/jobs` des lokalen Whisper-Workers weiter, mit
|
||||
Sprache `de`, Zusammenfassungsart `style=meeting` und
|
||||
`Idempotency-Key: swyxweb:<recordingId>`.
|
||||
6. Whisper startet die Transkription automatisch. SwyxWeb fragt den Auftrag
|
||||
alle drei Sekunden ab und zeigt das **Gesprächstranskript direkt aufgeklappt**
|
||||
unter **Aufnahmen · Transkript & Zusammenfassung** an, sobald es vorliegt.
|
||||
Das konfigurierte LLM ergänzt anschließend die Zusammenfassung. Das Transkript
|
||||
bleibt auch sichtbar, wenn die Zusammenfassung fehlschlägt. Ein erneuter Versuch
|
||||
setzt dann direkt bei der Zusammenfassung an, ohne Whisper erneut auszuführen.
|
||||
|
||||
Der Browser bestätigt SwyxTray die Übergabe erst mit der vom Transkriptionsdienst
|
||||
zurückgegebenen Auftragskennung. Die Tray-App speichert die Bestätigung dauerhaft
|
||||
und entfernt dann ihre temporäre Kopie. **Die originale SwyxIt!-WAV bleibt erhalten.**
|
||||
Ohne Bestätigung bleiben Aufnahmen in der Warteschlange, auch nach einem Neustart.
|
||||
Beim Wiederverbinden werden offene Aufnahmen und die letzten 100 übergebenen
|
||||
Aufträge erneut gemeldet. Dadurch erscheint das Ergebnis auch nach einem Neuladen
|
||||
wieder, sobald der Browser mit derselben Tray-App verbunden ist.
|
||||
|
||||
Die SwyxWeb-Seite muss für die Übertragung geöffnet und verbunden sein.
|
||||
Übertragungs- und Serverfehler werden alle 15 Sekunden erneut versucht. Der Worker
|
||||
verhindert doppelte Aufträge anhand der UUID, des Dateihashes und der Einstellungen.
|
||||
Dauerhafte Ablehnungen (z. B. ungültige WAV oder Kennungskonflikt) werden angezeigt.
|
||||
Bei einer fehlgeschlagenen Verarbeitung lässt sich der bestehende Auftrag fortsetzen.
|
||||
|
||||
Ein bloßer COM-/Server-Verbindungsabbruch oder Halten gilt nicht als Gesprächsende.
|
||||
Die Übergabe ist unabhängig davon, wer oder welche Anwendung das Gespräch beendet:
|
||||
Auflegen in SwyxWeb, SwyxIt! oder am Telefon, Auflegen der Gegenstelle und die
|
||||
bestätigte Freigabe oder Wiederbelegung der Leitung lösen denselben Ablauf aus.
|
||||
Ein Auflege- oder Aufnahmestopp-Befehl aus SwyxWeb ist dafür nicht erforderlich.
|
||||
Endet das Gespräch während einer Unterbrechung, wird die wartende Aufnahme nach
|
||||
dem Wiederverbinden und der Bestätigung des Endes automatisch übergeben.
|
||||
Auch nach einem Aufnahmestopp bleiben Download und manuelle Übergabe bis zum Ende
|
||||
des Gesprächs gesperrt. **Auflegen** wird beim Klick deaktiviert und wartet auf
|
||||
die Ende-Bestätigung; eine reine Kommandoquittung reicht nicht. Danach verschwindet
|
||||
das Gespräch aus den belegten Leitungen. Ein verspäteter Auflegebefehl mit alter
|
||||
`callId` kann keine inzwischen neu belegte Leitung beenden.
|
||||
Bereits vorhandene WAV-Dateien werden nicht automatisch gewählt. Ist die Zuordnung
|
||||
mehrdeutig (z. B. mehrere neue Dateien oder überlappende Aufnahmeabschnitte), erscheint
|
||||
statt eines möglicherweise falschen Uploads ein Fehler mit manueller Dateiauswahl.
|
||||
Die Automatik erfasst die über SwyxWeb/Tray gestarteten Aufnahmeabschnitte; direkt
|
||||
in SwyxIt! gestartete Mitschnitte sind diesem Ablauf nicht automatisch zugeordnet.
|
||||
|
||||
### Lokale Transkription und LLM-Zusammenfassung
|
||||
|
||||
Im Docker-Container startet der Whisper-Worker automatisch. Für die Entwicklung
|
||||
ohne Docker kann er in einer eigenen Python-Umgebung gestartet werden; dazu muss
|
||||
ein vollständiges CTranslate2-Whisper-Modell bereits lokal vorliegen:
|
||||
|
||||
```sh
|
||||
python3 -m venv transcription/.venv
|
||||
transcription/.venv/bin/pip install -r transcription/requirements.txt
|
||||
WHISPER_MODEL_PATH=/pfad/zum/whisper-modell WHISPER_DATA_DIR=/pfad/zu/auftraegen \
|
||||
transcription/.venv/bin/python -m uvicorn service:app --app-dir transcription --host 127.0.0.1 --port 8765
|
||||
```
|
||||
|
||||
Die lokalen VS-Code-Backend-Startkonfigurationen lesen die Worker-Adresse aus
|
||||
der Projektdatei `.env`. Für einen Backend-Start im Terminal aus `backend`:
|
||||
|
||||
```sh
|
||||
APP_TRANSCRIPTION_BASE_URL=http://127.0.0.1:8765 ./mvnw spring-boot:run
|
||||
```
|
||||
|
||||
Außerhalb von Docker bleibt der Upload ohne diese Einstellung deaktiviert.
|
||||
Die Seite prüft die Konfiguration alle 30 Sekunden erneut. Die Worker-Adresse gilt
|
||||
aus Sicht des Java-Backends.
|
||||
|
||||
Die Zusammenfassung wird vollständig in diesem Projekt gesteuert. In `.env`
|
||||
stehen dafür folgende Einstellungen:
|
||||
|
||||
| Variable | Bedeutung |
|
||||
|---|---|
|
||||
| `LLM_BASE_URL` | OpenAI-kompatible Basis-URL, einschließlich eines benötigten `/v1`-Präfixes |
|
||||
| `LLM_MODEL` | Exakte Modell-ID des gewählten Servers |
|
||||
| `LLM_API_KEY` | Optionaler Bearer-API-Schlüssel; bleibt bei Servern ohne Authentifizierung leer |
|
||||
| `LLM_TIMEOUT_SECONDS` | Zeitlimit pro HTTP-Anfrage, Standard `120` Sekunden |
|
||||
| `LLM_CHUNK_CHARS` | Textlänge pro Abschnitt, Standard `12000` Zeichen; an das Kontextfenster des Modells anpassen |
|
||||
|
||||
`LLM_BASE_URL` und `LLM_MODEL` müssen gemeinsam gesetzt werden. Beide bleiben
|
||||
in der Vorlage leer: Dann arbeitet Whisper wie bisher ohne Zusammenfassung.
|
||||
Beispiel für ein LLM auf dem Docker-Host:
|
||||
|
||||
```dotenv
|
||||
LLM_BASE_URL=http://host.docker.internal:1234/v1
|
||||
LLM_MODEL=modell-id-des-servers
|
||||
LLM_API_KEY=
|
||||
```
|
||||
|
||||
Für einen anderen OpenAI-kompatiblen Server dessen Basis-URL und Modell-ID
|
||||
eintragen; bei OpenAI lautet die Basis-URL `https://api.openai.com/v1`.
|
||||
Der Worker sendet `POST <LLM_BASE_URL>/chat/completions` mit `model` und `messages`
|
||||
und liest `choices[0].message.content` gemäß der
|
||||
[Chat-Completions-API](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create).
|
||||
API-Schlüssel gelangen weder ins Frontend noch ins Image oder in Auftragsdaten.
|
||||
Compose übergibt die Variablen beim Start; Änderungen werden mit
|
||||
`docker compose up -d` übernommen. Beim Python-Start ohne Docker die `LLM_*`-Werte
|
||||
in der Umgebung des Workers setzen; das Java-Backend braucht diese Werte nicht.
|
||||
|
||||
Lange Transkripte werden abschnittsweise zusammengefasst und anschließend
|
||||
zusammengeführt. Unterstützt werden die Stile `standard`, `short` und `meeting`.
|
||||
Das Transkript wird vor dem LLM-Aufruf dauerhaft gespeichert. Bei Zeitüberschreitung,
|
||||
HTTP-Fehler, unvollständiger LLM-Konfiguration oder ungültiger Antwort wird der Auftrag als fehlgeschlagen angezeigt;
|
||||
**Verarbeitung erneut versuchen** wiederholt nur die Zusammenfassung. Dasselbe
|
||||
gilt nach einem Neustart während der Zusammenfassung. Bereits zuvor abgeschlossene
|
||||
Aufträge werden beim Aktivieren des LLM nicht automatisch erneut verarbeitet.
|
||||
|
||||
Nach dem Update SwyxTray und den SwyxWeb-Container mit dem neuen Stand starten
|
||||
und die Browserseite neu laden. Protokoll 10 und der Backend-Mock bieten weiterhin
|
||||
manuelle WAV-Zuordnung; der Mock erzeugt keine Audiodateien.
|
||||
|
||||
| SwyxWeb-Endpunkt | Funktion |
|
||||
|---|---|
|
||||
| `GET /api/transcriptions/config` | Aktivierungszustand und maximale Dateigröße |
|
||||
| `POST /api/transcriptions` | Multipart: `file` (WAV), `recordingId` (UUID), optional `language`, `style` |
|
||||
| `GET /api/transcriptions/{jobId}` | Status, Transkript und optionale Zusammenfassung |
|
||||
| `POST /api/transcriptions/{jobId}/retry` | Fehlgeschlagenen Auftrag fortsetzen |
|
||||
|
||||
### WebSocket-Dateiprotokoll 11
|
||||
|
||||
`recording.start`/`recording.stop`, ihre Quittungen und Snapshots tragen
|
||||
`recordingId`. Ein doppelter Start liefert die bereits bestehende UUID zurück.
|
||||
Nach Dateifertigstellung und bestätigtem Gesprächsende, beim Verbinden und auf
|
||||
`status` meldet SwyxTray:
|
||||
|
||||
```json
|
||||
{"type":"recording.ready","transport":"websocket","recordingId":"12345678-1234-1234-1234-123456789abc","line":1,"callId":4711,"callEnded":true,"filename":"12345678-1234-1234-1234-123456789abc.wav","bytes":320044}
|
||||
```
|
||||
|
||||
Der Browser fordert mit `{"id":23,"cmd":"recording.download","recordingId":"…"}`
|
||||
die Datei an. Jede binäre WebSocket-Nachricht enthält 12 Headerbytes:
|
||||
ASCII `SWYX`, danach Kommando-ID und Byte-Offset als jeweils 32-Bit-Little-Endian,
|
||||
anschließend höchstens 65536 WAV-Bytes. Die abschließende `result`-Quittung bestätigt
|
||||
das Ende des Downloads. Der Browser prüft Reihenfolge und die angekündigte Gesamtlänge
|
||||
(maximal 500 MB). Nach einem Abbruch wird die vollständige Datei erneut angefordert.
|
||||
|
||||
Nach erfolgreichem Backend-Upload folgt
|
||||
`{"id":24,"cmd":"recording.ack","recordingId":"…","jobId":"<32 Hexzeichen>"}`.
|
||||
`recording.status` meldet `waiting_call_end`, `waiting`, `error` oder `submitted`.
|
||||
`callEnded` bestätigt das Gesprächsende unabhängig vom Aufnahmestopp; vorher
|
||||
weist die Tray-App Downloads und Empfangsbestätigungen zurück. Bei `submitted`
|
||||
steht die Auftragskennung in `jobId`. Mehrere Tabs verwenden denselben Auftrag.
|
||||
Die frühere HTTP-`recording.ready`-Variante wird im Frontend weiterhin gelesen,
|
||||
aber von dieser Tray-Version nicht verwendet.
|
||||
|
||||
Geprüft sind Windows-Dateisperren, Dateifertigstellung, Mehrdeutigkeiten, Neustart,
|
||||
Binärtransfer über echte WebSockets, Abbruch/Wiederverbindung und Empfangsbestätigung;
|
||||
außerdem Frontend, Java-Weiterleitung und lokale WAV-Verarbeitung. Ein Browsertest
|
||||
lief über die echte Windows-Outbox und WebSocket-Session zur SwyxWeb-API,
|
||||
mit synthetischer WAV und simulierten Modellergebnissen.
|
||||
Ein echter SwyxIt!-Mitschnitt bleibt als Praxistest offen.
|
||||
|
||||
Für den integrierten Whisper-Worker wurden zusätzlich eine deutsche Sprach-WAV
|
||||
über die SwyxWeb-API in einem Container mit `--network none`, doppelte Uploads,
|
||||
die Wiederholung eines fehlgeschlagenen Auftrags, ein Container-Neustart und ein
|
||||
Worker-Abbruch während der Transkription geprüft. Der unterbrochene Auftrag wurde
|
||||
nach dem Start automatisch abgeschlossen; fertige Transkripte blieben erhalten.
|
||||
Die LLM-Anbindung wurde mit einem lokalen OpenAI-kompatiblen HTTP-Testserver
|
||||
geprüft: Request-Format, optionaler Bearer-Schlüssel, Zeitüberschreitungen,
|
||||
Fehlerantworten, lange Transkripte und Wiederaufnahme der Zusammenfassung.
|
||||
Ein Container-Test mit echtem Whisper und simulierter LLM-Antwort bestätigt,
|
||||
dass ein HTTP-503-Fehler das Transkript erhält und der erneute Versuch ohne
|
||||
erneute WAV-Verarbeitung gelingt. Zusätzlich wurde eine synthetische deutsche
|
||||
Sprach-WAV vollständig über die SwyxWeb-API mit echtem Whisper im Container und
|
||||
einem echten OpenAI-kompatiblen LLM verarbeitet. Transkript und Zusammenfassung
|
||||
wurden dabei erfolgreich erstellt; die Verbindungsdaten bleiben in der lokalen `.env`.
|
||||
Die Worker-Tests einschließlich echter WAV-Dekodierung lassen sich mit Python
|
||||
und den Testabhängigkeiten ausführen:
|
||||
|
||||
```sh
|
||||
python3 -m venv transcription/.venv
|
||||
transcription/.venv/bin/pip install -r transcription/requirements-test.txt
|
||||
transcription/.venv/bin/pytest -q transcription/tests
|
||||
```
|
||||
Reference in new issue
Block a user