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 8e272264..7d681dc0 100644
--- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx
+++ b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx
@@ -37,9 +37,16 @@ export const VOICE_MODES = {
MODE_4: 'mode4', // 추후 추가
};
+// 음성인식 입력 모드 (VUI vs WebSpeech)
+export const VOICE_INPUT_MODE = {
+ VUI: 'vui', // VUI (Voice UI Framework)
+ WEBSPEECH: 'webspeech', // Web Speech API
+};
+
const OVERLAY_SPOTLIGHT_ID = 'voice-input-overlay-container';
const INPUT_SPOTLIGHT_ID = 'voice-overlay-input-box';
const MIC_SPOTLIGHT_ID = 'voice-overlay-mic-button';
+const MIC_WEBSPEECH_SPOTLIGHT_ID = 'voice-overlay-mic-webspeech-button';
const VoiceInputOverlay = ({
isVisible,
@@ -54,11 +61,15 @@ const VoiceInputOverlay = ({
const lastFocusedElement = useRef(null);
const [inputFocus, setInputFocus] = useState(false);
const [micFocused, setMicFocused] = useState(false);
+ const [micWebSpeechFocused, setMicWebSpeechFocused] = useState(false);
// 내부 모드 상태 관리 (prompt -> listening -> close)
const [currentMode, setCurrentMode] = useState(mode);
+ // 음성인식 입력 모드 (VUI vs WebSpeech)
+ const [voiceInputMode, setVoiceInputMode] = useState(null);
+ // ⛔ VUI 테스트 비활성화: VoicePanel 독립 테스트 시 충돌 방지
// Redux에서 voice 상태 가져오기
- const { isRegistered, lastSTTText, sttTimestamp } = useSelector((state) => state.voice);
+ // const { isRegistered, lastSTTText, sttTimestamp } = useSelector((state) => state.voice);
// Redux에서 shopperHouse 검색 결과 가져오기
const shopperHouseData = useSelector((state) => state.search.shopperHouseData);
@@ -78,25 +89,26 @@ const VoiceInputOverlay = ({
}
}, [shopperHouseData, isVisible, onClose]);
+ // ⛔ VUI 테스트 비활성화: STT 텍스트 수신 처리
// STT 텍스트 수신 시 처리
- useEffect(() => {
- if (lastSTTText && sttTimestamp && isVisible) {
- console.log('[VoiceInputOverlay] STT text received in overlay:', lastSTTText);
+ // useEffect(() => {
+ // if (lastSTTText && sttTimestamp && isVisible) {
+ // console.log('[VoiceInputOverlay] STT text received in overlay:', lastSTTText);
- // 입력창에 텍스트 표시 (부모 컴포넌트로 전달)
- if (onSearchChange) {
- onSearchChange({ value: lastSTTText });
- }
+ // // 입력창에 텍스트 표시 (부모 컴포넌트로 전달)
+ // if (onSearchChange) {
+ // onSearchChange({ value: lastSTTText });
+ // }
- // listening 모드로 전환 (시각적 피드백)
- setCurrentMode(VOICE_MODES.LISTENING);
+ // // listening 모드로 전환 (시각적 피드백)
+ // setCurrentMode(VOICE_MODES.LISTENING);
- // 1초 후 자동 닫기 (선택사항)
- setTimeout(() => {
- onClose();
- }, 1000);
- }
- }, [lastSTTText, sttTimestamp, isVisible, onSearchChange, onClose]);
+ // // 1초 후 자동 닫기 (선택사항)
+ // setTimeout(() => {
+ // onClose();
+ // }, 1000);
+ // }
+ // }, [lastSTTText, sttTimestamp, isVisible, onSearchChange, onClose]);
// Overlay가 열릴 때 포커스를 overlay 내부로 이동
useEffect(() => {
@@ -106,13 +118,15 @@ const VoiceInputOverlay = ({
// 모드 초기화 (항상 prompt 모드로 시작)
setCurrentMode(mode);
+ setVoiceInputMode(null);
// Overlay 내부로 포커스 이동
setTimeout(() => {
Spotlight.focus(OVERLAY_SPOTLIGHT_ID);
}, 100);
} else {
- // Overlay가 닫힐 때 원래 포커스 복원
+ // Overlay가 닫힐 때 원래 포커스 복원 및 상태 초기화
+ setVoiceInputMode(null);
if (lastFocusedElement.current) {
setTimeout(() => {
Spotlight.focus(lastFocusedElement.current);
@@ -199,7 +213,7 @@ const VoiceInputOverlay = ({
setInputFocus(false);
}, []);
- // 마이크 버튼 포커스 핸들러
+ // 마이크 버튼 포커스 핸들러 (VUI)
const handleMicFocus = useCallback(() => {
setMicFocused(true);
}, []);
@@ -208,10 +222,55 @@ const VoiceInputOverlay = ({
setMicFocused(false);
}, []);
- // 마이크 버튼 클릭 (모드 전환: prompt -> listening -> close)
- const handleMicClick = useCallback(
+ // WebSpeech 마이크 버튼 포커스 핸들러
+ const handleMicWebSpeechFocus = useCallback(() => {
+ setMicWebSpeechFocused(true);
+ }, []);
+
+ const handleMicWebSpeechBlur = useCallback(() => {
+ setMicWebSpeechFocused(false);
+ }, []);
+
+ // ⛔ VUI 테스트 비활성화: VUI 마이크 버튼 클릭 핸들러
+ // VUI 마이크 버튼 클릭 (모드 전환: prompt -> listening -> close)
+ // const handleVUIMicClick = useCallback(
+ // (e) => {
+ // console.log('[VoiceInputOverlay] handleVUIMicClick called, currentMode:', currentMode);
+
+ // // 이벤트 전파 방지 - dim 레이어의 onClick 실행 방지
+ // if (e && e.stopPropagation) {
+ // e.stopPropagation();
+ // }
+ // if (e && e.nativeEvent && e.nativeEvent.stopImmediatePropagation) {
+ // e.nativeEvent.stopImmediatePropagation();
+ // }
+
+ // if (currentMode === VOICE_MODES.PROMPT) {
+ // // prompt 모드에서 클릭 시 -> VUI listening 모드로 전환
+ // console.log('[VoiceInputOverlay] Switching to VUI LISTENING mode');
+ // setVoiceInputMode(VOICE_INPUT_MODE.VUI);
+ // setCurrentMode(VOICE_MODES.LISTENING);
+ // // 이 시점에서 webOS Voice Framework가 자동으로 음성인식 시작
+ // // (이미 registerVoiceFramework()로 등록되어 있으므로)
+ // } else if (currentMode === VOICE_MODES.LISTENING && voiceInputMode === VOICE_INPUT_MODE.VUI) {
+ // // VUI listening 모드에서 클릭 시 -> 종료
+ // console.log('[VoiceInputOverlay] Closing from VUI LISTENING mode');
+ // setVoiceInputMode(null);
+ // onClose();
+ // } else {
+ // // 기타 모드에서는 바로 종료
+ // console.log('[VoiceInputOverlay] Closing from other mode');
+ // setVoiceInputMode(null);
+ // onClose();
+ // }
+ // },
+ // [currentMode, voiceInputMode, onClose]
+ // );
+
+ // WebSpeech 마이크 버튼 클릭 (모드 전환: prompt -> listening -> close)
+ const handleWebSpeechMicClick = useCallback(
(e) => {
- console.log('[VoiceInputOverlay] handleMicClick called, currentMode:', currentMode);
+ console.log('[VoiceInputOverlay] handleWebSpeechMicClick called, currentMode:', currentMode);
// 이벤트 전파 방지 - dim 레이어의 onClick 실행 방지
if (e && e.stopPropagation) {
@@ -222,28 +281,34 @@ const VoiceInputOverlay = ({
}
if (currentMode === VOICE_MODES.PROMPT) {
- // prompt 모드에서 클릭 시 -> listening 모드로 전환
- console.log('[VoiceInputOverlay] Switching to LISTENING mode');
+ // prompt 모드에서 클릭 시 -> WebSpeech listening 모드로 전환
+ console.log('[VoiceInputOverlay] Switching to WebSpeech LISTENING mode');
+ setVoiceInputMode(VOICE_INPUT_MODE.WEBSPEECH);
setCurrentMode(VOICE_MODES.LISTENING);
- // 이 시점에서 webOS Voice Framework가 자동으로 음성인식 시작
- // (이미 registerVoiceFramework()로 등록되어 있으므로)
- } else if (currentMode === VOICE_MODES.LISTENING) {
- // listening 모드에서 클릭 시 -> 종료
- console.log('[VoiceInputOverlay] Closing from LISTENING mode');
+ // TODO: Web Speech API 시작 로직 추가
+ } else if (
+ currentMode === VOICE_MODES.LISTENING &&
+ voiceInputMode === VOICE_INPUT_MODE.WEBSPEECH
+ ) {
+ // WebSpeech listening 모드에서 클릭 시 -> 종료
+ console.log('[VoiceInputOverlay] Closing from WebSpeech LISTENING mode');
+ setVoiceInputMode(null);
onClose();
} else {
// 기타 모드에서는 바로 종료
console.log('[VoiceInputOverlay] Closing from other mode');
+ setVoiceInputMode(null);
onClose();
}
},
- [currentMode, onClose]
+ [currentMode, voiceInputMode, onClose]
);
// dim 레이어 클릭 핸들러 (마이크 버튼과 분리)
const handleDimClick = useCallback(
(e) => {
console.log('[VoiceInputOverlay] dimBackground clicked');
+ setVoiceInputMode(null);
onClose();
},
[onClose]
@@ -264,8 +329,8 @@ const VoiceInputOverlay = ({
{/* 배경 dim 레이어 - 클릭하면 닫힘 */}
- {/* Voice 등록 상태 표시 (디버깅용) */}
- {process.env.NODE_ENV === 'development' && (
+ {/* ⛔ VUI 테스트 비활성화: Voice 등록 상태 표시 (디버깅용) */}
+ {/* {process.env.NODE_ENV === 'development' && (
+ {isListening && (
+
+ )}
+
+
+ {/* 모드별 컨텐츠 */}
+ {renderModeContent()}
+
+ );
+};
+```
+
+---
+
+## 🧪 테스트 가이드
+
+### 1. 개발 환경 테스트 (Chrome 브라우저)
+
+**⚠️ 주의**: 개발 환경에서는 마이크 권한 팝업이 표시됩니다 (webOS TV와 다름)
+
+```bash
+# 1. 프로젝트 실행
+npm run serve
+
+# 2. Chrome 브라우저에서 접속
+# http://localhost:8080
+
+# 3. 콘솔에서 Web Speech API 지원 확인
+console.log('Web Speech API 지원:', !!(window.SpeechRecognition || window.webkitSpeechRecognition));
+
+# 4. SearchPanel 열기
+# 5. 마이크 버튼 클릭
+# 6. ⚠️ 브라우저에서 마이크 권한 허용 팝업 표시됨 (최초 1회)
+# - "Allow" 버튼 클릭
+# 7. 음성 발화 ("아이폰" 등)
+# 8. 콘솔에서 STT 결과 확인:
+# - [WebSpeech] Result: { transcript: "아이폰", isFinal: true, confidence: 0.9 }
+# - [WebSpeechActions] Result: ...
+# - [useWebSpeech] STT text received: 아이폰
+```
+
+**개발 환경 마이크 권한 문제 해결:**
+
+```javascript
+// Chrome 브라우저에서 마이크 권한이 차단되었을 경우:
+// 1. 주소창 왼쪽의 자물쇠 아이콘 클릭
+// 2. "마이크" 권한 설정
+// 3. "허용"으로 변경
+// 4. 페이지 새로고침
+```
+
+### 2. webOS TV 시뮬레이터 테스트
+
+**✅ 장점**: webOS TV 환경에서는 마이크 권한 팝업이 없음!
+
+```bash
+# 1. appinfo.json 권한 확인 (필수!)
+# webos-meta/appinfo.json에 "audio.capture" 권한이 있는지 확인
+
+# 2. 빌드
+npm run build
+
+# 3. 패키징
+npm run package
+
+# 4. 시뮬레이터에 설치
+npm run install-tv
+
+# 5. 실행
+npm run launch-tv
+
+# 6. 음성 입력 테스트
+# - SearchPanel 열기
+# - 마이크 버튼 클릭
+# - ✅ 권한 팝업 없이 바로 음성 인식 시작됨
+# - 음성 발화 ("아이폰" 등)
+# - 결과 확인 (SearchPanel에 검색 결과 표시)
+
+# 7. 로그 확인 (Remote Inspector)
+# - Chrome에서 chrome://webos-devtools 접속
+# - 시뮬레이터 앱 선택 → Inspect
+# - 콘솔에서 [WebSpeech] 로그 확인
+```
+
+**시뮬레이터 마이크 테스트:**
+
+```bash
+# 시뮬레이터에서는 PC의 마이크를 사용합니다
+# - macOS/Windows: 기본 마이크 자동 사용
+# - 마이크가 없으면 음성 인식 불가 ("no-speech" 에러)
+```
+
+### 3. 실제 TV 테스트
+
+**✅ 최종 테스트**: 실제 webOS TV에서 리모컨 마이크 사용
+
+```bash
+# 1. TV를 개발자 모드로 설정
+# - TV 설정 → 일반 → 정보 → TV 정보
+# - "개발자 모드 앱" 다운로드
+# - Dev Mode 활성화
+
+# 2. ares-setup-device로 TV 등록
+ares-setup-device --add tv --info "{'host': '192.168.x.x', 'port': '9922', 'username': 'prisoner'}"
+
+# 3. appinfo.json 권한 확인 (필수!)
+# webos-meta/appinfo.json에 "audio.capture" 권한 추가되어 있는지 확인
+
+# 4. 설치 및 실행
+npm run build-ipk
+npm run install-tv
+npm run launch-tv
+```
+
+**실제 TV 테스트 시나리오:**
+
+1. **앱 설치 시**:
+ - ✅ 마이크 권한 팝업 없음 (appinfo.json 권한으로 자동 허용)
+
+2. **마이크 버튼 클릭 테스트**:
+ ```
+ SearchPanel → 마이크 버튼 클릭
+ → VoiceInputOverlay 표시
+ → 마이크 버튼 다시 클릭
+ → 즉시 음성 인식 시작 (권한 팝업 없음)
+ → "아이폰" 발화
+ → 검색 결과 표시
+ ```
+
+3. **리모컨 음성 버튼 테스트** (선택사항):
+ ```
+ SearchPanel 화면에서
+ → 리모컨 음성 버튼(🎤) 누름
+ → VoiceInputOverlay 자동 표시 + 음성 인식 시작
+ → "갤럭시" 발화
+ → 검색 결과 표시
+ ```
+
+4. **로그 확인**:
+ ```bash
+ # Remote Inspector로 실제 TV 디버깅
+ ares-inspect com.lgshop.app --device tv --open
+
+ # 콘솔 로그 확인:
+ # [WebSpeech] Initialized with config: { lang: 'ko-KR', ... }
+ # [WebSpeech] Starting recognition...
+ # [WebSpeech] Recognition started
+ # [WebSpeech] Result: { transcript: "아이폰", isFinal: true, ... }
+ # [useWebSpeech] STT text received: 아이폰
+ ```
+
+---
+
+## 🔧 webOS TV 환경 최적화
+
+### 1. 마이크 권한 처리 (중요!)
+
+**webOS TV 환경의 특별한 점:**
+
+webOS TV에서는 일반 웹 브라우저와 달리 **별도의 런타임 마이크 권한 요청 팝업이 없습니다** (LG 담당자 확인).
+
+**권한 설정 방법:**
+
+1. `webos-meta/appinfo.json`에 `audio.capture` 권한 추가
+2. 앱 설치 시 자동으로 권한 부여
+3. 사용자에게 별도 팝업 표시 없음
+
+**파일**: `webos-meta/appinfo.json`
+
+```json
+{
+ "id": "com.lgshop.app",
+ "version": "2.0.0",
+ "vendor": "LG",
+ "type": "web",
+ "main": "index.html",
+ "title": "ShopTime",
+ "icon": "icon.png",
+ "requiredPermissions": [
+ "audio.capture"
+ ]
+}
+```
+
+**중요 사항:**
+
+- ✅ **webOS TV**: 권한 팝업 없음, appinfo.json만 설정
+- ⚠️ **Chrome 브라우저 (개발 환경)**: 최초 1회 권한 요청 팝업 표시됨
+- 💡 **결론**: TV 환경에서는 사용자 경험 단절 없이 바로 음성 인식 시작 가능
+
+**권한 체크 코드 (불필요):**
+
+webOS TV에서는 아래 권한 체크 코드가 불필요합니다:
+
+```javascript
+// ❌ webOS TV에서는 불필요한 코드
+// (Chrome 브라우저 개발 환경에서만 필요)
+const checkMicrophonePermission = async () => {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ stream.getTracks().forEach(track => track.stop());
+ return true;
+ } catch (error) {
+ console.error('Microphone permission denied:', error);
+ return false;
+ }
+};
+```
+
+**실제 필요한 것:**
+
+```javascript
+// ✅ webOS TV에서 필요한 것: appinfo.json 설정만
+// WebSpeechService.start()를 바로 호출하면 됨
+```
+
+### 2. 리모컨 버튼 통합
+
+webOS TV 리모컨의 음성 버튼과 통합:
+
+```javascript
+// src/views/SearchPanel/SearchPanel.new.jsx
+
+useEffect(() => {
+ if (!isOnTop) return;
+
+ // 리모컨 음성 버튼 (KeyCode 461) 감지
+ const handleKeyDown = (event) => {
+ if (event.keyCode === 461) {
+ // 음성 버튼
+ event.preventDefault();
+ setIsVoiceOverlayVisible(true);
+ // 자동으로 음성 인식 시작
+ setTimeout(() => {
+ startListening();
+ }, 300);
+ }
+ };
+
+ document.addEventListener('keydown', handleKeyDown);
+
+ return () => {
+ document.removeEventListener('keydown', handleKeyDown);
+ };
+}, [isOnTop, startListening]);
+```
+
+### 3. 네트워크 오류 처리
+
+Web Speech API는 Google 서버를 사용하므로 네트워크 오류 처리 필요:
+
+```javascript
+// src/hooks/useWebSpeech.js (수정)
+
+useEffect(() => {
+ if (webSpeech.error) {
+ console.error('[useWebSpeech] Error:', webSpeech.error);
+
+ // 네트워크 오류 시 Toast 표시
+ if (webSpeech.error.error === 'network') {
+ dispatch(
+ showErrorToast('네트워크 오류가 발생했습니다. 인터넷 연결을 확인해주세요.', {
+ duration: 3000,
+ })
+ );
+ }
+ // 마이크 권한 오류
+ else if (webSpeech.error.error === 'not-allowed') {
+ dispatch(
+ showErrorToast('마이크 사용 권한이 필요합니다.', {
+ duration: 3000,
+ })
+ );
+ }
+ // 음성 감지 실패
+ else if (webSpeech.error.error === 'no-speech') {
+ dispatch(
+ showWarningToast('음성이 감지되지 않았습니다. 다시 시도해주세요.', {
+ duration: 3000,
+ })
+ );
+ }
+ }
+}, [webSpeech.error, dispatch]);
+```
+
+---
+
+## 📊 Plan A vs Plan B 전환
+
+### Plan A (VUI Framework) 사용 시
+
+```javascript
+// src/views/SearchPanel/SearchPanel.new.jsx
+
+import { useSearchVoice } from '../../hooks/useSearchVoice';
+
+// ...
+
+useSearchVoice(isOnTop, handleSTTText);
+```
+
+### Plan B (Web Speech API) 사용 시
+
+```javascript
+// src/views/SearchPanel/SearchPanel.new.jsx
+
+import { useWebSpeech } from '../../hooks/useWebSpeech';
+
+// ...
+
+const { isListening, startListening, stopListening } = useWebSpeech(
+ isOnTop,
+ handleSTTText,
+ { lang: 'ko-KR' }
+);
+```
+
+### 두 가지 방식 병행 사용 (권장)
+
+```javascript
+// src/views/SearchPanel/SearchPanel.new.jsx
+
+import { useSearchVoice } from '../../hooks/useSearchVoice'; // Plan A
+import { useWebSpeech } from '../../hooks/useWebSpeech'; // Plan B
+
+// ...
+
+// 환경 변수로 제어
+const USE_WEB_SPEECH_API = process.env.USE_WEB_SPEECH_API === 'true';
+
+if (USE_WEB_SPEECH_API) {
+ // Plan B: Web Speech API
+ const { isListening, startListening, stopListening } = useWebSpeech(
+ isOnTop,
+ handleSTTText,
+ { lang: 'ko-KR' }
+ );
+} else {
+ // Plan A: VUI Framework
+ useSearchVoice(isOnTop, handleSTTText);
+}
+```
+
+---
+
+## 🚀 구현 체크리스트
+
+### 필수 구현
+
+- [ ] `WebSpeechService.js` 생성
+- [ ] `webSpeechActions.js` 생성
+- [ ] `actionTypes.js`에 Web Speech 타입 추가
+- [ ] `voiceReducer.js`에 Web Speech 상태 추가
+- [ ] `useWebSpeech.js` Hook 생성
+- [ ] `SearchPanel.new.jsx`에 useWebSpeech 통합
+- [ ] `VoiceInputOverlay.jsx`에 Web Speech 지원 추가
+
+### 선택적 최적화
+
+- [ ] 리모컨 음성 버튼 통합
+- [ ] 네트워크 오류 Toast 표시
+- [ ] Interim 결과 시각적 표시
+- [ ] 다국어 지원 (en-US, ja-JP 등)
+- [ ] webos-meta/appinfo.json에 권한 추가
+- [ ] 환경 변수로 Plan A/B 전환 가능하도록
+
+### 테스트
+
+- [ ] Chrome 브라우저에서 테스트
+- [ ] webOS 시뮬레이터에서 테스트
+- [ ] 실제 TV에서 테스트
+- [ ] 한국어/영어 음성 인식 테스트
+- [ ] 네트워크 오류 시나리오 테스트
+
+---
+
+## 📚 참고 자료
+
+### Web Speech API 문서
+
+- [MDN Web Speech API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API)
+- [SpeechRecognition Interface](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition)
+- [Chrome Platform Status - Web Speech](https://chromestatus.com/feature/4782875580825600)
+
+### webOS 관련
+
+- [webOS TV Developer Guide](https://webostv.developer.lge.com/)
+- [webOS TV API Reference](https://webostv.developer.lge.com/api/)
+
+### 프로젝트 문서
+
+- `vui-implement.md` - VUI Framework 구현 (Plan A)
+- `web-speech.md` - Web Speech API 구현 (Plan B, 이 문서)
+
+---
+
+## 💡 주의사항
+
+1. **네트워크 의존성**: Web Speech API는 Google 서버를 사용하므로 **인터넷 연결 필수**
+2. **HTTPS 필요**: 로컬호스트 외 환경에서는 HTTPS 필수 (webOS TV는 내부적으로 처리)
+3. **마이크 권한 (중요!)**:
+ - **webOS TV**: `appinfo.json`에 `audio.capture` 권한만 추가하면 됨. **별도 팝업 없음**
+ - **Chrome 브라우저 (개발 환경)**: 최초 1회 권한 요청 팝업 표시
+ - LG 담당자 확인: webOS TV에서는 런타임 권한 요청 없음
+4. **브라우저 호환성**: Chrome/Chromium 기반 브라우저만 지원 (Chrome 68 완벽 지원)
+5. **언어 지원**: 한국어('ko-KR'), 영어('en-US'), 일본어('ja-JP') 등 주요 언어 지원
+6. **연속 인식**: `continuous: false` 권장 (한 번 발화 → 한 번 인식이 TV UX에 적합)
+
+---
+
+## 🎯 다음 단계
+
+1. **WebSpeechService 구현** - 가장 먼저 구현
+2. **Redux 통합** - 액션 및 리듀서 추가
+3. **useWebSpeech Hook** - SearchPanel에서 사용할 Hook
+4. **SearchPanel 통합** - 기존 코드 최소 수정
+5. **테스트** - Chrome → 시뮬레이터 → 실제 TV 순서로
+6. **최적화** - 리모컨 버튼, 에러 처리 등
+
+---
+
+## 📌 핵심 요약 (TL;DR)
+
+### webOS TV 환경의 특별한 점
+
+| 항목 | Chrome 브라우저 (개발) | webOS TV (실제 환경) |
+|------|---------------------|-------------------|
+| **마이크 권한** | 런타임 팝업 표시 (최초 1회) | **팝업 없음** (appinfo.json만 설정) |
+| **권한 설정** | 브라우저 설정에서 수동 허용 | 앱 설치 시 자동 허용 |
+| **사용자 경험** | 권한 허용 단계 필요 | **즉시 음성 인식 시작** |
+
+### Plan A vs Plan B 최종 비교
+
+| 구분 | Plan A (VUI Framework) | Plan B (Web Speech API) |
+|------|----------------------|------------------------|
+| **API** | webOS Voice Conductor | Web Speech API |
+| **구현 복잡도** | ⭐⭐⭐⭐ 높음 | ⭐⭐ 중간 |
+| **플랫폼 의존성** | webOS 전용 | 범용 (Chrome 68+) |
+| **마이크 권한** | appinfo.json | appinfo.json (동일) |
+| **개발 편의성** | 복잡한 Luna 서비스 | 간단한 브라우저 API |
+| **디버깅** | VoicePanel 필요 | Chrome DevTools |
+| **리모컨 통합** | 자동 통합 | 수동 구현 필요 |
+| **네트워크 의존** | webOS 서버 | Google 서버 |
+
+### 결론 및 권장사항
+
+**Plan B (Web Speech API) 추천 상황:**
+- ✅ 빠른 프로토타이핑이 필요할 때
+- ✅ 크로스 플랫폼 개발을 고려할 때
+- ✅ Chrome 브라우저에서도 테스트하고 싶을 때
+- ✅ 간단하고 직관적인 API를 선호할 때
+
+**Plan A (VUI Framework) 추천 상황:**
+- ✅ webOS TV 전용 앱일 때
+- ✅ 리모컨 통합이 필수일 때
+- ✅ LG의 공식 음성 서비스 사용이 필요할 때
+- ✅ 오프라인 환경에서도 동작해야 할 때 (일부 기능)
+
+**현재 상황:**
+- 현재 Plan A (VUI Framework)를 테스트 중
+- Plan B (Web Speech API)는 대안 (fallback)으로 준비
+- 두 가지 모두 Redux 상태를 공유하므로 전환 용이
+
+---
+
+## 🚀 즉시 시작하기
+
+**1분 만에 Web Speech API 테스트:**
+
+```javascript
+// Chrome 콘솔에서 바로 테스트 가능!
+const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
+recognition.lang = 'ko-KR';
+recognition.onresult = (event) => {
+ console.log('인식 결과:', event.results[0][0].transcript);
+};
+recognition.start();
+// 발화: "안녕하세요"
+// 콘솔 출력: 인식 결과: 안녕하세요
+```
+
+**webOS TV에서 바로 사용하려면:**
+
+1. `webos-meta/appinfo.json`에 `"audio.capture"` 권한 추가
+2. `WebSpeechService.js` 복사
+3. `useWebSpeech` Hook 적용
+4. 끝! 🎉
+
+---
+
+이 문서를 참고하여 단계별로 구현하시면 됩니다. 질문이 있으시면 언제든지 말씀해주세요! 🚀