import React from 'react'; import { useRecorder } from '../../lib/dictation'; import { useWhisper } from '../../lib/whisper'; import { useNavigation } from '../../lib/navigation'; import { useToast } from '../Toast'; import { inputClass } from '../leads/leadUi'; const MIC_ICON = 'M19 11a7 7 0 01-14 0m7 7v3m0-3a7 7 0 007-7m-7 7a7 7 0 01-7-7m7-9a3 3 0 013 3v6a3 3 0 01-6 0V5a3 3 0 013-3z'; const STOP_ICON = 'M9 9h6v6H9z'; function formatSeconds(total: number): string { return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, '0')}`; } interface DictateButtonProps { /** Erhaelt den erkannten Text; der Aufrufer entscheidet, ob er anhaengt. */ onText: (text: string) => void; /** Kurzer Hinweis, was diktiert wird - landet im Titel des Buttons. */ label: string; compact?: boolean; /** * Wird vor dem Wechsel in die Whisper-Einstellungen aufgerufen - ein offener * Dialog muss sich sonst nicht schliessen und liegt ueber der Ansicht. */ onBeforeNavigate?: () => void; } export const DictateButton: React.FC = ({ onText, label, compact, onBeforeNavigate, }) => { const { status, isSupported } = useWhisper(); const { goTo } = useNavigation(); const { addToast } = useToast(); const { state, seconds, start, stop, cancel } = useRecorder(onText, (message) => addToast(message, 'error') ); // Ohne Mikrofon oder ohne HTTPS hat ein Diktat-Button keinen Sinn. if (!isSupported) return null; // Modell noch nicht geladen: statt eines toten Buttons der Weg dorthin. if (status !== 'ready') { return ( ); } const isRecording = state === 'recording'; const isBusy = state === 'transcribing'; return (
{isRecording && ( )}
); }; interface DictationHintProps { /** Wird vor dem Wechsel aufgerufen, z. B. um den Dialog zu schliessen. */ onBeforeNavigate?: () => void; } /** * Hinweisbox fuer Formulare: erklaert, warum kein Mikrofon zu sehen ist, und * fuehrt direkt zu den Whisper-Einstellungen. Zeigt sich nur, solange das * Modell nicht bereit ist. */ export const DictationHint: React.FC = ({ onBeforeNavigate }) => { const { status, isSupported, isCached, progress, progressLabel, phase } = useWhisper(); const { goTo } = useNavigation(); if (!isSupported || status === 'ready') return null; const isLoading = status === 'loading'; return (

{isLoading ? 'Spracherkennung wird geladen...' : status === 'error' ? 'Die Spracherkennung konnte nicht geladen werden.' : 'Diktieren ist möglich, das Modell fehlt aber noch.'}

{isLoading ? phase === 'init' ? 'Download fertig, Modell wird initialisiert...' : progressLabel || 'Bitte kurz warten.' : isCached ? 'Das Modell liegt bereits im Browser - es muss nur noch aktiviert werden.' : 'Das Modell wird einmalig in diesen Browser geladen und läuft danach lokal auf diesem Gerät.'}

{isLoading && (
)}
{!isLoading && ( )}
); }; interface DictateTextareaProps { id?: string; value: string; onChange: (value: string) => void; label: string; placeholder?: string; rows?: number; onBeforeNavigate?: () => void; } /** Textfeld mit Diktat-Button darueber; erkannter Text wird angehaengt. */ export const DictateTextarea: React.FC = ({ id, value, onChange, label, placeholder, rows = 4, onBeforeNavigate, }) => (
onChange(value ? `${value.trimEnd()} ${text}` : text)} />