diff --git a/resources/views/juil/construction-photos.blade.php b/resources/views/juil/construction-photos.blade.php index d53125b7..52ee209d 100644 --- a/resources/views/juil/construction-photos.blade.php +++ b/resources/views/juil/construction-photos.blade.php @@ -54,63 +54,102 @@ } // --- VoiceInputButton (Web Speech API STT) --- -function VoiceInputButton({ onResult, disabled, mode = 'replace' }) { +function VoiceInputButton({ onResult, disabled }) { const [recording, setRecording] = useState(false); + const [interim, setInterim] = useState(''); const recognitionRef = useRef(null); const isSupported = typeof window !== 'undefined' && (window.SpeechRecognition || window.webkitSpeechRecognition); - const toggle = (e) => { - e.preventDefault(); - e.stopPropagation(); - if (disabled || !isSupported) return; - - if (recording) { - recognitionRef.current?.stop(); - setRecording(false); - return; - } + const stopRecording = useCallback(() => { + recognitionRef.current?.stop(); + recognitionRef.current = null; + setRecording(false); + setInterim(''); + }, []); + const startRecording = useCallback(() => { const SR = window.SpeechRecognition || window.webkitSpeechRecognition; const recognition = new SR(); recognition.lang = 'ko-KR'; - recognition.continuous = false; - recognition.interimResults = false; + recognition.continuous = true; + recognition.interimResults = true; recognition.maxAlternatives = 1; + let finalText = ''; + recognition.onresult = (event) => { - const text = event.results[0][0].transcript; - if (text) onResult(text, mode); - setRecording(false); + let interimText = ''; + for (let i = event.resultIndex; i < event.results.length; i++) { + const transcript = event.results[i][0].transcript; + if (event.results[i].isFinal) { + finalText += transcript; + if (finalText) onResult(finalText); + finalText = ''; + interimText = ''; + } else { + interimText += transcript; + } + } + setInterim(interimText); + }; + recognition.onerror = () => stopRecording(); + recognition.onend = () => { + setRecording(false); + setInterim(''); + recognitionRef.current = null; }; - recognition.onerror = () => setRecording(false); - recognition.onend = () => setRecording(false); recognitionRef.current = recognition; recognition.start(); setRecording(true); + setInterim(''); + }, [onResult, stopRecording]); + + const toggle = (e) => { + e.preventDefault(); + e.stopPropagation(); + if (disabled || !isSupported) return; + recording ? stopRecording() : startRecording(); }; + useEffect(() => { + return () => { recognitionRef.current?.stop(); }; + }, []); + if (!isSupported) return null; return ( - +
+ + {recording && interim && ( +
+ ...{interim} +
+ )} +
); }