zlwu 11 maanden geleden
bovenliggende
commit
f8ad0138c6

+ 4 - 7
backend/main.py

@@ -10,7 +10,6 @@ from contextlib import asynccontextmanager
 from config import settings, get_dashscope_domain
 from config import settings, get_dashscope_domain
 from websocket_handler import WebSocketHandler
 from websocket_handler import WebSocketHandler
 from database import init_database, close_database
 from database import init_database, close_database
-import redis.asyncio as redis
 from services.redis_cache_service import RedisCacheService
 from services.redis_cache_service import RedisCacheService
 from services.user_service import user_service
 from services.user_service import user_service
 
 
@@ -31,18 +30,16 @@ async def lifespan(app: FastAPI):
     try:
     try:
         await init_database()
         await init_database()
         
         
-        # 获取Redis客户端(已配置健康检查)
+        # 获取Redis客户端(已配置健康检查),用于验证连通性
         from database import db_manager
         from database import db_manager
-        redis_client = await db_manager.get_redis()
+        await db_manager.get_redis()
         
         
-        # 创建Redis缓存服务
-        app.state.redis_cache = RedisCacheService(redis_client)
-        app.state.redis_client = redis_client
+        # 创建Redis缓存服务,按需获取最新连接
+        app.state.redis_cache = RedisCacheService(db_manager.get_redis)
         
         
     except Exception as e:
     except Exception as e:
         logger.error(f"❌ 数据库初始化失败: {e}")
         logger.error(f"❌ 数据库初始化失败: {e}")
         app.state.redis_cache = None
         app.state.redis_cache = None
-        app.state.redis_client = None
     
     
     # 🔥 启动HTTP连接保活任务(保持连接活跃,避免长时间不交互后响应变慢)
     # 🔥 启动HTTP连接保活任务(保持连接活跃,避免长时间不交互后响应变慢)
     try:
     try:

+ 15 - 5
backend/services/context_service.py

@@ -3,8 +3,9 @@
 """
 """
 import json
 import json
 import asyncio
 import asyncio
-from typing import List, Dict, Optional
+from typing import Awaitable, Callable, List, Dict, Optional
 import logging
 import logging
+import redis.asyncio as aioredis
 from sqlalchemy import select
 from sqlalchemy import select
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
@@ -22,19 +23,28 @@ class ContextService:
         """
         """
         self.db = db_manager
         self.db = db_manager
         self.redis = None
         self.redis = None
+        self._redis_provider: Optional[Callable[[], Awaitable[aioredis.Redis]]] = None
         if db_manager:
         if db_manager:
             # 🔥 延迟获取Redis(在首次使用时)
             # 🔥 延迟获取Redis(在首次使用时)
-            pass
+            self._redis_provider = db_manager.get_redis
 
 
     async def set_db_manager(self, db_manager):
     async def set_db_manager(self, db_manager):
         """设置数据库管理器(用于延迟初始化)"""
         """设置数据库管理器(用于延迟初始化)"""
         self.db = db_manager
         self.db = db_manager
-        self.redis = await db_manager.get_redis()
+        self._redis_provider = db_manager.get_redis
+        self.redis = None
     
     
     async def _ensure_redis(self):
     async def _ensure_redis(self):
         """确保Redis连接可用"""
         """确保Redis连接可用"""
