[251124] fix: Log정리-4
🕐 커밋 시간: 2025. 11. 24. 12:19:40 📊 변경 통계: • 총 파일: 6개 • 추가: +283줄 • 삭제: -255줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/mainActions.js ~ com.twin.app.shoptime/src/reducers/mainReducer.js ~ com.twin.app.shoptime/src/reducers/searchReducer.js ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/actions/mainActions.js (javascript): 🔄 Modified: clearSubCategory() 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx (javascript): 🔄 Modified: Spottable() 📄 com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx (javascript): ✅ Added: Spottable() 🔄 Modified: clearAllTimers() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선
This commit is contained in:
@@ -5,13 +5,18 @@ import { CATEGORY_DATA_MAX_RESULTS_LIMIT, LOG_CONTEXT_NAME, LOG_MESSAGE_ID } fro
|
||||
import * as HelperMethods from '../utils/helperMethods';
|
||||
import { types } from './actionTypes';
|
||||
import { addReservation, changeAppStatus, deleteReservation } from './commonActions';
|
||||
import { createDebugHelpers } from '../utils/debug';
|
||||
|
||||
// 디버그 헬퍼 설정
|
||||
const DEBUG_MODE = false;
|
||||
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
||||
|
||||
//IF-LGSP-007
|
||||
export const getMainLiveShow = (props) => (dispatch, getState) => {
|
||||
const vodIncFlag = props?.vodIncFlag;
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log('@@ getMainLiveShow onSuccess', response.data);
|
||||
dlog('@@ getMainLiveShow onSuccess', response.data);
|
||||
|
||||
dispatch({
|
||||
type: types.GET_MAIN_LIVE_SHOW,
|
||||
@@ -20,7 +25,7 @@ export const getMainLiveShow = (props) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error('@@ getMainLiveShow onFail', error);
|
||||
derror('@@ getMainLiveShow onFail', error);
|
||||
};
|
||||
|
||||
TAxios(dispatch, getState, 'get', URLS.GET_MAIN_LIVE_SHOW, { vodIncFlag }, {}, onSuccess, onFail);
|
||||
@@ -31,7 +36,7 @@ export const setMainLiveUpcomingAlarm = (props) => (dispatch, getState) => {
|
||||
const { alamDispFlag, chanId, endDt, patnrId, patncNm, showId, showNm, strtDt } = props;
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log('setMainLiveUpcomingAlarm onSuccess', response.data);
|
||||
dlog('setMainLiveUpcomingAlarm onSuccess', response.data);
|
||||
|
||||
if (alamDispFlag === 'Y') {
|
||||
const convertedStrtDt = convertUtcToLocal(strtDt);
|
||||
@@ -72,7 +77,7 @@ export const setMainLiveUpcomingAlarm = (props) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error('setMainLiveUpcomingAlarm onFail', error);
|
||||
derror('setMainLiveUpcomingAlarm onFail', error);
|
||||
};
|
||||
|
||||
TAxios(
|
||||
@@ -94,7 +99,7 @@ export const getMainCategoryDetail = (props) => (dispatch, getState) => {
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log('getMainCategoryDetail onSuccess ', response.data);
|
||||
dlog('getMainCategoryDetail onSuccess ', response.data);
|
||||
|
||||
dispatch({
|
||||
type: types.GET_PRODUCT_DETAIL,
|
||||
@@ -105,7 +110,7 @@ export const getMainCategoryDetail = (props) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error('getMainCategoryDetail onFail', error);
|
||||
derror('getMainCategoryDetail onFail', error);
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
};
|
||||
|
||||
@@ -125,7 +130,7 @@ export const getMainCategoryDetail = (props) => (dispatch, getState) => {
|
||||
export const getMainCategoryShowDetail = (props) => (dispatch, getState) => {
|
||||
const { patnrId, showId, curationId } = props;
|
||||
const onSuccess = (response) => {
|
||||
console.log('getMainCategoryShowDetail onSuccess ', response.data);
|
||||
dlog('getMainCategoryShowDetail onSuccess ', response.data);
|
||||
|
||||
dispatch({
|
||||
type: types.GET_MAIN_CATEGORY_SHOW_DETAIL,
|
||||
@@ -134,7 +139,7 @@ export const getMainCategoryShowDetail = (props) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error('getMainCategoryShowDetail onFail', error);
|
||||
derror('getMainCategoryShowDetail onFail', error);
|
||||
};
|
||||
|
||||
TAxios(
|
||||
@@ -165,7 +170,7 @@ export const getSubCategory =
|
||||
lastSubCategoryParams &&
|
||||
JSON.stringify(lastSubCategoryParams) === JSON.stringify(params)
|
||||
) {
|
||||
console.log('getSubCategory ignore patch');
|
||||
dlog('getSubCategory ignore patch');
|
||||
return;
|
||||
}
|
||||
lastSubCategoryParams = { ...params };
|
||||
@@ -176,7 +181,7 @@ export const getSubCategory =
|
||||
|
||||
let currentKey = key;
|
||||
const onSuccess = (response) => {
|
||||
console.log('getSubCategory onSuccess ', response.data);
|
||||
dlog('getSubCategory onSuccess ', response.data);
|
||||
|
||||
if (pageNo === 1) {
|
||||
getSubCategoryKey = new Date();
|
||||
@@ -220,7 +225,7 @@ export const getSubCategory =
|
||||
const canRetry = nextRetryCount < SUB_CATEGORY_RETRY_LIMIT;
|
||||
|
||||
if (canRetry) {
|
||||
console.warn('getSubCategory retry', {
|
||||
dwarn('getSubCategory retry', {
|
||||
lgCatCd,
|
||||
pageNo,
|
||||
retryCount: nextRetryCount,
|
||||
@@ -232,7 +237,7 @@ export const getSubCategory =
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('getSubCategory onFail', error);
|
||||
derror('getSubCategory onFail', error);
|
||||
if (pageNo === 1) {
|
||||
lastSubCategoryParams = {};
|
||||
}
|
||||
@@ -253,7 +258,7 @@ export const getSubCategory =
|
||||
export const continueGetSubCategory = (key, pageNo) => (dispatch, getState) => {
|
||||
if (!lastSubCategoryParams) {
|
||||
// <<<<<<< HEAD
|
||||
console.warn('No previous category parameters found');
|
||||
dwarn('No previous category parameters found');
|
||||
// =======
|
||||
// console.warn("No previous category parameters found");
|
||||
// >>>>>>> gitlab/develop
|
||||
@@ -295,7 +300,7 @@ const clearSubCategory = () => ({
|
||||
// TOP20 영상 목록 조회 IF-LGSP-069
|
||||
export const getTop20Show = () => (dispatch, getState) => {
|
||||
const onSuccess = (response) => {
|
||||
console.log('getTop20Show onSuccess ', response.data);
|
||||
dlog('getTop20Show onSuccess ', response.data);
|
||||
|
||||
dispatch({
|
||||
type: types.GET_TOP_20_SHOW,
|
||||
@@ -305,7 +310,7 @@ export const getTop20Show = () => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error('getTop20Show onFail', error);
|
||||
derror('getTop20Show onFail', error);
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
};
|
||||
|
||||
@@ -375,14 +380,14 @@ export const getMyFavoriteFlag = (params) => (dispatch, getState) => {
|
||||
const { patnrId, prdtId } = params;
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log('getMyFavoriteFlag onSuccess ', response.data);
|
||||
dlog('getMyFavoriteFlag onSuccess ', response.data);
|
||||
dispatch({
|
||||
type: types.GET_MY_FAVORITE_FLAG,
|
||||
payload: response.data.data,
|
||||
});
|
||||
};
|
||||
const onFail = (error) => {
|
||||
console.error('getMyFavoriteFlag onFail', error);
|
||||
derror('getMyFavoriteFlag onFail', error);
|
||||
};
|
||||
|
||||
TAxios(
|
||||
@@ -401,7 +406,7 @@ export const setMainLikeCategory = (params) => (dispatch, getState) => {
|
||||
const { patnrId, prdtId } = params;
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log('setMainLikeCategory onSuccess ', response.data);
|
||||
dlog('setMainLikeCategory onSuccess ', response.data);
|
||||
dispatch({
|
||||
type: types.SET_MAIN_LIKE_CATEGORY,
|
||||
payload: response.data.data,
|
||||
@@ -428,10 +433,10 @@ export const getHomeFullVideoInfo =
|
||||
({ lgCatCd }) =>
|
||||
(dispatch, getState) => {
|
||||
const onSuccess = (response) => {
|
||||
console.log('getHomeFullVideoInfo onSuccess', response.data.data.showInfos);
|
||||
dlog('getHomeFullVideoInfo onSuccess', response.data.data.showInfos);
|
||||
|
||||
// ✨ DEBUG: youmaylikeInfos 데이터 확인
|
||||
console.log('[DEBUG] GET_HOME_FULL_VIDEO_INFO - API Response:', {
|
||||
dlog('[DEBUG] GET_HOME_FULL_VIDEO_INFO - API Response:', {
|
||||
youmaylikeInfos: response.data.data.youmaylikeInfos,
|
||||
youmaylikeInfos_length: response.data.data.youmaylikeInfos?.length,
|
||||
liveChannelInfos_length: response.data.data.liveChannelInfos?.length,
|
||||
@@ -445,7 +450,7 @@ export const getHomeFullVideoInfo =
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error('getHomeFullVideoInfo onSuccess', error);
|
||||
derror('getHomeFullVideoInfo onSuccess', error);
|
||||
};
|
||||
|
||||
TAxios(
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { types } from '../actions/actionTypes';
|
||||
import { CATEGORY_DATA_MAX_RESULTS_LIMIT } from '../utils/Config';
|
||||
import { createDebugHelpers } from '../utils/debug';
|
||||
|
||||
// 디버그 헬퍼 설정
|
||||
const DEBUG_MODE = false;
|
||||
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
||||
|
||||
const initialState = {
|
||||
subCategoryData: {},
|
||||
@@ -159,7 +164,7 @@ export const mainReducer = (state = initialState, action) => {
|
||||
const { data, lgCatCd } = action.payload;
|
||||
|
||||
// ✨ DEBUG: Reducer에서 youmaylikeInfos 저장 확인
|
||||
console.log('[DEBUG] Reducer - GET_HOME_FULL_VIDEO_INFO:', {
|
||||
dlog('[DEBUG] Reducer - GET_HOME_FULL_VIDEO_INFO:', {
|
||||
youmaylikeInfos_length: data.youmaylikeInfos?.length,
|
||||
youmaylikeInfos: data.youmaylikeInfos,
|
||||
});
|
||||
@@ -197,7 +202,7 @@ export const mainReducer = (state = initialState, action) => {
|
||||
};
|
||||
}
|
||||
case types.CLEAR_SHOPNOW_INFO:
|
||||
console.log('[DEBUG] Reducer - CLEAR_SHOPNOW_INFO called - youmaylikeInfos will be null');
|
||||
dlog('[DEBUG] Reducer - CLEAR_SHOPNOW_INFO called - youmaylikeInfos will be null');
|
||||
return {
|
||||
...state,
|
||||
fullVideolgCatCd: '',
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { types } from '../actions/actionTypes';
|
||||
import { createDebugHelpers } from '../utils/debug';
|
||||
|
||||
// 디버그 헬퍼 설정
|
||||
const DEBUG_MODE = false;
|
||||
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
||||
|
||||
const initialState = {
|
||||
searchDatas: {},
|
||||
@@ -81,8 +86,8 @@ export const searchReducer = (state = initialState, action) => {
|
||||
};
|
||||
|
||||
case types.RESET_VOICE_SEARCH:
|
||||
console.log('[VoiceInput]-searchReducer-RESET_VOICE_SEARCH');
|
||||
console.log(
|
||||
dlog('[VoiceInput]-searchReducer-RESET_VOICE_SEARCH');
|
||||
dlog(
|
||||
JSON.stringify(
|
||||
{
|
||||
action: 'RESET_VOICE_SEARCH',
|
||||
@@ -120,7 +125,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
const newPreKey = action.payload?.results?.[0]?.searchId || 'null';
|
||||
const oldPreKey = state.preShopperHouseData?.results?.[0]?.searchId || 'null';
|
||||
|
||||
console.log('[ShopperHouse]-DIFF (after backup) preShopperHouseKey:', oldPreKey, '→', newPreKey);
|
||||
dlog('[ShopperHouse]-DIFF (after backup) preShopperHouseKey:', oldPreKey, '→', newPreKey);
|
||||
|
||||
return {
|
||||
...state,
|
||||
@@ -150,7 +155,16 @@ export const searchReducer = (state = initialState, action) => {
|
||||
const preSortingType = state.preShopperHouseData?.results?.[0]?.sortingType || 'null';
|
||||
|
||||
// [VoiceInput] Redux에 저장 로그
|
||||
console.log('[ShopperHouse]-DIFF (after API) shopperHouseKey:', newKey, '| preShopperHouseKey:', preKey, '| sortingType:', sortingType || 'null', '| preSortingType:', preSortingType);
|
||||
dlog(
|
||||
'[ShopperHouse]-DIFF (after API) shopperHouseKey:',
|
||||
newKey,
|
||||
'| preShopperHouseKey:',
|
||||
preKey,
|
||||
'| sortingType:',
|
||||
sortingType || 'null',
|
||||
'| preSortingType:',
|
||||
preSortingType
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
@@ -167,7 +181,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
}
|
||||
|
||||
case types.SET_SHOPPERHOUSE_ERROR:
|
||||
console.log('[VoiceInput] ❌ Redux shopperHouseError 저장:', action.payload);
|
||||
dlog('[VoiceInput] ❌ Redux shopperHouseError 저장:', action.payload);
|
||||
return {
|
||||
...state,
|
||||
shopperHouseError: action.payload,
|
||||
@@ -177,7 +191,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
};
|
||||
|
||||
case types.SHOW_SHOPPERHOUSE_ERROR:
|
||||
console.log('[ShopperHouse] 🔴 Redux shopperHouseErrorPopup 표시:', action.payload);
|
||||
dlog('[ShopperHouse] 🔴 Redux shopperHouseErrorPopup 표시:', action.payload);
|
||||
return {
|
||||
...state,
|
||||
shopperHouseErrorPopup: {
|
||||
@@ -190,7 +204,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
};
|
||||
|
||||
case types.HIDE_SHOPPERHOUSE_ERROR:
|
||||
console.log('[ShopperHouse] ✅ Redux shopperHouseErrorPopup 숨김');
|
||||
dlog('[ShopperHouse] ✅ Redux shopperHouseErrorPopup 숨김');
|
||||
return {
|
||||
...state,
|
||||
shopperHouseErrorPopup: {
|
||||
@@ -203,16 +217,13 @@ export const searchReducer = (state = initialState, action) => {
|
||||
};
|
||||
|
||||
case types.CLEAR_SHOPPERHOUSE_DATA:
|
||||
console.log('[DEBUG] 🧹 Redux shopperHouseData 초기화 호출 - 호출 스택 추적:');
|
||||
console.log(
|
||||
'[DEBUG] 호출 위치:',
|
||||
new Error().stack?.split('\n')[1]?.trim() || '(스택 추적 불가)'
|
||||
);
|
||||
console.log(
|
||||
dlog('[DEBUG] 🧹 Redux shopperHouseData 초기화 호출 - 호출 스택 추적:');
|
||||
dlog('[DEBUG] 호출 위치:', new Error().stack?.split('\n')[1]?.trim() || '(스택 추적 불가)');
|
||||
dlog(
|
||||
'[VoiceInput] 🧹 Redux shopperHouseData 초기화 (searchId & relativeQueries & preShopperHouseData는 유지)'
|
||||
);
|
||||
console.log('[VoiceInput]-searchReducer-CLEAR_SHOPPERHOUSE_DATA');
|
||||
console.log(
|
||||
dlog('[VoiceInput]-searchReducer-CLEAR_SHOPPERHOUSE_DATA');
|
||||
dlog(
|
||||
JSON.stringify(
|
||||
{
|
||||
shopperHouseData_cleared: true,
|
||||
@@ -239,7 +250,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
|
||||
// 🔽 검색 메인 데이터 처리
|
||||
case types.GET_SEARCH_MAIN: {
|
||||
console.log('🔍 [searchReducer] GET_SEARCH_MAIN 받은 payload:', action.payload);
|
||||
dlog('🔍 [searchReducer] GET_SEARCH_MAIN 받은 payload:', action.payload);
|
||||
|
||||
// 여러 가능한 구조 확인
|
||||
let resultData = null;
|
||||
@@ -247,15 +258,15 @@ export const searchReducer = (state = initialState, action) => {
|
||||
if (action.payload?.result) {
|
||||
// payload.result 구조
|
||||
resultData = action.payload.result;
|
||||
console.log('✅ [searchReducer] payload.result 구조 확인');
|
||||
dlog('✅ [searchReducer] payload.result 구조 확인');
|
||||
} else if (action.payload?.data?.result) {
|
||||
// payload.data.result 구조
|
||||
resultData = action.payload.data.result;
|
||||
console.log('✅ [searchReducer] payload.data.result 구조 확인');
|
||||
dlog('✅ [searchReducer] payload.data.result 구조 확인');
|
||||
} else if (action.payload?.data) {
|
||||
// payload.data에 직접 데이터가 있는 경우
|
||||
resultData = action.payload.data;
|
||||
console.log('✅ [searchReducer] payload.data 직접 구조 확인');
|
||||
dlog('✅ [searchReducer] payload.data 직접 구조 확인');
|
||||
}
|
||||
|
||||
if (!resultData) {
|
||||
@@ -264,7 +275,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
return state;
|
||||
}
|
||||
|
||||
console.log('[searchReducer] ✅ GET_SEARCH_MAIN success');
|
||||
dlog('[searchReducer] ✅ GET_SEARCH_MAIN success');
|
||||
|
||||
return {
|
||||
...state,
|
||||
@@ -278,7 +289,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
}
|
||||
|
||||
case types.CLEAR_SEARCH_MAIN_DATA:
|
||||
console.log('[searchReducer] 🧹 searchMainData 초기화');
|
||||
dlog('[searchReducer] 🧹 searchMainData 초기화');
|
||||
return {
|
||||
...state,
|
||||
searchMainData: {
|
||||
@@ -291,7 +302,7 @@ export const searchReducer = (state = initialState, action) => {
|
||||
|
||||
// 🎯 [Phase 1] SearchPanel 모드 제어 명령
|
||||
case types.SWITCH_TO_SEARCH_INPUT_OVERLAY:
|
||||
console.log('[searchReducer] 🔄 SWITCH_TO_SEARCH_INPUT_OVERLAY 명령 저장', {
|
||||
dlog('[searchReducer] 🔄 SWITCH_TO_SEARCH_INPUT_OVERLAY 명령 저장', {
|
||||
source: action.payload?.source,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
@@ -7,6 +7,12 @@ import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import { createDebugHelpers } from '../../../../utils/debug';
|
||||
|
||||
// 디버그 헬퍼 설정
|
||||
const DEBUG_MODE = false;
|
||||
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
||||
|
||||
// import icon_arrow_right from '../../../../../assets/images/icons';
|
||||
import icon_arrow_dwon from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png';
|
||||
import icon_shop_now from '../../../../../assets/images/player/icon_tabcontainer_shopnow.png';
|
||||
@@ -74,7 +80,7 @@ export default function TabContainerV2({
|
||||
|
||||
// ✨ DEBUG: youmaylikeInfos 데이터 로그
|
||||
useEffect(() => {
|
||||
console.log('[DEBUG] TabContainerV2 - youmaylikeInfos:', {
|
||||
dlog('[DEBUG] TabContainerV2 - youmaylikeInfos:', {
|
||||
exists: !!youmaylikeInfos,
|
||||
length: youmaylikeInfos?.length,
|
||||
data: youmaylikeInfos,
|
||||
|
||||
@@ -1,37 +1,19 @@
|
||||
// src/views/SearchPanel/SearchPanel.new.jsx
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import micIcon from '../../../assets/images/searchpanel/image-mic.png';
|
||||
import hotPicksImage from '../../../assets/images/searchpanel/img-hotpicks.png';
|
||||
import hotPicksBrandImage
|
||||
from '../../../assets/images/searchpanel/img-search-hotpicks.png';
|
||||
import {
|
||||
sendLogGNB,
|
||||
sendLogTotalRecommend,
|
||||
} from '../../actions/logActions';
|
||||
import hotPicksBrandImage from '../../../assets/images/searchpanel/img-search-hotpicks.png';
|
||||
import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
|
||||
import { getMyRecommandedKeyword } from '../../actions/myPageActions';
|
||||
import {
|
||||
popPanel,
|
||||
pushPanel,
|
||||
updatePanel,
|
||||
} from '../../actions/panelActions';
|
||||
import { popPanel, pushPanel, updatePanel } from '../../actions/panelActions';
|
||||
import {
|
||||
clearPanelCommand,
|
||||
clearShopperHouseData,
|
||||
@@ -51,36 +33,27 @@ import {
|
||||
// showWarningToast,
|
||||
// } from '../../actions/toastActions';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
import TItemCardNew, {
|
||||
removeDotAndColon,
|
||||
} from '../../components/TItemCard/TItemCard.new';
|
||||
import TItemCardNew, { removeDotAndColon } from '../../components/TItemCard/TItemCard.new';
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
import TVerticalPagenator
|
||||
from '../../components/TVerticalPagenator/TVerticalPagenator';
|
||||
import TVirtualGridList
|
||||
from '../../components/TVirtualGridList/TVirtualGridList';
|
||||
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
|
||||
import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList';
|
||||
import usePanelHistory from '../../hooks/usePanelHistory/usePanelHistory';
|
||||
// import VirtualKeyboardContainer from "../../components/TToast/VirtualKeyboardContainer";
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import { useSearchHistory } from '../../hooks/useSearchHistory';
|
||||
import {
|
||||
LOG_CONTEXT_NAME,
|
||||
LOG_MENU,
|
||||
LOG_MESSAGE_ID,
|
||||
panel_names,
|
||||
} from '../../utils/Config';
|
||||
import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../utils/Config';
|
||||
import NoSearchResults from './NoSearchResults/NoSearchResults';
|
||||
// import NoSearchResults from './NoSearchResults/NoSearchResults';
|
||||
import SearchInputOverlay from './SearchInputOverlay';
|
||||
import css from './SearchPanel.new.module.less';
|
||||
import SearchResultsNew from './SearchResults.new.v2';
|
||||
import TInputSimple, {
|
||||
ICONS,
|
||||
KINDS,
|
||||
} from './TInput/TInputSimple';
|
||||
import VoiceInputOverlay, {
|
||||
VOICE_MODES,
|
||||
} from './VoiceInputOverlay/VoiceInputOverlay';
|
||||
import TInputSimple, { ICONS, KINDS } from './TInput/TInputSimple';
|
||||
import VoiceInputOverlay, { VOICE_MODES } from './VoiceInputOverlay/VoiceInputOverlay';
|
||||
import { createDebugHelpers } from '../../utils/debug';
|
||||
|
||||
// 디버그 헬퍼 설정
|
||||
const DEBUG_MODE = false;
|
||||
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
||||
|
||||
/**
|
||||
* ✨ Mode-Based Architecture 도입
|
||||
@@ -240,7 +213,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 🐛 [DEBUG] shopperHouseData 상태 변경 추적 (DEBUG_MODE가 true일 경우에만)
|
||||
useEffect(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG] 📊 SearchPanel shopperHouseData 상태 변경:', {
|
||||
dlog('[DEBUG] 📊 SearchPanel shopperHouseData 상태 변경:', {
|
||||
hasData: !!shopperHouseData,
|
||||
dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0,
|
||||
searchId: shopperHouseData?.results?.[0]?.searchId || '(없음)',
|
||||
@@ -256,7 +229,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 🐛 [DEBUG] SearchPanel 마운트/언마운트 추적 (DEBUG_MODE가 true일 경우에만)
|
||||
useEffect(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG] 🚀 SearchPanel 마운트됨 - 초기 shopperHouseData 상태:', {
|
||||
dlog('[DEBUG] 🚀 SearchPanel 마운트됨 - 초기 shopperHouseData 상태:', {
|
||||
hasData: !!shopperHouseData,
|
||||
dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0,
|
||||
currentMode,
|
||||
@@ -266,7 +239,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
return () => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG] 🔚 SearchPanel 언마운트됨 - shopperHouseData 상태:', {
|
||||
dlog('[DEBUG] 🔚 SearchPanel 언마운트됨 - shopperHouseData 상태:', {
|
||||
hasData: !!shopperHouseData,
|
||||
dataLength: shopperHouseData?.results?.[0]?.docs?.length || 0,
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -278,7 +251,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 🐛 [DEBUG] isOnTop 상태 변경 추적 (DetailPanel <-> SearchPanel 전환, DEBUG_MODE가 true일 경우에만)
|
||||
useEffect(() => {
|
||||
if (isOnTopRef.current !== isOnTop && DEBUG_MODE) {
|
||||
console.log('[DEBUG] 🔄 SearchPanel isOnTop 상태 변경:', {
|
||||
dlog('[DEBUG] 🔄 SearchPanel isOnTop 상태 변경:', {
|
||||
from: isOnTopRef.current,
|
||||
to: isOnTop,
|
||||
shopperHouseData_preserved: !!shopperHouseData,
|
||||
@@ -317,7 +290,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// API 실패 시 fallback reference 초기화
|
||||
useEffect(() => {
|
||||
if (shopperHouseErrorPopup?.visible && shopperHouseErrorPopup?.type === 'API_FAILURE') {
|
||||
console.log('[SearchPanel] 🧹 API 실패 감지 - fallbackShopperHouseData 초기화');
|
||||
dlog('[SearchPanel] 🧹 API 실패 감지 - fallbackShopperHouseData 초기화');
|
||||
shopperHouseDataRef.current = null;
|
||||
}
|
||||
}, [shopperHouseErrorPopup?.visible, shopperHouseErrorPopup?.type]);
|
||||
@@ -356,7 +329,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
currentPanel?.panelInfo?.currentSpot;
|
||||
|
||||
if (DEBUG_MODE && isReturning) {
|
||||
console.log('[FOCUS] 🎯 DetailPanel 복귀 감지:', {
|
||||
dlog('[FOCUS] 🎯 DetailPanel 복귀 감지:', {
|
||||
current: currentPanel?.panelName,
|
||||
previous: previousPanel?.panelName,
|
||||
action: currentPanel?.action,
|
||||
@@ -449,9 +422,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
// ✨ [Phase 4] Enter/OK 키 처리 - SearchInputOverlay 표시
|
||||
if (e.key === 'Enter' || e.keyCode === 13) {
|
||||
console.log(
|
||||
'[DEBUG] [SearchPanel] TInputSimple에서 Enter/OK 키 감지 → SearchInputOverlay 오픈'
|
||||
);
|
||||
dlog('[DEBUG] [SearchPanel] TInputSimple에서 Enter/OK 키 감지 → SearchInputOverlay 오픈');
|
||||
e.preventDefault();
|
||||
|
||||
// ✨ [Phase 6] SearchInputOverlay 오픈 후 자동으로 입력 준비 완료
|
||||
@@ -525,7 +496,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// * 0hun: input에 포커스 발생하여 가상 키보드 활성 시, `isInputModeActive` 상태 Boolean 값 설정
|
||||
// */
|
||||
// const handleInputModeChange = useCallback((isActive) => {
|
||||
// console.log(
|
||||
// dlog(
|
||||
// "[SearchPanel] TInput 입력 모드:",
|
||||
// isActive ? "활성화 (키보드 표시)" : "비활성화 (키보드 숨김)"
|
||||
// );
|
||||
@@ -613,7 +584,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
}
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'🎤 [DEBUG][SearchPanel] openVoiceOverlay called, current isVoiceOverlayVisible:',
|
||||
isVoiceOverlayVisible
|
||||
);
|
||||
@@ -636,7 +607,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
*/
|
||||
const onCancel = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-onCancel called', {
|
||||
dlog('[DEBUG]-onCancel called', {
|
||||
isOnTop: isOnTopRef.current,
|
||||
isVoiceOverlayVisible,
|
||||
isSearchOverlayVisible,
|
||||
@@ -649,7 +620,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
if (!isOnTopRef.current) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-onCancel: isOnTopRef is false, returning');
|
||||
dlog('[DEBUG]-onCancel: isOnTopRef is false, returning');
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -657,7 +628,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// VoiceInputOverlay가 열려있으면 먼저 닫기
|
||||
if (isVoiceOverlayVisible) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-onCancel: closing VoiceInputOverlay');
|
||||
dlog('[DEBUG]-onCancel: closing VoiceInputOverlay');
|
||||
}
|
||||
setIsVoiceOverlayVisible(false);
|
||||
return;
|
||||
@@ -666,7 +637,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// SearchInputOverlay가 열려있으면 먼저 닫기
|
||||
if (isSearchOverlayVisible) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-onCancel: closing SearchInputOverlay');
|
||||
dlog('[DEBUG]-onCancel: closing SearchInputOverlay');
|
||||
}
|
||||
handleSearchOverlayClose();
|
||||
return;
|
||||
@@ -674,7 +645,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
// ✨ [Phase 5] VOICE_RESULT 모드에서 ESC/뒤로가기 누르면 INITIAL 모드로 돌아가기
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-VOICE_RESULT check:', {
|
||||
dlog('[DEBUG]-VOICE_RESULT check:', {
|
||||
currentMode,
|
||||
isVoiceResultMode: currentMode === SEARCH_PANEL_MODES.VOICE_RESULT,
|
||||
VOICE_RESULT_value: SEARCH_PANEL_MODES.VOICE_RESULT,
|
||||
@@ -683,11 +654,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
if (currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'[DEBUG]-VOICE_RESULT: Clearing ShopperHouse data (searchId will be preserved for 2nd search)'
|
||||
);
|
||||
console.log('[VoiceInput]-SearchPanel-onCancel-VOICE_RESULT');
|
||||
console.log('[VoiceInput] 🧹 VOICE_RESULT 모드에서 ESC 누름 - clearShopperHouseData 호출');
|
||||
dlog('[VoiceInput]-SearchPanel-onCancel-VOICE_RESULT');
|
||||
dlog('[VoiceInput] 🧹 VOICE_RESULT 모드에서 ESC 누름 - clearShopperHouseData 호출');
|
||||
}
|
||||
// 🎯 [포커스 로직 통합] 포커스는 상태 변경에 의해 자동으로 처리됨
|
||||
setIsShopperHousePending(false);
|
||||
@@ -696,17 +667,17 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
}
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-onCancel: normal cancel logic', { searchQuery });
|
||||
dlog('[DEBUG]-onCancel: normal cancel logic', { searchQuery });
|
||||
}
|
||||
|
||||
if (searchQuery === null || searchQuery === '') {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-onCancel: popping panel');
|
||||
dlog('[DEBUG]-onCancel: popping panel');
|
||||
}
|
||||
dispatch(popPanel(panel_names.SEARCH_PANEL));
|
||||
} else {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-onCancel: resetting search query');
|
||||
dlog('[DEBUG]-onCancel: resetting search query');
|
||||
}
|
||||
setSearchQuery('');
|
||||
// 🎯 [포커스 로직 통합] 포커스는 상태 변경(searchQuery)에 의해 자동으로 처리됨
|
||||
@@ -820,7 +791,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
const analyzeCurrentScenario = useCallback(() => {
|
||||
// DEBUG: 모든 기본 상태값 출력
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG] analyzeCurrentScenario 호출됨:', {
|
||||
dlog('[DEBUG] analyzeCurrentScenario 호출됨:', {
|
||||
// 🎯 기존 isOnTop과 usePanelHistory의 isOnTop 비교
|
||||
propIsOnTop: isOnTop,
|
||||
historyIsOnTop: currentIsOnTop,
|
||||
@@ -845,8 +816,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
if (isReturningFromDetailPanel) {
|
||||
const currentSpot = currentPanel?.panelInfo?.currentSpot;
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[Focus] usePanelHistory로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동');
|
||||
console.log('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (usePanelHistory)', {
|
||||
dlog('[Focus] usePanelHistory로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동');
|
||||
dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (usePanelHistory)', {
|
||||
currentSpot,
|
||||
mode: currentMode,
|
||||
fromSearchResult: currentMode === SEARCH_PANEL_MODES.SEARCH_RESULT,
|
||||
@@ -862,7 +833,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 🎯 [개선된 fallback] usePanelHistory의 isOnTop 정보 활용
|
||||
// DetailPanel에서 방금 복귀한 상황 (usePanelHistory가 없을 경우를 대비)
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG] 개선된 DETAIL_PANEL_RETURN 조건 확인 (fallback):', {
|
||||
dlog('[DEBUG] 개선된 DETAIL_PANEL_RETURN 조건 확인 (fallback):', {
|
||||
// 🎯 여러 isOnTop 소스 비교
|
||||
propIsOnTop: isOnTop,
|
||||
historyIsOnTop: currentIsOnTop,
|
||||
@@ -889,8 +860,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
) {
|
||||
const usedHistoryOnTop = currentIsOnTop && isOnTopChange?.becameOnTop;
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[Focus] 개선된 방식으로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동');
|
||||
console.log('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (improved fallback)', {
|
||||
dlog('[Focus] 개선된 방식으로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동');
|
||||
dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (improved fallback)', {
|
||||
currentSpot: panelInfo.currentSpot,
|
||||
mode: currentMode,
|
||||
fromSearchResult: currentMode === SEARCH_PANEL_MODES.SEARCH_RESULT,
|
||||
@@ -908,7 +879,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// - 위의 DETAIL_PANEL_RETURN이 아닌 경우 (= currentSpot이 없거나 모드가 검색 결과 아님)
|
||||
if (isOnTop && !isOnTopRef.current) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🎯 Scenario: INITIAL_OPEN', {
|
||||
dlog('[FOCUS] 🎯 Scenario: INITIAL_OPEN', {
|
||||
currentSpot: panelInfo?.currentSpot,
|
||||
mode: currentMode,
|
||||
});
|
||||
@@ -927,7 +898,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
currentModeRef.current !== SEARCH_PANEL_MODES.SEARCH_RESULT
|
||||
) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🎯 Scenario: SEARCH_RESULT_LOADED (Mode Changed)', {
|
||||
dlog('[FOCUS] 🎯 Scenario: SEARCH_RESULT_LOADED (Mode Changed)', {
|
||||
themeCount: searchDatas?.theme?.length || 0,
|
||||
itemCount: searchDatas?.item?.length || 0,
|
||||
showCount: searchDatas?.show?.length || 0,
|
||||
@@ -948,10 +919,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
currentMode === SEARCH_PANEL_MODES.VOICE_RESULT &&
|
||||
shopperHouseData &&
|
||||
// 🎯 [개선] 모드 변경 OR 새로운 데이터 도착 감지
|
||||
(currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT || shopperHouseDataRef.current !== shopperHouseData)
|
||||
(currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT ||
|
||||
shopperHouseDataRef.current !== shopperHouseData)
|
||||
) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🎯 Scenario: NEW_SEARCH_LOADED (Voice Result Mode)', {
|
||||
dlog('[FOCUS] 🎯 Scenario: NEW_SEARCH_LOADED (Voice Result Mode)', {
|
||||
itemCount: shopperHouseData?.results?.[0]?.docs?.length || 0,
|
||||
prevMode: currentModeRef.current,
|
||||
nextMode: currentMode,
|
||||
@@ -966,7 +938,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// Voice Overlay가 닫힌 상황
|
||||
if (!isVoiceOverlayVisible && isVoiceOverlayVisibleRef.current) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🎯 Scenario: VOICE_OVERLAY_CLOSED', {
|
||||
dlog('[FOCUS] 🎯 Scenario: VOICE_OVERLAY_CLOSED', {
|
||||
hasShopperHouseData: !!shopperHouseData,
|
||||
});
|
||||
}
|
||||
@@ -974,7 +946,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 이렇게 하면 VOICE_OVERLAY_CLOSED 시나리오에서 TInput으로 가는 것을 방지
|
||||
if (shopperHouseData && currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🔄 VOICE_OVERLAY_CLOSED + new data → NEW_SEARCH_LOADED 우선 처리');
|
||||
dlog('[FOCUS] 🔄 VOICE_OVERLAY_CLOSED + new data → NEW_SEARCH_LOADED 우선 처리');
|
||||
}
|
||||
return 'NEW_SEARCH_LOADED';
|
||||
}
|
||||
@@ -986,7 +958,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// - 검색이 수행되지 않았거나 SearchPanel이 SEARCH_RESULT 모드가 아닌 경우
|
||||
if (!isSearchOverlayVisible && isSearchOverlayVisibleRef.current) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED', {
|
||||
dlog('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED', {
|
||||
isSearchOverlayVisible,
|
||||
prevIsSearchOverlayVisible: isSearchOverlayVisibleRef.current,
|
||||
currentMode,
|
||||
@@ -1040,25 +1012,25 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
if (isReturningFromDetailPanel && currentPanel?.panelInfo?.currentSpot) {
|
||||
currentSpot = currentPanel.panelInfo.currentSpot;
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🎯 usePanelHistory currentSpot 사용:', currentSpot);
|
||||
dlog('[FOCUS] 🎯 usePanelHistory currentSpot 사용:', currentSpot);
|
||||
}
|
||||
}
|
||||
// 2. fallback: 기존 panelInfo.currentSpot 사용
|
||||
else if (panelInfo?.currentSpot) {
|
||||
currentSpot = panelInfo.currentSpot;
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🔄 fallback으로 panelInfo.currentSpot 사용:', currentSpot);
|
||||
dlog('[FOCUS] 🔄 fallback으로 panelInfo.currentSpot 사용:', currentSpot);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSpot && currentSpot.startsWith('searchItemContents')) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🎯 DETAIL_PANEL_RETURN: 이전 상품으로 포커스 복원:', currentSpot);
|
||||
dlog('[FOCUS] 🎯 DETAIL_PANEL_RETURN: 이전 상품으로 포커스 복원:', currentSpot);
|
||||
}
|
||||
return currentSpot;
|
||||
} else {
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'[FOCUS] ⚠️ DETAIL_PANEL_RETURN: currentSpot이 유효하지 않음, fallback으로 이동:',
|
||||
{
|
||||
currentSpot,
|
||||
@@ -1094,7 +1066,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// SearchInputOverlay에서 검색을 실행하면 isSearchOverlayVisible이 false로 설정되고
|
||||
// 동시에 검색 결과에 따라 모드가 변경되므로, 이 케이스는 검색어 선택 후 닫을 때만 발생
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED - TInputSimple으로 포커스');
|
||||
dlog('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED - TInputSimple으로 포커스');
|
||||
}
|
||||
return SPOTLIGHT_IDS.SEARCH_INPUT_BOX;
|
||||
|
||||
@@ -1118,7 +1090,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
*/
|
||||
const handleTransitionToSearchInput = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[SearchPanel] 🔄 handleTransitionToSearchInput 호출');
|
||||
dlog('[SearchPanel] 🔄 handleTransitionToSearchInput 호출');
|
||||
}
|
||||
|
||||
// Redux Thunk 액션으로 모든 전환 로직 처리
|
||||
@@ -1135,12 +1107,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
* Search overlay close handler
|
||||
*/
|
||||
const handleSearchOverlayClose = useCallback(() => {
|
||||
console.log('[DEBUG] 🚪 handleSearchOverlayClose 호출됨 - 직접 확인!', {
|
||||
dlog('[DEBUG] 🚪 handleSearchOverlayClose 호출됨 - 직접 확인!', {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG] 🚪 SearchInputOverlay closing');
|
||||
dlog('[DEBUG] 🚪 SearchInputOverlay closing');
|
||||
}
|
||||
|
||||
const hasSearchResults =
|
||||
@@ -1149,11 +1121,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
(searchDatas?.show?.length || 0) > 0;
|
||||
|
||||
// 🎯 SearchInputOverlay 닫힘 후 TInputSimple으로 포커스 이동을 위한 플래그 설정
|
||||
console.log('[DEBUG] setShouldFocusSearchInput(true) 설정 직전', {
|
||||
dlog('[DEBUG] setShouldFocusSearchInput(true) 설정 직전', {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
setShouldFocusSearchInput(true);
|
||||
console.log('[DEBUG] setShouldFocusSearchInput(true) 설정됨!', {
|
||||
dlog('[DEBUG] setShouldFocusSearchInput(true) 설정됨!', {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
@@ -1171,7 +1143,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
*/
|
||||
const handleVoiceOverlayClose = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'🚪 [DEBUG][SearchPanel] handleVoiceOverlayClose called, setting isVoiceOverlayVisible to FALSE'
|
||||
);
|
||||
}
|
||||
@@ -1213,7 +1185,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
// Redux state 업데이트를 위해 약간의 지연 후 API 호출
|
||||
setTimeout(() => {
|
||||
console.log('[HowAboutThese] 🔄 Redux 업데이트 후 API 호출');
|
||||
dlog('[HowAboutThese] 🔄 Redux 업데이트 후 API 호출');
|
||||
dispatch(getShopperHouseSearch(trimmedQuery, shopperHouseSearchId));
|
||||
}, 50); // 50ms 지연
|
||||
|
||||
@@ -1337,40 +1309,40 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
[hotPicksForYou, dispatch, SafeImage]
|
||||
);
|
||||
|
||||
const handleClick = useCallback((patnrId, prdtId) => {
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: { patnrId, prdtId },
|
||||
})
|
||||
);
|
||||
},[dispatch])
|
||||
|
||||
const handleClick = useCallback(
|
||||
(patnrId, prdtId) => {
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: { patnrId, prdtId },
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const renderTsvItem = useCallback(
|
||||
({ index, ...rest }) => {
|
||||
const { offerInfo, prdtId, imgUrl, patnrId, prdtNm, priceInfo } =
|
||||
tsvInfo[index];
|
||||
|
||||
return (
|
||||
<TItemCardNew
|
||||
imageAlt={prdtNm}
|
||||
imageSource={imgUrl}
|
||||
onClick={()=>{handleClick(patnrId,prdtId)}}
|
||||
offerInfo={offerInfo}
|
||||
priceInfo={priceInfo}
|
||||
productId={prdtId}
|
||||
productName={prdtNm}
|
||||
spotlightId={
|
||||
"searchMain-tsvInfo-spotlightId-" + removeDotAndColon(prdtId)
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[tsvInfo,handleClick]
|
||||
);
|
||||
({ index, ...rest }) => {
|
||||
const { offerInfo, prdtId, imgUrl, patnrId, prdtNm, priceInfo } = tsvInfo[index];
|
||||
|
||||
return (
|
||||
<TItemCardNew
|
||||
imageAlt={prdtNm}
|
||||
imageSource={imgUrl}
|
||||
onClick={() => {
|
||||
handleClick(patnrId, prdtId);
|
||||
}}
|
||||
offerInfo={offerInfo}
|
||||
priceInfo={priceInfo}
|
||||
productId={prdtId}
|
||||
productName={prdtNm}
|
||||
spotlightId={'searchMain-tsvInfo-spotlightId-' + removeDotAndColon(prdtId)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[tsvInfo, handleClick]
|
||||
);
|
||||
|
||||
/**
|
||||
* ✨ [Phase 2] 모드별 콘텐츠 렌더링 (VoiceInputOverlay의 renderModeContent와 동일한 패턴)
|
||||
@@ -1553,7 +1525,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
</div>
|
||||
<div className={css.itemList}>
|
||||
<TVirtualGridList
|
||||
dataSize={tsvInfo.length}
|
||||
dataSize={tsvInfo.length}
|
||||
direction="horizontal"
|
||||
renderItem={renderTsvItem}
|
||||
itemWidth={324}
|
||||
@@ -1561,11 +1533,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
spacing={18}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</SectionContainer>
|
||||
</>
|
||||
)}
|
||||
|
||||
</ContainerBasic>
|
||||
);
|
||||
}
|
||||
@@ -1606,7 +1576,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
let nextMode = SEARCH_PANEL_MODES.INITIAL;
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-MODE DECISION useEffect running', {
|
||||
dlog('[DEBUG]-MODE DECISION useEffect running', {
|
||||
isVoiceOverlayVisible,
|
||||
hasShopperHouseData: !!shopperHouseData,
|
||||
shopperHouseData_detail: shopperHouseData ? 'EXISTS' : 'NULL',
|
||||
@@ -1626,14 +1596,14 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 우선순위 1: 음성 입력 오버레이가 열려있으면 VOICE_INPUT 모드
|
||||
if (isVoiceOverlayVisible) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-MODE: isVoiceOverlayVisible is TRUE → VOICE_INPUT');
|
||||
dlog('[DEBUG]-MODE: isVoiceOverlayVisible is TRUE → VOICE_INPUT');
|
||||
}
|
||||
nextMode = SEARCH_PANEL_MODES.VOICE_INPUT;
|
||||
}
|
||||
// 우선순위 2: 음성 검색 결과가 있으면 VOICE_RESULT 모드
|
||||
else if (shopperHouseData || isShopperHousePending) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-MODE: shopperHouseData EXISTS or pending → VOICE_RESULT', {
|
||||
dlog('[DEBUG]-MODE: shopperHouseData EXISTS or pending → VOICE_RESULT', {
|
||||
hasData: !!shopperHouseData,
|
||||
isPending: isShopperHousePending,
|
||||
});
|
||||
@@ -1648,21 +1618,21 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
searchDatas?.show?.length > 0
|
||||
) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-MODE: searchResults EXISTS → SEARCH_RESULT');
|
||||
dlog('[DEBUG]-MODE: searchResults EXISTS → SEARCH_RESULT');
|
||||
}
|
||||
nextMode = SEARCH_PANEL_MODES.SEARCH_RESULT;
|
||||
}
|
||||
// 우선순위 4: 검색 입력 오버레이가 열려있으면 SEARCH_INPUT 모드
|
||||
else if (isSearchOverlayVisible) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-MODE: isSearchOverlayVisible is TRUE → SEARCH_INPUT');
|
||||
dlog('[DEBUG]-MODE: isSearchOverlayVisible is TRUE → SEARCH_INPUT');
|
||||
}
|
||||
nextMode = SEARCH_PANEL_MODES.SEARCH_INPUT;
|
||||
}
|
||||
// 우선순위 5: 초기 상태 (기본값)
|
||||
else {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-MODE: No condition met → INITIAL');
|
||||
dlog('[DEBUG]-MODE: No condition met → INITIAL');
|
||||
}
|
||||
nextMode = SEARCH_PANEL_MODES.INITIAL;
|
||||
}
|
||||
@@ -1670,7 +1640,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// 모드가 변경되었을 때만 업데이트
|
||||
if (nextMode !== currentMode) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log(`[DEBUG]-VOICE_RESULT 🔀 Mode changed: ${currentMode} → ${nextMode}`, {
|
||||
dlog(`[DEBUG]-VOICE_RESULT 🔀 Mode changed: ${currentMode} → ${nextMode}`, {
|
||||
isVoiceOverlayVisible,
|
||||
shopperHouseData: !!shopperHouseData,
|
||||
isShopperHousePending,
|
||||
@@ -1688,7 +1658,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
setCurrentMode(nextMode);
|
||||
} else {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-MODE: Mode unchanged -', currentMode);
|
||||
dlog('[DEBUG]-MODE: Mode unchanged -', currentMode);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
@@ -1711,11 +1681,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-searchQuery useEffect:', { searchQuery });
|
||||
dlog('[DEBUG]-searchQuery useEffect:', { searchQuery });
|
||||
}
|
||||
if (!searchQuery) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG]-VOICE_RESULT: searchQuery is empty, calling resetSearch');
|
||||
dlog('[DEBUG]-VOICE_RESULT: searchQuery is empty, calling resetSearch');
|
||||
}
|
||||
dispatch(resetSearch());
|
||||
}
|
||||
@@ -1757,7 +1727,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[DEBUG][Focus] Focus useEffect 호출됨 - 상태값 확인:', {
|
||||
dlog('[DEBUG][Focus] Focus useEffect 호출됨 - 상태값 확인:', {
|
||||
isOnTop,
|
||||
panelInfo: panelInfo,
|
||||
currentMode,
|
||||
@@ -1809,9 +1779,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
// NEW_SEARCH_LOADED: 음성 검색 결과 로드 시 VoiceInputOverlay와 충돌 방지
|
||||
// 다른 시나리오에서는 기존과 같은 지연 시간 (100ms)
|
||||
const focusDelay =
|
||||
scenario === 'DETAIL_PANEL_RETURN' || scenario === 'NEW_SEARCH_LOADED'
|
||||
? 50
|
||||
: 100;
|
||||
scenario === 'DETAIL_PANEL_RETURN' || scenario === 'NEW_SEARCH_LOADED' ? 50 : 100;
|
||||
|
||||
unifiedFocusTimerRef.current = setTimeout(() => {
|
||||
const targetElement = document.querySelector(`[data-spotlight-id="${targetId}"]`);
|
||||
@@ -1819,7 +1787,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
if (targetElement || targetId === SPOTLIGHT_IDS.SEARCH_INPUT_BOX) {
|
||||
Spotlight.focus(targetId);
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] ✅ 포커스 이동 완료:', {
|
||||
dlog('[FOCUS] ✅ 포커스 이동 완료:', {
|
||||
targetId,
|
||||
scenario,
|
||||
hasElement: !!targetElement,
|
||||
@@ -1829,7 +1797,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
}
|
||||
} else {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] ⚠️ 포커스 대상 요소를 찾지 못했습니다:', {
|
||||
dlog('[FOCUS] ⚠️ 포커스 대상 요소를 찾지 못했습니다:', {
|
||||
targetId,
|
||||
scenario,
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -1842,7 +1810,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
const fallbackElement = document.querySelector(`[data-spotlight-id="${fallbackTarget}"]`);
|
||||
if (fallbackElement) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'[FOCUS] 🔄 DETAIL_PANEL_RETURN fallback: 첫 번째 상품으로 포커스:',
|
||||
fallbackTarget
|
||||
);
|
||||
@@ -1860,7 +1828,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
if (scenario === 'NEW_SEARCH_LOADED' && targetId === 'searchItemContents0') {
|
||||
setTimeout(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🔄 NEW_SEARCH_LOADED: 1초 후 첫 번째 상품으로 다시 포커스 이동');
|
||||
dlog('[FOCUS] 🔄 NEW_SEARCH_LOADED: 1초 후 첫 번째 상품으로 다시 포커스 이동');
|
||||
}
|
||||
Spotlight.focus('searchItemContents0');
|
||||
}, 500); // 0.5초 후
|
||||
@@ -1908,7 +1876,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
* - 자동으로 플래그 초기화
|
||||
*/
|
||||
useEffect(() => {
|
||||
console.log('[DEBUG] shouldFocusSearchInput useEffect 실행됨!', {
|
||||
dlog('[DEBUG] shouldFocusSearchInput useEffect 실행됨!', {
|
||||
shouldFocusSearchInput,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
@@ -1916,12 +1884,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
if (shouldFocusSearchInput) {
|
||||
let focusTimer = null;
|
||||
|
||||
console.log('[DEBUG] shouldFocusSearchInput === true, 타이머 설정 중...', {
|
||||
dlog('[DEBUG] shouldFocusSearchInput === true, 타이머 설정 중...', {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] 🎯 SearchInputOverlay 닫힘 후 포커스 관리 useEffect 실행', {
|
||||
dlog('[FOCUS] 🎯 SearchInputOverlay 닫힘 후 포커스 관리 useEffect 실행', {
|
||||
shouldFocusSearchInput,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
@@ -1929,65 +1897,62 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
// 500ms 후 TInputSimple에 포커스 이동
|
||||
focusTimer = setTimeout(() => {
|
||||
console.log('[DEBUG] ⏰ 500ms 타이머 콜백 실행!', {
|
||||
dlog('[DEBUG] ⏰ 500ms 타이머 콜백 실행!', {
|
||||
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] ⏰ 500ms 타이머 콜백 실행 - TInputSimple으로 포커스 이동', {
|
||||
dlog('[FOCUS] ⏰ 500ms 타이머 콜백 실행 - TInputSimple으로 포커스 이동', {
|
||||
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[DEBUG] Spotlight.focus() 호출 직전', {
|
||||
dlog('[DEBUG] Spotlight.focus() 호출 직전', {
|
||||
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
|
||||
});
|
||||
|
||||
Spotlight.focus(SPOTLIGHT_IDS.SEARCH_INPUT_BOX);
|
||||
|
||||
console.log('[DEBUG] Spotlight.focus() 호출 완료!', {
|
||||
dlog('[DEBUG] Spotlight.focus() 호출 완료!', {
|
||||
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[FOCUS] ✅ Spotlight.focus() 호출 완료', {
|
||||
dlog('[FOCUS] ✅ Spotlight.focus() 호출 완료', {
|
||||
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[DEBUG] setShouldFocusSearchInput(false) 호출 직전', {
|
||||
dlog('[DEBUG] setShouldFocusSearchInput(false) 호출 직전', {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// 포커스 이동 완료 후 플래그 초기화
|
||||
setShouldFocusSearchInput(false);
|
||||
|
||||
console.log('[DEBUG] setShouldFocusSearchInput(false) 호출 완료!', {
|
||||
dlog('[DEBUG] setShouldFocusSearchInput(false) 호출 완료!', {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}, 500);
|
||||
|
||||
console.log('[DEBUG] 타이머 설정 완료 - 500ms 후 포커스 이동 예약됨', {
|
||||
dlog('[DEBUG] 타이머 설정 완료 - 500ms 후 포커스 이동 예약됨', {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Cleanup: 타이머 정리
|
||||
return () => {
|
||||
console.log('[DEBUG] shouldFocusSearchInput useEffect cleanup 실행', {
|
||||
dlog('[DEBUG] shouldFocusSearchInput useEffect cleanup 실행', {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
if (focusTimer) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
'[FOCUS] 🧹 SearchInputOverlay 포커스 관리 useEffect cleanup - 타이머 정리',
|
||||
{
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
);
|
||||
dlog('[FOCUS] 🧹 SearchInputOverlay 포커스 관리 useEffect cleanup - 타이머 정리', {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
clearTimeout(focusTimer);
|
||||
}
|
||||
@@ -2149,7 +2114,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
} // ✅ INITIAL, VOICE_RESULT & SEARCH_RESULT 모드에서 TInputSimple 내부 포커스 활성화
|
||||
onKeyDown={handleKeydown}
|
||||
onMouseDown={() => {
|
||||
console.log(
|
||||
dlog(
|
||||
'[DEBUG] [SearchPanel] TInputSimple에서 마우스 클릭 감지 → SearchInputOverlay 오픈'
|
||||
);
|
||||
setIsSearchOverlayVisible(true);
|
||||
|
||||
@@ -34,6 +34,7 @@ import { readLocalStorage, writeLocalStorage } from '../../../utils/helperMethod
|
||||
import TInputSimple, { ICONS, KINDS } from '../TInput/TInputSimple';
|
||||
import ApiStatusDisplay from './ApiStatusDisplay';
|
||||
import VoiceApiError from './modes/VoiceApiError';
|
||||
import { createDebugHelpers } from '../../../utils/debug';
|
||||
import VoiceListening from './modes/VoiceListening';
|
||||
import VoiceNotRecognized from './modes/VoiceNotRecognized';
|
||||
import VoiceNotRecognizedCircle from './modes/VoiceNotRecognizedCircle';
|
||||
@@ -56,6 +57,7 @@ const SpottableDebugButton = Spottable('div');
|
||||
|
||||
// Debug mode constant - 항상 디버그 화면 표시
|
||||
const DEBUG_MODE = false;
|
||||
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
||||
|
||||
// Voice overlay 모드 상수
|
||||
export const VOICE_MODES = {
|
||||
@@ -150,7 +152,7 @@ const VoiceInputOverlay = ({
|
||||
externalShopperHouseData = null, // 🎯 [포커스 충돌 해결] 외부 음성 검색 결과 데이터
|
||||
}) => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔄 [DEBUG] VoiceInputOverlay render - isVisible:', isVisible, 'mode:', mode);
|
||||
dlog('🔄 [DEBUG] VoiceInputOverlay render - isVisible:', isVisible, 'mode:', mode);
|
||||
}
|
||||
|
||||
const dispatch = useDispatch();
|
||||
@@ -198,7 +200,7 @@ const VoiceInputOverlay = ({
|
||||
const [webSpeechEventLogs, setWebSpeechEventLogs] = useState(() => {
|
||||
const persisted = readLocalStorage(VOICE_EVENT_LOGS_KEY, []);
|
||||
if (DEBUG_MODE) {
|
||||
console.log('📚 [DEBUG] Loaded webSpeechEventLogs from localStorage:', persisted);
|
||||
dlog('📚 [DEBUG] Loaded webSpeechEventLogs from localStorage:', persisted);
|
||||
}
|
||||
return persisted;
|
||||
});
|
||||
@@ -222,7 +224,7 @@ const VoiceInputOverlay = ({
|
||||
const [legacySearchHistory, setLegacySearchHistory] = useState(() => {
|
||||
const history = readLocalStorage(SEARCH_HISTORY_KEY, DEFAULT_SUGGESTIONS);
|
||||
if (DEBUG_MODE) {
|
||||
console.log('📚 [DEBUG] Loaded legacy searchHistory from localStorage:', history);
|
||||
dlog('📚 [DEBUG] Loaded legacy searchHistory from localStorage:', history);
|
||||
}
|
||||
return history;
|
||||
});
|
||||
@@ -231,11 +233,11 @@ const VoiceInputOverlay = ({
|
||||
|
||||
// ⭐ isVisible prop 변경 추적
|
||||
useEffect(() => {
|
||||
console.log('👁️ [DEBUG][VoiceInputOverlay] isVisible prop changed to:', isVisible);
|
||||
dlog('👁️ [DEBUG][VoiceInputOverlay] isVisible prop changed to:', isVisible);
|
||||
if (isVisible) {
|
||||
console.log('📺 [DEBUG][VoiceInputOverlay] Overlay is now VISIBLE, mode:', currentMode);
|
||||
dlog('📺 [DEBUG][VoiceInputOverlay] Overlay is now VISIBLE, mode:', currentMode);
|
||||
} else {
|
||||
console.log('🙈 [DEBUG][VoiceInputOverlay] Overlay is now HIDDEN');
|
||||
dlog('🙈 [DEBUG][VoiceInputOverlay] Overlay is now HIDDEN');
|
||||
}
|
||||
}, [isVisible, currentMode]);
|
||||
|
||||
@@ -268,7 +270,7 @@ const VoiceInputOverlay = ({
|
||||
};
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log(`[WebSpeech Event] ${event}: ${details}`);
|
||||
dlog(`[WebSpeech Event] ${event}: ${details}`);
|
||||
}
|
||||
|
||||
setWebSpeechEventLogs((prev) => {
|
||||
@@ -281,11 +283,9 @@ const VoiceInputOverlay = ({
|
||||
|
||||
// ⭐ currentMode 변경 추적
|
||||
useEffect(() => {
|
||||
console.log('🔀 [DEBUG][VoiceInputOverlay] currentMode changed to:', currentMode);
|
||||
dlog('🔀 [DEBUG][VoiceInputOverlay] currentMode changed to:', currentMode);
|
||||
if (isVisible) {
|
||||
console.log(
|
||||
`📍 [DEBUG][VoiceInputOverlay] Current state: isVisible=true, mode=${currentMode}`
|
||||
);
|
||||
dlog(`📍 [DEBUG][VoiceInputOverlay] Current state: isVisible=true, mode=${currentMode}`);
|
||||
// 모드 변경 이벤트 로깅
|
||||
addWebSpeechEventLog('MODE_CHANGE', `Mode switched to: ${currentMode}`);
|
||||
}
|
||||
@@ -296,7 +296,7 @@ const VoiceInputOverlay = ({
|
||||
if (webSpeechEventLogs.length > 0) {
|
||||
writeLocalStorage(VOICE_EVENT_LOGS_KEY, webSpeechEventLogs);
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'💾 [DEBUG] Saved webSpeechEventLogs to localStorage:',
|
||||
webSpeechEventLogs.length,
|
||||
'logs'
|
||||
@@ -329,14 +329,14 @@ const VoiceInputOverlay = ({
|
||||
writeLocalStorage(SEARCH_HISTORY_KEY, newHistory);
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('💾 [VoiceInputOverlay.v2] Legacy search history updated:', newHistory);
|
||||
dlog('💾 [VoiceInputOverlay.v2] Legacy search history updated:', newHistory);
|
||||
}
|
||||
|
||||
return newHistory;
|
||||
});
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'🎤 [VoiceInputOverlay] Voice search added to history:',
|
||||
trimmedText,
|
||||
'searchId:',
|
||||
@@ -383,12 +383,12 @@ const VoiceInputOverlay = ({
|
||||
// 🎯 API 실패 감지 - APIERROR 모드로 전환
|
||||
useEffect(() => {
|
||||
if (shopperHouseErrorPopup?.visible && shopperHouseErrorPopup?.type === 'API_FAILURE') {
|
||||
console.log('[VoiceInputOverlay] 🚨 API 실패 감지 - APIERROR 모드로 전환');
|
||||
dlog('[VoiceInputOverlay] 🚨 API 실패 감지 - APIERROR 모드로 전환');
|
||||
setCurrentMode(VOICE_MODES.APIERROR);
|
||||
|
||||
// 3초 후 자동으로 PROMPT 모드로 복귀
|
||||
const timer = setTimeout(() => {
|
||||
console.log('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀');
|
||||
dlog('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀');
|
||||
setCurrentMode(VOICE_MODES.PROMPT);
|
||||
}, 3000);
|
||||
|
||||
@@ -398,15 +398,15 @@ const VoiceInputOverlay = ({
|
||||
|
||||
// 🎯 VoiceResponse 에러 핸들러 - 빈 query 처리
|
||||
const handleVoiceResponseError = useCallback((error) => {
|
||||
console.log('[VoiceInputOverlay] ⚠️ VoiceResponse 에러 수신:', error);
|
||||
dlog('[VoiceInputOverlay] ⚠️ VoiceResponse 에러 수신:', error);
|
||||
|
||||
if (error.type === 'EMPTY_QUERY') {
|
||||
console.log('[VoiceInputOverlay] 🚨 빈 query 에러 - NOTRECOGNIZED 모드로 전환');
|
||||
dlog('[VoiceInputOverlay] 🚨 빈 query 에러 - NOTRECOGNIZED 모드로 전환');
|
||||
setCurrentMode(VOICE_MODES.NOTRECOGNIZED);
|
||||
|
||||
// 3초 후 자동으로 PROMPT 모드로 복귀
|
||||
const timer = setTimeout(() => {
|
||||
console.log('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (빈 query 에러)');
|
||||
dlog('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (빈 query 에러)');
|
||||
setCurrentMode(VOICE_MODES.PROMPT);
|
||||
}, 3000);
|
||||
|
||||
@@ -421,14 +421,14 @@ const VoiceInputOverlay = ({
|
||||
useEffect(() => {
|
||||
shopperHouseSearchIdRef.current = shopperHouseSearchId;
|
||||
if (shopperHouseSearchId) {
|
||||
console.log('[VoiceInput] 🔄 searchId ref 업데이트:', shopperHouseSearchId);
|
||||
dlog('[VoiceInput] 🔄 searchId ref 업데이트:', shopperHouseSearchId);
|
||||
}
|
||||
}, [shopperHouseSearchId]);
|
||||
|
||||
// 🔍 DEBUG: shopperHouseData 변경 추적
|
||||
useEffect(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔍 [DEBUG] shopperHouseData changed:', {
|
||||
dlog('🔍 [DEBUG] shopperHouseData changed:', {
|
||||
shopperHouseData,
|
||||
refValue: shopperHouseDataRef.current,
|
||||
isVisible,
|
||||
@@ -440,7 +440,7 @@ const VoiceInputOverlay = ({
|
||||
// 🔄 WebSpeech 에러 재시작 함수
|
||||
const restartWebSpeech = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] 🔄 Restarting WebSpeech after error');
|
||||
dlog('[VoiceInput] 🔄 Restarting WebSpeech after error');
|
||||
}
|
||||
|
||||
// 이벤트 로그 기록
|
||||
@@ -479,7 +479,7 @@ const VoiceInputOverlay = ({
|
||||
// 약간의 지연 후 새로 시작 (안정성을 위해)
|
||||
setTimeout(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] ✅ Restart complete - ready for new input');
|
||||
dlog('[VoiceInput] ✅ Restart complete - ready for new input');
|
||||
}
|
||||
}, 300);
|
||||
}, [dispatch, onSearchChange, addWebSpeechEventLog]);
|
||||
@@ -489,7 +489,7 @@ const VoiceInputOverlay = ({
|
||||
// Bubble 클릭으로 검색이 시작된 경우 WebSpeech 오류를 우회
|
||||
if (isBubbleClickSearch) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] 🔇 WebSpeech 오류 우회 (bubble click search)');
|
||||
dlog('[VoiceInput] 🔇 WebSpeech 오류 우회 (bubble click search)');
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -540,20 +540,20 @@ const VoiceInputOverlay = ({
|
||||
|
||||
// 음성 인식 중지
|
||||
if (isListening) {
|
||||
console.log('[VoiceInput] 🛑 Stopping listening due to error');
|
||||
dlog('[VoiceInput] 🛑 Stopping listening due to error');
|
||||
stopListening();
|
||||
}
|
||||
|
||||
// 에러 모드로 전환 (NOTRECOGNIZED) - 사용자에게 에러 상태 표시
|
||||
if (isVisible) {
|
||||
console.log('[VoiceInput] 🔀 Switching to NOTRECOGNIZED mode due to error');
|
||||
dlog('[VoiceInput] 🔀 Switching to NOTRECOGNIZED mode due to error');
|
||||
setCurrentMode(VOICE_MODES.NOTRECOGNIZED);
|
||||
setVoiceInputMode(null);
|
||||
|
||||
// 일부 에러 타입은 자동 재시작 시도
|
||||
const autoRetryErrors = ['network', 'aborted', 'service-not-allowed'];
|
||||
if (autoRetryErrors.includes(errorType)) {
|
||||
console.log('[VoiceInput] 🔄 Auto-restarting for error type:', errorType);
|
||||
dlog('[VoiceInput] 🔄 Auto-restarting for error type:', errorType);
|
||||
setTimeout(() => {
|
||||
restartWebSpeech();
|
||||
}, 2000); // 2초 후 자동 재시작
|
||||
@@ -640,7 +640,7 @@ const VoiceInputOverlay = ({
|
||||
// ⚠️ 최적화: 실시간 상태 표시 제거, 결과만 표시
|
||||
useEffect(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔍 [DEBUG] shopperHouseError useEffect running:', {
|
||||
dlog('🔍 [DEBUG] shopperHouseError useEffect running:', {
|
||||
isVisible,
|
||||
hasError: !!shopperHouseError,
|
||||
error: shopperHouseError,
|
||||
@@ -650,7 +650,7 @@ const VoiceInputOverlay = ({
|
||||
// ✨ 초기화 중에는 오류 처리 안 함
|
||||
if (isInitializingRef.current) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔍 [DEBUG] Skipping error check - overlay is initializing');
|
||||
dlog('🔍 [DEBUG] Skipping error check - overlay is initializing');
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -658,7 +658,7 @@ const VoiceInputOverlay = ({
|
||||
// 오류가 발생한 경우 - API 상태 표시 업데이트만 (팝업 대신)
|
||||
if (shopperHouseError && isVisible) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInputOverlay] ❌ API error - showing result only:', shopperHouseError);
|
||||
dlog('[VoiceInputOverlay] ❌ API error - showing result only:', shopperHouseError);
|
||||
}
|
||||
|
||||
// ⚠️ 심각한 오류만 표시 (5xx 서버 오류, 커스텀 메시지)
|
||||
@@ -676,7 +676,7 @@ const VoiceInputOverlay = ({
|
||||
// ShopperHouse API 응답 수신 시 overlay 닫기 및 API 상태 업데이트
|
||||
useEffect(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔍 [DEBUG] shopperHouseData useEffect running:', {
|
||||
dlog('🔍 [DEBUG] shopperHouseData useEffect running:', {
|
||||
isVisible,
|
||||
hasData: !!shopperHouseData,
|
||||
refValue: shopperHouseDataRef.current,
|
||||
@@ -688,7 +688,7 @@ const VoiceInputOverlay = ({
|
||||
// ✨ 초기화 중에는 close 로직 실행 안 함 (중복 닫기 방지)
|
||||
if (isInitializingRef.current) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔍 [DEBUG] Skipping close check - overlay is initializing');
|
||||
dlog('🔍 [DEBUG] Skipping close check - overlay is initializing');
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -696,7 +696,7 @@ const VoiceInputOverlay = ({
|
||||
// 이전 값과 비교하여 새로운 데이터가 들어왔을 때만 닫기
|
||||
if (isVisible && shopperHouseData && shopperHouseData !== shopperHouseDataRef.current) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInputOverlay.v2] ShopperHouse data received, closing overlay', {
|
||||
dlog('[VoiceInputOverlay.v2] ShopperHouse data received, closing overlay', {
|
||||
oldRef: shopperHouseDataRef.current,
|
||||
newData: shopperHouseData,
|
||||
});
|
||||
@@ -708,7 +708,7 @@ const VoiceInputOverlay = ({
|
||||
setApiStatusMessage('Success');
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInputOverlay] 📍 API Status updated:', {
|
||||
dlog('[VoiceInputOverlay] 📍 API Status updated:', {
|
||||
status: 'success',
|
||||
message: 'Success',
|
||||
});
|
||||
@@ -723,7 +723,7 @@ const VoiceInputOverlay = ({
|
||||
// STT 텍스트 수신 시 처리
|
||||
// useEffect(() => {
|
||||
// if (lastSTTText && sttTimestamp && isVisible) {
|
||||
// console.log('[VoiceInputOverlay.v2] STT text received in overlay:', lastSTTText);
|
||||
// dlog('[VoiceInputOverlay.v2] STT text received in overlay:', lastSTTText);
|
||||
//
|
||||
// // 입력창에 텍스트 표시 (부모 컴포넌트로 전달)
|
||||
// if (onSearchChange) {
|
||||
@@ -745,7 +745,7 @@ const VoiceInputOverlay = ({
|
||||
if (!ENABLE_WAKE_WORD) return;
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🎉 [VoiceInputOverlay.v2] Wake word detected! Switching to LISTENING mode');
|
||||
dlog('🎉 [VoiceInputOverlay.v2] Wake word detected! Switching to LISTENING mode');
|
||||
}
|
||||
|
||||
// 기존 타이머 정리
|
||||
@@ -766,7 +766,7 @@ const VoiceInputOverlay = ({
|
||||
// 15초 타이머 설정
|
||||
listeningTimerRef.current = setTimeout(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - PROMPT 모드로 복귀');
|
||||
dlog('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - PROMPT 모드로 복귀');
|
||||
}
|
||||
setCurrentMode(VOICE_MODES.PROMPT);
|
||||
setVoiceInputMode(null);
|
||||
@@ -789,7 +789,7 @@ const VoiceInputOverlay = ({
|
||||
|
||||
if (detected) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🎉 [VoiceInputOverlay.v2] Wake word detected in PROMPT mode:', text);
|
||||
dlog('🎉 [VoiceInputOverlay.v2] Wake word detected in PROMPT mode:', text);
|
||||
}
|
||||
handleWakeWordDetected();
|
||||
}
|
||||
@@ -799,7 +799,7 @@ const VoiceInputOverlay = ({
|
||||
useEffect(() => {
|
||||
if (isVisible && voiceVersion === VOICE_VERSION.WEB_SPEECH && isSupported === false) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('⚠️ [VoiceInputOverlay.v2] WebSpeech not supported, switching to NOINIT mode');
|
||||
dlog('⚠️ [VoiceInputOverlay.v2] WebSpeech not supported, switching to NOINIT mode');
|
||||
}
|
||||
setCurrentMode(VOICE_MODES.NOINIT);
|
||||
}
|
||||
@@ -808,7 +808,7 @@ const VoiceInputOverlay = ({
|
||||
// 🎤 음성 입력 최종 처리 함수 (15초 타이머 & 3초 silence detection 공통 사용)
|
||||
const processFinalVoiceInput = useCallback(
|
||||
(source) => {
|
||||
console.log(`[VoiceInput] 🏁 음성 입력 종료 (${source})`);
|
||||
dlog(`[VoiceInput] 🏁 음성 입력 종료 (${source})`);
|
||||
|
||||
// 모든 타이머 정리 및 ref 초기화
|
||||
clearTimerRef(listeningTimerRef);
|
||||
@@ -833,9 +833,9 @@ const VoiceInputOverlay = ({
|
||||
? reduxFinalText.trim()
|
||||
: interimRefText;
|
||||
|
||||
console.log('[VoiceInput] ├─ Redux finalText:', reduxFinalText || '(없음)');
|
||||
console.log('[VoiceInput] ├─ interimRef:', interimRefText || '(없음)');
|
||||
console.log('[VoiceInput] └─ 사용할 텍스트:', finalText, `(길이: ${finalText.length})`);
|
||||
dlog('[VoiceInput] ├─ Redux finalText:', reduxFinalText || '(없음)');
|
||||
dlog('[VoiceInput] ├─ interimRef:', interimRefText || '(없음)');
|
||||
dlog('[VoiceInput] └─ 사용할 텍스트:', finalText, `(길이: ${finalText.length})`);
|
||||
|
||||
if (finalText && finalText.length >= 3) {
|
||||
// STT 텍스트 저장
|
||||
@@ -851,11 +851,11 @@ const VoiceInputOverlay = ({
|
||||
|
||||
// ✨ ShopperHouse API 자동 호출 (2차 발화 시 searchId 포함)
|
||||
const query = finalText.trim();
|
||||
console.log('[VoiceInput] 📤 API 요청 전송');
|
||||
console.log('[VoiceInput] ├─ query:', query);
|
||||
console.log('[VoiceInput] ├─ ref 값:', shopperHouseSearchIdRef.current);
|
||||
console.log('[VoiceInput] ├─ currentSearchId:', currentSearchId);
|
||||
console.log('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)');
|
||||
dlog('[VoiceInput] 📤 API 요청 전송');
|
||||
dlog('[VoiceInput] ├─ query:', query);
|
||||
dlog('[VoiceInput] ├─ ref 값:', shopperHouseSearchIdRef.current);
|
||||
dlog('[VoiceInput] ├─ currentSearchId:', currentSearchId);
|
||||
dlog('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)');
|
||||
|
||||
// API 호출 이벤트 로깅
|
||||
const searchIdInfo = currentSearchId
|
||||
@@ -878,14 +878,14 @@ const VoiceInputOverlay = ({
|
||||
);
|
||||
} else {
|
||||
// 입력이 없거나 너무 짧으면 NOTRECOGNIZED 모드로 전환
|
||||
console.log('[VoiceInput] ⚠️ 입력 없음 또는 너무 짧음 - NOTRECOGNIZED 모드로 전환');
|
||||
console.log('[VoiceInput] └─ finalText 길이:', finalText.length);
|
||||
dlog('[VoiceInput] ⚠️ 입력 없음 또는 너무 짧음 - NOTRECOGNIZED 모드로 전환');
|
||||
dlog('[VoiceInput] └─ finalText 길이:', finalText.length);
|
||||
setCurrentMode(VOICE_MODES.NOTRECOGNIZED);
|
||||
setVoiceInputMode(null);
|
||||
|
||||
// 3초 후 자동으로 PROMPT 모드로 복귀
|
||||
const timer = setTimeout(() => {
|
||||
console.log('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (NOTRECOGNIZED)');
|
||||
dlog('[VoiceInputOverlay] ⏰ 3초 후 PROMPT 모드로 복귀 (NOTRECOGNIZED)');
|
||||
setCurrentMode(VOICE_MODES.PROMPT);
|
||||
}, 3000);
|
||||
|
||||
@@ -913,13 +913,13 @@ const VoiceInputOverlay = ({
|
||||
// currentMode === VOICE_MODES.LISTENING &&
|
||||
// !isListening
|
||||
// ) {
|
||||
// console.log('🎙️ [VoiceInputOverlay.v2] Auto-starting Web Speech API after mode change...');
|
||||
// dlog('🎙️ [VoiceInputOverlay.v2] Auto-starting Web Speech API after mode change...');
|
||||
// startListening();
|
||||
//
|
||||
// // 15초 타이머 설정
|
||||
// clearTimerRef(listeningTimerRef);
|
||||
// listeningTimerRef.current = setTimeout(() => {
|
||||
// console.log('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - WebSpeech 자동 종료');
|
||||
// dlog('⏰ [VoiceInputOverlay.v2] 15초 타임아웃 - WebSpeech 자동 종료');
|
||||
// stopListening();
|
||||
// setCurrentMode(VOICE_MODES.PROMPT);
|
||||
// setVoiceInputMode(null);
|
||||
@@ -961,7 +961,7 @@ const VoiceInputOverlay = ({
|
||||
const trimmedText = interimText?.trim() || '';
|
||||
if (!hasReached5Chars && trimmedText.length >= 5) {
|
||||
// 처음 5글자에 도달했을 때만 활성화
|
||||
console.log('[VoiceInput] 🎯 5글자 도달! SilenceCheck 활성화');
|
||||
dlog('[VoiceInput] 🎯 5글자 도달! SilenceCheck 활성화');
|
||||
setHasReached5Chars(true);
|
||||
setIsSilenceCheckActive(true);
|
||||
} else if (hasReached5Chars && trimmedText.length < 5) {
|
||||
@@ -983,7 +983,7 @@ const VoiceInputOverlay = ({
|
||||
// 3초 silence detection: 마지막 입력 후 3초 동안 추가 입력이 없으면 자동 종료
|
||||
clearTimerRef(silenceDetectionTimerRef);
|
||||
silenceDetectionTimerRef.current = setTimeout(() => {
|
||||
console.log('[VoiceInput] 🔇 3초 동안 입력 없음 - 자동 종료');
|
||||
dlog('[VoiceInput] 🔇 3초 동안 입력 없음 - 자동 종료');
|
||||
addWebSpeechEventLog('SILENCE_3S', 'No input detected for 3 seconds - auto finish');
|
||||
processFinalVoiceInput('3초 silence detection');
|
||||
}, 3000); // 3초
|
||||
@@ -1010,9 +1010,7 @@ const VoiceInputOverlay = ({
|
||||
if (currentMode !== VOICE_MODES.PROMPT) return;
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
'🎙️ [VoiceInputOverlay.v2] Starting background listening for wake word detection'
|
||||
);
|
||||
dlog('🎙️ [VoiceInputOverlay.v2] Starting background listening for wake word detection');
|
||||
}
|
||||
|
||||
// PROMPT 모드에서 백그라운드 리스닝 시작
|
||||
@@ -1037,7 +1035,7 @@ const VoiceInputOverlay = ({
|
||||
setSilenceSeconds(0);
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('⏱️ [VoiceInputOverlay.v2] Starting countdown from 15');
|
||||
dlog('⏱️ [VoiceInputOverlay.v2] Starting countdown from 15');
|
||||
}
|
||||
|
||||
// 1초마다 카운트다운
|
||||
@@ -1045,7 +1043,7 @@ const VoiceInputOverlay = ({
|
||||
setCountdown((prev) => {
|
||||
const next = prev - 1;
|
||||
if (DEBUG_MODE) {
|
||||
console.log('⏱️ [VoiceInputOverlay.v2] Countdown:', next);
|
||||
dlog('⏱️ [VoiceInputOverlay.v2] Countdown:', next);
|
||||
}
|
||||
if (next <= 0) {
|
||||
clearIntervalRef(countdownIntervalRef);
|
||||
@@ -1080,7 +1078,7 @@ const VoiceInputOverlay = ({
|
||||
setSilenceSeconds((prev) => {
|
||||
const next = prev + 1;
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🚦 [VoiceInput] Silence Detection Progress:', next);
|
||||
dlog('🚦 [VoiceInput] Silence Detection Progress:', next);
|
||||
}
|
||||
if (next >= 3) {
|
||||
// 3초 도달하면 interval 정리 (타이머가 곧 종료됨)
|
||||
@@ -1098,11 +1096,11 @@ const VoiceInputOverlay = ({
|
||||
|
||||
// VoiceInputOverlay 언마운트 시에만 초기화
|
||||
useEffect(() => {
|
||||
console.log('[VoiceInput] 🚀 VoiceInputOverlay 마운트됨 (searchId 유지)');
|
||||
dlog('[VoiceInput] 🚀 VoiceInputOverlay 마운트됨 (searchId 유지)');
|
||||
|
||||
// Cleanup: 언마운트 시에만 초기화
|
||||
return () => {
|
||||
console.log('[VoiceInput] 🔚 VoiceInputOverlay 언마운트 - searchId 초기화');
|
||||
dlog('[VoiceInput] 🔚 VoiceInputOverlay 언마운트 - searchId 초기화');
|
||||
// dispatch(clearShopperHouseData());
|
||||
dispatch(clearSTTText());
|
||||
};
|
||||
@@ -1111,34 +1109,34 @@ const VoiceInputOverlay = ({
|
||||
// Overlay가 열릴 때 포커스를 overlay 내부로 이동
|
||||
useEffect(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('👁️ [DEBUG] isVisible useEffect triggered - isVisible:', isVisible);
|
||||
dlog('👁️ [DEBUG] isVisible useEffect triggered - isVisible:', isVisible);
|
||||
}
|
||||
|
||||
if (isVisible) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('✅ [DEBUG] ===== OVERLAY OPENING =====');
|
||||
dlog('✅ [DEBUG] ===== OVERLAY OPENING =====');
|
||||
}
|
||||
|
||||
// ✨ 초기화 시작 플래그 설정 (close 로직 일시 차단)
|
||||
isInitializingRef.current = true;
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🚧 [DEBUG] isInitializingRef set to TRUE - close logic DISABLED');
|
||||
dlog('🚧 [DEBUG] isInitializingRef set to TRUE - close logic DISABLED');
|
||||
}
|
||||
|
||||
// 현재 포커스된 요소 저장
|
||||
lastFocusedElement.current = Spotlight.getCurrent();
|
||||
console.log('[lastFocusedElement] 🚀 Current focused element:', Spotlight.getCurrent());
|
||||
dlog('[lastFocusedElement] 🚀 Current focused element:', Spotlight.getCurrent());
|
||||
|
||||
// ✨ shopperHouseDataRef를 null로 초기화
|
||||
shopperHouseDataRef.current = null;
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔍 [DEBUG] shopperHouseDataRef initialized to NULL');
|
||||
dlog('🔍 [DEBUG] shopperHouseDataRef initialized to NULL');
|
||||
}
|
||||
|
||||
// 모드 초기화 (항상 prompt 모드로 시작)
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔀 [DEBUG] Setting currentMode to:', mode);
|
||||
dlog('🔀 [DEBUG] Setting currentMode to:', mode);
|
||||
}
|
||||
setCurrentMode(mode);
|
||||
setVoiceInputMode(null);
|
||||
@@ -1149,12 +1147,12 @@ const VoiceInputOverlay = ({
|
||||
// ✨ 초기화 완료 - close 로직 활성화
|
||||
isInitializingRef.current = false;
|
||||
if (DEBUG_MODE) {
|
||||
console.log('✅ [DEBUG] Initialization complete, close logic ENABLED');
|
||||
dlog('✅ [DEBUG] Initialization complete, close logic ENABLED');
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('❌ [DEBUG] ===== OVERLAY CLOSING =====');
|
||||
dlog('❌ [DEBUG] ===== OVERLAY CLOSING =====');
|
||||
}
|
||||
// Overlay가 닫힐 때 원래 포커스 복원 및 상태 초기화
|
||||
|
||||
@@ -1168,7 +1166,8 @@ const VoiceInputOverlay = ({
|
||||
// 🎯 [포커스 충돌 해결] VoiceInputOverlay를 통한 음성 검색 후에는 TInput으로 포커스 복원하지 않음
|
||||
// SearchResults의 첫 번째 상품으로 포커스가 가도록 SearchPanel에 위임
|
||||
// shopperHouseData가 있으면 (음성 검색 결과가 있으면) 포커스 복원하지 않음
|
||||
const hasVoiceSearchResult = shopperHouseData && shopperHouseData.results && shopperHouseData.results.length > 0;
|
||||
const hasVoiceSearchResult =
|
||||
shopperHouseData && shopperHouseData.results && shopperHouseData.results.length > 0;
|
||||
if (lastFocusedElement.current && !isVoiceResultMode && !hasVoiceSearchResult) {
|
||||
focusRestoreTimerRef.current = setTimeout(() => {
|
||||
Spotlight.focus(lastFocusedElement.current);
|
||||
@@ -1195,7 +1194,7 @@ const VoiceInputOverlay = ({
|
||||
// 🔄 API 에러 재시작 함수
|
||||
const handleApiErrorRestart = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] 🔄 Restarting after API error');
|
||||
dlog('[VoiceInput] 🔄 Restarting after API error');
|
||||
}
|
||||
|
||||
// ShopperHouse 에러 상태 정리 (Redux)
|
||||
@@ -1216,14 +1215,14 @@ const VoiceInputOverlay = ({
|
||||
setIsBubbleClickSearch(false); // bubble 클릭 상태 초기화
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] ✅ API error restart complete - ready for new input');
|
||||
dlog('[VoiceInput] ✅ API error restart complete - ready for new input');
|
||||
}
|
||||
}, [dispatch, onSearchChange]);
|
||||
|
||||
// API 재시도 함수
|
||||
const handleApiErrorRetry = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] 🔄 Retrying API call after error');
|
||||
dlog('[VoiceInput] 🔄 Retrying API call after error');
|
||||
}
|
||||
|
||||
// 이전 에러 상태 정리
|
||||
@@ -1235,9 +1234,9 @@ const VoiceInputOverlay = ({
|
||||
const currentSearchId = shopperHouseSearchIdRef.current;
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] 🔄 Retrying API call');
|
||||
console.log('[VoiceInput] ├─ query:', query);
|
||||
console.log('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)');
|
||||
dlog('[VoiceInput] 🔄 Retrying API call');
|
||||
dlog('[VoiceInput] ├─ query:', query);
|
||||
dlog('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)');
|
||||
}
|
||||
|
||||
// 다시 API 호출
|
||||
@@ -1267,7 +1266,7 @@ const VoiceInputOverlay = ({
|
||||
// Overlay 닫기 핸들러 (모든 닫기 동작을 통합)
|
||||
const handleClose = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🚪 [DEBUG] handleClose called - closing overlay');
|
||||
dlog('🚪 [DEBUG] handleClose called - closing overlay');
|
||||
}
|
||||
|
||||
// 1. 타이머 정리
|
||||
@@ -1309,7 +1308,7 @@ const VoiceInputOverlay = ({
|
||||
const handleSuggestionClick = useCallback(
|
||||
(suggestion) => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('💡 [DEBUG] handleSuggestionClick called with:', suggestion);
|
||||
dlog('💡 [DEBUG] handleSuggestionClick called with:', suggestion);
|
||||
}
|
||||
|
||||
// Bubble 클릭으로 검색 시작 상태 설정
|
||||
@@ -1339,9 +1338,9 @@ const VoiceInputOverlay = ({
|
||||
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 || '(없음 - 첫 번째 발화)');
|
||||
dlog('🔍 [DEBUG] Calling ShopperHouse API from bubble click');
|
||||
dlog('[VoiceInput] ├─ query:', query);
|
||||
dlog('[VoiceInput] └─ searchId:', currentSearchId || '(없음 - 첫 번째 발화)');
|
||||
}
|
||||
|
||||
// API 호출 이벤트 로깅 (Bubble 클릭)
|
||||
@@ -1379,7 +1378,7 @@ const VoiceInputOverlay = ({
|
||||
// Input 창에서 API 호출 핸들러 (돋보기 아이콘 클릭 시에만)
|
||||
const handleSearchSubmit = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInputOverlay.v2] Search submit:', searchQuery);
|
||||
dlog('[VoiceInputOverlay.v2] Search submit:', searchQuery);
|
||||
}
|
||||
if (searchQuery && searchQuery.trim()) {
|
||||
// ShopperHouse API 호출
|
||||
@@ -1423,7 +1422,7 @@ const VoiceInputOverlay = ({
|
||||
(e) => {
|
||||
if (e.key === 'Enter' || e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
console.log(
|
||||
dlog(
|
||||
'[VoiceInputOverlay] 🔄 TInputSimple에서 Enter 키 감지 → SearchInputOverlay 전환 시작'
|
||||
);
|
||||
// 🎯 SearchPanel의 콜백 호출
|
||||
@@ -1439,7 +1438,7 @@ const VoiceInputOverlay = ({
|
||||
// ⚠️ [251031] 마우스 클릭 시 프리징 발생 - 추후 원인 분석 후 활성화 필요
|
||||
// const handleInputMouseDown = useCallback((e) => {
|
||||
// e.preventDefault();
|
||||
// console.log('[VoiceInputOverlay] 🖱️ TInputSimple에서 마우스 클릭 감지 → SearchInputOverlay 전환 시작');
|
||||
// dlog('[VoiceInputOverlay] 🖱️ TInputSimple에서 마우스 클릭 감지 → SearchInputOverlay 전환 시작');
|
||||
// // 🎯 SearchPanel의 콜백 호출
|
||||
// if (onTransitionToSearchInput) {
|
||||
// onTransitionToSearchInput();
|
||||
@@ -1450,14 +1449,14 @@ const VoiceInputOverlay = ({
|
||||
const handleInputModeChange = useCallback(
|
||||
(inputMode) => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInputOverlay] TInput 모드 변경:', inputMode);
|
||||
dlog('[VoiceInputOverlay] TInput 모드 변경:', inputMode);
|
||||
}
|
||||
|
||||
if (inputMode === 'input') {
|
||||
// Input 모드로 전환되면 Overlay 닫기
|
||||
// SearchPanel의 TInput으로 자연스럽게 전환됨
|
||||
if (DEBUG_MODE) {
|
||||
console.log('⌨️ [DEBUG] Input 모드로 전환됨 - VoiceInputOverlay 닫고 SearchPanel로 이동');
|
||||
dlog('⌨️ [DEBUG] Input 모드로 전환됨 - VoiceInputOverlay 닫고 SearchPanel로 이동');
|
||||
}
|
||||
handleClose();
|
||||
}
|
||||
@@ -1469,7 +1468,7 @@ const VoiceInputOverlay = ({
|
||||
const handleToggleDashboard = useCallback(() => {
|
||||
setShowDashboard((prev) => !prev);
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔧 [DEBUG] Dashboard toggled:', !showDashboard);
|
||||
dlog('🔧 [DEBUG] Dashboard toggled:', !showDashboard);
|
||||
}
|
||||
}, [showDashboard]);
|
||||
|
||||
@@ -1484,7 +1483,7 @@ const VoiceInputOverlay = ({
|
||||
// 모드에 따른 컨텐츠 렌더링 - Memoized
|
||||
const renderModeContent = useMemo(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'🎬 [DEBUG][VoiceInputOverlay] renderModeContent called',
|
||||
'| currentMode:',
|
||||
currentMode,
|
||||
@@ -1527,8 +1526,8 @@ const VoiceInputOverlay = ({
|
||||
}
|
||||
|
||||
// ✨ [DEBUG] Redux 상태 확인 로그
|
||||
console.log('[VoiceInput]-shopperHouseRelativeQueries');
|
||||
console.log(
|
||||
dlog('[VoiceInput]-shopperHouseRelativeQueries');
|
||||
dlog(
|
||||
JSON.stringify(
|
||||
{
|
||||
hasRelativeQueries: hasRelativeQueries,
|
||||
@@ -1549,25 +1548,19 @@ const VoiceInputOverlay = ({
|
||||
);
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'✅ [DEBUG][VoiceInputOverlay] MODE = PROMPT | Rendering VoicePromptScreen',
|
||||
hasRelativeQueries ? '(with relativeQueries)' : '(with legacySearchHistory)',
|
||||
promptSuggestions.length,
|
||||
'suggestions'
|
||||
);
|
||||
if (hasRelativeQueries) {
|
||||
console.log(
|
||||
'[DEBUG][VoiceInputOverlay] ├─ searchId:',
|
||||
shopperHouseSearchId || '(없음)'
|
||||
);
|
||||
console.log('[DEBUG][VoiceInputOverlay] ├─ relativeQueries:', promptSuggestions);
|
||||
console.log('[DEBUG][VoiceInputOverlay] └─ promptTitle:', promptTitle);
|
||||
dlog('[DEBUG][VoiceInputOverlay] ├─ searchId:', shopperHouseSearchId || '(없음)');
|
||||
dlog('[DEBUG][VoiceInputOverlay] ├─ relativeQueries:', promptSuggestions);
|
||||
dlog('[DEBUG][VoiceInputOverlay] └─ promptTitle:', promptTitle);
|
||||
} else {
|
||||
console.log('[DEBUG][VoiceInputOverlay] ├─ relativeQueries가 없음');
|
||||
console.log(
|
||||
'[DEBUG][VoiceInputOverlay] └─ legacySearchHistory 사용:',
|
||||
promptSuggestions
|
||||
);
|
||||
dlog('[DEBUG][VoiceInputOverlay] ├─ relativeQueries가 없음');
|
||||
dlog('[DEBUG][VoiceInputOverlay] └─ legacySearchHistory 사용:', promptSuggestions);
|
||||
}
|
||||
}
|
||||
return (
|
||||
@@ -1581,7 +1574,7 @@ const VoiceInputOverlay = ({
|
||||
}
|
||||
case VOICE_MODES.LISTENING:
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'🎤 [DEBUG][VoiceInputOverlay] MODE = LISTENING | Rendering VoiceListening (15초 타이머)'
|
||||
);
|
||||
}
|
||||
@@ -1595,7 +1588,7 @@ const VoiceInputOverlay = ({
|
||||
);
|
||||
case VOICE_MODES.RESPONSE:
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'💬 [DEBUG][VoiceInputOverlay] MODE = RESPONSE | Rendering VoiceResponse with text:',
|
||||
sttResponseText,
|
||||
'isLoading: true (항상 로딩 애니메이션 표시)'
|
||||
@@ -1614,12 +1607,12 @@ const VoiceInputOverlay = ({
|
||||
);
|
||||
case VOICE_MODES.NOINIT:
|
||||
if (DEBUG_MODE) {
|
||||
console.log('⚠️ [DEBUG][VoiceInputOverlay] MODE = NOINIT | Rendering VoiceNotRecognized');
|
||||
dlog('⚠️ [DEBUG][VoiceInputOverlay] MODE = NOINIT | Rendering VoiceNotRecognized');
|
||||
}
|
||||
return <VoiceNotRecognized prompt={NOINIT_ERROR_MESSAGE} />;
|
||||
case VOICE_MODES.NOTRECOGNIZED:
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'❌ [DEBUG][VoiceInputOverlay] MODE = NOTRECOGNIZED | Rendering VoiceNotRecognized with error message'
|
||||
);
|
||||
}
|
||||
@@ -1631,7 +1624,7 @@ const VoiceInputOverlay = ({
|
||||
);
|
||||
case VOICE_MODES.APIERROR:
|
||||
if (DEBUG_MODE) {
|
||||
console.log(
|
||||
dlog(
|
||||
'💥 [DEBUG][VoiceInputOverlay] MODE = APIERROR | Rendering VoiceApiError with error details',
|
||||
shopperHouseError
|
||||
);
|
||||
@@ -1651,7 +1644,7 @@ const VoiceInputOverlay = ({
|
||||
return <VoiceNotRecognizedCircle />;
|
||||
default: {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🔄 [DEBUG][VoiceInputOverlay] MODE = DEFAULT | Rendering VoicePromptScreen');
|
||||
dlog('🔄 [DEBUG][VoiceInputOverlay] MODE = DEFAULT | Rendering VoicePromptScreen');
|
||||
}
|
||||
// default 케이스에서도 promptSuggestions 계산
|
||||
const hasRelativeQueries = !!shopperHouseRelativeQueries;
|
||||
@@ -1712,8 +1705,8 @@ const VoiceInputOverlay = ({
|
||||
const handleWebSpeechMicClick = useCallback(
|
||||
(e) => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🎤 [DEBUG] handleWebSpeechMicClick called, currentMode:', currentMode);
|
||||
console.log('🎤 [DEBUG] CURRENT_WEBSPEECH_VERSION:', CURRENT_WEBSPEECH_VERSION);
|
||||
dlog('🎤 [DEBUG] handleWebSpeechMicClick called, currentMode:', currentMode);
|
||||
dlog('🎤 [DEBUG] CURRENT_WEBSPEECH_VERSION:', CURRENT_WEBSPEECH_VERSION);
|
||||
}
|
||||
|
||||
// 이벤트 전파 방지 - dim 레이어의 onClick 실행 방지
|
||||
@@ -1730,9 +1723,9 @@ const VoiceInputOverlay = ({
|
||||
if (CURRENT_WEBSPEECH_VERSION === 'v1') {
|
||||
if (currentMode === VOICE_MODES.PROMPT) {
|
||||
// ✨ PROMPT 모드에서만 LISTENING으로 전환 가능
|
||||
console.log('\n🎤 ════════════════════════════════════════════════════════════════');
|
||||
console.log('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V1)');
|
||||
console.log('🎤 ════════════════════════════════════════════════════════════════\n');
|
||||
dlog('\n🎤 ════════════════════════════════════════════════════════════════');
|
||||
dlog('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V1)');
|
||||
dlog('🎤 ════════════════════════════════════════════════════════════════\n');
|
||||
|
||||
// 📋 단계별 처리:
|
||||
// 1. WebSpeech 완전 cleanup (이전 상태 제거)
|
||||
@@ -1743,56 +1736,56 @@ const VoiceInputOverlay = ({
|
||||
// ============================================================
|
||||
// 1️⃣ WebSpeech 완전 cleanup (이전 상태 제거)
|
||||
// ============================================================
|
||||
console.log('[VoiceInput] 📍 [STEP 1] WebSpeech Cleanup');
|
||||
console.log('[VoiceInput] ├─ Dispatching: cleanupWebSpeech()');
|
||||
dlog('[VoiceInput] 📍 [STEP 1] WebSpeech Cleanup');
|
||||
dlog('[VoiceInput] ├─ Dispatching: cleanupWebSpeech()');
|
||||
dispatch(cleanupWebSpeech());
|
||||
console.log('[VoiceInput] └─ ✅ Cleanup dispatched\n');
|
||||
dlog('[VoiceInput] └─ ✅ Cleanup dispatched\n');
|
||||
|
||||
// ============================================================
|
||||
// 2️⃣ Redux STT 데이터 초기화 (searchId는 Redux에서 유지됨)
|
||||
// ============================================================
|
||||
console.log('[VoiceInput] 📍 [STEP 2] STT Data Clear');
|
||||
console.log('[VoiceInput] ├─ Dispatching: clearSTTText()');
|
||||
dlog('[VoiceInput] 📍 [STEP 2] STT Data Clear');
|
||||
dlog('[VoiceInput] ├─ Dispatching: clearSTTText()');
|
||||
dispatch(clearSTTText());
|
||||
console.log('[VoiceInput] ├─ Clearing interim text buffer');
|
||||
dlog('[VoiceInput] ├─ Clearing interim text buffer');
|
||||
|
||||
// ✅ TInput 초기화
|
||||
if (onSearchChange) {
|
||||
onSearchChange({ value: '' });
|
||||
console.log('[VoiceInput] ├─ TInput cleared');
|
||||
dlog('[VoiceInput] ├─ TInput cleared');
|
||||
}
|
||||
|
||||
// ✅ Interim text ref 초기화
|
||||
interimTextRef.current = '';
|
||||
console.log('[VoiceInput] ├─ Interim text ref cleared');
|
||||
dlog('[VoiceInput] ├─ Interim text ref cleared');
|
||||
|
||||
// 기존 타이머 정리
|
||||
clearTimerRef(listeningTimerRef);
|
||||
clearTimerRef(silenceDetectionTimerRef);
|
||||
console.log('[VoiceInput] ├─ Previous timers cleared');
|
||||
dlog('[VoiceInput] ├─ Previous timers cleared');
|
||||
|
||||
// UI 모드 업데이트
|
||||
setVoiceInputMode(VOICE_INPUT_MODE.WEBSPEECH);
|
||||
setCurrentMode(VOICE_MODES.LISTENING);
|
||||
console.log('[VoiceInput] ├─ UI mode set to WEBSPEECH / LISTENING');
|
||||
dlog('[VoiceInput] ├─ UI mode set to WEBSPEECH / LISTENING');
|
||||
|
||||
// ✅ LISTENING 모드 진입 시 로그 초기화 (새로운 음성 입력 시작)
|
||||
setWebSpeechEventLogs([]);
|
||||
writeLocalStorage(VOICE_EVENT_LOGS_KEY, []);
|
||||
console.log('[VoiceInput] └─ ✅ Event logs cleared\n');
|
||||
dlog('[VoiceInput] └─ ✅ Event logs cleared\n');
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🧹 [DEBUG] Cleared webSpeechEventLogs on mic click');
|
||||
dlog('🧹 [DEBUG] Cleared webSpeechEventLogs on mic click');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3️⃣ WebSpeech 재초기화 (약간의 지연 후)
|
||||
// ============================================================
|
||||
console.log('[VoiceInput] ⏳ [STEP 3] Waiting 100ms for Redux state sync...');
|
||||
dlog('[VoiceInput] ⏳ [STEP 3] Waiting 100ms for Redux state sync...');
|
||||
const reinitializeWebSpeech = setTimeout(() => {
|
||||
console.log('[VoiceInput] 📍 [STEP 3] WebSpeech Initialization');
|
||||
console.log('[VoiceInput] ├─ Dispatching: initializeWebSpeech()');
|
||||
console.log('[VoiceInput] │ └─ lang: en-US, continuous: true, interimResults: true');
|
||||
dlog('[VoiceInput] 📍 [STEP 3] WebSpeech Initialization');
|
||||
dlog('[VoiceInput] ├─ Dispatching: initializeWebSpeech()');
|
||||
dlog('[VoiceInput] │ └─ lang: en-US, continuous: true, interimResults: true');
|
||||
|
||||
// Redux 상태 업데이트를 기다리기 위해 약간의 지연
|
||||
dispatch(
|
||||
@@ -1803,37 +1796,37 @@ const VoiceInputOverlay = ({
|
||||
})
|
||||
);
|
||||
|
||||
console.log('[VoiceInput] └─ ✅ Initialize dispatched\n');
|
||||
dlog('[VoiceInput] └─ ✅ Initialize dispatched\n');
|
||||
|
||||
// ============================================================
|
||||
// 4️⃣ WebSpeech 즉시 시작
|
||||
// ============================================================
|
||||
console.log('[VoiceInput] 📍 [STEP 4] WebSpeech Start');
|
||||
console.log('[VoiceInput] ├─ Dispatching: startWebSpeech()');
|
||||
dlog('[VoiceInput] 📍 [STEP 4] WebSpeech Start');
|
||||
dlog('[VoiceInput] ├─ Dispatching: startWebSpeech()');
|
||||
dispatch(startWebSpeech());
|
||||
console.log('[VoiceInput] └─ ✅ Start dispatched\n');
|
||||
dlog('[VoiceInput] └─ ✅ Start dispatched\n');
|
||||
|
||||
// ============================================================
|
||||
// 5️⃣ 15초 타이머 설정 및 준비 완료
|
||||
// ============================================================
|
||||
console.log('[VoiceInput] 📍 [STEP 5] Setup MaxListeningTime Timer');
|
||||
console.log('[VoiceInput] ├─ Setting 15-second listening timeout');
|
||||
dlog('[VoiceInput] 📍 [STEP 5] Setup MaxListeningTime Timer');
|
||||
dlog('[VoiceInput] ├─ Setting 15-second listening timeout');
|
||||
listeningTimerRef.current = setTimeout(() => {
|
||||
console.log('[VoiceInput] ⏲️ [TIMEOUT] 15-second max listening time reached');
|
||||
dlog('[VoiceInput] ⏲️ [TIMEOUT] 15-second max listening time reached');
|
||||
addWebSpeechEventLog('TIMEOUT_15S', '15 second timeout reached - finishing input');
|
||||
processFinalVoiceInput('15초 타임아웃');
|
||||
}, 15000); // 15초
|
||||
|
||||
console.log('[VoiceInput] ├─ ⏲️ Timer started (15000ms)');
|
||||
console.log('[VoiceInput] └─ ✅ Timer configured\n');
|
||||
dlog('[VoiceInput] ├─ ⏲️ Timer started (15000ms)');
|
||||
dlog('[VoiceInput] └─ ✅ Timer configured\n');
|
||||
|
||||
// ============================================================
|
||||
// ✨ INITIALIZATION COMPLETE
|
||||
// ============================================================
|
||||
console.log('🎤 ════════════════════════════════════════════════════════════════');
|
||||
console.log('✨ [VoiceInput] 음성 인식 준비 완료! (Voice Recognition Ready!)');
|
||||
console.log('✨ [VoiceInput] Waiting for voice input... (max 15 seconds)');
|
||||
console.log('🎤 ════════════════════════════════════════════════════════════════\n');
|
||||
dlog('🎤 ════════════════════════════════════════════════════════════════');
|
||||
dlog('✨ [VoiceInput] 음성 인식 준비 완료! (Voice Recognition Ready!)');
|
||||
dlog('✨ [VoiceInput] Waiting for voice input... (max 15 seconds)');
|
||||
dlog('🎤 ════════════════════════════════════════════════════════════════\n');
|
||||
}, 100);
|
||||
|
||||
// Cleanup: 언마운트 시 타이머 정리
|
||||
@@ -1843,7 +1836,7 @@ const VoiceInputOverlay = ({
|
||||
} else {
|
||||
// listening 모드 또는 기타 모드에서 클릭 시 -> overlay 닫기
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🎤 [DEBUG] Closing overlay (not in PROMPT mode)');
|
||||
dlog('🎤 [DEBUG] Closing overlay (not in PROMPT mode)');
|
||||
}
|
||||
handleClose();
|
||||
}
|
||||
@@ -1852,12 +1845,12 @@ const VoiceInputOverlay = ({
|
||||
// V2: Promise 체인 비동기 방식 (미래 - 아직 구현 대기 중)
|
||||
// ============================================================
|
||||
else if (CURRENT_WEBSPEECH_VERSION === 'v2') {
|
||||
console.log('\n🎤 ════════════════════════════════════════════════════════════════');
|
||||
console.log('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V2)');
|
||||
console.log('🎤 ════════════════════════════════════════════════════════════════\n');
|
||||
dlog('\n🎤 ════════════════════════════════════════════════════════════════');
|
||||
dlog('🎤 [VoiceInput] MIC BUTTON CLICKED - WebSpeech Initialization Flow (V2)');
|
||||
dlog('🎤 ════════════════════════════════════════════════════════════════\n');
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log('🎤 [DEBUG] V2 flow triggered');
|
||||
dlog('🎤 [DEBUG] V2 flow triggered');
|
||||
}
|
||||
|
||||
// ⚠️ V2 로직은 여기에 구현됨
|
||||
@@ -2135,7 +2128,7 @@ const VoiceInputOverlay = ({
|
||||
inputFocus={inputFocused}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onMouseDown={() => {
|
||||
console.log('[InputSimple] 마우스 클릭 감지');
|
||||
dlog('[InputSimple] 마우스 클릭 감지');
|
||||
if (onTransitionToSearchInput) {
|
||||
onTransitionToSearchInput();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user