# 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 |