zlwu 11 ヶ月 前
親
コミット
d50baae25d

BIN
.playwright-mcp/page-2025-11-18T10-36-24-597Z.png


+ 1 - 1
backend/main.py

@@ -267,7 +267,7 @@ if __name__ == "__main__":
         "main:app",
         host=settings.host,
         port=settings.port,
-        reload=True,
+        reload=settings.DEBUG,
         ssl_keyfile=ssl_keyfile,
         ssl_certfile=ssl_certfile,
     )

+ 2 - 0
frontend/.env.production

@@ -6,6 +6,8 @@
 # - 如果后端独立域名:wss://api.yourdomain.com/ws
 # - 如果使用IP:wss://your-server-ip:8000/ws
 VITE_WS_URL=wss://www.hikelly.chat/ws
+VITE_METRICS_BASE=https://www.hikelly.chat
+
 VITE_SSL_KEYFILE=/etc/nginx/cert/www.hikelly.chat.key
 VITE_SSL_CERTFILE=/etc/nginx/cert/www.hikelly.chat.pem
 

+ 1 - 0
frontend/SSL证书配置.md

@@ -125,5 +125,6 @@ ssl/
 
 
 
+
 
 

+ 0 - 2
frontend/src/components/AudioWave.tsx

@@ -44,8 +44,6 @@ export const AudioWave: React.FC<AudioWaveProps> = ({
   const barSpacing = barWidth + actualGap;
   
   const STATIC_HEIGHT = Math.max(16, height * 0.35); // 静态时竖条高度(高度的35%,最小16px)
-  const MIN_HEIGHT = Math.max(2, height * 0.04); // 最小高度(高度的4%,最小2px)
-  const MAX_HEIGHT = STATIC_HEIGHT * 2; // 最大高度(固定为静态高度的2倍)
   
   // 竖条高度数组:存储每个竖条的当前高度
   const [barHeights, setBarHeights] = useState<number[]>([]);

+ 5 - 5
frontend/src/index.css

@@ -106,7 +106,7 @@ body[data-too-small="true"] {
   width: 100%;
   height: 100%;
   max-width: 100%;
-  background: #F5F3F0;
+  /* background: #F5F3F0; */
   position: relative;
   overflow: hidden;
 }
@@ -140,8 +140,8 @@ body[data-too-small="true"] {
   
   .content-card {
     max-width: 640px;
-    border-radius: 20px;
-    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+    /* border-radius: 20px; */
+    /* box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); */
   }
   
   html[data-device-type="tablet"] {
@@ -159,8 +159,8 @@ body[data-too-small="true"] {
     max-width: 430px; /* 固定为移动端最大宽度 */
     height: 812px; /* 固定为设计稿高度 */
     max-height: calc(100vh - 80px); /* 留出上下边距 */
-    border-radius: 30px;
-    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+    /* border-radius: 30px; */
+    /* box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); */
     /* 3D 效果 */
     transform: perspective(1000px);
   }

+ 8 - 2
frontend/src/pages/ChatBotPage copy.tsx

@@ -449,6 +449,10 @@ export const ChatBotPage = () => {
   // 处理WebSocket消息
   const handleMessage = (message: Message) => {
     const requestId = message.requestId || currentRequestId;
+    if (!requestId) {
+      console.warn('[ChatBotPage] 收到缺少 requestId 的消息,已忽略', message.type);
+      return;
+    }
     
     switch (message.type) {
       case 'text_result':
@@ -628,7 +632,8 @@ export const ChatBotPage = () => {
     sendingTimeoutRef.current = window.setTimeout(() => {
       const state = useAppStore.getState().currentState;
       if (state === 'sending' || state === 'playing') {
-        console.warn(`[超时] [${currentRequestId.substring(0, 8)}] 响应超时,自动重置`);
+        const shortId = currentRequestId ? currentRequestId.substring(0, 8) : '';
+        console.warn(`[超时] [${shortId}] 响应超时,自动重置`);
         antMessage.warning('响应超时,请重试');
         setState('idle');
         reset();
@@ -643,7 +648,8 @@ export const ChatBotPage = () => {
   const handleCancel = () => {
     if (currentState !== 'recording' || !currentRequestId) return;
 
-    console.log(`[取消] [${currentRequestId.substring(0, 8)}] 取消录音`);
+    const shortId = currentRequestId.substring(0, 8);
+    console.log(`[取消] [${shortId}] 取消录音`);
     recorderRef.current?.stop();
     stopRecordingTimer();
 

+ 8 - 2
frontend/src/pages/ChatBotPage.tsx

@@ -449,6 +449,10 @@ export const ChatBotPage = () => {
   // 处理WebSocket消息
   const handleMessage = (message: Message) => {
     const requestId = message.requestId || currentRequestId;
+    if (!requestId) {
+      console.warn('[ChatBotPage] 收到缺少 requestId 的消息,已忽略', message.type);
+      return;
+    }
     
     switch (message.type) {
       case 'text_result':
@@ -628,7 +632,8 @@ export const ChatBotPage = () => {
     sendingTimeoutRef.current = window.setTimeout(() => {
       const state = useAppStore.getState().currentState;
       if (state === 'sending' || state === 'playing') {
-        console.warn(`[超时] [${currentRequestId.substring(0, 8)}] 响应超时,自动重置`);
+        const shortId = currentRequestId ? currentRequestId.substring(0, 8) : '';
+        console.warn(`[超时] [${shortId}] 响应超时,自动重置`);
         antMessage.warning('响应超时,请重试');
         setState('idle');
         reset();
@@ -643,7 +648,8 @@ export const ChatBotPage = () => {
   const handleCancel = () => {
     if (currentState !== 'recording' || !currentRequestId) return;
 
-    console.log(`[取消] [${currentRequestId.substring(0, 8)}] 取消录音`);
+    const shortId = currentRequestId.substring(0, 8);
+    console.log(`[取消] [${shortId}] 取消录音`);
     recorderRef.current?.stop();
     stopRecordingTimer();
 

+ 3 - 10
frontend/src/pages/GreetingPage.tsx

@@ -4,7 +4,6 @@ import { getStorage, setStorage, STORAGE_KEYS, markRouteVisited, getUserId } fro
 import { getUserInfo, updateUsername } from '../utils/api';
 import { px2rem } from '../utils/rem';
 import AvatarZone from '../assets/Avatar-anima-zone.png';
-import NamingBg from '../assets/tolan-bg.svg';
 import ConfirmBtnImage from '../assets/confirm-btn.png';
 
 export const GreetingPage = () => {
@@ -126,15 +125,7 @@ export const GreetingPage = () => {
 
   return (
     <div className="mobile-container fixed top-0 left-0 animate-fade-in">
-      <div
-        className="content-card safe-area-inset relative flex flex-col min-h-screen overflow-y-auto"
-        style={{
-          backgroundImage: `url(${NamingBg})`,
-          backgroundSize: 'cover',
-          backgroundPosition: 'center',
-          backgroundColor: '#25232E',
-        }}
-      >
+      <div className="content-card safe-area-inset relative flex flex-col min-h-screen overflow-y-auto">
         {/* 返回按钮 - 响应式定位,与 Replay intro 垂直居中对齐 */}
         <button 
           className="absolute z-10 flex items-center justify-center 
@@ -193,6 +184,8 @@ export const GreetingPage = () => {
               style={{
                 fontSize: isCompactLayout ? px2rem(26) : px2rem(31),
                 lineHeight: isCompactLayout ? px2rem(40) : px2rem(50),
+                whiteSpace: 'nowrap',
+                wordBreak: 'keep-all',
               }}
             >
               What is your name?

+ 1 - 10
frontend/src/pages/LumiNamingPage.tsx

@@ -4,7 +4,6 @@ import { getStorage, setStorage, STORAGE_KEYS, markRouteVisited, getUserId } fro
 import { updateBotName } from '../utils/api';
 import { px2rem } from '../utils/rem';
 import LumiImage from '../assets/lumi.png';
-import NamingBg from '../assets/tolan-bg.svg';
 import ConfirmBtnImage from '../assets/confirm-btn.png';
 
 export const LumiNamingPage = () => {
@@ -94,15 +93,7 @@ export const LumiNamingPage = () => {
 
   return (
     <div className="mobile-container fixed top-0 left-0 animate-fade-in">
-      <div
-        className="content-card safe-area-inset relative flex flex-col min-h-screen overflow-y-auto"
-        style={{
-          backgroundImage: `url(${NamingBg})`,
-          backgroundSize: 'cover',
-          backgroundPosition: 'center',
-          backgroundColor: '#25232E',
-        }}
-      >
+      <div className="content-card safe-area-inset relative flex flex-col min-h-screen overflow-y-auto">
         {/* 返回按钮 */}
         <button
           className="absolute z-10 flex items-center justify-center 

+ 0 - 2
frontend/src/pages/RootRedirect.tsx

@@ -10,7 +10,6 @@ import { createUser } from '@/utils/api';
  */
 export const RootRedirect = () => {
   const navigate = useNavigate();
-  const [isLoading, setIsLoading] = useState(true);
   const [error, setError] = useState<string | null>(null);
 
   useEffect(() => {
@@ -57,7 +56,6 @@ export const RootRedirect = () => {
       } catch (err) {
         console.error('[RootRedirect] 错误:', err);
         setError(err instanceof Error ? err.message : '未知错误');
-        setIsLoading(false);
       }
     };
 

+ 38 - 21
frontend/src/pages/VideoIntroPage.tsx

@@ -1,7 +1,7 @@
 import { useEffect, useRef, useState } from 'react';
 import { useNavigate } from 'react-router-dom';
 import videojs from 'video.js';
-import { setStorage, STORAGE_KEYS, markRouteVisited } from '@/utils/storage';
+import { setStorage, STORAGE_KEYS } from '@/utils/storage';
 import 'video.js/dist/video-js.css';
 import './VideoIntroPage.css';
 import chatbotBg from '@/assets/chatbot-bg.svg';
@@ -91,7 +91,7 @@ export const VideoIntroPage = () => {
         let hasStartedPlaying = false;
 
         // 确保视频元素可见
-        const playerEl = this.el();
+        const playerEl = this.el() as HTMLElement | null;
         if (playerEl) {
           playerEl.style.visibility = 'visible';
           playerEl.style.opacity = '1';
@@ -278,30 +278,37 @@ export const VideoIntroPage = () => {
               videoElement.load();
               
               // 监听 canplay 事件后再播放
+              const safeVideoElement: HTMLVideoElement = videoElement;
               const canplayHandler = () => {
                 console.log('[VideoIntroPage] canplay 事件触发,开始播放');
-                videoElement.removeEventListener('canplay', canplayHandler);
-                this.play().catch((error) => {
-                  console.error('[VideoIntroPage] ❌ 播放失败:', error);
-                });
+                safeVideoElement.removeEventListener('canplay', canplayHandler);
+                const playResult = this.play();
+                if (playResult) {
+                  playResult.catch((error: unknown) => {
+                    console.error('[VideoIntroPage] ❌ 播放失败:', error);
+                  });
+                }
               };
-              videoElement.addEventListener('canplay', canplayHandler);
+              safeVideoElement.addEventListener('canplay', canplayHandler);
               
               // 超时保护:如果 3 秒后还没 canplay,尝试直接播放
               setTimeout(() => {
-                videoElement.removeEventListener('canplay', canplayHandler);
-                if (videoElement.paused) {
+                safeVideoElement.removeEventListener('canplay', canplayHandler);
+                if (safeVideoElement.paused) {
                   console.log('[VideoIntroPage] ⏰ 超时,尝试直接播放');
-                  this.play().catch((error) => {
-                    console.error('[VideoIntroPage] ❌ 超时后播放失败:', error);
-                  });
+                  const playResult = this.play();
+                  if (playResult) {
+                    playResult.catch((error: unknown) => {
+                      console.error('[VideoIntroPage] ❌ 超时后播放失败:', error);
+                    });
+                  }
                 }
               }, 3000);
             }
           }
         });
 
-        this.on('error', (error) => {
+        this.on('error', (error: unknown) => {
           // 视频加载/播放错误,输出调试信息
           const videoElement = this.el().querySelector('video') as HTMLVideoElement;
           const errorInfo = videoElement ? {
@@ -443,6 +450,9 @@ export const VideoIntroPage = () => {
     if (!isLoading && playerRef.current) {
       console.log('[VideoIntroPage] 加载完成,触发播放');
       const player = playerRef.current;
+      if (!player) {
+        return;
+      }
       const videoElement = player.el().querySelector('video') as HTMLVideoElement;
       
       if (videoElement) {
@@ -452,14 +462,21 @@ export const VideoIntroPage = () => {
         videoElement.volume = 1.0;
         
         // 尝试播放
-        player.play().catch((error) => {
-          console.warn('[VideoIntroPage] 自动播放失败,尝试静音播放:', error);
-          // 如果失败,尝试静音播放
-          player.muted(true);
-          player.play().catch((err) => {
-            console.error('[VideoIntroPage] 静音播放也失败:', err);
-          });
-        });
+        const playPromise = player.play();
+        if (playPromise) {
+          playPromise
+            .catch((error: unknown) => {
+              console.warn('[VideoIntroPage] 自动播放失败,尝试静音播放:', error);
+              // 如果失败,尝试静音播放
+              player.muted(true);
+              const mutedPlayPromise = player.play();
+              if (mutedPlayPromise) {
+                mutedPlayPromise.catch((err: unknown) => {
+                  console.error('[VideoIntroPage] 静音播放也失败:', err);
+                });
+              }
+            });
+        }
       }
     }
   }, [isLoading]);

+ 17 - 7
frontend/src/pages/WelcomePage copy.tsx

@@ -137,6 +137,9 @@ export const WelcomePage = () => {
     if (viewState === 'playing' && playerRef.current) {
       console.log('[WelcomePage] 开始播放视频');
       const player = playerRef.current;
+      if (!player) {
+        return;
+      }
       const videoElement = player.el().querySelector('video') as HTMLVideoElement;
       
       if (videoElement) {
@@ -156,13 +159,20 @@ export const WelcomePage = () => {
         videoElement.volume = 1.0;
         
         // 尝试播放
-        player.play().catch((error) => {
-          console.warn('[WelcomePage] 自动播放失败,尝试静音播放:', error);
-          player.muted(true);
-          player.play().catch((err) => {
-            console.error('[WelcomePage] 静音播放也失败:', err);
-          });
-        });
+        const playPromise = player.play();
+        if (playPromise) {
+          playPromise
+            .catch((error: unknown) => {
+              console.warn('[WelcomePage] 自动播放失败,尝试静音播放:', error);
+              player.muted(true);
+              const mutedPlayPromise = player.play();
+              if (mutedPlayPromise) {
+                mutedPlayPromise.catch((err: unknown) => {
+                  console.error('[WelcomePage] 静音播放也失败:', err);
+                });
+              }
+            });
+        }
       }
     }
   }, [viewState]);

+ 1 - 0
frontend/src/pages/WelcomePage.css

@@ -45,6 +45,7 @@
   background-size: cover;
   background-position: center;
   background-repeat: no-repeat;
+  background-image: url('../assets/chatbot-bg.svg');
   z-index: 0;
 }
 

+ 18 - 14
frontend/src/pages/WelcomePage.tsx

@@ -3,7 +3,6 @@ import { useNavigate } from 'react-router-dom';
 import videojs from 'video.js';
 import { setStorage, STORAGE_KEYS, markRouteVisited } from '@/utils/storage';
 import 'video.js/dist/video-js.css';
-import chatbotBg from '@/assets/chatbot-bg.svg';
 import lumiImage from '@/assets/Lumi-image.png';
 import helloImage from '@/assets/Hello,.png';
 import './WelcomePage.css';
@@ -137,6 +136,9 @@ export const WelcomePage = () => {
     if (viewState === 'playing' && playerRef.current) {
       console.log('[WelcomePage] 开始播放视频');
       const player = playerRef.current;
+      if (!player) {
+        return;
+      }
       const videoElement = player.el().querySelector('video') as HTMLVideoElement;
       
       if (videoElement) {
@@ -156,13 +158,20 @@ export const WelcomePage = () => {
         videoElement.volume = 1.0;
         
         // 尝试播放
-        player.play().catch((error) => {
-          console.warn('[WelcomePage] 自动播放失败,尝试静音播放:', error);
-          player.muted(true);
-          player.play().catch((err) => {
-            console.error('[WelcomePage] 静音播放也失败:', err);
-          });
-        });
+        const playPromise = player.play();
+        if (playPromise) {
+          playPromise
+            .catch((error: unknown) => {
+              console.warn('[WelcomePage] 自动播放失败,尝试静音播放:', error);
+              player.muted(true);
+              const mutedPlayPromise = player.play();
+              if (mutedPlayPromise) {
+                mutedPlayPromise.catch((err: unknown) => {
+                  console.error('[WelcomePage] 静音播放也失败:', err);
+                });
+              }
+            });
+        }
       }
     }
   }, [viewState]);
@@ -199,12 +208,7 @@ export const WelcomePage = () => {
   return (
     <div className="welcome-page">
       {/* 背景层 - 全屏覆盖 */}
-      <div 
-        className="welcome-bg"
-        style={{
-          backgroundImage: `url(${chatbotBg})`,
-        }}
-      />
+      <div className="welcome-bg" />
       
       {/* 欢迎界面内容 */}
       {viewState === 'welcome' && (

+ 20 - 0
frontend/src/vite-env.d.ts

@@ -8,3 +8,23 @@ interface ImportMeta {
   readonly env: ImportMetaEnv;
 }
 
+declare module '*.svg' {
+  const src: string;
+  export default src;
+}
+
+declare module '*.png' {
+  const src: string;
+  export default src;
+}
+
+declare module '*.gif' {
+  const src: string;
+  export default src;
+}
+
+declare module '*.GIF' {
+  const src: string;
+  export default src;
+}
+

+ 3 - 4
nginx.conf

@@ -89,9 +89,9 @@ http {
             proxy_connect_timeout 10s;
         }
 
-        # API接口代理(如果有REST API需求)
+        # API接口代理(保持 /api 前缀透传给后端)
         location ^~ /api/ {
-            proxy_pass http://127.0.0.1:8766/;
+            proxy_pass http://127.0.0.1:8766$request_uri;
             proxy_set_header Host $host;
             proxy_set_header X-Real-IP $remote_addr;
             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@@ -100,8 +100,7 @@ http {
             proxy_send_timeout 60s;
             proxy_connect_timeout 10s;
         }
-
-        # 客户端指标上报接口代理
+# 客户端指标上报接口代理
         location = /metrics/client {
             proxy_pass http://127.0.0.1:8766/metrics/client;
             proxy_set_header Host $host;