Diktat: kein automatisches Laden mehr (docs/04-diktat.md)
This commit is contained in:
+132
-129
@@ -1,129 +1,132 @@
|
||||
# 04 Diktat (Whisper im Browser)
|
||||
|
||||
Überall dort, wo längerer Text entsteht – Notiz am Kontakt, Notizen an Lead und
|
||||
Unternehmen, Projektnotizen, Ausschreibungstext – sitzt ein Mikrofon-Knopf. Die
|
||||
Erkennung läuft **vollständig im Browser des Benutzers**.
|
||||
|
||||
Das Backend ist daran nicht beteiligt und sieht keine Audiodaten: kein
|
||||
API-Schlüssel, keine Kosten pro Minute, kein Datenabfluss. Aufnahmen werden
|
||||
nirgends gespeichert.
|
||||
|
||||
Die Umsetzung ist aus dem perinco CRM übernommen und technisch identisch – wer
|
||||
das eine kennt, findet sich im anderen zurecht.
|
||||
|
||||
## Weg der Aufnahme
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
MIC(("Mikrofon")) --> MR["MediaRecorder<br/>webm / ogg / mp4"]
|
||||
MR --> AC["AudioContext + OfflineAudioContext<br/>16 kHz Mono PCM"]
|
||||
AC --> W["Web Worker"]
|
||||
W --> ONNX["Whisper als ONNX<br/>WebGPU, sonst WASM"]
|
||||
ONNX --> T["Text"]
|
||||
T --> F["Formularfeld<br/>angehaengt, nie ersetzt"]
|
||||
|
||||
HF["Hugging-Face-CDN"] -.->|"einmalig ~300 MB"| CACHE[("Cache-Speicher<br/>der Seite")]
|
||||
CACHE --> ONNX
|
||||
```
|
||||
|
||||
Die Erkennung liegt im Web Worker (`src/lib/whisper.worker.ts`), damit die
|
||||
Oberfläche währenddessen nicht einfriert. Die ONNX-Laufzeit wird **mit
|
||||
ausgeliefert** (Vite bündelt die `.wasm`, rund 21 MB unter `dist/assets/`), nur
|
||||
die Modelldateien kommen beim ersten Laden vom CDN und bleiben danach im
|
||||
Cache-Speicher der Seite.
|
||||
|
||||
## Festlegungen
|
||||
|
||||
Es gibt bewusst nichts einzustellen (`src/lib/whisper.tsx`):
|
||||
|
||||
| Festlegung | Wert | Warum |
|
||||
| --- | --- | --- |
|
||||
| Modell | `onnx-community/whisper-large-v3-turbo` | beste Erkennung im Deutschen, auch bei Fachbegriffen („S4 HANA"); `small` verliert genau dort, `tiny` ist unbrauchbar |
|
||||
| Genauigkeit | `q4` | `q8` liefert auf der Grafikkarte Unsinn, `fp16` blockiert den Tab, `fp32` passt nicht in einen Speicherblock |
|
||||
| Rechenweg | WebGPU, sonst WASM | automatisch; ohne Grafikbeschleunigung dauert es länger, funktioniert aber |
|
||||
| Sprache | Deutsch | fest |
|
||||
|
||||
## Lebenszyklus des Modells
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> idle: Anmeldung
|
||||
idle --> loading: "Sprachmodell laden" oder Cache vorhanden
|
||||
loading --> ready: Worker meldet "ready"
|
||||
loading --> error: Speicher, Abbruch, 2 min ohne Regung
|
||||
ready --> idle: "Entladen"
|
||||
ready --> idle: "Sprachmodell loeschen" (Cache geleert)
|
||||
error --> loading: erneuter Versuch
|
||||
```
|
||||
|
||||
* **Liegt das Modell im Cache, stellt es sich beim Anmelden von selbst bereit.**
|
||||
Einmal herunterladen genügt also.
|
||||
* **Ohne Cache passiert nichts von allein** – ein Download von 300 MB startet nie
|
||||
unaufgefordert.
|
||||
* Der Selbstversuch läuft genau **einmal je Seitenaufruf**: wer das Modell
|
||||
absichtlich löscht oder entlädt, will es nicht sofort wiederhaben.
|
||||
* Vor dem Download bittet die Anwendung um **dauerhaften Speicher**
|
||||
(`navigator.storage.persist`). Chrome darf ablehnen; dann funktioniert alles
|
||||
genauso, nur ohne diese Zusicherung.
|
||||
|
||||
### Hänger werden erkannt
|
||||
|
||||
Zwei Fristen, beide mit verwertbarer Meldung statt endlosem „Lädt …":
|
||||
|
||||
| Frist | Wann |
|
||||
| --- | --- |
|
||||
| 120 s ohne Regung des Workers | beim Laden – meist scheitert das Anlegen der ONNX-Sitzung am Speicher |
|
||||
| max(45 s, 15 × Aufnahmedauer), höchstens 300 s | bei einer Erkennung |
|
||||
|
||||
In beiden Fällen wird der Worker beendet; ein neuer Versuch bekommt einen
|
||||
frischen.
|
||||
|
||||
## Bedienung
|
||||
|
||||
*Einstellungen → Diktat* (für jede Rolle sichtbar, denn jeder richtet sein Gerät
|
||||
selbst ein):
|
||||
|
||||
1. **Sprachmodell laden** – zeigt die tatsächliche Downloadgröße, bei Hugging
|
||||
Face erfragt (`src/lib/whisperModels.ts`), und einen Fortschrittsbalken.
|
||||
2. **Probe sprechen** – kurz etwas diktieren und den erkannten Text ansehen.
|
||||
3. **Sprachmodell löschen** – entfernt es aus diesem Browser.
|
||||
|
||||
Danach steht in den Formularen ein Mikrofon-Knopf. Während der Aufnahme läuft
|
||||
die Zeit, daneben liegt ein Kreuz zum Verwerfen.
|
||||
|
||||
Der Knopf zeigt drei Zustände:
|
||||
|
||||
| Zustand | Was zu sehen ist |
|
||||
| --- | --- |
|
||||
| kein Mikrofon / kein HTTPS | gar nichts – ein toter Knopf wäre schlechter |
|
||||
| Modell fehlt | *Diktat einrichten*, führt direkt in den Reiter |
|
||||
| bereit | Mikrofon, dann laufende Zeit, dann *Erkenne …* |
|
||||
|
||||
Erkannter Text wird **angehängt**, nie ersetzt: in den WYSIWYG-Feldern als
|
||||
eigener Absatz am Ende, in den Textfeldern hinter dem Vorhandenen. Ins DOM des
|
||||
Editors zu schreiben wäre die Alternative – dann hinge das Ergebnis aber an der
|
||||
Schreibmarke, die während der Aufnahme längst woanders stehen kann.
|
||||
|
||||
## Dateien
|
||||
|
||||
| Datei | Aufgabe |
|
||||
| --- | --- |
|
||||
| `src/lib/whisper.tsx` | Kontext: Zustand, Fortschritt, Cache-Erkennung, Fähigkeiten des Geräts |
|
||||
| `src/lib/whisper.worker.ts` | Pipeline, Erkennung, Fehler auch außerhalb der await-Kette |
|
||||
| `src/lib/dictation.tsx` | `useRecorder` – Mikrofon, Dauer, Abbruch |
|
||||
| `src/lib/audio.ts` | webm/ogg/mp4 → 16 kHz Mono Float32 |
|
||||
| `src/lib/whisperModels.ts` | Downloadgröße, Formatierung |
|
||||
| `src/components/ui/Dictate.tsx` | `DictateButton`, `DictateTextarea`, `appendParagraph` |
|
||||
| `src/components/settings/WhisperSettings.tsx` | der Reiter |
|
||||
|
||||
Abhängigkeit: `@huggingface/transformers` (^3.8.1).
|
||||
|
||||
## Störungssuche
|
||||
|
||||
| Symptom | Ursache |
|
||||
| --- | --- |
|
||||
| Kein Mikrofon-Knopf zu sehen | kein `MediaRecorder` oder keine sichere Verbindung (HTTPS) |
|
||||
| „Kein Zugriff aufs Mikrofon" | Berechtigung im Browser verweigert |
|
||||
| Erkennung sehr langsam | keine WebGPU – der Reiter sagt es ausdrücklich |
|
||||
| Modell nach Neustart wieder weg | privates Fenster oder Speicherbeschränkung; der Reiter weist darauf hin |
|
||||
| „Der Speicher reicht … nicht" | zu wenig Arbeitsspeicher für die ONNX-Sitzung |
|
||||
# 04 Diktat (Whisper im Browser)
|
||||
|
||||
Überall dort, wo längerer Text entsteht – Notiz am Kontakt, Notizen an Lead und
|
||||
Unternehmen, Projektnotizen, Ausschreibungstext – sitzt ein Mikrofon-Knopf. Die
|
||||
Erkennung läuft **vollständig im Browser des Benutzers**.
|
||||
|
||||
Das Backend ist daran nicht beteiligt und sieht keine Audiodaten: kein
|
||||
API-Schlüssel, keine Kosten pro Minute, kein Datenabfluss. Aufnahmen werden
|
||||
nirgends gespeichert.
|
||||
|
||||
Die Umsetzung ist aus dem perinco CRM übernommen und technisch identisch – wer
|
||||
das eine kennt, findet sich im anderen zurecht.
|
||||
|
||||
## Weg der Aufnahme
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
MIC(("Mikrofon")) --> MR["MediaRecorder<br/>webm / ogg / mp4"]
|
||||
MR --> AC["AudioContext + OfflineAudioContext<br/>16 kHz Mono PCM"]
|
||||
AC --> W["Web Worker"]
|
||||
W --> ONNX["Whisper als ONNX<br/>WebGPU, sonst WASM"]
|
||||
ONNX --> T["Text"]
|
||||
T --> F["Formularfeld<br/>angehaengt, nie ersetzt"]
|
||||
|
||||
HF["Hugging-Face-CDN"] -.->|"einmalig ~300 MB"| CACHE[("Cache-Speicher<br/>der Seite")]
|
||||
CACHE --> ONNX
|
||||
```
|
||||
|
||||
Die Erkennung liegt im Web Worker (`src/lib/whisper.worker.ts`), damit die
|
||||
Oberfläche währenddessen nicht einfriert. Die ONNX-Laufzeit wird **mit
|
||||
ausgeliefert** (Vite bündelt die `.wasm`, rund 21 MB unter `dist/assets/`), nur
|
||||
die Modelldateien kommen beim ersten Laden vom CDN und bleiben danach im
|
||||
Cache-Speicher der Seite.
|
||||
|
||||
## Festlegungen
|
||||
|
||||
Es gibt bewusst nichts einzustellen (`src/lib/whisper.tsx`):
|
||||
|
||||
| Festlegung | Wert | Warum |
|
||||
| --- | --- | --- |
|
||||
| Modell | `onnx-community/whisper-large-v3-turbo` | beste Erkennung im Deutschen, auch bei Fachbegriffen („S4 HANA"); `small` verliert genau dort, `tiny` ist unbrauchbar |
|
||||
| Genauigkeit | `q4` | `q8` liefert auf der Grafikkarte Unsinn, `fp16` blockiert den Tab, `fp32` passt nicht in einen Speicherblock |
|
||||
| Rechenweg | WebGPU, sonst WASM | automatisch; ohne Grafikbeschleunigung dauert es länger, funktioniert aber |
|
||||
| Sprache | Deutsch | fest |
|
||||
|
||||
## Lebenszyklus des Modells
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> idle: Anmeldung
|
||||
idle --> loading: Klick auf "Sprachmodell laden" / "Diktat aktivieren"
|
||||
loading --> ready: Worker meldet "ready"
|
||||
loading --> error: Speicher, Abbruch, 2 min ohne Regung
|
||||
ready --> idle: "Entladen"
|
||||
ready --> idle: "Sprachmodell loeschen" (Cache geleert)
|
||||
error --> loading: erneuter Versuch
|
||||
```
|
||||
|
||||
* **Es lädt nichts von selbst** – weder der Download noch das Aktivieren aus dem
|
||||
Cache. Beides braucht einen Klick.
|
||||
* Der Grund ist gemessen, nicht vermutet: das Anlegen der ONNX-Sitzung übersetzt
|
||||
hunderte Shader und legt dabei Zeiger und Darstellung für einige Sekunden
|
||||
still. Lief das beim Anmelden von selbst, sah die ganze Anwendung aus, als
|
||||
hinge sie – und zwar genau dann, wenn jemand gerade eine Lead-Kachel ziehen
|
||||
wollte.
|
||||
* Liegt das Modell im Cache, heißt der Knopf *Diktat aktivieren* und der
|
||||
Download entfällt.
|
||||
* Vor dem Download bittet die Anwendung um **dauerhaften Speicher**
|
||||
(`navigator.storage.persist`). Chrome darf ablehnen; dann funktioniert alles
|
||||
genauso, nur ohne diese Zusicherung.
|
||||
|
||||
### Hänger werden erkannt
|
||||
|
||||
Zwei Fristen, beide mit verwertbarer Meldung statt endlosem „Lädt …":
|
||||
|
||||
| Frist | Wann |
|
||||
| --- | --- |
|
||||
| 120 s ohne Regung des Workers | beim Laden – meist scheitert das Anlegen der ONNX-Sitzung am Speicher |
|
||||
| max(45 s, 15 × Aufnahmedauer), höchstens 300 s | bei einer Erkennung |
|
||||
|
||||
In beiden Fällen wird der Worker beendet; ein neuer Versuch bekommt einen
|
||||
frischen.
|
||||
|
||||
## Bedienung
|
||||
|
||||
*Einstellungen → Diktat* (für jede Rolle sichtbar, denn jeder richtet sein Gerät
|
||||
selbst ein):
|
||||
|
||||
1. **Sprachmodell laden** – zeigt die tatsächliche Downloadgröße, bei Hugging
|
||||
Face erfragt (`src/lib/whisperModels.ts`), und einen Fortschrittsbalken.
|
||||
2. **Probe sprechen** – kurz etwas diktieren und den erkannten Text ansehen.
|
||||
3. **Sprachmodell löschen** – entfernt es aus diesem Browser.
|
||||
|
||||
Danach steht in den Formularen ein Mikrofon-Knopf. Während der Aufnahme läuft
|
||||
die Zeit, daneben liegt ein Kreuz zum Verwerfen.
|
||||
|
||||
Der Knopf zeigt drei Zustände:
|
||||
|
||||
| Zustand | Was zu sehen ist |
|
||||
| --- | --- |
|
||||
| kein Mikrofon / kein HTTPS | gar nichts – ein toter Knopf wäre schlechter |
|
||||
| Modell fehlt | *Diktat einrichten*, führt direkt in den Reiter |
|
||||
| bereit | Mikrofon, dann laufende Zeit, dann *Erkenne …* |
|
||||
|
||||
Erkannter Text wird **angehängt**, nie ersetzt: in den WYSIWYG-Feldern als
|
||||
eigener Absatz am Ende, in den Textfeldern hinter dem Vorhandenen. Ins DOM des
|
||||
Editors zu schreiben wäre die Alternative – dann hinge das Ergebnis aber an der
|
||||
Schreibmarke, die während der Aufnahme längst woanders stehen kann.
|
||||
|
||||
## Dateien
|
||||
|
||||
| Datei | Aufgabe |
|
||||
| --- | --- |
|
||||
| `src/lib/whisper.tsx` | Kontext: Zustand, Fortschritt, Cache-Erkennung, Fähigkeiten des Geräts |
|
||||
| `src/lib/whisper.worker.ts` | Pipeline, Erkennung, Fehler auch außerhalb der await-Kette |
|
||||
| `src/lib/dictation.tsx` | `useRecorder` – Mikrofon, Dauer, Abbruch |
|
||||
| `src/lib/audio.ts` | webm/ogg/mp4 → 16 kHz Mono Float32 |
|
||||
| `src/lib/whisperModels.ts` | Downloadgröße, Formatierung |
|
||||
| `src/components/ui/Dictate.tsx` | `DictateButton`, `DictateTextarea`, `appendParagraph` |
|
||||
| `src/components/settings/WhisperSettings.tsx` | der Reiter |
|
||||
|
||||
Abhängigkeit: `@huggingface/transformers` (^3.8.1).
|
||||
|
||||
## Störungssuche
|
||||
|
||||
| Symptom | Ursache |
|
||||
| --- | --- |
|
||||
| Kein Mikrofon-Knopf zu sehen | kein `MediaRecorder` oder keine sichere Verbindung (HTTPS) |
|
||||
| „Kein Zugriff aufs Mikrofon" | Berechtigung im Browser verweigert |
|
||||
| Erkennung sehr langsam | keine WebGPU – der Reiter sagt es ausdrücklich |
|
||||
| Modell nach Neustart wieder weg | privates Fenster oder Speicherbeschränkung; der Reiter weist darauf hin |
|
||||
| „Der Speicher reicht … nicht" | zu wenig Arbeitsspeicher für die ONNX-Sitzung |
|
||||
|
||||
Reference in New Issue
Block a user