|
|
@@ -1,4 +1,5 @@
|
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
+import { useNavigate } from 'react-router-dom';
|
|
|
import { message as antMessage } from 'antd';
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
import { useAppStore } from '@/store';
|
|
|
@@ -6,10 +7,10 @@ import { AudioRecorder, type FrequencyFrame } from '@/utils/audioRecorder';
|
|
|
import { AudioPlayer } from '@/utils/audioPlayer';
|
|
|
import { WebSocketManager } from '@/utils/websocket';
|
|
|
import { prepareMobileAudio, requestVolumeControl, isMobileDevice } from '@/utils/audioHelper';
|
|
|
-import { setStorage, getStorage, STORAGE_KEYS, markRouteVisited, getUserId } from '@/utils/storage';
|
|
|
+import { setStorage, getStorage, STORAGE_KEYS, markRouteVisited, getUserId, getVisitedRoutes } from '@/utils/storage';
|
|
|
import { onScreenResize, calculateLayout, type CalculatedLayout, updateTooSmallFlag } from '@/utils/screenSize';
|
|
|
import { performanceLogger } from '@/utils/logger';
|
|
|
-import { getUserInfo } from '@/utils/api';
|
|
|
+import { getUserInfo, updateUserUIState, getUserUIState } from '@/utils/api';
|
|
|
import { px2vw, px2vh, px2vmin } from '@/utils/viewport';
|
|
|
import type { Message, EnergyStoneSpawnMessage } from '@/types';
|
|
|
import '@/styles/chatbot-fix.css';
|
|
|
@@ -40,6 +41,7 @@ const COUNTDOWN_THRESHOLD_MS = 10_000;
|
|
|
const WS_URL = import.meta.env.VITE_WS_URL || 'ws://localhost:8000/ws';
|
|
|
|
|
|
export const ChatBotPage = () => {
|
|
|
+ const navigate = useNavigate();
|
|
|
const {
|
|
|
currentState,
|
|
|
currentRequestId,
|
|
|
@@ -73,6 +75,17 @@ export const ChatBotPage = () => {
|
|
|
if (!hasShown) {
|
|
|
// 第一次打开,立即保存标记并显示
|
|
|
setStorage(STORAGE_KEYS.HAS_SHOWN_TAP_TO_CHAT, 'true');
|
|
|
+
|
|
|
+ // 同步到后端 UI 状态(如果已有 user_id)
|
|
|
+ const userId = getUserId();
|
|
|
+ if (userId) {
|
|
|
+ updateUserUIState(userId, {
|
|
|
+ has_shown_tap_to_chat: true,
|
|
|
+ }).catch(() => {
|
|
|
+ // 同步失败不影响前端提示展示
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
@@ -145,6 +158,15 @@ export const ChatBotPage = () => {
|
|
|
avatarCUnlockedRef.current = true;
|
|
|
setIsAvatarCUnlocked(true);
|
|
|
setStorage(STORAGE_KEYS.AVATAR_C_UNLOCKED, 'true');
|
|
|
+
|
|
|
+ const userId = getUserId();
|
|
|
+ if (userId) {
|
|
|
+ updateUserUIState(userId, {
|
|
|
+ avatar_c_unlocked: true,
|
|
|
+ }).catch(() => {
|
|
|
+ // 同步失败不影响主流程
|
|
|
+ });
|
|
|
+ }
|
|
|
}, []);
|
|
|
const registerSpeechDuration = useCallback(
|
|
|
(durationMs: number) => {
|
|
|
@@ -367,6 +389,14 @@ export const ChatBotPage = () => {
|
|
|
if (!hasShownCancel) {
|
|
|
setShowCancelTooltip(true);
|
|
|
setStorage(STORAGE_KEYS.HAS_SHOWN_CANCEL_TOOLTIP, 'true');
|
|
|
+ const userId = getUserId();
|
|
|
+ if (userId) {
|
|
|
+ updateUserUIState(userId, {
|
|
|
+ has_shown_cancel_tooltip: true,
|
|
|
+ }).catch(() => {
|
|
|
+ // 同步失败不影响主流程
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
// 🔧 调试模式:注释掉自动隐藏,方便调试
|
|
|
// 3秒后自动隐藏
|
|
|
@@ -392,6 +422,14 @@ export const ChatBotPage = () => {
|
|
|
if (!hasShownSend) {
|
|
|
setShowSendTooltip(true);
|
|
|
setStorage(STORAGE_KEYS.HAS_SHOWN_SEND_TOOLTIP, 'true');
|
|
|
+ const userId = getUserId();
|
|
|
+ if (userId) {
|
|
|
+ updateUserUIState(userId, {
|
|
|
+ has_shown_send_tooltip: true,
|
|
|
+ }).catch(() => {
|
|
|
+ // 同步失败不影响主流程
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
// 🔧 调试模式:注释掉自动隐藏,方便调试
|
|
|
// 3秒后自动隐藏
|
|
|
@@ -442,6 +480,14 @@ export const ChatBotPage = () => {
|
|
|
setShowProgressBarTooltip(true);
|
|
|
setStorage(STORAGE_KEYS.HAS_SHOWN_PROGRESS_BAR_TOOLTIP, 'true');
|
|
|
setHasShownProgressBarTooltip(true);
|
|
|
+ const userId = getUserId();
|
|
|
+ if (userId) {
|
|
|
+ updateUserUIState(userId, {
|
|
|
+ has_shown_progress_bar_tooltip: true,
|
|
|
+ }).catch(() => {
|
|
|
+ // 同步失败不影响主流程
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
}, [displayProgress, hasShownProgressBarTooltip, isConnected, currentState]);
|
|
|
|
|
|
@@ -498,6 +544,58 @@ export const ChatBotPage = () => {
|
|
|
|
|
|
// 记录路由访问
|
|
|
markRouteVisited('/chatbot');
|
|
|
+
|
|
|
+ const visited = getVisitedRoutes();
|
|
|
+ const userId = getUserId();
|
|
|
+ if (userId) {
|
|
|
+ // 先尝试从后端拉取 UI 状态用于恢复本地提示与 Avatar 状态
|
|
|
+ getUserUIState(userId)
|
|
|
+ .then((resp) => {
|
|
|
+ if (!resp.success || !resp.data) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const state = resp.data;
|
|
|
+
|
|
|
+ if (state.current_page) {
|
|
|
+ setStorage(STORAGE_KEYS.CURRENT_PAGE, state.current_page);
|
|
|
+ }
|
|
|
+ if (Array.isArray(state.visited_routes)) {
|
|
|
+ setStorage(STORAGE_KEYS.VISITED_ROUTES, JSON.stringify(state.visited_routes));
|
|
|
+ }
|
|
|
+ if (state.has_shown_tap_to_chat) {
|
|
|
+ setStorage(STORAGE_KEYS.HAS_SHOWN_TAP_TO_CHAT, 'true');
|
|
|
+ setShowTapToChat(false);
|
|
|
+ }
|
|
|
+ if (state.has_shown_cancel_tooltip) {
|
|
|
+ setStorage(STORAGE_KEYS.HAS_SHOWN_CANCEL_TOOLTIP, 'true');
|
|
|
+ setShowCancelTooltip(false);
|
|
|
+ }
|
|
|
+ if (state.has_shown_send_tooltip) {
|
|
|
+ setStorage(STORAGE_KEYS.HAS_SHOWN_SEND_TOOLTIP, 'true');
|
|
|
+ setShowSendTooltip(false);
|
|
|
+ }
|
|
|
+ if (state.has_shown_progress_bar_tooltip) {
|
|
|
+ setStorage(STORAGE_KEYS.HAS_SHOWN_PROGRESS_BAR_TOOLTIP, 'true');
|
|
|
+ setHasShownProgressBarTooltip(true);
|
|
|
+ }
|
|
|
+ if (state.avatar_c_unlocked && !avatarCUnlockedRef.current) {
|
|
|
+ avatarCUnlockedRef.current = true;
|
|
|
+ setIsAvatarCUnlocked(true);
|
|
|
+ setStorage(STORAGE_KEYS.AVATAR_C_UNLOCKED, 'true');
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 拉取失败不影响主流程
|
|
|
+ });
|
|
|
+
|
|
|
+ // 同步当前页面与已访问路由
|
|
|
+ updateUserUIState(userId, {
|
|
|
+ current_page: 'chatbot',
|
|
|
+ visited_routes: visited,
|
|
|
+ }).catch(() => {
|
|
|
+ // 同步失败不影响主流程
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
const init = async () => {
|
|
|
try {
|
|
|
@@ -1049,6 +1147,7 @@ export const ChatBotPage = () => {
|
|
|
>
|
|
|
<div
|
|
|
className="text-fluid-sm"
|
|
|
+ onClick={() => navigate('/level5-welcome')}
|
|
|
style={{
|
|
|
fontWeight: 500,
|
|
|
color: 'rgba(255, 255, 255, 0.9)',
|
|
|
@@ -1058,6 +1157,18 @@ export const ChatBotPage = () => {
|
|
|
background: 'rgba(255, 255, 255, 0.1)',
|
|
|
backdropFilter: 'blur(10px)',
|
|
|
border: '1px solid rgba(255, 255, 255, 0.2)',
|
|
|
+ cursor: 'pointer',
|
|
|
+ userSelect: 'none',
|
|
|
+ WebkitUserSelect: 'none',
|
|
|
+ transition: 'all 0.2s ease',
|
|
|
+ }}
|
|
|
+ onMouseEnter={(e) => {
|
|
|
+ e.currentTarget.style.background = 'rgba(255, 255, 255, 0.15)';
|
|
|
+ e.currentTarget.style.transform = 'scale(1.05)';
|
|
|
+ }}
|
|
|
+ onMouseLeave={(e) => {
|
|
|
+ e.currentTarget.style.background = 'rgba(255, 255, 255, 0.1)';
|
|
|
+ e.currentTarget.style.transform = 'scale(1)';
|
|
|
}}
|
|
|
>
|
|
|
{currentEmotion}
|