import React, { useEffect, useState } from 'react'; import { ConfirmDialog } from '../ui/Modal'; import { labelClass } from '../ui/form'; import { useToast } from '../Toast'; import { useRecorder } from '../../lib/dictation'; import { WHISPER_MODEL, useWhisper } from '../../lib/whisper'; import { fetchModelSize, formatSize } from '../../lib/whisperModels'; /* * Reiter "Diktat" in den Einstellungen - für jede Rolle sichtbar, denn jeder * richtet das Diktat auf seinem eigenen Gerät ein. * * Einzustellen gibt es hier bewusst nichts: Modell, Genauigkeit, Rechenweg und * Sprache stehen fest (siehe lib/whisper.tsx). Übrig bleibt, was wirklich * gebraucht wird - einmal herunterladen, je Sitzung einmal aktivieren. Und der * Weg, es wieder loszuwerden. */ export const WhisperSettings: React.FC = () => { const { toast } = useToast(); const { status, phase, progress, progressLabel, error, isCached, capabilities, load, clearCache, isSupported, } = useWhisper(); const [testText, setTestText] = useState(''); const [askClear, setAskClear] = useState(false); /** Downloadgröße, direkt bei Hugging Face erfragt. */ const [sizeLabel, setSizeLabel] = useState(null); useEffect(() => { let cancelled = false; fetchModelSize(WHISPER_MODEL) .then((size) => { if (!cancelled && size) setSizeLabel(formatSize(size.totalBytes)); }) .catch(() => { // Ohne Angabe läuft es genauso - nur ohne Zahl im Text. }); return () => { cancelled = true; }; }, []); const recorder = useRecorder( (text) => { setTestText(text); toast('Erkennung abgeschlossen.', 'success'); }, (message) => toast(message, 'error') ); const handleLoad = async () => { try { await load(); toast('Diktat ist bereit.', 'success'); } catch (err) { toast(err instanceof Error ? err.message : 'Laden fehlgeschlagen.', 'error'); } }; const isLoading = status === 'loading'; return (

Diktat mit Whisper

Die Spracherkennung läuft vollständig in diesem Browser. Aufnahmen verlassen das Gerät nicht und werden nirgends gespeichert – weder auf dem ICON-Server noch bei einem Dienstleister.

Der Download fällt einmal je Browser an; das Modell bleibt danach hier liegen. Aktiviert wird es bei jeder Anmeldung mit einem Klick – das geschieht bewusst nicht von selbst: das Einrichten der Erkennung beansprucht für einige Sekunden die Grafikkarte, und das soll niemanden mitten in der Arbeit treffen.

{/* Zustand und die beiden Aktionen */}

{status === 'ready' ? 'Diktat ist bereit' : isLoading ? phase === 'init' ? 'Wird eingerichtet …' : 'Wird geladen …' : status === 'error' ? 'Laden fehlgeschlagen' : 'Diktat noch nicht eingerichtet'}

{status === 'ready' ? 'Die Mikrofon-Knöpfe in den Formularen sind aktiv.' : isLoading ? phase === 'init' ? 'Gleich fertig.' : progressLabel || 'Download startet …' : isCached ? 'Das Modell liegt in diesem Browser – ein Klick aktiviert es für diese Sitzung.' : `Einmalig werden ${sizeLabel ?? 'die Modelldateien'} geladen – danach bleibt es in diesem Browser.`}

{status !== 'ready' && ( )} {(isCached || status === 'ready') && ( )}
{isLoading && (

Während das Modell eingerichtet wird, kann die Oberfläche kurz stocken. Das legt sich, sobald oben „Diktat ist bereit" steht.

)} {isLoading && (
)} {error && (

{error}

)} {/* Nur melden, was den Benutzer betrifft - sonst Stille. */} {!isSupported && (

Auf diesem Gerät ist kein Diktat möglich – es fehlt ein Mikrofon oder eine sichere Verbindung. Die Mikrofon-Knöpfe bleiben ausgeblendet.

)} {isSupported && !capabilities.webgpu && (

Dieses Gerät hat keine nutzbare Grafikbeschleunigung. Das Diktat funktioniert, die Erkennung dauert aber deutlich länger.

)} {status === 'ready' && !isCached && (

Dieser Browser hat das Modell nicht behalten – beim nächsten Start muss es erneut geladen werden. Ursache ist meist ein privates Fenster oder eine Speicherbeschränkung.

)}
{/* Probe */}

Probe sprechen

Kurz etwas diktieren, um zu prüfen, ob alles sitzt.

{recorder.state === 'recording' && ( )} {status !== 'ready' && ( Erst das Sprachmodell laden. )}

{testText || Noch keine Probe.}

{ await clearCache(); setAskClear(false); toast('Sprachmodell entfernt.', 'success'); }} onCancel={() => setAskClear(false)} />
); };