[251022] fix: VoiceInputOverlay FinalText processing updated
🕐 커밋 시간: 2025. 10. 22. 16:56:15 📊 변경 통계: • 총 파일: 7개 • 추가: +271줄 • 삭제: -198줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/actionTypes.js ~ com.twin.app.shoptime/src/actions/webSpeechActions.js ~ com.twin.app.shoptime/src/reducers/voiceReducer.js ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceDebugDashboard.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.module.less ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoicePromptScreen.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx (javascript): 🔄 Modified: clearAllTimers() 📄 com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoicePromptScreen.jsx (javascript): ✅ Added: createBubbleClickHandler() 🔄 Modified: SpotlightContainerDecorator() ❌ Deleted: handleBubbleClick() 🔧 주요 변경 내용: • 타입 시스템 안정성 강화 • 핵심 비즈니스 로직 개선
This commit is contained in:
@@ -2,227 +2,226 @@
|
||||
|
||||
export const types = {
|
||||
// panel actions
|
||||
PUSH_PANEL: "PUSH_PANEL",
|
||||
POP_PANEL: "POP_PANEL",
|
||||
UPDATE_PANEL: "UPDATE_PANEL",
|
||||
RESET_PANELS: "RESET_PANELS",
|
||||
PUSH_PANEL: 'PUSH_PANEL',
|
||||
POP_PANEL: 'POP_PANEL',
|
||||
UPDATE_PANEL: 'UPDATE_PANEL',
|
||||
RESET_PANELS: 'RESET_PANELS',
|
||||
|
||||
// device actions
|
||||
GET_AUTHENTICATION_CODE: "GET_AUTHENTICATION_CODE",
|
||||
REGISTER_DEVICE: "REGISTER_DEVICE",
|
||||
REGISTER_DEVICE_INFO: "REGISTER_DEVICE_INFO",
|
||||
GET_DEVICE_INFO: "GET_DEVICE_INFO",
|
||||
CLEAR_REGISTER_DEVICE_INFO: "CLEAR_REGISTER_DEVICE_INFO",
|
||||
REGISTER_DEVICE_RESET: "REGISTER_DEVICE_RESET",
|
||||
GET_AUTHENTICATION_CODE: 'GET_AUTHENTICATION_CODE',
|
||||
REGISTER_DEVICE: 'REGISTER_DEVICE',
|
||||
REGISTER_DEVICE_INFO: 'REGISTER_DEVICE_INFO',
|
||||
GET_DEVICE_INFO: 'GET_DEVICE_INFO',
|
||||
CLEAR_REGISTER_DEVICE_INFO: 'CLEAR_REGISTER_DEVICE_INFO',
|
||||
REGISTER_DEVICE_RESET: 'REGISTER_DEVICE_RESET',
|
||||
|
||||
// common actions
|
||||
GET_HTTP_HEADER: "GET_HTTP_HEADER",
|
||||
CHANGE_APP_STATUS: "CHANGE_APP_STATUS",
|
||||
SEND_BROADCAST: "SEND_BROADCAST",
|
||||
CHANGE_LOCAL_SETTINGS: "CHANGE_LOCAL_SETTINGS",
|
||||
GNB_OPENED: "GNB_OPENED",
|
||||
SET_SHOW_POPUP: "SET_SHOW_POPUP",
|
||||
SET_SHOW_SECONDARY_POPUP: "SET_SHOW_SECONDARY_POPUP",
|
||||
SET_HIDE_POPUP: "SET_HIDE_POPUP",
|
||||
SET_HIDE_SECONDARY_POPUP: "SET_HIDE_SECONDARY_POPUP",
|
||||
SHOW_OPTIONAL_TERMS_CONFIRM_POPUP: "SHOW_OPTIONAL_TERMS_CONFIRM_POPUP",
|
||||
HIDE_OPTIONAL_TERMS_CONFIRM_POPUP: "HIDE_OPTIONAL_TERMS_CONFIRM_POPUP",
|
||||
TOGGLE_OPTIONAL_TERMS_CONFIRM: "TOGGLE_OPTIONAL_TERMS_CONFIRM",
|
||||
GET_HTTP_HEADER: 'GET_HTTP_HEADER',
|
||||
CHANGE_APP_STATUS: 'CHANGE_APP_STATUS',
|
||||
SEND_BROADCAST: 'SEND_BROADCAST',
|
||||
CHANGE_LOCAL_SETTINGS: 'CHANGE_LOCAL_SETTINGS',
|
||||
GNB_OPENED: 'GNB_OPENED',
|
||||
SET_SHOW_POPUP: 'SET_SHOW_POPUP',
|
||||
SET_SHOW_SECONDARY_POPUP: 'SET_SHOW_SECONDARY_POPUP',
|
||||
SET_HIDE_POPUP: 'SET_HIDE_POPUP',
|
||||
SET_HIDE_SECONDARY_POPUP: 'SET_HIDE_SECONDARY_POPUP',
|
||||
SHOW_OPTIONAL_TERMS_CONFIRM_POPUP: 'SHOW_OPTIONAL_TERMS_CONFIRM_POPUP',
|
||||
HIDE_OPTIONAL_TERMS_CONFIRM_POPUP: 'HIDE_OPTIONAL_TERMS_CONFIRM_POPUP',
|
||||
TOGGLE_OPTIONAL_TERMS_CONFIRM: 'TOGGLE_OPTIONAL_TERMS_CONFIRM',
|
||||
// 선택약관 팝업 상태 관리
|
||||
SET_OPTIONAL_TERMS_POPUP_SHOWN: "SET_OPTIONAL_TERMS_POPUP_SHOWN",
|
||||
SET_OPTIONAL_TERMS_USER_DECISION: "SET_OPTIONAL_TERMS_USER_DECISION",
|
||||
RESET_OPTIONAL_TERMS_SESSION: "RESET_OPTIONAL_TERMS_SESSION",
|
||||
SET_OPTIONAL_TERMS_POPUP_SHOWN: 'SET_OPTIONAL_TERMS_POPUP_SHOWN',
|
||||
SET_OPTIONAL_TERMS_USER_DECISION: 'SET_OPTIONAL_TERMS_USER_DECISION',
|
||||
RESET_OPTIONAL_TERMS_SESSION: 'RESET_OPTIONAL_TERMS_SESSION',
|
||||
// 선택약관 직접 상태 업데이트 (API 호출 없이)
|
||||
UPDATE_OPTIONAL_TERMS_AGREE_DIRECT: "UPDATE_OPTIONAL_TERMS_AGREE_DIRECT",
|
||||
UPDATE_TERMS_AGREEMENT_STATUS_DIRECT: "UPDATE_TERMS_AGREEMENT_STATUS_DIRECT",
|
||||
SET_EXIT_APP: "SET_EXIT_APP",
|
||||
GET_LOGIN_USER_DATA: "GET_LOGIN_USER_DATA",
|
||||
GET_TERMS_AGREE_YN: "GET_TERMS_AGREE_YN",
|
||||
LAUNCH_MEMBERSHIP_APP: "LAUNCH_MEMBERSHIP_APP",
|
||||
SET_FOCUS: "SET_FOCUS",
|
||||
SET_GNB_MENU: "SET_GNB_MENU",
|
||||
SET_SYSTEM_NOTICE: "SET_SYSTEM_NOTICE",
|
||||
SET_SYSTEM_TERMINATION: "SET_SYSTEM_TERMINATION",
|
||||
SET_DEEP_LINK: "SET_DEEP_LINK",
|
||||
SET_SECOND_LAYER_INFO: "SET_SECOND_LAYER_INFO",
|
||||
SET_ERROR_MESSAGE: "SET_ERROR_MESSAGE",
|
||||
CLEAR_ERROR_MESSAGE: "CLEAR_ERROR_MESSAGE",
|
||||
GET_DEVICE_MACADDRESS: "GET_DEVICE_MACADDRESS",
|
||||
SET_DEVICE_REGISTERED: "SET_DEVICE_REGISTERED",
|
||||
UPDATE_OPTIONAL_TERMS_AGREE_DIRECT: 'UPDATE_OPTIONAL_TERMS_AGREE_DIRECT',
|
||||
UPDATE_TERMS_AGREEMENT_STATUS_DIRECT: 'UPDATE_TERMS_AGREEMENT_STATUS_DIRECT',
|
||||
SET_EXIT_APP: 'SET_EXIT_APP',
|
||||
GET_LOGIN_USER_DATA: 'GET_LOGIN_USER_DATA',
|
||||
GET_TERMS_AGREE_YN: 'GET_TERMS_AGREE_YN',
|
||||
LAUNCH_MEMBERSHIP_APP: 'LAUNCH_MEMBERSHIP_APP',
|
||||
SET_FOCUS: 'SET_FOCUS',
|
||||
SET_GNB_MENU: 'SET_GNB_MENU',
|
||||
SET_SYSTEM_NOTICE: 'SET_SYSTEM_NOTICE',
|
||||
SET_SYSTEM_TERMINATION: 'SET_SYSTEM_TERMINATION',
|
||||
SET_DEEP_LINK: 'SET_DEEP_LINK',
|
||||
SET_SECOND_LAYER_INFO: 'SET_SECOND_LAYER_INFO',
|
||||
SET_ERROR_MESSAGE: 'SET_ERROR_MESSAGE',
|
||||
CLEAR_ERROR_MESSAGE: 'CLEAR_ERROR_MESSAGE',
|
||||
GET_DEVICE_MACADDRESS: 'GET_DEVICE_MACADDRESS',
|
||||
SET_DEVICE_REGISTERED: 'SET_DEVICE_REGISTERED',
|
||||
|
||||
// billing actions
|
||||
GET_MY_INFO_BILLING_SEARCH: "GET_MY_INFO_BILLING_SEARCH",
|
||||
GET_MY_INFO_BILLING_SEARCH: 'GET_MY_INFO_BILLING_SEARCH',
|
||||
|
||||
// card actions
|
||||
GET_MY_INFO_CARD_SEARCH: "GET_MY_INFO_CARD_SEARCH",
|
||||
GET_MY_INFO_CARD_SEARCH: 'GET_MY_INFO_CARD_SEARCH',
|
||||
|
||||
// shipping actions
|
||||
GET_MY_INFO_SHIPPING_SEARCH: "GET_MY_INFO_SHIPPING_SEARCH",
|
||||
GET_MY_INFO_SHIPPING_SEARCH: 'GET_MY_INFO_SHIPPING_SEARCH',
|
||||
|
||||
// cart actions
|
||||
GET_MY_INFO_CART_SEARCH: "GET_MY_INFO_CART_SEARCH",
|
||||
ADD_TO_CART: "ADD_TO_CART",
|
||||
REMOVE_FROM_CART: "REMOVE_FROM_CART",
|
||||
UPDATE_CART_ITEM: "UPDATE_CART_ITEM",
|
||||
CLEAR_CART: "CLEAR_CART",
|
||||
GET_MY_INFO_CART_SEARCH: 'GET_MY_INFO_CART_SEARCH',
|
||||
ADD_TO_CART: 'ADD_TO_CART',
|
||||
REMOVE_FROM_CART: 'REMOVE_FROM_CART',
|
||||
UPDATE_CART_ITEM: 'UPDATE_CART_ITEM',
|
||||
CLEAR_CART: 'CLEAR_CART',
|
||||
|
||||
// appData actions
|
||||
ADD_MAIN_INDEX: "ADD_MAIN_INDEX",
|
||||
SEND_SMS: "SEND_SMS",
|
||||
CLEAR_SMS: "CLEAR_SMS",
|
||||
ADD_MAIN_INDEX: 'ADD_MAIN_INDEX',
|
||||
SEND_SMS: 'SEND_SMS',
|
||||
CLEAR_SMS: 'CLEAR_SMS',
|
||||
|
||||
// home actions
|
||||
GET_HOME_TERMS: "GET_HOME_TERMS",
|
||||
SET_TERMS_ID_MAP: "SET_TERMS_ID_MAP",
|
||||
SET_OPTIONAL_TERMS_AVAILABILITY: "SET_OPTIONAL_TERMS_AVAILABILITY",
|
||||
GET_HOME_MENU: "GET_HOME_MENU",
|
||||
GET_HOME_LAYOUT: "GET_HOME_LAYOUT",
|
||||
GET_HOME_MAIN_CONTENTS: "GET_HOME_MAIN_CONTENTS",
|
||||
GET_HOME_FULL_VIDEO_INFO: "GET_HOME_FULL_VIDEO_INFO",
|
||||
GET_THEME_CURATION_INFO: "GET_THEME_CURATION_INFO",
|
||||
GET_THEME_CURATION_DETAIL_INFO: "GET_THEME_CURATION_DETAIL_INFO",
|
||||
GET_THEME_HOTEL_DETAIL_INFO: "GET_THEME_HOTEL_DETAIL_INFO",
|
||||
GET_THEME_MENU_SHELF_INFO: "GET_THEME_MENU_SHELF_INFO",
|
||||
CLEAR_THEME_MENU_SHELF_INFO: "CLEAR_THEME_MENU_SHELF_INFO",
|
||||
CLEAR_THEME_DETAIL: "CLEAR_THEME_DETAIL",
|
||||
CHECK_ENTER_THROUGH_GNB: "CHECK_ENTER_THROUGH_GNB",
|
||||
SET_DEFAULT_FOCUS: "SET_DEFAULT_FOCUS",
|
||||
SET_BANNER_INDEX: "SET_BANNER_INDEX",
|
||||
RESET_HOME_INFO: "RESET_HOME_INFO",
|
||||
UPDATE_HOME_INFO: "UPDATE_HOME_INFO",
|
||||
GET_HOME_TERMS: 'GET_HOME_TERMS',
|
||||
SET_TERMS_ID_MAP: 'SET_TERMS_ID_MAP',
|
||||
SET_OPTIONAL_TERMS_AVAILABILITY: 'SET_OPTIONAL_TERMS_AVAILABILITY',
|
||||
GET_HOME_MENU: 'GET_HOME_MENU',
|
||||
GET_HOME_LAYOUT: 'GET_HOME_LAYOUT',
|
||||
GET_HOME_MAIN_CONTENTS: 'GET_HOME_MAIN_CONTENTS',
|
||||
GET_HOME_FULL_VIDEO_INFO: 'GET_HOME_FULL_VIDEO_INFO',
|
||||
GET_THEME_CURATION_INFO: 'GET_THEME_CURATION_INFO',
|
||||
GET_THEME_CURATION_DETAIL_INFO: 'GET_THEME_CURATION_DETAIL_INFO',
|
||||
GET_THEME_HOTEL_DETAIL_INFO: 'GET_THEME_HOTEL_DETAIL_INFO',
|
||||
GET_THEME_MENU_SHELF_INFO: 'GET_THEME_MENU_SHELF_INFO',
|
||||
CLEAR_THEME_MENU_SHELF_INFO: 'CLEAR_THEME_MENU_SHELF_INFO',
|
||||
CLEAR_THEME_DETAIL: 'CLEAR_THEME_DETAIL',
|
||||
CHECK_ENTER_THROUGH_GNB: 'CHECK_ENTER_THROUGH_GNB',
|
||||
SET_DEFAULT_FOCUS: 'SET_DEFAULT_FOCUS',
|
||||
SET_BANNER_INDEX: 'SET_BANNER_INDEX',
|
||||
RESET_HOME_INFO: 'RESET_HOME_INFO',
|
||||
UPDATE_HOME_INFO: 'UPDATE_HOME_INFO',
|
||||
|
||||
// brand actions
|
||||
GET_BRAND_LIST: "GET_BRAND_LIST",
|
||||
GET_BRAND_LAYOUT_INFO: "GET_BRAND_LAYOUT_INFO",
|
||||
GET_BRAND_LIVE_CHANNEL_INFO: "GET_BRAND_LIVE_CHANNEL_INFO",
|
||||
GET_BRAND_CHAN_INFO: "GET_BRAND_CHAN_INFO",
|
||||
GET_BRAND_TSV_INFO: "GET_BRAND_TSV_INFO",
|
||||
GET_BRAND_RECOMMENDED_SHOW_INFO: "GET_BRAND_RECOMMENDED_SHOW_INFO",
|
||||
GET_BRAND_SERIES_INFO: "GET_BRAND_SERIES_INFO",
|
||||
GET_BRAND_CATEGORY_INFO: "GET_BRAND_CATEGORY_INFO",
|
||||
GET_BRAND_CATEGORY_PRODUCT_INFO: "GET_BRAND_CATEGORY_PRODUCT_INFO",
|
||||
GET_BRAND_BEST_SELLER: "GET_BRAND_BEST_SELLER",
|
||||
GET_BRAND_CREATORS_INFO: "GET_BRAND_CREATORS_INFO",
|
||||
GET_BRAND_SHOWROOM: "GET_BRAND_SHOWROOM",
|
||||
GET_BRAND_RECENTLY_AIRED: "GET_BRAND_RECENTLY_AIRED",
|
||||
SET_BRAND_LIVE_CHANNEL_UPCOMING: "SET_BRAND_LIVE_CHANNEL_UPCOMING",
|
||||
SET_BRAND_CHAN_INFO: "SET_BRAND_CHAN_INFO",
|
||||
RESET_BRAND_STATE: "RESET_BRAND_STATE",
|
||||
RESET_BRAND_STATE_EXCEPT_BRAND_INFO: "RESET_BRAND_STATE_EXCEPT_BRAND_INFO",
|
||||
RESET_BRAND_LAYOUT_INFO: "RESET_BRAND_LAYOUT_INFO",
|
||||
GET_BRAND_LIST: 'GET_BRAND_LIST',
|
||||
GET_BRAND_LAYOUT_INFO: 'GET_BRAND_LAYOUT_INFO',
|
||||
GET_BRAND_LIVE_CHANNEL_INFO: 'GET_BRAND_LIVE_CHANNEL_INFO',
|
||||
GET_BRAND_CHAN_INFO: 'GET_BRAND_CHAN_INFO',
|
||||
GET_BRAND_TSV_INFO: 'GET_BRAND_TSV_INFO',
|
||||
GET_BRAND_RECOMMENDED_SHOW_INFO: 'GET_BRAND_RECOMMENDED_SHOW_INFO',
|
||||
GET_BRAND_SERIES_INFO: 'GET_BRAND_SERIES_INFO',
|
||||
GET_BRAND_CATEGORY_INFO: 'GET_BRAND_CATEGORY_INFO',
|
||||
GET_BRAND_CATEGORY_PRODUCT_INFO: 'GET_BRAND_CATEGORY_PRODUCT_INFO',
|
||||
GET_BRAND_BEST_SELLER: 'GET_BRAND_BEST_SELLER',
|
||||
GET_BRAND_CREATORS_INFO: 'GET_BRAND_CREATORS_INFO',
|
||||
GET_BRAND_SHOWROOM: 'GET_BRAND_SHOWROOM',
|
||||
GET_BRAND_RECENTLY_AIRED: 'GET_BRAND_RECENTLY_AIRED',
|
||||
SET_BRAND_LIVE_CHANNEL_UPCOMING: 'SET_BRAND_LIVE_CHANNEL_UPCOMING',
|
||||
SET_BRAND_CHAN_INFO: 'SET_BRAND_CHAN_INFO',
|
||||
RESET_BRAND_STATE: 'RESET_BRAND_STATE',
|
||||
RESET_BRAND_STATE_EXCEPT_BRAND_INFO: 'RESET_BRAND_STATE_EXCEPT_BRAND_INFO',
|
||||
RESET_BRAND_LAYOUT_INFO: 'RESET_BRAND_LAYOUT_INFO',
|
||||
|
||||
// main actions
|
||||
GET_SUB_CATEGORY: "GET_SUB_CATEGORY",
|
||||
APPEND_SUB_CATEGORY: "APPEND_SUB_CATEGORY",
|
||||
GET_TOP_20_SHOW: "GET_TOP_20_SHOW",
|
||||
GET_PRODUCT_DETAIL: "GET_PRODUCT_DETAIL",
|
||||
GET_YOUMAYLIKE: "GET_YOUMAYLIKE",
|
||||
SET_MAIN_LIKE_CATEGORY: "SET_MAIN_LIKE_CATEGORY",
|
||||
SET_MAIN_LIVE_UPCOMING_ALARM: "SET_MAIN_LIVE_UPCOMING_ALARM",
|
||||
GET_MAIN_LIVE_SHOW_NOW_PRODUCT: "GET_MAIN_LIVE_SHOW_NOW_PRODUCT",
|
||||
GET_MAIN_LIVE_SHOW: "GET_MAIN_LIVE_SHOW",
|
||||
GET_MAIN_CATEGORY_SHOW_DETAIL: "GET_MAIN_CATEGORY_SHOW_DETAIL",
|
||||
CLEAR_PRODUCT_DETAIL: "CLEAR_PRODUCT_DETAIL",
|
||||
CLEAR_SUB_CATEGORY: "CLEAR_SUB_CATEGORY",
|
||||
CLEAR_SHOPNOW_INFO: "CLEAR_SHOPNOW_INFO",
|
||||
GET_SUB_CATEGORY: 'GET_SUB_CATEGORY',
|
||||
APPEND_SUB_CATEGORY: 'APPEND_SUB_CATEGORY',
|
||||
GET_TOP_20_SHOW: 'GET_TOP_20_SHOW',
|
||||
GET_PRODUCT_DETAIL: 'GET_PRODUCT_DETAIL',
|
||||
GET_YOUMAYLIKE: 'GET_YOUMAYLIKE',
|
||||
SET_MAIN_LIKE_CATEGORY: 'SET_MAIN_LIKE_CATEGORY',
|
||||
SET_MAIN_LIVE_UPCOMING_ALARM: 'SET_MAIN_LIVE_UPCOMING_ALARM',
|
||||
GET_MAIN_LIVE_SHOW_NOW_PRODUCT: 'GET_MAIN_LIVE_SHOW_NOW_PRODUCT',
|
||||
GET_MAIN_LIVE_SHOW: 'GET_MAIN_LIVE_SHOW',
|
||||
GET_MAIN_CATEGORY_SHOW_DETAIL: 'GET_MAIN_CATEGORY_SHOW_DETAIL',
|
||||
CLEAR_PRODUCT_DETAIL: 'CLEAR_PRODUCT_DETAIL',
|
||||
CLEAR_SUB_CATEGORY: 'CLEAR_SUB_CATEGORY',
|
||||
CLEAR_SHOPNOW_INFO: 'CLEAR_SHOPNOW_INFO',
|
||||
|
||||
// myPage actions
|
||||
GET_MY_RECOMMANDED_KEYWORD: "GET_MY_RECOMMANDED_KEYWORD",
|
||||
GET_MY_FAQ_INFO: "GET_MY_FAQ_INFO",
|
||||
GET_NOTICE: "GET_NOTICE",
|
||||
GET_MY_CUSTOMERS: "GET_MY_CUSTOMERS",
|
||||
GET_MY_FAVORITE: "GET_MY_FAVORITE",
|
||||
DELETE_MY_FAVORITE: "DELETE_MY_FAVORITE",
|
||||
GET_MY_FAVORITE_FLAG: "GET_MY_FAVORITE_FLAG",
|
||||
SET_MY_TERMS_WITHDRAW: "SET_MY_TERMS_WITHDRAW",
|
||||
GET_MY_UPCOMING_CHANGE_INFO: "GET_MY_UPCOMING_CHANGE_INFO",
|
||||
GET_MY_UPCOMING_ALERT_SHOW: "GET_MY_UPCOMING_ALERT_SHOW",
|
||||
DELETE_MY_UPCOMING_ALERT_SHOW: "DELETE_MY_UPCOMING_ALERT_SHOW",
|
||||
GET_MY_UPCOMING_ALERT_SHOW_KEYS: "GET_MY_UPCOMING_ALERT_SHOW_KEYS",
|
||||
SET_MY_UPCOMING_USE_ALERT: "SET_MY_UPCOMING_USE_ALERT",
|
||||
GET_UPCOMING_ALERT_SHOW_CHANGE_INFO: "GET_UPCOMING_ALERT_SHOW_CHANGE_INFO",
|
||||
GET_MY_RECENTLY_VIEWED_INFO: "GET_MY_RECENTLY_VIEWED_INFO",
|
||||
CLEAR_RECENTLY_VIEWED_INFO: "CLEAR_RECENTLY_VIEWED_INFO",
|
||||
CLEAR_FAVORITES: "CLEAR_FAVORITES",
|
||||
SET_MYPAGE_TERMS_AGREE: "SET_MYPAGE_TERMS_AGREE",
|
||||
SET_MYPAGE_TERMS_AGREE_SUCCESS: "SET_MYPAGE_TERMS_AGREE_SUCCESS",
|
||||
SET_MYPAGE_TERMS_AGREE_FAIL: "SET_MYPAGE_TERMS_AGREE_FAIL",
|
||||
GET_MY_RECOMMANDED_KEYWORD: 'GET_MY_RECOMMANDED_KEYWORD',
|
||||
GET_MY_FAQ_INFO: 'GET_MY_FAQ_INFO',
|
||||
GET_NOTICE: 'GET_NOTICE',
|
||||
GET_MY_CUSTOMERS: 'GET_MY_CUSTOMERS',
|
||||
GET_MY_FAVORITE: 'GET_MY_FAVORITE',
|
||||
DELETE_MY_FAVORITE: 'DELETE_MY_FAVORITE',
|
||||
GET_MY_FAVORITE_FLAG: 'GET_MY_FAVORITE_FLAG',
|
||||
SET_MY_TERMS_WITHDRAW: 'SET_MY_TERMS_WITHDRAW',
|
||||
GET_MY_UPCOMING_CHANGE_INFO: 'GET_MY_UPCOMING_CHANGE_INFO',
|
||||
GET_MY_UPCOMING_ALERT_SHOW: 'GET_MY_UPCOMING_ALERT_SHOW',
|
||||
DELETE_MY_UPCOMING_ALERT_SHOW: 'DELETE_MY_UPCOMING_ALERT_SHOW',
|
||||
GET_MY_UPCOMING_ALERT_SHOW_KEYS: 'GET_MY_UPCOMING_ALERT_SHOW_KEYS',
|
||||
SET_MY_UPCOMING_USE_ALERT: 'SET_MY_UPCOMING_USE_ALERT',
|
||||
GET_UPCOMING_ALERT_SHOW_CHANGE_INFO: 'GET_UPCOMING_ALERT_SHOW_CHANGE_INFO',
|
||||
GET_MY_RECENTLY_VIEWED_INFO: 'GET_MY_RECENTLY_VIEWED_INFO',
|
||||
CLEAR_RECENTLY_VIEWED_INFO: 'CLEAR_RECENTLY_VIEWED_INFO',
|
||||
CLEAR_FAVORITES: 'CLEAR_FAVORITES',
|
||||
SET_MYPAGE_TERMS_AGREE: 'SET_MYPAGE_TERMS_AGREE',
|
||||
SET_MYPAGE_TERMS_AGREE_SUCCESS: 'SET_MYPAGE_TERMS_AGREE_SUCCESS',
|
||||
SET_MYPAGE_TERMS_AGREE_FAIL: 'SET_MYPAGE_TERMS_AGREE_FAIL',
|
||||
|
||||
// onSale actions
|
||||
GET_HOME_ON_SALE_INFO: "GET_HOME_ON_SALE_INFO",
|
||||
GET_ON_SALE_INFO: "GET_ON_SALE_INFO",
|
||||
COPY_CATEGORY_INFO: "COPY_CATEGORY_INFO",
|
||||
RESET_ON_SALE_STATE: "RESET_ON_SALE_STATE",
|
||||
GET_HOME_ON_SALE_INFO: 'GET_HOME_ON_SALE_INFO',
|
||||
GET_ON_SALE_INFO: 'GET_ON_SALE_INFO',
|
||||
COPY_CATEGORY_INFO: 'COPY_CATEGORY_INFO',
|
||||
RESET_ON_SALE_STATE: 'RESET_ON_SALE_STATE',
|
||||
|
||||
// product actions
|
||||
GET_BEST_SELLER: "GET_BEST_SELLER",
|
||||
GET_PRODUCT_GROUP: "GET_PRODUCT_GROUP",
|
||||
GET_PRODUCT_OPTION: "GET_PRODUCT_OPTION",
|
||||
GET_PRODUCT_IMAGE_LENGTH: "GET_PRODUCT_IMAGE_LENGTH",
|
||||
GET_VIDEO_INDECATOR_FOCUS: "GET_VIDEO_INDECATOR_FOCUS",
|
||||
GET_PRODUCT_OPTION_ID: "GET_PRODUCT_OPTION_ID",
|
||||
CLEAR_PRODUCT_OPTIONS: "CLEAR_PRODUCT_OPTIONS",
|
||||
GET_USER_REVIEW: "GET_USER_REVIEW",
|
||||
TOGGLE_SHOW_ALL_REVIEWS: "TOGGLE_SHOW_ALL_REVIEWS",
|
||||
RESET_SHOW_ALL_REVIEWS: "RESET_SHOW_ALL_REVIEWS",
|
||||
GET_BEST_SELLER: 'GET_BEST_SELLER',
|
||||
GET_PRODUCT_GROUP: 'GET_PRODUCT_GROUP',
|
||||
GET_PRODUCT_OPTION: 'GET_PRODUCT_OPTION',
|
||||
GET_PRODUCT_IMAGE_LENGTH: 'GET_PRODUCT_IMAGE_LENGTH',
|
||||
GET_VIDEO_INDECATOR_FOCUS: 'GET_VIDEO_INDECATOR_FOCUS',
|
||||
GET_PRODUCT_OPTION_ID: 'GET_PRODUCT_OPTION_ID',
|
||||
CLEAR_PRODUCT_OPTIONS: 'CLEAR_PRODUCT_OPTIONS',
|
||||
GET_USER_REVIEW: 'GET_USER_REVIEW',
|
||||
TOGGLE_SHOW_ALL_REVIEWS: 'TOGGLE_SHOW_ALL_REVIEWS',
|
||||
RESET_SHOW_ALL_REVIEWS: 'RESET_SHOW_ALL_REVIEWS',
|
||||
|
||||
// search actions
|
||||
|
||||
GET_SEARCH: "GET_SEARCH",
|
||||
GET_SEARCH_MAIN: "GET_SEARCH_MAIN",
|
||||
CLEAR_SEARCH_MAIN_DATA: "CLEAR_SEARCH_MAIN_DATA",
|
||||
GET_SHOPPERHOUSE_SEARCH: "GET_SHOPPERHOUSE_SEARCH",
|
||||
CLEAR_SHOPPERHOUSE_DATA: "CLEAR_SHOPPERHOUSE_DATA",
|
||||
RESET_SEARCH: "RESET_SEARCH",
|
||||
RESET_VOICE_SEARCH: "RESET_VOICE_SEARCH",
|
||||
GET_SEARCH_PROCESSED: "GET_SEARCH_PROCESSED",
|
||||
SET_SEARCH_INIT_PERFORMED: "SET_SEARCH_INIT_PERFORMED",
|
||||
UPDATE_SEARCH_TIMESTAMP: "UPDATE_SEARCH_TIMESTAMP",
|
||||
|
||||
GET_SEARCH: 'GET_SEARCH',
|
||||
GET_SEARCH_MAIN: 'GET_SEARCH_MAIN',
|
||||
CLEAR_SEARCH_MAIN_DATA: 'CLEAR_SEARCH_MAIN_DATA',
|
||||
GET_SHOPPERHOUSE_SEARCH: 'GET_SHOPPERHOUSE_SEARCH',
|
||||
CLEAR_SHOPPERHOUSE_DATA: 'CLEAR_SHOPPERHOUSE_DATA',
|
||||
RESET_SEARCH: 'RESET_SEARCH',
|
||||
RESET_VOICE_SEARCH: 'RESET_VOICE_SEARCH',
|
||||
GET_SEARCH_PROCESSED: 'GET_SEARCH_PROCESSED',
|
||||
SET_SEARCH_INIT_PERFORMED: 'SET_SEARCH_INIT_PERFORMED',
|
||||
UPDATE_SEARCH_TIMESTAMP: 'UPDATE_SEARCH_TIMESTAMP',
|
||||
SET_SHOPPERHOUSE_ERROR: 'SET_SHOPPERHOUSE_ERROR',
|
||||
|
||||
|
||||
// event actions
|
||||
GET_WELCOME_EVENT_INFO: "GET_WELCOME_EVENT_INFO",
|
||||
GET_EVENT_ISSUED_STATUS: "GET_EVENT_ISSUED_STATUS",
|
||||
SET_EVENT_ISSUE_REQ: "SET_EVENT_ISSUE_REQ",
|
||||
SET_EVENT_POP_CLICK_INFO: "SET_EVENT_POP_CLICK_INFO",
|
||||
CLEAR_EVENT_INFO: "CLEAR_EVENT_INFO",
|
||||
SKIP_CLICK_INFO: "SKIP_CLICK_INFO",
|
||||
CLEAR_CURATION_COUPON: "CLEAR_CURATION_COUPON",
|
||||
CLEAR_EVENT_ISSUED_STATUS_SUCCEESS: "CLEAR_EVENT_ISSUED_STATUS_SUCCEESS",
|
||||
GET_WELCOME_EVENT_INFO: 'GET_WELCOME_EVENT_INFO',
|
||||
GET_EVENT_ISSUED_STATUS: 'GET_EVENT_ISSUED_STATUS',
|
||||
SET_EVENT_ISSUE_REQ: 'SET_EVENT_ISSUE_REQ',
|
||||
SET_EVENT_POP_CLICK_INFO: 'SET_EVENT_POP_CLICK_INFO',
|
||||
CLEAR_EVENT_INFO: 'CLEAR_EVENT_INFO',
|
||||
SKIP_CLICK_INFO: 'SKIP_CLICK_INFO',
|
||||
CLEAR_CURATION_COUPON: 'CLEAR_CURATION_COUPON',
|
||||
CLEAR_EVENT_ISSUED_STATUS_SUCCEESS: 'CLEAR_EVENT_ISSUED_STATUS_SUCCEESS',
|
||||
|
||||
// coupon actions
|
||||
GET_PRODUCT_COUPON_INFO: "GET_PRODUCT_COUPON_INFO",
|
||||
GET_PRODUCT_COUPON_SEARCH: "GET_PRODUCT_COUPON_SEARCH",
|
||||
GET_PRODUCT_COUPON_DOWNLOAD: "GET_PRODUCT_COUPON_DOWNLOAD",
|
||||
GET_PRODUCT_COUPON_TOTDOWNLOAD: "GET_PRODUCT_COUPON_TOTDOWNLOAD",
|
||||
CLEAR_COUPON_INFO: "CLEAR_COUPON_INFO",
|
||||
CLEAR_GET_PRODUCT_COUPON_DOWNLOAD: "CLEAR_GET_PRODUCT_COUPON_DOWNLOAD",
|
||||
GET_PRODUCT_COUPON_INFO: 'GET_PRODUCT_COUPON_INFO',
|
||||
GET_PRODUCT_COUPON_SEARCH: 'GET_PRODUCT_COUPON_SEARCH',
|
||||
GET_PRODUCT_COUPON_DOWNLOAD: 'GET_PRODUCT_COUPON_DOWNLOAD',
|
||||
GET_PRODUCT_COUPON_TOTDOWNLOAD: 'GET_PRODUCT_COUPON_TOTDOWNLOAD',
|
||||
CLEAR_COUPON_INFO: 'CLEAR_COUPON_INFO',
|
||||
CLEAR_GET_PRODUCT_COUPON_DOWNLOAD: 'CLEAR_GET_PRODUCT_COUPON_DOWNLOAD',
|
||||
|
||||
// checkout actions
|
||||
GET_CHECKOUT_INFO: "GET_CHECKOUT_INFO",
|
||||
INSERT_MY_INFO_CHECKOUT_ORDER: "INSERT_MY_INFO_CHECKOUT_ORDER",
|
||||
GET_TAX_INFOS: "GET_TAX_INFOS",
|
||||
UPDATE_SELECTED_SHIPPING_ADDR: "UPDATE_SELECTED_SHIPPING_ADDR",
|
||||
UPDATE_SELECTED_BILLING_ADDR: "UPDATE_SELECTED_BILLING_ADDR",
|
||||
UPDATE_SELECTED_COUPON: "UPDATE_SELECTED_COUPON",
|
||||
GET_CHECKOUT_TOTAL_AMT: "GET_CHECKOUT_TOTAL_AMT",
|
||||
CHECKOUT_DATA_RESET: "CHECKOUT_DATA_RESET",
|
||||
GET_CHECKOUT_INFO: 'GET_CHECKOUT_INFO',
|
||||
INSERT_MY_INFO_CHECKOUT_ORDER: 'INSERT_MY_INFO_CHECKOUT_ORDER',
|
||||
GET_TAX_INFOS: 'GET_TAX_INFOS',
|
||||
UPDATE_SELECTED_SHIPPING_ADDR: 'UPDATE_SELECTED_SHIPPING_ADDR',
|
||||
UPDATE_SELECTED_BILLING_ADDR: 'UPDATE_SELECTED_BILLING_ADDR',
|
||||
UPDATE_SELECTED_COUPON: 'UPDATE_SELECTED_COUPON',
|
||||
GET_CHECKOUT_TOTAL_AMT: 'GET_CHECKOUT_TOTAL_AMT',
|
||||
CHECKOUT_DATA_RESET: 'CHECKOUT_DATA_RESET',
|
||||
|
||||
// order actions
|
||||
SET_PURCHASE_TERMS_AGREE: "SET_PURCHASE_TERMS_AGREE",
|
||||
SET_PURCHASE_TERMS_WITHDRAW: "SET_PURCHASE_TERMS_WITHDRAW",
|
||||
GET_MY_INFO_ORDER_SEARCH: "GET_MY_INFO_ORDER_SEARCH",
|
||||
GET_MY_INFO_ORDER_SEARCH_CANCEL: "GET_MY_INFO_ORDER_SEARCH_CANCEL",
|
||||
GET_MY_INFO_ORDER_DETAIL_SEARCH: "GET_MY_INFO_ORDER_DETAIL_SEARCH",
|
||||
GET_MY_INFO_ORDER_SHIPPING_SEARCH: "GET_MY_INFO_ORDER_SHIPPING_SEARCH",
|
||||
CLEAR_MY_INFO_ORDER_SEARCH: "CLEAR_MY_INFO_ORDER_SEARCH",
|
||||
SET_PURCHASE_TERMS_AGREE: 'SET_PURCHASE_TERMS_AGREE',
|
||||
SET_PURCHASE_TERMS_WITHDRAW: 'SET_PURCHASE_TERMS_WITHDRAW',
|
||||
GET_MY_INFO_ORDER_SEARCH: 'GET_MY_INFO_ORDER_SEARCH',
|
||||
GET_MY_INFO_ORDER_SEARCH_CANCEL: 'GET_MY_INFO_ORDER_SEARCH_CANCEL',
|
||||
GET_MY_INFO_ORDER_DETAIL_SEARCH: 'GET_MY_INFO_ORDER_DETAIL_SEARCH',
|
||||
GET_MY_INFO_ORDER_SHIPPING_SEARCH: 'GET_MY_INFO_ORDER_SHIPPING_SEARCH',
|
||||
CLEAR_MY_INFO_ORDER_SEARCH: 'CLEAR_MY_INFO_ORDER_SEARCH',
|
||||
|
||||
// play controller
|
||||
GET_CHAT_LOG: "GET_CHAT_LOG",
|
||||
GET_SUBTITLE: "GET_SUBTITLE",
|
||||
CLEAR_PLAYER_INFO: "CLEAR_PLAYER_INFO",
|
||||
UPDATE_VIDEO_PLAY_STATE: "UPDATE_VIDEO_PLAY_STATE",
|
||||
GET_CHAT_LOG: 'GET_CHAT_LOG',
|
||||
GET_SUBTITLE: 'GET_SUBTITLE',
|
||||
CLEAR_PLAYER_INFO: 'CLEAR_PLAYER_INFO',
|
||||
UPDATE_VIDEO_PLAY_STATE: 'UPDATE_VIDEO_PLAY_STATE',
|
||||
|
||||
// 🔽 [추가] 플레이 제어 매니저 액션 타입
|
||||
/**
|
||||
@@ -233,89 +232,89 @@ export const types = {
|
||||
* SET_PLAYER_CONTROL: 특정 컴포넌트에게 비디오 재생 제어권을 부여합니다.
|
||||
* CLEAR_PLAYER_CONTROL: 컴포넌트로부터 비디오 재생 제어권을 회수합니다.
|
||||
*/
|
||||
SET_PLAYER_CONTROL: "SET_PLAYER_CONTROL",
|
||||
CLEAR_PLAYER_CONTROL: "CLEAR_PLAYER_CONTROL",
|
||||
SET_PLAYER_CONTROL: 'SET_PLAYER_CONTROL',
|
||||
CLEAR_PLAYER_CONTROL: 'CLEAR_PLAYER_CONTROL',
|
||||
|
||||
// reset action
|
||||
RESET_REDUX_STATE: "RESET_REDUX_STATE",
|
||||
RESET_REDUX_STATE: 'RESET_REDUX_STATE',
|
||||
|
||||
// cancel actions
|
||||
GET_MY_INFO_ORDER_CANCEL_SEARCH: "GET_MY_INFO_ORDER_CANCEL_SEARCH",
|
||||
GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH:
|
||||
"GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH",
|
||||
UPDATE_ORDER_PARTIAL_CANCEL: "UPDATE_ORDER_PARTIAL_CANCEL",
|
||||
PAYMENT_TOTAL_CANCEL: "PAYMENT_TOTAL_CANCEL",
|
||||
GET_MY_INFO_ORDER_CANCEL_SEARCH: 'GET_MY_INFO_ORDER_CANCEL_SEARCH',
|
||||
GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH: 'GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH',
|
||||
UPDATE_ORDER_PARTIAL_CANCEL: 'UPDATE_ORDER_PARTIAL_CANCEL',
|
||||
PAYMENT_TOTAL_CANCEL: 'PAYMENT_TOTAL_CANCEL',
|
||||
|
||||
// emp actions
|
||||
GET_SHOPTIME_TERMS: "GET_SHOPTIME_TERMS",
|
||||
GET_SHOPTIME_TERMS: 'GET_SHOPTIME_TERMS',
|
||||
|
||||
// pinCode actions
|
||||
GET_MY_INFO_CARD_PINCODE_CHECK: "GET_MY_INFO_CARD_PINCODE_CHECK",
|
||||
GET_MY_INFO_CARD_PINCODE_CHECK: 'GET_MY_INFO_CARD_PINCODE_CHECK',
|
||||
|
||||
// new actions
|
||||
CANCEL_FOCUS_ELEMENT: "CANCEL_FOCUS_ELEMENT",
|
||||
CANCEL_FOCUS_ELEMENT: 'CANCEL_FOCUS_ELEMENT',
|
||||
|
||||
// 약관동의 여부 확인 상태
|
||||
GET_TERMS_AGREE_YN_START: "GET_TERMS_AGREE_YN_START",
|
||||
GET_TERMS_AGREE_YN_SUCCESS: "GET_TERMS_AGREE_YN_SUCCESS",
|
||||
GET_TERMS_AGREE_YN_FAILURE: "GET_TERMS_AGREE_YN_FAILURE",
|
||||
GET_TERMS_AGREE_YN_START: 'GET_TERMS_AGREE_YN_START',
|
||||
GET_TERMS_AGREE_YN_SUCCESS: 'GET_TERMS_AGREE_YN_SUCCESS',
|
||||
GET_TERMS_AGREE_YN_FAILURE: 'GET_TERMS_AGREE_YN_FAILURE',
|
||||
|
||||
// device
|
||||
REQ_REG_DEVICE_INFO: "REQ_REG_DEVICE_INFO",
|
||||
REQ_REG_DEVICE_INFO: 'REQ_REG_DEVICE_INFO',
|
||||
|
||||
// 🔽 [추가] 영구재생 비디오 정보 저장
|
||||
SET_PERSISTENT_VIDEO_INFO: "SET_PERSISTENT_VIDEO_INFO",
|
||||
SET_PERSISTENT_VIDEO_INFO: 'SET_PERSISTENT_VIDEO_INFO',
|
||||
|
||||
// 🔽 [추가] 배너 비디오 제어 액션 타입
|
||||
/**
|
||||
* HomeBanner의 배너 간 비디오 재생 제어를 위한 액션 타입들.
|
||||
* 첫 번째 배너 상시 재생과 두 번째 배너 포커스 재생을 관리합니다.
|
||||
*/
|
||||
SET_BANNER_STATE: "SET_BANNER_STATE",
|
||||
SET_BANNER_FOCUS: "SET_BANNER_FOCUS",
|
||||
SET_BANNER_AVAILABILITY: "SET_BANNER_AVAILABILITY",
|
||||
SET_BANNER_TRANSITION: "SET_BANNER_TRANSITION",
|
||||
PAUSE_PLAYER_CONTROL: "PAUSE_PLAYER_CONTROL",
|
||||
RESUME_PLAYER_CONTROL: "RESUME_PLAYER_CONTROL",
|
||||
SET_BANNER_STATE: 'SET_BANNER_STATE',
|
||||
SET_BANNER_FOCUS: 'SET_BANNER_FOCUS',
|
||||
SET_BANNER_AVAILABILITY: 'SET_BANNER_AVAILABILITY',
|
||||
SET_BANNER_TRANSITION: 'SET_BANNER_TRANSITION',
|
||||
PAUSE_PLAYER_CONTROL: 'PAUSE_PLAYER_CONTROL',
|
||||
RESUME_PLAYER_CONTROL: 'RESUME_PLAYER_CONTROL',
|
||||
// 🔽 [추가] HomeBanner 동영상 포커스 정책 관리
|
||||
SET_CURRENT_FOCUS_BANNER: "SET_CURRENT_FOCUS_BANNER",
|
||||
UPDATE_VIDEO_POLICY: "UPDATE_VIDEO_POLICY",
|
||||
SET_MODAL_BORDER: "SET_MODAL_BORDER",
|
||||
SET_BANNER_VISIBILITY: "SET_BANNER_VISIBILITY",
|
||||
SET_CURRENT_FOCUS_BANNER: 'SET_CURRENT_FOCUS_BANNER',
|
||||
UPDATE_VIDEO_POLICY: 'UPDATE_VIDEO_POLICY',
|
||||
SET_MODAL_BORDER: 'SET_MODAL_BORDER',
|
||||
SET_BANNER_VISIBILITY: 'SET_BANNER_VISIBILITY',
|
||||
|
||||
// 🔽 [추가] JustForYou 상품 관리 부분
|
||||
GET_RECENTLY_SAW_ITEM: "GET_RECENTLY_SAW_ITEM",
|
||||
GET_LIKE_BRAND_PRODUCT: "GET_LIKE_BRAND_PRODUCT",
|
||||
GET_MORE_TO_CONCIDER_AT_THIS_PRICE: "GET_MORE_TO_CONCIDER_AT_THIS_PRICE",
|
||||
GET_JUSTFORYOU_INFO: "GET_JUSTFORYOU_INFO",
|
||||
GET_RECENTLY_SAW_ITEM: 'GET_RECENTLY_SAW_ITEM',
|
||||
GET_LIKE_BRAND_PRODUCT: 'GET_LIKE_BRAND_PRODUCT',
|
||||
GET_MORE_TO_CONCIDER_AT_THIS_PRICE: 'GET_MORE_TO_CONCIDER_AT_THIS_PRICE',
|
||||
GET_JUSTFORYOU_INFO: 'GET_JUSTFORYOU_INFO',
|
||||
|
||||
// 🔽 Voice Conductor 관련 액션 타입
|
||||
VOICE_REGISTER_SUCCESS: "VOICE_REGISTER_SUCCESS",
|
||||
VOICE_REGISTER_FAILURE: "VOICE_REGISTER_FAILURE",
|
||||
VOICE_SET_TICKET: "VOICE_SET_TICKET",
|
||||
VOICE_SET_CONTEXT_SUCCESS: "VOICE_SET_CONTEXT_SUCCESS",
|
||||
VOICE_SET_CONTEXT_FAILURE: "VOICE_SET_CONTEXT_FAILURE",
|
||||
VOICE_PERFORM_ACTION: "VOICE_PERFORM_ACTION",
|
||||
VOICE_REPORT_RESULT_SUCCESS: "VOICE_REPORT_RESULT_SUCCESS",
|
||||
VOICE_REPORT_RESULT_FAILURE: "VOICE_REPORT_RESULT_FAILURE",
|
||||
VOICE_UPDATE_INTENTS: "VOICE_UPDATE_INTENTS",
|
||||
VOICE_CLEAR_STATE: "VOICE_CLEAR_STATE",
|
||||
VOICE_ADD_LOG: "VOICE_ADD_LOG",
|
||||
VOICE_CLEAR_LOGS: "VOICE_CLEAR_LOGS",
|
||||
VOICE_STT_TEXT_RECEIVED: "VOICE_STT_TEXT_RECEIVED", // STT 텍스트 수신
|
||||
VOICE_REGISTER_SUCCESS: 'VOICE_REGISTER_SUCCESS',
|
||||
VOICE_REGISTER_FAILURE: 'VOICE_REGISTER_FAILURE',
|
||||
VOICE_SET_TICKET: 'VOICE_SET_TICKET',
|
||||
VOICE_SET_CONTEXT_SUCCESS: 'VOICE_SET_CONTEXT_SUCCESS',
|
||||
VOICE_SET_CONTEXT_FAILURE: 'VOICE_SET_CONTEXT_FAILURE',
|
||||
VOICE_PERFORM_ACTION: 'VOICE_PERFORM_ACTION',
|
||||
VOICE_REPORT_RESULT_SUCCESS: 'VOICE_REPORT_RESULT_SUCCESS',
|
||||
VOICE_REPORT_RESULT_FAILURE: 'VOICE_REPORT_RESULT_FAILURE',
|
||||
VOICE_UPDATE_INTENTS: 'VOICE_UPDATE_INTENTS',
|
||||
VOICE_CLEAR_STATE: 'VOICE_CLEAR_STATE',
|
||||
VOICE_ADD_LOG: 'VOICE_ADD_LOG',
|
||||
VOICE_CLEAR_LOGS: 'VOICE_CLEAR_LOGS',
|
||||
VOICE_STT_TEXT_RECEIVED: 'VOICE_STT_TEXT_RECEIVED', // STT 텍스트 수신
|
||||
|
||||
// 🔽 Web Speech API 관련
|
||||
WEB_SPEECH_INITIALIZED: "WEB_SPEECH_INITIALIZED",
|
||||
WEB_SPEECH_START: "WEB_SPEECH_START",
|
||||
WEB_SPEECH_INTERIM_RESULT: "WEB_SPEECH_INTERIM_RESULT",
|
||||
WEB_SPEECH_END: "WEB_SPEECH_END",
|
||||
WEB_SPEECH_ERROR: "WEB_SPEECH_ERROR",
|
||||
WEB_SPEECH_CLEANUP: "WEB_SPEECH_CLEANUP",
|
||||
WEB_SPEECH_INITIALIZED: 'WEB_SPEECH_INITIALIZED',
|
||||
WEB_SPEECH_START: 'WEB_SPEECH_START',
|
||||
WEB_SPEECH_INTERIM_RESULT: 'WEB_SPEECH_INTERIM_RESULT',
|
||||
WEB_SPEECH_FINAL_RESULT: 'WEB_SPEECH_FINAL_RESULT', // ⭐ TV 환경 대응을 위한 final result
|
||||
WEB_SPEECH_END: 'WEB_SPEECH_END',
|
||||
WEB_SPEECH_ERROR: 'WEB_SPEECH_ERROR',
|
||||
WEB_SPEECH_CLEANUP: 'WEB_SPEECH_CLEANUP',
|
||||
|
||||
//convert img
|
||||
CONVERT_PDF_TO_IMAGE: "CONVERT_PDF_TO_IMAGE",
|
||||
CONVERT_PDF_TO_IMAGE_SUCCESS: "CONVERT_PDF_TO_IMAGE_SUCCESS",
|
||||
CONVERT_PDF_TO_IMAGE_FAILURE: "CONVERT_PDF_TO_IMAGE_FAILURE",
|
||||
CLEAR_CONVERTED_IMAGE: "CLEAR_CONVERTED_IMAGE",
|
||||
CLEAR_CONVERTED_IMAGE_BY_URL: "CLEAR_CONVERTED_IMAGE_BY_URL", // ⭐ 새로 추가
|
||||
CONVERT_PDF_TO_IMAGE: 'CONVERT_PDF_TO_IMAGE',
|
||||
CONVERT_PDF_TO_IMAGE_SUCCESS: 'CONVERT_PDF_TO_IMAGE_SUCCESS',
|
||||
CONVERT_PDF_TO_IMAGE_FAILURE: 'CONVERT_PDF_TO_IMAGE_FAILURE',
|
||||
CLEAR_CONVERTED_IMAGE: 'CLEAR_CONVERTED_IMAGE',
|
||||
CLEAR_CONVERTED_IMAGE_BY_URL: 'CLEAR_CONVERTED_IMAGE_BY_URL', // ⭐ 새로 추가
|
||||
};
|
||||
|
||||
@@ -50,19 +50,29 @@ export const initializeWebSpeech =
|
||||
|
||||
webSpeechService.on('result', (result) => {
|
||||
console.log(
|
||||
`[VoiceInput]-[WebSpeech] ACTION-EVENT: WEB_SPEECH_INTERIM_RESULT 디스패치 - isFinal=${result.isFinal}, text="${result.transcript}"`
|
||||
`[VoiceInput]-[WebSpeech] ACTION-EVENT: result 수신 - isFinal=${result.isFinal}, text="${result.transcript}"`
|
||||
);
|
||||
|
||||
// ✅ continuous: true 모드에서는 중간 final result를 무시하고
|
||||
// 항상 interim result만 사용 (15초 타이머로 제어)
|
||||
// Interim 결과 (중간 결과) - 전체 연결된 텍스트 사용
|
||||
// Interim 결과 (중간 결과) - 항상 디스패치하여 실시간 표시
|
||||
dispatch({
|
||||
type: types.WEB_SPEECH_INTERIM_RESULT,
|
||||
payload: result.transcript, // 이미 전체 연결된 텍스트 (final + interim)
|
||||
payload: result.transcript, // 전체 연결된 텍스트 (final + interim)
|
||||
});
|
||||
|
||||
// ✅ Final 결과는 무시 (15초 타이머가 끝날 때 VoiceInputOverlay에서 처리)
|
||||
// continuous: true일 때 중간에 final이 와도 계속 듣기
|
||||
// ✅ Final 결과 처리 추가 (TV 환경 대응)
|
||||
// TV에서는 final result가 와야 API 호출이 가능할 수 있음
|
||||
if (result.isFinal) {
|
||||
console.log(
|
||||
`[VoiceInput]-[WebSpeech] ACTION-EVENT: WEB_SPEECH_FINAL_RESULT 디스패치 - finalText="${result.transcript}"`
|
||||
);
|
||||
dispatch({
|
||||
type: types.WEB_SPEECH_FINAL_RESULT,
|
||||
payload: {
|
||||
finalText: result.transcript,
|
||||
confidence: result.confidence,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
webSpeechService.on('error', (errorInfo) => {
|
||||
|
||||
@@ -36,6 +36,8 @@ const initialState = {
|
||||
isInitialized: false,
|
||||
isListening: false,
|
||||
interimText: null,
|
||||
finalText: null, // ⭐ TV 환경 대응을 위한 final text
|
||||
confidence: null,
|
||||
error: null,
|
||||
},
|
||||
};
|
||||
@@ -159,6 +161,8 @@ export const voiceReducer = (state = initialState, action) => {
|
||||
...state.webSpeech,
|
||||
isListening: true,
|
||||
interimText: null,
|
||||
finalText: null, // ⭐ 새로운 인식 시작 시 초기화
|
||||
confidence: null,
|
||||
error: null,
|
||||
},
|
||||
};
|
||||
@@ -172,13 +176,29 @@ export const voiceReducer = (state = initialState, action) => {
|
||||
},
|
||||
};
|
||||
|
||||
case types.WEB_SPEECH_FINAL_RESULT:
|
||||
// ⭐ TV 환경 대응: Final result 처리
|
||||
return {
|
||||
...state,
|
||||
webSpeech: {
|
||||
...state.webSpeech,
|
||||
finalText: action.payload.finalText,
|
||||
confidence: action.payload.confidence,
|
||||
interimText: action.payload.finalText, // final이 오면 interim도 업데이트
|
||||
},
|
||||
lastSTTText: action.payload.finalText, // ⭐ API 호출을 위한 STT 텍스트 업데이트
|
||||
sttTimestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
case types.WEB_SPEECH_END:
|
||||
return {
|
||||
...state,
|
||||
webSpeech: {
|
||||
...state.webSpeech,
|
||||
isListening: false,
|
||||
interimText: null,
|
||||
// ⭐ END 시에는 텍스트를 유지 (API 호출을 위해)
|
||||
// interimText: null,
|
||||
// finalText: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -199,6 +219,8 @@ export const voiceReducer = (state = initialState, action) => {
|
||||
isInitialized: false,
|
||||
isListening: false,
|
||||
interimText: null,
|
||||
finalText: null,
|
||||
confidence: null,
|
||||
error: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ const VoiceDebugDashboard = ({
|
||||
lastSTTText,
|
||||
sttResponseText,
|
||||
interimText,
|
||||
finalText,
|
||||
errorMessage,
|
||||
voiceInputMode,
|
||||
isBubbleClickSearch,
|
||||
@@ -116,6 +117,18 @@ const VoiceDebugDashboard = ({
|
||||
<span className={css.textLabel}>Interim Text:</span>
|
||||
<div className={css.textContent}>{interimText || '(empty)'}</div>
|
||||
</div>
|
||||
<div className={css.textItem}>
|
||||
<span className={css.textLabel}>✅ Final Text (isFinal):</span>
|
||||
<div
|
||||
className={css.textContent}
|
||||
style={{
|
||||
backgroundColor: finalText ? '#2d5016' : 'inherit',
|
||||
padding: finalText ? '8px' : '0',
|
||||
}}
|
||||
>
|
||||
{finalText || '(empty)'}
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.textItem}>
|
||||
<span className={css.textLabel}>STT Response Text:</span>
|
||||
<div className={css.textContent}>{sttResponseText || '(empty)'}</div>
|
||||
@@ -185,6 +198,7 @@ VoiceDebugDashboard.propTypes = {
|
||||
lastSTTText: PropTypes.string,
|
||||
sttResponseText: PropTypes.string,
|
||||
interimText: PropTypes.string,
|
||||
finalText: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
voiceInputMode: PropTypes.string,
|
||||
isBubbleClickSearch: PropTypes.bool,
|
||||
@@ -196,6 +210,7 @@ VoiceDebugDashboard.defaultProps = {
|
||||
lastSTTText: '',
|
||||
sttResponseText: '',
|
||||
interimText: '',
|
||||
finalText: '',
|
||||
errorMessage: '',
|
||||
voiceInputMode: null,
|
||||
isBubbleClickSearch: false,
|
||||
|
||||
@@ -196,6 +196,46 @@ const VoiceInputOverlay = ({
|
||||
}
|
||||
}, [isVisible, currentMode]);
|
||||
|
||||
// 이벤트 타입별 색상 분류
|
||||
const getWebSpeechEventType = useCallback((event) => {
|
||||
const types = {
|
||||
INIT: 'info',
|
||||
START: 'success',
|
||||
RESULT: 'info',
|
||||
RESULT_FINAL: 'success',
|
||||
ERROR: 'error',
|
||||
END: 'warning',
|
||||
STOP: 'warning',
|
||||
ABORT: 'error',
|
||||
RESTART: 'info',
|
||||
};
|
||||
return types[event] || 'info';
|
||||
}, []);
|
||||
|
||||
// 🎤 WebSpeech 이벤트 로그 관리 함수
|
||||
const addWebSpeechEventLog = useCallback(
|
||||
(event, details = '') => {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const newLog = {
|
||||
id: Date.now(),
|
||||
timestamp,
|
||||
event,
|
||||
details,
|
||||
type: getWebSpeechEventType(event),
|
||||
};
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log(`[WebSpeech Event] ${event}: ${details}`);
|
||||
}
|
||||
|
||||
setWebSpeechEventLogs((prev) => {
|
||||
const updated = [newLog, ...prev];
|
||||
return updated.slice(0, 10); // 최근 10개만 유지
|
||||
});
|
||||
},
|
||||
[getWebSpeechEventType]
|
||||
);
|
||||
|
||||
// ⭐ currentMode 변경 추적
|
||||
useEffect(() => {
|
||||
console.log('🔀 [DEBUG][VoiceInputOverlay] currentMode changed to:', currentMode);
|
||||
@@ -270,46 +310,6 @@ const VoiceInputOverlay = ({
|
||||
// Redux에서 STT 결과 및 에러 상태 가져오기
|
||||
const { lastSTTText, sttTimestamp, webSpeech = {} } = useSelector((state) => state.voice);
|
||||
|
||||
// 이벤트 타입별 색상 분류
|
||||
const getWebSpeechEventType = useCallback((event) => {
|
||||
const types = {
|
||||
INIT: 'info',
|
||||
START: 'success',
|
||||
RESULT: 'info',
|
||||
RESULT_FINAL: 'success',
|
||||
ERROR: 'error',
|
||||
END: 'warning',
|
||||
STOP: 'warning',
|
||||
ABORT: 'error',
|
||||
RESTART: 'info',
|
||||
};
|
||||
return types[event] || 'info';
|
||||
}, []);
|
||||
|
||||
// 🎤 WebSpeech 이벤트 로그 관리 함수
|
||||
const addWebSpeechEventLog = useCallback(
|
||||
(event, details = '') => {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const newLog = {
|
||||
id: Date.now(),
|
||||
timestamp,
|
||||
event,
|
||||
details,
|
||||
type: getWebSpeechEventType(event),
|
||||
};
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
console.log(`[WebSpeech Event] ${event}: ${details}`);
|
||||
}
|
||||
|
||||
setWebSpeechEventLogs((prev) => {
|
||||
const updated = [newLog, ...prev];
|
||||
return updated.slice(0, 10); // 최근 10개만 유지
|
||||
});
|
||||
},
|
||||
[getWebSpeechEventType]
|
||||
);
|
||||
|
||||
// Redux에서 shopperHouse 검색 결과 및 에러 가져오기 (simplified ref usage)
|
||||
const shopperHouseData = useSelector((state) => state.search.shopperHouseData);
|
||||
const shopperHouseSearchId = useSelector((state) => state.search.shopperHouseSearchId); // 2차 발화용 searchId
|
||||
@@ -340,6 +340,53 @@ const VoiceInputOverlay = ({
|
||||
}
|
||||
}, [shopperHouseData, isVisible]);
|
||||
|
||||
// 🔄 WebSpeech 에러 재시작 함수
|
||||
const restartWebSpeech = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] 🔄 Restarting WebSpeech after error');
|
||||
}
|
||||
|
||||
// 이벤트 로그 기록
|
||||
addWebSpeechEventLog('RESTART', 'WebSpeech restarted after error or manual action');
|
||||
|
||||
// 에러 상태 정리 (Redux)
|
||||
dispatch(clearSTTText());
|
||||
|
||||
// 모든 타이머 정리
|
||||
clearTimerRef(listeningTimerRef);
|
||||
clearTimerRef(silenceDetectionTimerRef);
|
||||
clearIntervalRef(countdownIntervalRef);
|
||||
|
||||
// Ref 초기화
|
||||
listeningTimerRef.current = null;
|
||||
silenceDetectionTimerRef.current = null;
|
||||
countdownIntervalRef.current = null;
|
||||
interimTextRef.current = '';
|
||||
|
||||
// 에러 메시지 정리
|
||||
setErrorMessage('');
|
||||
|
||||
// WebSpeech 이벤트 로그 정리
|
||||
setWebSpeechEventLogs([]);
|
||||
|
||||
// Input 필드 초기화
|
||||
if (onSearchChange) {
|
||||
onSearchChange({ value: '' });
|
||||
}
|
||||
|
||||
// PROMPT 모드로 복귀
|
||||
setCurrentMode(VOICE_MODES.PROMPT);
|
||||
setVoiceInputMode(null);
|
||||
setIsBubbleClickSearch(false); // bubble 클릭 상태 초기화
|
||||
|
||||
// 약간의 지연 후 새로 시작 (안정성을 위해)
|
||||
setTimeout(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] ✅ Restart complete - ready for new input');
|
||||
}
|
||||
}, 300);
|
||||
}, [dispatch, onSearchChange, addWebSpeechEventLog]);
|
||||
|
||||
// 🚨 WebSpeech 에러 감시 및 처리 (Phase 1)
|
||||
useEffect(() => {
|
||||
// Bubble 클릭으로 검색이 시작된 경우 WebSpeech 오류를 우회
|
||||
@@ -356,19 +403,19 @@ const VoiceInputOverlay = ({
|
||||
typeof webSpeech.error === 'string'
|
||||
? webSpeech.error
|
||||
: webSpeech.error.error || webSpeech.error;
|
||||
const errorMessage =
|
||||
const errorMessageWebSpeech =
|
||||
typeof webSpeech.error === 'object'
|
||||
? webSpeech.error.message || 'No message'
|
||||
: webSpeech.message || 'No message';
|
||||
|
||||
console.error('[VoiceInput] 🔴 WebSpeech error detected:', errorType);
|
||||
console.error('[VoiceInput] ├─ error type:', errorType);
|
||||
console.error('[VoiceInput] ├─ message:', errorMessage);
|
||||
console.error('[VoiceInput] ├─ message:', errorMessageWebSpeech);
|
||||
console.error('[VoiceInput] ├─ current mode:', currentMode);
|
||||
console.error('[VoiceInput] └─ is listening:', isListening);
|
||||
|
||||
// WebSpeech 이벤트 로그 기록
|
||||
addWebSpeechEventLog('ERROR', `${errorType}: ${errorMessage}`);
|
||||
addWebSpeechEventLog('ERROR', `${errorType}: ${errorMessageWebSpeech}`);
|
||||
|
||||
// 사용자에게 표시할 에러 메시지 생성
|
||||
let userErrorMessage = '음성 인식에 문제가 발생했습니다.';
|
||||
@@ -429,6 +476,7 @@ const VoiceInputOverlay = ({
|
||||
restartWebSpeech,
|
||||
addWebSpeechEventLog,
|
||||
isBubbleClickSearch,
|
||||
webSpeech,
|
||||
]);
|
||||
|
||||
// 🎤 WebSpeech 이벤트 감지 (전용 디버그용)
|
||||
@@ -653,6 +701,87 @@ const VoiceInputOverlay = ({
|
||||
}
|
||||
}, [isVisible, voiceVersion, isSupported]);
|
||||
|
||||
// 🎤 음성 입력 최종 처리 함수 (15초 타이머 & 3초 silence detection 공통 사용)
|
||||
const processFinalVoiceInput = useCallback(
|
||||
(source) => {
|
||||
console.log(`[VoiceInput] 🏁 음성 입력 종료 (${source})`);
|
||||
|
||||
// 모든 타이머 정리 및 ref 초기화
|
||||
clearTimerRef(listeningTimerRef);
|
||||
clearIntervalRef(countdownIntervalRef);
|
||||
clearTimerRef(silenceDetectionTimerRef);
|
||||
|
||||
// ✅ Ref 명시적 초기화 (중요!)
|
||||
listeningTimerRef.current = null;
|
||||
countdownIntervalRef.current = null;
|
||||
silenceDetectionTimerRef.current = null;
|
||||
|
||||
// 음성 인식 중지
|
||||
stopListening();
|
||||
|
||||
// ✨ TV 환경 대응: Redux의 finalText(isFinal)를 우선 사용, 없으면 interimTextRef 사용
|
||||
const reduxFinalText = webSpeech?.finalText;
|
||||
const interimRefText = interimTextRef.current.trim();
|
||||
|
||||
// 우선순위: 1. Redux finalText (isFinal) 2. interimTextRef (누적)
|
||||
const finalText =
|
||||
reduxFinalText && reduxFinalText.trim().length >= 3
|
||||
? reduxFinalText.trim()
|
||||
: interimRefText;
|
||||
|
||||
console.log('[VoiceInput] ├─ Redux finalText:', reduxFinalText || '(없음)');
|
||||
console.log('[VoiceInput] ├─ interimRef:', interimRefText || '(없음)');
|
||||
console.log('[VoiceInput] └─ 사용할 텍스트:', finalText, `(길이: ${finalText.length})`);
|
||||
|
||||
if (finalText && finalText.length >= 3) {
|
||||
// STT 텍스트 저장
|
||||
setSttResponseText(finalText);
|
||||
|
||||
// RESPONSE 모드로 전환
|
||||
setCurrentMode(VOICE_MODES.RESPONSE);
|
||||
setVoiceInputMode(null);
|
||||
|
||||
// ✨ 검색 기록에 추가
|
||||
addToSearchHistory(finalText);
|
||||
|
||||
// ✨ ShopperHouse API 자동 호출 (2차 발화 시 searchId 포함)
|
||||
const query = finalText.trim();
|
||||
// ✅ Ref에서 최신 searchId 읽기 (useCallback closure 문제 해결)
|
||||
const currentSearchId = shopperHouseSearchIdRef.current;
|
||||
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 || '(없음 - 첫 번째 발화)');
|
||||
|
||||
// API 호출 이벤트 로깅
|
||||
const searchIdInfo = currentSearchId
|
||||
? `(2nd query, searchId: ${currentSearchId.substring(0, 8)}...)`
|
||||
: '(1st query, no searchId)';
|
||||
addWebSpeechEventLog('API_REQUEST', `Query: "${query}" ${searchIdInfo}`);
|
||||
|
||||
dispatch(getShopperHouseSearch(query, currentSearchId));
|
||||
} else {
|
||||
// 입력이 없거나 너무 짧으면 PROMPT 모드로 복귀
|
||||
console.log('[VoiceInput] ⚠️ 입력 없음 또는 너무 짧음 - PROMPT 모드로 복귀');
|
||||
console.log('[VoiceInput] └─ finalText 길이:', finalText.length);
|
||||
setCurrentMode(VOICE_MODES.PROMPT);
|
||||
setVoiceInputMode(null);
|
||||
}
|
||||
|
||||
// Ref 초기화
|
||||
interimTextRef.current = '';
|
||||
},
|
||||
[
|
||||
stopListening,
|
||||
addToSearchHistory,
|
||||
dispatch,
|
||||
addWebSpeechEventLog,
|
||||
webSpeech, // ✨ Redux finalText를 사용하기 위해 dependency 추가
|
||||
// ✅ shopperHouseSearchId 제거 - ref 사용으로 closure 문제 해결
|
||||
]
|
||||
);
|
||||
|
||||
// ⛔ 독립 테스트: WebSpeech API 호출 비활성화
|
||||
// WebSpeech 모드로 전환되면 자동으로 음성 인식 시작
|
||||
// useEffect(() => {
|
||||
@@ -675,8 +804,6 @@ const VoiceInputOverlay = ({
|
||||
// }
|
||||
// }, [voiceInputMode, currentMode, isListening, startListening, stopListening]);
|
||||
|
||||
// 이 useEffect 제거 - renderModeContent()에서 직접 판단하므로 불필요
|
||||
|
||||
// 🎤 Interim 텍스트 실시간 표시 및 ref 업데이트 + 3초 silence detection
|
||||
useEffect(() => {
|
||||
if (currentMode !== VOICE_MODES.LISTENING) {
|
||||
@@ -712,7 +839,7 @@ const VoiceInputOverlay = ({
|
||||
clearTimerRef(silenceDetectionTimerRef);
|
||||
silenceDetectionTimerRef.current = null;
|
||||
};
|
||||
}, [interimText, currentMode, onSearchChange, processFinalVoiceInput]);
|
||||
}, [interimText, currentMode, onSearchChange, processFinalVoiceInput, addWebSpeechEventLog]);
|
||||
|
||||
// 🎉 Wake Word Detection: PROMPT 모드에서 백그라운드 리스닝 시작
|
||||
useEffect(() => {
|
||||
@@ -924,121 +1051,6 @@ const VoiceInputOverlay = ({
|
||||
}
|
||||
}, [dispatch, sttResponseText]);
|
||||
|
||||
// 🔄 WebSpeech 에러 재시작 함수
|
||||
const restartWebSpeech = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] 🔄 Restarting WebSpeech after error');
|
||||
}
|
||||
|
||||
// 이벤트 로그 기록
|
||||
addWebSpeechEventLog('RESTART', 'WebSpeech restarted after error or manual action');
|
||||
|
||||
// 에러 상태 정리 (Redux)
|
||||
dispatch(clearSTTText());
|
||||
|
||||
// 모든 타이머 정리
|
||||
clearTimerRef(listeningTimerRef);
|
||||
clearTimerRef(silenceDetectionTimerRef);
|
||||
clearIntervalRef(countdownIntervalRef);
|
||||
|
||||
// Ref 초기화
|
||||
listeningTimerRef.current = null;
|
||||
silenceDetectionTimerRef.current = null;
|
||||
countdownIntervalRef.current = null;
|
||||
interimTextRef.current = '';
|
||||
|
||||
// 에러 메시지 정리
|
||||
setErrorMessage('');
|
||||
|
||||
// WebSpeech 이벤트 로그 정리
|
||||
setWebSpeechEventLogs([]);
|
||||
|
||||
// Input 필드 초기화
|
||||
if (onSearchChange) {
|
||||
onSearchChange({ value: '' });
|
||||
}
|
||||
|
||||
// PROMPT 모드로 복귀
|
||||
setCurrentMode(VOICE_MODES.PROMPT);
|
||||
setVoiceInputMode(null);
|
||||
setIsBubbleClickSearch(false); // bubble 클릭 상태 초기화
|
||||
|
||||
// 약간의 지연 후 새로 시작 (안정성을 위해)
|
||||
setTimeout(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('[VoiceInput] ✅ Restart complete - ready for new input');
|
||||
}
|
||||
}, 300);
|
||||
}, [dispatch, onSearchChange, addWebSpeechEventLog]);
|
||||
|
||||
// 🎤 음성 입력 최종 처리 함수 (15초 타이머 & 3초 silence detection 공통 사용)
|
||||
const processFinalVoiceInput = useCallback(
|
||||
(source) => {
|
||||
console.log(`[VoiceInput] 🏁 음성 입력 종료 (${source})`);
|
||||
|
||||
// 모든 타이머 정리 및 ref 초기화
|
||||
clearTimerRef(listeningTimerRef);
|
||||
clearIntervalRef(countdownIntervalRef);
|
||||
clearTimerRef(silenceDetectionTimerRef);
|
||||
|
||||
// ✅ Ref 명시적 초기화 (중요!)
|
||||
listeningTimerRef.current = null;
|
||||
countdownIntervalRef.current = null;
|
||||
silenceDetectionTimerRef.current = null;
|
||||
|
||||
// 음성 인식 중지
|
||||
stopListening();
|
||||
|
||||
// ✅ 누적된 interimText를 최종 결과로 사용
|
||||
const finalText = interimTextRef.current.trim();
|
||||
console.log('[VoiceInput] └─ 최종 텍스트:', finalText);
|
||||
|
||||
if (finalText && finalText.length >= 3) {
|
||||
// STT 텍스트 저장
|
||||
setSttResponseText(finalText);
|
||||
|
||||
// RESPONSE 모드로 전환
|
||||
setCurrentMode(VOICE_MODES.RESPONSE);
|
||||
setVoiceInputMode(null);
|
||||
|
||||
// ✨ 검색 기록에 추가
|
||||
addToSearchHistory(finalText);
|
||||
|
||||
// ✨ ShopperHouse API 자동 호출 (2차 발화 시 searchId 포함)
|
||||
const query = finalText.trim();
|
||||
// ✅ Ref에서 최신 searchId 읽기 (useCallback closure 문제 해결)
|
||||
const currentSearchId = shopperHouseSearchIdRef.current;
|
||||
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 || '(없음 - 첫 번째 발화)');
|
||||
|
||||
// API 호출 이벤트 로깅
|
||||
const searchIdInfo = currentSearchId
|
||||
? `(2nd query, searchId: ${currentSearchId.substring(0, 8)}...)`
|
||||
: '(1st query, no searchId)';
|
||||
addWebSpeechEventLog('API_REQUEST', `Query: "${query}" ${searchIdInfo}`);
|
||||
|
||||
dispatch(getShopperHouseSearch(query, currentSearchId));
|
||||
} else {
|
||||
// 입력이 없거나 너무 짧으면 PROMPT 모드로 복귀
|
||||
console.log('[VoiceInput] ⚠️ 입력 없음 또는 너무 짧음 - PROMPT 모드로 복귀');
|
||||
setCurrentMode(VOICE_MODES.PROMPT);
|
||||
setVoiceInputMode(null);
|
||||
}
|
||||
|
||||
// Ref 초기화
|
||||
interimTextRef.current = '';
|
||||
},
|
||||
[
|
||||
stopListening,
|
||||
addToSearchHistory,
|
||||
dispatch,
|
||||
// ✅ shopperHouseSearchId 제거 - ref 사용으로 closure 문제 해결
|
||||
]
|
||||
);
|
||||
|
||||
// Overlay 닫기 핸들러 (모든 닫기 동작을 통합)
|
||||
const handleClose = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
@@ -1119,7 +1131,7 @@ const VoiceInputOverlay = ({
|
||||
}
|
||||
}
|
||||
},
|
||||
[onSearchChange, dispatch, addToSearchHistory]
|
||||
[onSearchChange, dispatch, addToSearchHistory, addWebSpeechEventLog]
|
||||
);
|
||||
|
||||
// Input 창에서 API 호출 핸들러 (돋보기 아이콘 클릭 시에만)
|
||||
@@ -1387,7 +1399,15 @@ const VoiceInputOverlay = ({
|
||||
handleClose();
|
||||
}
|
||||
},
|
||||
[currentMode, handleClose, startListening, onSearchChange, dispatch, processFinalVoiceInput]
|
||||
[
|
||||
currentMode,
|
||||
handleClose,
|
||||
startListening,
|
||||
onSearchChange,
|
||||
dispatch,
|
||||
processFinalVoiceInput,
|
||||
addWebSpeechEventLog,
|
||||
]
|
||||
);
|
||||
|
||||
// 마이크 버튼 키다운 핸들러
|
||||
@@ -1705,6 +1725,7 @@ const VoiceInputOverlay = ({
|
||||
lastSTTText={lastSTTText}
|
||||
sttResponseText={sttResponseText}
|
||||
interimText={interimText}
|
||||
finalText={webSpeech?.finalText}
|
||||
errorMessage={errorMessage}
|
||||
voiceInputMode={voiceInputMode}
|
||||
isBubbleClickSearch={isBubbleClickSearch}
|
||||
|
||||
@@ -587,8 +587,8 @@
|
||||
}
|
||||
|
||||
.dashboardTitle {
|
||||
font-size: 32px; // 24px → 32px
|
||||
font-weight: bold;
|
||||
font-size: 36px; // 32px → 36px (더 크게)
|
||||
font-weight: 900; // bold → 900 (더 굵게)
|
||||
color: #FFB81C; // Shoptime 브랜드 색상
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
@@ -637,8 +637,8 @@
|
||||
|
||||
h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 24px; // 20px → 24px
|
||||
font-weight: bold;
|
||||
font-size: 28px; // 24px → 28px (더 크게)
|
||||
font-weight: 900; // bold → 900 (더 굵게)
|
||||
color: #4CAF50;
|
||||
border-bottom: 2px solid rgba(76, 175, 80, 0.3);
|
||||
padding-bottom: 12px;
|
||||
@@ -650,7 +650,8 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
font-size: 18px; // 16px → 18px
|
||||
font-size: 22px; // 18px → 22px (더 크게)
|
||||
font-weight: 700; // 전체 텍스트 굵게
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -667,18 +668,18 @@
|
||||
border-left: 3px solid rgba(255, 255, 255, 0.1);
|
||||
|
||||
.stateLabel {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-weight: 800; // 600 → 800 (더 굵게)
|
||||
min-width: 120px;
|
||||
font-size: 16px; // 명시적 크기 지정
|
||||
font-size: 20px; // 16px → 20px (더 크게)
|
||||
}
|
||||
|
||||
.stateValue {
|
||||
color: #2196F3;
|
||||
font-weight: 500;
|
||||
font-weight: 700; // 500 → 700 (더 굵게)
|
||||
word-break: break-all;
|
||||
max-width: 60%;
|
||||
font-size: 16px; // 명시적 크기 지정
|
||||
font-size: 20px; // 16px → 20px (더 크게)
|
||||
|
||||
&.active {
|
||||
color: #4CAF50;
|
||||
@@ -699,9 +700,9 @@
|
||||
gap: 8px;
|
||||
|
||||
.textLabel {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 16px; // 14px → 16px
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 20px; // 16px → 20px (더 크게)
|
||||
font-weight: 800; // 600 → 800 (더 굵게)
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
@@ -713,7 +714,8 @@
|
||||
padding: 14px; // 12px → 14px
|
||||
color: #4CAF50;
|
||||
font-family: monospace;
|
||||
font-size: 16px; // 14px → 16px
|
||||
font-size: 20px; // 16px → 20px (더 크게)
|
||||
font-weight: 700; // 굵게 추가
|
||||
min-height: 50px; // 40px → 50px
|
||||
max-height: 120px; // 100px → 120px
|
||||
overflow-y: auto;
|
||||
@@ -730,7 +732,8 @@
|
||||
padding: 14px; // 12px → 14px
|
||||
border-radius: 4px;
|
||||
color: #FF6B6B;
|
||||
font-size: 17px; // 15px → 17px
|
||||
font-size: 21px; // 17px → 21px (더 크게)
|
||||
font-weight: 700; // 굵게 추가
|
||||
line-height: 1.6; // 1.5 → 1.6
|
||||
}
|
||||
|
||||
@@ -755,7 +758,8 @@
|
||||
padding: 14px; // 12px → 14px
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
align-items: flex-start;
|
||||
font-size: 16px; // 14px → 16px
|
||||
font-size: 20px; // 16px → 20px (더 크게)
|
||||
font-weight: 700; // 굵게 추가
|
||||
transition: background 0.2s ease;
|
||||
|
||||
&:last-child {
|
||||
@@ -775,14 +779,15 @@
|
||||
.timelineIndex {
|
||||
min-width: 30px;
|
||||
text-align: right;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 14px; // 12px → 14px
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 18px; // 14px → 18px (더 크게)
|
||||
font-weight: 700; // 굵게 추가
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.timelineIcon {
|
||||
min-width: 24px;
|
||||
font-size: 20px; // 18px → 20px
|
||||
font-size: 24px; // 20px → 24px (더 크게)
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -797,33 +802,37 @@
|
||||
margin-bottom: 4px;
|
||||
|
||||
.eventName {
|
||||
font-weight: 600;
|
||||
font-weight: 800; // 600 → 800 (더 굵게)
|
||||
color: white;
|
||||
min-width: 120px;
|
||||
font-size: 16px; // 명시적 크기 지정
|
||||
font-size: 20px; // 16px → 20px (더 크게)
|
||||
}
|
||||
|
||||
.eventTime {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 14px; // 12px → 14px
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 18px; // 14px → 18px (더 크게)
|
||||
font-weight: 600; // 굵게 추가
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.timelineDetails {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-left: 0;
|
||||
word-break: break-word;
|
||||
padding: 4px 0;
|
||||
font-family: monospace;
|
||||
font-size: 15px; // 13px → 15px
|
||||
font-size: 19px; // 15px → 19px (더 크게)
|
||||
font-weight: 600; // 굵게 추가
|
||||
}
|
||||
|
||||
.noEvents {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-style: italic;
|
||||
font-size: 22px; // 더 크게
|
||||
font-weight: 700; // 굵게 추가
|
||||
}
|
||||
|
||||
// 스크롤바 스타일링 (대시보드용)
|
||||
|
||||
@@ -16,13 +16,16 @@ const PromptContainer = SpotlightContainerDecorator(
|
||||
);
|
||||
|
||||
const VoicePromptScreen = ({ title = 'Try saying', suggestions = [], onSuggestionClick }) => {
|
||||
const handleBubbleClick = (suggestion) => {
|
||||
console.log('💡 [DEBUG][VoicePromptScreen] Bubble clicked:', suggestion);
|
||||
// 커링 패턴: suggestion을 미리 바인딩하는 핸들러 생성
|
||||
const createBubbleClickHandler = (suggestion) => {
|
||||
return () => {
|
||||
console.log('💡 [DEBUG][VoicePromptScreen] Bubble clicked:', suggestion);
|
||||
|
||||
// 부모 컴포넌트로 suggestion 텍스트 전달 (API 호출은 부모에서 처리)
|
||||
if (onSuggestionClick) {
|
||||
onSuggestionClick(suggestion);
|
||||
}
|
||||
// 부모 컴포넌트로 suggestion 텍스트 전달 (API 호출은 부모에서 처리)
|
||||
if (onSuggestionClick) {
|
||||
onSuggestionClick(suggestion);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -38,7 +41,7 @@ const VoicePromptScreen = ({ title = 'Try saying', suggestions = [], onSuggestio
|
||||
<SpottableBubble
|
||||
key={index}
|
||||
className={css.bubbleMessage}
|
||||
onClick={() => handleBubbleClick(suggestion)}
|
||||
onClick={createBubbleClickHandler(suggestion)}
|
||||
spotlightId={`voice-bubble-${index}`}
|
||||
>
|
||||
<div className={css.bubbleText}>{suggestion}</div>
|
||||
|
||||
Reference in New Issue
Block a user