-        if not self.redis and self.db:
-            self.redis = await self.db.get_redis()
+        if not self._redis_provider:
+            if not self.db:
+                raise RuntimeError("Redis 未初始化,缺少数据库管理器")
+            self._redis_provider = self.db.get_redis
+        redis_client = await self._redis_provider()
+        if not redis_client:
+            raise RuntimeError("Redis 客户端不可用")
+        self.redis = redis_client
+        return redis_client
     
     
     async def get_context_for_round(
     async def get_context_for_round(
         self, 
         self, 

+ 17 - 6
backend/services/message_service.py

@@ -5,12 +5,13 @@ import uuid
 import json
 import json
 import asyncio
 import asyncio
 from datetime import datetime
 from datetime import datetime
-from typing import Dict, Optional
+from typing import Awaitable, Callable, Dict, Optional
 import logging
 import logging
 from sqlalchemy import select, update, func
 from sqlalchemy import select, update, func
 from sqlalchemy.orm import selectinload
 from sqlalchemy.orm import selectinload
 from database import db_manager
 from database import db_manager
 from models_db import Message, User
 from models_db import Message, User
+import redis.asyncio as aioredis
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -27,19 +28,28 @@ class MessageService:
         """
         """
         self.db = db_manager
         self.db = db_manager
         self.redis = None
         self.redis = None
+        self._redis_provider: Optional[Callable[[], Awaitable[aioredis.Redis]]] = None
         if db_manager:
         if db_manager:
             # 🔥 延迟获取Redis(在首次使用时)
             # 🔥 延迟获取Redis(在首次使用时)
-            pass
+            self._redis_provider = db_manager.get_redis
 
 
     async def set_db_manager(self, db_manager):
     async def set_db_manager(self, db_manager):
         """设置数据库管理器(用于延迟初始化)"""
         """设置数据库管理器(用于延迟初始化)"""
         self.db = db_manager
         self.db = db_manager
-        self.redis = await db_manager.get_redis()
+        self._redis_provider = db_manager.get_redis
+        self.redis = None
     
     
     async def _ensure_redis(self):
     async def _ensure_redis(self):
         """确保Redis连接可用"""
         """确保Redis连接可用"""
-        if not self.redis and self.db:
-            self.redis = await self.db.get_redis()
+        if not self._redis_provider:
+            if not self.db:
+                raise RuntimeError("Redis 未初始化,缺少数据库管理器")
+            self._redis_provider = self.db.get_redis
+        redis_client = await self._redis_provider()
+        if not redis_client:
+            raise RuntimeError("Redis 客户端不可用")
+        self.redis = redis_client
+        return redis_client
     
     
     async def save_message(
     async def save_message(
         self,
         self,
@@ -466,8 +476,9 @@ class MessageService:
             当前轮次(从1开始)
             当前轮次(从1开始)
         """
         """
         try:
         try:
+            redis = await self._ensure_redis()
             meta_key = f"user:{user_id}:meta"
             meta_key = f"user:{user_id}:meta"
-            current_round = await self.redis.hget(meta_key, 'current_round')
+            current_round = await redis.hget(meta_key, 'current_round')
             
             
             if current_round:
             if current_round:
                 return int(current_round)
                 return int(current_round)

+ 44 - 23
backend/services/redis_cache_service.py

@@ -4,9 +4,9 @@ Redis缓存管理服务
 """
 """
 import json
 import json
 import time
 import time
-import asyncio
-from typing import Dict, List, Optional
+from typing import Awaitable, Callable, Dict, List, Optional
 import logging
 import logging
+import redis.asyncio as aioredis
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -14,14 +14,16 @@ logger = logging.getLogger(__name__)
 class RedisCacheService:
 class RedisCacheService:
     """Redis缓存服务"""
     """Redis缓存服务"""
     
     
-    def __init__(self, redis_client):
+    def __init__(self, redis_provider: Callable[[], Awaitable[aioredis.Redis]]):
         """
         """
         初始化Redis缓存服务
         初始化Redis缓存服务
         
         
         Args:
         Args:
-            redis_client: redis.asyncio.Redis 实例
+            redis_provider: 一个可调用对象,返回 redis.asyncio.Redis 实例
         """
         """
-        self.redis = redis_client
+        if not callable(redis_provider):
+            raise ValueError("redis_provider 必须可调用")
+        self._redis_provider = redis_provider
         
         
         # 缓存Key设计
         # 缓存Key设计
         self.KEY_USER_CONTEXT = "context:{user_id}"           # 用户上下文
         self.KEY_USER_CONTEXT = "context:{user_id}"           # 用户上下文
@@ -34,6 +36,13 @@ class RedisCacheService:
         self.TTL_MESSAGES = 86400     # 24小时
         self.TTL_MESSAGES = 86400     # 24小时
         self.TTL_SUMMARY = 604800     # 7天
         self.TTL_SUMMARY = 604800     # 7天
     
     
+    async def _get_redis(self) -> aioredis.Redis:
+        """获取最新的Redis客户端,确保连接有效"""
+        redis_client = await self._redis_provider()
+        if not redis_client:
+            raise RuntimeError("Redis 客户端不可用")
+        return redis_client
+    
     async def get_context(self, user_id: str) -> Optional[Dict]:
     async def get_context(self, user_id: str) -> Optional[Dict]:
         """
         """
         从Redis获取用户上下文(零延迟)
         从Redis获取用户上下文(零延迟)
@@ -47,8 +56,9 @@ class RedisCacheService:
             }
             }
         """
         """
         try:
         try:
+            redis = await self._get_redis()
             key = self.KEY_USER_CONTEXT.format(user_id=user_id)
             key = self.KEY_USER_CONTEXT.format(user_id=user_id)
-            cached = await self.redis.get(key)
+            cached = await redis.get(key)
             
             
             if cached:
             if cached:
                 context = json.loads(cached)
                 context = json.loads(cached)
@@ -77,10 +87,11 @@ class RedisCacheService:
             ttl: 过期时间(秒),默认24小时
             ttl: 过期时间(秒),默认24小时
         """
         """
         try:
         try:
+            redis = await self._get_redis()
             key = self.KEY_USER_CONTEXT.format(user_id=user_id)
             key = self.KEY_USER_CONTEXT.format(user_id=user_id)
             ttl = ttl or self.TTL_CONTEXT
             ttl = ttl or self.TTL_CONTEXT
             
             
-            await self.redis.setex(
+            await redis.setex(
                 key,
                 key,
                 ttl,
                 ttl,
                 json.dumps(context, ensure_ascii=False)
                 json.dumps(context, ensure_ascii=False)
@@ -106,6 +117,7 @@ class RedisCacheService:
         使用Redis List结构,支持高效追加和范围查询
         使用Redis List结构,支持高效追加和范围查询
         """
         """
         try:
         try:
+            redis = await self._get_redis()
             key = self.KEY_USER_MESSAGES.format(user_id=user_id)
             key = self.KEY_USER_MESSAGES.format(user_id=user_id)
             
             
             message = {
             message = {
@@ -116,13 +128,13 @@ class RedisCacheService:
             }
             }
             
             
             # 追加到List尾部
             # 追加到List尾部
-            await self.redis.rpush(key, json.dumps(message, ensure_ascii=False))
+            await redis.rpush(key, json.dumps(message, ensure_ascii=False))
             
             
             # 设置过期时间
             # 设置过期时间
-            await self.redis.expire(key, self.TTL_MESSAGES)
+            await redis.expire(key, self.TTL_MESSAGES)
             
             
             # 限制List长度(最多保留200条消息)
             # 限制List长度(最多保留200条消息)
-            await self.redis.ltrim(key, -200, -1)
+            await redis.ltrim(key, -200, -1)
             
             
             logger.debug(f"✅ Redis追加消息: user={user_id}, round={round_number}, role={role}")
             logger.debug(f"✅ Redis追加消息: user={user_id}, round={round_number}, role={role}")
             return True
             return True
@@ -144,10 +156,11 @@ class RedisCacheService:
             count: 消息数量(默认30条,即15轮)
             count: 消息数量(默认30条,即15轮)
         """
         """
         try:
         try:
+            redis = await self._get_redis()
             key = self.KEY_USER_MESSAGES.format(user_id=user_id)
             key = self.KEY_USER_MESSAGES.format(user_id=user_id)
             
             
             # 获取最后N条消息
             # 获取最后N条消息
-            messages_raw = await self.redis.lrange(key, -count, -1)
+            messages_raw = await redis.lrange(key, -count, -1)
             
             
             messages = []
             messages = []
             for msg_bytes in messages_raw:
             for msg_bytes in messages_raw:
@@ -169,8 +182,9 @@ class RedisCacheService:
     async def get_current_round(self, user_id: str) -> int:
     async def get_current_round(self, user_id: str) -> int:
         """获取当前轮次"""
         """获取当前轮次"""
         try:
         try:
+            redis = await self._get_redis()
             key = self.KEY_USER_ROUND.format(user_id=user_id)
             key = self.KEY_USER_ROUND.format(user_id=user_id)
-            round_bytes = await self.redis.get(key)
+            round_bytes = await redis.get(key)
             
             
             if round_bytes:
             if round_bytes:
                 round_str = round_bytes.decode('utf-8') if isinstance(round_bytes, bytes) else round_bytes
                 round_str = round_bytes.decode('utf-8') if isinstance(round_bytes, bytes) else round_bytes
@@ -184,8 +198,9 @@ class RedisCacheService:
     async def set_current_round(self, user_id: str, round_number: int) -> bool:
     async def set_current_round(self, user_id: str, round_number: int) -> bool:
         """设置当前轮次"""
         """设置当前轮次"""
         try:
         try:
+            redis = await self._get_redis()
             key = self.KEY_USER_ROUND.format(user_id=user_id)
             key = self.KEY_USER_ROUND.format(user_id=user_id)
-            await self.redis.setex(key, self.TTL_CONTEXT, str(round_number))
+            await redis.setex(key, self.TTL_CONTEXT, str(round_number))
             return True
             return True
         except Exception as e:
         except Exception as e:
             logger.error(f"❌ Redis设置轮次失败: {e}")
             logger.error(f"❌ Redis设置轮次失败: {e}")
@@ -194,14 +209,15 @@ class RedisCacheService:
     async def increment_round(self, user_id: str) -> int:
     async def increment_round(self, user_id: str) -> int:
         """轮次自增"""
         """轮次自增"""
         try:
         try:
+            redis = await self._get_redis()
             key = self.KEY_USER_ROUND.format(user_id=user_id)
             key = self.KEY_USER_ROUND.format(user_id=user_id)
-            new_round = await self.redis.incr(key)
-            await self.redis.expire(key, self.TTL_CONTEXT)
+            new_round = await redis.incr(key)
+            await redis.expire(key, self.TTL_CONTEXT)
             
             
             # 同时更新 user:{user_id}:meta 中的 current_round(保持一致性)
             # 同时更新 user:{user_id}:meta 中的 current_round(保持一致性)
             meta_key = f"user:{user_id}:meta"
             meta_key = f"user:{user_id}:meta"
-            await self.redis.hset(meta_key, 'current_round', new_round)
-            await self.redis.expire(meta_key, self.TTL_CONTEXT)
+            await redis.hset(meta_key, 'current_round', new_round)
+            await redis.expire(meta_key, self.TTL_CONTEXT)
             
             
             logger.debug(f"✅ Redis轮次自增: user={user_id}, new_round={new_round}")
             logger.debug(f"✅ Redis轮次自增: user={user_id}, new_round={new_round}")
             return new_round
             return new_round
@@ -213,8 +229,9 @@ class RedisCacheService:
     async def get_summary(self, user_id: str, end_round: int) -> Optional[str]:
     async def get_summary(self, user_id: str, end_round: int) -> Optional[str]:
         """获取历史总结"""
         """获取历史总结"""
         try:
         try:
+            redis = await self._get_redis()
             key = self.KEY_USER_SUMMARY.format(user_id=user_id, end_round=end_round)
             key = self.KEY_USER_SUMMARY.format(user_id=user_id, end_round=end_round)
-            summary_bytes = await self.redis.get(key)
+            summary_bytes = await redis.get(key)
             
             
             if summary_bytes:
             if summary_bytes:
                 summary = summary_bytes.decode('utf-8') if isinstance(summary_bytes, bytes) else summary_bytes
                 summary = summary_bytes.decode('utf-8') if isinstance(summary_bytes, bytes) else summary_bytes
@@ -235,8 +252,9 @@ class RedisCacheService:
     ) -> bool:
     ) -> bool:
         """保存历史总结"""
         """保存历史总结"""
         try:
         try:
+            redis = await self._get_redis()
             key = self.KEY_USER_SUMMARY.format(user_id=user_id, end_round=end_round)
             key = self.KEY_USER_SUMMARY.format(user_id=user_id, end_round=end_round)
-            await self.redis.setex(key, self.TTL_SUMMARY, summary)
+            await redis.setex(key, self.TTL_SUMMARY, summary)
             
             
             logger.info(f"✅ Redis保存总结: user={user_id}, end_round={end_round}, 长度={len(summary)}字")
             logger.info(f"✅ Redis保存总结: user={user_id}, end_round={end_round}, 长度={len(summary)}字")
             return True
             return True
@@ -394,8 +412,9 @@ class RedisCacheService:
     async def invalidate_context(self, user_id: str) -> bool:
     async def invalidate_context(self, user_id: str) -> bool:
         """清除用户上下文缓存(强制刷新)"""
         """清除用户上下文缓存(强制刷新)"""
         try:
         try:
+            redis = await self._get_redis()
             key = self.KEY_USER_CONTEXT.format(user_id=user_id)
             key = self.KEY_USER_CONTEXT.format(user_id=user_id)
-            await self.redis.delete(key)
+            await redis.delete(key)
             logger.info(f"🗑️ Redis清除缓存: user={user_id}")
             logger.info(f"🗑️ Redis清除缓存: user={user_id}")
             return True
             return True
         except Exception as e:
         except Exception as e:
@@ -412,13 +431,14 @@ class RedisCacheService:
             current_round: 当前轮次
             current_round: 当前轮次
         """
         """
         try:
         try:
+            redis = await self._get_redis()
             # 清空旧数据
             # 清空旧数据
             messages_key = self.KEY_USER_MESSAGES.format(user_id=user_id)
             messages_key = self.KEY_USER_MESSAGES.format(user_id=user_id)
-            await self.redis.delete(messages_key)
+            await redis.delete(messages_key)
             
             
             # 批量写入消息
             # 批量写入消息
             if messages:
             if messages:
-                pipeline = self.redis.pipeline()
+                pipeline = redis.pipeline()
                 for msg in messages:
                 for msg in messages:
                     pipeline.rpush(
                     pipeline.rpush(
                         messages_key,
                         messages_key,
@@ -440,7 +460,8 @@ class RedisCacheService:
     async def health_check(self) -> bool:
     async def health_check(self) -> bool:
         """健康检查"""
         """健康检查"""
         try:
         try:
-            await self.redis.ping()
+            redis = await self._get_redis()
+            await redis.ping()
             return True
             return True
         except Exception as e:
         except Exception as e:
             logger.error(f"❌ Redis健康检查失败: {e}")
             logger.error(f"❌ Redis健康检查失败: {e}")

+ 19 - 6
backend/services/summary_service.py

@@ -2,9 +2,10 @@
 对话总结服务(SQLAlchemy ORM 版本)
 对话总结服务(SQLAlchemy ORM 版本)
 """
 """
 import uuid
 import uuid
-from typing import List, Dict
+from typing import Awaitable, Callable, List, Dict, Optional
 import logging
 import logging
 from sqlalchemy import select
 from sqlalchemy import select
+import redis.asyncio as aioredis
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -21,11 +22,21 @@ class SummaryService:
         """
         """
         self.db = db_manager
         self.db = db_manager
         self.redis = None  # 🔥 延迟获取Redis
         self.redis = None  # 🔥 延迟获取Redis
+        self._redis_provider: Optional[Callable[[], Awaitable[aioredis.Redis]]] = (
+            db_manager.get_redis if db_manager else None
+        )
     
     
     async def _ensure_redis(self):
     async def _ensure_redis(self):
         """确保Redis连接可用"""
         """确保Redis连接可用"""
-        if not self.redis and self.db:
-            self.redis = await self.db.get_redis()
+        if not self._redis_provider:
+            if not self.db:
+                raise RuntimeError("Redis 未初始化,缺少数据库管理器")
+            self._redis_provider = self.db.get_redis
+        redis_client = await self._redis_provider()
+        if not redis_client:
+            raise RuntimeError("Redis 客户端不可用")
+        self.redis = redis_client
+        return redis_client
     
     
     async def generate_summary(
     async def generate_summary(
         self,
         self,
@@ -328,8 +339,9 @@ class SummaryService:
             summary_text: 总结文本
             summary_text: 总结文本
         """
         """
         try:
         try:
+            redis = await self._ensure_redis()
             cache_key = f"user:{user_id}:latest_summary"
             cache_key = f"user:{user_id}:latest_summary"
-            await self.redis.setex(cache_key, 86400, summary_text)
+            await redis.setex(cache_key, 86400, summary_text)
             logger.debug(f"✅ 总结已缓存: user={user_id}")
             logger.debug(f"✅ 总结已缓存: user={user_id}")
         except Exception as e:
         except Exception as e:
             logger.warning(f"⚠️ 缓存总结失败: user={user_id}, error={e}")
             logger.warning(f"⚠️ 缓存总结失败: user={user_id}, error={e}")
@@ -345,9 +357,10 @@ class SummaryService:
             总结文本或空字符串
             总结文本或空字符串
         """
         """
         try:
         try:
+            redis = await self._ensure_redis()
             # 先查Redis
             # 先查Redis
             cache_key = f"user:{user_id}:latest_summary"
             cache_key = f"user:{user_id}:latest_summary"
-            cached = await self.redis.get(cache_key)
+            cached = await redis.get(cache_key)
             
             
             if cached:
             if cached:
                 return cached
                 return cached
@@ -368,7 +381,7 @@ class SummaryService:
                 if summary:
                 if summary:
                     summary_text = summary.summary_text
                     summary_text = summary.summary_text
                 # 回填缓存
                 # 回填缓存
-                await self.redis.setex(cache_key, 86400, summary_text)
+                await redis.setex(cache_key, 86400, summary_text)
                 return summary_text
                 return summary_text
             
             
             return ""
             return ""

+ 16 - 7
backend/文档/Redis框架选择说明.md

@@ -4,25 +4,34 @@
 
 
 ### 当前方案
 ### 当前方案
 - **客户端库**: `redis[asyncio]` (官方异步 Redis 客户端)
 - **客户端库**: `redis[asyncio]` (官方异步 Redis 客户端)
-- **封装层**: `RedisCacheService` (自定义服务层)
-- **状态**: ✅ 已实现连接池、健康检查、自动重连
+- **连接管理**: `DatabaseManager` 统一维护连接池、健康检查、后台重连
+- **服务接入**: `RedisCacheService/MessageService/ContextService/SummaryService` 通过 `redis_provider` **按需获取最新连接**,避免复用已关闭的句柄
+- **状态**: ✅ 已实现连接池、健康检查、自动重连、连接刷新
 
 
 ### 代码示例
 ### 代码示例
 ```python
 ```python
-# database.py - 连接管理
+# database.py - 连接管理 & 健康检查
 self.redis_client = await aioredis.from_url(
 self.redis_client = await aioredis.from_url(
     redis_url,
     redis_url,
-    encoding="utf-8",
-    decode_responses=True,
     max_connections=settings.REDIS_MAX_CONNECTIONS,
     max_connections=settings.REDIS_MAX_CONNECTIONS,
     health_check_interval=30,
     health_check_interval=30,
 )
 )
 
 
-# redis_cache_service.py - 业务封装
+# services/redis_cache_service.py - 业务封装(关键:实时获取连接)
 class RedisCacheService:
 class RedisCacheService:
+    def __init__(self, redis_provider: Callable[[], Awaitable[aioredis.Redis]]):
+        self._redis_provider = redis_provider
+
+    async def _get_redis(self) -> aioredis.Redis:
+        redis_client = await self._redis_provider()
+        if not redis_client:
+            raise RuntimeError("Redis 客户端不可用")
+        return redis_client
+
     async def get_context(self, user_id: str) -> Optional[Dict]:
     async def get_context(self, user_id: str) -> Optional[Dict]:
+        redis = await self._get_redis()
         key = self.KEY_USER_CONTEXT.format(user_id=user_id)
         key = self.KEY_USER_CONTEXT.format(user_id=user_id)
-        cached = await self.redis.get(key)
+        cached = await redis.get(key)
         return json.loads(cached) if cached else None
         return json.loads(cached) if cached else None
 ```
 ```
 
 

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

@@ -119,6 +119,8 @@ ssl/
 
 
 
 
 
 
+
+
 
 
 
 
 
 

BIN
frontend/src/assets/02.GIF


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

@@ -15,6 +15,7 @@ import '@/styles/chatbot-fix.css';
 import avatarC from '@/assets/Avatar-C.svg';
 import avatarC from '@/assets/Avatar-C.svg';
 import treesImage from '@/assets/Group-3465183.svg';
 import treesImage from '@/assets/Group-3465183.svg';
 import animaZoneAvatar from '@/assets/Avatar-anima-zone.png';
 import animaZoneAvatar from '@/assets/Avatar-anima-zone.png';
+import animaZoneAvatarGif from '@/assets/02.GIF';
 import audioButton from '@/assets/audio-button.png';
 import audioButton from '@/assets/audio-button.png';
 import { AudioWave } from '@/components/AudioWave';
 import { AudioWave } from '@/components/AudioWave';
 import microphoneIcon from '@/assets/microphone-icon.svg';
 import microphoneIcon from '@/assets/microphone-icon.svg';
@@ -658,6 +659,10 @@ export const ChatBotPage = () => {
     remainingTimeMs !== null &&
     remainingTimeMs !== null &&
     remainingTimeMs <= COUNTDOWN_THRESHOLD_MS;
     remainingTimeMs <= COUNTDOWN_THRESHOLD_MS;
 
 
+  const isAvatarAnimating = currentState === 'playing';
+  const currentAvatarSrc = isAvatarAnimating ? animaZoneAvatarGif : animaZoneAvatar;
+  const currentAvatarAlt = isAvatarAnimating ? 'AnimaZone Avatar Animated' : 'AnimaZone Avatar';
+
   return (
   return (
     <div 
     <div 
       className={`chatbot-container relative w-full ${
       className={`chatbot-container relative w-full ${
@@ -726,8 +731,8 @@ export const ChatBotPage = () => {
 
 
         {/* AnimaZone 头像 - 设计稿:top 248px (树木175px + 73px), 尺寸 343×340, 左右居中 */}
         {/* AnimaZone 头像 - 设计稿:top 248px (树木175px + 73px), 尺寸 343×340, 左右居中 */}
         <img
         <img
-          src={animaZoneAvatar}
-          alt="AnimaZone Avatar"
+          src={currentAvatarSrc}
+          alt={currentAvatarAlt}
           className="chatbot-anima-zone-avatar absolute"
           className="chatbot-anima-zone-avatar absolute"
           style={{
           style={{
             width: `${layout.mainAvatarWidth}px`,
             width: `${layout.mainAvatarWidth}px`,

+ 1 - 0
frontend/vite.config.ts

@@ -38,6 +38,7 @@ export default defineConfig({
       '@': path.resolve(__dirname, './src'),
       '@': path.resolve(__dirname, './src'),
     },
     },
   },
   },
+  assetsInclude: ['**/*.GIF'],
   server: {
   server: {
     port: 3000,
     port: 3000,
     host: '0.0.0.0',  // 允许外部访问
     host: '0.0.0.0',  // 允许外部访问