diff --git a/com.twin.app.shoptime/src/hooks/useWebSpeech.js b/com.twin.app.shoptime/src/hooks/useWebSpeech.js index b7f73395..f608ad15 100644 --- a/com.twin.app.shoptime/src/hooks/useWebSpeech.js +++ b/com.twin.app.shoptime/src/hooks/useWebSpeech.js @@ -63,10 +63,12 @@ export const useWebSpeech = (isActive, onSTTText, config = {}) => { dispatch(stopWebSpeech()); }, [dispatch]); - // WebSpeech API 지원 여부 체크 + // WebSpeech API 브라우저 지원 여부 체크 + // - 브라우저가 SpeechRecognition API를 지원하는지만 확인 + // - 마이크 접근 불가 등의 에러는 error 필드로 별도 처리 const isSupported = - !webSpeech.error || - (typeof webSpeech.error === 'string' && !webSpeech.error.includes('not supported')); + typeof window !== 'undefined' && + !!(window.SpeechRecognition || window.webkitSpeechRecognition); return { isInitialized: webSpeech.isInitialized, diff --git a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.v2.jsx b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.v2.jsx index 4ecb8ee6..6776d575 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.v2.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.v2.jsx @@ -45,8 +45,14 @@ export const VOICE_MODES = { MODE_4: 'mode4', // 추후 추가 }; -// NOINIT 모드 에러 메시지 -const NOINIT_ERROR_MESSAGE = 'Voice recognition is not supported on this device.'; +// NOINIT 모드 에러 메시지 - 에러 타입별로 구분 +const NOINIT_ERROR_MESSAGES = { + 'not-supported': 'Voice recognition is not supported on this browser.', + 'audio-capture': 'No microphone detected. Please connect a microphone.', + 'not-allowed': 'Microphone access denied. Please allow microphone permissions.', + 'service-not-allowed': 'Voice recognition service is not available.', + default: 'Voice recognition is not available.', +}; // 음성인식 입력 모드 (VUI vs WebSpeech) export const VOICE_INPUT_MODE = { @@ -127,6 +133,8 @@ const VoiceInputOverlay = ({ const [sttResponseText, setSttResponseText] = useState(''); // Voice Version (어떤 음성 시스템을 사용할지 결정) const [voiceVersion, setVoiceVersion] = useState(VOICE_VERSION.WEB_SPEECH); + // NOINIT 모드 에러 메시지 + const [noInitErrorMessage, setNoInitErrorMessage] = useState(NOINIT_ERROR_MESSAGES.default); // 🔊 Beep 소리 재생 함수 - zero dependencies const playBeep = useCallback(() => { @@ -338,15 +346,37 @@ const VoiceInputOverlay = ({ } }, [interimText, currentMode, handleWakeWordDetected]); - // WebSpeech가 지원되지 않을 때 NOINIT 모드로 전환 + // WebSpeech가 지원되지 않거나 마이크 에러 발생 시 NOINIT 모드로 전환 useEffect(() => { - if (isVisible && voiceVersion === VOICE_VERSION.WEB_SPEECH && isSupported === false) { + if (!isVisible || voiceVersion !== VOICE_VERSION.WEB_SPEECH) return; + + // 브라우저가 WebSpeech를 지원하지 않는 경우 + if (isSupported === false) { if (DEBUG_MODE) { console.log('⚠️ [VoiceInputOverlay.v2] WebSpeech not supported, switching to NOINIT mode'); } + setNoInitErrorMessage(NOINIT_ERROR_MESSAGES['not-supported']); setCurrentMode(VOICE_MODES.NOINIT); + return; } - }, [isVisible, voiceVersion, isSupported]); + + // 마이크 관련 에러 발생 시 + if (error && typeof error === 'string') { + if (DEBUG_MODE) { + console.log('⚠️ [VoiceInputOverlay.v2] WebSpeech error detected:', error); + } + + // 에러 타입 파싱 (error는 'audio-capture' 같은 문자열 또는 객체일 수 있음) + const errorType = typeof error === 'string' ? error : error.error || error.message || ''; + + // 마이크 접근 불가 에러들 + if (['audio-capture', 'not-allowed', 'service-not-allowed'].some(e => errorType.includes(e))) { + const matchedError = ['audio-capture', 'not-allowed', 'service-not-allowed'].find(e => errorType.includes(e)); + setNoInitErrorMessage(NOINIT_ERROR_MESSAGES[matchedError] || NOINIT_ERROR_MESSAGES.default); + setCurrentMode(VOICE_MODES.NOINIT); + } + } + }, [isVisible, voiceVersion, isSupported, error]); // ⛔ 독립 테스트: WebSpeech API 호출 비활성화 // WebSpeech 모드로 전환되면 자동으로 음성 인식 시작 @@ -582,9 +612,9 @@ const VoiceInputOverlay = ({ return ; case VOICE_MODES.NOINIT: if (DEBUG_MODE) { - console.log('📺 Rendering: VoiceNotRecognized (NOINIT mode)'); + console.log('📺 Rendering: VoiceNotRecognized (NOINIT mode) with message:', noInitErrorMessage); } - return ; + return ; case VOICE_MODES.NOTRECOGNIZED: if (DEBUG_MODE) { console.log('📺 Rendering: VoiceNotRecognized (NOTRECOGNIZED mode)'); @@ -613,6 +643,7 @@ const VoiceInputOverlay = ({ interimText, sttResponseText, handleTalkAgain, + noInitErrorMessage, ]); // 입력창 포커스 핸들러 diff --git a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceListening.figma.jsx b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceListening.figma.jsx deleted file mode 100644 index 9546ce2b..00000000 --- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceListening.figma.jsx +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
-
-
-
-
-
-
-