From 3371b8874e0a8fcc319ca53bf06b1798cd9122ff Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 14 Sep 2026 15:49:24 +0200 Subject: [PATCH] Diktat: kein automatisches Laden mehr (docs/04-diktat.md) --- docs/04-diktat.md | 261 +++++++++++++++++++++++----------------------- 1 file changed, 132 insertions(+), 129 deletions(-) diff --git a/docs/04-diktat.md b/docs/04-diktat.md index 993943c..e330f04 100644 --- a/docs/04-diktat.md +++ b/docs/04-diktat.md @@ -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
webm / ogg / mp4"] - MR --> AC["AudioContext + OfflineAudioContext
16 kHz Mono PCM"] - AC --> W["Web Worker"] - W --> ONNX["Whisper als ONNX
WebGPU, sonst WASM"] - ONNX --> T["Text"] - T --> F["Formularfeld
angehaengt, nie ersetzt"] - - HF["Hugging-Face-CDN"] -.->|"einmalig ~300 MB"| CACHE[("Cache-Speicher
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
webm / ogg / mp4"] + MR --> AC["AudioContext + OfflineAudioContext
16 kHz Mono PCM"] + AC --> W["Web Worker"] + W --> ONNX["Whisper als ONNX
WebGPU, sonst WASM"] + ONNX --> T["Text"] + T --> F["Formularfeld
angehaengt, nie ersetzt"] + + HF["Hugging-Face-CDN"] -.->|"einmalig ~300 MB"| CACHE[("Cache-Speicher
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 |