From 31cdfedf3fd015cb70fe6ab28b99d9256dfcfb17 Mon Sep 17 00:00:00 2001 From: optrader Date: Thu, 16 Oct 2025 14:10:19 +0900 Subject: [PATCH] [251016] fix: VUI Test-1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ• ์ปค๋ฐ‹ ์‹œ๊ฐ„: 2025. 10. 16. 14:10:17 ๐Ÿ“Š ๋ณ€๊ฒฝ ํ†ต๊ณ„: โ€ข ์ด ํŒŒ์ผ: 3๊ฐœ โ€ข ์ถ”๊ฐ€: +160์ค„ โ€ข ์‚ญ์ œ: -3์ค„ ๐Ÿ“ ์ถ”๊ฐ€๋œ ํŒŒ์ผ: + com.twin.app.shoptime/vui-guide.2.md ๐Ÿ“ ์ˆ˜์ •๋œ ํŒŒ์ผ: ~ com.twin.app.shoptime/src/App/App.js ~ com.twin.app.shoptime/src/actions/voiceActions.js ๐Ÿ”ง ํ•จ์ˆ˜ ๋ณ€๊ฒฝ ๋‚ด์šฉ: ๐Ÿ“„ com.twin.app.shoptime/src/App/App.js (javascript): โœ… Added: processArgs(), sendVoiceLogToPanel() ๐Ÿ“„ com.twin.app.shoptime/src/actions/voiceActions.js (javascript): ๐Ÿ”„ Modified: addLog() ๐Ÿ”ง ์ฃผ์š” ๋ณ€๊ฒฝ ๋‚ด์šฉ: โ€ข ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ฐœ์„  โ€ข ๊ฐœ๋ฐœ ๋ฌธ์„œ ๋ฐ ๊ฐ€์ด๋“œ ๊ฐœ์„  --- com.twin.app.shoptime/src/App/App.js | 144 ++++++++ .../src/actions/voiceActions.js | 46 ++- com.twin.app.shoptime/vui-guide.2.md | 347 ++++++++++++++++++ 3 files changed, 534 insertions(+), 3 deletions(-) create mode 100644 com.twin.app.shoptime/vui-guide.2.md diff --git a/com.twin.app.shoptime/src/App/App.js b/com.twin.app.shoptime/src/App/App.js index defc0d1c..64799da6 100644 --- a/com.twin.app.shoptime/src/App/App.js +++ b/com.twin.app.shoptime/src/App/App.js @@ -63,6 +63,7 @@ import css from './App.module.less'; import { handleBypassLink } from './bypassLinkHandler'; import { handleDeepLink } from './deepLinkHandler'; import { sendLogTotalRecommend } from '../actions/logActions'; +import { types } from '../actions/actionTypes'; // import { // startFocusMonitoring, // stopFocusMonitoring, @@ -109,15 +110,158 @@ const processArgs = (args) => { }); }; +// Voice ๊ด€๋ จ ๋กœ๊ทธ๋ฅผ VoicePanel๋กœ ์ „์†กํ•˜๋Š” ํ—ฌํผ ํ•จ์ˆ˜ +const sendVoiceLogToPanel = (args) => { + try { + const firstArg = args[0]; + // [Voice] ๋˜๋Š” [VoiceConductor] ํƒœ๊ทธ ํ™•์ธ + if ( + typeof firstArg === 'string' && + (firstArg.includes('[Voice]') || firstArg.includes('[VoiceConductor]')) + ) { + // ๋กœ๊ทธ ํƒ€์ž… ๊ฒฐ์ • + let logType = 'INFO'; + let title = firstArg; + + if ( + firstArg.includes('ERROR') || + firstArg.includes('Error') || + firstArg.includes('Failed') || + firstArg.includes('failed') + ) { + logType = 'ERROR'; + } else if ( + firstArg.includes('Response') || + firstArg.includes('response') || + firstArg.includes('success') + ) { + logType = 'RESPONSE'; + } else if ( + firstArg.includes('Registering') || + firstArg.includes('Sending') || + firstArg.includes('request') + ) { + logType = 'REQUEST'; + } else if ( + firstArg.includes('received') || + firstArg.includes('Handling') || + firstArg.includes('โญ') + ) { + logType = 'ACTION'; + } else if (firstArg.includes('command')) { + logType = 'COMMAND'; + } + + // ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ + const logData = {}; + if (args.length > 1) { + args.slice(1).forEach((arg, index) => { + if (typeof arg === 'object') { + Object.assign(logData, arg); + } else { + logData[`arg${index + 1}`] = arg; + } + }); + } + + // Redux๋กœ dispatch + store.dispatch({ + type: types.VOICE_ADD_LOG, + payload: { + timestamp: new Date().toISOString(), + type: logType, + title: title.replace(/^\[Voice\]\s*/, '').replace(/^\[VoiceConductor\]\s*/, ''), + data: Object.keys(logData).length > 0 ? logData : { message: firstArg }, + success: logType !== 'ERROR', + }, + }); + } + } catch (error) { + // ๋กœ๊น… ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ์›๋ž˜ ๋กœ๊ทธ๋Š” ์ถœ๋ ฅ๋˜์–ด์•ผ ํ•จ + originalConsoleLog.call(console, '[VoiceLog] Error sending to panel:', error); + } +}; + console.log = function (...args) { + // Voice ๋กœ๊ทธ๋ฅผ VoicePanel๋กœ ์ „์†ก + sendVoiceLogToPanel(args); + // ์›๋ž˜ console.log ์‹คํ–‰ originalConsoleLog.apply(console, processArgs(args)); }; console.error = function (...args) { + // Voice ๋กœ๊ทธ๋ฅผ VoicePanel๋กœ ์ „์†ก (์—๋Ÿฌ๋Š” ๊ฐ•์ œ๋กœ ERROR ํƒ€์ž…) + try { + const firstArg = args[0]; + if ( + typeof firstArg === 'string' && + (firstArg.includes('[Voice]') || firstArg.includes('[VoiceConductor]')) + ) { + const logData = {}; + if (args.length > 1) { + args.slice(1).forEach((arg, index) => { + if (typeof arg === 'object') { + Object.assign(logData, arg); + } else { + logData[`arg${index + 1}`] = arg; + } + }); + } + + store.dispatch({ + type: types.VOICE_ADD_LOG, + payload: { + timestamp: new Date().toISOString(), + type: 'ERROR', + title: firstArg.replace(/^\[Voice\]\s*/, '').replace(/^\[VoiceConductor\]\s*/, ''), + data: Object.keys(logData).length > 0 ? logData : { message: firstArg }, + success: false, + }, + }); + } + } catch (error) { + originalConsoleError.call(console, '[VoiceLog] Error sending error to panel:', error); + } + originalConsoleError.apply(console, processArgs(args)); }; console.warn = function (...args) { + // Voice ๋กœ๊ทธ๋ฅผ VoicePanel๋กœ ์ „์†ก (๊ฒฝ๊ณ ๋Š” ERROR ํƒ€์ž…์œผ๋กœ) + try { + const firstArg = args[0]; + if ( + typeof firstArg === 'string' && + (firstArg.includes('[Voice]') || firstArg.includes('[VoiceConductor]')) + ) { + const logData = {}; + if (args.length > 1) { + args.slice(1).forEach((arg, index) => { + if (typeof arg === 'object') { + Object.assign(logData, arg); + } else { + logData[`arg${index + 1}`] = arg; + } + }); + } + + store.dispatch({ + type: types.VOICE_ADD_LOG, + payload: { + timestamp: new Date().toISOString(), + type: 'ERROR', + title: + 'WARNING: ' + + firstArg.replace(/^\[Voice\]\s*/, '').replace(/^\[VoiceConductor\]\s*/, ''), + data: Object.keys(logData).length > 0 ? logData : { message: firstArg }, + success: false, + }, + }); + } + } catch (error) { + originalConsoleWarn.call(console, '[VoiceLog] Error sending warning to panel:', error); + } + originalConsoleWarn.apply(console, processArgs(args)); }; diff --git a/com.twin.app.shoptime/src/actions/voiceActions.js b/com.twin.app.shoptime/src/actions/voiceActions.js index 7d23efda..c7d77fd0 100644 --- a/com.twin.app.shoptime/src/actions/voiceActions.js +++ b/com.twin.app.shoptime/src/actions/voiceActions.js @@ -99,20 +99,30 @@ export const registerVoiceFramework = () => (dispatch, getState) => { // performAction command received if (res.command === 'performAction' && res.action) { - console.log('[Voice] performAction command received:', res.action); + console.log('[Voice] โญโญโญ performAction command received:', res.action); + + // โญ ์ค‘์š”: performAction ์ˆ˜์‹  ์„ฑ๊ณต ๋กœ๊ทธ (๋ช…ํ™•ํ•˜๊ฒŒ) dispatch( - addLog('COMMAND', 'performAction Command Received', { + addLog('COMMAND', 'โœ… performAction RECEIVED!', { + message: 'โœ… SUCCESS! Voice framework sent performAction event.', command: res.command, action: res.action, + intent: res.action?.intent, + value: res.action?.value || res.action?.itemId, }) ); + dispatch({ type: types.VOICE_PERFORM_ACTION, payload: res.action, }); + // Get voiceTicket from Redux state (performAction response doesn't include voiceTicket) + const { voiceTicket } = getState().voice; + console.log('[Voice] Using voiceTicket from state:', voiceTicket); + // Process the action and report result - dispatch(handleVoiceAction(res.voiceTicket, res.action)); + dispatch(handleVoiceAction(voiceTicket, res.action)); } }, @@ -215,6 +225,36 @@ export const sendVoiceIntents = (voiceTicket) => (dispatch, getState) => { type: types.VOICE_SET_CONTEXT_SUCCESS, payload: res, }); + + // โญ ์ค‘์š”: Voice input ๋Œ€๊ธฐ ์ƒํƒœ ๋กœ๊ทธ + dispatch( + addLog('ACTION', '๐ŸŽค Ready for Voice Input', { + message: 'Context set successfully. Press the MIC button on remote and speak.', + nextStep: 'Waiting for performAction event...', + voiceTicket: voiceTicket, + }) + ); + + // 15์ดˆ ํ›„์—๋„ performAction์ด ์•ˆ ์˜ค๋ฉด ๊ฒฝ๊ณ  ๋กœ๊ทธ (ํƒ€์ด๋จธ) + setTimeout(() => { + const currentState = getState().voice; + // lastSTTText๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์•˜์œผ๋ฉด (performAction์ด ์•ˆ ์™”์œผ๋ฉด) + if (!currentState.lastSTTText || currentState.sttTimestamp < Date.now() - 14000) { + dispatch( + addLog('ERROR', 'โš ๏ธ No performAction received yet', { + message: 'performAction event was not received within 15 seconds after setContext.', + possibleReasons: [ + '1. Did you press the MIC button on the remote control?', + '2. Did you speak after pressing the MIC button?', + '3. UseIME intent might not be supported on this webOS version', + '4. Voice framework might not be routing events correctly', + ], + suggestion: + 'Try pressing the remote MIC button and speaking clearly. Check VoicePanel logs for performAction event.', + }) + ); + } + }, 15000); }, onFailure: (err) => { diff --git a/com.twin.app.shoptime/vui-guide.2.md b/com.twin.app.shoptime/vui-guide.2.md new file mode 100644 index 00000000..c8286686 --- /dev/null +++ b/com.twin.app.shoptime/vui-guide.2.md @@ -0,0 +1,347 @@ +# `com.webos.service.voiceconductor` v1.1 API ๋ฌธ์„œ + +> **์ƒ์„ฑ์ผ**: 2024/04/18 +> **์ตœ์ข… ์ˆ˜์ •์ž**: soonwon.hong +> **๋ฌธ์„œ ์ƒ์„ฑ ๋„๊ตฌ**: APIEditorScript (`apieditor.script`) +> **๋Œ€์ƒ ํ”Œ๋žซํผ**: webOS TV + +--- + +## ๐Ÿ“Œ ์š”์•ฝ (Summary) + +`voiceconductor`๋Š” webOS TV์˜ **์Œ์„ฑ ํ”„๋ ˆ์ž„์›Œํฌ์˜ ํ•ต์‹ฌ ์„œ๋น„์Šค**๋กœ, ์ „์ฒด ์Œ์„ฑ ์ฒ˜๋ฆฌ ํ๋ฆ„์„ ์กฐ์œจ(Conduct)ํ•ฉ๋‹ˆ๋‹ค. +๋ชจ๋“  ์Œ์„ฑ ๊ด€๋ จ ์š”์ฒญ์€ ์ด ์„œ๋น„์Šค๋ฅผ ํ†ตํ•ด ์‹œ์ž‘๋˜๋ฉฐ, ๋‹ค์Œ ์ฃผ์š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: + +- **STT (Speech-to-Text)**: ์‚ฌ์šฉ์ž ์Œ์„ฑ์„ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ +- **Intent ์ธ์‹**: ์Œ์„ฑ ๋˜๋Š” ํ…์ŠคํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์˜๋„(Intent) ๋ถ„์„ + +--- + +## ๐Ÿ” ๊ฐœ์š” (Overview) + +### API ์นดํ…Œ๊ณ ๋ฆฌ + +| ์นดํ…Œ๊ณ ๋ฆฌ | ์„ค๋ช… | +|--------|------| +| `/` | ๊ตญ๊ฐ€๋ณ„ ๋น„์ฆˆ๋‹ˆ์Šค ๊ณ„์•ฝ์— ๋”ฐ๋ผ **Google, Alexa, Alibaba, LG** ๋“ฑ ๋‹ค์–‘ํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ค‘ ํ•˜๋‚˜๊ฐ€ ์ž๋™ ์„ ํƒ๋˜์–ด ์‹คํ–‰ | +| `/system` | **LG ์ž์‚ฌ ํ”Œ๋Ÿฌ๊ทธ์ธ**์„ ๊ฐ•์ œ๋กœ ์‚ฌ์šฉ (๊ธฐ๋ณธ๊ฐ’). `/`์™€ ๋™์ผํ•œ API ํ˜•์‹์„ ๊ฐ€์ง€๋ฉฐ, ์ค‘๋ณต ์„ค๋ช… ์ƒ๋žต | +| `/interactor` | **In-App Control** ๊ธฐ๋Šฅ ์ง€์› (webOS 4.5+) โ€” ์•ฑ ๋‚ด์—์„œ ์Œ์„ฑ ๋ช…๋ น ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ | + +--- + +## ๐Ÿ”ง ๋ฉ”์„œ๋“œ (Methods) + +### `/getRecognitionStatus` +> ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ ์Œ์„ฑ ์ธ์‹ ์ž‘์—… ์ƒํƒœ ์กฐํšŒ (Subscription ์ง€์›) + +- **์ง€์› ํ”Œ๋žซํผ**: ์—†์Œ (`[Private]`) +- **ACG**: ์—†์Œ +- **ํŒŒ๋ผ๋ฏธํ„ฐ**: + - `taskTypes` (String[]): `["fullVoiceNlp", "voiceNlp", "textNlp", "stt"]` + - `subscribe` (Boolean): ๊ตฌ๋… ์—ฌ๋ถ€ +- **๋ฐ˜ํ™˜**: + - `tasks`: ์‹คํ–‰ ์ค‘์ธ `voiceTask` ๊ฐ์ฒด ๋ฐฐ์—ด + - `subscribed`, `returnValue`, `errorCode`, `errorText` + +โœ… **์˜ˆ์‹œ**: +```json +{ + "tasks": [ + { + "voiceTicket": "V00001", + "taskType": "fullVoiceNlp", + "voiceEngine": "googleAssistant", + "recognitionSource": { "input": "voiceinput", "type": "remote", "key": "mrcu" }, + "status": "init" + } + ], + "subscribed": true, + "returnValue": true +} +``` + +--- + +### `/cancelRecognition` +> ์ง„ํ–‰ ์ค‘์ธ ์Œ์„ฑ ์ธ์‹ ์ž‘์—… ์ทจ์†Œ + +- **์ง€์›**: TV (`[Private]`) +- **ํŒŒ๋ผ๋ฏธํ„ฐ**: `voiceTicket` (String) +- **๋ฐ˜ํ™˜**: `returnValue`, `errorCode`, `errorText` + +> โš ๏ธ `stopRecordingVoice`์™€ ๋‹ฌ๋ฆฌ, ๋…น์Œ ์ค‘์ด ์•„๋‹ ๊ฒฝ์šฐ STT ํ”Œ๋Ÿฌ๊ทธ์ธ์— ์ข…๋ฃŒ ์š”์ฒญ์„ ๋ณด๋‚ด์ง€ ์•Š์Œ. + +--- + +### `/checkUpdate` +> ํŠน์ • ํŒจํ‚ค์ง€์— ๋Œ€ํ•œ ์Œ์„ฑ ๊ธฐ๋Šฅ ์—…๋ฐ์ดํŠธ ํ•„์š” ์—ฌ๋ถ€ ํ™•์ธ + +- **์ง€์›**: TV (`[Do Not Publish]`) +- **ํŒŒ๋ผ๋ฏธํ„ฐ**: + - `type`: `"package"` (๊ณ ์ •) + - `id`: ํŒจํ‚ค์ง€ ID (์˜ˆ: `"amazon.alexa"`) +- **๋ฐ˜ํ™˜**: `needUpdate` (Boolean) + +--- + +### `/getSupportedLanguages` +> ํ˜„์žฌ ์ง€์›ํ•˜๋Š” ์–ธ์–ด ๋ชฉ๋ก ์กฐํšŒ + +- **์ง€์›**: TV (`[Public]`) +- **ํŒŒ๋ผ๋ฏธํ„ฐ**: + - `languageCodes`: BCP-47 ์–ธ์–ด ์ฝ”๋“œ ๋ฐฐ์—ด (์˜ˆ: `["ko-KR", "en-US"]`) + - `voiceEngine`: `"stt"` (๊ธฐ๋ณธ) ๋˜๋Š” `"nlp"` + - `showAll`: ์—”์ง„๋ณ„ ์ƒ์„ธ ์–ธ์–ด ๋ฐ˜ํ™˜ ์—ฌ๋ถ€ + - `loadDefault`: ์„œ๋ฒ„ ์—†์ด ๊ธฐ๋ณธ ์–ธ์–ด ๋ฐ˜ํ™˜ +- **๋ฐ˜ํ™˜**: + - `voiceLanguages`: `{ "ko-KR": "ko-KR", "xx-XX": "notSupported" }` + - `voiceEngines`: (showAll=true ์‹œ) + +โœ… **์„ฑ๊ณต ์‘๋‹ต**: +```json +{ + "returnValue": true, + "voiceLanguages": { + "ko-KR": "ko-KR", + "en-US": "en-US", + "xx-XX": "notSupported" + } +} +``` + +--- + +### `/getUtteranceEvents` +> **`getVoiceUiEvents`์™€ ๋™์ผ** โ€” ์„ค๋ช… ์ƒ๋žต + +--- + +### `/getVoiceKey` +> ์„œ๋ฒ„ ํ†ต์‹ ์— ํ•„์š”ํ•œ ๋ณด์•ˆ ํ‚ค ๋ฐ˜ํ™˜ + +- **๋ฐ˜ํ™˜**: `vsn`, `staticVoiceKey`, `dynamicVoiceKey` + +--- + +### `/getVoiceUiEvents` +> ์Œ์„ฑ ์ฒ˜๋ฆฌ ์ค‘ UI ์ด๋ฒคํŠธ ์ˆ˜์‹  (Subscription ํ•„์ˆ˜) + +- **์ง€์›**: TV (`[Public]`) +- **์ด๋ฒคํŠธ ์ข…๋ฅ˜**: + - `registered`, `sttStart`, `sttVoiceLevel`, `sttPartialResult`, `sttResult`, `sttEnd` + - `nlpStart`, `nlpEnd`, `actionFeedback`, `sessionEnd`, `error` + +โœ… **์ด๋ฒคํŠธ ์˜ˆ์‹œ**: +```json +{ "event": "sttVoiceLevel", "level": 45, "subscribed": true } +{ "event": "actionFeedback", "feedback": { "systemUtterance": "{{1๋กœ}} ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค." } } +{ "event": "sessionEnd", "subscribed": false } +``` + +--- + +## ๐ŸŽฎ In-App Control (`/interactor`) + +### `/interactor/register` +> ์•ฑ์„ ์Œ์„ฑ ํ”„๋ ˆ์ž„์›Œํฌ์— ๋“ฑ๋ก (Subscription ํ•„์ˆ˜) + +- **๋ช…๋ น์–ด**: + - `setContext`: ์•ฑ์ด `setContext` ํ˜ธ์ถœ ํ•„์š” + - `performAction`: ์•ฑ์ด ์•ก์…˜ ์ˆ˜ํ–‰ ํ›„ `reportActionResult` ํ˜ธ์ถœ + +โœ… **์‘๋‹ต ์˜ˆ์‹œ**: +```json +{ "command": "setContext", "voiceTicket": "V00000006" } +{ "command": "performAction", "action": { "intent": "Select", "itemId": "test" } } +``` + +--- + +### `/interactor/reportActionResult` +> In-App ์•ก์…˜ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ ๋ณด๊ณ  + +- **ํ•„์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ**: `voiceTicket`, `result` (Boolean) +- **์˜ต์…˜**: `feedback` (`inAppFeedback`) + +--- + +### `/interactor/setContext` +> ์•ฑ์ด ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ Intent ๋ชฉ๋ก ๋“ฑ๋ก + +- **ํŒŒ๋ผ๋ฏธํ„ฐ**: `voiceTicket`, `inAppIntents` (๋ฐฐ์—ด) +- **Intent ์œ ํ˜•**: `Select`, `Scroll`, `PlayContent`, `Zoom` ๋“ฑ 9๊ฐ€์ง€ + +โœ… **์˜ˆ์‹œ**: +```json +{ + "inAppIntents": [ + { + "intent": "Select", + "items": [{ "itemId": "test", "value": ["test"] }] + } + ] +} +``` + +--- + +## ๐ŸŽค ์Œ์„ฑ ์ธ์‹ API + +### `/recognizeIntentByText` +> ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ Intent ๋ถ„์„ ๋ฐ ์‹คํ–‰ + +- **์ง€์›**: TV (`[Public]`) +- **ํŒŒ๋ผ๋ฏธํ„ฐ**: `text`, `language`, `runVoiceUi`, `inAppControl`, `source` +- **๋ฐ˜ํ™˜**: `serverResponse` (`responseCode`, `responseMessage`) + +--- + +### `/recognizeIntentByVoice` +> PCM ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์Œ์„ฑ Intent ๋ถ„์„ (**ํ…Œ์ŠคํŠธ ์ „์šฉ**) + +- **์ง€์›**: TV (`[Private]`) +- **ํ•„์ˆ˜**: `pcmPath` + +--- + +### `/recognizeVoice` +> ์Œ์„ฑ์„ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ (STT) + +- **๋ฐ˜ํ™˜**: `text` (String ๋ฐฐ์—ด) + +--- + +### `/recognizeVoiceWithDetails` +> STT ๊ณผ์ •์˜ ์ƒ์„ธ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ (Subscription ํ•„์ˆ˜) + +- **์ด๋ฒคํŠธ**: `sttVoiceLevel`, `sttPartialResult`, `sttResult`, `sessionEnd` ๋“ฑ +- **๋ฐ˜ํ™˜**: `event`, `text`, `level`, `feedback`, `voiceTicket` + +โœ… **์‘๋‹ต ์˜ˆ์‹œ**: +```json +{ "event": "sttResult", "text": ["๋ฐฐํŠธ๋งจ", "๋ฒ ํŠธ๋งจ"], "voiceTicket": "V00000007" } +``` + +--- + +### `/setInputEvent` +> ํ•˜๋“œ์›จ์–ด/์†Œํ”„ํŠธ์›จ์–ด ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ + +- **inputType**: `"hold"` (down/up), `"wakeword"` +- **deviceId**: `"remote"`, `"amazonAlexa"` ๋“ฑ + +--- + +### `/recordVoice` & `/stopRecordingVoice` +> ์Œ์„ฑ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆฌ๋ฐ ๋…น์Œ ์‹œ์ž‘/์ข…๋ฃŒ + +- **recordVoice**: WebSocket URL ๋ฐ˜ํ™˜ (`websocketUrl`) +- **stopRecordingVoice**: ๋…น์Œ ์ค‘์ง€ + +โœ… **recordVoice ์‘๋‹ต**: +```json +{ "event": "recordingStart", "websocketUrl": "wss://..." } +{ "event": "recordingEnd" } +``` + +--- + +## ๐Ÿ“ฆ ์‹œ์Šคํ…œ API + +### `/system/checkUpdate` +> LG ์ž์‚ฌ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ธฐ๋ฐ˜ ์—…๋ฐ์ดํŠธ ํ™•์ธ + +- **๋™์ž‘**: ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํŒ์—… ํ‘œ์‹œ ํ›„ ์Œ์„ฑ ๊ธฐ๋Šฅ ์ค‘๋‹จ + +--- + +## โŒ ์˜ค๋ฅ˜ ์ฝ”๋“œ ์ฐธ์กฐ + +| ์ฝ”๋“œ | ํ…์ŠคํŠธ | ์„ค๋ช… | +|------|--------|------| +| 100 | `bad params` | ํŒŒ๋ผ๋ฏธํ„ฐ ์˜ค๋ฅ˜ | +| 101 | `deprecated api` | ๋” ์ด์ƒ ์ง€์›๋˜์ง€ ์•Š์Œ | +| 300 | `precondition not satisfied` | ๋„คํŠธ์›Œํฌ/์„ค์ • ๋ฏธ์™„๋ฃŒ | +| 301 | `internal processing error` | ๋‚ด๋ถ€ ์˜ค๋ฅ˜ (๋ฉ”๋ชจ๋ฆฌ ๋“ฑ) | +| 302 | `failed recognize voice` | STT ์‹คํŒจ | +| 303 | `failed recognize intent` | NLP ์‹คํŒจ | +| 304 | `unsupported language` | ์–ธ์–ด ๋ฏธ์ง€์› | +| 305 | `already processing another request` | ๋‹ค๋ฅธ ์š”์ฒญ ์ง„ํ–‰ ์ค‘ | + +--- + +## ๐Ÿงฑ ๊ฐ์ฒด ์ •์˜ (Objects) + +### `actionFeedback` +- `systemUtterance`: ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œํ•  ๋ฌธ๊ตฌ +- `exception`: ์˜ˆ์™ธ ์œ ํ˜• (์˜ˆ: `"alreadyCompleted"`) + +--- + +### `serverResponse` +- `responseCode`: ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ ์ฝ”๋“œ +- `responseMessage`: ํ”ผ๋“œ๋ฐฑ ๋ฉ”์‹œ์ง€ + +--- + +### `inAppAction` +- `type`: `"IntentMatch"` +- `intent`: `"Select"`, `"Scroll"`, `"Zoom"` ๋“ฑ +- `itemId`: ์•ฑ ๋‚ด ๊ณ ์œ  ID +- ์ถ”๊ฐ€ ํ•„๋“œ: `scroll`, `checked`, `state`, `control`, `zoom` + +--- + +### `inAppIntent` +- `intent`: ์ง€์› Intent ์œ ํ˜• +- `items`: ์„ ํƒ ๊ฐ€๋Šฅํ•œ ํ•ญ๋ชฉ (`inAppIntentItem` ๋ฐฐ์—ด) +- `itemId`: ์Šคํฌ๋กค/์คŒ ๋“ฑ ๋‹จ์ผ ์•ก์…˜์šฉ ID + +--- + +### `inAppIntentItem` +- `itemId`: ๊ณ ์œ  ID +- `value`: STT ๋งค์นญ ๋ฌธ๊ตฌ ๋ฐฐ์—ด +- `title`: UI ํ‘œ์‹œ ๋ฌธ๊ตฌ +- `checked` / `state`: ์ฒดํฌ๋ฐ•์Šค/ํ† ๊ธ€ ์ƒํƒœ + +--- + +### `inAppFeedback` +- `voiceUi`: `actionFeedback` ๊ฐ์ฒด + +--- + +### `voiceEngines` +- ์—”์ง„๋ณ„ ์–ธ์–ด ์ง€์› ์ •๋ณด: + ```json + { + "thinQtv": { "ko-KR": "ko-KR" }, + "googleAssistant": { "ko-KR": "notSupported" } + } + ``` + +--- + +### `voiceTask` +- `voiceTicket`: ์„ธ์…˜ ID +- `status`: `"init"`, `"running"`, `"finished"` +- `taskType`: `"stt"`, `"fullVoiceNlp"` ๋“ฑ +- `voiceEngine`: ์‚ฌ์šฉ ์ค‘์ธ ์—”์ง„ (์˜ˆ: `"googleAssistant"`) +- `recognitionSource`: ์ž…๋ ฅ ์†Œ์Šค ์ •๋ณด + +--- + +### `voiceRecognitionSource` +- `input`: `"voiceinput"` ๋˜๋Š” `"text"` +- `type`: `"remote"`, `"mrcu"`, `"amazonEcho"` ๋“ฑ +- `key`: ์†Œ์Šค ์„ธ๋ถ€ ํ‚ค (์˜ˆ: `"mrcu"`) + +--- + +> ๐Ÿ“Ž **๊ด€๋ จ ๋ฌธ์„œ**: [LG Collab ๋ฌธ์„œ](http://collab.lge.com/main/pages/viewpage.action?pageId=789627390) + +--- + +โœ… ์ด ๋ฌธ์„œ๋Š” **webOS TV ์Œ์„ฑ ํ”„๋ ˆ์ž„์›Œํฌ ๊ฐœ๋ฐœ์ž**๋ฅผ ์œ„ํ•œ ์ „์ฒด API ์ฐธ์กฐ์ž…๋‹ˆ๋‹ค. +๐Ÿ”’ `[Private]` ๋˜๋Š” `[Do Not Publish]`๋กœ ํ‘œ์‹œ๋œ API๋Š” ์™ธ๋ถ€ ๊ณต๊ฐœ์šฉ์ด ์•„๋‹™๋‹ˆ๋‹ค. \ No newline at end of file