893 Stellen in Fliesstext, Tabellen und Mermaid-Beschriftungen. Dazu die sichtbaren ss-Fälle (ausschließlich, groß, außen, schließt, heißt, draußen, weiße, anstoßen, Straße). Nicht angetastet, damit die Doku weiter beschreibt, was wirklich in der Datenbank steht: - Codeblöcke und Code-Spans - dort stehen Bezeichner, SQL, Enum-Werte und Dateinamen: `mail_pool`, `'uebernommen'`, `extract.js` - in Mermaid nur der Text in Anführungszeichen; Knotennamen, Pfeile und Werte wie source = 'Mail' bleiben, wie sie sind - `empfaenger` im ER-Diagramm von crm_mail_contact_candidates: die Aufzählung dort nennt die Datenbankwerte, nicht die Anzeige Die Menü-Skizze in der Benutzeranleitung zeigt jetzt die aufgeklappten Kontakte mit "Aus Mails" samt Zähler. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
86 lines
3.2 KiB
Markdown
86 lines
3.2 KiB
Markdown
# 04 Diktat (Whisper im Browser)
|
|
|
|
Überall, wo längerer Text entsteht - Projektbeschreibung, Interaktionsnotiz,
|
|
nächster Schritt, interne Notizen - sitzt ein Mikrofon-Button. Die Erkennung
|
|
läuft **vollständig im Browser des Benutzers**.
|
|
|
|
Das Backend ist daran nicht beteiligt und sieht keine Audiodaten: kein API-Key,
|
|
keine Kosten pro Minute, kein Datenabfluss. Aufnahmen werden nirgends gespeichert.
|
|
|
|
## Weg der Aufnahme
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
MIC(("Mikrofon")) --> MR["MediaRecorder"]
|
|
MR --> AC["AudioContext<br/>16 kHz Mono PCM"]
|
|
AC --> W["Web Worker"]
|
|
W --> ONNX["Whisper als ONNX<br/>WebGPU, sonst WASM"]
|
|
ONNX --> T["Text"]
|
|
T --> F["Formularfeld"]
|
|
|
|
HF["Hugging-Face-CDN"] -.->|"einmalig 759 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 bundelt die `.wasm`), nur die Modelldateien kommen beim ersten
|
|
Laden vom CDN und bleiben danach im Cache-Speicher der Seite.
|
|
|
|
## Lebenszyklus des Modells
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> idle : Anmeldung
|
|
idle --> loading : "Sprachmodell laden" oder<br/>automatisch, wenn schon im Cache
|
|
loading --> ready : im Worker initialisiert
|
|
loading --> error : Abbruch, Haenger, kein Speicher
|
|
error --> loading : erneut versuchen
|
|
ready --> idle : "Sprachmodell löschen"
|
|
ready --> ready : Diktat benutzen
|
|
|
|
note right of loading
|
|
Phase download: Dateien holen
|
|
Phase init: Modell im Worker aufbauen
|
|
end note
|
|
```
|
|
|
|
Beim Anmelden wird das Modell automatisch bereitgestellt, **wenn** es schon in
|
|
diesem Browser liegt. Ein Download von 759 MB startet nie von allein, und der
|
|
Automatismus greift bewusst nur einmal pro Seitenaufruf: wer das Modell löscht,
|
|
bekommt es nicht sofort wieder heruntergeladen.
|
|
|
|
## Wo es sitzt
|
|
|
|
Reiter **Whisper** in den Einstellungen - für **jede Rolle** sichtbar, denn jeder
|
|
richtet sein Diktat auf seinem eigenen Gerät ein. Genau deshalb sind die
|
|
Einstellungen überhaupt für alle Rollen zugänglich; welche Reiter erscheinen,
|
|
entscheidet die Rolle:
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
E["Einstellungen"] --> A["Mein Konto<br/>(alle)"]
|
|
E --> W["Whisper<br/>(alle)"]
|
|
E --> N["Nutzerverwaltung<br/>(admin)"]
|
|
E --> S["Scanner<br/>(admin)"]
|
|
```
|
|
|
|
Ist das Modell noch nicht geladen, wird der Mikrofon-Button zu *Diktat
|
|
einrichten* und springt per `goTo('Einstellungen', { tab: 'whisper' })` direkt
|
|
dorthin - ein offener Dialog wird vorher geschlossen (`onBeforeNavigate`).
|
|
|
|
## Warum keine Auswahl
|
|
|
|
Modell, Genauigkeit, Rechenweg und Sprache stehen als Konstanten in
|
|
`src/lib/whisper.tsx`: `large-v3-turbo`, `q4`, Grafikkarte wenn vorhanden,
|
|
Deutsch. Jede andere Kombination war in den Messungen entweder schlechter oder
|
|
kaputt - bei `small` in fp16 auf WebGPU reagiert der ganze Browser-Tab nicht mehr,
|
|
und **kein Timeout in der Seite kann das abfangen**, weil das Skript selbst nicht
|
|
mehr läuft.
|
|
|
|
Eine Auswahl hätte also nur Gelegenheit geboten, etwas falsch zu machen. Die
|
|
Messwerte stehen im [README](../README.md#warum-small-und-q4---die-messungen).
|
|
|
|
Als Netz bleibt eine Frist pro Erkennung (15x Aufnahmedauer, mindestens 45 s,
|
|
höchstens 5 min) und eine Hänger-Erkennung beim Laden.
|