From 9920facaf5c29fb71c4045b2d3c1564fc0dac8ab Mon Sep 17 00:00:00 2001 From: optrader Date: Mon, 20 Oct 2025 21:01:45 +0900 Subject: [PATCH] [251020] fix: VoiceInputOverlay Voice Enhancement-2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ• ์ปค๋ฐ‹ ์‹œ๊ฐ„: 2025. 10. 20. 21:01:43 ๐Ÿ“Š ๋ณ€๊ฒฝ ํ†ต๊ณ„: โ€ข ์ด ํŒŒ์ผ: 1๊ฐœ โ€ข ์ถ”๊ฐ€: +109์ค„ โ€ข ์‚ญ์ œ: -61์ค„ ๐Ÿ“ ์ˆ˜์ •๋œ ํŒŒ์ผ: ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx ๐Ÿ”ง ํ•จ์ˆ˜ ๋ณ€๊ฒฝ ๋‚ด์šฉ: ๐Ÿ“„ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx (javascript): ๐Ÿ”„ Modified: clearAllTimers() --- .../VoiceInputOverlay/VoiceInputOverlay.jsx | 179 ++++++++++++------ 1 file changed, 118 insertions(+), 61 deletions(-) diff --git a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx index f5fd1ca2..323653f1 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx @@ -138,6 +138,7 @@ const VoiceInputOverlay = ({ const wakeWordRestartTimerRef = useRef(null); const countdownIntervalRef = useRef(null); const sttDebounceTimerRef = useRef(null); // STT ๊ฒฐ๊ณผ debounce ํƒ€์ด๋จธ + const silenceDetectionTimerRef = useRef(null); // 3์ดˆ silence detection ํƒ€์ด๋จธ // All timer refs array for batch cleanup const allTimerRefs = [ @@ -149,6 +150,7 @@ const VoiceInputOverlay = ({ wakeWordRestartTimerRef, countdownIntervalRef, sttDebounceTimerRef, + silenceDetectionTimerRef, ]; const [micFocused, setMicFocused] = useState(false); @@ -320,6 +322,17 @@ const VoiceInputOverlay = ({ const shopperHouseDataRef = useRef(null); const isInitializingRef = useRef(false); // overlay ์ดˆ๊ธฐํ™” ์ค‘ ํ”Œ๋ž˜๊ทธ + // โœ… searchId ref ์ถ”๊ฐ€ - processFinalVoiceInput์—์„œ ์ตœ์‹  ๊ฐ’์„ ์ฝ๊ธฐ ์œ„ํ•จ + const shopperHouseSearchIdRef = useRef(shopperHouseSearchId); + + // โœ… searchId๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ref ์—…๋ฐ์ดํŠธ + useEffect(() => { + shopperHouseSearchIdRef.current = shopperHouseSearchId; + if (shopperHouseSearchId) { + console.log('[VoiceInput] ๐Ÿ”„ searchId ref ์—…๋ฐ์ดํŠธ:', shopperHouseSearchId); + } + }, [shopperHouseSearchId]); + // ๐Ÿ” DEBUG: shopperHouseData ๋ณ€๊ฒฝ ์ถ”์  useEffect(() => { if (DEBUG_MODE) { @@ -484,20 +497,41 @@ const VoiceInputOverlay = ({ // ์ด useEffect ์ œ๊ฑฐ - renderModeContent()์—์„œ ์ง์ ‘ ํŒ๋‹จํ•˜๋ฏ€๋กœ ๋ถˆํ•„์š” - // ๐ŸŽค Interim ํ…์ŠคํŠธ ์‹ค์‹œ๊ฐ„ ํ‘œ์‹œ ๋ฐ ref ์—…๋ฐ์ดํŠธ + // ๐ŸŽค Interim ํ…์ŠคํŠธ ์‹ค์‹œ๊ฐ„ ํ‘œ์‹œ ๋ฐ ref ์—…๋ฐ์ดํŠธ + 3์ดˆ silence detection useEffect(() => { - if (currentMode !== VOICE_MODES.LISTENING) return; + if (currentMode !== VOICE_MODES.LISTENING) { + // โœ… LISTENING ๋ชจ๋“œ๊ฐ€ ์•„๋‹ˆ๋ฉด ํƒ€์ด๋จธ ์ •๋ฆฌ + clearTimerRef(silenceDetectionTimerRef); + silenceDetectionTimerRef.current = null; + return; + } // โœ… Ref ์—…๋ฐ์ดํŠธ (15์ดˆ ํƒ€์ด๋จธ์—์„œ ์ตœ์‹  ๊ฐ’์„ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก) interimTextRef.current = interimText || ''; - if (!interimText) return; + if (!interimText) { + // ์ž…๋ ฅ์ด ์—†์œผ๋ฉด silence detection ์‹œ์ž‘ํ•˜์ง€ ์•Š์Œ + return; + } // โœจ TInput์— ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ…์ŠคํŠธ ํ‘œ์‹œ if (onSearchChange) { onSearchChange({ value: interimText }); } - }, [interimText, currentMode, onSearchChange]); + + // โœ… 3์ดˆ silence detection: ๋งˆ์ง€๋ง‰ ์ž…๋ ฅ ํ›„ 3์ดˆ ๋™์•ˆ ์ถ”๊ฐ€ ์ž…๋ ฅ์ด ์—†์œผ๋ฉด ์ž๋™ ์ข…๋ฃŒ + clearTimerRef(silenceDetectionTimerRef); + silenceDetectionTimerRef.current = setTimeout(() => { + console.log('[VoiceInput] ๐Ÿ”‡ 3์ดˆ ๋™์•ˆ ์ž…๋ ฅ ์—†์Œ - ์ž๋™ ์ข…๋ฃŒ'); + processFinalVoiceInput('3์ดˆ silence detection'); + }, 3000); // 3์ดˆ + + // Cleanup: ์ƒˆ๋กœ์šด ์ž…๋ ฅ์ด ๋“ค์–ด์˜ค๊ฑฐ๋‚˜ ๋ชจ๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ํƒ€์ด๋จธ ๋ฆฌ์…‹ + return () => { + clearTimerRef(silenceDetectionTimerRef); + silenceDetectionTimerRef.current = null; + }; + }, [interimText, currentMode, onSearchChange, processFinalVoiceInput]); // ๐ŸŽ‰ Wake Word Detection: PROMPT ๋ชจ๋“œ์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌ์Šค๋‹ ์‹œ์ž‘ useEffect(() => { @@ -558,23 +592,17 @@ const VoiceInputOverlay = ({ } }, [currentMode, voiceInputMode]); - // VoiceInputOverlay ๋งˆ์šดํŠธ ์‹œ ์ดˆ๊ธฐํ™” (ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰) + // VoiceInputOverlay ์–ธ๋งˆ์šดํŠธ ์‹œ์—๋งŒ ์ดˆ๊ธฐํ™” useEffect(() => { - console.log('[VoiceInput] ๐Ÿš€ VoiceInputOverlay ๋งˆ์šดํŠธ - searchId ์ดˆ๊ธฐํ™”'); + console.log('[VoiceInput] ๐Ÿš€ VoiceInputOverlay ๋งˆ์šดํŠธ๋จ (searchId ์œ ์ง€)'); - // โœจ Redux shopperHouseData ์ดˆ๊ธฐํ™” - dispatch(clearShopperHouseData()); - - // โœจ Redux lastSTTText ์ดˆ๊ธฐํ™” - dispatch(clearSTTText()); - - // Cleanup: ์–ธ๋งˆ์šดํŠธ ์‹œ ์ดˆ๊ธฐํ™” + // Cleanup: ์–ธ๋งˆ์šดํŠธ ์‹œ์—๋งŒ ์ดˆ๊ธฐํ™” return () => { console.log('[VoiceInput] ๐Ÿ”š VoiceInputOverlay ์–ธ๋งˆ์šดํŠธ - searchId ์ดˆ๊ธฐํ™”'); dispatch(clearShopperHouseData()); dispatch(clearSTTText()); }; - }, []); // ๋นˆ dependency - ๋งˆ์šดํŠธ/์–ธ๋งˆ์šดํŠธ ์‹œ์—๋งŒ ์‹คํ–‰ + }, []); // ๋นˆ dependency - ์–ธ๋งˆ์šดํŠธ ์‹œ์—๋งŒ ์‹คํ–‰ // Overlay๊ฐ€ ์—ด๋ฆด ๋•Œ ํฌ์ปค์Šค๋ฅผ overlay ๋‚ด๋ถ€๋กœ ์ด๋™ useEffect(() => { @@ -841,12 +869,75 @@ const VoiceInputOverlay = ({ setMicFocused(false); }, []); + // ๐ŸŽค ์Œ์„ฑ ์ž…๋ ฅ ์ตœ์ข… ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ (15์ดˆ ํƒ€์ด๋จธ & 3์ดˆ silence detection ๊ณตํ†ต ์‚ฌ์šฉ) + const processFinalVoiceInput = useCallback( + (source) => { + console.log(`[VoiceInput] ๐Ÿ ์Œ์„ฑ ์ž…๋ ฅ ์ข…๋ฃŒ (${source})`); + + // ๋ชจ๋“  ํƒ€์ด๋จธ ์ •๋ฆฌ ๋ฐ ref ์ดˆ๊ธฐํ™” + clearTimerRef(listeningTimerRef); + clearIntervalRef(countdownIntervalRef); + clearTimerRef(silenceDetectionTimerRef); + + // โœ… Ref ๋ช…์‹œ์  ์ดˆ๊ธฐํ™” (์ค‘์š”!) + listeningTimerRef.current = null; + countdownIntervalRef.current = null; + silenceDetectionTimerRef.current = null; + + // ์Œ์„ฑ ์ธ์‹ ์ค‘์ง€ + stopListening(); + + // โœ… ๋ˆ„์ ๋œ interimText๋ฅผ ์ตœ์ข… ๊ฒฐ๊ณผ๋กœ ์‚ฌ์šฉ + const finalText = interimTextRef.current.trim(); + console.log('[VoiceInput] โ””โ”€ ์ตœ์ข… ํ…์ŠคํŠธ:', finalText); + + if (finalText && finalText.length >= 3) { + // STT ํ…์ŠคํŠธ ์ €์žฅ + setSttResponseText(finalText); + + // RESPONSE ๋ชจ๋“œ๋กœ ์ „ํ™˜ + setCurrentMode(VOICE_MODES.RESPONSE); + setVoiceInputMode(null); + + // โœจ ๊ฒ€์ƒ‰ ๊ธฐ๋ก์— ์ถ”๊ฐ€ + addToSearchHistory(finalText); + + // โœจ ShopperHouse API ์ž๋™ ํ˜ธ์ถœ (2์ฐจ ๋ฐœํ™” ์‹œ searchId ํฌํ•จ) + const query = finalText.trim(); + // โœ… Ref์—์„œ ์ตœ์‹  searchId ์ฝ๊ธฐ (useCallback closure ๋ฌธ์ œ ํ•ด๊ฒฐ) + const currentSearchId = shopperHouseSearchIdRef.current; + console.log('[VoiceInput] ๐Ÿ“ค API ์š”์ฒญ ์ „์†ก'); + console.log('[VoiceInput] โ”œโ”€ query:', query); + console.log('[VoiceInput] โ”œโ”€ ref ๊ฐ’:', shopperHouseSearchIdRef.current); + console.log('[VoiceInput] โ”œโ”€ currentSearchId:', currentSearchId); + console.log('[VoiceInput] โ””โ”€ searchId:', currentSearchId || '(์—†์Œ - ์ฒซ ๋ฒˆ์งธ ๋ฐœํ™”)'); + + dispatch(getShopperHouseSearch(query, currentSearchId)); + } else { + // ์ž…๋ ฅ์ด ์—†๊ฑฐ๋‚˜ ๋„ˆ๋ฌด ์งง์œผ๋ฉด PROMPT ๋ชจ๋“œ๋กœ ๋ณต๊ท€ + console.log('[VoiceInput] โš ๏ธ ์ž…๋ ฅ ์—†์Œ ๋˜๋Š” ๋„ˆ๋ฌด ์งง์Œ - PROMPT ๋ชจ๋“œ๋กœ ๋ณต๊ท€'); + setCurrentMode(VOICE_MODES.PROMPT); + setVoiceInputMode(null); + } + + // Ref ์ดˆ๊ธฐํ™” + interimTextRef.current = ''; + }, + [ + stopListening, + addToSearchHistory, + dispatch, + // โœ… shopperHouseSearchId ์ œ๊ฑฐ - ref ์‚ฌ์šฉ์œผ๋กœ closure ๋ฌธ์ œ ํ•ด๊ฒฐ + ] + ); + // Overlay ๋‹ซ๊ธฐ ํ•ธ๋“ค๋Ÿฌ (๋ชจ๋“  ๋‹ซ๊ธฐ ๋™์ž‘์„ ํ†ตํ•ฉ) const handleClose = useCallback(() => { if (DEBUG_MODE) { console.log('๐Ÿšช [DEBUG] handleClose called - closing overlay'); } clearTimerRef(listeningTimerRef); + clearTimerRef(silenceDetectionTimerRef); setVoiceInputMode(null); setCurrentMode(VOICE_MODES.PROMPT); setSttResponseText(''); @@ -891,6 +982,7 @@ const VoiceInputOverlay = ({ // ๊ธฐ์กด ํƒ€์ด๋จธ ์ •๋ฆฌ clearTimerRef(listeningTimerRef); + clearTimerRef(silenceDetectionTimerRef); setVoiceInputMode(VOICE_INPUT_MODE.WEBSPEECH); setCurrentMode(VOICE_MODES.LISTENING); @@ -898,53 +990,9 @@ const VoiceInputOverlay = ({ // WebSpeech API ์‹œ์ž‘ startListening(); - // 15์ดˆ ํƒ€์ด๋จธ ์„ค์ •: 15์ดˆ ํ›„ ๋ˆ„์ ๋œ interimText๋ฅผ ์ตœ์ข… ๊ฒฐ๊ณผ๋กœ ์ฒ˜๋ฆฌ + // 15์ดˆ ํƒ€์ด๋จธ ์„ค์ •: ์ตœ๋Œ€ ์ž…๋ ฅ ์‹œ๊ฐ„ ์ œํ•œ listeningTimerRef.current = setTimeout(() => { - if (DEBUG_MODE) { - console.log('โฐ [VoiceInputOverlay.v2] 15์ดˆ ํƒ€์ž„์•„์›ƒ - ์ตœ์ข… ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ'); - } - - // ์นด์šดํŠธ๋‹ค์šด ์ •๋ฆฌ - clearIntervalRef(countdownIntervalRef); - - // ์Œ์„ฑ ์ธ์‹ ์ค‘์ง€ - stopListening(); - - // โœ… 15์ดˆ ๋™์•ˆ ๋ˆ„์ ๋œ interimText๋ฅผ ์ตœ์ข… ๊ฒฐ๊ณผ๋กœ ์‚ฌ์šฉ (ref๋กœ๋ถ€ํ„ฐ ์ฝ๊ธฐ) - const finalText = interimTextRef.current.trim(); - console.log('[VoiceInput] ๐Ÿ 15์ดˆ ํƒ€์ด๋จธ ์ข…๋ฃŒ - ์ตœ์ข… ํ…์ŠคํŠธ:', finalText); - - if (finalText && finalText.length >= 3) { - // STT ํ…์ŠคํŠธ ์ €์žฅ - setSttResponseText(finalText); - - // RESPONSE ๋ชจ๋“œ๋กœ ์ „ํ™˜ - setCurrentMode(VOICE_MODES.RESPONSE); - setVoiceInputMode(null); - - // โœจ ๊ฒ€์ƒ‰ ๊ธฐ๋ก์— ์ถ”๊ฐ€ - addToSearchHistory(finalText); - - // โœจ ShopperHouse API ์ž๋™ ํ˜ธ์ถœ (2์ฐจ ๋ฐœํ™” ์‹œ searchId ํฌํ•จ) - const query = finalText.trim(); - console.log('[VoiceInput] ๐Ÿ“ค API ์š”์ฒญ ์ „์†ก'); - console.log('[VoiceInput] โ”œโ”€ query:', query); - console.log( - '[VoiceInput] โ””โ”€ searchId:', - shopperHouseSearchId || '(์—†์Œ - ์ฒซ ๋ฒˆ์งธ ๋ฐœํ™”)' - ); - - dispatch(getShopperHouseSearch(query, shopperHouseSearchId)); - } else { - // ์ž…๋ ฅ์ด ์—†๊ฑฐ๋‚˜ ๋„ˆ๋ฌด ์งง์œผ๋ฉด PROMPT ๋ชจ๋“œ๋กœ ๋ณต๊ท€ - console.log('[VoiceInput] โš ๏ธ ์ž…๋ ฅ ์—†์Œ ๋˜๋Š” ๋„ˆ๋ฌด ์งง์Œ - PROMPT ๋ชจ๋“œ๋กœ ๋ณต๊ท€'); - setCurrentMode(VOICE_MODES.PROMPT); - setVoiceInputMode(null); - } - - // Ref ์ดˆ๊ธฐํ™” - interimTextRef.current = ''; - listeningTimerRef.current = null; + processFinalVoiceInput('15์ดˆ ํƒ€์ž„์•„์›ƒ'); }, 15000); // 15์ดˆ } else { // listening ๋ชจ๋“œ ๋˜๋Š” ๊ธฐํƒ€ ๋ชจ๋“œ์—์„œ ํด๋ฆญ ์‹œ -> overlay ๋‹ซ๊ธฐ @@ -954,7 +1002,16 @@ const VoiceInputOverlay = ({ handleClose(); } }, - [currentMode, handleClose, playBeep, startListening, stopListening, onSearchChange] + [ + currentMode, + handleClose, + playBeep, + startListening, + stopListening, + onSearchChange, + dispatch, + processFinalVoiceInput, + ] ); // ๋งˆ์ดํฌ ๋ฒ„ํŠผ ํ‚ค๋‹ค์šด ํ•ธ๋“ค๋Ÿฌ