diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowButton.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowButton.jsx
index ebef2735..1bd3c794 100644
--- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowButton.jsx
+++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowButton.jsx
@@ -9,17 +9,18 @@ import css from './ShopNowButton.module.less';
const SpottableDiv = Spottable('div');
export default function ShopNowButton({ onClick }) {
+ const handleSpotlightUp = (e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
+ };
return (
{
- e.stopPropagation();
- e.preventDefault();
- Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
- }}
+ onSpotlightUp={handleSpotlightUp}
>
SHOP NOW
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 34fe338c..6cdeae6d 100644
--- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx
+++ b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx
@@ -115,7 +115,6 @@ const VoiceInputOverlay = ({
isVisible,
onClose,
mode = VOICE_MODES.PROMPT,
- suggestions = [],
searchQuery = '',
onSearchChange,
}) => {
@@ -164,6 +163,8 @@ const VoiceInputOverlay = ({
const [errorMessage, setErrorMessage] = useState('');
// WebSpeech 이벤트 로그 저장 (디버그용)
const [webSpeechEventLogs, setWebSpeechEventLogs] = useState([]);
+ // Bubble 클릭으로 검색이 시작되었는지 추적하는 상태
+ const [isBubbleClickSearch, setIsBubbleClickSearch] = useState(false);
// 검색 기록 관리 (localStorage 기반, 최근 5개)
const [searchHistory, setSearchHistory] = useState(() => {
const history = readLocalStorage(SEARCH_HISTORY_KEY, DEFAULT_SUGGESTIONS);
@@ -241,28 +242,7 @@ const VoiceInputOverlay = ({
);
// Redux에서 STT 결과 및 에러 상태 가져오기
- const { lastSTTText, sttTimestamp, webSpeech } = useSelector((state) => state.voice);
-
- // 🎤 WebSpeech 이벤트 로그 관리 함수
- const addWebSpeechEventLog = useCallback((event, details = '') => {
- const timestamp = new Date().toLocaleTimeString();
- const newLog = {
- id: Date.now(),
- timestamp,
- event,
- details,
- type: getWebSpeechEventType(event),
- };
-
- if (DEBUG_MODE) {
- console.log(`[WebSpeech Event] ${event}: ${details}`);
- }
-
- setWebSpeechEventLogs((prev) => {
- const updated = [newLog, ...prev];
- return updated.slice(0, 10); // 최근 10개만 유지
- });
- }, []);
+ const { lastSTTText, sttTimestamp, webSpeech = {} } = useSelector((state) => state.voice);
// 이벤트 타입별 색상 분류
const getWebSpeechEventType = useCallback((event) => {
@@ -280,6 +260,30 @@ const VoiceInputOverlay = ({
return types[event] || 'info';
}, []);
+ // 🎤 WebSpeech 이벤트 로그 관리 함수
+ const addWebSpeechEventLog = useCallback(
+ (event, details = '') => {
+ const timestamp = new Date().toLocaleTimeString();
+ const newLog = {
+ id: Date.now(),
+ timestamp,
+ event,
+ details,
+ type: getWebSpeechEventType(event),
+ };
+
+ if (DEBUG_MODE) {
+ console.log(`[WebSpeech Event] ${event}: ${details}`);
+ }
+
+ setWebSpeechEventLogs((prev) => {
+ const updated = [newLog, ...prev];
+ return updated.slice(0, 10); // 최근 10개만 유지
+ });
+ },
+ [getWebSpeechEventType]
+ );
+
// Redux에서 shopperHouse 검색 결과 및 에러 가져오기 (simplified ref usage)
const shopperHouseData = useSelector((state) => state.search.shopperHouseData);
const shopperHouseSearchId = useSelector((state) => state.search.shopperHouseSearchId); // 2차 발화용 searchId
@@ -312,10 +316,18 @@ const VoiceInputOverlay = ({
// 🚨 WebSpeech 에러 감시 및 처리 (Phase 1)
useEffect(() => {
- if (webSpeech.error) {
+ // Bubble 클릭으로 검색이 시작된 경우 WebSpeech 오류를 우회
+ if (isBubbleClickSearch) {
+ if (DEBUG_MODE) {
+ console.log('[VoiceInput] 🔇 WebSpeech 오류 우회 (bubble click search)');
+ }
+ return;
+ }
+
+ if (webSpeech && webSpeech.error) {
console.error('[VoiceInput] 🔴 WebSpeech error detected:', webSpeech.error);
console.error('[VoiceInput] ├─ error type:', webSpeech.error);
- console.error('[VoiceInput] ├─ message:', webSpeech.message);
+ console.error('[VoiceInput] ├─ message:', webSpeech.message || 'No message');
console.error('[VoiceInput] ├─ current mode:', currentMode);
console.error('[VoiceInput] └─ is listening:', isListening);
@@ -373,12 +385,14 @@ const VoiceInputOverlay = ({
}
}, [
webSpeech.error,
+ webSpeech.message,
currentMode,
isListening,
isVisible,
stopListening,
restartWebSpeech,
addWebSpeechEventLog,
+ isBubbleClickSearch,
]);
// 🎤 WebSpeech 이벤트 감지 (전용 디버그용)
@@ -739,7 +753,7 @@ const VoiceInputOverlay = ({
dispatch(clearShopperHouseData());
dispatch(clearSTTText());
};
- }, []); // 빈 dependency - 언마운트 시에만 실행
+ }, [dispatch]); // 언마운트 시에만 실행
// Overlay가 열릴 때 포커스를 overlay 내부로 이동
useEffect(() => {
@@ -841,6 +855,7 @@ const VoiceInputOverlay = ({
setCurrentMode(VOICE_MODES.PROMPT);
setVoiceInputMode(null);
setSttResponseText('');
+ setIsBubbleClickSearch(false); // bubble 클릭 상태 초기화
if (DEBUG_MODE) {
console.log('[VoiceInput] ✅ API error restart complete - ready for new input');
@@ -916,6 +931,7 @@ const VoiceInputOverlay = ({
// PROMPT 모드로 복귀
setCurrentMode(VOICE_MODES.PROMPT);
setVoiceInputMode(null);
+ setIsBubbleClickSearch(false); // bubble 클릭 상태 초기화
// 약간의 지연 후 새로 시작 (안정성을 위해)
setTimeout(() => {
@@ -925,240 +941,6 @@ const VoiceInputOverlay = ({
}, 300);
}, [dispatch, onSearchChange, addWebSpeechEventLog]);
- // Suggestion 버튼 클릭 핸들러 - Input 창에 텍스트 설정 + API 자동 호출 + response 모드 전환
- const handleSuggestionClick = useCallback(
- (suggestion) => {
- if (DEBUG_MODE) {
- console.log('💡 [DEBUG] handleSuggestionClick called with:', suggestion);
- }
-
- // 따옴표 제거
- const query = suggestion.replace(/^["']|["']$/g, '').trim();
-
- // Input 창에 텍스트 설정 (유지)
- if (onSearchChange) {
- onSearchChange({ value: query });
- }
-
- // ✨ 검색 기록에 추가
- addToSearchHistory(query);
-
- // ✨ RESPONSE 모드로 전환을 위한 텍스트 설정
- setSttResponseText(query);
-
- // ✨ RESPONSE 모드로 직접 전환
- setCurrentMode(VOICE_MODES.RESPONSE);
- setVoiceInputMode(null);
-
- // ✨ ShopperHouse API 자동 호출 (searchId 포함)
- if (query && query.length >= 3) {
- // ✅ Ref에서 최신 searchId 읽기 (이전 검색이 있는 경우 2차 발화 처리)
- const currentSearchId = shopperHouseSearchIdRef.current;
- if (DEBUG_MODE) {
- console.log('🔍 [DEBUG] Calling ShopperHouse API from bubble click');
- console.log('[VoiceInput] ├─ query:', query);
- console.log('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)');
- }
- try {
- dispatch(getShopperHouseSearch(query, currentSearchId));
- } catch (error) {
- console.error('[VoiceInput] ❌ API 호출 실패:', error);
- // 에러 발생 시 PROMPT 모드로 복귀
- setCurrentMode(VOICE_MODES.PROMPT);
- setVoiceInputMode(null);
- }
- }
- },
- [onSearchChange, dispatch, addToSearchHistory]
- );
-
- // Input 창에서 API 호출 핸들러 (돋보기 아이콘 클릭 시에만)
- const handleSearchSubmit = useCallback(() => {
- if (DEBUG_MODE) {
- console.log('[VoiceInputOverlay.v2] Search submit:', searchQuery);
- }
- if (searchQuery && searchQuery.trim()) {
- // ShopperHouse API 호출
- dispatch(getShopperHouseSearch(searchQuery.trim()));
-
- // Input 내용 비우기
- if (onSearchChange) {
- onSearchChange({ value: '' });
- }
-
- // Clear existing timer before setting new one
- clearTimerRef(searchSubmitFocusTimerRef);
-
- // API 호출 후 Input 박스로 포커스 이동
- searchSubmitFocusTimerRef.current = setTimeout(() => {
- Spotlight.focus(INPUT_SPOTLIGHT_ID);
- }, 100);
-
- // VoiceInputOverlay는 SearchPanel과 다른 API를 사용하므로 onSearchSubmit 호출 안 함
- // if (onSearchSubmit) {
- // onSearchSubmit(searchQuery);
- // }
- }
- }, [dispatch, searchQuery, onSearchChange]);
-
- // Input 창에서 엔터키 핸들러 (API 호출하지 않음)
- const handleInputKeyDown = useCallback((e) => {
- if (e.key === 'Enter' || e.keyCode === 13) {
- e.preventDefault();
- // Enter 키로는 API 호출하지 않음
- // 돋보기 아이콘 클릭/Enter로만 API 호출
- }
- }, []);
-
- // Input 모드 변경 핸들러 - Input 모드로 전환되면 VoiceInputOverlay 닫기
- const handleInputModeChange = useCallback(
- (mode) => {
- if (DEBUG_MODE) {
- console.log('[VoiceInputOverlay] TInput 모드 변경:', mode);
- }
-
- if (mode === 'input') {
- // Input 모드로 전환되면 Overlay 닫기
- // SearchPanel의 TInput으로 자연스럽게 전환됨
- if (DEBUG_MODE) {
- console.log('⌨️ [DEBUG] Input 모드로 전환됨 - VoiceInputOverlay 닫고 SearchPanel로 이동');
- }
- handleClose();
- }
- },
- [handleClose]
- );
-
- // ⛔ TALK AGAIN 버튼 제거됨 - 더 이상 사용하지 않음
- // const handleTalkAgain = useCallback(() => { ... }, []);
-
- // 모드에 따른 컨텐츠 렌더링 - Memoized
- const renderModeContent = useMemo(() => {
- if (DEBUG_MODE) {
- console.log(
- '🎬 [DEBUG][VoiceInputOverlay] renderModeContent called',
- '| currentMode:',
- currentMode,
- '| voiceInputMode:',
- voiceInputMode,
- '| isListening:',
- isListening
- );
- }
-
- switch (currentMode) {
- case VOICE_MODES.PROMPT:
- if (DEBUG_MODE) {
- console.log(
- '✅ [DEBUG][VoiceInputOverlay] MODE = PROMPT | Rendering VoicePromptScreen with',
- searchHistory.length,
- 'suggestions'
- );
- }
- return (
-
- );
- case VOICE_MODES.LISTENING:
- if (DEBUG_MODE) {
- console.log(
- '🎤 [DEBUG][VoiceInputOverlay] MODE = LISTENING | Rendering VoiceListening (15초 타이머)'
- );
- }
- return
;
- case VOICE_MODES.RESPONSE:
- if (DEBUG_MODE) {
- console.log(
- '💬 [DEBUG][VoiceInputOverlay] MODE = RESPONSE | Rendering VoiceResponse with text:',
- sttResponseText,
- 'isLoading: true (항상 로딩 애니메이션 표시)'
- );
- }
- // ✅ RESPONSE 모드에서는 항상 로딩 애니메이션 표시
- // shopperHouseData는 overlay 닫기에만 사용 (searchId 유지 필요)
- return (
-
- );
- case VOICE_MODES.NOINIT:
- if (DEBUG_MODE) {
- console.log('⚠️ [DEBUG][VoiceInputOverlay] MODE = NOINIT | Rendering VoiceNotRecognized');
- }
- return
;
- case VOICE_MODES.NOTRECOGNIZED:
- if (DEBUG_MODE) {
- console.log(
- '❌ [DEBUG][VoiceInputOverlay] MODE = NOTRECOGNIZED | Rendering VoiceNotRecognized with error message'
- );
- }
- return (
-
- );
- case VOICE_MODES.APIERROR:
- if (DEBUG_MODE) {
- console.log(
- '💥 [DEBUG][VoiceInputOverlay] MODE = APIERROR | Rendering VoiceApiError with error details',
- shopperHouseError
- );
- }
- return (
-
- );
- case VOICE_MODES.MODE_3:
- // 추후 MODE_3 컴포넌트 추가
- return
;
- case VOICE_MODES.MODE_4:
- // 추후 MODE_4 컴포넌트 추가
- return
;
- default:
- if (DEBUG_MODE) {
- console.log('🔄 [DEBUG][VoiceInputOverlay] MODE = DEFAULT | Rendering VoicePromptScreen');
- }
- return (
-
- );
- }
- }, [
- currentMode,
- voiceInputMode,
- isListening,
- searchHistory,
- handleSuggestionClick,
- interimText,
- sttResponseText,
- countdown,
- errorMessage,
- restartWebSpeech,
- shopperHouseError,
- handleApiErrorRetry,
- handleApiErrorRestart,
- ]);
-
- // 마이크 버튼 포커스 핸들러 (VUI)
- const handleMicFocus = useCallback(() => {
- setMicFocused(true);
- }, []);
-
- const handleMicBlur = useCallback(() => {
- setMicFocused(false);
- }, []);
-
// 🎤 음성 입력 최종 처리 함수 (15초 타이머 & 3초 silence detection 공통 사용)
const processFinalVoiceInput = useCallback(
(source) => {
@@ -1232,9 +1014,254 @@ const VoiceInputOverlay = ({
setCurrentMode(VOICE_MODES.PROMPT);
setSttResponseText('');
setErrorMessage('');
+ setIsBubbleClickSearch(false); // bubble 클릭 상태 초기화
onClose();
}, [onClose]);
+ // Suggestion 버튼 클릭 핸들러 - Input 창에 텍스트 설정 + API 자동 호출 + response 모드 전환
+ const handleSuggestionClick = useCallback(
+ (suggestion) => {
+ if (DEBUG_MODE) {
+ console.log('💡 [DEBUG] handleSuggestionClick called with:', suggestion);
+ }
+
+ // Bubble 클릭으로 검색 시작 상태 설정
+ setIsBubbleClickSearch(true);
+
+ // 따옴표 제거
+ const query = suggestion.replace(/^["']|["']$/g, '').trim();
+
+ // Input 창에 텍스트 설정 (유지)
+ if (onSearchChange) {
+ onSearchChange({ value: query });
+ }
+
+ // ✨ 검색 기록에 추가
+ addToSearchHistory(query);
+
+ // ✨ RESPONSE 모드로 전환을 위한 텍스트 설정
+ setSttResponseText(query);
+
+ // ✨ RESPONSE 모드로 직접 전환
+ setCurrentMode(VOICE_MODES.RESPONSE);
+ setVoiceInputMode(null);
+
+ // ✨ ShopperHouse API 자동 호출 (searchId 포함)
+ if (query && query.length >= 3) {
+ // ✅ Ref에서 최신 searchId 읽기 (이전 검색이 있는 경우 2차 발화 처리)
+ const currentSearchId = shopperHouseSearchIdRef.current;
+ if (DEBUG_MODE) {
+ console.log('🔍 [DEBUG] Calling ShopperHouse API from bubble click');
+ console.log('[VoiceInput] ├─ query:', query);
+ console.log('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)');
+ }
+ try {
+ dispatch(getShopperHouseSearch(query, currentSearchId));
+ } catch (error) {
+ console.error('[VoiceInput] ❌ API 호출 실패:', error);
+ // 에러 발생 시 PROMPT 모드로 복귀
+ setCurrentMode(VOICE_MODES.PROMPT);
+ setVoiceInputMode(null);
+ setIsBubbleClickSearch(false); // 상태 초기화
+ }
+ }
+ },
+ [onSearchChange, dispatch, addToSearchHistory]
+ );
+
+ // Input 창에서 API 호출 핸들러 (돋보기 아이콘 클릭 시에만)
+ const handleSearchSubmit = useCallback(() => {
+ if (DEBUG_MODE) {
+ console.log('[VoiceInputOverlay.v2] Search submit:', searchQuery);
+ }
+ if (searchQuery && searchQuery.trim()) {
+ // ShopperHouse API 호출
+ dispatch(getShopperHouseSearch(searchQuery.trim()));
+
+ // Input 내용 비우기
+ if (onSearchChange) {
+ onSearchChange({ value: '' });
+ }
+
+ // Clear existing timer before setting new one
+ clearTimerRef(searchSubmitFocusTimerRef);
+
+ // API 호출 후 Input 박스로 포커스 이동
+ searchSubmitFocusTimerRef.current = setTimeout(() => {
+ Spotlight.focus(INPUT_SPOTLIGHT_ID);
+ }, 100);
+
+ // VoiceInputOverlay는 SearchPanel과 다른 API를 사용하므로 onSearchSubmit 호출 안 함
+ // if (onSearchSubmit) {
+ // onSearchSubmit(searchQuery);
+ // }
+ }
+ }, [dispatch, searchQuery, onSearchChange]);
+
+ // Input 창에서 엔터키 핸들러 (API 호출하지 않음)
+ const handleInputKeyDown = useCallback((e) => {
+ if (e.key === 'Enter' || e.keyCode === 13) {
+ e.preventDefault();
+ // Enter 키로는 API 호출하지 않음
+ // 돋보기 아이콘 클릭/Enter로만 API 호출
+ }
+ }, []);
+
+ // Input 모드 변경 핸들러 - Input 모드로 전환되면 VoiceInputOverlay 닫기
+ const handleInputModeChange = useCallback(
+ (inputMode) => {
+ if (DEBUG_MODE) {
+ console.log('[VoiceInputOverlay] TInput 모드 변경:', inputMode);
+ }
+
+ if (inputMode === 'input') {
+ // Input 모드로 전환되면 Overlay 닫기
+ // SearchPanel의 TInput으로 자연스럽게 전환됨
+ if (DEBUG_MODE) {
+ console.log('⌨️ [DEBUG] Input 모드로 전환됨 - VoiceInputOverlay 닫고 SearchPanel로 이동');
+ }
+ handleClose();
+ }
+ },
+ [handleClose]
+ );
+
+ // ⛔ TALK AGAIN 버튼 제거됨 - 더 이상 사용하지 않음
+ // const handleTalkAgain = useCallback(() => { ... }, []);
+
+ // 모드에 따른 컨텐츠 렌더링 - Memoized
+ const renderModeContent = useMemo(() => {
+ if (DEBUG_MODE) {
+ console.log(
+ '🎬 [DEBUG][VoiceInputOverlay] renderModeContent called',
+ '| currentMode:',
+ currentMode,
+ '| voiceInputMode:',
+ voiceInputMode,
+ '| isListening:',
+ isListening
+ );
+ }
+
+ switch (currentMode) {
+ case VOICE_MODES.PROMPT:
+ if (DEBUG_MODE) {
+ console.log(
+ '✅ [DEBUG][VoiceInputOverlay] MODE = PROMPT | Rendering VoicePromptScreen with',
+ searchHistory.length,
+ 'suggestions'
+ );
+ }
+ return (
+
+ );
+ case VOICE_MODES.LISTENING:
+ if (DEBUG_MODE) {
+ console.log(
+ '🎤 [DEBUG][VoiceInputOverlay] MODE = LISTENING | Rendering VoiceListening (15초 타이머)'
+ );
+ }
+ return
;
+ case VOICE_MODES.RESPONSE:
+ if (DEBUG_MODE) {
+ console.log(
+ '💬 [DEBUG][VoiceInputOverlay] MODE = RESPONSE | Rendering VoiceResponse with text:',
+ sttResponseText,
+ 'isLoading: true (항상 로딩 애니메이션 표시)'
+ );
+ }
+ // ✅ RESPONSE 모드에서는 항상 로딩 애니메이션 표시
+ // shopperHouseData는 overlay 닫기에만 사용 (searchId 유지 필요)
+ return (
+
+ );
+ case VOICE_MODES.NOINIT:
+ if (DEBUG_MODE) {
+ console.log('⚠️ [DEBUG][VoiceInputOverlay] MODE = NOINIT | Rendering VoiceNotRecognized');
+ }
+ return
;
+ case VOICE_MODES.NOTRECOGNIZED:
+ if (DEBUG_MODE) {
+ console.log(
+ '❌ [DEBUG][VoiceInputOverlay] MODE = NOTRECOGNIZED | Rendering VoiceNotRecognized with error message'
+ );
+ }
+ return (
+
+ );
+ case VOICE_MODES.APIERROR:
+ if (DEBUG_MODE) {
+ console.log(
+ '💥 [DEBUG][VoiceInputOverlay] MODE = APIERROR | Rendering VoiceApiError with error details',
+ shopperHouseError
+ );
+ }
+ return (
+
+ );
+ case VOICE_MODES.MODE_3:
+ // 추후 MODE_3 컴포넌트 추가
+ return
;
+ case VOICE_MODES.MODE_4:
+ // 추후 MODE_4 컴포넌트 추가
+ return
;
+ default:
+ if (DEBUG_MODE) {
+ console.log('🔄 [DEBUG][VoiceInputOverlay] MODE = DEFAULT | Rendering VoicePromptScreen');
+ }
+ return (
+
+ );
+ }
+ }, [
+ currentMode,
+ voiceInputMode,
+ isListening,
+ searchHistory,
+ handleSuggestionClick,
+ interimText,
+ sttResponseText,
+ countdown,
+ errorMessage,
+ restartWebSpeech,
+ shopperHouseError,
+ shopperHouseSearchId,
+ handleApiErrorRetry,
+ handleApiErrorRestart,
+ ]);
+
+ // 마이크 버튼 포커스 핸들러 (VUI)
+ const handleMicFocus = useCallback(() => {
+ setMicFocused(true);
+ }, []);
+
+ const handleMicBlur = useCallback(() => {
+ setMicFocused(false);
+ }, []);
+
+ // Input wrapper click handler to prevent event propagation
+ const handleInputWrapperClick = useCallback((e) => {
+ e.stopPropagation();
+ }, []);
+
// WebSpeech 마이크 버튼 클릭 핸들러
const handleWebSpeechMicClick = useCallback(
(e) => {
@@ -1290,15 +1317,7 @@ const VoiceInputOverlay = ({
handleClose();
}
},
- [
- currentMode,
- handleClose,
- startListening,
- stopListening,
- onSearchChange,
- dispatch,
- processFinalVoiceInput,
- ]
+ [currentMode, handleClose, startListening, onSearchChange, dispatch, processFinalVoiceInput]
);
// 마이크 버튼 키다운 핸들러
@@ -1431,13 +1450,13 @@ const VoiceInputOverlay = ({
Interim:{' '}
- "{interimText || 'N/A'}"
+ "{interimText || 'N/A'}"
Ref Text:{' '}
- "{interimTextRef.current || 'N/A'}"
+ "{interimTextRef.current || 'N/A'}"
@@ -1449,7 +1468,7 @@ const VoiceInputOverlay = ({
STT Result:{' '}
- "{lastSTTText || 'None'}"
+ "{lastSTTText || 'None'}"
@@ -1515,7 +1534,7 @@ const VoiceInputOverlay = ({
spotlightDisabled={!isVisible}
>
{/* 입력창과 마이크 버튼 - SearchPanel.inputContainer와 동일한 구조 */}
-
e.stopPropagation()}>
+
@@ -1596,7 +1615,6 @@ VoiceInputOverlay.propTypes = {
isVisible: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
mode: PropTypes.oneOf(Object.values(VOICE_MODES)),
- suggestions: PropTypes.arrayOf(PropTypes.string),
searchQuery: PropTypes.string,
onSearchChange: PropTypes.func,
onSearchSubmit: PropTypes.func,
@@ -1604,7 +1622,6 @@ VoiceInputOverlay.propTypes = {
VoiceInputOverlay.defaultProps = {
mode: VOICE_MODES.PROMPT,
- suggestions: [],
searchQuery: '',
onSearchChange: null,
onSearchSubmit: null,
diff --git a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceApiError.jsx b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceApiError.jsx
index d2b62705..8791810f 100644
--- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceApiError.jsx
+++ b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceApiError.jsx
@@ -9,53 +9,53 @@ const SpottableButton = Spottable('div');
const VoiceApiError = ({ error = null, onRestart = null, onRetry = null }) => {
// 에러 객체에서 메시지 추출
- const getErrorMessage = (error) => {
- if (!error) return 'An unknown error occurred.';
+ const getErrorMessage = (err) => {
+ if (!err) return 'An unknown error occurred.';
- if (typeof error === 'string') {
- return error;
+ if (typeof err === 'string') {
+ return err;
}
// API 에러 객체 구조에 따라 메시지 추출
- if (error.message) {
- return error.message;
+ if (err.message) {
+ return err.message;
}
- if (error.data && error.data.message) {
- return error.data.message;
+ if (err.data && err.data.message) {
+ return err.data.message;
}
- if (error.response && error.response.data && error.response.data.message) {
- return error.response.data.message;
+ if (err.response && err.response.data && err.response.data.message) {
+ return err.response.data.message;
}
- if (error.status) {
- return `API Error: ${error.status} - ${error.statusText || 'Unknown error'}`;
+ if (err.status) {
+ return `API Error: ${err.status} - ${err.statusText || 'Unknown error'}`;
}
- return JSON.stringify(error);
+ return JSON.stringify(err);
};
// 에러 상세 정보 추출
- const getErrorDetails = (error) => {
- if (!error) return null;
+ const getErrorDetails = (err) => {
+ if (!err) return null;
const details = [];
- if (error.status) {
- details.push(`Status: ${error.status}`);
+ if (err.status) {
+ details.push(`Status: ${err.status}`);
}
- if (error.code) {
- details.push(`Code: ${error.code}`);
+ if (err.code) {
+ details.push(`Code: ${err.code}`);
}
- if (error.timestamp) {
- details.push(`Time: ${new Date(error.timestamp).toLocaleString()}`);
+ if (err.timestamp) {
+ details.push(`Time: ${new Date(err.timestamp).toLocaleString()}`);
}
- if (error.endpoint) {
- details.push(`Endpoint: ${error.endpoint}`);
+ if (err.endpoint) {
+ details.push(`Endpoint: ${err.endpoint}`);
}
return details.length > 0 ? details : null;
diff --git a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoicePromptScreen.jsx b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoicePromptScreen.jsx
index dc76ef5b..36b0eb22 100644
--- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoicePromptScreen.jsx
+++ b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoicePromptScreen.jsx
@@ -34,12 +34,11 @@ const VoicePromptScreen = ({ title = 'Try saying', suggestions = [], onSuggestio
{title}
{suggestions.map((suggestion, index) => {
- const handleClick = () => handleBubbleClick(suggestion);
return (
handleBubbleClick(suggestion)}
spotlightId={`voice-bubble-${index}`}
>
{suggestion}
diff --git a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceResponse.jsx b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceResponse.jsx
index 4cf749c9..6ba7af22 100644
--- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceResponse.jsx
+++ b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceResponse.jsx
@@ -5,7 +5,7 @@ import Spottable from '@enact/spotlight/Spottable';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import css from './VoiceResponse.module.less';
-const SpottableBubble = Spottable('div');
+// const SpottableBubble = Spottable('div');
const ResponseContainer = SpotlightContainerDecorator(
{
@@ -46,7 +46,7 @@ const VoiceResponse = ({ responseText = '', isLoading = true, query = '', search
return result;
};
- const displayText = capitalizeSentences(responseText);
+ // const displayText = capitalizeSentences(responseText);
// 타이핑 애니메이션 효과 (반복)
useEffect(() => {