[251018] feat: WebSpeech
🕐 커밋 시간: 2025. 10. 18. 20:56:03 📊 변경 통계: • 총 파일: 3개 • 추가: +43줄 • 삭제: -20줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/hooks/useWebSpeech.js ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.v2.jsx 🗑️ 삭제된 파일: - com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceListening.figma.jsx 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • 소규모 기능 개선
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 <VoiceResponse responseText={sttResponseText} onTalkAgain={handleTalkAgain} />;
|
||||
case VOICE_MODES.NOINIT:
|
||||
if (DEBUG_MODE) {
|
||||
console.log('📺 Rendering: VoiceNotRecognized (NOINIT mode)');
|
||||
console.log('📺 Rendering: VoiceNotRecognized (NOINIT mode) with message:', noInitErrorMessage);
|
||||
}
|
||||
return <VoiceNotRecognized prompt={NOINIT_ERROR_MESSAGE} />;
|
||||
return <VoiceNotRecognized prompt={noInitErrorMessage} />;
|
||||
case VOICE_MODES.NOTRECOGNIZED:
|
||||
if (DEBUG_MODE) {
|
||||
console.log('📺 Rendering: VoiceNotRecognized (NOTRECOGNIZED mode)');
|
||||
@@ -613,6 +643,7 @@ const VoiceInputOverlay = ({
|
||||
interimText,
|
||||
sttResponseText,
|
||||
handleTalkAgain,
|
||||
noInitErrorMessage,
|
||||
]);
|
||||
|
||||
// 입력창 포커스 핸들러
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<div style={{width: '100%', height: '100%', position: 'relative'}}>
|
||||
<div style={{width: 510, height: 25, left: 0, top: 0, position: 'absolute', background: '#424242', borderRadius: 100}} />
|
||||
<div style={{width: 510, height: 25, left: 0, top: 0, position: 'absolute', opacity: 0.10, background: '#C70850', borderRadius: 100}} />
|
||||
<div style={{width: 480, height: 25, left: 15, top: 0, position: 'absolute', opacity: 0.20, background: '#C70850', borderRadius: 100}} />
|
||||
<div style={{width: 390, height: 25, left: 60, top: 0, position: 'absolute', opacity: 0.30, background: '#C70850', borderRadius: 100}} />
|
||||
<div style={{width: 350, height: 25, left: 80, top: 0, position: 'absolute', opacity: 0.40, background: '#C70850', borderRadius: 100}} />
|
||||
<div style={{width: 320, height: 25, left: 95, top: 0, position: 'absolute', opacity: 0.50, background: '#C70850', borderRadius: 100}} />
|
||||
<div style={{width: 260, height: 25, left: 125, top: 0, position: 'absolute', background: '#C70850', borderRadius: 100}} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user