import React, { useEffect, useRef, useState } from 'react'; import { Loader2, Square } from 'lucide-react'; import { useVoiceInput } from '../../lib/useVoiceInput'; import { VoiceWave } from '../../components/VoiceWave'; import { triggerHaptic } from '../../lib/api'; interface DictationBlockProps { onDone: (text: string) => void; onCancel: () => void; } function formatDuration(sec: number): string { const m = String(Math.floor(sec / 60)).padStart(2, '0'); const s = String(sec % 60).padStart(2, '0'); return `${m}:${s}`; } // What an empty bullet turns into after /diktat: listening starts right away, the whole bullet is one // button with live text growing above the wave, mm:ss timer and a translucent "Stopp" on top. A tap ends // it; the text lands in the bullet as plain text. export const DictationBlock: React.FC = ({ onDone, onCancel }) => { const [interim, setInterim] = useState(''); const [elapsedSec, setElapsedSec] = useState(0); const [error, setError] = useState(null); const doneRef = useRef(false); const buttonRef = useRef(null); const voice = useVoiceInput({ live: true, continuous: true, onText: (text, final) => { if (!final) { setInterim(text); return; } if (doneRef.current) return; doneRef.current = true; if (text.trim()) onDone(text.trim()); else onCancel(); }, onError: (message) => { setError(message); window.setTimeout(() => { if (!doneRef.current) { doneRef.current = true; onCancel(); } }, 2500); }, }); useEffect(() => { triggerHaptic([15, 60, 25]); void voice.start(); buttonRef.current?.focus(); const started = Date.now(); const timer = window.setInterval(() => { setElapsedSec(Math.floor((Date.now() - started) / 1000)); }, 500); return () => window.clearInterval(timer); // start once when the bullet turns into the dictation // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const transcribing = voice.state === 'transcribing'; const chunkBusy = voice.pendingChunks > 0 || transcribing; return ( ); };