Bladeren bron

第一次提交

“陈博论” 7 maanden geleden
commit
f529344911

+ 335 - 0
.gitignore

@@ -0,0 +1,335 @@
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+Pipfile.lock
+
+# poetry
+poetry.lock
+
+# pdm
+.pdm.toml
+
+# PEP 582
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+.idea/
+
+# VS Code
+.vscode/
+
+# Sublime Text
+*.sublime-project
+*.sublime-workspace
+
+# Vim
+*.swp
+*.swo
+*~
+
+# Emacs
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# macOS
+.DS_Store
+.AppleDouble
+.LSOverride
+Icon
+._*
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# Windows
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+*.tmp
+*.temp
+Desktop.ini
+$RECYCLE.BIN/
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+*.lnk
+
+# Linux
+*~
+.fuse_hidden*
+.directory
+.Trash-*
+.nfs*
+
+# Logs
+logs/
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Runtime data
+pids/
+*.pid
+*.seed
+*.pid.lock
+
+# Coverage directory used by tools like istanbul
+coverage/
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage
+.grunt
+
+# Bower dependency directory
+bower_components
+
+# node_modules
+node_modules/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+public
+
+# Vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+# 项目特定的忽略文件
+# 阿里云API密钥文件(如果存在)
+*.key
+*.pem
+secrets.json
+config.json
+
+# 临时文件
+temp/
+tmp/
+*.tmp
+*.temp
+
+# 上传文件目录(如果存在)
+uploads/
+user_files/
+
+# 数据库文件
+*.db
+*.sqlite
+*.sqlite3
+
+# 备份文件
+*.bak
+*.backup
+*.old
+
+# 压缩文件
+*.zip
+*.tar.gz
+*.rar
+*.7z 

+ 135 - 0
README.md

@@ -0,0 +1,135 @@
+# 聚德AI短视频脚本撰写系统
+
+一个基于Flask的AI短视频脚本撰写平台,支持钉钉OAuth登录认证。
+
+## 功能特性
+
+- 🔐 **钉钉OAuth认证** - 企业级安全登录
+- 🤖 **AI脚本生成** - 基于阿里云百炼API
+- 💬 **智能对话** - 支持多轮对话和上下文记忆
+- 📱 **响应式设计** - 现代化UI界面
+- 🔒 **JWT认证** - 安全的API访问控制
+
+## 系统架构
+
+```
+jude-AIwriting/
+├── app.py                 # Flask主应用
+├── auth_middleware.py     # JWT认证中间件
+├── auth_routes.py         # 认证相关路由
+├── config.py             # 配置管理
+├── utils.py              # 工具函数
+├── requirements.txt       # Python依赖
+├── static/               # 静态资源
+│   ├── css/             # 样式文件
+│   ├── js/              # JavaScript文件
+│   └── fonts/           # 字体文件
+└── templates/            # HTML模板
+    ├── index.html        # 主页面
+    └── login.html        # 登录页面
+```
+
+## 核心组件
+
+### 1. 认证系统 (`auth_routes.py`)
+- 钉钉OAuth登录流程
+- JWT token生成和验证
+- 用户信息管理
+
+### 2. 认证中间件 (`auth_middleware.py`)
+- 登录状态检查
+- API访问控制
+- Token验证
+
+### 3. 主应用 (`app.py`)
+- Flask应用配置
+- Session管理
+- 路由注册
+
+## 安装和配置
+
+### 1. 安装依赖
+```bash
+pip install -r requirements.txt
+```
+
+### 2. 环境变量配置
+创建 `.env` 文件:
+```env
+# 钉钉应用配置
+DINGTALK_CLIENT_ID=your_client_id
+DINGTALK_CLIENT_SECRET=your_client_secret
+DINGTALK_REDIRECT_URI=http://localhost:8000/api/auth/dingtalk/callback
+
+# 阿里云API配置
+API_KEY=your_api_key
+APP_ID=your_app_id
+API_ENDPOINT=https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation
+
+# 应用配置
+SECRET_KEY=your_secret_key
+PORT=8000
+FLASK_DEBUG=false
+```
+
+### 3. 启动应用
+```bash
+python app.py
+```
+
+## 使用流程
+
+1. **访问应用** - 浏览器访问 `http://localhost:8000`
+2. **钉钉登录** - 点击钉钉登录按钮,完成OAuth认证
+3. **开始对话** - 输入需求,AI生成短视频脚本
+4. **管理会话** - 支持重置对话、查看历史等
+
+## 代码优化说明
+
+### 已完成的优化
+- ✅ 去除冗余的日志输出
+- ✅ 简化用户信息解析逻辑
+- ✅ 合并重复的认证代码
+- ✅ 移除不必要的sessionStorage备份
+- ✅ 简化前端JavaScript代码
+- ✅ 优化Flask配置
+
+### 核心特性保留
+- 🔒 完整的钉钉OAuth认证流程
+- 🔐 JWT token安全机制
+- 👤 用户信息显示和管理
+- 💬 AI对话功能
+- 🎨 现代化UI界面
+
+## 技术栈
+
+- **后端**: Flask + Python 3.8+
+- **认证**: JWT + 钉钉OAuth
+- **前端**: HTML5 + CSS3 + JavaScript
+- **API**: 阿里云百炼
+- **数据库**: 内存存储(可扩展为Redis/MySQL)
+
+## 安全特性
+
+- JWT token过期机制(7天)
+- 钉钉企业级认证
+- API访问频率限制
+- 输入内容验证和清理
+
+## 开发说明
+
+### 代码结构
+- 模块化设计,职责分离
+- 统一的错误处理
+- 清晰的日志记录
+- 类型提示支持
+
+### 扩展建议
+- 添加Redis缓存支持
+- 集成数据库存储
+- 增加更多AI模型支持
+- 添加用户权限管理
+
+## 许可证
+
+MIT License 

+ 1556 - 0
app.py

@@ -0,0 +1,1556 @@
+import os
+import json
+import uuid
+import time
+import logging
+from typing import Dict, Any, Generator, Optional, List
+from flask_cors import CORS
+
+import requests
+from flask import Flask, request, jsonify, render_template, session, Response, stream_with_context, redirect
+from http import HTTPStatus
+from dotenv import load_dotenv, find_dotenv
+from dashscope import Application
+from datetime import datetime
+
+# 先加载环境变量,确保后续模块读取到 .env(支持不同工作目录)
+load_dotenv(find_dotenv(), override=False)
+
+# 导入认证模块
+from auth_middleware import init_auth
+from auth_routes import auth_bp
+
+# 导入配置和工具
+from config import config
+from utils import (
+    sanitize_input, validate_user_id, rate_limit, 
+    truncate_conversation_history, log_request_info, get_beijing_time,
+    generate_session_id
+)
+
+# 导入数据库模型
+from models import init_database, db, User, Conversation, Message, get_or_create_user
+
+# 配置日志
+logging.basicConfig(
+    level=getattr(logging, config.log_level),
+    format=config.log_format
+)
+logger = logging.getLogger(__name__)
+
+# 创建Flask应用
+app = Flask(__name__)
+CORS(app)  # 启用CORS支持
+
+# 配置session
+app.secret_key = config.secret_key
+app.config['PERMANENT_SESSION_LIFETIME'] = 7 * 24 * 60 * 60  # 7天
+app.config['SESSION_COOKIE_SECURE'] = False  # 开发环境设为False
+app.config['SESSION_COOKIE_HTTPONLY'] = True
+app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
+
+# 配置数据库
+app.config['SQLALCHEMY_DATABASE_URI'] = config.get_database_url()
+app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
+app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
+    'pool_pre_ping': True,
+    'pool_recycle': 300,
+    'pool_timeout': 20,
+    'max_overflow': 0
+}
+
+# 初始化数据库
+try:
+    init_database(app)
+except Exception as e:
+    logger.error(f"数据库初始化失败: {str(e)}", exc_info=True)
+    raise
+
+# 初始化认证中间件
+auth_middleware = init_auth(app)
+app.auth_middleware = auth_middleware
+
+# 注册认证蓝图
+app.register_blueprint(auth_bp)
+
+# 存储用户当前对话ID的字典(保留用于兼容性)
+user_current_conversations: Dict[str, int] = {}
+
+def save_message_to_database(user_id: int, conversation_id: int, role: str, content: str, 
+                            thinking_content: str = None, message_order: int = 1) -> Message:
+    """保存消息到数据库"""
+    try:
+        message = Message(
+            conversation_id=conversation_id,
+            role=role,
+            content=content,
+            thinking_content=thinking_content,
+            content_type='text',
+            message_order=message_order
+        )
+        
+        db.session.add(message)
+        
+        # 更新对话的消息计数和最后消息时间
+        conversation = Conversation.query.get(conversation_id)
+        if conversation:
+            conversation.message_count += 1
+            conversation.last_message_at = get_beijing_time()
+            conversation.updated_at = get_beijing_time()
+        
+        # 一次性提交所有更改
+        db.session.commit()
+        
+        return message
+        
+    except Exception as e:
+        db.session.rollback()
+        logger.error(f"保存消息到数据库时发生错误: {str(e)}", exc_info=True)
+        raise
+
+def get_or_create_conversation(user_id: int, title: str = None, conversation_id: int = None, force_new: bool = False) -> Conversation:
+    """获取或创建对话"""
+    try:
+        # 如果强制创建新对话,直接创建
+        if force_new:
+            if not title:
+                title = f"对话 {get_beijing_time().strftime('%Y-%m-%d %H:%M')}"
+            
+            # 生成新的session_id
+            session_id = generate_session_id(user_id)
+            
+            conversation = Conversation(
+                user_id=user_id,
+                title=title,
+                conversation_type='chat',
+                session_id=session_id,
+                message_count=0
+            )
+            
+            db.session.add(conversation)
+            db.session.commit()
+            
+            # 更新用户的当前对话ID
+            user_current_conversations[str(user_id)] = conversation.conversation_id
+            
+            
+            return conversation
+        
+        # 如果指定了conversation_id,验证并返回该对话
+        if conversation_id:
+            conversation = Conversation.query.filter_by(
+                conversation_id=conversation_id,
+                user_id=user_id,
+                is_deleted=False
+            ).first()
+            if conversation:
+                # 如果对话没有session_id,生成一个
+                if not conversation.session_id:
+                    conversation.session_id = generate_session_id(user_id, conversation_id)
+                    db.session.commit()
+                
+                # 更新用户的当前对话ID
+                user_current_conversations[str(user_id)] = conversation.conversation_id
+                return conversation
+        
+        # 检查用户是否有当前对话映射
+        current_conv_id = user_current_conversations.get(str(user_id))
+        
+        if current_conv_id:
+            conversation = Conversation.query.filter_by(
+                conversation_id=current_conv_id,
+                user_id=user_id,
+                is_deleted=False
+            ).first()
+            if conversation:
+                # 如果对话没有session_id,生成一个
+                if not conversation.session_id:
+                    conversation.session_id = generate_session_id(user_id, current_conv_id)
+                    db.session.commit()
+                return conversation
+        
+        # 创建新对话
+        if not title:
+            title = f"对话 {get_beijing_time().strftime('%Y-%m-%d %H:%M')}"
+        
+        # 生成新的session_id
+        session_id = generate_session_id(user_id)
+        
+        conversation = Conversation(
+            user_id=user_id,
+            title=title,
+            conversation_type='chat',
+            session_id=session_id,
+            message_count=0
+        )
+        
+        db.session.add(conversation)
+        db.session.commit()
+        
+        # 更新用户的当前对话ID
+        user_current_conversations[str(user_id)] = conversation.conversation_id
+        
+        return conversation
+        
+    except Exception as e:
+        db.session.rollback()
+        logger.error(f"获取或创建对话时发生错误: {str(e)}", exc_info=True)
+        raise
+
+def switch_to_conversation(user_id: int, conversation_id: int) -> Conversation:
+    """切换到指定对话"""
+    try:
+        # 验证对话是否属于当前用户
+        conversation = Conversation.query.filter_by(
+            conversation_id=conversation_id,
+            user_id=user_id,
+            is_deleted=False
+        ).first()
+        
+        if not conversation:
+            raise ValueError(f"对话 {conversation_id} 不存在或无权限访问")
+        
+        # 如果对话没有session_id,生成一个
+        if not conversation.session_id:
+            conversation.session_id = generate_session_id(user_id, conversation_id)
+            db.session.commit()
+            
+        
+        # 更新用户的当前对话ID
+        user_current_conversations[str(user_id)] = conversation.conversation_id
+        
+        
+        return conversation
+        
+    except Exception as e:
+        logger.error(f"切换对话时发生错误: {str(e)}", exc_info=True)
+        raise
+
+def generate_conversation_title(user_message: str) -> str:
+    """根据用户消息生成对话标题"""
+    # 取用户消息的前50个字符作为标题
+    title = user_message.strip()[:50]
+    if len(user_message) > 50:
+        title += "..."
+    return title
+
+@app.route('/')
+def index():
+    """渲染聊天界面"""
+    # 检查是否是登录成功跳转
+    login_success = request.args.get('login_success')
+    token = request.args.get('token')
+    user_info_json = request.args.get('user_info')
+    
+    if login_success and token and user_info_json:
+        # 登录成功,处理用户信息
+        try:
+            import urllib.parse
+            import json
+            user_info = json.loads(urllib.parse.unquote(user_info_json))
+            
+            # 保存到session
+            session['auth_token'] = token
+            session['user_info'] = user_info
+            
+            # 将用户信息传递给模板
+            return render_template('index.html', user_info=user_info, jwt_token=token)
+        except Exception as e:
+            logger.error(f'处理登录成功参数失败: {e}')
+            # 如果处理失败,重定向到登录页面
+            return redirect('/login')
+    else:
+        # 正常访问,检查认证状态
+        # 优先从session获取
+        user_info = session.get('user_info', {})
+        jwt_token = session.get('auth_token', '')
+        
+        # 如果session中没有,尝试从localStorage获取(通过JavaScript)
+        if not jwt_token:
+            # 返回一个页面,让JavaScript检查localStorage
+            return render_template('index.html', user_info={}, jwt_token='')
+        
+        # 将用户信息传递给模板
+        return render_template('index.html', user_info=user_info, jwt_token=jwt_token)
+
+@app.route('/login')
+def login():
+    """登录页面"""
+    return render_template('login.html')
+
+@app.route('/login_error')
+def login_error():
+    """登录错误页面"""
+    error_type = request.args.get('error', 'unknown')
+    error_message = request.args.get('message', '')
+    error_code = request.args.get('code', '')
+
+    return render_template('login_error.html',
+                         error_type=error_type,
+                         error_message=error_message,
+                         error_code=error_code)
+
+@app.route('/api/chat', methods=['POST'])
+@auth_middleware.login_required
+@rate_limit(config.rate_limit_requests, config.rate_limit_window)
+def chat():
+    """处理聊天请求并调用阿里云百炼API (非流式响应)"""
+    try:
+        data = request.json or {}
+        message = data.get('message', '')
+        user_id = data.get('user_id', str(uuid.uuid4()))
+        agent = data.get('agent') or 'kuaishou'
+        conversation_id = data.get('conversation_id')  # 新增:支持指定对话ID
+        session_id = data.get('session_id')  # 新增:支持指定session_id
+        force_new = data.get('force_new', False)  # 新增:强制创建新对话
+        
+        # 输入验证
+        message, error = sanitize_input(message, config.max_message_length)
+        if error:
+            return jsonify({
+                'success': False,
+                'error': error
+            }), HTTPStatus.BAD_REQUEST
+        
+        user_id, error = validate_user_id(user_id)
+        if error:
+            return jsonify({
+                'success': False,
+                'error': error
+            }), HTTPStatus.BAD_REQUEST
+        
+        # 从认证中间件获取用户信息
+        auth_user_info = getattr(request, 'user_info', {})
+        auth_user_id = auth_user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        # 选择对应智能体 APP_ID
+        chosen_app_id, appid_env_key = config.get_app_id_for_agent_with_source(agent)
+        if not chosen_app_id:
+            logger.error(f"未找到对应智能体APP_ID,agent={agent}")
+            return jsonify({
+                'success': False,
+                'error': f"服务器未配置该智能体的 APP_ID: {agent}"
+            }), HTTPStatus.INTERNAL_SERVER_ERROR
+        
+        # 记录请求信息
+        log_request_info(message, user_id, 'POST')
+        
+        # 获取或创建对话
+        conversation = get_or_create_conversation(
+            auth_user_id, 
+            generate_conversation_title(message),
+            conversation_id,
+            force_new
+        )
+        
+        # 优先使用前端传来的session_id,否则使用对话的session_id
+        api_session_id = session_id or conversation.session_id
+        
+        # 准备API请求参数
+        headers = {
+            "Authorization": f"Bearer {config.api_key}",
+            "Content-Type": "application/json",
+            "X-DashScope-SSE": "disable"  # 禁用流式输出
+        }
+        
+        # 从数据库获取对话历史
+        messages_query = Message.query.filter_by(
+            conversation_id=conversation.conversation_id,
+            is_deleted=False
+        ).order_by(Message.message_order.asc()).all()
+        
+        # 构建多轮对话历史
+        messages = []
+        for msg in messages_query:
+            messages.append({"role": msg.role, "content": msg.content})
+        
+        # 添加用户新消息到对话历史
+        messages.append({"role": "user", "content": message})
+        
+        # 截断对话历史以控制内存使用
+        messages = truncate_conversation_history(messages, config.max_conversation_history)
+        
+        payload = {
+            "model": "application",
+            "parameters": {"app_id": chosen_app_id},
+            "input": {"messages": messages}  # 发送完整的对话历史
+        }
+        
+        # 如果有会话ID,添加到请求参数中
+        if api_session_id:
+            payload["parameters"]["session_id"] = api_session_id
+        
+        # 发送API请求
+        response = requests.post(
+            config.api_endpoint,
+            headers=headers,
+            json=payload,
+            timeout=60
+        )
+        
+        # 转换为JSON
+        response_json = response.json()
+        
+        if response.status_code != HTTPStatus.OK:
+            return jsonify({
+                'success': False,
+                'error': f"错误: {response.status_code} - {response_json.get('message', '未知错误')}",
+                'request_id': response_json.get('request_id', ''),
+                'agent': agent
+            }), HTTPStatus.INTERNAL_SERVER_ERROR
+        
+        # 解析返回结果
+        output = response_json.get('output', {})
+        choices = output.get('choices', [{}])
+        text = choices[0].get('message', {}).get('content', '') if choices else ''
+        new_session_id = output.get('session_id', '')
+        thoughts_content = output.get('thoughts', [])
+        
+        # 更新对话的session_id(如果API返回了新的session_id且与当前不同)
+        if new_session_id and new_session_id != api_session_id:
+            conversation.session_id = new_session_id
+            db.session.commit()
+            
+        elif not api_session_id and new_session_id:
+            # 如果对话原本没有session_id,现在有了,更新数据库
+            conversation.session_id = new_session_id
+            db.session.commit()
+            
+        
+        # 保存到数据库
+        try:
+            # 获取当前对话的消息数量,用于设置消息顺序
+            current_message_count = Message.query.filter_by(
+                conversation_id=conversation.conversation_id,
+                is_deleted=False
+            ).count()
+            
+            # 保存用户消息
+            save_message_to_database(
+                auth_user_id,
+                conversation.conversation_id,
+                'user',
+                message,
+                message_order=current_message_count + 1
+            )
+            
+            # 保存AI回复
+            if text:
+                thinking_text = '\n'.join(thoughts_content) if thoughts_content else None
+                save_message_to_database(
+                    auth_user_id,
+                    conversation.conversation_id,
+                    'assistant',
+                    text,
+                    thinking_text,
+                    message_order=current_message_count + 2
+                )
+            
+            # 更新对话标题(如果是第一条消息)
+            if conversation.message_count <= 2:  # 用户消息 + AI回复
+                new_title = generate_conversation_title(message)
+                # 检查是否是默认标题("新对话"或"对话 时间"格式)
+                is_default_title = (
+                    conversation.title == "新对话" or 
+                    conversation.title == f"对话 {conversation.created_at.strftime('%Y-%m-%d %H:%M')}"
+                )
+                if is_default_title:
+                    conversation.title = new_title
+                    db.session.commit()
+            
+        except Exception as db_error:
+            logger.error(f"保存对话到数据库时发生错误: {str(db_error)}", exc_info=True)
+            # 不影响主要功能,继续返回结果
+        
+        return jsonify({
+            'success': True,
+            'message': text,
+            'user_id': user_id,
+            'session_id': new_session_id or api_session_id,
+            'thoughts': thoughts_content,
+            'agent': agent,
+            'conversation_id': conversation.conversation_id
+        })
+    
+    except requests.exceptions.RequestException as e:
+        logger.error(f"API请求异常: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"API请求异常: {str(e)}"
+        }), HTTPStatus.SERVICE_UNAVAILABLE
+    except Exception as e:
+        logger.error(f"API调用错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@app.route('/api/chat/stream', methods=['GET', 'POST'])
+@auth_middleware.login_required
+@rate_limit(config.rate_limit_requests, config.rate_limit_window)
+def chat_stream():
+    """处理聊天请求并以流式方式返回响应"""
+    try:
+        # 从请求中获取数据
+        if request.method == 'GET':
+            message = request.args.get('message', '')
+            user_id = request.args.get('user_id', str(uuid.uuid4()))
+            agent = request.args.get('agent') or 'kuaishou'
+            conversation_id = request.args.get('conversation_id')  # 支持指定对话ID
+        else:  # POST
+            data = request.json or {}
+            message = data.get('message', '')
+            user_id = data.get('user_id', str(uuid.uuid4()))
+            agent = data.get('agent') or 'kuaishou'
+            conversation_id = data.get('conversation_id')  # 支持指定对话ID
+            session_id = data.get('session_id')  # 支持指定session_id
+            force_new = data.get('force_new', False)  # 强制创建新对话
+        
+        # 输入验证
+        message, error = sanitize_input(message, config.max_message_length)
+        if error:
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': error, 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        user_id, error = validate_user_id(user_id)
+        if error:
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': error, 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        # 选择对应智能体 APP_ID
+        chosen_app_id, appid_env_key = config.get_app_id_for_agent_with_source(agent)
+        if not chosen_app_id:
+            logger.error(f"未找到对应智能体APP_ID,agent={agent}")
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': f'服务器未配置该智能体的 APP_ID: {agent}', 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        # 记录请求信息
+        log_request_info(message, user_id, request.method)
+        
+        # 从认证中间件获取用户信息
+        auth_user_info = getattr(request, 'user_info', {})
+        auth_user_id = auth_user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        # 获取或创建对话
+        conversation = get_or_create_conversation(
+            auth_user_id, 
+            generate_conversation_title(message),
+            conversation_id,
+            force_new
+        )
+        
+        # 优先使用前端传来的session_id,否则使用对话的session_id
+        api_session_id = session_id or conversation.session_id
+        
+        # 从数据库获取对话历史
+        messages_query = Message.query.filter_by(
+            conversation_id=conversation.conversation_id,
+            is_deleted=False
+        ).order_by(Message.message_order.asc()).all()
+        
+        # 构建多轮对话历史
+        messages = []
+        for msg in messages_query:
+            messages.append({"role": msg.role, "content": msg.content})
+        
+        # 添加用户新消息到对话历史
+        messages.append({"role": "user", "content": message})
+        
+        # 截断对话历史以控制内存使用
+        messages = truncate_conversation_history(messages, config.max_conversation_history)
+        
+        # 立即保存用户消息到数据库(在流式响应开始前)
+        try:
+            # 获取当前对话的消息数量,用于设置消息顺序
+            current_message_count = Message.query.filter_by(
+                conversation_id=conversation.conversation_id,
+                is_deleted=False
+            ).count()
+            
+            # 立即保存用户消息
+            user_message = save_message_to_database(
+                auth_user_id,
+                conversation.conversation_id,
+                'user',
+                message,
+                message_order=current_message_count + 1
+            )
+            
+            
+        except Exception as db_error:
+            logger.error(f"保存用户消息到数据库时发生错误: {str(db_error)}", exc_info=True)
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': f'保存用户消息失败: {str(db_error)}', 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        # 定义流式响应生成器
+        def generate() -> Generator[str, None, None]:
+            try:
+                # 发送初始响应,包含用户消息ID,防止浏览器超时
+                initial_data = {
+                    'success': True,
+                    'chunk': '',
+                    'done': False,
+                    'agent': agent,
+                    'user_message_id': user_message.message_id if user_message else None
+                }
+                yield f"data: {json.dumps(initial_data)}\n\n"
+                
+                try:
+                    # 使用DashScope SDK直接调用 - 多轮对话模式
+                    params = {
+                        "api_key": config.api_key,
+                        "app_id": chosen_app_id,
+                        "messages": messages,  # 发送完整的对话历史
+                        "stream": True,
+                        "incremental_output": True,
+                        "has_thoughts": True
+                    }
+                    
+                    # 使用对话的session_id
+                    if api_session_id:
+                        params["session_id"] = api_session_id
+                    
+                    response = Application.call(**params)
+                    
+                    new_session_id: Optional[str] = None
+                    accumulated_text: str = ""
+                    thoughts_content: List[str] = []
+                    
+                    # 处理流式响应
+                    for chunk in response:
+                        # 检查客户端是否断开连接
+                        if request.environ.get('werkzeug.server.shutdown') or not request:
+                            break
+                            
+                        if chunk.status_code != HTTPStatus.OK:
+                            yield f"data: {json.dumps({'success': False, 'error': f'错误: {chunk.status_code} - {chunk.message}', 'request_id': chunk.request_id, 'done': True, 'agent': agent})}\n\n"
+                            return
+                        
+                        # 处理思考过程
+                        if hasattr(chunk.output, 'thoughts') and chunk.output.thoughts:
+                            for it in chunk.output.thoughts:
+                                if it.action_type == 'reasoning':
+                                    content = str(it.thought) if not isinstance(it.thought, str) else it.thought
+                                    thoughts_content.append(content)
+                                    yield f"data: {json.dumps({'success': True, 'thought': content, 'done': False, 'agent': agent})}\n\n"
+                        
+                        # 处理回答内容
+                        if hasattr(chunk.output, 'text') and chunk.output.text:
+                            current_text = str(chunk.output.text)
+                            accumulated_text += current_text
+                            yield f"data: {json.dumps({'success': True, 'chunk': current_text, 'done': False, 'agent': agent})}\n\n"
+                        
+                        # 检查是否有会话ID
+                        if hasattr(chunk.output, 'session_id') and chunk.output.session_id:
+                            new_session_id = chunk.output.session_id
+                    
+                    # 处理结束
+                    if new_session_id and new_session_id != api_session_id:
+                        conversation.session_id = new_session_id
+                        db.session.commit()
+                    elif not api_session_id and new_session_id:
+                        # 如果对话原本没有session_id,现在有了,更新数据库
+                        conversation.session_id = new_session_id
+                        db.session.commit()
+                    
+                    # 保存AI回复到数据库
+                    try:
+                        if accumulated_text:
+                            thinking_text = '\n'.join(thoughts_content) if thoughts_content else None
+                            ai_message = save_message_to_database(
+                                auth_user_id,
+                                conversation.conversation_id,
+                                'assistant',
+                                accumulated_text,
+                                thinking_text,
+                                message_order=current_message_count + 2
+                            )
+                            
+                            # 更新对话标题(如果是第一条消息)
+                            if conversation.message_count <= 2:  # 用户消息 + AI回复
+                                new_title = generate_conversation_title(message)
+                                # 检查是否是默认标题("新对话"或"对话 时间"格式)
+                                is_default_title = (
+                                    conversation.title == "新对话" or 
+                                    conversation.title == f"对话 {conversation.created_at.strftime('%Y-%m-%d %H:%M')}"
+                                )
+                                if is_default_title:
+                                    conversation.title = new_title
+                                    db.session.commit()
+                            
+                    except Exception as db_error:
+                        logger.error(f"保存AI回复到数据库时发生错误: {str(db_error)}", exc_info=True)
+                        # 不影响主要功能,继续返回结果
+                    
+                    # 发送完成标记
+                    final_data = {
+                        'success': True,
+                        'done': True,
+                        'thoughts': thoughts_content,
+                        'agent': agent,
+                        'session_id': new_session_id or api_session_id,
+                        'user_id': user_id,
+                        'conversation_id': conversation.conversation_id
+                    }
+                    
+                    yield f"data: {json.dumps(final_data)}\n\n"
+                    
+                except Exception as e:
+                    logger.error(f"DashScope SDK 调用错误: {str(e)}", exc_info=True)
+                    yield f"data: {json.dumps({'success': False, 'error': f'DashScope API调用错误: {str(e)}', 'done': True, 'agent': agent})}\n\n"
+            
+            except Exception as e:
+                logger.error(f"流式API调用错误: {str(e)}", exc_info=True)
+                yield f"data: {json.dumps({'success': False, 'error': f'发生错误: {str(e)}', 'done': True, 'agent': agent})}\n\n"
+        
+        # 创建并返回流式响应
+        response = Response(stream_with_context(generate()), content_type='text/event-stream')
+        response.headers['Cache-Control'] = 'no-cache'
+        response.headers['X-Accel-Buffering'] = 'no'
+        return response
+    
+    except Exception as e:
+        logger.error(f"请求处理错误: {str(e)}", exc_info=True)
+        response = Response(
+            f"data: {json.dumps({'success': False, 'error': f'发生错误: {str(e)}', 'done': True})}\n\n",
+            content_type='text/event-stream'
+        )
+        response.headers['Cache-Control'] = 'no-cache'
+        return response
+
+@app.route('/api/reset', methods=['POST'])
+@auth_middleware.login_required
+@rate_limit(10, 60)
+def reset_session():
+    """重置用户会话"""
+    try:
+        data = request.json or {}
+        user_id = data.get('user_id')
+        
+        if not user_id:
+            return jsonify({
+                'success': True,
+                'message': '没有提供用户ID,但重置请求已处理'
+            })
+        
+        # 验证用户ID格式
+        user_id, error = validate_user_id(user_id)
+        if error:
+            return jsonify({
+                'success': False,
+                'error': error
+            }), HTTPStatus.BAD_REQUEST
+        
+        # 从认证中间件获取用户信息
+        auth_user_info = getattr(request, 'user_info', {})
+        auth_user_id = auth_user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        # 清除数据库中的当前对话ID(如果有认证用户信息)
+        if auth_user_id and str(auth_user_id) in user_current_conversations:
+            del user_current_conversations[str(auth_user_id)]
+            
+        return jsonify({
+            'success': True,
+            'message': '会话已重置'
+        })
+    except Exception as e:
+        logger.error(f"重置会话时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@app.route('/api/status', methods=['GET'])
+@auth_middleware.login_required
+def get_status():
+    """获取应用状态信息"""
+    try:
+        active_users = len(user_current_conversations)
+        total_conversations = Conversation.query.filter_by(is_deleted=False).count()
+        
+        return jsonify({
+            'success': True,
+            'status': 'running',
+            'active_users': active_users,
+            'total_conversations': total_conversations,
+            'app_version': '2.0.0',
+            'python_version': '3.12',
+            'config': {
+                'max_message_length': config.max_message_length,
+                'max_conversation_history': config.max_conversation_history,
+                'rate_limit_requests': config.rate_limit_requests,
+                'rate_limit_window': config.rate_limit_window
+            }
+        })
+    except Exception as e:
+        logger.error(f"获取状态时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+# ==================== 历史记录相关API ====================
+
+@app.route('/api/conversations', methods=['GET'])
+@auth_middleware.login_required
+def get_conversations():
+    """获取用户的历史对话列表,按时间分组"""
+    try:
+        # 从认证中间件获取用户信息
+        user_info = getattr(request, 'user_info', {})
+        user_id = user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        
+        if not user_id:
+            logger.warning('获取历史记录失败 - 用户ID为空')
+            return jsonify({
+                'success': False,
+                'error': '用户信息获取失败'
+            }), HTTPStatus.UNAUTHORIZED
+        
+        # 获取用户对象
+        user = User.query.filter_by(user_id=user_id, is_deleted=False).first()
+        if not user:
+            return jsonify({
+                'success': False,
+                'error': '用户不存在'
+            }), HTTPStatus.NOT_FOUND
+        
+        # 获取用户的对话列表,按更新时间倒序
+        conversations = Conversation.query.filter_by(
+            user_id=user_id, 
+            is_deleted=False
+        ).order_by(Conversation.updated_at.desc()).all()
+        
+        # 按时间分组
+        from datetime import datetime, timedelta
+        now = get_beijing_time()
+        today = now.date()
+        yesterday = today - timedelta(days=1)
+        week_ago = today - timedelta(days=7)
+        month_ago = today - timedelta(days=30)
+        
+        grouped_conversations = {
+            'today': [],
+            'yesterday': [],
+            'week': [],
+            'month': []
+        }
+        
+        for conv in conversations:
+            conv_date = conv.updated_at.date()
+            conv_data = {
+                'conversation_id': conv.conversation_id,
+                'title': conv.title,
+                'message_count': conv.message_count,
+                'session_id': conv.session_id,
+                'last_message_at': conv.last_message_at.isoformat() if conv.last_message_at else None,
+                'created_at': conv.created_at.isoformat(),
+                'updated_at': conv.updated_at.isoformat()
+            }
+            
+            if conv_date == today:
+                grouped_conversations['today'].append(conv_data)
+            elif conv_date == yesterday:
+                grouped_conversations['yesterday'].append(conv_data)
+            elif conv_date >= week_ago:
+                grouped_conversations['week'].append(conv_data)
+            elif conv_date >= month_ago:
+                grouped_conversations['month'].append(conv_data)
+        
+        return jsonify({
+            'success': True,
+            'conversations': grouped_conversations
+        })
+        
+    except Exception as e:
+        logger.error(f"获取对话列表时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@app.route('/api/conversations/<int:conversation_id>', methods=['GET'])
+@auth_middleware.login_required
+def get_conversation_messages(conversation_id):
+    """获取指定对话的所有消息"""
+    try:
+        # 从认证中间件获取用户信息
+        user_info = getattr(request, 'user_info', {})
+        user_id = user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        if not user_id:
+            return jsonify({
+                'success': False,
+                'error': '用户信息获取失败'
+            }), HTTPStatus.UNAUTHORIZED
+        
+        # 验证对话是否属于当前用户
+        conversation = Conversation.query.filter_by(
+            conversation_id=conversation_id,
+            user_id=user_id,
+            is_deleted=False
+        ).first()
+        
+        if not conversation:
+            return jsonify({
+                'success': False,
+                'error': '对话不存在或无权限访问'
+            }), HTTPStatus.NOT_FOUND
+        
+        # 获取对话的所有消息,按顺序排列
+        messages = Message.query.filter_by(
+            conversation_id=conversation_id,
+            is_deleted=False
+        ).order_by(Message.message_order.asc()).all()
+        
+        messages_data = []
+        for msg in messages:
+            message_data = {
+                'message_id': msg.message_id,
+                'role': msg.role,
+                'content': msg.content,
+                'thinking_content': msg.thinking_content,
+                'content_type': msg.content_type,
+                'message_order': msg.message_order,
+                'created_at': msg.created_at.isoformat()
+            }
+            messages_data.append(message_data)
+        
+        return jsonify({
+            'success': True,
+            'conversation': {
+                'conversation_id': conversation.conversation_id,
+                'title': conversation.title,
+                'conversation_type': conversation.conversation_type,
+                'session_id': conversation.session_id,
+                'message_count': conversation.message_count,
+                'created_at': conversation.created_at.isoformat(),
+                'updated_at': conversation.updated_at.isoformat()
+            },
+            'messages': messages_data
+        })
+        
+    except Exception as e:
+        logger.error(f"获取对话消息时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@app.route('/api/conversations/<int:conversation_id>/rename', methods=['POST'])
+@auth_middleware.login_required
+def rename_conversation(conversation_id):
+    """重命名对话"""
+    try:
+        # 从认证中间件获取用户信息
+        user_info = getattr(request, 'user_info', {})
+        user_id = user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        if not user_id:
+            return jsonify({
+                'success': False,
+                'error': '用户信息获取失败'
+            }), HTTPStatus.UNAUTHORIZED
+        
+        data = request.json or {}
+        new_title = data.get('title', '').strip()
+        
+        if not new_title:
+            return jsonify({
+                'success': False,
+                'error': '新标题不能为空'
+            }), HTTPStatus.BAD_REQUEST
+        
+        if len(new_title) > 500:
+            return jsonify({
+                'success': False,
+                'error': '标题长度不能超过500个字符'
+            }), HTTPStatus.BAD_REQUEST
+        
+        # 验证对话是否属于当前用户
+        conversation = Conversation.query.filter_by(
+            conversation_id=conversation_id,
+            user_id=user_id,
+            is_deleted=False
+        ).first()
+        
+        if not conversation:
+            return jsonify({
+                'success': False,
+                'error': '对话不存在或无权限访问'
+            }), HTTPStatus.NOT_FOUND
+        
+        # 更新标题
+        conversation.title = new_title
+        conversation.updated_at = get_beijing_time()
+        
+        db.session.commit()
+        
+        return jsonify({
+            'success': True,
+            'message': '对话标题已更新',
+            'conversation': {
+                'conversation_id': conversation.conversation_id,
+                'title': conversation.title,
+                'updated_at': conversation.updated_at.isoformat()
+            }
+        })
+        
+    except Exception as e:
+        db.session.rollback()
+        logger.error(f"重命名对话时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@app.route('/api/conversations/<int:conversation_id>', methods=['DELETE'])
+@auth_middleware.login_required
+def delete_conversation(conversation_id):
+    """删除对话(软删除)"""
+    try:
+        # 从认证中间件获取用户信息
+        user_info = getattr(request, 'user_info', {})
+        user_id = user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        if not user_id:
+            return jsonify({
+                'success': False,
+                'error': '用户信息获取失败'
+            }), HTTPStatus.UNAUTHORIZED
+        
+        # 验证对话是否属于当前用户
+        conversation = Conversation.query.filter_by(
+            conversation_id=conversation_id,
+            user_id=user_id,
+            is_deleted=False
+        ).first()
+        
+        if not conversation:
+            return jsonify({
+                'success': False,
+                'error': '对话不存在或无权限访问'
+            }), HTTPStatus.NOT_FOUND
+        
+        # 软删除对话
+        conversation.is_deleted = True
+        conversation.deleted_at = get_beijing_time()
+        conversation.updated_at = get_beijing_time()
+        
+        # 同时软删除该对话的所有消息
+        Message.query.filter_by(
+            conversation_id=conversation_id,
+            is_deleted=False
+        ).update({
+            'is_deleted': True,
+            'deleted_at': get_beijing_time()
+        })
+        
+        db.session.commit()
+        
+        return jsonify({
+            'success': True,
+            'message': '对话已删除'
+        })
+        
+    except Exception as e:
+        db.session.rollback()
+        logger.error(f"删除对话时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@app.route('/api/conversations/new', methods=['POST'])
+@auth_middleware.login_required
+def create_new_conversation():
+    """创建新对话"""
+    try:
+        # 从认证中间件获取用户信息
+        user_info = getattr(request, 'user_info', {})
+        user_id = user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        if not user_id:
+            return jsonify({
+                'success': False,
+                'error': '用户信息获取失败'
+            }), HTTPStatus.UNAUTHORIZED
+        
+        # 获取用户对象
+        user = User.query.filter_by(user_id=user_id, is_deleted=False).first()
+        if not user:
+            return jsonify({
+                'success': False,
+                'error': '用户不存在'
+            }), HTTPStatus.NOT_FOUND
+        
+        data = request.json or {}
+        title = data.get('title', '新对话').strip()
+        
+        if not title:
+            title = '新对话'
+        
+        if len(title) > 500:
+            return jsonify({
+                'success': False,
+                'error': '标题长度不能超过500个字符'
+            }), HTTPStatus.BAD_REQUEST
+        
+        # 生成新的session_id
+        session_id = generate_session_id(user_id)
+        
+        # 创建新对话
+        conversation = Conversation(
+            user_id=user_id,
+            title=title,
+            conversation_type='chat',
+            session_id=session_id,
+            message_count=0
+        )
+        
+        db.session.add(conversation)
+        db.session.commit()
+        
+        # 更新用户的当前对话ID
+        user_current_conversations[str(user_id)] = conversation.conversation_id
+        
+        
+        return jsonify({
+            'success': True,
+            'message': '新对话已创建',
+            'conversation': {
+                'conversation_id': conversation.conversation_id,
+                'title': conversation.title,
+                'created_at': conversation.created_at.isoformat(),
+                'updated_at': conversation.updated_at.isoformat()
+            }
+        })
+        
+    except Exception as e:
+        db.session.rollback()
+        logger.error(f"创建新对话时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@app.route('/api/conversations/clear-current', methods=['POST'])
+@auth_middleware.login_required
+def clear_current_conversation():
+    """清除用户的当前对话映射,强制下次创建新对话"""
+    try:
+        # 从认证中间件获取用户信息
+        user_info = getattr(request, 'user_info', {})
+        user_id = user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        if not user_id:
+            return jsonify({
+                'success': False,
+                'error': '用户信息获取失败'
+            }), HTTPStatus.UNAUTHORIZED
+        
+        # 清除用户的当前对话映射
+        if str(user_id) in user_current_conversations:
+            del user_current_conversations[str(user_id)]
+        
+        return jsonify({
+            'success': True,
+            'message': '当前对话映射已清除'
+        })
+        
+    except Exception as e:
+        logger.error(f"清除当前对话映射时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@app.route('/api/conversations/<int:conversation_id>/switch', methods=['POST'])
+@auth_middleware.login_required
+def switch_conversation(conversation_id):
+    """切换到指定对话"""
+    try:
+        # 从认证中间件获取用户信息
+        user_info = getattr(request, 'user_info', {})
+        user_id = user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        if not user_id:
+            return jsonify({
+                'success': False,
+                'error': '用户信息获取失败'
+            }), HTTPStatus.UNAUTHORIZED
+        
+        # 切换到指定对话
+        conversation = switch_to_conversation(user_id, conversation_id)
+        
+        return jsonify({
+            'success': True,
+            'message': '对话切换成功',
+            'conversation': {
+                'conversation_id': conversation.conversation_id,
+                'title': conversation.title,
+                'session_id': conversation.session_id,
+                'message_count': conversation.message_count,
+                'last_message_at': conversation.last_message_at.isoformat() if conversation.last_message_at else None,
+                'created_at': conversation.created_at.isoformat(),
+                'updated_at': conversation.updated_at.isoformat()
+            }
+        })
+        
+    except ValueError as e:
+        return jsonify({
+            'success': False,
+            'error': str(e)
+        }), HTTPStatus.NOT_FOUND
+    except Exception as e:
+        logger.error(f"切换对话时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@app.route('/api/conversations/<int:conversation_id>/status', methods=['GET'])
+@auth_middleware.login_required
+def get_conversation_status(conversation_id):
+    """获取对话的实时状态信息"""
+    try:
+        # 从认证中间件获取用户信息
+        user_info = getattr(request, 'user_info', {})
+        user_id = user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        if not user_id:
+            return jsonify({
+                'success': False,
+                'error': '用户信息获取失败'
+            }), HTTPStatus.UNAUTHORIZED
+        
+        # 验证对话是否属于当前用户
+        conversation = Conversation.query.filter_by(
+            conversation_id=conversation_id,
+            user_id=user_id,
+            is_deleted=False
+        ).first()
+        
+        if not conversation:
+            return jsonify({
+                'success': False,
+                'error': '对话不存在或无权限访问'
+            }), HTTPStatus.NOT_FOUND
+        
+        # 获取对话的最新消息
+        latest_message = Message.query.filter_by(
+            conversation_id=conversation_id,
+            is_deleted=False
+        ).order_by(Message.message_order.desc()).first()
+        
+        status_info = {
+            'conversation_id': conversation_id,
+            'title': conversation.title,
+            'message_count': conversation.message_count,
+            'last_message_at': conversation.last_message_at.isoformat() if conversation.last_message_at else None,
+            'updated_at': conversation.updated_at.isoformat(),
+            'has_latest_message': latest_message is not None,
+            'latest_message_role': latest_message.role if latest_message else None,
+            'latest_message_preview': latest_message.content[:100] + '...' if latest_message and len(latest_message.content) > 100 else latest_message.content if latest_message else None
+        }
+        
+        return jsonify({
+            'success': True,
+            'status': status_info
+        })
+        
+    except Exception as e:
+        logger.error(f"获取对话状态时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@app.route('/api/messages/<int:message_id>/edit', methods=['POST'])
+@auth_middleware.login_required
+@rate_limit(config.rate_limit_requests, config.rate_limit_window)
+def edit_message(message_id):
+    """编辑用户消息并重新生成AI回复(流式响应)"""
+    try:
+        # 从请求中获取数据
+        data = request.json or {}
+        new_content = data.get('content', '')
+        agent = data.get('agent') or 'kuaishou'
+        
+        # 输入验证
+        new_content, error = sanitize_input(new_content, config.max_message_length)
+        if error:
+            # 流式响应中的错误返回
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': error, 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        # 从认证中间件获取用户信息
+        auth_user_info = getattr(request, 'user_info', {})
+        auth_user_id = auth_user_info.get('db_user_id')  # 使用数据库用户ID
+        
+        if not auth_user_id:
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': '用户信息获取失败', 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        # 获取要编辑的消息
+        message = Message.query.filter_by(
+            message_id=message_id,
+            is_deleted=False
+        ).first()
+        
+        if not message:
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': '消息不存在', 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        # 验证消息是否是用户消息
+        if message.role != 'user':
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': '只能编辑用户消息', 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        # 获取对话信息
+        conversation = Conversation.query.filter_by(
+            conversation_id=message.conversation_id,
+            user_id=auth_user_id,
+            is_deleted=False
+        ).first()
+        
+        if not conversation:
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': '对话不存在或无权限访问', 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        # 记录请求信息
+        log_request_info(new_content, str(auth_user_id), 'POST')
+        
+        # 选择对应智能体 APP_ID
+        chosen_app_id, appid_env_key = config.get_app_id_for_agent_with_source(agent)
+        if not chosen_app_id:
+            logger.error(f"未找到对应智能体APP_ID,agent={agent}")
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': f'服务器未配置该智能体的 APP_ID: {agent}', 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        # 使用对话的session_id
+        api_session_id = conversation.session_id
+        
+        # ===== 立即软删除当前消息及之后的所有消息(在流式响应开始前) =====
+        try:
+            # 计算将被删除的消息数(包括当前消息及之后的所有消息)
+            deleted_count = Message.query.filter_by(
+                conversation_id=conversation.conversation_id,
+                is_deleted=False
+            ).filter(
+                Message.message_order >= message.message_order
+            ).count()
+            
+            # 软删除当前消息及之后的所有消息(包括原来的AI回复等)
+            Message.query.filter_by(
+                conversation_id=conversation.conversation_id,
+                is_deleted=False
+            ).filter(
+                Message.message_order >= message.message_order
+            ).update({
+                'is_deleted': True,
+                'deleted_at': get_beijing_time()
+            }, synchronize_session=False)
+            
+            # 更新消息计数:减去被删除的消息数
+            conversation.message_count -= deleted_count
+            db.session.commit()
+            
+            # 保存编辑后的用户消息(使用相同的message_order)
+            new_user_message = save_message_to_database(
+                auth_user_id,
+                conversation.conversation_id,
+                'user',
+                new_content,
+                None,
+                message_order=message.message_order
+            )
+            
+        except Exception as db_error:
+            db.session.rollback()
+            logger.error(f"软删除消息或保存编辑消息时发生错误: {str(db_error)}", exc_info=True)
+            return Response(
+                f"data: {json.dumps({'success': False, 'error': f'更新消息失败: {str(db_error)}', 'done': True})}\n\n",
+                content_type='text/event-stream'
+            )
+        
+        # 获取该消息之前的所有消息(不包括已删除的消息,现在数据库中已删除的消息不会出现在查询结果中)
+        previous_messages = Message.query.filter_by(
+            conversation_id=message.conversation_id,
+            is_deleted=False
+        ).filter(
+            Message.message_order < message.message_order
+        ).order_by(Message.message_order.asc()).all()
+        
+        # 构建对话历史(使用编辑后的消息)
+        messages = []
+        for msg in previous_messages:
+            messages.append({"role": msg.role, "content": msg.content})
+        
+        # 添加编辑后的用户消息(刚保存的)
+        messages.append({"role": "user", "content": new_content})
+        
+        # 截断对话历史以控制内存使用
+        messages = truncate_conversation_history(messages, config.max_conversation_history)
+        
+        # 定义流式响应生成器
+        def generate() -> Generator[str, None, None]:
+            try:
+                # 发送初始响应
+                initial_data = {
+                    'success': True,
+                    'chunk': '',
+                    'done': False,
+                    'agent': agent,
+                    'message_id': message_id
+                }
+                yield f"data: {json.dumps(initial_data)}\n\n"
+                
+                try:
+                    # 使用DashScope SDK直接调用 - 多轮对话模式
+                    params = {
+                        "api_key": config.api_key,
+                        "app_id": chosen_app_id,
+                        "messages": messages,  # 发送完整的对话历史
+                        "stream": True,
+                        "incremental_output": True,
+                        "has_thoughts": True
+                    }
+                    
+                    # 使用对话的session_id
+                    if api_session_id:
+                        params["session_id"] = api_session_id
+                    
+                    response = Application.call(**params)
+                    
+                    new_session_id: Optional[str] = None
+                    accumulated_text: str = ""
+                    thoughts_content: List[str] = []
+                    
+                    # 处理流式响应
+                    for chunk in response:
+                        # 检查客户端是否断开连接
+                        if request.environ.get('werkzeug.server.shutdown') or not request:
+                            break
+                            
+                        if chunk.status_code != HTTPStatus.OK:
+                            yield f"data: {json.dumps({'success': False, 'error': f'错误: {chunk.status_code} - {chunk.message}', 'request_id': chunk.request_id, 'done': True, 'agent': agent})}\n\n"
+                            return
+                        
+                        # 处理思考过程
+                        if hasattr(chunk.output, 'thoughts') and chunk.output.thoughts:
+                            for it in chunk.output.thoughts:
+                                if it.action_type == 'reasoning':
+                                    content = str(it.thought) if not isinstance(it.thought, str) else it.thought
+                                    thoughts_content.append(content)
+                                    yield f"data: {json.dumps({'success': True, 'thought': content, 'done': False, 'agent': agent})}\n\n"
+                        
+                        # 处理回答内容
+                        if hasattr(chunk.output, 'text') and chunk.output.text:
+                            current_text = str(chunk.output.text)
+                            accumulated_text += current_text
+                            yield f"data: {json.dumps({'success': True, 'chunk': current_text, 'done': False, 'agent': agent})}\n\n"
+                        
+                        # 检查是否有会话ID
+                        if hasattr(chunk.output, 'session_id') and chunk.output.session_id:
+                            new_session_id = chunk.output.session_id
+                    
+                    # 处理结束,保存AI回复到数据库
+                    try:
+                        # 更新session_id(如果需要)
+                        if new_session_id and new_session_id != api_session_id:
+                            conversation.session_id = new_session_id
+                            db.session.commit()
+                        
+                        # 保存新的AI回复(如果有)
+                        if accumulated_text:
+                            thinking_text = '\n'.join(thoughts_content) if thoughts_content else None
+                            ai_message = save_message_to_database(
+                                auth_user_id,
+                                conversation.conversation_id,
+                                'assistant',
+                                accumulated_text,
+                                thinking_text,
+                                message_order=message.message_order + 1
+                            )
+                            # save_message_to_database 已经提交,message_count 已更新
+                        else:
+                            # 如果没有AI回复,更新对话的更新时间
+                            conversation.updated_at = get_beijing_time()
+                            conversation.last_message_at = get_beijing_time()
+                            db.session.commit()
+                        
+                    except Exception as db_error:
+                        db.session.rollback()
+                        logger.error(f"保存AI回复到数据库时发生错误: {str(db_error)}", exc_info=True)
+                        # 不影响主要功能,继续返回结果
+                    
+                    # 发送完成标记
+                    final_data = {
+                        'success': True,
+                        'done': True,
+                        'thoughts': thoughts_content,
+                        'agent': agent,
+                        'session_id': new_session_id or api_session_id,
+                        'message_id': message_id,
+                        'conversation_id': conversation.conversation_id
+                    }
+                    
+                    yield f"data: {json.dumps(final_data)}\n\n"
+                    
+                except Exception as e:
+                    logger.error(f"DashScope SDK 调用错误: {str(e)}", exc_info=True)
+                    yield f"data: {json.dumps({'success': False, 'error': f'DashScope API调用错误: {str(e)}', 'done': True, 'agent': agent})}\n\n"
+            
+            except Exception as e:
+                logger.error(f"流式API调用错误: {str(e)}", exc_info=True)
+                yield f"data: {json.dumps({'success': False, 'error': f'发生错误: {str(e)}', 'done': True, 'agent': agent})}\n\n"
+        
+        # 创建并返回流式响应
+        response = Response(stream_with_context(generate()), content_type='text/event-stream')
+        response.headers['Cache-Control'] = 'no-cache'
+        response.headers['X-Accel-Buffering'] = 'no'
+        return response
+    
+    except Exception as e:
+        logger.error(f"编辑消息时发生错误: {str(e)}", exc_info=True)
+        return jsonify({
+            'success': False,
+            'error': f"发生错误: {str(e)}"
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+    
+
+# 健康检查端点
+@app.route('/health', methods=['GET'])
+def health_check():
+    """健康检查端点"""
+    return jsonify({
+        'status': 'ok',
+        'app_version': '2.0.0',
+        'python_version': '3.12',
+        'timestamp': time.time()
+    })
+
+if __name__ == '__main__':
+    app.run(host='0.0.0.0', port=config.port, debug=config.debug) 

+ 127 - 0
auth_middleware.py

@@ -0,0 +1,127 @@
+import jwt
+import time
+from functools import wraps
+from flask import request, jsonify, redirect, url_for, session
+from http import HTTPStatus
+from config import config
+
+def is_api_request() -> bool:
+    """判断是否为API请求"""
+    return (request.path.startswith('/api/') or 
+            request.headers.get('X-Requested-With') == 'XMLHttpRequest' or
+            'application/json' in (request.headers.get('Accept') or ''))
+
+class AuthMiddleware:
+    """认证中间件"""
+    
+    def __init__(self, app=None):
+        self.app = app
+        if app is not None:
+            self.init_app(app)
+    
+    def init_app(self, app):
+        """初始化应用"""
+        self.app = app
+        app.config['JWT_SECRET_KEY'] = config.secret_key
+        app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 7 * 24 * 60 * 60  # 7天
+    
+    def generate_token(self, user_id: str, user_info: dict) -> str:
+        """生成JWT令牌"""
+        payload = {
+            'user_id': user_id,
+            'user_info': user_info,
+            'exp': time.time() + self.app.config['JWT_ACCESS_TOKEN_EXPIRES'],
+            'iat': time.time()
+        }
+        token = jwt.encode(payload, self.app.config['JWT_SECRET_KEY'], algorithm='HS256')
+        return token.decode('utf-8') if isinstance(token, bytes) else token
+    
+    def verify_token(self, token: str) -> dict:
+        """验证JWT令牌"""
+        import logging
+        logger = logging.getLogger(__name__)
+        
+        try:
+            payload = jwt.decode(token, self.app.config['JWT_SECRET_KEY'], algorithms=['HS256'])
+            return payload
+        except jwt.ExpiredSignatureError:
+            logger.warning('JWT token已过期')
+            raise ValueError('令牌已过期')
+        except jwt.InvalidTokenError as e:
+            logger.warning(f'JWT token无效: {str(e)}')
+            raise ValueError('无效的令牌')
+    
+    def login_required(self, f):
+        """登录要求装饰器"""
+        @wraps(f)
+        def decorated_function(*args, **kwargs):
+            token = self._get_token()
+            
+            if not token:
+                return self._handle_no_token()
+            
+            try:
+                payload = self.verify_token(token)
+                request.user_id = payload['user_id']
+                request.user_info = payload['user_info']
+                return f(*args, **kwargs)
+            except ValueError as e:
+                return self._handle_invalid_token(e)
+        
+        return decorated_function
+    
+    def _get_token(self) -> str:
+        """获取token"""
+        # 从请求头获取
+        if 'Authorization' in request.headers:
+            auth_header = request.headers['Authorization']
+            if auth_header.startswith('Bearer '):
+                return auth_header.split(' ')[1]
+        
+        # 从session获取
+        return session.get('auth_token')
+    
+    def _handle_no_token(self):
+        """处理无token情况"""
+        if is_api_request():
+            return jsonify({
+                'success': False,
+                'error': '需要登录',
+                'code': 'AUTH_REQUIRED'
+            }), HTTPStatus.UNAUTHORIZED
+        return redirect('/login')
+    
+    def _handle_invalid_token(self, error):
+        """处理无效token情况"""
+        if is_api_request():
+            error_str = str(error)
+            error_code = 'INVALID_TOKEN'
+            
+            if '令牌已过期' in error_str or 'expired' in error_str.lower():
+                error_code = 'TOKEN_EXPIRED'
+            elif '无效的令牌' in error_str or 'invalid' in error_str.lower():
+                error_code = 'TOKEN_INVALID'
+            
+            return jsonify({
+                'success': False,
+                'error': str(error),
+                'code': error_code,
+                'can_refresh': 'TOKEN_EXPIRED' in error_code
+            }), HTTPStatus.UNAUTHORIZED
+
+        # 根据错误类型重定向到不同页面
+        error_str = str(error)
+        if '令牌已过期' in error_str or 'expired' in error_str.lower():
+            return redirect('/login_error?error=token_expired&message=登录已过期,请重新登录&code=TOKEN_EXPIRED')
+        elif '无效的令牌' in error_str or 'invalid' in error_str.lower():
+            return redirect('/login_error?error=token_invalid&message=登录凭证无效,请重新登录&code=TOKEN_INVALID')
+        else:
+            return redirect('/login')
+
+# 创建全局实例
+auth_middleware = AuthMiddleware()
+
+def init_auth(app):
+    """初始化认证中间件"""
+    auth_middleware.init_app(app)
+    return auth_middleware 

+ 476 - 0
auth_routes.py

@@ -0,0 +1,476 @@
+import os
+import json
+import uuid
+import logging
+from typing import Dict
+
+import requests
+from flask import Blueprint, request, jsonify, session, make_response, current_app, render_template, redirect, url_for
+from http import HTTPStatus
+
+# 导入数据库模型
+from models import get_or_create_user, update_user_login_info, User, db
+
+# 认证蓝图
+auth_bp = Blueprint('auth', __name__, url_prefix='/api/auth')
+logger = logging.getLogger(__name__)
+
+def get_dt_config() -> Dict[str, str]:
+    """获取钉钉配置"""
+    return {
+        'client_id': os.getenv('DINGTALK_CLIENT_ID', ''),
+        'client_secret': os.getenv('DINGTALK_CLIENT_SECRET', ''),
+        'redirect_uri': os.getenv('DINGTALK_REDIRECT_URI', '')
+    }
+
+def dt_enabled() -> bool:
+    """检查钉钉是否已配置"""
+    cfg = get_dt_config()
+    return all([cfg['client_id'], cfg['client_secret'], cfg['redirect_uri']])
+
+def check_database_connection() -> bool:
+    """检查数据库连接是否正常"""
+    try:
+        with current_app.app_context():
+            # 尝试执行一个简单的查询
+            from sqlalchemy import text
+            db.session.execute(text('SELECT 1'))
+            return True
+    except Exception as e:
+        logger.error(f'数据库连接检查失败: {e}')
+        return False
+
+@auth_bp.route('/ping', methods=['GET'])
+def ping():
+    """健康检查接口"""
+    return jsonify({
+        'success': True, 
+        'message': 'auth ok',
+        'dingtalk_enabled': dt_enabled()
+    })
+
+@auth_bp.route('/login_error')
+def login_error():
+    """登录错误页面"""
+    error_type = request.args.get('error', 'unknown')
+    error_message = request.args.get('message', '')
+    error_code = request.args.get('code', '')
+
+    return render_template('login_error.html',
+                         error_type=error_type,
+                         error_message=error_message,
+                         error_code=error_code)
+
+@auth_bp.route('/dingtalk/login_url', methods=['GET'])
+def dingtalk_login_url():
+    """生成钉钉登录URL"""
+    if not dt_enabled():
+        return jsonify({'success': False, 'error': 'DingTalk未配置'}), HTTPStatus.BAD_REQUEST
+
+    try:
+        state = uuid.uuid4().hex
+        session['dt_oauth_state'] = state
+        cfg = get_dt_config()
+
+        auth_url = (
+            'https://login.dingtalk.com/oauth2/auth'
+            f'?client_id={cfg["client_id"]}'
+            f'&response_type=code'
+            f'&scope=openid%20profile%20Contact.User.Read'
+            f'&redirect_uri={requests.utils.quote(cfg["redirect_uri"], safe="")}'
+            f'&state={state}'
+            f'&prompt=consent'
+        )
+        return jsonify({'success': True, 'url': auth_url})
+    except Exception as e:
+        logger.error(f'生成钉钉登录URL失败: {e}')
+        return jsonify({'success': False, 'error': '生成登录链接失败'}), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@auth_bp.route('/dingtalk/callback', methods=['GET'])
+def dingtalk_oauth_callback():
+    """钉钉OAuth回调处理"""
+    if not dt_enabled():
+        return redirect(url_for('auth.login_error',
+                              error='config_error',
+                              message='钉钉OAuth未正确配置,请联系管理员'))
+    
+    # 检查数据库连接
+    if not check_database_connection():
+        return redirect(url_for('auth.login_error',
+                              error='database_error',
+                              message='数据库连接失败,请稍后重试',
+                              code='DATABASE_CONNECTION_FAILED'))
+
+    try:
+        # 验证参数
+        code = request.args.get('code')
+        state = request.args.get('state')
+        if not code or not state or state != session.get('dt_oauth_state'):
+            return redirect(url_for('auth.login_error',
+                                  error='oauth_failed',
+                                  message='OAuth认证参数无效或已过期',
+                                  code='INVALID_PARAMS'))
+
+        # 获取access token
+        cfg = get_dt_config()
+        try:
+            token_resp = requests.post(
+                'https://api.dingtalk.com/v1.0/oauth2/userAccessToken',
+                json={
+                    'clientId': cfg['client_id'],
+                    'clientSecret': cfg['client_secret'],
+                    'code': code,
+                    'grantType': 'authorization_code'
+                },
+                timeout=15
+            )
+        except requests.exceptions.Timeout:
+            logger.error('获取access token超时')
+            return redirect(url_for('auth.login_error',
+                                  error='timeout',
+                                  message='请求超时,请稍后重试',
+                                  code='TOKEN_TIMEOUT'))
+        except requests.exceptions.RequestException as e:
+            logger.error(f'获取access token网络错误: {e}')
+            return redirect(url_for('auth.login_error',
+                                  error='network_error',
+                                  message='网络连接异常,请检查网络后重试',
+                                  code='NETWORK_ERROR'))
+
+        if token_resp.status_code != 200:
+            logger.error(f'获取access token失败: {token_resp.status_code}')
+            return redirect(url_for('auth.login_error',
+                                  error='token_failed',
+                                  message=f'获取访问令牌失败 (HTTP {token_resp.status_code})',
+                                  code=f'TOKEN_HTTP_{token_resp.status_code}'))
+
+        token_data = token_resp.json()
+        access_token = token_data.get('accessToken')
+        if not access_token:
+            logger.error(f"access_token为空: {token_data}")
+            return redirect(url_for('auth.login_error',
+                                  error='token_failed',
+                                  message='获取到的访问令牌无效',
+                                  code='EMPTY_TOKEN'))
+
+        # 获取用户信息
+        user_info = get_dingtalk_user_info(access_token)
+        if not user_info:
+            return redirect(url_for('auth.login_error',
+                                  error='userinfo_failed',
+                                  message='无法获取用户信息,请确保钉钉账户信息完整',
+                                  code='USERINFO_FAILED'))
+
+        user_id = user_info.get('openid') or str(uuid.uuid4())
+
+        # 保存或更新用户信息到数据库
+        try:
+            # 获取客户端IP
+            client_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
+            if client_ip and ',' in client_ip:
+                client_ip = client_ip.split(',')[0].strip()
+            
+            # 准备用户数据
+            user_display_name = user_info.get('name', '钉钉用户')
+            user_data = {
+                'username': f"dingtalk_{user_id[:8]}",  # 系统内部唯一标识
+                'nickname': user_display_name,  # 用户显示名称
+                'avatar_url': user_info.get('avatar', ''),
+                'email': user_info.get('email', ''),
+                'phone': user_info.get('mobile', '') or None,  # 空字符串转换为None
+                'dingtalk_id': user_id,
+                'dingtalk_unionid': user_info.get('openid', ''),
+                'openid': user_info.get('openid', ''),
+                'status': 'active'
+            }
+            
+            # 获取或创建用户
+            db_user = get_or_create_user('dingtalk', **user_data)
+            
+            # 更新登录信息
+            update_user_login_info(db_user, client_ip)
+            
+            # 更新session中的用户信息,包含数据库ID
+            user_info['db_user_id'] = db_user.user_id
+            
+        except Exception as e:
+            logger.error(f'保存用户信息到数据库失败: {e}', exc_info=True)
+            # 数据库操作失败,返回错误页面
+            return redirect(url_for('auth.login_error',
+                                  error='database_error',
+                                  message=f'用户信息保存失败: {str(e)}',
+                                  code='DATABASE_SAVE_FAILED'))
+
+        # 生成JWT token
+        jwt_token = generate_jwt_token(user_id, user_info)
+        if not jwt_token:
+            return redirect(url_for('auth.login_error',
+                                  error='jwt_failed',
+                                  message='生成登录凭证失败,系统可能正在维护中',
+                                  code='JWT_GENERATION_FAILED'))
+
+        # 保存到session
+        session['auth_token'] = jwt_token
+        session['user_info'] = user_info
+
+        # 直接跳转到首页,通过URL参数传递用户信息
+        import urllib.parse
+        user_info_json = urllib.parse.quote(json.dumps(user_info, ensure_ascii=False))
+        redirect_url = f"/?login_success=true&token={jwt_token}&user_info={user_info_json}"
+        return redirect(redirect_url)
+
+    except Exception as e:
+        logger.error(f'钉钉OAuth回调处理失败: {e}', exc_info=True)
+        return redirect(url_for('auth.login_error',
+                              error='unknown',
+                              message=f'登录过程中发生未知错误: {str(e)}',
+                              code='CALLBACK_EXCEPTION'))
+
+def get_dingtalk_user_info(access_token: str) -> Dict:
+    """获取钉钉用户信息"""
+    try:
+        # 使用正确的钉钉API端点
+        headers = {
+            'x-acs-dingtalk-access-token': access_token,
+            'Content-Type': 'application/json'
+        }
+        # 钉钉官方推荐的获取用户信息API
+        api_url = 'https://api.dingtalk.com/v1.0/contact/users/me'
+        
+        resp = requests.get(api_url, headers=headers, timeout=15)
+        
+        if resp.status_code == 200:
+            data = resp.json()
+            
+            # 检查是否有错误码
+            if not data.get('errcode') or data.get('errcode') == 0:
+                user_info = parse_dingtalk_user_data(data)
+                if user_info:
+                    return user_info
+            else:
+                logger.warning(f'钉钉API返回错误: {data.get("errcode")} - {data.get("errmsg")}')
+        else:
+            logger.warning(f'钉钉API调用失败: HTTP {resp.status_code}')
+            
+        # 如果API调用失败,返回默认用户信息
+        logger.warning('钉钉API调用失败,使用默认用户信息')
+        return get_default_user_info()
+            
+    except Exception as e:
+        logger.warning(f'获取钉钉用户信息失败: {e}')
+        return get_default_user_info()
+
+def parse_dingtalk_user_data(data: Dict) -> Dict:
+    """解析钉钉用户数据"""
+    try:
+        # 尝试多种可能的字段名
+        name_fields = ['nick', 'nickname', 'realName', 'name', 'nickName', 'userid', 'user_id']
+        name = None
+        for field in name_fields:
+            if data.get(field) and data.get(field).strip():
+                name = data.get(field).strip()
+                break
+        
+        if not name:
+            name = '钉钉用户'
+        
+        # 获取用户ID
+        openid = (data.get('openId') or data.get('openid') or 
+                  data.get('unionId') or data.get('unionid') or
+                  data.get('userid') or data.get('user_id') or 
+                  str(uuid.uuid4()))
+        
+        # 获取头像
+        avatar_fields = ['avatarUrl', 'avatar_url', 'avatar', 'headimgurl', 'avatarUrl']
+        avatar = None
+        for field in avatar_fields:
+            if data.get(field) and data.get(field).strip():
+                avatar = data.get(field).strip()
+                break
+        
+        # 如果头像URL是相对路径,转换为完整URL
+        if avatar and not avatar.startswith(('http://', 'https://')):
+            if avatar.startswith('/'):
+                avatar = f'https://oapi.dingtalk.com{avatar}'
+            else:
+                avatar = f'https://oapi.dingtalk.com/{avatar}'
+        
+        # 获取其他信息
+        email = data.get('email') or data.get('mail') or ''
+        mobile = data.get('mobile') or data.get('phone') or ''
+        job_number = data.get('jobNumber') or data.get('job_number') or ''
+        title = data.get('title') or data.get('position') or ''
+        dept_name = data.get('deptName') or data.get('department_name') or ''
+        
+        result = {
+            'name': name,
+            'nickname': name,  # 添加nickname字段,确保前端能正确显示
+            'openid': openid,
+            'avatar': avatar or '',
+            'email': email,
+            'mobile': mobile,
+            'job_number': job_number,
+            'title': title,
+            'dept_name': dept_name,
+            'login_type': 'dingtalk'
+        }
+        
+        return result
+        
+    except Exception as e:
+        logger.error(f'解析钉钉用户数据失败: {e}')
+        return None
+
+def get_default_user_info() -> Dict:
+    """获取默认用户信息"""
+    return {
+        'name': '钉钉用户',
+        'nickname': '钉钉用户',  # 添加nickname字段
+        'openid': str(uuid.uuid4()),
+        'avatar': '',
+        'email': '',
+        'mobile': '',
+        'job_number': '',
+        'title': '',
+        'dept_name': '',
+        'login_type': 'dingtalk'
+    }
+
+def generate_jwt_token(user_id: str, user_info: Dict) -> str:
+    """生成JWT token"""
+    try:
+        if not hasattr(current_app, 'auth_middleware'):
+            logger.error('auth_middleware未配置')
+            return None
+            
+        auth_middleware = current_app.auth_middleware
+        if not auth_middleware:
+            logger.error('auth_middleware为空')
+            return None
+        
+        # 确保user_info包含必要的字段
+        if not user_info.get('name') and not user_info.get('nickname'):
+            user_info['name'] = '钉钉用户'
+            user_info['nickname'] = '钉钉用户'
+            
+        token = auth_middleware.generate_token(user_id, user_info)
+        return token
+        
+    except Exception as e:
+        logger.error(f'生成JWT token失败: {e}', exc_info=True)
+        return None
+
+@auth_bp.route('/logout', methods=['POST'])
+def logout():
+    """退出登录"""
+    try:
+        session.clear()
+        return jsonify({'success': True, 'message': '退出登录成功'})
+    except Exception as e:
+        logger.error(f'退出登录失败: {e}')
+        return jsonify({'success': False, 'error': '退出登录失败'}), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@auth_bp.route('/verify', methods=['GET'])
+def verify_token():
+    """验证令牌"""
+    try:
+        token = request.headers.get('Authorization', '').replace('Bearer ', '')
+        if not token:
+            return jsonify({'success': False, 'error': '缺少令牌'}), HTTPStatus.UNAUTHORIZED
+            
+        auth_middleware = current_app.auth_middleware
+        payload = auth_middleware.verify_token(token)
+        
+        return jsonify({
+            'success': True, 
+            'user_id': payload['user_id'], 
+            'user_info': payload['user_info']
+        })
+        
+    except ValueError as e:
+        return jsonify({'success': False, 'error': str(e)}), HTTPStatus.UNAUTHORIZED
+    except Exception as e:
+        logger.error(f'验证令牌失败: {e}')
+        return jsonify({'success': False, 'error': '验证失败'}), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@auth_bp.route('/user/info', methods=['GET'])
+def get_user_info():
+    """获取用户信息"""
+    try:
+        token = request.headers.get('Authorization', '').replace('Bearer ', '')
+        if not token:
+            return jsonify({'success': False, 'error': '缺少令牌'}), HTTPStatus.UNAUTHORIZED
+            
+        auth_middleware = current_app.auth_middleware
+        payload = auth_middleware.verify_token(token)
+        
+        return jsonify({'success': True, 'user_info': payload['user_info']})
+        
+    except ValueError as e:
+        return jsonify({'success': False, 'error': str(e)}), HTTPStatus.UNAUTHORIZED
+    except Exception as e:
+        logger.error(f'获取用户信息失败: {e}')
+        return jsonify({'success': False, 'error': '获取用户信息失败'}), HTTPStatus.INTERNAL_SERVER_ERROR
+
+@auth_bp.route('/refresh', methods=['POST'])
+def refresh_token():
+    """刷新JWT令牌"""
+    try:
+        token = request.headers.get('Authorization', '').replace('Bearer ', '')
+        if not token:
+            logger.warning('刷新token请求缺少Authorization头')
+            return jsonify({
+                'success': False, 
+                'error': '缺少令牌',
+                'code': 'MISSING_TOKEN'
+            }), HTTPStatus.UNAUTHORIZED
+            
+        auth_middleware = current_app.auth_middleware
+        
+        try:
+            # 验证当前token
+            payload = auth_middleware.verify_token(token)
+            user_id = payload['user_id']
+            user_info = payload['user_info']
+            
+            # 生成新的token
+            new_token = auth_middleware.generate_token(user_id, user_info)
+            
+            logger.info(f'Token刷新成功,用户ID: {user_id}')
+            return jsonify({
+                'success': True, 
+                'token': new_token,
+                'message': 'Token刷新成功'
+            })
+            
+        except ValueError as e:
+            logger.warning(f'Token验证失败: {str(e)}')
+            # Token已过期或无效,尝试从session获取用户信息
+            user_info = session.get('user_info')
+            if user_info:
+                user_id = user_info.get('openid') or str(uuid.uuid4())
+                new_token = auth_middleware.generate_token(user_id, user_info)
+                
+                logger.info(f'从session刷新token成功,用户ID: {user_id}')
+                return jsonify({
+                    'success': True, 
+                    'token': new_token,
+                    'message': 'Token刷新成功'
+                })
+            else:
+                logger.warning('Token已过期且session中无用户信息,需要重新登录')
+                return jsonify({
+                    'success': False, 
+                    'error': 'Token已过期且无法刷新,请重新登录',
+                    'code': 'TOKEN_EXPIRED_NO_SESSION'
+                }), HTTPStatus.UNAUTHORIZED
+        
+    except Exception as e:
+        logger.error(f'刷新Token失败: {e}', exc_info=True)
+        return jsonify({
+            'success': False, 
+            'error': '刷新Token失败',
+            'code': 'REFRESH_ERROR'
+        }), HTTPStatus.INTERNAL_SERVER_ERROR
+
+ 

+ 167 - 0
config.py

@@ -0,0 +1,167 @@
+import os
+from dataclasses import dataclass
+from typing import Optional, Tuple
+from dotenv import load_dotenv
+
+# 加载环境变量
+load_dotenv()
+
+@dataclass
+class AppConfig:
+    """应用配置数据类"""
+    # API配置
+    api_key: str
+    app_id: str
+    api_endpoint: str
+    
+    # 应用配置
+    secret_key: str
+    port: int
+    debug: bool
+    
+    # 数据库配置
+    db_host: str
+    db_port: int
+    db_name: str
+    db_user: str
+    db_password: str
+    db_charset: str
+    
+    # 安全配置
+    max_message_length: int
+    max_conversation_history: int
+    rate_limit_requests: int
+    rate_limit_window: int
+    
+    # 日志配置
+    log_level: str
+    log_format: str
+    
+    @classmethod
+    def from_env(cls) -> 'AppConfig':
+        """从环境变量创建配置"""
+        return cls(
+            # API配置
+            api_key=os.getenv("API_KEY") or "",
+            app_id=os.getenv("APP_ID") or "",
+            api_endpoint=os.getenv("API_ENDPOINT") or "",
+            
+            # 应用配置
+            secret_key=os.getenv("SECRET_KEY") or os.urandom(24).hex(),
+            port=int(os.getenv("PORT", "8000")),
+            debug=os.getenv("FLASK_DEBUG", "false").lower() == "true",
+            
+            # 数据库配置
+            db_host=os.getenv("DB_HOST", "localhost"),
+            db_port=int(os.getenv("DB_PORT", "3306")),
+            db_name=os.getenv("DB_NAME", "ai_writing_db"),
+            db_user=os.getenv("DB_USER", "root"),
+            db_password=os.getenv("DB_PASSWORD", ""),
+            db_charset=os.getenv("DB_CHARSET", "utf8mb4"),
+            
+            # 安全配置
+            max_message_length=int(os.getenv("MAX_MESSAGE_LENGTH", "20000")),
+            max_conversation_history=int(os.getenv("MAX_CONVERSATION_HISTORY", "50")),
+            rate_limit_requests=int(os.getenv("RATE_LIMIT_REQUESTS", "100")),
+            rate_limit_window=int(os.getenv("RATE_LIMIT_WINDOW", "3600")),
+            
+            # 日志配置
+            log_level=os.getenv("LOG_LEVEL", "INFO"),
+            log_format=os.getenv("LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
+        )
+    
+    def validate(self) -> None:
+        """验证配置的有效性"""
+        if not self.api_key:
+            raise ValueError("API_KEY 环境变量必须设置")
+        # 允许通过基础 APP_ID 或 按智能体区分的 APP_ID_{AGENT} 提供
+        has_base_app_id = bool(self.app_id)
+        has_any_agent_app_id = any(k.startswith("APP_ID_") and os.getenv(k) for k in os.environ.keys())
+        if not (has_base_app_id or has_any_agent_app_id):
+            raise ValueError("必须设置 APP_ID,或通过 APP_ID_{AGENT} 之一提供,例如 APP_ID_KUAISHOU")
+        if not self.api_endpoint:
+            raise ValueError("API_ENDPOINT 环境变量必须设置")
+        
+        if self.max_message_length <= 0:
+            raise ValueError("MAX_MESSAGE_LENGTH 必须大于0")
+        if self.max_conversation_history <= 0:
+            raise ValueError("MAX_CONVERSATION_HISTORY 必须大于0")
+        if self.rate_limit_requests <= 0:
+            raise ValueError("RATE_LIMIT_REQUESTS 必须大于0")
+        if self.rate_limit_window <= 0:
+            raise ValueError("RATE_LIMIT_WINDOW 必须大于0")
+
+    # 新增:根据智能体名称获取 APP_ID(默认快手),带来源信息
+    def get_app_id_for_agent_with_source(self, agent_name: Optional[str]) -> Tuple[str, str]:
+        """
+        根据前端传入的智能体标识,从环境变量读取对应的 APP_ID,并返回使用的环境变量键名。
+        优先匹配:
+        - 快手: APP_ID
+        - 头条: TOUTIAO_APP_ID
+        - 视频号: SHIPINGHAO_APP_ID
+        其次再尝试 APP_ID_{ALIAS} 以及其它别名;最后回退 APP_ID(通用)与 APP_ID_KUAISHOU。
+        返回: (app_id, source_env_key)
+        若找不到返回 ("", "")
+        """
+        normalized = (agent_name or "kuaishou").strip().lower()
+
+        # 1) 按你的明确映射优先
+        primary_map = {
+            'kuaishou': ["APP_ID"],
+            'toutiao': ["TOUTIAO_APP_ID"],
+            'videoaccount': ["SHIPINHAO_APP_ID"],
+            'planning': ["PLANNING_APP_ID"],
+            'diagnosis': ["DIAGNOSIS_APP_ID"],
+        }
+
+        # 2) 兼容性别名(作为次优先)
+        alias_map = {
+            'kuaishou': ["APP_ID_KUAISHOU", "APP_ID_KS", "APP_ID_快手"],
+            'toutiao': ["APP_ID_TOUTIAO", "APP_ID_TT", "APP_ID_头条"],
+            'videoaccount': ["APP_ID_VIDEOACCOUNT", "APP_ID_WECHAT_VIDEO", "APP_ID_SHIPINHAO", "APP_ID_SHIPINGHAO", "APP_ID_视频号", "APP_ID_WXSPH"],
+            'planning': ["APP_ID_PLANNING", "APP_ID_PL", "APP_ID_规划"],
+            'diagnosis': ["DIAGNOSIS_APP_ID",],
+        }
+
+        for key in primary_map.get(normalized, []):
+            val = os.getenv(key)
+            if val:
+                return val, key
+
+        # 生成 APP_ID_{AGENT.upper()} 形式作为额外兼容
+        generic_key = f"APP_ID_{normalized.upper()}"
+        if generic_key not in alias_map.get(normalized, []):
+            alias_candidates = [generic_key] + alias_map.get(normalized, [])
+        else:
+            alias_candidates = alias_map.get(normalized, [])
+
+        for key in alias_candidates:
+            val = os.getenv(key)
+            if val:
+                return val, key
+
+        # 3) 通用 APP_ID 兜底(非快手也可作为兜底)
+        if self.app_id:
+            return self.app_id, "APP_ID"
+        fallback = os.getenv("APP_ID_KUAISHOU")
+        if fallback:
+            return fallback, "APP_ID_KUAISHOU"
+        return "", ""
+
+    # 兼容旧方法:仅返回 APP_ID
+    def get_app_id_for_agent(self, agent_name: Optional[str]) -> str:
+        app_id, _ = self.get_app_id_for_agent_with_source(agent_name)
+        return app_id
+    
+    def get_database_url(self) -> str:
+        """获取数据库连接URL"""
+        return f"mysql+pymysql://{self.db_user}:{self.db_password}@{self.db_host}:{self.db_port}/{self.db_name}?charset={self.db_charset}"
+
+# 创建全局配置实例
+config = AppConfig.from_env()
+
+# 验证配置
+try:
+    config.validate()
+except ValueError as e:
+    exit(1) 

+ 346 - 0
models.py

@@ -0,0 +1,346 @@
+"""
+数据库模型定义
+根据提供的SQL文件创建对应的SQLAlchemy模型
+"""
+
+from datetime import datetime
+from typing import Optional
+from flask_sqlalchemy import SQLAlchemy
+from sqlalchemy import text
+from sqlalchemy import BigInteger, String, Text, DateTime, Boolean, Integer, Enum
+from utils import get_beijing_time
+
+# 创建数据库实例
+db = SQLAlchemy()
+
+class User(db.Model):
+    """增强版用户表"""
+    __tablename__ = 'users'
+    
+    # 基础信息
+    user_id = db.Column(BigInteger, primary_key=True, autoincrement=True)
+    username = db.Column(String(50), nullable=False, unique=True, comment='用户名')
+    
+    # 登录方式相关
+    login_type = db.Column(Enum('dingtalk', 'wecom', 'email', 'phone'), nullable=False, comment='登录方式')
+    dingtalk_id = db.Column(String(200), unique=True, comment='钉钉用户ID')
+    wecom_id = db.Column(String(200), unique=True, comment='企业微信用户ID')
+    
+    # 联系信息
+    email = db.Column(String(255), unique=True, comment='邮箱地址')
+    phone = db.Column(String(20), unique=True, comment='手机号码')
+    
+    # 密码安全(邮箱/手机登录使用)
+    password_hash = db.Column(String(255), comment='密码哈希值')
+    salt = db.Column(String(100), comment='密码盐值')
+    failed_login_attempts = db.Column(Integer, default=0, comment='失败登录次数')
+    locked_until = db.Column(DateTime, nullable=True, comment='账户锁定到期时间')
+    
+    # 个人信息
+    avatar_url = db.Column(String(500), comment='用户头像URL')
+    nickname = db.Column(String(100), comment='用户昵称')
+    
+    # 权限与状态
+    role = db.Column(Enum('admin', 'user'), default='user', comment='用户角色')
+    status = db.Column(Enum('active', 'inactive', 'suspended'), default='active', comment='用户状态')
+    is_deleted = db.Column(Boolean, default=False, comment='是否已删除')
+    deleted_at = db.Column(DateTime, nullable=True, comment='删除时间')
+    
+    # 第三方集成信息
+    dingtalk_unionid = db.Column(String(200), comment='钉钉UnionID')
+    wecom_unionid = db.Column(String(200), comment='企业微信UnionID')
+    openid = db.Column(String(200), comment='通用OpenID')
+    
+    # 时间戳
+    created_at = db.Column(DateTime, default=get_beijing_time, comment='创建时间')
+    updated_at = db.Column(DateTime, default=get_beijing_time, onupdate=get_beijing_time, comment='更新时间')
+    last_login_at = db.Column(DateTime, nullable=True, comment='最后登录时间')
+    last_login_ip = db.Column(String(45), comment='最后登录IP')
+    
+    # 关系
+    conversations = db.relationship('Conversation', backref='user', lazy=True, cascade='all, delete-orphan')
+    
+    def __repr__(self):
+        return f'<User {self.username}>'
+    
+    def to_dict(self):
+        """转换为字典格式"""
+        return {
+            'user_id': self.user_id,
+            'username': self.username,
+            'login_type': self.login_type,
+            'dingtalk_id': self.dingtalk_id,
+            'wecom_id': self.wecom_id,
+            'email': self.email,
+            'phone': self.phone,
+            'avatar_url': self.avatar_url,
+            'nickname': self.nickname,
+            'role': self.role,
+            'status': self.status,
+            'is_deleted': self.is_deleted,
+            'deleted_at': self.deleted_at.isoformat() if self.deleted_at else None,
+            'dingtalk_unionid': self.dingtalk_unionid,
+            'wecom_unionid': self.wecom_unionid,
+            'openid': self.openid,
+            'created_at': self.created_at.isoformat() if self.created_at else None,
+            'updated_at': self.updated_at.isoformat() if self.updated_at else None,
+            'last_login_at': self.last_login_at.isoformat() if self.last_login_at else None,
+            'last_login_ip': self.last_login_ip
+        }
+
+class Conversation(db.Model):
+    """对话会话表"""
+    __tablename__ = 'conversations'
+    
+    conversation_id = db.Column(BigInteger, primary_key=True, autoincrement=True)
+    user_id = db.Column(BigInteger, db.ForeignKey('users.user_id'), nullable=False, comment='用户ID')
+    title = db.Column(String(500), nullable=False, comment='对话标题')
+    conversation_type = db.Column(Enum('chat', 'qa', 'assistant'), default='chat', comment='对话类型')
+    session_id = db.Column(String(255), nullable=True, unique=True, comment='AI会话ID,用于上下文隔离')
+    is_archived = db.Column(Boolean, default=False, comment='是否已归档')
+    is_deleted = db.Column(Boolean, default=False, comment='是否已删除')
+    deleted_at = db.Column(DateTime, nullable=True, comment='删除时间')
+    message_count = db.Column(Integer, default=0, comment='消息数量')
+    last_message_at = db.Column(DateTime, nullable=True, comment='最后消息时间')
+    created_at = db.Column(DateTime, default=get_beijing_time, comment='创建时间')
+    updated_at = db.Column(DateTime, default=get_beijing_time, onupdate=get_beijing_time, comment='更新时间')
+    
+    # 关系
+    messages = db.relationship('Message', backref='conversation', lazy=True, cascade='all, delete-orphan')
+    
+    def __repr__(self):
+        return f'<Conversation {self.conversation_id}: {self.title}>'
+    
+    def to_dict(self):
+        """转换为字典格式"""
+        return {
+            'conversation_id': self.conversation_id,
+            'user_id': self.user_id,
+            'title': self.title,
+            'conversation_type': self.conversation_type,
+            'session_id': self.session_id,
+            'is_archived': self.is_archived,
+            'is_deleted': self.is_deleted,
+            'deleted_at': self.deleted_at.isoformat() if self.deleted_at else None,
+            'message_count': self.message_count,
+            'last_message_at': self.last_message_at.isoformat() if self.last_message_at else None,
+            'created_at': self.created_at.isoformat() if self.created_at else None,
+            'updated_at': self.updated_at.isoformat() if self.updated_at else None
+        }
+
+class Message(db.Model):
+    """消息记录表"""
+    __tablename__ = 'messages'
+    
+    message_id = db.Column(BigInteger, primary_key=True, autoincrement=True)
+    conversation_id = db.Column(BigInteger, db.ForeignKey('conversations.conversation_id'), nullable=False, comment='会话ID')
+    role = db.Column(Enum('user', 'assistant', 'system', 'thinking'), nullable=False, comment='消息角色')
+    content = db.Column(Text, nullable=False, comment='消息内容')
+    thinking_content = db.Column(Text, nullable=True, comment='AI思考过程内容')
+    content_type = db.Column(Enum('text', 'image', 'file'), default='text', comment='内容类型')
+    message_order = db.Column(Integer, nullable=False, comment='消息顺序(从1开始)')
+    token_count = db.Column(Integer, default=0, comment='Token数量')
+    is_deleted = db.Column(Boolean, default=False, comment='是否已删除')
+    deleted_at = db.Column(DateTime, nullable=True, comment='删除时间')
+    created_at = db.Column(DateTime, default=get_beijing_time, comment='创建时间')
+    
+    def __repr__(self):
+        return f'<Message {self.message_id}: {self.role}>'
+    
+    def to_dict(self):
+        """转换为字典格式"""
+        return {
+            'message_id': self.message_id,
+            'conversation_id': self.conversation_id,
+            'role': self.role,
+            'content': self.content,
+            'thinking_content': self.thinking_content,
+            'content_type': self.content_type,
+            'message_order': self.message_order,
+            'token_count': self.token_count,
+            'is_deleted': self.is_deleted,
+            'deleted_at': self.deleted_at.isoformat() if self.deleted_at else None,
+            'created_at': self.created_at.isoformat() if self.created_at else None
+        }
+
+class SystemConfig(db.Model):
+    """系统配置表"""
+    __tablename__ = 'system_config'
+    
+    config_id = db.Column(Integer, primary_key=True, autoincrement=True)
+    config_key = db.Column(String(100), nullable=False, unique=True, comment='配置键')
+    config_value = db.Column(Text, nullable=True, comment='配置值')
+    config_type = db.Column(Enum('string', 'number', 'boolean', 'json'), default='string', comment='配置类型')
+    description = db.Column(String(500), nullable=True, comment='配置描述')
+    created_at = db.Column(DateTime, default=get_beijing_time)
+    updated_at = db.Column(DateTime, default=get_beijing_time, onupdate=get_beijing_time)
+    
+    def __repr__(self):
+        return f'<SystemConfig {self.config_key}>'
+    
+    def to_dict(self):
+        """转换为字典格式"""
+        return {
+            'config_id': self.config_id,
+            'config_key': self.config_key,
+            'config_value': self.config_value,
+            'config_type': self.config_type,
+            'description': self.description,
+            'created_at': self.created_at.isoformat() if self.created_at else None,
+            'updated_at': self.updated_at.isoformat() if self.updated_at else None
+        }
+
+def init_database(app):
+    """初始化数据库"""
+    db.init_app(app)
+    
+    with app.app_context():
+        # 创建所有表
+        db.create_all()
+        
+        # 创建索引
+        create_indexes()
+        
+        # 插入默认配置
+        insert_default_configs()
+
+def create_indexes():
+    """创建数据库索引"""
+    try:
+        # 定义所有需要创建的索引
+        indexes = [
+            # 用户表索引
+            ("idx_users_login_type_status", "CREATE INDEX idx_users_login_type_status ON users (login_type, status)"),
+            ("idx_users_role_status", "CREATE INDEX idx_users_role_status ON users (role, status)"),
+            ("idx_users_email", "CREATE INDEX idx_users_email ON users (email)"),
+            ("idx_users_phone", "CREATE INDEX idx_users_phone ON users (phone)"),
+            ("idx_users_dingtalk", "CREATE INDEX idx_users_dingtalk ON users (dingtalk_id)"),
+            ("idx_users_wecom", "CREATE INDEX idx_users_wecom ON users (wecom_id)"),
+            ("idx_users_created_at", "CREATE INDEX idx_users_created_at ON users (created_at)"),
+            ("idx_users_last_login", "CREATE INDEX idx_users_last_login ON users (last_login_at)"),
+            ("idx_users_is_deleted", "CREATE INDEX idx_users_is_deleted ON users (is_deleted)"),
+            ("idx_users_status_deleted", "CREATE INDEX idx_users_status_deleted ON users (status, is_deleted)"),
+            
+            # 对话表索引
+            ("idx_conversations_user_id", "CREATE INDEX idx_conversations_user_id ON conversations (user_id)"),
+            ("idx_conversations_user_updated", "CREATE INDEX idx_conversations_user_updated ON conversations (user_id, updated_at)"),
+            ("idx_conversations_user_archived", "CREATE INDEX idx_conversations_user_archived ON conversations (user_id, is_archived)"),
+            ("idx_conversations_user_deleted", "CREATE INDEX idx_conversations_user_deleted ON conversations (user_id, is_deleted)"),
+            ("idx_conversations_updated_at", "CREATE INDEX idx_conversations_updated_at ON conversations (updated_at)"),
+            ("idx_conversations_last_message", "CREATE INDEX idx_conversations_last_message ON conversations (last_message_at)"),
+            ("idx_conversations_type_status", "CREATE INDEX idx_conversations_type_status ON conversations (conversation_type, is_deleted)"),
+            
+            # 消息表索引
+            ("idx_messages_conversation_id", "CREATE INDEX idx_messages_conversation_id ON messages (conversation_id)"),
+            ("idx_messages_conversation_order", "CREATE INDEX idx_messages_conversation_order ON messages (conversation_id, message_order)"),
+            ("idx_messages_conversation_deleted", "CREATE INDEX idx_messages_conversation_deleted ON messages (conversation_id, is_deleted)"),
+            ("idx_messages_created_at", "CREATE INDEX idx_messages_created_at ON messages (created_at)"),
+            ("idx_messages_role", "CREATE INDEX idx_messages_role ON messages (role)"),
+            ("idx_messages_content_type", "CREATE INDEX idx_messages_content_type ON messages (content_type)")
+        ]
+        
+        db.session.commit()
+    except Exception as e:
+        db.session.rollback()
+
+def insert_default_configs():
+    """插入默认系统配置"""
+    try:
+        # 检查是否已有配置
+        if SystemConfig.query.first():
+            return
+        
+        # 只插入必要的配置
+        essential_configs = [
+            ('system_name', 'AI聊天系统', 'string', '系统名称'),
+            ('system_version', '2.0.0', 'string', '系统版本'),
+            ('user_registration', 'true', 'boolean', '是否允许用户注册'),
+            ('default_user_role', 'user', 'string', '默认用户角色')
+        ]
+        
+        for config_key, config_value, config_type, description in essential_configs:
+            config = SystemConfig(
+                config_key=config_key,
+                config_value=config_value,
+                config_type=config_type,
+                description=description
+            )
+            db.session.add(config)
+        
+        db.session.commit()
+    except Exception as e:
+        db.session.rollback()
+
+def get_or_create_user(login_type: str, **kwargs) -> User:
+    """获取或创建用户"""
+    try:
+        # 根据登录类型查找用户
+        if login_type == 'dingtalk':
+            dingtalk_id = kwargs.get('dingtalk_id')
+            if dingtalk_id:
+                user = User.query.filter_by(dingtalk_id=dingtalk_id, is_deleted=False).first()
+                if user:
+                    return user
+        
+        # 如果没找到,创建新用户
+        # 确保username不重复且符合长度要求
+        username = kwargs.get('username', f"{login_type}_user_{get_beijing_time().timestamp()}")
+        
+        # 确保用户名长度符合要求(至少3个字符)
+        if len(username) < 3:
+            username = f"{username}_{get_beijing_time().timestamp()}"
+        
+        # 检查username是否已存在,如果存在则生成新的
+        counter = 1
+        original_username = username
+        while User.query.filter_by(username=username, is_deleted=False).first():
+            username = f"{original_username}_{counter}"
+            counter += 1
+        
+        # 从kwargs中移除username,避免重复参数
+        if 'username' in kwargs:
+            del kwargs['username']
+        
+        # 处理唯一约束冲突(email, phone等)
+        # 如果email已存在,清空email
+        if 'email' in kwargs and kwargs['email']:
+            existing_email = User.query.filter_by(email=kwargs['email'], is_deleted=False).first()
+            if existing_email:
+                kwargs['email'] = ''
+        
+        # 如果phone已存在,清空phone
+        if 'phone' in kwargs and kwargs['phone']:
+            existing_phone = User.query.filter_by(phone=kwargs['phone'], is_deleted=False).first()
+            if existing_phone:
+                kwargs['phone'] = ''
+        
+        # 确保空字符串字段设置为None,避免数据库约束错误
+        if 'email' in kwargs and kwargs['email'] == '':
+            kwargs['email'] = None
+        if 'phone' in kwargs and kwargs['phone'] == '':
+            kwargs['phone'] = None
+            
+        user = User(
+            login_type=login_type,
+            username=username,
+            **kwargs
+        )
+        
+        db.session.add(user)
+        db.session.commit()
+        return user
+        
+    except Exception as e:
+        db.session.rollback()
+        raise
+
+def update_user_login_info(user: User, login_ip: str = None):
+    """更新用户登录信息"""
+    try:
+        user.last_login_at = get_beijing_time()
+        if login_ip:
+            user.last_login_ip = login_ip
+        db.session.commit()
+    except Exception as e:
+        db.session.rollback()
+        raise

+ 14 - 0
requirements.txt

@@ -0,0 +1,14 @@
+flask
+dashscope
+python-dotenv
+gunicorn
+requests
+flask_cors
+PyJWT
+SQLAlchemy
+PyMySQL
+Flask-SQLAlchemy
+alembic
+openai==2.14.0
+
+

+ 118 - 0
static/css/chat.css

@@ -0,0 +1,118 @@
+/* 思考过程容器样式 */
+.thought-container {
+    position: relative;
+    margin: 10px 0;
+    padding: 15px 15px 15px 20px;
+    background-color: rgba(0, 0, 0, 0.03);
+    border-radius: 8px;
+    font-size: 14px;
+    color: #666;
+    line-height: 1.6;
+    width: 100%;
+    box-sizing: border-box;
+    overflow-wrap: break-word;
+    word-wrap: break-word;
+    word-break: normal;
+    white-space: normal;
+    hyphens: auto;
+    transition: max-height 0.3s ease;
+    text-align: left;
+}
+
+/* 思考过程容器的内容区域 */
+.thought-container-content {
+    display: block;
+    width: 100%;
+}
+
+.thought-container p {
+    margin: 0;
+    padding: 0;
+    display: inline;
+    white-space: normal;
+    overflow-wrap: break-word;
+    word-wrap: break-word;
+    word-break: normal;
+}
+
+/* 给每个段落之间添加空格 */
+.thought-container p:after {
+    content: " ";
+    white-space: normal;
+}
+
+.thought-container br {
+    display: none;
+}
+
+.thought-container::before {
+    content: "思考中...";
+    display: block;
+    font-size: 0.9rem;
+    color: #7952b3;
+    font-weight: 600;
+    margin-bottom: 8px;
+    font-style: normal;
+    letter-spacing: 0.03em;
+}
+
+.thought-container p:last-child {
+    margin-bottom: 0;
+}
+
+/* 思考过程切换按钮 */
+.thought-toggle {
+    position: absolute;
+    top: 8px;
+    left: 8px;
+    width: 24px;
+    height: 24px;
+    background: rgba(255, 255, 255, 0.7);
+    border: 1px solid #ddd;
+    border-radius: 50%;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0;
+    transition: all 0.2s ease;
+    z-index: 20;
+}
+
+.thought-toggle:hover {
+    background: rgba(255, 255, 255, 0.9);
+    transform: scale(1.05);
+}
+
+.thought-toggle:focus {
+    outline: none;
+    box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
+}
+
+.thought-toggle svg {
+    width: 16px;
+    height: 16px;
+    color: #666;
+    transition: transform 0.2s ease;
+}
+
+/* 暗色模式适配 */
+@media (prefers-color-scheme: dark) {
+    .thought-container {
+        background-color: rgba(255, 255, 255, 0.05);
+        color: #aaa;
+    }
+    
+    .thought-toggle {
+        background: rgba(0, 0, 0, 0.3);
+        border-color: #444;
+    }
+    
+    .thought-toggle:hover {
+        background: rgba(0, 0, 0, 0.4);
+    }
+    
+    .thought-toggle svg {
+        color: #aaa;
+    }
+} 

+ 115 - 0
static/css/components/copy-button.css

@@ -0,0 +1,115 @@
+/* 复制按钮样式 */
+.message { 
+  position: relative;
+  margin-bottom: 50px;
+}
+
+.message-content { 
+  position: relative;
+}
+
+.copy-button,
+.copy-button-direct {
+  position: absolute;
+  width: 32px;
+  height: 32px;
+  background: var(--primary-indigo);
+  color: white;
+  border: none;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  z-index: 1001;
+  box-shadow: 0 4px 6px rgba(0,0,0,0.2);
+  transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
+  opacity: 0;
+}
+
+.copy-button {
+  bottom: 8px;
+  right: 8px;
+}
+
+.copy-button-direct {
+  top: 10px;
+  right: 10px;
+}
+
+/* Show button on hover */
+.message:hover .copy-button,
+.message-content:hover .copy-button-direct {
+  opacity: 1;
+}
+
+/* 确保AI消息的复制按钮可见 */
+.message.bot:hover .copy-button,
+.message.bot-message:hover .copy-button {
+  opacity: 1;
+}
+
+.copy-button:hover,
+.copy-button-direct:hover {
+  transform: scale(1.1);
+  box-shadow: 0 6px 10px rgba(0,0,0,0.25);
+}
+
+.copy-button svg,
+.copy-button-direct svg {
+  width: 18px;
+  height: 18px;
+}
+
+/* 复制通知样式 */
+.copy-notification {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  padding: 12px 20px;
+  border-radius: 8px;
+  color: white;
+  font-size: 14px;
+  font-weight: 500;
+  z-index: 9999;
+  box-shadow: 0 4px 10px rgba(0,0,0,0.2);
+  opacity: 0;
+  transition: all 0.3s ease;
+}
+
+.copy-notification.success {
+  background-color: rgba(16, 185, 129, 0.95);
+}
+
+.copy-notification.error {
+  background-color: rgba(239, 68, 68, 0.95);
+}
+
+.copy-notification.show {
+  opacity: 1;
+}
+
+/* 深色模式适配 */
+@media (prefers-color-scheme: dark) {
+  .copy-button {
+    background: var(--primary-indigo);
+    color: white;
+  }
+  
+  .copy-button:hover {
+    background: var(--primary-purple);
+  }
+  
+  .copy-notification {
+    background: var(--glass-background-dark);
+  }
+  
+  .copy-notification.success {
+    background: rgba(16, 185, 129, 0.8);
+  }
+  
+  .copy-notification.error {
+    background: rgba(239, 68, 68, 0.8);
+  }
+} 

+ 214 - 0
static/css/components/markdown.css

@@ -0,0 +1,214 @@
+/* Markdown 渲染样式 */
+
+.markdown-content {
+  line-height: 1.6;
+}
+
+/* 标题样式 */
+.markdown-content h1,
+.markdown-content h2,
+.markdown-content h3,
+.markdown-content h4,
+.markdown-content h5,
+.markdown-content h6 {
+  margin-top: 1.5em;
+  margin-bottom: 0.5em;
+  font-weight: 600;
+  color: var(--gray-900);
+}
+
+.markdown-content h1 {
+  font-size: 1.8em;
+  border-bottom: 1px solid rgba(99, 102, 241, 0.2);
+  padding-bottom: 0.3em;
+}
+
+.markdown-content h2 {
+  font-size: 1.5em;
+  border-bottom: 1px solid rgba(99, 102, 241, 0.1);
+  padding-bottom: 0.2em;
+}
+
+.markdown-content h3 { font-size: 1.3em; }
+.markdown-content h4 { font-size: 1.15em; }
+.markdown-content h5 { font-size: 1.05em; }
+.markdown-content h6 { font-size: 1em; }
+
+/* 段落和列表样式 */
+.markdown-content p {
+  margin-bottom: 1em;
+}
+
+.markdown-content ul,
+.markdown-content ol {
+  padding-left: 1.5em;
+  margin-bottom: 1em;
+}
+
+.markdown-content li {
+  margin-bottom: 0.5em;
+}
+
+/* 代码样式 */
+.markdown-content code {
+  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
+  background-color: rgba(99, 102, 241, 0.08);
+  padding: 0.2em 0.4em;
+  border-radius: 3px;
+  font-size: 0.9em;
+}
+
+.markdown-content pre {
+  background-color: rgba(255, 255, 255, 0.7);
+  backdrop-filter: blur(var(--blur-sm));
+  -webkit-backdrop-filter: blur(var(--blur-sm));
+  padding: 1em;
+  border-radius: var(--radius);
+  overflow-x: auto;
+  margin-bottom: 1em;
+  box-shadow: var(--shadow-sm);
+  border: var(--glass-border);
+}
+
+.markdown-content pre code {
+  background-color: transparent;
+  padding: 0;
+  font-size: 0.9em;
+  color: inherit;
+}
+
+/* 引用块样式 */
+.markdown-content blockquote {
+  border-left: 4px solid rgba(99, 102, 241, 0.4);
+  padding-left: 1em;
+  color: var(--gray-700);
+  margin-bottom: 1em;
+  background-color: rgba(99, 102, 241, 0.05);
+  padding: 0.5em 1em;
+  border-radius: 0 var(--radius) var(--radius) 0;
+}
+
+/* 水平线样式 */
+.markdown-content hr {
+  border: 0;
+  border-top: 1px solid rgba(99, 102, 241, 0.2);
+  margin: 1.5em 0;
+}
+
+/* 链接样式 */
+.markdown-content a {
+  color: var(--primary-indigo);
+  text-decoration: none;
+  border-bottom: 1px dotted var(--primary-indigo);
+}
+
+.markdown-content a:hover {
+  border-bottom: 1px solid var(--primary-indigo);
+}
+
+/* 图片样式 */
+.markdown-content img {
+  max-width: 100%;
+  height: auto;
+  border-radius: var(--radius);
+  margin: 1em 0;
+  box-shadow: var(--shadow-sm);
+}
+
+/* 任务列表样式 */
+.markdown-content ul.task-list {
+  list-style-type: none;
+  padding-left: 1em;
+}
+
+.markdown-content ul.task-list li {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 0.5em;
+}
+
+.markdown-content ul.task-list li input[type="checkbox"] {
+  margin-right: 0.5em;
+  margin-top: 0.3em;
+}
+
+/* 嵌套列表间距 */
+.markdown-content ul ul,
+.markdown-content ul ol,
+.markdown-content ol ul,
+.markdown-content ol ol {
+  margin-top: 0.5em;
+  margin-bottom: 0;
+}
+
+/* 审核内容样式 */
+.markdown-content .violation {
+  color: var(--danger-color);
+  font-weight: 500;
+  background-color: rgba(239, 68, 68, 0.08);
+  padding: 0 0.2em;
+  border-radius: 2px;
+}
+
+.markdown-content .audit-pass {
+  color: var(--success-color);
+  font-weight: 500;
+}
+
+.markdown-content .audit-fail {
+  color: var(--danger-color);
+  font-weight: 500;
+}
+
+/* 风险表格样式 */
+.markdown-content table.risk-table tr.violation-row {
+  background-color: rgba(239, 68, 68, 0.05);
+}
+
+.markdown-content table.risk-table tr.warning-row {
+  background-color: rgba(245, 158, 11, 0.05);
+}
+
+.markdown-content table.risk-table tr.safe-row {
+  background-color: rgba(16, 185, 129, 0.05);
+}
+
+/* 深色模式适配 */
+@media (prefers-color-scheme: dark) {
+  .markdown-content h1,
+  .markdown-content h2,
+  .markdown-content h3,
+  .markdown-content h4,
+  .markdown-content h5,
+  .markdown-content h6 {
+    color: var(--gray-200);
+  }
+
+  .markdown-content pre {
+    background-color: rgba(30, 30, 40, 0.7);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+  }
+  
+  .markdown-content code {
+    background-color: rgba(99, 102, 241, 0.15);
+    color: var(--gray-200);
+  }
+  
+  .markdown-content blockquote {
+    color: var(--gray-300);
+    background-color: rgba(99, 102, 241, 0.1);
+  }
+  
+  .markdown-content a {
+    color: var(--primary-blue);
+    border-bottom: 1px dotted var(--primary-blue);
+  }
+  
+  .markdown-content a:hover {
+    border-bottom: 1px solid var(--primary-blue);
+  }
+  
+  .markdown-content .violation {
+    background-color: rgba(239, 68, 68, 0.15);
+  }
+} 

+ 508 - 0
static/css/components/tables.css

@@ -0,0 +1,508 @@
+/* 表格样式组件 */
+
+/* 通用表格样式 */
+.message-content table,
+.markdown-content table,
+.bot .message-content table {
+  width: 100% !important;
+  table-layout: fixed !important;
+  border-collapse: collapse !important;
+  margin: 1em 0 !important;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05) !important;
+  border-radius: 4px !important;
+  overflow: hidden !important; /* 确保圆角边框生效 */
+  display: table !important; /* 强制表格显示为表格,而不是块 */
+  height: auto !important; /* 高度自适应内容 */
+  max-height: none !important; /* 不限制最大高度 */
+}
+
+/* 表格单元格通用样式 */
+.message-content table th,
+.message-content table td,
+.markdown-content table th,
+.markdown-content table td,
+.bot .message-content table th,
+.bot .message-content table td {
+  padding: 6px 8px !important; /* 减小内边距,使表格更紧凑 */
+  border: 1px solid #e2e8f0 !important;
+  vertical-align: middle !important; /* 使用middle对齐,减少额外高度 */
+  word-wrap: break-word !important;
+  word-break: break-word !important;
+  white-space: normal !important;
+  height: auto !important;
+  min-height: auto !important; /* 确保最小高度自适应 */
+  max-height: none !important; /* 不限制最大高度 */
+  overflow: visible !important;
+  line-height: 1.4 !important; /* 减小行高,使文字更紧凑 */
+  text-align: center !important; /* 确保所有单元格文字居中 */
+}
+
+/* 表头样式 - 确保居中显示 */
+.message-content table th,
+.markdown-content table th,
+.bot .message-content table th {
+  background-color: #eef2ff !important; /* 轻微的紫色背景 */
+  color: #4F46E5 !important; /* 与网站主题色匹配 */
+  font-weight: 600 !important;
+  text-align: center !important; /* 强制表头文字居中显示 */
+  padding: 8px 8px !important; /* 减小表头内边距,使其更紧凑 */
+  line-height: 1.4 !important; /* 保持与单元格一致的行高 */
+  height: auto !important; /* 高度自适应 */
+  min-height: auto !important;
+}
+
+/* 序号列(第一列)宽度和居中 */
+.message-content table th:first-child,
+.message-content table td:first-child,
+.markdown-content table th:first-child,
+.markdown-content table td:first-child,
+.bot .message-content table th:first-child,
+.bot .message-content table td:first-child {
+  width: 5% !important;
+  min-width: 40px !important;
+  text-align: center !important;
+  vertical-align: middle !important;
+  font-weight: 500 !important;
+  height: auto !important; /* 确保高度自适应 */
+  min-height: auto !important;
+}
+
+/* 违规原文列(第二列)宽度 */
+.message-content table th:nth-child(2),
+.message-content table td:nth-child(2),
+.markdown-content table th:nth-child(2),
+.markdown-content table td:nth-child(2),
+.bot .message-content table th:nth-child(2),
+.bot .message-content table td:nth-child(2) {
+  width: 30% !important;
+}
+
+/* 违规原因列(第三列)宽度 */
+.message-content table th:nth-child(3),
+.message-content table td:nth-child(3),
+.markdown-content table th:nth-child(3),
+.markdown-content table td:nth-child(3),
+.bot .message-content table th:nth-child(3),
+.bot .message-content table td:nth-child(3) {
+  width: 30% !important;
+}
+
+/* 修改建议列(第四列)宽度 */
+.message-content table th:nth-child(4),
+.message-content table td:nth-child(4),
+.markdown-content table th:nth-child(4),
+.markdown-content table td:nth-child(4),
+.bot .message-content table th:nth-child(4),
+.bot .message-content table td:nth-child(4) {
+  width: 35% !important;
+}
+
+/* 普通 markdown 表格增强样式(排除审核结果等业务表) */
+.message-content table:not(.audit-result):not(.risk-table),
+.markdown-content table:not(.audit-result):not(.risk-table),
+.bot .message-content table:not(.audit-result):not(.risk-table) {
+  table-layout: auto !important; /* 让列宽根据内容自适应 */
+}
+
+.message-content table:not(.audit-result):not(.risk-table) th,
+.message-content table:not(.audit-result):not(.risk-table) td,
+.markdown-content table:not(.audit-result):not(.risk-table) th,
+.markdown-content table:not(.audit-result):not(.risk-table) td,
+.bot .message-content table:not(.audit-result):not(.risk-table) th,
+.bot .message-content table:not(.audit-result):not(.risk-table) td {
+  width: auto !important;
+  min-width: 0 !important;
+}
+
+.message-content table:not(.audit-result):not(.risk-table) td,
+.markdown-content table:not(.audit-result):not(.risk-table) td,
+.bot .message-content table:not(.audit-result):not(.risk-table) td {
+  text-align: left !important; /* 内容默认左对齐更易阅读 */
+  vertical-align: top !important; /* 从上方开始排版,减少空白 */
+}
+
+.message-content table:not(.audit-result):not(.risk-table) th,
+.markdown-content table:not(.audit-result):not(.risk-table) th,
+.bot .message-content table:not(.audit-result):not(.risk-table) th {
+  text-align: left !important; /* 表头同样左对齐,保持风格一致 */
+  white-space: nowrap !important;
+}
+
+/* 第一行文字居中 - 针对性强化 */
+.message-content table tbody tr:first-child td,
+.markdown-content table tbody tr:first-child td,
+.bot .message-content table tbody tr:first-child td {
+  text-align: center !important;
+  vertical-align: middle !important;
+  background-color: #f9fafb !important; /* 轻微的灰色背景 */
+  height: auto !important; /* 确保高度自适应 */
+  min-height: auto !important;
+}
+
+/* 让表格行有更好的视觉区分 */
+.message-content table tbody tr:nth-child(even),
+.markdown-content table tbody tr:nth-child(even),
+.bot .message-content table tbody tr:nth-child(even) {
+  background-color: #f9fafb !important;
+}
+
+.message-content table tbody tr:hover,
+.markdown-content table tbody tr:hover,
+.bot .message-content table tbody tr:hover {
+  background-color: #f3f4f7 !important;
+}
+
+/* 专门针对审核结果表格的增强样式 */
+table.audit-result,
+table[data-type="audit-result"],
+.message-content table.audit-result,
+.message-content table[data-type="audit-result"],
+.markdown-content table.audit-result,
+.markdown-content table[data-type="audit-result"],
+.bot .message-content table.audit-result,
+.bot .message-content table[data-type="audit-result"] {
+  width: 100% !important;
+  border: 1px solid #e2e8f0 !important;
+  display: table !important;
+}
+
+/* 审核结果表格的单元格增强 */
+table.audit-result th,
+table.audit-result td,
+table[data-type="audit-result"] th,
+table[data-type="audit-result"] td,
+.message-content table.audit-result th,
+.message-content table.audit-result td,
+.message-content table[data-type="audit-result"] th,
+.message-content table[data-type="audit-result"] td,
+.markdown-content table.audit-result th,
+.markdown-content table.audit-result td,
+.markdown-content table[data-type="audit-result"] th,
+.markdown-content table[data-type="audit-result"] td,
+.bot .message-content table.audit-result th,
+.bot .message-content table.audit-result td,
+.bot .message-content table[data-type="audit-result"] th,
+.bot .message-content table[data-type="audit-result"] td {
+  padding: 6px 8px !important; /* 减小内边距,使表格更紧凑 */
+  vertical-align: middle !important; /* 使用middle对齐,减少高度 */
+  text-align: center !important; /* 确保所有单元格文字居中 */
+  height: auto !important; /* 确保高度自适应 */
+  min-height: auto !important;
+  line-height: 1.4 !important; /* 保持紧凑的行高 */
+}
+
+/* 审核结果表格的表头增强 - 强制居中 */
+table.audit-result th,
+table[data-type="audit-result"] th,
+.message-content table.audit-result th,
+.message-content table[data-type="audit-result"] th,
+.markdown-content table.audit-result th,
+.markdown-content table[data-type="audit-result"] th,
+.bot .message-content table.audit-result th,
+.bot .message-content table[data-type="audit-result"] th {
+  text-align: center !important;
+  background-color: #eef2ff !important; /* 轻微的紫色背景 */
+  color: #4F46E5 !important; /* 与网站主题色匹配 */
+}
+
+/* 审核结果表格第一行增强 - 强制居中 */
+table.audit-result tbody tr:first-child td,
+table[data-type="audit-result"] tbody tr:first-child td,
+.message-content table.audit-result tbody tr:first-child td,
+.message-content table[data-type="audit-result"] tbody tr:first-child td,
+.markdown-content table.audit-result tbody tr:first-child td,
+.markdown-content table[data-type="audit-result"] tbody tr:first-child td,
+.bot .message-content table.audit-result tbody tr:first-child td,
+.bot .message-content table[data-type="audit-result"] tbody tr:first-child td {
+  text-align: center !important;
+  vertical-align: middle !important;
+  background-color: #f9fafb !important; /* 轻微的灰色背景 */
+  height: auto !important; /* 确保高度自适应 */
+  min-height: auto !important;
+}
+
+/* 表格单元格文字黑色和居中显示(不包括表头) */
+.message-content table td,
+.markdown-content table td,
+.bot .message-content table td,
+table td {
+  color: #000000 !important; /* 确保所有普通单元格文字为黑色 */
+  text-align: center !important; /* 确保所有单元格文字居中 */
+}
+
+/* 保持表头样式不变,但依然居中 */
+.message-content table th,
+.markdown-content table th,
+.bot .message-content table th,
+table th {
+  text-align: center !important; /* 确保表头文字居中 */
+}
+
+/* 审核结果状态样式 */
+.message-content table tr.safe-row td,
+.markdown-content table tr.safe-row td,
+.bot .message-content table tr.safe-row td,
+.message-content table.risk-table tr.safe-row td,
+.markdown-content table.risk-table tr.safe-row td,
+.bot .message-content table.risk-table tr.safe-row td,
+.safe-row td {
+  color: #000000 !important; /* 正常黑色文本 */
+}
+
+.message-content table tr.violation-row,
+.markdown-content table tr.violation-row,
+.bot .message-content table tr.violation-row,
+.message-content table.risk-table tr.violation-row,
+.markdown-content table.risk-table tr.violation-row,
+.bot .message-content table.risk-table tr.violation-row,
+.violation-row {
+  background-color: rgba(239, 68, 68, 0.05) !important; /* 轻微红色背景 */
+}
+
+.message-content table tr.warning-row,
+.markdown-content table tr.warning-row,
+.bot .message-content table tr.warning-row,
+.message-content table.risk-table tr.warning-row,
+.markdown-content table.risk-table tr.warning-row,
+.bot .message-content table.risk-table tr.warning-row,
+.warning-row {
+  background-color: rgba(245, 158, 11, 0.05) !important; /* 轻微黄色背景 */
+}
+
+/* 响应式样式 */
+@media (max-width: 768px) {
+  .message-content table,
+  .markdown-content table,
+  .bot .message-content table {
+    display: block !important;
+    overflow-x: auto !important;
+    -webkit-overflow-scrolling: touch !important;
+    width: 100% !important; /* 确保在移动设备上也是100%宽度 */
+  }
+  
+  .message-content table th,
+  .message-content table td,
+  .markdown-content table th,
+  .markdown-content table td,
+  .bot .message-content table th,
+  .bot .message-content table td {
+    min-width: 120px !important;
+    text-align: center !important; /* 确保在移动设备上也居中 */
+  }
+  
+  /* 即使在移动设备上也保持表格的宽度一致,只在需要时允许水平滚动 */
+  table.audit-result,
+  table[data-type="audit-result"],
+  .message-content table.audit-result,
+  .message-content table[data-type="audit-result"],
+  .markdown-content table.audit-result,
+  .markdown-content table[data-type="audit-result"],
+  .bot .message-content table.audit-result,
+  .bot .message-content table[data-type="audit-result"] {
+    display: block !important;
+    overflow-x: auto !important;
+    white-space: nowrap !important;
+    width: 100% !important;
+  }
+  
+  table.audit-result td,
+  table[data-type="audit-result"] td,
+  .message-content table.audit-result td,
+  .message-content table[data-type="audit-result"] td,
+  .markdown-content table.audit-result td,
+  .markdown-content table[data-type="audit-result"] td,
+  .bot .message-content table.audit-result td,
+  .bot .message-content table[data-type="audit-result"] td {
+    white-space: normal !important;
+    min-width: 150px !important;
+    text-align: center !important; /* 确保在移动设备上也居中 */
+  }
+}
+
+/* 暗黑模式表格样式优化 */
+@media (prefers-color-scheme: dark) {
+  /* 通用表格样式 - 暗黑模式 */
+  .message-content table,
+  .markdown-content table,
+  .bot .message-content table {
+    background-color: rgba(30, 41, 59, 0.6) !important;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
+    border: 1px solid rgba(255, 255, 255, 0.1) !important;
+  }
+
+  /* 表格单元格通用样式 - 暗黑模式 */
+  .message-content table th,
+  .message-content table td,
+  .markdown-content table th,
+  .markdown-content table td,
+  .bot .message-content table th,
+  .bot .message-content table td {
+    border-color: rgba(255, 255, 255, 0.15) !important;
+    color: #e2e8f0 !important; /* 浅色文字 */
+    padding: 6px 8px !important; /* 与亮色模式保持一致的紧凑内边距 */
+    line-height: 1.4 !important; /* 紧凑的行高 */
+    height: auto !important; /* 高度自适应 */
+    min-height: auto !important;
+    vertical-align: middle !important; /* 使用middle对齐 */
+  }
+
+  /* 表头样式 - 暗黑模式 */
+  .message-content table th,
+  .markdown-content table th,
+  .bot .message-content table th {
+    background-color: rgba(51, 65, 85, 0.8) !important; /* 深色背景 */
+    color: #cbd5e1 !important; /* 浅色文字 */
+    font-weight: 600 !important;
+    border-color: rgba(99, 102, 241, 0.3) !important; /* 主题色边框 */
+    padding: 8px 8px !important; /* 与亮色模式保持一致 */
+    line-height: 1.4 !important; /* 紧凑的行高 */
+    height: auto !important; /* 高度自适应 */
+    min-height: auto !important;
+  }
+
+  /* 第一行文字居中 - 暗黑模式 */
+  .message-content table tbody tr:first-child td,
+  .markdown-content table tbody tr:first-child td,
+  .bot .message-content table tbody tr:first-child td {
+    background-color: rgba(51, 65, 85, 0.4) !important; /* 深色背景 */
+    color: #e2e8f0 !important;
+    height: auto !important; /* 确保高度自适应 */
+    min-height: auto !important;
+  }
+
+  /* 偶数行背景 - 暗黑模式 */
+  .message-content table tbody tr:nth-child(even),
+  .markdown-content table tbody tr:nth-child(even),
+  .bot .message-content table tbody tr:nth-child(even) {
+    background-color: rgba(30, 41, 59, 0.4) !important; /* 深色背景 */
+  }
+
+  /* 悬停效果 - 暗黑模式 */
+  .message-content table tbody tr:hover,
+  .markdown-content table tbody tr:hover,
+  .bot .message-content table tbody tr:hover {
+    background-color: rgba(51, 65, 85, 0.6) !important; /* 深色悬停背景 */
+  }
+
+  /* 审核结果表格 - 暗黑模式 */
+  table.audit-result,
+  table[data-type="audit-result"],
+  .message-content table.audit-result,
+  .message-content table[data-type="audit-result"],
+  .markdown-content table.audit-result,
+  .markdown-content table[data-type="audit-result"],
+  .bot .message-content table.audit-result,
+  .bot .message-content table[data-type="audit-result"] {
+    border-color: rgba(255, 255, 255, 0.15) !important;
+    background-color: rgba(30, 41, 59, 0.6) !important;
+  }
+
+  /* 审核结果表格的表头 - 暗黑模式 */
+  table.audit-result th,
+  table[data-type="audit-result"] th,
+  .message-content table.audit-result th,
+  .message-content table[data-type="audit-result"] th,
+  .markdown-content table.audit-result th,
+  .markdown-content table[data-type="audit-result"] th,
+  .bot .message-content table.audit-result th,
+  .bot .message-content table[data-type="audit-result"] th {
+    background-color: rgba(99, 102, 241, 0.2) !important; /* 主题色背景 */
+    color: #e2e8f0 !important; /* 浅色文字 */
+    border-color: rgba(99, 102, 241, 0.4) !important;
+    padding: 8px 8px !important; /* 保持紧凑的内边距 */
+    line-height: 1.4 !important; /* 紧凑的行高 */
+    height: auto !important; /* 高度自适应 */
+    min-height: auto !important;
+  }
+
+  /* 审核结果表格单元格 - 暗黑模式 */
+  table.audit-result td,
+  table[data-type="audit-result"] td,
+  .message-content table.audit-result td,
+  .message-content table[data-type="audit-result"] td,
+  .markdown-content table.audit-result td,
+  .markdown-content table[data-type="audit-result"] td,
+  .bot .message-content table.audit-result td,
+  .bot .message-content table[data-type="audit-result"] td {
+    padding: 6px 8px !important; /* 保持紧凑的内边距 */
+    line-height: 1.4 !important; /* 紧凑的行高 */
+    height: auto !important; /* 高度自适应 */
+    min-height: auto !important;
+    vertical-align: middle !important; /* 使用middle对齐 */
+  }
+
+  /* 审核结果表格第一行 - 暗黑模式 */
+  table.audit-result tbody tr:first-child td,
+  table[data-type="audit-result"] tbody tr:first-child td,
+  .message-content table.audit-result tbody tr:first-child td,
+  .message-content table[data-type="audit-result"] tbody tr:first-child td,
+  .markdown-content table.audit-result tbody tr:first-child td,
+  .markdown-content table[data-type="audit-result"] tbody tr:first-child td,
+  .bot .message-content table.audit-result tbody tr:first-child td,
+  .bot .message-content table[data-type="audit-result"] tbody tr:first-child td {
+    background-color: rgba(51, 65, 85, 0.4) !important;
+    color: #e2e8f0 !important;
+    height: auto !important; /* 确保高度自适应 */
+    min-height: auto !important;
+  }
+
+  /* 表格单元格文字颜色 - 暗黑模式(覆盖黑色文字) */
+  .message-content table td,
+  .markdown-content table td,
+  .bot .message-content table td,
+  table td {
+    color: #cbd5e1 !important; /* 浅色文字替代黑色 */
+    background-color: rgba(30, 41, 59, 0.3) !important;
+    height: auto !important; /* 确保高度自适应 */
+    min-height: auto !important;
+    padding: 6px 8px !important; /* 保持紧凑的内边距 */
+    line-height: 1.4 !important; /* 紧凑的行高 */
+    vertical-align: middle !important; /* 使用middle对齐 */
+  }
+
+  /* 审核结果状态样式 - 暗黑模式 */
+  .message-content table tr.safe-row td,
+  .markdown-content table tr.safe-row td,
+  .bot .message-content table tr.safe-row td,
+  .message-content table.risk-table tr.safe-row td,
+  .markdown-content table.risk-table tr.safe-row td,
+  .bot .message-content table.risk-table tr.safe-row td,
+  .safe-row td {
+    color: #86efac !important; /* 绿色文字 */
+    background-color: rgba(16, 185, 129, 0.15) !important;
+  }
+
+  .message-content table tr.violation-row,
+  .markdown-content table tr.violation-row,
+  .bot .message-content table tr.violation-row,
+  .message-content table.risk-table tr.violation-row,
+  .markdown-content table.risk-table tr.violation-row,
+  .bot .message-content table.risk-table tr.violation-row,
+  .violation-row {
+    background-color: rgba(239, 68, 68, 0.2) !important; /* 红色背景增强 */
+  }
+
+  .message-content table tr.violation-row td,
+  .markdown-content table tr.violation-row td,
+  .bot .message-content table tr.violation-row td,
+  .violation-row td {
+    color: #fca5a5 !important; /* 浅红色文字 */
+  }
+
+  .message-content table tr.warning-row,
+  .markdown-content table tr.warning-row,
+  .bot .message-content table tr.warning-row,
+  .message-content table.risk-table tr.warning-row,
+  .markdown-content table.risk-table tr.warning-row,
+  .bot .message-content table.risk-table tr.warning-row,
+  .warning-row {
+    background-color: rgba(245, 158, 11, 0.2) !important; /* 黄色背景增强 */
+  }
+
+  .message-content table tr.warning-row td,
+  .markdown-content table tr.warning-row td,
+  .bot .message-content table tr.warning-row td,
+  .warning-row td {
+    color: #fcd34d !important; /* 浅黄色文字 */
+  }
+} 

+ 41 - 0
static/css/fonts.css

@@ -0,0 +1,41 @@
+/* Inter字体文件本地版 */
+@font-face {
+  font-family: 'Inter';
+  font-style: normal;
+  font-weight: 300;
+  font-display: swap;
+  src: local('Inter Light'), local('Inter-Light'), url('../fonts/Inter-Light.woff2') format('woff2');
+}
+@font-face {
+  font-family: 'Inter';
+  font-style: normal;
+  font-weight: 400;
+  font-display: swap;
+  src: local('Inter Regular'), local('Inter-Regular'), url('../fonts/Inter-Regular.woff2') format('woff2');
+}
+@font-face {
+  font-family: 'Inter';
+  font-style: normal;
+  font-weight: 500;
+  font-display: swap;
+  src: local('Inter Medium'), local('Inter-Medium'), url('../fonts/Inter-Medium.woff2') format('woff2');
+}
+@font-face {
+  font-family: 'Inter';
+  font-style: normal;
+  font-weight: 600;
+  font-display: swap;
+  src: local('Inter SemiBold'), local('Inter-SemiBold'), url('../fonts/Inter-SemiBold.woff2') format('woff2');
+}
+@font-face {
+  font-family: 'Inter';
+  font-style: normal;
+  font-weight: 700;
+  font-display: swap;
+  src: local('Inter Bold'), local('Inter-Bold'), url('../fonts/Inter-Bold.woff2') format('woff2');
+}
+
+/* 作为备选方案使用系统字体 */
+body {
+  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+} 

+ 2 - 0
static/css/github.min.css

@@ -0,0 +1,2 @@
+.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}
+

+ 12 - 0
static/css/highlightjs.min.css

@@ -0,0 +1,12 @@
+pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}
+/*!
+  Theme: GitHub
+  Description: Light theme as seen on github.com
+  Author: github.com
+  Maintainer: @Hirse
+  Updated: 2021-05-15
+
+  Outdated base version: https://github.com/primer/github-syntax-light
+  Current colors taken from GitHub's CSS
+*/
+.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}

+ 863 - 0
static/css/login.css

@@ -0,0 +1,863 @@
+/* 登录页面样式 - 现代化亮色设计 */
+* {
+    box-sizing: border-box;
+}
+
+body {
+    margin: 0;
+    padding: 0;
+    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+    background: #f8fafc;
+    overflow-x: hidden;
+}
+
+/* 动画背景 */
+.animated-background {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: -1;
+    background: linear-gradient(135deg, #e0f2fe 0%, #f0f9ff 25%, #f8fafc 50%, #f1f5f9 75%, #e2e8f0 100%);
+}
+
+.gradient-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: radial-gradient(circle at 20% 80%, rgba(59, 130, 246, 0.1) 0%, transparent 50%),
+                radial-gradient(circle at 80% 20%, rgba(168, 85, 247, 0.1) 0%, transparent 50%),
+                radial-gradient(circle at 40% 40%, rgba(14, 165, 233, 0.08) 0%, transparent 50%);
+}
+
+/* 浮动形状动画 */
+.floating-shapes {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+}
+
+.shape {
+    position: absolute;
+    border-radius: 50%;
+    background: linear-gradient(45deg, rgba(59, 130, 246, 0.1), rgba(168, 85, 247, 0.08));
+    animation: float 20s infinite linear;
+}
+
+.shape-1 {
+    width: 80px;
+    height: 80px;
+    top: 10%;
+    left: 10%;
+    animation-delay: 0s;
+    animation-duration: 25s;
+}
+
+.shape-2 {
+    width: 120px;
+    height: 120px;
+    top: 60%;
+    right: 10%;
+    animation-delay: -5s;
+    animation-duration: 30s;
+}
+
+.shape-3 {
+    width: 60px;
+    height: 60px;
+    bottom: 20%;
+    left: 20%;
+    animation-delay: -10s;
+    animation-duration: 20s;
+}
+
+.shape-4 {
+    width: 100px;
+    height: 100px;
+    top: 30%;
+    right: 30%;
+    animation-delay: -15s;
+    animation-duration: 35s;
+}
+
+.shape-5 {
+    width: 40px;
+    height: 40px;
+    bottom: 60%;
+    right: 60%;
+    animation-delay: -20s;
+    animation-duration: 15s;
+}
+
+@keyframes float {
+    0% {
+        transform: translateY(0px) rotate(0deg);
+        opacity: 0.7;
+    }
+    50% {
+        transform: translateY(-20px) rotate(180deg);
+        opacity: 1;
+    }
+    100% {
+        transform: translateY(0px) rotate(360deg);
+        opacity: 0.7;
+    }
+}
+
+/* 主容器 */
+.login-container {
+    min-height: 100vh;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 20px;
+    position: relative;
+}
+
+/* 登录卡片 */
+.login-card {
+    width: 100%;
+    max-width: 595px;
+    padding: 50px 40px;
+    text-align: center;
+    background: rgba(255, 255, 255, 0.9);
+    backdrop-filter: blur(25px);
+    border: 1px solid rgba(255, 255, 255, 0.3);
+    border-radius: 24px;
+    box-shadow: 
+        0 25px 45px rgba(0, 0, 0, 0.08),
+        0 0 0 1px rgba(255, 255, 255, 0.8),
+        inset 0 1px 0 rgba(255, 255, 255, 0.9);
+    position: relative;
+    overflow: hidden;
+    animation: cardEntrance 0.8s ease-out;
+}
+
+.login-card::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: -100%;
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.1), transparent);
+    transition: left 0.6s ease;
+}
+
+.login-card:hover::before {
+    left: 100%;
+}
+
+@keyframes cardEntrance {
+    from {
+        opacity: 0;
+        transform: translateY(30px) scale(0.95);
+    }
+    to {
+        opacity: 1;
+        transform: translateY(0) scale(1);
+    }
+}
+
+/* 装饰元素 */
+.card-decoration {
+    position: absolute;
+    top: 20px;
+    left: 50%;
+    transform: translateX(-50%);
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.decoration-line {
+    width: 30px;
+    height: 2px;
+    background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.4), transparent);
+    border-radius: 1px;
+}
+
+.decoration-dot {
+    width: 6px;
+    height: 6px;
+    background: rgba(59, 130, 246, 0.6);
+    border-radius: 50%;
+    animation: pulse 2s infinite;
+}
+
+/* 登录头部 */
+.login-header {
+    margin-bottom: 50px;
+    position: relative;
+}
+
+.logo-container {
+    position: relative;
+    display: inline-block;
+    margin-bottom: 25px;
+}
+
+.logo-glow {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 120px;
+    height: 120px;
+    background: radial-gradient(circle, rgba(59, 130, 246, 0.2) 0%, transparent 70%);
+    border-radius: 50%;
+    animation: glow 3s ease-in-out infinite alternate;
+}
+
+@keyframes glow {
+    from {
+        opacity: 0.5;
+        transform: translate(-50%, -50%) scale(1);
+    }
+    to {
+        opacity: 0.8;
+        transform: translate(-50%, -50%) scale(1.1);
+    }
+}
+
+.login-logo {
+    width: 90px;
+    height: 90px;
+    border-radius: 22px;
+    box-shadow: 
+        0 8px 32px rgba(0, 0, 0, 0.1),
+        0 0 0 1px rgba(255, 255, 255, 0.8);
+    position: relative;
+    z-index: 1;
+    transition: transform 0.3s ease;
+}
+
+.login-logo:hover {
+    transform: scale(1.05);
+}
+
+.login-header h1 {
+    font-size: 32px;
+    font-weight: 700;
+    color: #1e293b;
+    margin-bottom: 12px;
+    background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+    letter-spacing: -0.5px;
+}
+
+.login-subtitle {
+    font-size: 18px;
+    color: #64748b;
+    margin: 0 0 20px 0;
+    font-weight: 400;
+}
+
+.header-underline {
+    width: 60px;
+    height: 3px;
+    background: linear-gradient(90deg, #3b82f6, #8b5cf6);
+    border-radius: 2px;
+    margin: 0 auto;
+    animation: underlineExpand 0.8s ease-out 0.5s both;
+}
+
+@keyframes underlineExpand {
+    from {
+        width: 0;
+        opacity: 0;
+    }
+    to {
+        width: 60px;
+        opacity: 1;
+    }
+}
+
+/* 登录方法 */
+.login-methods {
+    display: flex;
+    flex-direction: column;
+    gap: 24px;
+    margin-bottom: 40px;
+}
+
+.login-method {
+    display: flex;
+    align-items: center;
+    padding: 28px;
+    background: rgba(255, 255, 255, 0.8);
+    border-radius: 20px;
+    border: 1px solid rgba(226, 232, 240, 0.8);
+    transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+    position: relative;
+    overflow: hidden;
+    backdrop-filter: blur(10px);
+    gap: 24px; /* 增加元素之间的间距 */
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
+}
+
+.login-method::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: -100%;
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.08), transparent);
+    transition: left 0.6s ease;
+}
+
+.login-method:hover::before {
+    left: 100%;
+}
+
+.login-method:hover {
+    transform: translateY(-4px);
+    box-shadow: 
+        0 20px 40px rgba(0, 0, 0, 0.1),
+        0 0 0 1px rgba(59, 130, 246, 0.2);
+    border-color: rgba(59, 130, 246, 0.3);
+}
+
+.method-icon {
+    width: 56px;
+    height: 56px;
+    border-radius: 16px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: 0; /* 移除右边距,使用gap控制间距 */
+    position: relative;
+    overflow: hidden;
+    flex-shrink: 0; /* 防止图标被压缩 */
+}
+
+.icon-bg {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(135deg, #00A6FB, #0582ca); /* 钉钉品牌色渐变 */
+    border-radius: 16px;
+    transition: transform 0.3s ease;
+}
+
+.method-svg {
+    width: 32px;
+    height: 32px;
+    position: relative;
+    z-index: 1;
+    transition: transform 0.3s ease;
+    /* filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3)); */
+}
+
+/* 确保图标在亮色主题下清晰可见 */
+.method-svg circle,
+.method-svg path {
+    stroke-width: 2;
+}
+
+/* 钉钉图标特殊处理,确保在浅色背景下可见 */
+.method-svg[alt="钉钉"] {
+    filter: drop-shadow(0 2px 6px rgba(50, 150, 250, 0.4));
+    /* background: rgba(255, 255, 255, 0.95); */
+    border-radius: 10px;
+    padding: 6px;
+    width: 45px;
+    height: 45px;
+    transition: all 0.3s ease;
+    /* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
+}
+
+.login-method:hover .icon-bg {
+    transform: scale(1.1);
+}
+
+.login-method:hover .method-svg {
+    transform: scale(1.1);
+}
+
+/* 钉钉图标悬停效果 */
+.login-method:hover .method-svg[alt="钉钉"] {
+    /* filter: drop-shadow(0 4px 12px rgba(50, 150, 250, 0.6)); */
+    transform: scale(1.1) rotate(3deg);
+    /* background: rgba(255, 255, 255, 1); */
+    /* box-shadow: 0 4px 16px rgba(50, 150, 250, 0.3); */
+}
+
+.method-info {
+    flex: 1;
+    text-align: left;
+    position: relative;
+    margin-right: 5px; /* 增加与按钮的间距 */
+}
+
+.method-info h3 {
+    font-size: 20px;
+    font-weight: 600;
+    color: #1e293b;
+    margin: 0 0 6px 0;
+    letter-spacing: -0.3px;
+}
+
+.method-info p {
+    font-size: 15px;
+    color: #64748b;
+    margin: 0 0 8px 0;
+    line-height: 1.4;
+}
+
+.method-badge {
+    display: inline-block;
+    background: linear-gradient(135deg, #10b981, #059669);
+    color: white;
+    font-size: 12px;
+    font-weight: 600;
+    padding: 4px 12px;
+    border-radius: 20px;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+    box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
+}
+
+/* 登录按钮 */
+.login-btn {
+    width: 165px;
+    padding: 12px 0;
+    border: none;
+    border-radius: 12px;
+    font-size: 15px;
+    font-weight: 600;
+    cursor: pointer;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+    position: relative;
+    overflow: hidden;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    flex-shrink: 0; /* 防止按钮被压缩 */
+    min-width: 120px; /* 设置最小宽度 */
+    justify-content: center; /* 按钮内容居中 */
+}
+
+.dingtalk-btn {
+    background: linear-gradient(135deg, #00a6fb, #0582ca);
+    color: white;
+    box-shadow: 
+        0 8px 24px rgba(0, 166, 251, 0.3),
+        0 0 0 1px rgba(0, 166, 251, 0.1);
+}
+
+.dingtalk-btn:hover {
+    transform: translateY(-2px);
+    box-shadow: 
+        0 12px 32px rgba(0, 166, 251, 0.4),
+        0 0 0 1px rgba(0, 166, 251, 0.2);
+}
+
+.dingtalk-btn:active {
+    transform: translateY(0);
+}
+
+.btn-text {
+    position: relative;
+    z-index: 1;
+}
+
+.btn-icon {
+    font-size: 18px;
+    transition: transform 0.3s ease;
+}
+
+.dingtalk-btn:hover .btn-icon {
+    transform: translateX(4px);
+}
+
+/* 登录页脚 */
+.login-footer {
+    margin-top: 40px;
+    padding-top: 25px;
+    border-top: 1px solid rgba(226, 232, 240, 0.8);
+}
+
+.login-footer p {
+    font-size: 14px;
+    color: #64748b;
+    margin: 0;
+    line-height: 1.5;
+}
+
+.terms-link, .privacy-link {
+    color: #3b82f6;
+    text-decoration: none;
+    font-weight: 500;
+    transition: all 0.3s ease;
+    position: relative;
+}
+
+.terms-link::after, .privacy-link::after {
+    content: '';
+    position: absolute;
+    bottom: -2px;
+    left: 0;
+    width: 0;
+    height: 1px;
+    background: #3b82f6;
+    transition: width 0.3s ease;
+}
+
+.terms-link:hover::after, .privacy-link:hover::after {
+    width: 100%;
+}
+
+.terms-link:hover, .privacy-link:hover {
+    color: #1d4ed8;
+}
+
+/* 登录状态提示样式 */
+.login-status {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.3);
+    backdrop-filter: blur(10px);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 1000;
+    animation: fadeIn 0.3s ease;
+}
+
+@keyframes fadeIn {
+    from { opacity: 0; }
+    to { opacity: 1; }
+}
+
+.status-content {
+    text-align: center;
+    padding: 50px;
+    background: rgba(255, 255, 255, 0.95);
+    border-radius: 20px;
+    backdrop-filter: blur(20px);
+    border: 1px solid rgba(255, 255, 255, 0.8);
+    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
+    animation: slideUp 0.4s ease;
+}
+
+@keyframes slideUp {
+    from {
+        opacity: 0;
+        transform: translateY(20px);
+    }
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+.status-icon {
+    font-size: 56px;
+    margin-bottom: 24px;
+    animation: pulse 2s infinite;
+}
+
+.status-text {
+    font-size: 20px;
+    color: #1e293b;
+    font-weight: 500;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+    .login-container {
+        padding: 15px;
+    }
+    
+    .login-card {
+        padding: 40px 25px;
+        border-radius: 20px;
+    }
+    
+    .login-header h1 {
+        font-size: 28px;
+    }
+    
+    .login-subtitle {
+        font-size: 16px;
+    }
+    
+    .login-method {
+        padding: 24px 20px;
+        flex-direction: column;
+        text-align: center;
+        gap: 20px; /* 移动端增加垂直间距 */
+    }
+    
+    .method-icon {
+        margin-right: 0;
+        margin-bottom: 0; /* 移除底部边距,使用gap控制 */
+    }
+    
+    .method-info {
+        text-align: center;
+        margin-right: 0; /* 移动端移除右边距 */
+    }
+    
+    .method-badge {
+        margin-top: 8px;
+    }
+    
+    .login-btn {
+        width: 100%;
+        justify-content: center;
+        min-width: auto; /* 移动端移除最小宽度限制 */
+    }
+    
+    .shape {
+        display: none;
+    }
+}
+
+@media (max-width: 480px) {
+    .login-card {
+        padding: 30px 20px;
+    }
+    
+    .login-header h1 {
+        font-size: 24px;
+    }
+    
+    .login-logo {
+        width: 70px;
+        height: 70px;
+    }
+    
+    .logo-glow {
+        width: 100px;
+        height: 100px;
+    }
+}
+
+/* 加载动画 */
+.loading-spinner {
+    width: 24px;
+    height: 24px;
+    border: 3px solid rgba(226, 232, 240, 0.8);
+    border-top: 3px solid #3b82f6;
+    border-radius: 50%;
+    animation: spin 1s linear infinite;
+    margin: 0 auto;
+}
+
+@keyframes spin {
+    0% { transform: rotate(0deg); }
+    100% { transform: rotate(360deg); }
+}
+
+/* 错误提示样式 */
+.error-message {
+    background: rgba(239, 68, 68, 0.1);
+    color: #dc2626;
+    padding: 16px 20px;
+    border-radius: 12px;
+    margin: 20px 0;
+    border-left: 4px solid #ef4444;
+    font-size: 14px;
+    backdrop-filter: blur(10px);
+}
+
+/* 成功提示样式 */
+.success-message {
+    background: rgba(34, 197, 94, 0.1);
+    color: #16a34a;
+    padding: 16px 20px;
+    border-radius: 12px;
+    margin: 20px 0;
+    border-left: 4px solid #22c55e;
+    font-size: 14px;
+    backdrop-filter: blur(10px);
+}
+
+/* 滚动条样式 */
+::-webkit-scrollbar {
+    width: 8px;
+}
+
+::-webkit-scrollbar-track {
+    background: rgba(226, 232, 240, 0.8);
+    border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb {
+    background: rgba(148, 163, 184, 0.6);
+    border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+    background: rgba(148, 163, 184, 0.8);
+}
+
+/* 登录成功页面专用样式 */
+.success-content {
+    text-align: center;
+    margin-top: 30px;
+}
+
+.success-icon {
+    width: 80px;
+    height: 80px;
+    margin: 0 auto 30px;
+    background: linear-gradient(135deg, #10b981, #059669);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-shadow: 
+        0 8px 32px rgba(16, 185, 129, 0.3),
+        0 0 0 1px rgba(16, 185, 129, 0.1);
+    animation: successPulse 2s ease-in-out infinite;
+}
+
+.success-icon svg {
+    width: 40px;
+    height: 40px;
+    color: white;
+    stroke-width: 3;
+}
+
+@keyframes successPulse {
+    0%, 100% {
+        transform: scale(1);
+        box-shadow: 
+            0 8px 32px rgba(16, 185, 129, 0.3),
+            0 0 0 1px rgba(16, 185, 129, 0.1);
+    }
+    50% {
+        transform: scale(1.05);
+        box-shadow: 
+            0 12px 40px rgba(16, 185, 129, 0.4),
+            0 0 0 1px rgba(16, 185, 129, 0.2);
+    }
+}
+
+.user-info {
+    background: rgba(255, 255, 255, 0.8);
+    border-radius: 16px;
+    padding: 24px;
+    margin: 30px 0;
+    border: 1px solid rgba(226, 232, 240, 0.8);
+    backdrop-filter: blur(10px);
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
+}
+
+.user-name {
+    font-size: 24px;
+    font-weight: 700;
+    color: #1e293b;
+    margin-bottom: 8px;
+    background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+}
+
+.user-type {
+    font-size: 16px;
+    color: #64748b;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 1px;
+    background: linear-gradient(135deg, #10b981, #059669);
+    color: white;
+    padding: 6px 16px;
+    border-radius: 20px;
+    display: inline-block;
+    box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
+}
+
+.redirect-container {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 12px;
+    margin-top: 30px;
+    padding: 20px;
+    background: rgba(255, 255, 255, 0.6);
+    border-radius: 12px;
+    border: 1px solid rgba(226, 232, 240, 0.6);
+    backdrop-filter: blur(10px);
+}
+
+.redirect-container span {
+    font-size: 16px;
+    color: #64748b;
+    font-weight: 500;
+}
+
+/* 响应式设计 - 登录成功页面 */
+@media (max-width: 768px) {
+    .success-icon {
+        width: 60px;
+        height: 60px;
+        margin-bottom: 20px;
+    }
+    
+    .success-icon svg {
+        width: 30px;
+        height: 30px;
+    }
+    
+    .user-info {
+        padding: 20px;
+        margin: 20px 0;
+    }
+    
+    .user-name {
+        font-size: 20px;
+    }
+    
+    .user-type {
+        font-size: 14px;
+        padding: 4px 12px;
+    }
+    
+    .redirect-container {
+        padding: 16px;
+        margin-top: 20px;
+    }
+    
+    .redirect-container span {
+        font-size: 14px;
+    }
+}
+
+@media (max-width: 480px) {
+    .success-icon {
+        width: 50px;
+        height: 50px;
+    }
+    
+    .success-icon svg {
+        width: 25px;
+        height: 25px;
+    }
+    
+    .user-info {
+        padding: 16px;
+    }
+    
+    .user-name {
+        font-size: 18px;
+    }
+} 

+ 251 - 0
static/css/modal.css

@@ -0,0 +1,251 @@
+/* 自定义弹框样式 */
+.custom-modal {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: 10000;
+    display: none;
+    align-items: center;
+    justify-content: center;
+    opacity: 0;
+    visibility: hidden;
+    transition: all 0.3s ease;
+}
+
+.custom-modal.show {
+    display: flex;
+    opacity: 1;
+    visibility: visible;
+}
+
+.modal-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.5);
+    backdrop-filter: blur(4px);
+    -webkit-backdrop-filter: blur(4px);
+}
+
+.modal-content {
+    position: relative;
+    background: #ffffff;
+    border: 1px solid #e5e7eb;
+    border-radius: 16px;
+    box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+    backdrop-filter: none;
+    -webkit-backdrop-filter: none;
+    max-width: 480px;
+    width: 90%;
+    max-height: 90vh;
+    overflow: hidden;
+    transform: scale(0.9) translateY(20px);
+    transition: all 0.3s ease;
+}
+
+.custom-modal.show .modal-content {
+    transform: scale(1) translateY(0);
+}
+
+.modal-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 24px 24px 0 24px;
+    border-bottom: 1px solid #e5e7eb;
+    margin-bottom: 24px;
+}
+
+.modal-title {
+    font-size: 20px;
+    font-weight: 600;
+    color: #111827;
+    margin: 0;
+}
+
+.modal-close {
+    background: none;
+    border: none;
+    color: #6b7280;
+    cursor: pointer;
+    padding: 8px;
+    border-radius: 8px;
+    transition: all 0.2s ease;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.modal-close:hover {
+    background: #f3f4f6;
+    color: #374151;
+}
+
+.modal-body {
+    padding: 0 24px 24px 24px;
+    text-align: center;
+}
+
+.modal-icon {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 20px;
+}
+
+.modal-icon.delete-icon {
+    color: #ef4444;
+}
+
+.modal-icon.rename-icon {
+    color: #3b82f6;
+}
+
+.modal-icon.message-icon {
+    color: #10b981;
+}
+
+.modal-message {
+    font-size: 16px;
+    color: #374151;
+    margin: 0;
+    line-height: 1.5;
+}
+
+.form-group {
+    text-align: left;
+    margin-top: 20px;
+}
+
+.form-label {
+    display: block;
+    font-size: 14px;
+    font-weight: 500;
+    color: #374151;
+    margin-bottom: 8px;
+}
+
+.form-input {
+    width: 100%;
+    padding: 12px 16px;
+    background: #ffffff;
+    border: 1px solid #d1d5db;
+    border-radius: 8px;
+    color: #111827;
+    font-size: 16px;
+    transition: all 0.2s ease;
+    box-sizing: border-box;
+}
+
+.form-input:focus {
+    outline: none;
+    border-color: #3b82f6;
+    background: #ffffff;
+    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+}
+
+.form-input::placeholder {
+    color: #9ca3af;
+}
+
+.form-hint {
+    font-size: 12px;
+    color: #6b7280;
+    margin-top: 4px;
+}
+
+.modal-footer {
+    display: flex;
+    gap: 12px;
+    padding: 0 24px 24px 24px;
+    justify-content: flex-end;
+}
+
+.btn {
+    padding: 12px 24px;
+    border: none;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    min-width: 80px;
+}
+
+.btn-primary {
+    background: #3b82f6;
+    color: white;
+}
+
+.btn-primary:hover {
+    background: #2563eb;
+    transform: translateY(-1px);
+}
+
+.btn-secondary {
+    background: #f9fafb;
+    color: #374151;
+    border: 1px solid #d1d5db;
+}
+
+.btn-secondary:hover {
+    background: #f3f4f6;
+    transform: translateY(-1px);
+}
+
+.btn-danger {
+    background: #ef4444;
+    color: white;
+}
+
+.btn-danger:hover {
+    background: #dc2626;
+    transform: translateY(-1px);
+}
+
+.btn:active {
+    transform: translateY(0);
+}
+
+/* 消息弹框特殊样式 */
+.message-modal-content {
+    max-width: 360px;
+}
+
+.message-modal-content .modal-body {
+    padding: 24px;
+}
+
+.message-modal-content .modal-footer {
+    padding: 0 24px 24px 24px;
+    justify-content: center;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+    .modal-content {
+        width: 95%;
+        margin: 20px;
+    }
+    
+    .modal-header {
+        padding: 20px 20px 0 20px;
+    }
+    
+    .modal-body {
+        padding: 0 20px 20px 20px;
+    }
+    
+    .modal-footer {
+        padding: 0 20px 20px 20px;
+        flex-direction: column;
+    }
+    
+    .btn {
+        width: 100%;
+    }
+}

+ 153 - 0
static/css/stream.css

@@ -0,0 +1,153 @@
+/* 流式响应相关的样式 */
+
+/* 消息加载状态 */
+.message.loading .message-content {
+    min-height: 24px;
+}
+
+/* 打字指示器动画 */
+.typing-indicator {
+    display: inline-flex;
+    align-items: center;
+    margin: 0 auto;
+}
+
+.typing-indicator span {
+    display: inline-block;
+    width: 8px;
+    height: 8px;
+    background-color: rgba(99, 102, 241, 0.7);
+    border-radius: 50%;
+    margin: 0 2px;
+    animation: bounce 1.4s infinite ease-in-out;
+}
+
+.typing-indicator span:nth-child(1) {
+    animation-delay: 0s;
+}
+
+.typing-indicator span:nth-child(2) {
+    animation-delay: 0.2s;
+}
+
+.typing-indicator span:nth-child(3) {
+    animation-delay: 0.4s;
+}
+
+@keyframes bounce {
+    0%, 80%, 100% {
+        transform: scale(0.6);
+    }
+    40% {
+        transform: scale(1);
+    }
+}
+
+/* Markdown 内容样式 */
+.markdown-content {
+    line-height: 1.6;
+}
+
+.markdown-content h1,
+.markdown-content h2,
+.markdown-content h3,
+.markdown-content h4,
+.markdown-content h5,
+.markdown-content h6 {
+    margin-top: 1.5em;
+    margin-bottom: 0.5em;
+    font-weight: 600;
+    color: var(--gray-900);
+}
+
+.markdown-content h1 {
+    font-size: 1.8em;
+    border-bottom: 1px solid rgba(99, 102, 241, 0.2);
+    padding-bottom: 0.3em;
+}
+
+.markdown-content h2 {
+    font-size: 1.5em;
+    border-bottom: 1px solid rgba(99, 102, 241, 0.1);
+    padding-bottom: 0.2em;
+}
+
+.markdown-content h3 {
+    font-size: 1.3em;
+}
+
+.markdown-content p {
+    margin-bottom: 1em;
+}
+
+.markdown-content ul,
+.markdown-content ol {
+    padding-left: 1.5em;
+    margin-bottom: 1em;
+}
+
+.markdown-content li {
+    margin-bottom: 0.5em;
+}
+
+.markdown-content code {
+    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
+    background-color: rgba(99, 102, 241, 0.08);
+    padding: 0.2em 0.4em;
+    border-radius: 3px;
+    font-size: 0.9em;
+}
+
+.markdown-content pre {
+    background-color: rgba(255, 255, 255, 0.7);
+    backdrop-filter: blur(var(--blur-sm));
+    -webkit-backdrop-filter: blur(var(--blur-sm));
+    padding: 1em;
+    border-radius: var(--radius);
+    overflow-x: auto;
+    margin-bottom: 1em;
+    box-shadow: var(--shadow-sm);
+    border: var(--glass-border);
+}
+
+.markdown-content pre code {
+    background-color: transparent;
+    padding: 0;
+    font-size: 0.9em;
+    color: inherit;
+}
+
+.markdown-content blockquote {
+    border-left: 4px solid rgba(99, 102, 241, 0.4);
+    padding-left: 1em;
+    color: var(--gray-700);
+    margin-bottom: 1em;
+    background-color: rgba(99, 102, 241, 0.05);
+    padding: 0.5em 1em;
+    border-radius: 0 var(--radius) var(--radius) 0;
+}
+
+.markdown-content table {
+    border-collapse: collapse;
+    width: 100%;
+    margin-bottom: 1em;
+    background-color: rgba(255, 255, 255, 0.5);
+    backdrop-filter: blur(var(--blur-sm));
+    -webkit-backdrop-filter: blur(var(--blur-sm));
+    border-radius: var(--radius);
+    overflow: hidden;
+    box-shadow: var(--shadow-sm);
+}
+
+.markdown-content th,
+.markdown-content td {
+    border: 1px solid rgba(99, 102, 241, 0.2);
+    padding: 0.75em;
+    text-align: left;
+}
+
+.markdown-content th {
+    background-color: rgba(99, 102, 241, 0.1);
+    font-weight: 600;
+    color: var(--gray-900);
+} 

+ 3171 - 0
static/css/style.css

@@ -0,0 +1,3171 @@
+/* 现代文案撰写系统界面 - 毛玻璃效果版 */
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
+
+/* 导入组件样式 */
+@import 'components/copy-button.css';
+@import 'components/tables.css';
+@import 'components/markdown.css';
+
+/*
+ * Z-Index 层级说明:
+ * 1000: 用户下拉菜单、通知消息
+ * 1001: 移动端智能体菜单遮罩层
+ * 1002: 智能体菜单、智能体下拉按钮
+ * 1003+: 其他高级UI元素
+ */
+
+:root {
+    /* 主色调更新为现代风格 */
+    --primary-indigo: #6366f1;
+    --primary-purple: #8b5cf6;
+    --primary-blue: #3b82f6;
+    --primary-teal: #14b8a6;
+    
+    /* 渐变色 */
+    --indigo-purple-gradient: linear-gradient(135deg, var(--primary-indigo), var(--primary-purple));
+    --blue-teal-gradient: linear-gradient(135deg, var(--primary-blue), var(--primary-teal));
+    
+    /* 状态颜色 */
+    --success-color: #10b981;
+    --warning-color: #f59e0b;
+    --danger-color: #ef4444;
+    --info-color: #3b82f6;
+    
+    /* 创意等级颜色 */
+    --creative-high-color: #10b981;
+    --creative-medium-color: #f59e0b;
+    --creative-low-color: #ef4444;
+    
+    /* 中性色调 */
+    --gray-50: #f9fafb;
+    --gray-100: #f3f4f6;
+    --gray-200: #e5e7eb;
+    --gray-300: #d1d5db;
+    --gray-400: #9ca3af;
+    --gray-500: #6b7280;
+    --gray-600: #4b5563;
+    --gray-700: #374151;
+    --gray-800: #1f2937;
+    --gray-900: #111827;
+    
+    /* 阴影效果 */
+    --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+    --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+    --shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+    --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+    --shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);
+    --shadow-glow-indigo: 0 0 15px 2px rgba(99, 102, 241, 0.25);
+    --shadow-glow-purple: 0 0 15px 2px rgba(139, 92, 246, 0.25);
+    
+    /* 圆角设置 */
+    --radius-sm: 0.25rem;
+    --radius: 0.5rem;
+    --radius-md: 0.75rem;
+    --radius-lg: 1rem;
+    --radius-xl: 1.5rem;
+    --radius-full: 9999px;
+    
+    /* 过渡和动效 */
+    --transition: all 0.3s ease;
+    --transition-slow: all 0.5s ease;
+    --transition-bounce: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
+    
+    /* 毛玻璃效果 */
+    --blur-sm: 4px;
+    --blur-md: 8px;
+    --blur-lg: 12px;
+    --glass-opacity: 0.7;
+    --glass-border: 1px solid rgba(255, 255, 255, 0.2);
+    --glass-background: rgba(255, 255, 255, 0.15);
+    --glass-background-dark: rgba(23, 25, 35, 0.6);
+}
+
+/* 水波纹效果 */
+.ripple {
+    position: absolute;
+    border-radius: 50%;
+    background-color: rgba(255, 255, 255, 0.4);
+    transform: scale(0);
+    animation: ripple 0.6s linear;
+    pointer-events: none;
+}
+
+@keyframes ripple {
+    to {
+        transform: scale(4);
+        opacity: 0;
+    }
+}
+
+.btn, #reset-btn, #toggle-streaming-btn, #send-btn {
+    position: relative;
+    overflow: hidden;
+}
+
+/* 发送按钮动画效果 */
+#send-btn.sending {
+    animation: sending-pulse 0.6s ease-in-out;
+}
+
+/* 停止按钮样式 */
+#send-btn.stopping {
+    background-color: var(--danger-color);
+    transition: background-color 0.3s ease;
+    position: relative;
+}
+
+#send-btn.stopping:hover {
+    background-color: #f43636;
+    transform: translateY(-2px);
+}
+
+#send-btn.stopping svg {
+    width: 12px;
+    height: 12px;
+    color: white;
+    background-color: white;
+    border-radius: 2px;
+}
+
+@keyframes sending-pulse {
+    0% {
+        transform: scale(1);
+    }
+    50% {
+        transform: scale(0.92);
+        background: var(--primary-indigo);
+    }
+    100% {
+        transform: scale(1);
+    }
+}
+
+* {
+    box-sizing: border-box;
+    margin: 0;
+    padding: 0;
+}
+
+/* 全局过渡效果 - 确保主题切换平滑 */
+* {
+    transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
+}
+
+/* 排除不需要过渡的元素 */
+.typing-indicator span,
+.loading-spinner,
+.ripple,
+.message {
+    transition: none !important;
+}
+
+html {
+    height: 100%;
+    scroll-behavior: smooth;
+}
+
+body {
+    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+    background: linear-gradient(135deg, #f5f3ff, #ede9fe, #dbeafe, #e0f2fe);
+    background-attachment: fixed;
+    color: var(--gray-800);
+    line-height: 1.6;
+    min-height: 100vh;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: 20px;
+    position: relative;
+    overflow-x: hidden;
+    transition: background 0.3s ease, color 0.3s ease;
+}
+
+/* 背景动画元素 */
+.glass-particles {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: -1;
+    overflow: hidden;
+}
+
+.glass-particles::before,
+.glass-particles::after {
+    content: '';
+    position: absolute;
+    border-radius: 50%;
+    filter: blur(var(--blur-lg));
+    opacity: 0.4;
+    animation: float 20s infinite alternate ease-in-out;
+}
+
+.glass-particles::before {
+    width: 300px;
+    height: 300px;
+    background: var(--primary-indigo);
+    top: 10%;
+    left: 15%;
+    animation-delay: 0s;
+}
+
+.glass-particles::after {
+    width: 250px;
+    height: 250px;
+    background: var(--primary-purple);
+    bottom: 15%;
+    right: 10%;
+    animation-delay: -10s;
+}
+
+@keyframes float {
+    0% {
+        transform: translate(0, 0) scale(1);
+    }
+    50% {
+        transform: translate(5%, 5%) scale(1.1);
+    }
+    100% {
+        transform: translate(-5%, -5%) scale(0.9);
+    }
+}
+
+body::before {
+    content: '';
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%236366f1' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
+    opacity: 0.5;
+    z-index: -1;
+}
+
+/* 通用毛玻璃效果 */
+.glass-panel {
+    background-color: rgb(255, 255, 255);
+    backdrop-filter: blur(var(--blur-sm));
+    -webkit-backdrop-filter: blur(var(--blur-sm));
+    box-shadow: var(--shadow);
+    border: var(--glass-border);
+    transition: var(--transition);
+}
+
+.glass-panel:hover {
+    box-shadow: var(--shadow-md);
+}
+
+.glass-header {
+    backdrop-filter: blur(var(--blur-md));
+    -webkit-backdrop-filter: blur(var(--blur-md));
+    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
+    z-index: 100;
+    position: relative;
+}
+
+.chat-container {
+    width: 100%;
+    max-width: 1600px;
+    height: calc(100vh - 40px);
+    max-height: 900px;
+    display: flex;
+    flex-direction: row;
+    margin: 0 auto;
+    box-shadow: var(--shadow-lg);
+    position: relative;
+    background-color: rgba(255, 255, 255, 0.1);
+    backdrop-filter: blur(var(--blur-sm));
+    -webkit-backdrop-filter: blur(var(--blur-sm));
+    border: var(--glass-border);
+    border-radius: var(--radius-lg);
+    transition: var(--transition);
+    overflow: hidden; /* 防止内容溢出 */
+    z-index: 1;
+}
+
+.chat-container:hover {
+    box-shadow: var(--shadow-glow-indigo);
+}
+
+/* 历史记录侧边栏样式 */
+.history-sidebar {
+    width: 280px;
+    min-width: 280px;
+    height: 100%;
+    background: rgba(255, 255, 255, 0.15);
+    backdrop-filter: blur(var(--blur-md));
+    -webkit-backdrop-filter: blur(var(--blur-md));
+    border-right: var(--glass-border);
+    display: flex;
+    flex-direction: column;
+    position: relative;
+    z-index: 2;
+}
+
+.sidebar-header {
+    padding: 20px 16px 16px 16px;
+    border-bottom: var(--glass-border);
+    background: rgba(255, 255, 255, 0.1);
+}
+
+.logo-container {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    margin-bottom: 16px;
+}
+
+.sidebar-logo {
+    width: 32px;
+    height: 32px;
+    border-radius: var(--radius);
+    object-fit: contain;
+    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
+}
+
+.sidebar-title {
+    font-size: 1.1rem;
+    font-weight: 600;
+    color: var(--gray-800);
+}
+
+.new-chat-btn {
+    width: 100%;
+    padding: 12px 16px;
+    background: rgba(255, 255, 255, 0.8);
+    border: 1px solid rgba(255, 255, 255, 0.3);
+    border-radius: var(--radius);
+    color: var(--gray-700);
+    font-size: 0.9rem;
+    font-weight: 500;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    box-shadow: var(--shadow-sm);
+    position: relative;
+    overflow: hidden;
+}
+
+.new-chat-btn::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: -100%;
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
+    transition: left 0.5s ease;
+}
+
+.new-chat-btn:hover {
+    background: rgba(255, 255, 255, 0.95);
+    color: var(--gray-900);
+    transform: translateY(-2px);
+    box-shadow: var(--shadow-md);
+    border-color: rgba(99, 102, 241, 0.3);
+}
+
+.new-chat-btn:hover::before {
+    left: 100%;
+}
+
+.new-chat-btn:active {
+    transform: translateY(-1px);
+    box-shadow: var(--shadow-sm);
+}
+
+.new-chat-btn svg {
+    transition: transform 0.3s ease;
+}
+
+.new-chat-btn:hover svg {
+    transform: scale(1.1) rotate(90deg);
+}
+
+.history-content {
+    flex: 1;
+    overflow-y: auto;
+    padding: 8px 0;
+    scrollbar-width: thin;
+    scrollbar-color: rgba(100, 116, 139, 0.3) transparent;
+}
+
+.history-content::-webkit-scrollbar {
+    width: 6px;
+}
+
+.history-content::-webkit-scrollbar-track {
+    background: transparent;
+}
+
+.history-content::-webkit-scrollbar-thumb {
+    background-color: rgba(100, 116, 139, 0.3);
+    border-radius: 3px;
+}
+
+.history-content::-webkit-scrollbar-thumb:hover {
+    background-color: rgba(100, 116, 139, 0.5);
+}
+
+.history-group {
+    margin-bottom: 24px;
+}
+
+.group-header {
+    padding: 8px 16px 8px 16px;
+    font-size: 0.8rem;
+    font-weight: 600;
+    color: var(--gray-500);
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+    margin-bottom: 8px;
+    position: relative;
+    transition: all 0.3s ease;
+}
+
+.group-header::before {
+    content: '';
+    position: absolute;
+    left: 0;
+    top: 50%;
+    width: 3px;
+    height: 12px;
+    background: linear-gradient(to bottom, var(--primary-indigo), var(--primary-purple));
+    border-radius: 2px;
+    transform: translateY(-50%);
+    opacity: 0.6;
+    transition: all 0.3s ease;
+}
+
+.group-header:hover {
+    color: var(--gray-700);
+    transform: translateX(4px);
+}
+
+.group-header:hover::before {
+    opacity: 1;
+    height: 16px;
+}
+
+.conversation-list {
+    padding: 0 8px;
+}
+
+.conversation-item {
+    display: flex;
+    align-items: center;
+    padding: 12px 16px;
+    margin-bottom: 4px;
+    border-radius: var(--radius);
+    cursor: pointer;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    position: relative;
+    background: transparent;
+    border: 1px solid transparent;
+    overflow: hidden;
+}
+
+.conversation-item::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: -100%;
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.1), transparent);
+    transition: left 0.6s ease;
+    opacity: 0;
+}
+
+.conversation-item:hover {
+    background: rgba(255, 255, 255, 0.15);
+    border-color: rgba(255, 255, 255, 0.25);
+    transform: translateX(4px);
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.conversation-item:hover::before {
+    left: 100%;
+    opacity: 1;
+}
+
+.conversation-item.active {
+    background: rgba(99, 102, 241, 0.2);
+    border-color: rgba(99, 102, 241, 0.4);
+    box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
+    transform: translateX(6px);
+}
+
+.conversation-item.active::before {
+    background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.2), transparent);
+    opacity: 1;
+}
+
+.conversation-content {
+    flex: 1;
+    min-width: 0;
+    margin-right: 8px;
+}
+
+.conversation-title {
+    font-size: 0.9rem;
+    color: var(--gray-700);
+    font-weight: 500;
+    line-height: 1.4;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.conversation-item.active .conversation-title {
+    color: var(--primary-indigo);
+    font-weight: 600;
+}
+
+.conversation-menu {
+    width: 24px;
+    height: 24px;
+    background: transparent;
+    border: none;
+    border-radius: 50%;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    opacity: 0;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    color: var(--gray-500);
+    position: relative;
+    overflow: hidden;
+}
+
+.conversation-menu::before {
+    content: '';
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: 0;
+    height: 0;
+    background: rgba(99, 102, 241, 0.1);
+    border-radius: 50%;
+    transition: all 0.3s ease;
+    transform: translate(-50%, -50%);
+}
+
+.conversation-item:hover .conversation-menu {
+    opacity: 1;
+    background: rgba(255, 255, 255, 0.1);
+    transform: scale(1.1);
+}
+
+.conversation-item:hover .conversation-menu::before {
+    width: 100%;
+    height: 100%;
+}
+
+.conversation-menu:hover {
+    background: rgba(255, 255, 255, 0.2);
+    color: var(--gray-700);
+    transform: scale(1.2);
+}
+
+.conversation-menu:hover::before {
+    background: rgba(99, 102, 241, 0.2);
+}
+
+.sidebar-footer {
+    padding: 16px;
+    border-top: var(--glass-border);
+    background: rgba(255, 255, 255, 0.05);
+}
+
+.user-profile {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 12px;
+    border-radius: var(--radius);
+    background: rgba(255, 255, 255, 0.1);
+    border: 1px solid rgba(255, 255, 255, 0.2);
+    cursor: pointer;
+    transition: var(--transition);
+}
+
+.user-profile:hover {
+    background: rgba(255, 255, 255, 0.15);
+    border-color: rgba(255, 255, 255, 0.3);
+}
+
+.user-avatar-sidebar {
+    width: 32px;
+    height: 32px;
+    border-radius: 50%;
+    overflow: hidden;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border: 2px solid rgba(255, 255, 255, 0.3);
+    flex-shrink: 0;
+}
+
+.user-avatar-sidebar img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+}
+
+.avatar-fallback-sidebar {
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: white;
+    font-weight: 600;
+    font-size: 14px;
+    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
+}
+
+.user-info-sidebar {
+    flex: 1;
+    min-width: 0;
+}
+
+.user-name-sidebar {
+    font-size: 0.9rem;
+    font-weight: 600;
+    color: var(--gray-800);
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.user-menu-btn {
+    width: 24px;
+    height: 24px;
+    background: transparent;
+    border: none;
+    border-radius: 50%;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    opacity: 0.6;
+    transition: var(--transition);
+    color: var(--gray-500);
+}
+
+.user-profile:hover .user-menu-btn {
+    opacity: 1;
+    background: rgba(255, 255, 255, 0.1);
+}
+
+.user-menu-btn:hover {
+    background: rgba(255, 255, 255, 0.2);
+    color: var(--gray-700);
+}
+
+/* 对话上下文菜单样式 */
+.conversation-context-menu {
+    position: absolute;
+    background: rgba(255, 255, 255, 0.95);
+    backdrop-filter: blur(20px);
+    -webkit-backdrop-filter: blur(20px);
+    border: 1px solid rgba(255, 255, 255, 0.3);
+    border-radius: 8px;
+    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
+    padding: 8px 0;
+    min-width: 120px;
+    z-index: 1000;
+    opacity: 0;
+    transform: scale(0.9);
+    transition: all 0.2s ease;
+}
+
+.conversation-context-menu.show {
+    opacity: 1;
+    transform: scale(1);
+}
+
+.menu-item {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 8px 16px;
+    cursor: pointer;
+    transition: background-color 0.2s ease;
+    color: var(--gray-700);
+    font-size: 0.9rem;
+}
+
+.menu-item:hover {
+    background: rgba(99, 102, 241, 0.1);
+    color: var(--primary-indigo);
+}
+
+.menu-item svg {
+    width: 16px;
+    height: 16px;
+    flex-shrink: 0;
+}
+
+/* 主聊天区域样式 */
+.main-chat-area {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    min-width: 0;
+    height: 100%;
+}
+
+/* 智能体切换菜单栏样式 */
+.agent-switcher-container {
+    position: absolute;
+    left: -280px;
+    top: 80px;
+    width: 260px;
+    z-index: 20;
+}
+
+.agent-switcher {
+    width: 100%;
+    background: rgba(255, 255, 255, 0.15);
+    backdrop-filter: blur(var(--blur-md));
+    -webkit-backdrop-filter: blur(var(--blur-md));
+    border: var(--glass-border);
+    border-radius: var(--radius-lg);
+    padding: 20px;
+    box-shadow: var(--shadow-lg);
+    transition: var(--transition);
+}
+
+.agent-switcher:hover {
+    box-shadow: var(--shadow-glow-indigo);
+    transform: translateX(5px);
+}
+
+/* 状态指示器样式 */
+.agent-status-indicator {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 20px;
+    padding: 12px 16px;
+    background: rgba(255, 255, 255, 0.1);
+    border-radius: var(--radius);
+    border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.status-dot {
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    background: var(--success-color);
+    animation: pulse 2s infinite;
+}
+
+.status-dot.active {
+    background: var(--primary-indigo);
+}
+
+.status-text {
+    font-size: 0.9rem;
+    color: var(--gray-700);
+    font-weight: 500;
+}
+
+#current-platform-name {
+    color: var(--primary-indigo);
+    font-weight: 600;
+}
+
+@keyframes pulse {
+    0% {
+        opacity: 1;
+    }
+    50% {
+        opacity: 0.5;
+    }
+    100% {
+        opacity: 1;
+    }
+}
+
+.agent-switcher-header {
+    margin-bottom: 20px;
+    padding-bottom: 15px;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.agent-switcher-header h3 {
+    margin: 0 0 10px 0;
+    font-size: 1.1rem;
+    font-weight: 600;
+    color: var(--gray-800);
+    text-align: center;
+}
+
+.current-agent {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+    font-size: 0.9rem;
+}
+
+.agent-label {
+    color: var(--gray-600);
+}
+
+.agent-name {
+    color: var(--primary-indigo);
+    font-weight: 600;
+}
+
+.agent-list {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+}
+
+.agent-item {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 16px 20px;
+    border-radius: var(--radius);
+    background: rgba(255, 255, 255, 0.1);
+    border: 1px solid transparent;
+    cursor: pointer;
+    transition: var(--transition);
+    position: relative;
+    overflow: hidden;
+    user-select: none; /* 防止文本被选中 */
+}
+
+.agent-item:active {
+    transform: scale(0.98);
+    transition: transform 0.1s ease;
+}
+
+.agent-item.active {
+    background: rgba(99, 102, 241, 0.1) !important;
+    border-color: rgba(99, 102, 241, 0.4) !important;
+    box-shadow: var(--shadow-glow-indigo) !important;
+    transform: translateX(5px);
+    cursor: default; /* 激活状态下显示默认光标 */
+}
+
+.agent-item.active::before {
+    opacity: 1;
+}
+
+.agent-item.active .agent-title {
+    color: var(--primary-indigo) !important;
+    font-weight: 700;
+}
+
+.agent-item.active:hover {
+    transform: translateX(5px); /* 保持激活状态的偏移 */
+}
+
+.agent-item::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(135deg, rgba(99, 102, 241, 0.1), rgba(139, 92, 246, 0.1));
+    opacity: 0;
+    transition: var(--transition);
+}
+
+.agent-item:hover {
+    background: rgba(255, 255, 255, 0.2);
+    border-color: rgba(99, 102, 241, 0.3);
+    transform: translateX(3px);
+}
+
+.agent-item:hover::before {
+    opacity: 1;
+}
+
+.agent-item.active .agent-title {
+    color: var(--primary-indigo) !important;
+    font-weight: 700;
+}
+
+.agent-icon {
+    width: 50px;
+    height: 50px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: var(--radius);
+    flex-shrink: 0;
+    transition: var(--transition);
+    overflow: hidden;
+}
+
+.platform-icon {
+    width: 32px;
+    height: 32px;
+    object-fit: contain;
+    transition: var(--transition);
+    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
+}
+
+.icon-fallback {
+    font-size: 1.8rem;
+    color: var(--gray-600);
+    display: none;
+}
+
+/* 图标悬停效果 */
+.agent-item:hover .platform-icon {
+    filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.15));
+}
+
+/* 激活状态下的图标效果 */
+.agent-item.active .platform-icon {
+    filter: drop-shadow(0 4px 12px rgba(99, 102, 241, 0.3));
+}
+
+
+
+
+
+.agent-item:hover .agent-icon .platform-icon {
+    transform: scale(1.1);
+}
+
+
+
+.agent-info {
+    flex: 1;
+    min-width: 0;
+}
+
+.agent-title {
+    font-weight: 600;
+    color: var(--gray-800);
+    font-size: 1.1rem;
+}
+
+
+
+/* 聊天主内容区域 */
+.chat-main-content {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    position: relative;
+    min-height: 0; /* 确保flex子元素能够正确收缩 */
+}
+
+/* 移动端菜单切换按钮 - 默认隐藏 */
+.mobile-menu-toggle {
+    display: none;
+    position: fixed;
+    left: 20px;
+    top: 100px;
+    z-index: 999;
+    width: 50px;
+    height: 50px;
+    background: var(--primary-indigo);
+    border: none;
+    border-radius: 50%;
+    color: white;
+    font-size: 1.2rem;
+    box-shadow: var(--shadow-lg);
+    cursor: pointer;
+    transition: var(--transition);
+}
+
+.mobile-menu-toggle:hover {
+    transform: scale(1.1);
+    box-shadow: var(--shadow-glow-indigo);
+}
+
+.logo-title-container {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+}
+
+.logo-title-link{
+    color: red;
+    text-decoration: none;
+}
+
+.header-logo {
+    width: 40px;
+    height: 40px;
+    border-radius: var(--radius);
+    object-fit: contain;
+    transition: var(--transition);
+    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
+}
+
+.header-logo:hover {
+    transform: scale(1.05);
+    filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.15));
+}
+
+.version-tag {
+    font-size: 0.7rem;
+    font-weight: 500;
+    color: var(--gray-50);
+    background: linear-gradient(135deg, var(--primary-indigo), var(--primary-purple));
+    padding: 2px 8px;
+    border-radius: var(--radius-full);
+    margin-left: 8px;
+    box-shadow: var(--shadow-sm);
+    vertical-align: middle;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+}
+
+header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 12px 20px;
+    background-color: rgba(255, 255, 255, 0.25);
+    border-bottom: var(--glass-border);
+    position: relative;
+    z-index: 2;
+    backdrop-filter: blur(var(--blur-md));
+    -webkit-backdrop-filter: blur(var(--blur-md));
+    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
+}
+
+header::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(to right, rgba(99, 102, 241, 0.15), rgba(139, 92, 246, 0.15));
+    opacity: 0.5;
+    z-index: -1;
+    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
+}
+
+header h1 {
+    font-size: 1.25rem;
+    font-weight: 600;
+    color: var(--gray-800);
+    display: flex;
+    align-items: center;
+    margin: 0;
+}
+
+header h1 svg {
+    margin-right: 8px;
+}
+
+.buttons-container {
+    display: flex;
+    gap: 10px;
+    align-items: center;
+}
+
+/* 用户信息样式 */
+.user-info {
+    position: relative;
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 10px 16px;
+    background: rgba(255, 255, 255, 0.15);
+    border-radius: 12px;
+    border: 1px solid rgba(255, 255, 255, 0.25);
+    backdrop-filter: blur(15px);
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    cursor: pointer;
+}
+
+.user-info:hover {
+    background: rgba(255, 255, 255, 0.2);
+    border-color: rgba(255, 255, 255, 0.35);
+    transform: translateY(-1px);
+    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
+}
+
+.user-details {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+}
+
+.user-avatar {
+    position: relative;
+    width: 36px;
+    height: 36px;
+    border-radius: 50%;
+    overflow: hidden;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border: 2px solid rgba(255, 255, 255, 0.3);
+    transition: all 0.3s ease;
+}
+
+.user-info:hover .user-avatar {
+    border-color: rgba(255, 255, 255, 0.5);
+    transform: scale(1.05);
+}
+
+.user-avatar img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+    transition: transform 0.3s ease;
+}
+
+.user-info:hover .user-avatar img {
+    transform: scale(1.1);
+}
+
+.avatar-fallback {
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: white;
+    font-weight: 600;
+    font-size: 16px;
+    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
+}
+
+.user-text {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 2px;
+}
+
+.user-info-row {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    width: 100%;
+}
+
+.user-name {
+    font-size: 14px;
+    font-weight: 600;
+    color: #1a1a1a;
+    white-space: nowrap;
+    text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5);
+}
+
+.user-type {
+    font-size: 11px;
+    color: #666;
+    opacity: 0.8;
+    padding: 2px 6px;
+    background: rgba(255, 255, 255, 0.3);
+    border-radius: 10px;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+}
+
+.user-title {
+    font-size: 10px;
+    color: #888;
+    opacity: 0.7;
+    font-style: italic;
+    max-width: 120px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.user-dropdown {
+    position: absolute;
+    top: calc(100% + 8px);
+    right: 0;
+    background: rgba(255, 255, 255, 0.98);
+    border-radius: 12px;
+    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
+    backdrop-filter: blur(20px);
+    border: 1px solid rgba(255, 255, 255, 0.3);
+    min-width: 220px;
+    z-index: 1000;
+    opacity: 0;
+    visibility: hidden;
+    transform: translateY(-15px) scale(0.95);
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    overflow: hidden;
+}
+
+.user-info:hover .user-dropdown {
+    opacity: 1;
+    visibility: visible;
+    transform: translateY(0) scale(1);
+}
+
+.user-dropdown::before {
+    content: '';
+    position: absolute;
+    top: -6px;
+    right: 20px;
+    width: 12px;
+    height: 12px;
+    background: rgba(255, 255, 255, 0.98);
+    border: 1px solid rgba(255, 255, 255, 0.3);
+    border-bottom: none;
+    border-right: none;
+    transform: rotate(45deg);
+}
+
+.user-dropdown-content {
+    padding: 16px;
+}
+
+.user-info-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 10px 0;
+    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+    transition: background-color 0.2s ease;
+}
+
+.user-info-item:hover {
+    background-color: rgba(0, 0, 0, 0.02);
+    padding-left: 8px;
+    padding-right: 8px;
+    margin-left: -8px;
+    margin-right: -8px;
+    border-radius: 6px;
+}
+
+.user-info-item:last-child {
+    border-bottom: none;
+}
+
+.info-label {
+    font-size: 12px;
+    color: #666;
+    font-weight: 500;
+}
+
+.info-value {
+    font-size: 12px;
+    color: #333;
+    max-width: 120px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    font-weight: 500;
+}
+
+/* 成功和错误消息样式 */
+.success-message, .error-message {
+    position: fixed;
+    top: 20px;
+    left: 50%;
+    transform: translateX(-50%);
+    padding: 12px 24px;
+    border-radius: 8px;
+    font-weight: 500;
+    z-index: 1000;
+    animation: slideDown 0.3s ease-out;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.success-message {
+    background: linear-gradient(135deg, #4caf50, #45a049);
+    color: white;
+}
+
+.error-message {
+    background: linear-gradient(135deg, #f44336, #d32f2f);
+    color: white;
+}
+
+@keyframes slideDown {
+    from {
+        opacity: 0;
+        transform: translateX(-50%) translateY(-20px);
+    }
+    to {
+        opacity: 1;
+        transform: translateX(-50%) translateY(0);
+    }
+}
+
+.logout-btn {
+    width: 100%;
+    padding: 10px 16px;
+    background: linear-gradient(135deg, #ff4757 0%, #ff3742 100%);
+    color: white;
+    border: none;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+}
+
+.logout-btn:hover {
+    background: linear-gradient(135deg, #ff3742 0%, #ff2f3a 100%);
+    transform: translateY(-1px);
+    box-shadow: 0 4px 12px rgba(255, 71, 87, 0.3);
+}
+
+.logout-btn svg {
+    transition: transform 0.3s ease;
+}
+
+.logout-btn:hover svg {
+    transform: translateX(2px);
+}
+
+.btn {
+    padding: 8px 16px;
+    background-color: transparent;
+    border: none;
+    border-radius: var(--radius);
+    font-weight: 500;
+    font-size: 0.875rem;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 6px;
+    transition: var(--transition);
+    /* color: var(--gray-700); */
+    background-color: rgba(255, 255, 255, 0.5);
+    backdrop-filter: blur(var(--blur-sm));
+    -webkit-backdrop-filter: blur(var(--blur-sm));
+    box-shadow: var(--shadow-sm);
+}
+
+.btn:hover {
+    background-color: rgba(255, 255, 255, 0.7);
+    color: var(--gray-900);
+    box-shadow: var(--shadow);
+    transform: translateY(-1px);
+}
+
+.btn:active {
+    transform: translateY(0);
+    box-shadow: var(--shadow-sm);
+}
+
+.glass-btn {
+    padding: 8px 16px;
+    background-color: rgba(255, 255, 255, 0.3);
+    border: var(--glass-border);
+    color: var(--gray-700);
+    box-shadow: var(--shadow-sm);
+    transition: var(--transition);
+}
+
+.glass-btn:hover {
+    background-color: rgba(255, 255, 255, 0.4);
+    box-shadow: var(--shadow);
+    color: var(--gray-900);
+}
+
+.glass-btn-primary {
+    padding: 10px;
+    background-color: var(--primary-indigo);
+    border: none;
+    color: white;
+    border-radius: var(--radius-full);
+    width: 40px;
+    height: 40px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-shadow: var(--shadow);
+    transition: var(--transition);
+    cursor: pointer;
+    opacity: 1;
+    position: relative;
+    overflow: hidden;
+}
+
+.glass-btn-primary:hover {
+    transform: translateY(-1px);
+    box-shadow: var(--shadow-md);
+}
+
+.glass-btn-primary:active {
+    transform: translateY(0);
+}
+
+.glass-btn svg, .glass-btn-primary svg {
+    width: 16px;
+    height: 16px;
+}
+
+.glass-btn:hover svg {
+    transform: scale(1.05);
+}
+
+.glass-btn-primary:hover svg {
+    transform: scale(1.1);
+}
+
+#reset-btn {
+    color: var(--gray-700);
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    font-size: 0.875rem;
+    font-weight: 500;
+    background-color: rgba(255, 255, 255, 0.4);
+    box-shadow: var(--shadow-sm);
+}
+
+#reset-btn:hover {
+    background-color: rgba(255, 255, 255, 0.6);
+    color: var(--gray-900);
+    box-shadow: var(--shadow);
+}
+
+#reset-btn.confirming {
+    background-color: var(--danger-color);
+    color: white;
+    animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+    0% {
+        box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
+    }
+    70% {
+        box-shadow: 0 0 0 5px rgba(239, 68, 68, 0);
+    }
+    100% {
+        box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
+    }
+}
+
+/* 智能体选择按钮样式 - 与重置按钮保持一致 */
+#agent-dropdown {
+    color: var(--gray-700);
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    font-size: 0.875rem;
+    font-weight: 500;
+    background-color: rgba(255, 255, 255, 0.4);
+    box-shadow: var(--shadow-sm);
+    cursor: pointer;
+    border-radius: var(--radius);
+    transition: var(--transition);
+    position: relative;
+    z-index: 1002;
+}
+
+#agent-dropdown:hover {
+    background-color: rgba(255, 255, 255, 0.6);
+    color: var(--gray-900);
+    box-shadow: var(--shadow);
+}
+
+#agent-dropdown svg {
+    transition: var(--transition);
+}
+
+#agent-dropdown:hover svg {
+    transform: scale(1.05);
+}
+
+/* 智能体菜单样式 */
+#agent-menu {
+    z-index: 1001 !important;
+    position: absolute !important;
+    top: calc(100% + 6px) !important;
+    left: 0 !important;
+    min-width: 220px !important;
+    background: rgba(255, 255, 255, 0.95) !important;
+    backdrop-filter: blur(20px) !important;
+    -webkit-backdrop-filter: blur(20px) !important;
+    border: 1px solid rgba(255, 255, 255, 0.3) !important;
+    border-radius: 12px !important;
+    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15) !important;
+    overflow: hidden !important;
+}
+
+#agent-menu ul {
+    list-style: none !important;
+    margin: 0 !important;
+    padding: 8px !important;
+}
+
+.agent-option {
+    display: flex !important;
+    align-items: center !important;
+    gap: 8px !important;
+    padding: 12px !important;
+    border-radius: 8px !important;
+    cursor: pointer !important;
+    transition: all 0.2s ease !important;
+    background: transparent !important;
+    border: 1px solid transparent !important;
+}
+
+.agent-option:hover {
+    background: rgba(99, 102, 241, 0.1) !important;
+    border-color: rgba(99, 102, 241, 0.2) !important;
+    transform: translateY(-1px) !important;
+}
+
+.agent-option[data-active="true"] {
+    background: rgba(99, 102, 241, 0.15) !important;
+    border-color: rgba(99, 102, 241, 0.3) !important;
+    box-shadow: 0 2px 8px rgba(99, 102, 241, 0.2) !important;
+}
+
+.chat-messages {
+    flex: 1;
+    min-height: 0; /* 确保能够收缩 */
+    overflow-y: auto;
+    padding: 30px;
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    gap: 30px;
+    background: rgba(255, 255, 255, 0.1);
+    backdrop-filter: blur(var(--blur-sm));
+    -webkit-backdrop-filter: blur(var(--blur-sm));
+    scrollbar-width: thin;
+    scrollbar-color: rgba(100, 116, 139, 0.5) transparent;
+}
+
+.chat-messages::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(to bottom, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.02));
+    opacity: 0.3;
+    z-index: -1;
+    pointer-events: none;
+}
+
+.chat-messages::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+}
+
+.chat-messages::-webkit-scrollbar-track {
+    background: transparent;
+}
+
+.chat-messages::-webkit-scrollbar-thumb {
+    background-color: rgba(100, 116, 139, 0.5);
+    border-radius: 20px;
+}
+
+.chat-messages::-webkit-scrollbar-thumb:hover {
+    background-color: rgba(100, 116, 139, 0.7);
+}
+
+.message {
+    padding: 15px 20px;
+    border-radius: var(--radius);
+    animation: fadeIn 0.3s ease-out forwards;
+    word-break: break-word;
+    position: relative;
+    opacity: 0;
+    transform: translateY(10px);
+    transition: transform 0.3s ease, opacity 0.3s ease;
+}
+
+@keyframes fadeIn {
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+.message.user {
+    align-self: flex-end;
+    background-color: rgba(59, 130, 246, 0.1);
+}
+
+.message.bot {
+    align-self: flex-start;
+    background-color: rgba(255, 255, 255, 0.15);
+    width: 90%;
+    max-width: 1200px;
+}
+
+.message-content {
+    font-size: 1rem;
+    line-height: 1.6;
+    background: rgba(255, 255, 255, 0.8);
+    padding: 20px 30px;
+    border-radius: var(--radius);
+    box-shadow: var(--shadow-sm);
+    backdrop-filter: blur(var(--blur-sm));
+    -webkit-backdrop-filter: blur(var(--blur-sm));
+}
+
+.message.bot .message-content {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    padding: 24px;
+    line-height: 1.7;
+    font-size: 1.05rem;
+}
+
+.thought-container {
+    width: 100%;
+    margin: 10px 0 20px 0;
+    background-color: rgba(121, 82, 179, 0.08);
+    border-left: 3px solid #7952b3;
+    padding: 14px 18px;
+    border-radius: 0 8px 8px 0;
+    font-style: italic;
+    color: #6c757d;
+    position: relative;
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+    transition: all 0.3s ease;
+}
+
+.thought-container[data-collapsed="true"] {
+    padding-bottom: 10px;
+}
+
+.thought-container::before {
+    content: "思考中...";
+    display: inline-block;
+    font-size: 0.9rem;
+    color: #7952b3;
+    font-weight: 600;
+    margin-bottom: 8px;
+    font-style: normal;
+    letter-spacing: 0.03em;
+}
+
+.thought-container p {
+    margin: 0 0 8px 0;
+    padding: 0;
+    line-height: 1.6;
+    font-size: 0.95rem;
+    white-space: normal;
+    word-break: normal;
+    word-wrap: break-word;
+    overflow-wrap: break-word;
+    text-align: left;
+    width: 100%;
+    transition: all 0.2s ease;
+    display: block;
+}
+
+/* 最后一个段落不需要底部间距 */
+.thought-container p:last-child {
+    margin-bottom: 0;
+}
+
+/* 确保段落内容按段落显示,不连续 */
+.thought-container p:not(:last-child) {
+    margin-bottom: 8px;
+}
+
+/* 思考过程第一段的特殊样式 */
+.thought-container p:first-of-type {
+    font-weight: normal;
+    margin-top: 0;
+}
+
+/* 折叠/展开思考过程功能 */
+.thought-toggle {
+    position: absolute;
+    top: 12px;
+    right: 12px;
+    background: transparent;
+    border: none;
+    color: #7952b3;
+    cursor: pointer;
+    font-size: 1.2rem;
+    opacity: 0.7;
+    transition: opacity 0.2s;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 24px;
+    height: 24px;
+    padding: 0;
+    border-radius: 50%;
+}
+
+.thought-toggle:hover {
+    opacity: 1;
+    background-color: rgba(121, 82, 179, 0.1);
+}
+
+.chat-input-container {
+    padding: 20px;
+    display: flex;
+    gap: 10px;
+    align-items: flex-start; /* 改为顶部对齐,适应多行输入 */
+    border-top: var(--glass-border);
+    position: relative;
+    z-index: 1;
+    background: rgba(255, 255, 255, 0.15);
+    backdrop-filter: blur(var(--blur-md));
+    -webkit-backdrop-filter: blur(var(--blur-md));
+    flex-shrink: 0; /* 防止输入框被压缩 */
+}
+
+.chat-input-container::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(to right, rgba(99, 102, 241, 0.05), rgba(139, 92, 246, 0.05));
+    opacity: 0.7;
+    z-index: -1;
+    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
+}
+
+#user-input {
+    flex: 1;
+    padding: 12px 20px;
+    border: var(--glass-border);
+    border-radius: var(--radius);
+    background-color: rgba(255, 255, 255, 0.8);
+    font-size: 1rem;
+    resize: none;
+    min-height: 44px; /* 设置最小高度 */
+    max-height: 200px;
+    overflow-y: auto;
+    line-height: 1.5;
+    box-shadow: var(--shadow-inner);
+    transition: var(--transition);
+    font-family: inherit;
+}
+
+#user-input:focus {
+    outline: none;
+    border-color: rgba(99, 102, 241, 0.5);
+    box-shadow: var(--shadow-glow-indigo);
+}
+
+.chat-input-container::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(to right, rgba(99, 102, 241, 0.05), rgba(139, 92, 246, 0.05));
+    opacity: 0.7;
+    z-index: -1;
+    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
+}
+
+#send-btn {
+    width: 40px;
+    height: 40px;
+    background-color: var(--primary-indigo);
+    border: none;
+    border-radius: var(--radius-full);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    box-shadow: var(--shadow);
+    transition: var(--transition);
+    color: white;
+    padding: 0;
+    overflow: hidden;
+}
+
+#send-btn:hover {
+    transform: translateY(-2px) scale(1.03);
+    box-shadow: var(--shadow-md);
+}
+
+#send-btn:active {
+    transform: translateY(0) scale(0.98);
+}
+
+.typing-indicator {
+    display: inline-flex;
+    align-items: center;
+    background-color: rgba(255, 255, 255, 0.5);
+    padding: 8px 16px;
+    border-radius: var(--radius-full);
+    box-shadow: var(--shadow-sm);
+    backdrop-filter: blur(var(--blur-sm));
+    -webkit-backdrop-filter: blur(var(--blur-sm));
+    margin: 0 auto;
+}
+
+.typing-indicator span {
+    display: inline-block;
+    width: 8px;
+    height: 8px;
+    background-color: var(--primary-indigo);
+    border-radius: 50%;
+    margin: 0 2px;
+    animation: typing 1.4s infinite ease-in-out;
+}
+
+.typing-indicator span:nth-child(1) {
+    animation-delay: 0s;
+}
+
+.typing-indicator span:nth-child(2) {
+    animation-delay: 0.2s;
+}
+
+.typing-indicator span:nth-child(3) {
+    animation-delay: 0.4s;
+}
+
+@keyframes typing {
+    0%, 80%, 100% {
+        transform: scale(0.6);
+        opacity: 0.6;
+    }
+    40% {
+        transform: scale(1);
+        opacity: 1;
+    }
+}
+
+/* 深色模式适配 - 自动跟随浏览器主题 */
+@media (prefers-color-scheme: dark) {
+    :root {
+        /* 深色模式下的颜色变量 */
+        --glass-border: 1px solid rgba(255, 255, 255, 0.1);
+        --glass-background: rgba(23, 25, 35, 0.3);
+        --glass-background-dark: rgba(15, 23, 42, 0.6);
+        --shadow-glow-indigo: 0 0 15px 2px rgba(99, 102, 241, 0.35);
+        --shadow-glow-purple: 0 0 15px 2px rgba(139, 92, 246, 0.35);
+        
+        /* 深色模式下的文本颜色 */
+        --text-primary: #e2e8f0;
+        --text-secondary: #cbd5e1;
+        --text-muted: #94a3b8;
+        --text-inverse: #1e293b;
+        
+        /* 深色模式下的背景颜色 */
+        --bg-primary: #0f172a;
+        --bg-secondary: #1e293b;
+        --bg-tertiary: #334155;
+        --bg-glass: rgba(15, 23, 42, 0.8);
+    }
+    
+    body {
+        background: linear-gradient(135deg, #0f172a, #1e293b, #334155, #475569);
+        color: var(--text-primary);
+        transition: background 0.3s ease, color 0.3s ease;
+    }
+    
+    body::before {
+        opacity: 0.1;
+        background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%236366f1' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
+    }
+    
+    /* 主容器深色模式 */
+    .chat-container {
+        background-color: var(--bg-glass);
+        border: var(--glass-border);
+        backdrop-filter: blur(var(--blur-md));
+        -webkit-backdrop-filter: blur(var(--blur-md));
+    }
+    
+    /* 头部深色模式 */
+    header {
+        background-color: rgba(15, 23, 42, 0.8);
+        border-bottom: var(--glass-border);
+        backdrop-filter: blur(var(--blur-md));
+        -webkit-backdrop-filter: blur(var(--blur-md));
+    }
+    
+    header::before {
+        background: linear-gradient(to right, rgba(99, 102, 241, 0.1), rgba(139, 92, 246, 0.1));
+        opacity: 0.3;
+    }
+    
+    header h1 {
+        color: var(--text-primary);
+    }
+    
+    /* 侧边栏深色模式 */
+    .history-sidebar {
+        background: rgba(15, 23, 42, 0.6);
+        border-right: var(--glass-border);
+    }
+    
+    .sidebar-header {
+        background: rgba(15, 23, 42, 0.8);
+        border-bottom: var(--glass-border);
+    }
+    
+    .sidebar-title {
+        color: var(--text-primary);
+    }
+    
+    .new-chat-btn {
+        background: rgba(30, 41, 59, 0.8);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+        color: var(--text-secondary);
+        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+    }
+    
+    .new-chat-btn::before {
+        background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.3), transparent);
+    }
+    
+    .new-chat-btn:hover {
+        background: rgba(51, 65, 85, 0.95);
+        color: var(--text-primary);
+        border-color: rgba(99, 102, 241, 0.4);
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(99, 102, 241, 0.2);
+    }
+    
+    .new-chat-btn:hover svg {
+        transform: scale(1.1) rotate(90deg);
+        filter: drop-shadow(0 0 4px rgba(99, 102, 241, 0.5));
+    }
+    
+    .conversation-item {
+        background: transparent;
+        border: 1px solid transparent;
+    }
+    
+    .conversation-item::before {
+        background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.15), transparent);
+    }
+    
+    .conversation-item:hover {
+        background: rgba(30, 41, 59, 0.6);
+        border-color: rgba(255, 255, 255, 0.15);
+        transform: translateX(4px);
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+    }
+    
+    .conversation-item.active {
+        background: rgba(99, 102, 241, 0.25);
+        border-color: rgba(99, 102, 241, 0.5);
+        box-shadow: 0 6px 16px rgba(99, 102, 241, 0.4);
+        transform: translateX(6px);
+    }
+    
+    .conversation-item.active::before {
+        background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.3), transparent);
+        opacity: 1;
+    }
+    
+    .conversation-title {
+        color: var(--text-secondary);
+    }
+    
+    .conversation-item.active .conversation-title {
+        color: var(--primary-indigo);
+    }
+    
+    .conversation-menu {
+        color: var(--text-muted);
+    }
+    
+    .conversation-menu::before {
+        background: rgba(99, 102, 241, 0.15);
+    }
+    
+    .conversation-item:hover .conversation-menu {
+        background: rgba(30, 41, 59, 0.6);
+        transform: scale(1.1);
+    }
+    
+    .conversation-menu:hover {
+        background: rgba(51, 65, 85, 0.8);
+        color: var(--text-primary);
+        transform: scale(1.2);
+    }
+    
+    .conversation-menu:hover::before {
+        background: rgba(99, 102, 241, 0.3);
+    }
+    
+    .group-header {
+        color: var(--text-muted);
+    }
+    
+    .group-header::before {
+        background: linear-gradient(to bottom, var(--primary-indigo), var(--primary-purple));
+        opacity: 0.4;
+    }
+    
+    .group-header:hover {
+        color: var(--text-secondary);
+        transform: translateX(4px);
+    }
+    
+    .group-header:hover::before {
+        opacity: 0.8;
+        height: 16px;
+        box-shadow: 0 0 8px rgba(99, 102, 241, 0.3);
+    }
+    
+    .sidebar-footer {
+        background: rgba(15, 23, 42, 0.4);
+        border-top: var(--glass-border);
+    }
+    
+    .user-profile {
+        background: rgba(30, 41, 59, 0.6);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+    }
+    
+    .user-profile:hover {
+        background: rgba(51, 65, 85, 0.8);
+        border-color: rgba(255, 255, 255, 0.2);
+    }
+    
+    .user-name-sidebar {
+        color: var(--text-primary);
+    }
+    
+    /* 聊天消息区域深色模式 */
+    .chat-messages {
+        background: rgba(15, 23, 42, 0.3);
+    }
+    
+    .chat-messages::before {
+        background: linear-gradient(to bottom, rgba(30, 41, 59, 0.3), rgba(15, 23, 42, 0.5));
+        opacity: 0.4;
+    }
+    
+    /* 消息样式深色模式 */
+    .message.user {
+        background-color: rgba(99, 102, 241, 0.2);
+    }
+    
+    .message.bot {
+        background-color: rgba(30, 41, 59, 0.3);
+    }
+    
+    .message-content {
+        background: rgba(30, 41, 59, 0.8);
+        color: var(--text-primary);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+    }
+    
+    .message.bot .message-content {
+        background: rgba(30, 41, 59, 0.9);
+        color: var(--text-primary);
+    }
+    
+    /* 思考过程深色模式 */
+    .thought-container {
+        background-color: rgba(121, 82, 179, 0.15);
+        border-left-color: #9370db;
+        color: var(--text-secondary);
+    }
+    
+    .thought-container::before {
+        color: #a98eda;
+    }
+    
+    /* 按钮深色模式 */
+    .btn, #reset-btn, #toggle-streaming-btn {
+        background-color: rgba(30, 41, 59, 0.8);
+        color: var(--text-secondary);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+    }
+    
+    .btn:hover, #reset-btn:hover, #toggle-streaming-btn:hover {
+        background-color: rgba(51, 65, 85, 0.9);
+        color: var(--text-primary);
+    }
+    
+    .glass-btn {
+        background-color: rgba(30, 41, 59, 0.6);
+        border: var(--glass-border);
+        color: var(--text-secondary);
+    }
+    
+    .glass-btn:hover {
+        background-color: rgba(51, 65, 85, 0.8);
+        color: var(--text-primary);
+    }
+    
+    /* 智能体菜单深色模式 */
+    .agent-switcher {
+        background: rgba(15, 23, 42, 0.8);
+        border: var(--glass-border);
+    }
+    
+    .agent-switcher-header h3 {
+        color: var(--text-primary);
+    }
+    
+    .agent-label {
+        color: var(--text-muted);
+    }
+    
+    .agent-item {
+        background: rgba(30, 41, 59, 0.6);
+        border: 1px solid transparent;
+    }
+    
+    .agent-item:hover {
+        background: rgba(51, 65, 85, 0.8);
+        border-color: rgba(99, 102, 241, 0.3);
+    }
+    
+    .agent-item.active {
+        background: rgba(99, 102, 241, 0.2) !important;
+        border-color: rgba(99, 102, 241, 0.4) !important;
+    }
+    
+    .agent-title {
+        color: var(--text-primary);
+    }
+    
+    .agent-item.active .agent-title {
+        color: var(--primary-indigo) !important;
+    }
+    
+    /* 输入区域深色模式 */
+    .chat-input-container {
+        background: rgba(15, 23, 42, 0.8);
+        border-top: var(--glass-border);
+    }
+    
+    .chat-input-container::before {
+        background: linear-gradient(to right, rgba(99, 102, 241, 0.1), rgba(139, 92, 246, 0.1));
+        opacity: 0.4;
+    }
+    
+    #user-input {
+        background-color: rgba(30, 41, 59, 0.8);
+        color: var(--text-primary);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+    }
+    
+    #user-input:focus {
+        border-color: rgba(99, 102, 241, 0.5);
+        background-color: rgba(30, 41, 59, 0.9);
+    }
+    
+    #user-input::placeholder {
+        color: var(--text-muted);
+    }
+    
+    /* 用户信息深色模式 */
+    .user-info {
+        background: rgba(30, 41, 59, 0.8);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+    }
+    
+    .user-info:hover {
+        background: rgba(51, 65, 85, 0.9);
+        border-color: rgba(255, 255, 255, 0.2);
+    }
+    
+    .user-name {
+        color: var(--text-primary);
+    }
+    
+    .user-type {
+        color: var(--text-muted);
+        background: rgba(51, 65, 85, 0.6);
+    }
+    
+    .user-title {
+        color: var(--text-muted);
+    }
+    
+    .user-dropdown {
+        background: rgba(15, 23, 42, 0.95);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+    }
+    
+    .user-dropdown::before {
+        background: rgba(15, 23, 42, 0.95);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+    }
+    
+    .info-label {
+        color: var(--text-muted);
+    }
+    
+    .info-value {
+        color: var(--text-secondary);
+    }
+    
+    /* 通知消息深色模式 */
+    .typing-indicator, .loading-animation {
+        background-color: rgba(30, 41, 59, 0.8);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+    }
+    
+    .loading-text {
+        color: var(--text-secondary);
+    }
+    
+    .status-badge {
+        background-color: rgba(30, 41, 59, 0.9);
+    }
+    
+    .ripple {
+        background-color: rgba(255, 255, 255, 0.1);
+    }
+    
+    /* 智能体切换通知深色模式 */
+    .agent-switch-notification .notification-content {
+        background: rgba(15, 23, 42, 0.95);
+        border: 1px solid rgba(99, 102, 241, 0.3);
+    }
+    
+    .notification-text {
+        color: var(--text-primary);
+    }
+    
+    /* 上下文菜单深色模式 */
+    .conversation-context-menu {
+        background: rgba(15, 23, 42, 0.95);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+    }
+    
+    .menu-item {
+        color: var(--text-secondary);
+    }
+    
+    .menu-item:hover {
+        background: rgba(99, 102, 241, 0.2);
+        color: var(--primary-indigo);
+    }
+    
+    /* 智能体菜单深色模式 */
+    #agent-menu {
+        background: rgba(15, 23, 42, 0.95) !important;
+        border: 1px solid rgba(255, 255, 255, 0.1) !important;
+    }
+    
+    .agent-option {
+        background: rgba(30, 41, 59, 0.6) !important;
+        border: 1px solid rgba(255, 255, 255, 0.1) !important;
+        color: var(--text-secondary) !important;
+    }
+    
+    .agent-option:hover {
+        background: rgba(99, 102, 241, 0.2) !important;
+        border-color: rgba(99, 102, 241, 0.3) !important;
+        color: var(--primary-indigo) !important;
+    }
+    
+    .agent-option[data-active="true"] {
+        background: rgba(99, 102, 241, 0.3) !important;
+        border-color: rgba(99, 102, 241, 0.5) !important;
+        color: var(--primary-indigo) !important;
+    }
+    
+    /* Markdown内容深色模式 */
+    .bot .message-content blockquote {
+        border-left-color: rgba(99, 102, 241, 0.5);
+        background-color: rgba(30, 58, 138, 0.3);
+        color: var(--text-secondary);
+    }
+    
+    .bot .message-content code {
+        background-color: rgba(30, 58, 138, 0.3);
+        color: var(--text-secondary);
+    }
+    
+    .bot .message-content pre {
+        background-color: rgba(15, 23, 42, 0.8);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+    }
+    
+    .bot .message-content h1,
+    .bot .message-content h2,
+    .bot .message-content h3,
+    .bot .message-content h4,
+    .bot .message-content h5,
+    .bot .message-content h6 {
+        color: var(--text-primary);
+    }
+    
+    .bot .message-content table {
+        background-color: rgba(30, 41, 59, 0.5);
+    }
+    
+    .bot .message-content table th,
+    .bot .message-content table td {
+        border-color: rgba(255, 255, 255, 0.1);
+    }
+    
+    .bot .message-content table th {
+        background-color: rgba(51, 65, 85, 0.7);
+        color: var(--text-primary);
+    }
+    
+    .bot .message-content table td {
+        color: var(--text-secondary);
+    }
+    
+    /* 滚动条深色模式 */
+    .chat-messages::-webkit-scrollbar-thumb {
+        background-color: rgba(100, 116, 139, 0.6);
+    }
+    
+    .chat-messages::-webkit-scrollbar-thumb:hover {
+        background-color: rgba(100, 116, 139, 0.8);
+    }
+    
+    .history-content::-webkit-scrollbar-thumb {
+        background-color: rgba(100, 116, 139, 0.4);
+    }
+    
+    .history-content::-webkit-scrollbar-thumb:hover {
+        background-color: rgba(100, 116, 139, 0.6);
+    }
+    
+    /* 移动端侧边栏切换按钮深色模式 */
+    .sidebar-toggle {
+        background: var(--primary-indigo);
+        box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
+    }
+    
+    .sidebar-toggle::before {
+        background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+    }
+    
+    .sidebar-toggle:hover {
+        box-shadow: 0 12px 35px rgba(99, 102, 241, 0.4);
+    }
+}
+
+/* 响应式布局 */
+@media (max-width: 768px) {
+    .chat-container {
+        height: calc(100vh - 20px);
+        max-height: none;
+        border-radius: var(--radius);
+        margin: 10px;
+        flex-direction: column;
+    }
+
+    /* 移动端侧边栏样式 */
+    .history-sidebar {
+        position: fixed;
+        left: -280px;
+        top: 0;
+        width: 280px;
+        height: 100vh;
+        z-index: 1000;
+        transition: transform 0.3s ease;
+        border-right: none;
+        border-radius: 0;
+    }
+
+    .history-sidebar.show {
+        transform: translateX(280px);
+    }
+
+    /* 移动端主聊天区域 */
+    .main-chat-area {
+        width: 100%;
+        height: 100%;
+    }
+
+    /* 移动端侧边栏切换按钮 */
+    .sidebar-toggle {
+        position: fixed;
+        left: 20px;
+        top: 20px;
+        z-index: 1001;
+        width: 44px;
+        height: 44px;
+        background: var(--primary-indigo);
+        border: none;
+        border-radius: 50%;
+        color: white;
+        font-size: 1.2rem;
+        box-shadow: var(--shadow-lg);
+        cursor: pointer;
+        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        position: relative;
+        overflow: hidden;
+    }
+
+    .sidebar-toggle::before {
+        content: '';
+        position: absolute;
+        top: 0;
+        left: -100%;
+        width: 100%;
+        height: 100%;
+        background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+        transition: left 0.5s ease;
+    }
+
+    .sidebar-toggle:hover {
+        transform: scale(1.1);
+        box-shadow: var(--shadow-glow-indigo);
+    }
+
+    .sidebar-toggle:hover::before {
+        left: 100%;
+    }
+
+    .sidebar-toggle:active {
+        transform: scale(0.95);
+    }
+
+    /* 移动端侧边栏遮罩层 */
+    .sidebar-overlay {
+        position: fixed;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background: rgba(0, 0, 0, 0.5);
+        backdrop-filter: blur(4px);
+        -webkit-backdrop-filter: blur(4px);
+        z-index: 999;
+        opacity: 0;
+        visibility: hidden;
+        transition: all 0.3s ease;
+    }
+
+    .sidebar-overlay.show {
+        opacity: 1;
+        visibility: visible;
+    }
+    
+    header, 
+    .glass-header,
+    header::before {
+        border-radius: var(--radius) var(--radius) 0 0;
+        padding: 12px 16px;
+        flex-direction: column;
+        gap: 12px;
+        align-items: stretch;
+    }
+    
+    /* 头部第一行:图标、应用文案和版本号 */
+    .logo-title-container {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: 8px;
+        order: 1;
+    }
+
+    .logo-title-link{
+        display: none;
+    }
+    
+    .header-logo {
+        width: 32px;
+        height: 32px;
+    }
+    
+    header h1 {
+        font-size: 1.1rem;
+        margin: 0;
+        text-align: center;
+    }
+    
+    .version-tag {
+        font-size: 0.65rem;
+        padding: 1px 6px;
+    }
+    
+    /* 头部第二行:智能体切换和重置按钮 */
+    .buttons-container {
+        display: flex;
+        gap: 8px;
+        align-items: center;
+        justify-content: center;
+        order: 2;
+        flex-wrap: wrap;
+    }
+    
+    /* 智能体下拉菜单移动端优化 */
+    #agent-dropdown {
+        flex: 1;
+        min-width: 120px;
+        justify-content: center;
+        padding: 8px 12px;
+        font-size: 0.8rem;
+    }
+    
+    #agent-dropdown span {
+        font-size: 0.8rem;
+    }
+    
+    /* 重置按钮移动端优化 */
+    #reset-btn {
+        flex: 1;
+        min-width: 120px;
+        justify-content: center;
+        padding: 8px 12px;
+        font-size: 0.8rem;
+    }
+    
+    #reset-btn svg {
+        width: 14px;
+        height: 14px;
+    }
+    
+    /* 用户信息移动端优化 - 保持左右布局,上下排版 */
+    .user-info {
+        order: 3;
+        flex-direction: row;
+        align-items: center;
+        gap: 12px;
+        padding: 12px 16px;
+        margin-top: 8px;
+        width: 100%;
+        max-width: 280px;
+        margin-left: auto;
+        margin-right: auto;
+    }
+    
+    .user-details {
+        flex-direction: row;
+        align-items: center;
+        gap: 12px;
+        text-align: left;
+        flex: 1;
+    }
+    
+    .user-avatar {
+        width: 44px;
+        height: 44px;
+        flex-shrink: 0;
+    }
+    
+    .user-text {
+        display: flex;
+        flex-direction: column;
+        align-items: flex-start;
+        gap: 3px;
+        flex: 1;
+        min-width: 0;
+    }
+    
+    .user-info-row {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        width: 100%;
+    }
+    
+    .user-name {
+        font-size: 0.9rem;
+        font-weight: 600;
+        white-space: nowrap;
+    }
+    
+    .user-type {
+        font-size: 0.75rem;
+        padding: 1px 5px;
+        white-space: nowrap;
+    }
+    
+    .user-title {
+        font-size: 0.8rem;
+        max-width: none;
+        width: 100%;
+    }
+    
+    /* 移动端用户下拉菜单优化 */
+    .user-dropdown {
+        position: fixed;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%) scale(0.95);
+        right: auto;
+        width: 90%;
+        max-width: 300px;
+        opacity: 0;
+        visibility: hidden;
+        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    }
+    
+    .user-info:hover .user-dropdown {
+        opacity: 1;
+        visibility: visible;
+        transform: translate(-50%, -50%) scale(1);
+    }
+    
+    .user-dropdown::before {
+        display: none;
+    }
+    
+    .user-type {
+        font-size: 0.75rem;
+        padding: 2px 8px;
+    }
+    
+    .user-title {
+        font-size: 0.75rem;
+        opacity: 0.8;
+    }
+    
+    /* 智能体菜单移动端优化 */
+    #agent-menu {
+        position: fixed !important;
+        top: 50% !important;
+        left: 50% !important;
+        transform: translate(-50%, -50%) !important;
+        width: 280px !important;
+        max-width: 90vw !important;
+        z-index: 1002 !important;
+        background: rgba(255, 255, 255, 0.95) !important;
+        backdrop-filter: blur(20px) !important;
+        border: 1px solid rgba(255, 255, 255, 0.3) !important;
+        box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2) !important;
+    }
+    
+    #agent-menu ul {
+        padding: 16px !important;
+    }
+    
+    .agent-option {
+        padding: 16px !important;
+        margin-bottom: 8px !important;
+        border-radius: 12px !important;
+        background: rgba(255, 255, 255, 0.8) !important;
+        border: 1px solid rgba(255, 255, 255, 0.3) !important;
+        transition: all 0.2s ease !important;
+    }
+    
+    .agent-option:hover {
+        background: rgba(99, 102, 241, 0.1) !important;
+        border-color: rgba(99, 102, 241, 0.3) !important;
+        transform: translateY(-2px) !important;
+    }
+    
+    .agent-option[data-active="true"] {
+        background: rgba(99, 102, 241, 0.15) !important;
+        border-color: rgba(99, 102, 241, 0.4) !important;
+        box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2) !important;
+    }
+    
+    /* 聊天内容区域移动端优化 */
+    .chat-main-content {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        min-height: 0;
+        background: rgba(255, 255, 255, 0.02);
+    }
+    
+    .chat-messages {
+        padding: 16px;
+        gap: 20px;
+        flex: 1;
+        min-height: 0;
+        overflow-y: auto;
+        -webkit-overflow-scrolling: touch;
+    }
+    
+    .chat-messages::-webkit-scrollbar {
+        width: 6px;
+    }
+    
+    .chat-messages::-webkit-scrollbar-track {
+        background: transparent;
+    }
+    
+    .chat-messages::-webkit-scrollbar-thumb {
+        background: rgba(255, 255, 255, 0.2);
+        border-radius: 3px;
+    }
+    
+    .chat-messages::-webkit-scrollbar-thumb:hover {
+        background: rgba(255, 255, 255, 0.3);
+    }
+    
+    .message {
+        border-radius: 16px;
+        margin-bottom: 16px;
+        box-shadow: var(--shadow-sm);
+        transition: all 0.2s ease;
+    }
+    
+    .message:hover {
+        transform: translateY(-1px);
+        box-shadow: var(--shadow);
+    }
+    
+    .message-content {
+        padding: 16px;
+        font-size: 0.9rem;
+        line-height: 1.6;
+        border-radius: 16px;
+    }
+    
+    .message.bot {
+        width: 100%;
+        max-width: 100%;
+        margin-right: 0;
+    }
+    
+    .message.bot .message-content {
+        padding: 16px;
+        background: rgba(255, 255, 255, 0.15);
+        border: 1px solid rgba(255, 255, 255, 0.1);
+    }
+    
+    .message.user {
+        align-self: flex-end;
+        margin-left: 20px;
+    }
+    
+    .message.user .message-content {
+        background: rgba(99, 102, 241, 0.15);
+        border: 1px solid rgba(99, 102, 241, 0.2);
+        color: var(--gray-800);
+    }
+    
+    .thought-container {
+        margin: 12px 0 20px 0;
+        padding: 12px 16px;
+        border-radius: 12px;
+        background: rgba(99, 102, 241, 0.08);
+        border-left: 3px solid var(--primary-indigo);
+    }
+    
+    /* 输入区域移动端优化 */
+    .chat-input-container {
+        padding: 16px;
+        border-top: 1px solid rgba(255, 255, 255, 0.1);
+        background: rgba(255, 255, 255, 0.05);
+        position: relative;
+    }
+    
+    .chat-input-container::before {
+        content: '';
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background: linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.02));
+        opacity: 0.3;
+        z-index: -1;
+        pointer-events: none;
+        border-radius: 0 0 var(--radius) var(--radius);
+    }
+    
+    #user-input {
+        padding: 12px 16px;
+        font-size: 0.9rem;
+        min-height: 44px;
+        max-height: 120px;
+        resize: none;
+        border-radius: 12px;
+        border: 1px solid rgba(255, 255, 255, 0.2);
+        background: rgba(255, 255, 255, 0.1);
+        color: var(--gray-800);
+        transition: all 0.2s ease;
+    }
+    
+    #user-input:focus {
+        outline: none;
+        border-color: var(--primary-indigo);
+        background: rgba(255, 255, 255, 0.15);
+        box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
+    }
+    
+    #send-btn {
+        width: 44px;
+        height: 44px;
+        min-width: 44px;
+        border-radius: 12px;
+        background: var(--primary-indigo);
+        border: none;
+        color: white;
+        transition: all 0.2s ease;
+        box-shadow: var(--shadow-sm);
+    }
+    
+    #send-btn:hover {
+        background: var(--primary-purple);
+        transform: translateY(-1px);
+        box-shadow: var(--shadow);
+    }
+    
+    #send-btn:active {
+        transform: translateY(0);
+        box-shadow: var(--shadow-sm);
+    }
+    
+    #send-btn svg {
+        width: 18px;
+        height: 18px;
+    }
+    
+    /* 移动端智能体切换菜单栏适配 */
+    .agent-switcher-container {
+        position: fixed;
+        left: 0;
+        top: 0;
+        width: 100%;
+        height: 100%;
+        z-index: 1000;
+        background: rgba(0, 0, 0, 0.5);
+        opacity: 0;
+        visibility: hidden;
+        transition: all 0.3s ease;
+    }
+
+    .agent-switcher-container.show {
+        opacity: 1;
+        visibility: visible;
+    }
+
+    .agent-switcher {
+        position: absolute;
+        left: 0;
+        top: 0;
+        width: 280px;
+        height: 100%;
+        border-radius: 0;
+        transform: translateX(-100%);
+        transition: transform 0.3s ease;
+        overflow-y: auto;
+    }
+
+    .agent-switcher-container.show .agent-switcher {
+        transform: translateX(0);
+    }
+
+    /* 移动端平台项样式优化 */
+    .agent-item {
+        padding: 20px 24px;
+        margin: 0 16px 16px 16px;
+        border-radius: var(--radius-lg);
+    }
+
+    .agent-icon {
+        width: 60px;
+        height: 60px;
+    }
+
+    .platform-icon {
+        width: 40px;
+        height: 40px;
+        filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.1));
+    }
+
+    .agent-title {
+        font-size: 1.2rem;
+    }
+
+    /* 添加移动端菜单切换按钮 */
+    .mobile-menu-toggle {
+        display: block;
+        position: fixed;
+        left: 20px;
+        top: 100px;
+        z-index: 999;
+        width: 50px;
+        height: 50px;
+        background: var(--primary-indigo);
+        border: none;
+        border-radius: 50%;
+        color: white;
+        font-size: 1.2rem;
+        box-shadow: var(--shadow-lg);
+        cursor: pointer;
+        transition: var(--transition);
+    }
+
+    .mobile-menu-toggle:hover {
+        transform: scale(1.1);
+        box-shadow: var(--shadow-glow-indigo);
+    }
+    
+    /* 移动端智能体切换通知优化 */
+    .agent-switch-notification {
+        top: 20px;
+        right: 20px;
+        left: 20px;
+        transform: translateY(-100px);
+    }
+    
+    .agent-switch-notification.show {
+        transform: translateY(0);
+    }
+    
+    .notification-content {
+        padding: 12px 16px;
+        min-width: auto;
+        width: 100%;
+    }
+    
+    .notification-text {
+        font-size: 0.85rem;
+    }
+}
+
+/* 改进markdown内容的渲染样式 */
+.markdown-content {
+    width: 100%;
+}
+
+.markdown-content p {
+    margin-bottom: 1em;
+}
+
+.markdown-content h1,
+.markdown-content h2,
+.markdown-content h3,
+.markdown-content h4 {
+    margin-top: 1.2em;
+    margin-bottom: 0.8em;
+    font-weight: 600;
+    line-height: 1.3;
+}
+
+.markdown-content h1 {
+    font-size: 1.7em;
+}
+
+.markdown-content h2 {
+    font-size: 1.5em;
+}
+
+.markdown-content h3 {
+    font-size: 1.3em;
+}
+
+.markdown-content ul,
+.markdown-content ol {
+    margin-bottom: 1em;
+    padding-left: 1.5em;
+}
+
+.markdown-content li {
+    margin-bottom: 0.5em;
+}
+
+.markdown-content pre {
+    margin-bottom: 1.2em;
+    border-radius: 6px;
+} 
+
+/* 智能体切换通知样式 */
+.agent-switch-notification {
+    position: fixed;
+    top: 100px;
+    right: 30px;
+    z-index: 1003;
+    transform: translateX(400px);
+    transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+.agent-switch-notification.show {
+    transform: translateX(0);
+}
+
+.notification-content {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 16px 20px;
+    background: rgba(255, 255, 255, 0.95);
+    backdrop-filter: blur(var(--blur-md));
+    -webkit-backdrop-filter: blur(var(--blur-md));
+    border: 1px solid rgba(99, 102, 241, 0.2);
+    border-radius: var(--radius-lg);
+    box-shadow: var(--shadow-lg);
+    min-width: 200px;
+}
+
+.notification-icon {
+    font-size: 1.2rem;
+    animation: bounce 0.6s ease-in-out;
+}
+
+.notification-text {
+    font-weight: 500;
+    color: var(--gray-800);
+    font-size: 0.95rem;
+}
+
+/* 平台特定通知样式 */
+.agent-switch-notification[data-platform="kuaishou"] .notification-content {
+    border-color: rgba(255, 69, 0, 0.3);
+    background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(255, 69, 0, 0.05));
+}
+
+.agent-switch-notification[data-platform="toutiao"] .notification-content {
+    border-color: rgba(0, 123, 255, 0.3);
+    background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(0, 123, 255, 0.05));
+}
+
+.agent-switch-notification[data-platform="videoaccount"] .notification-content {
+    border-color: rgba(40, 167, 69, 0.3);
+    background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(40, 167, 69, 0.05));
+}
+
+@keyframes bounce {
+    0%, 20%, 50%, 80%, 100% {
+        transform: translateY(0);
+    }
+    40% {
+        transform: translateY(-8px);
+    }
+    60% {
+        transform: translateY(-4px);
+    }
+} 
+
+/* 移动端智能体菜单遮罩层样式 */
+.mobile-agent-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.5);
+    backdrop-filter: blur(4px);
+    -webkit-backdrop-filter: blur(4px);
+    z-index: 1001;
+    opacity: 0;
+    transition: opacity 0.3s ease;
+}
+
+.mobile-agent-overlay.show {
+    opacity: 1;
+}
+
+/* 移动端智能体菜单动画 */
+@media (max-width: 768px) {
+    #agent-menu {
+        opacity: 0;
+        transform: translate(-50%, -50%) scale(0.9) !important;
+        transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) !important;
+    }
+    
+    #agent-menu.show {
+        opacity: 1;
+        transform: translate(-50%, -50%) scale(1) !important;
+    }
+} 
+
+/* 移动端触摸优化 */
+.glass-btn,
+#agent-dropdown,
+#reset-btn,
+#send-btn {
+    -webkit-tap-highlight-color: transparent;
+    touch-action: manipulation;
+    user-select: none;
+}
+
+.glass-btn:active,
+#agent-dropdown:active,
+#reset-btn:active,
+#send-btn:active {
+    transform: scale(0.95);
+    transition: transform 0.1s ease;
+}
+
+/* 移动端智能体选项触摸优化 */
+.agent-option {
+    -webkit-tap-highlight-color: transparent;
+    touch-action: manipulation;
+    user-select: none;
+}
+
+.agent-option:active {
+    transform: scale(0.98);
+    transition: transform 0.1s ease;
+}
+
+/* 移动端用户信息触摸优化 */
+.user-info {
+    -webkit-tap-highlight-color: transparent;
+    touch-action: manipulation;
+}
+
+.user-info:active {
+    transform: scale(0.98);
+    transition: transform 0.1s ease;
+}
+
+/* 移动端输入框触摸优化 */
+#user-input {
+    -webkit-tap-highlight-color: transparent;
+    touch-action: manipulation;
+}
+
+/* 历史记录加载动画和空状态样式 */
+.history-loading, .history-empty {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 20px;
+    color: var(--gray-500);
+    font-size: 14px;
+    text-align: center;
+    gap: 10px;
+    height: 100%;
+}
+
+.history-loading .loading-spinner {
+    border: 4px solid rgba(99, 102, 241, 0.2);
+    border-top: 4px solid var(--primary-indigo);
+    border-radius: 50%;
+    width: 30px;
+    height: 30px;
+    animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+    0% { transform: rotate(0deg); }
+    100% { transform: rotate(360deg); }
+}
+
+.history-empty svg {
+    color: var(--gray-400);
+    width: 48px;
+    height: 48px;
+}
+
+.history-empty p {
+    margin: 0;
+    font-weight: 500;
+}
+
+.history-empty .new-chat-btn {
+    margin-top: 15px;
+} 

+ 285 - 0
static/css/table-fix.css

@@ -0,0 +1,285 @@
+/* 审核表格修复样式 - 增强版,确保覆盖原有样式 */
+/* 通用表格样式 */
+.message-content table,
+.markdown-content table,
+.bot .message-content table {
+    width: 100% !important;
+    table-layout: fixed !important;
+    border-collapse: collapse !important;
+    margin: 1em 0 !important;
+    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05) !important;
+    border-radius: 4px !important;
+    overflow: hidden !important; /* 确保圆角边框生效 */
+    display: table !important; /* 强制表格显示为表格,而不是块 */
+}
+
+/* 表格单元格通用样式 */
+.message-content table th,
+.message-content table td,
+.markdown-content table th,
+.markdown-content table td,
+.bot .message-content table th,
+.bot .message-content table td {
+    padding: 10px !important;
+    border: 1px solid #e2e8f0 !important;
+    vertical-align: top !important;
+    word-wrap: break-word !important;
+    word-break: break-word !important;
+    white-space: normal !important;
+    height: auto !important;
+    overflow: visible !important;
+    line-height: 1.5 !important;
+    text-align: center !important; /* 确保所有单元格文字居中 */
+}
+
+/* 表头样式 - 确保居中显示 */
+.message-content table th,
+.markdown-content table th,
+.bot .message-content table th {
+    background-color: #eef2ff !important; /* 轻微的紫色背景 */
+    color: #4F46E5 !important; /* 与网站主题色匹配 */
+    font-weight: 600 !important;
+    text-align: center !important; /* 强制表头文字居中显示 */
+    padding: 12px 10px !important; /* 增加表头高度 */
+}
+
+/* 序号列(第一列)宽度和居中 */
+.message-content table th:first-child,
+.message-content table td:first-child,
+.markdown-content table th:first-child,
+.markdown-content table td:first-child,
+.bot .message-content table th:first-child,
+.bot .message-content table td:first-child {
+    width: 5% !important;
+    min-width: 40px !important;
+    text-align: center !important;
+    vertical-align: middle !important;
+    font-weight: 500 !important;
+}
+
+/* 违规原文列(第二列)宽度 */
+.message-content table th:nth-child(2),
+.message-content table td:nth-child(2),
+.markdown-content table th:nth-child(2),
+.markdown-content table td:nth-child(2),
+.bot .message-content table th:nth-child(2),
+.bot .message-content table td:nth-child(2) {
+    width: 30% !important;
+}
+
+/* 违规原因列(第三列)宽度 */
+.message-content table th:nth-child(3),
+.message-content table td:nth-child(3),
+.markdown-content table th:nth-child(3),
+.markdown-content table td:nth-child(3),
+.bot .message-content table th:nth-child(3),
+.bot .message-content table td:nth-child(3) {
+    width: 30% !important;
+}
+
+/* 修改建议列(第四列)宽度 */
+.message-content table th:nth-child(4),
+.message-content table td:nth-child(4),
+.markdown-content table th:nth-child(4),
+.markdown-content table td:nth-child(4),
+.bot .message-content table th:nth-child(4),
+.bot .message-content table td:nth-child(4) {
+    width: 35% !important;
+}
+
+/* 第一行文字居中 - 针对性强化 */
+.message-content table tbody tr:first-child td,
+.markdown-content table tbody tr:first-child td,
+.bot .message-content table tbody tr:first-child td {
+    text-align: center !important;
+    vertical-align: middle !important;
+    background-color: #f9fafb !important; /* 轻微的灰色背景 */
+}
+
+/* 让表格行有更好的视觉区分 */
+.message-content table tbody tr:nth-child(even),
+.markdown-content table tbody tr:nth-child(even),
+.bot .message-content table tbody tr:nth-child(even) {
+    background-color: #f9fafb !important;
+}
+
+.message-content table tbody tr:hover,
+.markdown-content table tbody tr:hover,
+.bot .message-content table tbody tr:hover {
+    background-color: #f3f4f7 !important;
+}
+
+/* 专门针对审核结果表格的增强样式 */
+table.audit-result,
+table[data-type="audit-result"],
+.message-content table.audit-result,
+.message-content table[data-type="audit-result"],
+.markdown-content table.audit-result,
+.markdown-content table[data-type="audit-result"],
+.bot .message-content table.audit-result,
+.bot .message-content table[data-type="audit-result"] {
+    width: 100% !important;
+    border: 1px solid #e2e8f0 !important;
+    display: table !important;
+}
+
+/* 审核结果表格的单元格增强 */
+table.audit-result th,
+table.audit-result td,
+table[data-type="audit-result"] th,
+table[data-type="audit-result"] td,
+.message-content table.audit-result th,
+.message-content table.audit-result td,
+.message-content table[data-type="audit-result"] th,
+.message-content table[data-type="audit-result"] td,
+.markdown-content table.audit-result th,
+.markdown-content table.audit-result td,
+.markdown-content table[data-type="audit-result"] th,
+.markdown-content table[data-type="audit-result"] td,
+.bot .message-content table.audit-result th,
+.bot .message-content table.audit-result td,
+.bot .message-content table[data-type="audit-result"] th,
+.bot .message-content table[data-type="audit-result"] td {
+    padding: 12px !important;
+    vertical-align: top !important;
+    text-align: center !important; /* 确保所有单元格文字居中 */
+}
+
+/* 审核结果表格的表头增强 - 强制居中 */
+table.audit-result th,
+table[data-type="audit-result"] th,
+.message-content table.audit-result th,
+.message-content table[data-type="audit-result"] th,
+.markdown-content table.audit-result th,
+.markdown-content table[data-type="audit-result"] th,
+.bot .message-content table.audit-result th,
+.bot .message-content table[data-type="audit-result"] th {
+    text-align: center !important;
+    background-color: #eef2ff !important; /* 轻微的紫色背景 */
+    color: #4F46E5 !important; /* 与网站主题色匹配 */
+}
+
+/* 审核结果表格第一行增强 - 强制居中 */
+table.audit-result tbody tr:first-child td,
+table[data-type="audit-result"] tbody tr:first-child td,
+.message-content table.audit-result tbody tr:first-child td,
+.message-content table[data-type="audit-result"] tbody tr:first-child td,
+.markdown-content table.audit-result tbody tr:first-child td,
+.markdown-content table[data-type="audit-result"] tbody tr:first-child td,
+.bot .message-content table.audit-result tbody tr:first-child td,
+.bot .message-content table[data-type="audit-result"] tbody tr:first-child td {
+    text-align: center !important;
+    vertical-align: middle !important;
+    background-color: #f9fafb !important; /* 轻微的灰色背景 */
+}
+
+/* 响应式样式 */
+@media (max-width: 768px) {
+    .message-content table,
+    .markdown-content table,
+    .bot .message-content table {
+        display: block !important;
+        overflow-x: auto !important;
+        -webkit-overflow-scrolling: touch !important;
+        width: 100% !important; /* 确保在移动设备上也是100%宽度 */
+    }
+    
+    .message-content table th,
+    .message-content table td,
+    .markdown-content table th,
+    .markdown-content table td,
+    .bot .message-content table th,
+    .bot .message-content table td {
+        min-width: 120px !important;
+        text-align: center !important; /* 确保在移动设备上也居中 */
+    }
+    
+    /* 即使在移动设备上也保持表格的宽度一致,只在需要时允许水平滚动 */
+    table.audit-result,
+    table[data-type="audit-result"],
+    .message-content table.audit-result,
+    .message-content table[data-type="audit-result"],
+    .markdown-content table.audit-result,
+    .markdown-content table[data-type="audit-result"],
+    .bot .message-content table.audit-result,
+    .bot .message-content table[data-type="audit-result"] {
+        display: block !important;
+        overflow-x: auto !important;
+        white-space: nowrap !important;
+        width: 100% !important;
+    }
+    
+    table.audit-result td,
+    table[data-type="audit-result"] td,
+    .message-content table.audit-result td,
+    .message-content table[data-type="audit-result"] td,
+    .markdown-content table.audit-result td,
+    .markdown-content table[data-type="audit-result"] td,
+    .bot .message-content table.audit-result td,
+    .bot .message-content table[data-type="audit-result"] td {
+        white-space: normal !important;
+        min-width: 150px !important;
+        text-align: center !important; /* 确保在移动设备上也居中 */
+    }
+}
+
+/* 表格单元格文字黑色和居中显示(不包括表头) */
+.message-content table td,
+.markdown-content table td,
+.bot .message-content table td,
+table td {
+    color: #000000 !important; /* 确保所有普通单元格文字为黑色 */
+    text-align: center !important; /* 确保所有单元格文字居中 */
+}
+
+/* 保持表头样式不变,但依然居中 */
+.message-content table th,
+.markdown-content table th,
+.bot .message-content table th,
+table th {
+    text-align: center !important; /* 确保表头文字居中 */
+}
+
+/* 覆盖safe-row的颜色,也使用黑色 */
+.message-content table tr.safe-row td,
+.markdown-content table tr.safe-row td,
+.bot .message-content table tr.safe-row td,
+.message-content table.risk-table tr.safe-row td,
+.markdown-content table.risk-table tr.safe-row td,
+.bot .message-content table.risk-table tr.safe-row td,
+.safe-row td {
+    color: #000000 !important; /* 使用纯黑色 */
+}
+
+/* 特殊行的样式优先级调整 */
+/* 由于需要所有表格文字均为黑色,因此注释掉特殊颜色的样式 */
+/*
+.message-content table tr.violation-row td,
+.markdown-content table tr.violation-row td,
+.bot .message-content table tr.violation-row td,
+.message-content table.risk-table tr.violation-row td,
+.markdown-content table.risk-table tr.violation-row td,
+.bot .message-content table.risk-table tr.violation-row td {
+    color: var(--audit-fail-color) !important;
+}
+
+.message-content table tr.warning-row td,
+.markdown-content table tr.warning-row td,
+.bot .message-content table tr.warning-row td {
+    color: var(--warning-color) !important;
+}
+*/
+
+/* 确保所有表格单元格都居中显示和文字黑色 - 最高优先级规则 */
+.message-content table td,
+.markdown-content table td,
+.bot .message-content table td,
+table td,
+tr td,
+tbody td,
+.message-content tbody td,
+.markdown-content tbody td {
+    text-align: center !important;
+    color: #000000 !important;
+    vertical-align: middle !important; /* 垂直居中 */
+} 

BIN
static/fonts/Inter-Bold.woff2


BIN
static/fonts/Inter-Light.woff2


BIN
static/fonts/Inter-Medium.woff2


BIN
static/fonts/Inter-Regular.woff2


BIN
static/fonts/Inter-SemiBold.woff2


+ 1 - 0
static/img/diagnosis.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1775188143050" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6309" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M378.98 671.8l72.43-140.61 152.96 265.3 44.64-2.64 59.45-138.72h132.17v-50H691.98L669 620.28l-46.27 107.95-150.9-261.72-43.88 1.04-86.44 167.8H183.37v50h173.38l22.23-13.55z" fill="#38B487" p-id="6310"></path><path d="M860 180H753V89a25 25 0 0 0-50 0v91H310V89a25 25 0 0 0-50 0v91h-96A100 100 0 0 0 64 280v580a100 100 0 0 0 100 100h696a100 100 0 0 0 100-100V280a100 100 0 0 0-100-100z m-107 50v49a25 25 0 0 1-50 0v-49z m-443 0v49a25 25 0 0 1-50 0v-49z m600 630a50.06 50.06 0 0 1-50 50H164a50.06 50.06 0 0 1-50-50V370h796z" fill="#38B487" p-id="6311"></path></svg>

File diff suppressed because it is too large
+ 0 - 0
static/img/dingding.svg


File diff suppressed because it is too large
+ 0 - 0
static/img/kuaishou.svg


BIN
static/img/planning.png


File diff suppressed because it is too large
+ 0 - 0
static/img/toutiao.svg


File diff suppressed because it is too large
+ 0 - 0
static/img/videoaccount.svg


File diff suppressed because it is too large
+ 0 - 0
static/img/wx.svg


+ 2250 - 0
static/js/chat.js

@@ -0,0 +1,2250 @@
+document.addEventListener('DOMContentLoaded', () => {
+    // 注意:认证状态检查由 index.html 中的 checkAuthStatus 函数处理
+    // 这里不再重复检查,避免冲突
+
+    // 防重复重定向标志
+    let isRedirecting = false;
+
+    // 主题检测和适配功能
+    function initThemeDetection () {
+        // 检测当前主题偏好
+        const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+
+        // 为body添加主题类名,便于CSS选择器
+        if (prefersDark) {
+            document.body.classList.add('dark-theme');
+            document.body.classList.remove('light-theme');
+        } else {
+            document.body.classList.add('light-theme');
+            document.body.classList.remove('dark-theme');
+        }
+
+        // 监听主题变化
+        const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+        mediaQuery.addEventListener('change', (e) => {
+            if (e.matches) {
+                // 切换到深色主题
+                document.body.classList.add('dark-theme');
+                document.body.classList.remove('light-theme');
+            } else {
+                // 切换到浅色主题
+                document.body.classList.add('light-theme');
+                document.body.classList.remove('dark-theme');
+            }
+
+            // 触发自定义事件,通知其他组件主题已变化
+            window.dispatchEvent(new CustomEvent('themeChanged', {
+                detail: { isDark: e.matches }
+            }));
+        });
+
+        // 添加平滑过渡效果
+        const style = document.createElement('style');
+        style.textContent = `
+            * {
+                transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease !important;
+            }
+            
+            /* 确保过渡效果不会影响动画 */
+            .typing-indicator span,
+            .loading-spinner,
+            .ripple,
+            .message {
+                transition: none !important;
+            }
+        `;
+        document.head.appendChild(style);
+    }
+
+    // 初始化主题检测
+    initThemeDetection();
+
+    // 获取DOM元素
+    const chatMessages = document.getElementById('chat-messages');
+    const userInput = document.getElementById('user-input');
+    const sendButton = document.getElementById('send-btn');
+    const resetButton = document.getElementById('reset-btn');
+    const logoutButton = document.getElementById('logout-btn');
+
+    // 初始化复制按钮
+    if (typeof window.setupCopyButtons === 'function') {
+        window.setupCopyButtons();
+    }
+
+    // 绑定退出登录事件
+    if (logoutButton) {
+        logoutButton.addEventListener('click', handleLogout);
+    }
+
+    // 添加字符计数器样式
+    const style = document.createElement('style');
+    style.textContent = `
+        .char-counter {
+            position: absolute;
+            bottom: 8px;
+            right: 10px;
+            font-size: 12px;
+            color: #999;
+            opacity: 0.8;
+            transition: all 0.3s ease;
+            display: flex;
+            align-items: center;
+            background: rgba(255, 255, 255, 0.9);
+            padding: 2px 6px;
+            border-radius: 4px;
+            pointer-events: none;
+            z-index: 1;
+        }
+        
+        .char-counter.warning {
+            color: #ff9800;
+            font-weight: 500;
+            background: rgba(255, 152, 0, 0.1);
+        }
+        
+        .char-counter.exceeded {
+            color: #f44336;
+            font-weight: 500;
+            background: rgba(244, 67, 54, 0.1);
+            animation: pulse 1s infinite;
+        }
+        
+        @keyframes pulse {
+            0% { transform: scale(1); }
+            50% { transform: scale(1.05); }
+            100% { transform: scale(1); }
+        }
+        
+        .error-message {
+            background-color: #ff5252;
+            color: white;
+            padding: 10px 15px;
+            border-radius: 4px;
+            margin: 10px auto;
+            max-width: 80%;
+            text-align: center;
+            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+            position: relative;
+            transition: opacity 0.3s ease, transform 0.3s ease;
+        }
+
+        /* AI思考过程样式 */
+        .thought-container {
+            background-color: #f3f0fa;
+            border-left: 3px solid #7952b3;
+            padding: 10px 15px;
+            margin: 10px 0 10px 0;
+            border-radius: 0 4px 4px 0;
+            font-style: italic;
+            color: #7952b3;
+            position: relative;
+            font-size: 0.98em;
+        }
+        .thought-container::before {
+            content: "思考过程";
+            display: block;
+            font-size: 0.75rem;
+            color: #7952b3;
+            font-weight: 600;
+            margin-bottom: 4px;
+            font-style: normal;
+        }
+        .thought-toggle-btn {
+            position: absolute;
+            top: 8px;
+            right: 12px;
+            background: none;
+            border: none;
+            color: #7952b3;
+            font-size: 1.1em;
+            cursor: pointer;
+            z-index: 2;
+            padding: 2px 6px;
+            border-radius: 3px;
+            transition: background 0.2s;
+        }
+        .thought-toggle-btn:hover {
+            background: #e5e1f6;
+        }
+        .thought-content {
+            overflow-y: auto;
+            max-height: 500px;
+            transition: max-height 0.35s cubic-bezier(.4,0,.2,1), padding 0.3s;
+            will-change: max-height;
+            /* 自定义滚动条样式 */
+            scrollbar-width: thin;
+            scrollbar-color: #d1c4e9 #f3f0fa;
+        }
+        
+        /* Webkit浏览器的滚动条样式 */
+        .thought-content::-webkit-scrollbar {
+            width: 8px;
+        }
+        
+        .thought-content::-webkit-scrollbar-track {
+            background: #f3f0fa;
+            border-radius: 4px;
+        }
+        
+        .thought-content::-webkit-scrollbar-thumb {
+            background: linear-gradient(135deg, #d1c4e9, #b39ddb);
+            border-radius: 4px;
+            border: 1px solid #f3f0fa;
+            transition: background 0.2s ease;
+        }
+        
+        .thought-content::-webkit-scrollbar-thumb:hover {
+            background: linear-gradient(135deg, #b39ddb, #9575cd);
+        }
+        
+        .thought-content::-webkit-scrollbar-thumb:active {
+            background: linear-gradient(135deg, #9575cd, #7e57c2);
+        }
+        .thought-container.collapsed .thought-content {
+            max-height: 0;
+            padding: 0 !important;
+        }
+        .thought-container.collapsed::before {
+            content: "思考过程(已收起)";
+        }
+        .thought-container p {
+            margin: 0 0 6px 0;
+            font-style: italic;
+            color: #7952b3;
+            background: none;
+            border-bottom: 1px dashed #d1c4e9;
+            padding-bottom: 2px;
+        }
+        .thought-container p:last-child {
+            border-bottom: none;
+        }
+
+        /* 打字机闪烁光标 */
+        .cursor-blink {
+            display: inline-block;
+            color: #666;
+            font-weight: bold;
+            margin-left: 1px;
+            animation: blink 1s step-end infinite;
+        }
+        
+        @keyframes blink {
+            50% { opacity: 0; }
+        }
+
+        #send-btn:disabled {
+            opacity: 0.6;
+            cursor: not-allowed;
+            transform: scale(0.98);
+        }
+
+        #send-btn:disabled:hover {
+            background-color: #007bff;
+        }
+        
+        /* 输入框Loading样式 */
+        .input-loading {
+            background-image: linear-gradient(90deg, rgba(239, 242, 255, 0.5), rgba(255, 255, 255, 0.8), rgba(239, 242, 255, 0.5));
+            background-size: 200% 100%;
+            animation: input-loading 1.5s infinite;
+            border-color: #6366f1 !important;
+            box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.15);
+        }
+        
+        @keyframes input-loading {
+            0% { background-position: 100% 50%; }
+            100% { background-position: 0% 50%; }
+        }
+        
+        /* 消息气泡动画效果 */
+        .message-content.typing {
+            position: relative;
+            overflow: hidden;
+        }
+        
+        .message-content.typing::before {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            border-radius: inherit;
+            border: 2px solid;
+            border-color: #6366f1;
+            box-shadow: 0 0 5px rgba(99, 102, 241, 0.5);
+            animation: border-pulse 0.8s ease-in-out infinite;
+            pointer-events: none;
+        }
+        
+        @keyframes border-pulse {
+            0% { opacity: 0.4; border-color: #6366f1; }
+            50% { opacity: 1; border-color: #818cf8; }
+            100% { opacity: 0.4; border-color: #6366f1; }
+        }
+        
+        /* 对话切换状态样式 */
+        .conversation-item.streaming {
+            position: relative;
+        }
+        
+        .conversation-item.streaming::after {
+            content: '';
+            position: absolute;
+            top: 8px;
+            right: 8px;
+            width: 8px;
+            height: 8px;
+            background: #10b981;
+            border-radius: 50%;
+            animation: pulse-dot 2s infinite;
+        }
+        
+        .conversation-item.paused {
+            position: relative;
+        }
+        
+        .conversation-item.paused::after {
+            content: '';
+            position: absolute;
+            top: 8px;
+            right: 8px;
+            width: 8px;
+            height: 8px;
+            background: #f59e0b;
+            border-radius: 50%;
+            animation: pulse-dot 2s infinite;
+        }
+        
+        @keyframes pulse-dot {
+            0% { opacity: 1; transform: scale(1); }
+            50% { opacity: 0.5; transform: scale(1.2); }
+            100% { opacity: 1; transform: scale(1); }
+        }
+        
+        .paused-tip {
+            background: rgba(245, 158, 11, 0.1);
+            border: 1px solid rgba(245, 158, 11, 0.3);
+            border-radius: 4px;
+            padding: 4px 8px;
+            font-size: 0.85em;
+            color: #f59e0b;
+            margin-top: 8px;
+            font-style: italic;
+            text-align: center;
+        }
+    `;
+    document.head.appendChild(style);
+
+    // 状态变量 - 确保用户ID始终存在
+    let userId = localStorage.getItem('chat_user_id');
+    if (!userId) {
+        userId = generateUUID();
+        localStorage.setItem('chat_user_id', userId);
+    }
+
+    // 新增:用于流式输出中断的标志
+    let isStopped = false;
+    let isWaitingForResponse = false;
+    let currentBotMessageElement = null;
+    let currentBotMessageContainer = null; // 添加消息容器引用
+    let useStreamingMode = true; // 始终启用流式响应
+    let currentLoadingElement = null;
+    let markdownEnabled = true; // 启用 Markdown 渲染
+
+    // 统一的Markdown渲染函数
+    function renderMarkdown (text) {
+        if (!markdownEnabled || typeof showdown === 'undefined') {
+            return text;
+        }
+
+        const converter = new showdown.Converter({
+            strikethrough: true,
+            tables: true,
+            tasklists: true,
+            ghCodeBlocks: true,
+            openLinksInNewWindow: true,
+            emoji: true,
+            underline: true,
+            simplifiedAutoLink: true,
+            excludeTrailingPunctuationFromURLs: true,
+            literalMidWordUnderscores: true,
+            tablesHeaderId: true,
+            smoothLivePreview: true,
+            smartIndentationFix: true,
+            disableForced4SpacesIndentedSublists: true,
+            simpleLineBreaks: true,
+            requireSpaceBeforeHeadingText: true,
+            ghCompatibleHeaderId: true,
+            ghMentions: true,
+            ghMentionsLink: 'https://github.com/{mention}',
+            encodeEmails: true,
+            backslashEscapesHTMLTags: true,
+            omitExtraWLInCodeBlocks: true,
+            noHeaderId: false,
+            prefixHeaderId: false,
+            rawPrefixHeaderId: false,
+            rawHeaderId: false,
+            headerLevelStart: 1,
+            parseImgDimensions: true
+        });
+
+        return converter.makeHtml(text);
+    }
+
+    // 思考过程专用的渲染函数
+    function renderThoughts (text) {
+        if (!markdownEnabled || typeof showdown === 'undefined') {
+            return text;
+        }
+
+        const converter = new showdown.Converter({
+            strikethrough: true,
+            tables: true,
+            tasklists: true,
+            ghCodeBlocks: true,
+            openLinksInNewWindow: true,
+            emoji: true,
+            underline: true,
+            simplifiedAutoLink: true,
+            excludeTrailingPunctuationFromURLs: true,
+            literalMidWordUnderscores: true,
+            tablesHeaderId: true,
+            smoothLivePreview: true,
+            smartIndentationFix: true,
+            disableForced4SpacesIndentedSublists: true,
+            simpleLineBreaks: false, // 禁用简单换行,保持段落结构
+            requireSpaceBeforeHeadingText: true,
+            ghCompatibleHeaderId: true,
+            ghMentions: true,
+            ghMentionsLink: 'https://github.com/{mention}',
+            encodeEmails: true,
+            backslashEscapesHTMLTags: true,
+            omitExtraWLInCodeBlocks: true,
+            noHeaderId: false,
+            prefixHeaderId: false,
+            rawPrefixHeaderId: false,
+            rawHeaderId: false,
+            headerLevelStart: 1,
+            parseImgDimensions: true
+        });
+
+        return converter.makeHtml(text);
+    }
+
+    let renderDebounceTimer = null; // 用于防抖动渲染
+    let maxMessageLength = 20000; // 设置最大消息长度与后端POST限制一致
+    let eventSource = null; // 存储EventSource实例,用于停止请求
+    let abortController = null; // 用于中止fetch请求
+
+    // 全局对话状态管理 - 支持多对话并发流式响应
+    const conversationStates = new Map(); // 存储每个对话的状态
+    let currentActiveConversationId = null; // 当前激活的对话ID
+    let conversationStreams = new Map(); // 存储每个对话的流式连接
+
+    // 用于控制自动滚动的变量
+    let shouldAutoScroll = true;
+    let lastScrollTop = 0;
+    let scrollThreshold = 50; // 距离底部多少像素内触发自动滚动
+
+    // 对话状态管理函数
+    function initializeConversationState (conversationId) {
+        if (!conversationStates.has(conversationId)) {
+            conversationStates.set(conversationId, {
+                isStreaming: false,
+                isPaused: false,
+                currentMessage: '',
+                currentThoughts: [],
+                messageElement: null,
+                thoughtContainer: null,
+                abortController: null,
+                lastUpdateTime: Date.now(),
+                isVisible: false,
+                isUserStopped: false // 标记是否为用户主动停止
+            });
+        }
+    }
+
+    function setActiveConversation (conversationId) {
+        // 暂停当前激活的对话(如果存在)
+        if (currentActiveConversationId && currentActiveConversationId !== conversationId) {
+            pauseConversationStream(currentActiveConversationId);
+        }
+
+        // 激活新对话
+        currentActiveConversationId = conversationId;
+        initializeConversationState(conversationId);
+
+        // 切换对话时默认启用自动跟随底部
+        shouldAutoScroll = true;
+
+        const state = conversationStates.get(conversationId);
+        state.isVisible = true;
+
+        // 根据当前对话状态更新输入框UI
+        updateInputUIForConversation(conversationId);
+
+        // 如果该对话有正在进行的流式响应,恢复显示
+        if (state.isStreaming) {
+            if (state.isPaused) {
+                // 如果被暂停了,恢复显示
+                resumeConversationStream(conversationId);
+            } else {
+                // 如果正在流式响应但没有暂停,确保可见
+                state.isVisible = true;
+                updateConversationItemStatus(conversationId, 'streaming');
+
+                // 确保消息元素可见
+                if (state.messageElement) {
+                    state.messageElement.style.opacity = '1';
+                    state.messageElement.style.transform = 'translateY(0)';
+
+                    // 滚动到消息位置
+                    const chatMessages = document.getElementById('chat-messages');
+                    if (chatMessages) {
+                        state.messageElement.scrollIntoView({ behavior: 'smooth', block: 'end' });
+                    }
+                }
+            }
+        } else if (state.currentMessage || state.currentThoughts.length > 0) {
+            // 如果流式响应已完成,但还有内容,确保显示
+            if (state.messageElement) {
+                state.isVisible = true;
+                updateConversationItemStatus(conversationId, 'stopped');
+
+                // 确保消息元素可见
+                state.messageElement.style.opacity = '1';
+                state.messageElement.style.transform = 'translateY(0)';
+
+                // 滚动到消息位置
+                const chatMessages = document.getElementById('chat-messages');
+                if (chatMessages && state.messageElement) {
+                    state.messageElement.scrollIntoView({ behavior: 'smooth', block: 'end' });
+                }
+            }
+        }
+
+        // 更新全局状态:检查是否有任何对话正在流式响应
+        isWaitingForResponse = isAnyConversationStreaming();
+    }
+
+    // 根据对话状态更新输入框UI
+    function updateInputUIForConversation (conversationId) {
+        const state = conversationStates.get(conversationId);
+        const userInput = document.getElementById('user-input');
+        const sendButton = document.getElementById('send-btn');
+
+        if (!userInput || !sendButton) return;
+
+        // 检查当前对话是否正在流式响应
+        const isCurrentConversationStreaming = state && state.isStreaming && !state.isPaused;
+
+        if (isCurrentConversationStreaming) {
+            // 如果当前对话正在流式响应,设置输入框为回复中状态
+            userInput.classList.add('input-loading');
+            userInput.placeholder = '正在回复中...';
+            userInput.disabled = true;
+
+            // 更新发送按钮为停止按钮
+            sendButton.innerHTML = `
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                    <rect x="6" y="6" width="12" height="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                </svg>`;
+            sendButton.classList.add('stopping');
+
+            // 更新全局状态
+            isWaitingForResponse = true;
+        } else {
+            // 如果当前对话没有流式响应,恢复输入框为正常状态
+            userInput.classList.remove('input-loading');
+            userInput.placeholder = '请输入需要撰写的品类名和要求...';
+            userInput.disabled = false;
+
+            // 恢复发送按钮
+            sendButton.innerHTML = `
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                    <path d="M22 2L11 13M22 2L15 22L11 13M11 13L2 9L22 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                </svg>`;
+            sendButton.classList.remove('stopping');
+
+            // 更新全局状态
+            isWaitingForResponse = false;
+        }
+    }
+
+    // 新增:检查是否有任何对话正在流式响应
+    function isAnyConversationStreaming () {
+        for (const [conversationId, state] of conversationStates) {
+            if (state.isStreaming && !state.isPaused) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // 新增:获取当前正在流式响应的对话ID
+    function getStreamingConversationId () {
+        for (const [conversationId, state] of conversationStates) {
+            if (state.isStreaming && !state.isPaused) {
+                return conversationId;
+            }
+        }
+        return null;
+    }
+
+    function pauseConversationStream (conversationId) {
+        const state = conversationStates.get(conversationId);
+        if (state && state.isStreaming) {
+            state.isPaused = true;
+            state.isVisible = false;
+
+            // 在思考过程区域显示"已暂停"提示
+            if (state.thoughtContainer && !state.thoughtContainer.querySelector('.paused-tip')) {
+                const pausedTip = document.createElement('div');
+                pausedTip.className = 'paused-tip';
+                pausedTip.textContent = '对话已切换,AI回复已暂停';
+                pausedTip.style.color = '#f59e0b';
+                pausedTip.style.marginTop = '8px';
+                pausedTip.style.fontSize = '0.9em';
+                pausedTip.style.fontStyle = 'italic';
+                state.thoughtContainer.appendChild(pausedTip);
+            }
+
+            // 更新侧边栏状态显示
+            updateConversationItemStatus(conversationId, 'paused');
+        }
+    }
+
+    function resumeConversationStream (conversationId) {
+        const state = conversationStates.get(conversationId);
+        if (state && state.isStreaming && state.isPaused) {
+            state.isPaused = false;
+            state.isVisible = true;
+
+            // 移除暂停提示
+            const pausedTip = state.thoughtContainer?.querySelector('.paused-tip');
+            if (pausedTip) {
+                pausedTip.remove();
+            }
+
+            // 更新侧边栏状态显示
+            updateConversationItemStatus(conversationId, 'streaming');
+
+            // 更新输入框状态
+            updateInputUIForConversation(conversationId);
+
+            // 恢复显示最新的思考过程
+            if (state.thoughtContainer && state.currentThoughts.length > 0) {
+                const thoughtContent = state.thoughtContainer.querySelector('.thought-content');
+                if (thoughtContent) {
+                    const combinedThoughts = state.currentThoughts.join('');
+                    // 使用思考过程专用的渲染函数
+                    thoughtContent.innerHTML = renderThoughts(combinedThoughts);
+                }
+            }
+
+            // 恢复显示最新的消息内容
+            if (state.messageElement && state.currentMessage) {
+                const contentContainer = state.messageElement.querySelector('.message-content');
+                if (contentContainer) {
+                    debouncedRenderMarkdown(contentContainer, state.currentMessage);
+                }
+            }
+
+            // 确保消息元素可见
+            if (state.messageElement) {
+                state.messageElement.style.opacity = '1';
+                state.messageElement.style.transform = 'translateY(0)';
+
+                // 滚动到当前对话的底部
+                const chatMessages = document.getElementById('chat-messages');
+                if (chatMessages) {
+                    chatMessages.scrollTop = chatMessages.scrollHeight;
+                }
+            }
+
+            // 如果消息元素不在聊天区域中,重新添加到聊天区域
+            if (state.messageElement && !state.messageElement.parentNode) {
+                const chatMessages = document.getElementById('chat-messages');
+                if (chatMessages) {
+                    chatMessages.appendChild(state.messageElement);
+                    // 滚动到底部
+                    setTimeout(() => {
+                        chatMessages.scrollTop = chatMessages.scrollHeight;
+                    }, 100);
+                }
+            }
+        }
+    }
+
+    function stopConversationStream (conversationId) {
+        const state = conversationStates.get(conversationId);
+        if (state) {
+            // 标记为用户主动停止
+            state.isUserStopped = true;
+            state.isStreaming = false;
+            state.isPaused = false;
+
+            if (state.abortController) {
+                state.abortController.abort();
+                state.abortController = null;
+            }
+
+            // 移除暂停提示
+            const pausedTip = state.thoughtContainer?.querySelector('.paused-tip');
+            if (pausedTip) {
+                pausedTip.remove();
+            }
+        }
+
+        // 更新侧边栏状态显示
+        updateConversationItemStatus(conversationId, 'stopped');
+
+        // 更新全局状态:检查是否有任何对话正在流式响应
+        isWaitingForResponse = isAnyConversationStreaming();
+
+        // 如果停止的是当前激活的对话,更新输入框状态
+        if (conversationId === currentActiveConversationId) {
+            updateInputUIForConversation(conversationId);
+        }
+    }
+
+    function updateConversationItemStatus (conversationId, status) {
+        const conversationItem = document.querySelector(`[data-conversation-id="${conversationId}"]`);
+        if (conversationItem) {
+            const statusDot = conversationItem.querySelector('.conversation-status-dot');
+
+            // 移除所有状态类
+            conversationItem.classList.remove('streaming', 'paused');
+
+            // 添加新的状态类
+            if (status === 'streaming') {
+                conversationItem.classList.add('streaming');
+                if (statusDot) {
+                    statusDot.style.opacity = '1';
+                    statusDot.style.background = '#10b981';
+                }
+            } else if (status === 'paused') {
+                conversationItem.classList.add('paused');
+                if (statusDot) {
+                    statusDot.style.opacity = '1';
+                    statusDot.style.background = '#f59e0b';
+                }
+            } else if (status === 'stopped') {
+                if (statusDot) {
+                    statusDot.style.opacity = '0';
+                }
+            }
+        }
+    }
+
+    // 创建字符计数器
+    const charCounter = document.createElement('div');
+    charCounter.className = 'char-counter';
+    charCounter.textContent = '0/' + maxMessageLength;
+    charCounter.id = 'char-counter';
+
+    // 将字符计数器添加到输入框的父元素中,以便定位在输入框内部
+    const inputContainer = document.querySelector('.chat-input-container');
+    if (inputContainer) {
+        inputContainer.style.position = 'relative';
+        inputContainer.appendChild(charCounter);
+    }
+
+    // 防抖动函数
+    function debounce (func, wait) {
+        return function () {
+            const context = this;
+            const args = arguments;
+            clearTimeout(renderDebounceTimer);
+            renderDebounceTimer = setTimeout(() => {
+                func.apply(context, args);
+            }, wait);
+        };
+    }
+
+    // 防抖动的Markdown渲染函数,确保光标跟随文字
+    const debouncedRenderMarkdown = debounce((element, text) => {
+        // 检查是否需要自动滚动
+        const wasAtBottom = shouldAutoScroll && isUserNearBottom();
+
+        // 使用 requestAnimationFrame 优化渲染性能
+        requestAnimationFrame(() => {
+            try {
+                // 移除现有光标
+                const existingCursor = element.querySelector('.cursor-blink');
+                if (existingCursor) existingCursor.remove();
+
+                // 使用统一的Markdown渲染函数
+                element.innerHTML = renderMarkdown(text);
+
+                // 添加代码高亮
+                applyCodeHighlighting(element);
+                // 处理审核结果表格
+                processAuditResultTables();
+            } catch (error) {
+                // 出错时使用简单文本显示
+                element.textContent = text;
+            }
+
+            // 根据情况滚动到底部
+            if (wasAtBottom) {
+                requestAnimationFrame(() => {
+                    forceScrollToBottom();
+                });
+            }
+        });
+    }, 16); // 降低防抖时间到16ms,约等于60fps
+
+    // 改进 Markdown 解析器
+    function parseMarkdown (text) {
+        if (!markdownEnabled || !text) return text;
+
+        try {
+            // 检查marked是否存在
+            if (typeof marked === 'undefined') {
+                // 简单的备用实现:仅处理基本格式
+                const formattedText = text
+                    .replace(/\n\n/g, '</p><p>') // 段落
+                    .replace(/\n/g, '<br>') // 换行
+                    .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') // 粗体
+                    .replace(/\*(.*?)\*/g, '<em>$1</em>') // 斜体
+                    .replace(/`(.*?)`/g, '<code>$1</code>'); // 行内代码
+
+                return `<div class="markdown-content"><p>${formattedText}</p></div>`;
+            }
+
+            // 配置marked选项
+            marked.setOptions({
+                renderer: new marked.Renderer(),
+                highlight: function (code, lang) {
+                    const language = hljs.getLanguage(lang) ? lang : 'plaintext';
+                    return hljs.highlight(code, { language }).value;
+                },
+                langPrefix: 'hljs language-', // 为代码块添加类名
+                pedantic: false,
+                gfm: true,
+                breaks: true,
+                sanitize: false,
+                smartypants: false,
+                xhtml: false
+            });
+
+            // 创建自定义渲染器
+            const renderer = new marked.Renderer();
+
+            // 定制表格渲染方法
+            renderer.table = function (header, body) {
+                // 检测表格中是否包含违规内容
+                const hasViolations = body.includes('violation-row');
+                // 添加audit-result类,确保能应用我们的CSS样式
+                return `<table class="audit-result ${hasViolations ? 'risk-table' : ''}" data-type="audit-result">\n<thead>\n${header}</thead>\n<tbody>\n${body}</tbody>\n</table>\n`;
+            };
+
+            // 定制表格行渲染方法
+            renderer.tablerow = function (content) {
+                // 检测行内是否包含违规关键词
+                const violationKeywords = ['违规', '违反', '不符合', '禁止', '风险', '投诉', '举报', '警告', '严重', '不通过'];
+                let rowClass = '';
+
+                if (violationKeywords.some(keyword => content.includes(keyword))) {
+                    rowClass = 'violation-row';
+                } else if (content.includes('注意') || content.includes('警示') || content.includes('提醒')) {
+                    rowClass = 'warning-row';
+                } else if (content.includes('正常') || content.includes('合规') || content.includes('通过')) {
+                    rowClass = 'safe-row';
+                }
+
+                return `<tr class="${rowClass}">${content}</tr>\n`;
+            };
+
+            // 定制段落渲染方法
+            renderer.paragraph = function (text) {
+                // 高亮审核结果关键词
+                if (text.includes('通过') && !text.includes('不通过')) {
+                    text = text.replace(
+                        /(.*?)(通过)(.*?)/g,
+                        '$1<span class="audit-pass">$2</span>$3'
+                    );
+                }
+                if (text.includes('不通过')) {
+                    text = text.replace(
+                        /(.*?)(不通过)(.*?)/g,
+                        '$1<span class="audit-fail">$2</span>$3'
+                    );
+                }
+
+                // 突出显示包含违规关键词的段落
+                const violationKeywords = ['违规', '违反', '不符合', '禁止', '风险', '投诉', '举报', '警告', '严重'];
+
+                violationKeywords.forEach(keyword => {
+                    if (text.includes(keyword)) {
+                        text = text.replace(
+                            new RegExp(`(.*?)(${keyword})(.*?)`, 'g'),
+                            '$1<span class="violation">$2</span>$3'
+                        );
+                    }
+                });
+
+                return `<p>${text}</p>\n`;
+            };
+
+            // 使用定制的渲染器解析Markdown
+            marked.setOptions({ renderer: renderer });
+
+            // 解析Markdown文本
+            const parsed = marked.parse(text);
+
+            // 添加markdown-content类以应用样式
+            const result = `<div class="markdown-content">${parsed}</div>`;
+
+
+            return result;
+
+        } catch (error) {
+            // 发生错误时,回退到原始文本
+            return `<p>${text}</p>`;
+        }
+    }
+
+    // 自动调整文本区域高度
+    function autoResizeTextarea () {
+        // 重置高度,以便重新计算
+        userInput.style.height = 'auto';
+
+        // 设置行高基准值
+        const lineHeight = 24; // 假设行高为24px
+        const minHeight = lineHeight + 12; // 最小高度,一行加上padding
+        const maxHeight = 200; // 最大高度
+
+        // 计算内容高度
+        const contentHeight = userInput.scrollHeight;
+
+        if (contentHeight <= minHeight) {
+            // 内容少于或等于一行
+            userInput.style.height = minHeight + 'px';
+            userInput.style.overflowY = 'hidden'; // 隐藏滚动条
+        } else if (contentHeight > maxHeight) {
+            // 内容超过最大高度
+            userInput.style.height = maxHeight + 'px';
+            userInput.style.overflowY = 'auto'; // 显示滚动条
+        } else {
+            // 内容在一行和最大高度之间
+            userInput.style.height = contentHeight + 'px';
+            userInput.style.overflowY = 'hidden'; // 隐藏滚动条
+        }
+    }
+
+    // 更新字符计数器
+    function updateCharCounter () {
+        const charCount = userInput.value.length;
+        const counterElement = document.getElementById('char-counter');
+
+        if (counterElement) {
+            counterElement.textContent = `${charCount}/${maxMessageLength}`;
+
+            // 当接近字符上限时改变样式提醒用户
+            counterElement.classList.remove('warning', 'exceeded');
+
+            if (charCount > maxMessageLength) {
+                counterElement.classList.add('exceeded');
+            } else if (charCount > maxMessageLength * 0.9) {
+                counterElement.classList.add('warning');
+            }
+        }
+    }
+
+    // 检查用户是否接近底部
+    function isUserNearBottom () {
+        return chatMessages.scrollHeight - chatMessages.scrollTop - chatMessages.clientHeight < scrollThreshold;
+    }
+
+    // 强制滚动到底部
+    function forceScrollToBottom (force = false) {
+        // 如果正在编辑消息,不滚动
+        if (window.isEditingMessage) {
+            return;
+        }
+        if (!force && !shouldAutoScroll) {
+            return;
+        }
+        chatMessages.scrollTop = chatMessages.scrollHeight;
+    }
+
+    // 添加用户消息
+    function addUserMessage (message, messageId = null) {
+        const messageElement = document.createElement('div');
+        messageElement.className = 'message user';
+
+        const contentElement = document.createElement('div');
+        contentElement.className = 'message-content';
+        contentElement.textContent = message;
+
+        messageElement.appendChild(contentElement);
+
+        // 如果有 messageId,添加编辑按钮
+        if (messageId) {
+            // 转义特殊字符,防止XSS攻击
+            const escapedContent = message
+                .replace(/\\/g, '\\\\')
+                .replace(/"/g, '&quot;')
+                .replace(/'/g, '&#39;')
+                .replace(/</g, '&lt;')
+                .replace(/>/g, '&gt;')
+                .replace(/\n/g, '\\n');
+
+            const editButton = document.createElement('button');
+            editButton.className = 'edit-button';
+            editButton.setAttribute('title', '编辑消息');
+            editButton.innerHTML = `
+                <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                    <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                    <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                </svg>
+            `;
+            editButton.onclick = function () {
+                // 调用全局的 editMessage 函数(在 index.html 中定义)
+                if (typeof window.editMessage === 'function') {
+                    window.editMessage(messageId, message);
+                }
+            };
+
+            messageElement.appendChild(editButton);
+            messageElement.setAttribute('data-message-id', messageId);
+        }
+
+        chatMessages.appendChild(messageElement);
+
+        // 确保消息立即可见
+        messageElement.style.opacity = '1';
+        messageElement.style.transform = 'translateY(0)';
+
+        // 滚动到底部
+        forceScrollToBottom(true);
+
+        return messageElement;
+    }
+
+    // 修改添加机器人消息函数
+    function addBotMessage (message, isMarkdown = true, thoughts = []) {
+        if (message.length <= 0 && (!thoughts || thoughts.length === 0)) {
+            return;
+        }
+
+        const botMessage = document.createElement('div');
+        botMessage.className = 'message bot-message';
+
+        // 先渲染思考过程(如有)
+        if (thoughts && Array.isArray(thoughts) && thoughts.length > 0) {
+            const thoughtContainer = document.createElement('div');
+            thoughtContainer.className = 'thought-container';
+
+            // 创建折叠按钮
+            const toggleBtn = document.createElement('button');
+            toggleBtn.className = 'thought-toggle-btn';
+            toggleBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
+            toggleBtn.title = '收起/展开思考过程';
+            toggleBtn.onclick = function (e) {
+                e.stopPropagation();
+                thoughtContainer.classList.toggle('collapsed');
+                const icon = toggleBtn.querySelector('svg path');
+                if (thoughtContainer.classList.contains('collapsed')) {
+                    icon.setAttribute('d', 'M18 15L12 9L6 15');
+                } else {
+                    icon.setAttribute('d', 'M6 9l6 6 6-6');
+                }
+            };
+            thoughtContainer.appendChild(toggleBtn);
+
+            const contentArea = document.createElement('div');
+            contentArea.className = 'thought-content';
+
+            // 处理思考过程,显示为连续的文本块
+            if (Array.isArray(thoughts)) {
+                // 如果是数组,合并后使用思考过程专用的渲染函数
+                const combinedThoughts = thoughts.join('');
+                contentArea.innerHTML = renderThoughts(combinedThoughts);
+            } else if (typeof thoughts === 'string') {
+                // 如果是字符串,使用思考过程专用的渲染函数
+                contentArea.innerHTML = renderThoughts(thoughts);
+            }
+
+            thoughtContainer.appendChild(contentArea);
+            botMessage.appendChild(thoughtContainer);
+        }
+
+        // 再渲染AI回复内容
+        const messageContent = document.createElement('div');
+        messageContent.className = 'message-content';
+        if (isMarkdown) {
+            messageContent.innerHTML = renderMarkdown(message);
+        } else {
+            messageContent.innerText = message;
+        }
+        botMessage.appendChild(messageContent);
+
+        document.getElementById('chat-messages').appendChild(botMessage);
+        applyCodeHighlighting(botMessage);
+        processAuditResultTables();
+
+        // 为新添加的消息添加复制按钮
+        if (typeof window.setupCopyButtons === 'function') {
+            window.setupCopyButtons();
+        }
+
+        forceScrollToBottom();
+
+        return botMessage;
+    }
+
+    // 添加错误消息
+    function addErrorMessage (error, isAuthError = false) {
+        // 如果已经有打字动画,则移除
+        removeTypingAnimation();
+
+        const errorElement = document.createElement('div');
+        errorElement.className = 'error-message';
+
+        if (isAuthError) {
+            errorElement.innerHTML = `
+                <div style="display: flex; align-items: center; gap: 8px;">
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M12 9V13M12 17H12.01M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                    </svg>
+                    <span>${error}</span>
+                </div>
+                <div style="margin-top: 8px; font-size: 0.9em; opacity: 0.9;">
+                    正在尝试重新连接,如果失败将跳转到登录页面...
+                </div>
+            `;
+        } else {
+            errorElement.textContent = error;
+        }
+
+        chatMessages.appendChild(errorElement);
+        forceScrollToBottom();
+
+        // 认证错误显示更长时间,其他错误5秒后移除
+        const timeout = isAuthError ? 10000 : 5000;
+        setTimeout(() => {
+            if (errorElement.parentNode) {
+                errorElement.parentNode.removeChild(errorElement);
+            }
+        }, timeout);
+    }
+
+    // 生成UUID
+    function generateUUID () {
+        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+            var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
+            return v.toString(16);
+        });
+    }
+
+    // 应用代码块高亮
+    function applyCodeHighlighting (element) {
+        // 检查hljs是否存在
+        if (typeof hljs === 'undefined') {
+            return;
+        }
+
+        try {
+            const codeBlocks = element.querySelectorAll('pre code');
+            for (const block of codeBlocks) {
+                hljs.highlightElement(block);
+            }
+        } catch (error) {
+        }
+    }
+
+    // 处理审核结果表格
+    function processAuditResultTables () {
+        const tables = document.querySelectorAll('table.audit-result');
+        tables.forEach(table => {
+            // 如果表格已经处理过,则跳过
+            if (table.dataset.processed) return;
+
+            // 标记表格已处理
+            table.dataset.processed = 'true';
+
+            // 处理表格内的长文本
+            const cells = table.querySelectorAll('td');
+            cells.forEach(cell => {
+                if (cell.textContent.length > 100) {
+                    cell.classList.add('long-text');
+                }
+            });
+        });
+    }
+
+    // 修改发送消息函数
+    function sendMessage () {
+        const message = userInput.value.trim();
+
+        // 检查是否有任何对话正在流式响应
+        if (!message || isAnyConversationStreaming()) return;
+
+        if (message.length > maxMessageLength) {
+            alert(`消息长度不能超过${maxMessageLength}字符`);
+            return;
+        }
+
+        // 获取当前对话ID
+        const conversationId = window.currentConversationId || 'new';
+
+        // 初始化对话状态
+        initializeConversationState(conversationId);
+        setActiveConversation(conversationId);
+
+        // 确保当前对话可见
+        const state = conversationStates.get(conversationId);
+        if (state) {
+            state.isVisible = true;
+        }
+
+        // 展示用户消息(立即显示,不等待后端响应)
+        // 注意:此时还没有 message_id,后续会在收到后端响应后更新
+        const userMessageElement = addUserMessage(message);
+
+        // 将用户消息保存到对话状态中,确保切换对话时不会丢失
+        if (state) {
+            state.userMessages = state.userMessages || [];
+            state.userMessages.push({
+                content: message,
+                timestamp: new Date(),
+                element: userMessageElement,
+                messageId: null // 将在收到后端响应后设置
+            });
+        }
+
+        // 清空输入框
+        userInput.value = '';
+        autoResizeTextarea();
+        updateCharCounter();
+
+        // 更新按钮状态为停止按钮
+        sendButton.innerHTML = `
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                <rect x="6" y="6" width="12" height="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+            </svg>`;
+        sendButton.classList.add('stopping');
+
+        // 标记正在等待响应
+        isWaitingForResponse = true;
+
+        // 将输入框设置为loading状态
+        userInput.classList.add('input-loading');
+        userInput.placeholder = '正在回复中...';
+        userInput.disabled = true;
+
+        // 准备请求数据
+        const requestData = {
+            message: message,
+            user_id: userId,
+            agent: (localStorage.getItem('current_agent') || 'kuaishou'),
+            conversation_id: window.currentConversationId || null,
+            session_id: window.currentSessionId || null,
+            force_new: window.forceNewConversation || false
+        };
+
+        // 重置强制创建新对话的标志
+        window.forceNewConversation = false;
+
+        // 根据选择的模式,使用不同的请求方式
+        if (useStreamingMode) {
+            handleStreamResponse(message, conversationId);
+        } else {
+            handleStandardResponse(message, requestData);
+        }
+
+        isStopped = false; // 每次发送消息时重置
+    }
+
+    // 修改处理流式响应函数
+    function handleStreamResponse (message, conversationId) {
+        const state = conversationStates.get(conversationId);
+        if (!state) {
+            return;
+        }
+
+        // 重置对话状态
+        state.isStreaming = true;
+        state.isPaused = false;
+        state.isUserStopped = false; // 重置用户停止标志
+        state.currentMessage = '';
+        state.currentThoughts = [];
+        state.lastUpdateTime = Date.now();
+
+        // 更新侧边栏状态显示
+        updateConversationItemStatus(conversationId, 'streaming');
+
+        let fullMessage = '';
+        let lastProcessedMessage = '';
+        let lastRenderTime = 0;
+        const RENDER_INTERVAL = 16; // 16ms,约等于60fps
+        let allThoughts = [];
+        let thoughtContainer = null; // 新增思考过程容器
+        let thoughtContent = null;   // 新增思考内容包裹层
+        let thoughtToggleBtn = null; // 折叠按钮
+        let contentContainer = null; // 结果内容容器,初始不创建
+
+        try {
+            // 创建AbortController用于取消请求
+            state.abortController = new AbortController();
+
+            // 创建机器人消息元素
+            const messageDiv = document.createElement('div');
+            messageDiv.className = 'message bot';
+
+            // 创建思考过程容器
+            thoughtContainer = document.createElement('div');
+            thoughtContainer.className = 'thought-container';
+
+            // 创建折叠按钮
+            thoughtToggleBtn = document.createElement('button');
+            thoughtToggleBtn.className = 'thought-toggle-btn';
+            // 展开时默认向上箭头
+            thoughtToggleBtn.innerHTML = '<span class="toggle-icon">'
+                + '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">'
+                + '<path d="M18 15L12 9L6 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
+                + '</svg>'
+                + '</span>';
+            thoughtToggleBtn.title = '收起/展开思考过程';
+            thoughtToggleBtn.onclick = function (e) {
+                e.stopPropagation();
+                thoughtContainer.classList.toggle('collapsed');
+                // 切换SVG图标
+                const icon = thoughtToggleBtn.querySelector('.toggle-icon');
+                if (thoughtContainer.classList.contains('collapsed')) {
+                    // 向下箭头
+                    icon.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">'
+                        + '<path d="M6 9L12 15L18 9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
+                        + '</svg>';
+                } else {
+                    // 向上箭头
+                    icon.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">'
+                        + '<path d="M18 15L12 9L6 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
+                        + '</svg>';
+                }
+            };
+            thoughtContainer.appendChild(thoughtToggleBtn);
+
+            // 创建思考内容包裹层
+            thoughtContent = document.createElement('div');
+            thoughtContent.className = 'thought-content';
+            thoughtContainer.appendChild(thoughtContent);
+
+            messageDiv.appendChild(thoughtContainer);
+
+            // 保存到对话状态
+            state.messageElement = messageDiv;
+            state.thoughtContainer = thoughtContainer;
+
+            // 添加到聊天区域
+            chatMessages.appendChild(state.messageElement);
+
+            // 确保消息元素可见
+            messageDiv.style.opacity = '1';
+            messageDiv.style.transform = 'translateY(0)';
+
+            // 添加对话ID标识,便于后续管理
+            messageDiv.setAttribute('data-conversation-id', conversationId);
+
+            // 滚动到底部
+            forceScrollToBottom();
+
+            // 始终使用POST方法发送所有消息
+            fetch('/api/chat/stream', {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({
+                    message: message,
+                    user_id: userId,
+                    agent: (localStorage.getItem('current_agent') || 'kuaishou'),
+                    conversation_id: window.currentConversationId || null,
+                    session_id: window.currentSessionId || null,
+                    force_new: window.forceNewConversation || false
+                }),
+                signal: state.abortController.signal
+            })
+                .then(response => {
+                    if (!response.ok) {
+                        throw new Error(`HTTP error! Status: ${response.status}`);
+                    }
+
+                    // 创建新的EventSource,包装响应
+                    const reader = response.body.getReader();
+                    const decoder = new TextDecoder();
+                    let buffer = '';
+
+                    function readStream () {
+                        reader.read().then(({ done, value }) => {
+                            if (done) {
+                                // 流式响应完成
+                                state.isStreaming = false;
+                                updateConversationItemStatus(conversationId, 'stopped');
+                                resetUIState();
+                                return;
+                            }
+
+                            // 解码二进制数据为文本
+                            const chunk = decoder.decode(value, { stream: true });
+                            buffer += chunk;
+
+                            // 处理buffer中完整的SSE消息
+                            const lines = buffer.split('\n\n');
+                            buffer = lines.pop() || ''; // 最后一个可能不完整,保留到下次处理
+
+                            lines.forEach(line => {
+                                if (line.startsWith('data: ')) {
+                                    try {
+                                        const eventData = JSON.parse(line.substring(6));
+                                        processEventData(eventData, state);
+                                    } catch (error) {
+                                    }
+                                }
+                            });
+
+                            // 无论是否暂停,都继续读取流数据
+                            readStream(); // 继续读取下一块数据
+                        }).catch(error => {
+                            // 如果是用户主动停止,不显示错误提示
+                            if (!state.isPaused && !state.isUserStopped && error.name !== 'AbortError') {
+                                addErrorMessage('连接错误,请重试');
+                            }
+                            state.isStreaming = false;
+                            state.isUserStopped = false; // 重置标志
+                            resetUIState();
+                        });
+                    }
+
+                    readStream(); // 开始读取流
+                })
+                .catch(error => {
+                    // 如果是用户主动停止(AbortError),不显示错误提示
+                    if (!state.isPaused && !state.isUserStopped && error.name !== 'AbortError') {
+                        if (error.message.includes('401')) {
+                            addErrorMessage('登录已过期,正在重新连接...', true);
+                            // 尝试刷新token
+                            tryRefreshToken().then(refreshed => {
+                                if (!refreshed) {
+                                    clearAuthAndRedirect();
+                                }
+                            });
+                        } else {
+                            addErrorMessage('连接错误,请重试: ' + error.message);
+                        }
+                    }
+                    state.isStreaming = false;
+                    state.isUserStopped = false; // 重置标志
+                    updateConversationItemStatus(conversationId, 'stopped');
+                    resetUIState();
+                });
+
+            // 提取通用的事件数据处理逻辑为函数
+            function processEventData (data, conversationState) {
+                // 更新最后更新时间
+                conversationState.lastUpdateTime = Date.now();
+
+                // 处理用户消息ID(在初始响应中返回)
+                if (data.user_message_id) {
+                    // 更新最后添加的用户消息,为其添加编辑按钮
+                    if (conversationState.userMessages && conversationState.userMessages.length > 0) {
+                        const lastUserMessage = conversationState.userMessages[conversationState.userMessages.length - 1];
+                        if (lastUserMessage && !lastUserMessage.messageId) {
+                            lastUserMessage.messageId = data.user_message_id;
+
+                            // 更新消息元素,添加编辑按钮
+                            if (lastUserMessage.element) {
+                                const messageElement = lastUserMessage.element;
+                                const messageId = data.user_message_id;
+                                const messageContent = lastUserMessage.content;
+
+                                // 转义特殊字符
+                                const escapedContent = messageContent
+                                    .replace(/\\/g, '\\\\')
+                                    .replace(/"/g, '&quot;')
+                                    .replace(/'/g, '&#39;')
+                                    .replace(/</g, '&lt;')
+                                    .replace(/>/g, '&gt;')
+                                    .replace(/\n/g, '\\n');
+
+                                // 创建编辑按钮
+                                const editButton = document.createElement('button');
+                                editButton.className = 'edit-button';
+                                editButton.setAttribute('title', '编辑消息');
+                                editButton.innerHTML = `
+                                    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                                        <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                                        <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                                    </svg>
+                                `;
+                                editButton.onclick = function () {
+                                    if (typeof window.editMessage === 'function') {
+                                        window.editMessage(messageId, messageContent);
+                                    }
+                                };
+
+                                messageElement.appendChild(editButton);
+                                messageElement.setAttribute('data-message-id', messageId);
+                            }
+                        }
+                    }
+                }
+
+                if (!data.success) {
+                    if (data.error && data.error.includes('401')) {
+                        addErrorMessage('登录已过期,正在重新连接...', true);
+                        // 尝试刷新token
+                        tryRefreshToken().then(refreshed => {
+                            if (!refreshed) {
+                                clearAuthAndRedirect();
+                            }
+                        });
+                    } else {
+                        addErrorMessage(data.error || '发生未知错误');
+                    }
+                    conversationState.isStreaming = false;
+                    updateConversationItemStatus(conversationId, 'stopped');
+                    resetUIState();
+                    return;
+                }
+
+                // 处理思考过程
+                if (data.thought) {
+                    allThoughts.push(data.thought);
+                    conversationState.currentThoughts = allThoughts;
+                    // 显示为连续的文本块,不添加换行符
+                    if (thoughtContent) {
+                        const combinedThoughts = allThoughts.join('');
+                        // 使用思考过程专用的渲染函数
+                        thoughtContent.innerHTML = renderThoughts(combinedThoughts);
+                        // 只有在可见且未暂停时才滚动
+                        if (conversationState.isVisible && !conversationState.isPaused) {
+                            forceScrollToBottom();
+                        }
+                    }
+                    return;
+                }
+
+                if (data.chunk) {
+                    fullMessage += data.chunk;
+                    conversationState.currentMessage = fullMessage;
+
+                    // 第一次收到chunk时再创建contentContainer
+                    if (!contentContainer) {
+                        contentContainer = document.createElement('div');
+                        contentContainer.className = 'message-content typing';
+                        messageDiv.appendChild(contentContainer);
+                    }
+
+                    // 使用 requestAnimationFrame 优化渲染性能
+                    const now = performance.now();
+                    if (now - lastRenderTime >= RENDER_INTERVAL) {
+                        requestAnimationFrame(() => {
+                            if (fullMessage !== lastProcessedMessage && contentContainer) {
+                                // 总是渲染内容,但只在可见且未暂停时滚动
+                                debouncedRenderMarkdown(contentContainer, fullMessage);
+                                lastProcessedMessage = fullMessage;
+
+                                if (conversationState.isVisible && !conversationState.isPaused && isUserNearBottom()) {
+                                    requestAnimationFrame(() => {
+                                        forceScrollToBottom();
+                                    });
+                                }
+                            }
+                        });
+                        lastRenderTime = now;
+                    }
+                }
+
+                if (data.session_id) {
+                    // 更新当前对话的session_id
+                    window.currentSessionId = data.session_id;
+                }
+
+                if (data.conversation_id) {
+                    // 更新当前对话ID
+                    window.currentConversationId = data.conversation_id;
+
+                    // 更新对话状态中的对话ID
+                    if (conversationId !== data.conversation_id) {
+                        // 获取当前状态
+                        const currentState = conversationStates.get(conversationId);
+                        if (currentState) {
+                            // 更新状态到新的对话ID
+                            conversationStates.set(data.conversation_id, currentState);
+                            conversationStates.delete(conversationId);
+
+                            // 更新当前激活的对话ID
+                            if (currentActiveConversationId === conversationId) {
+                                currentActiveConversationId = data.conversation_id;
+                            }
+                        }
+                    }
+                }
+
+                if (data.done) {
+                    if (contentContainer) {
+                        contentContainer.classList.remove('typing');
+                        // 只在done时渲染最终结果
+                        debouncedRenderMarkdown(contentContainer, fullMessage);
+                    }
+                    conversationState.isStreaming = false;
+                    updateConversationItemStatus(conversationId, 'stopped');
+                    resetUIState();
+                    userInput.focus();
+                    if (conversationState.isVisible) {
+                        forceScrollToBottom();
+                    }
+
+                    // 为流式响应完成的消息添加复制按钮
+                    if (typeof window.setupCopyButtons === 'function') {
+                        window.setupCopyButtons();
+                    }
+
+                    // 对话完成后刷新历史记录
+                    if (typeof loadConversationHistory === 'function') {
+                        // 添加延迟确保数据库保存完成
+                        setTimeout(() => {
+                            loadConversationHistory(true); // 自动选中最新的对话
+                        }, 500);
+                    }
+
+                    // 如果是新对话,更新侧边栏选中状态
+                    if (data.conversation_id && data.conversation_id !== conversationId) {
+                        if (typeof updateConversationSelection === 'function') {
+                            updateConversationSelection(data.conversation_id);
+                        }
+                    }
+
+                    // 更新对话内容缓存
+                    if (typeof updateConversationCache === 'function') {
+                        updateConversationCache(conversationId, fullMessage, allThoughts);
+                    }
+
+                    return;
+                }
+            }
+        } catch (error) {
+            addErrorMessage('连接错误,请重试');
+
+            state.isStreaming = false;
+            updateConversationItemStatus(conversationId, 'stopped');
+            resetUIState();
+        }
+    }
+
+    // 修改处理标准响应函数
+    function handleStandardResponse (message, requestData) {
+        abortController = new AbortController();
+        fetch('/api/chat', {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json'
+            },
+            body: JSON.stringify(requestData),
+            signal: abortController.signal
+        })
+            .then(response => response.json())
+            .then(data => {
+                if (data.success) {
+                    addBotMessage(data.message, true, data.thoughts);
+
+                    // 更新当前对话ID和session_id
+                    if (data.conversation_id) {
+                        window.currentConversationId = data.conversation_id;
+                    }
+                    if (data.session_id) {
+                        window.currentSessionId = data.session_id;
+                    }
+
+                    // 对话完成后刷新历史记录
+                    if (typeof loadConversationHistory === 'function') {
+                        // 添加延迟确保数据库保存完成
+                        setTimeout(() => {
+                            loadConversationHistory(true); // 自动选中最新的对话
+                        }, 500);
+                    }
+
+                    // 如果是新对话,更新侧边栏选中状态
+                    if (data.conversation_id) {
+                        if (typeof updateConversationSelection === 'function') {
+                            updateConversationSelection(data.conversation_id);
+                        }
+                    }
+
+                } else {
+                    if (data.error && data.error.includes('401')) {
+                        addErrorMessage('登录已过期,正在重新连接...', true);
+                        // 尝试刷新token
+                        tryRefreshToken().then(refreshed => {
+                            if (!refreshed) {
+                                clearAuthAndRedirect();
+                            }
+                        });
+                    } else {
+                        addErrorMessage(data.error || '发生未知错误');
+                    }
+                }
+            })
+            .catch(error => {
+                if (error.name === 'AbortError') {
+                    addErrorMessage('请求已取消');
+                } else {
+                    addErrorMessage('连接错误,请重试');
+                }
+            })
+            .finally(() => {
+                document.querySelectorAll('.message-content.typing').forEach(el => {
+                    el.classList.remove('typing');
+                });
+                resetUIState();
+            });
+    }
+
+    // 修改停止请求函数
+    function stopRequest () {
+        isStopped = true; // 新增:设置停止标志
+
+        // 停止当前激活对话的流式响应
+        if (currentActiveConversationId) {
+            stopConversationStream(currentActiveConversationId);
+        }
+
+        if (eventSource) {
+            closeEventSource();
+        }
+        if (abortController) {
+            abortController.abort();
+            abortController = null;
+        }
+
+        // 重置为发送按钮
+        resetUIState();
+
+        // 重置消息元素引用
+        currentBotMessageElement = null;
+        currentBotMessageContainer = null;
+    }
+
+    // 关闭EventSource连接
+    function closeEventSource () {
+        if (eventSource) {
+            eventSource.close();
+            eventSource = null;
+        }
+    }
+
+    // 修改重置UI状态函数
+    function resetUIState () {
+        // 更新全局状态:检查是否有任何对话正在流式响应
+        isWaitingForResponse = isAnyConversationStreaming();
+
+        // 根据当前激活的对话状态来重置UI
+        if (currentActiveConversationId) {
+            updateInputUIForConversation(currentActiveConversationId);
+        } else {
+            // 如果没有激活的对话,恢复默认状态
+            sendButton.innerHTML = `
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                    <path d="M22 2L11 13M22 2L15 22L11 13M11 13L2 9L22 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                </svg>`;
+            sendButton.classList.remove('stopping');
+
+            // 恢复输入框状态
+            userInput.classList.remove('input-loading');
+            userInput.placeholder = '请输入需要撰写的品类名和要求...';
+            userInput.disabled = false;
+        }
+
+        // 重置消息元素引用
+        currentBotMessageElement = null;
+        currentBotMessageContainer = null;
+
+        // 如果有加载动画,移除它
+        if (currentLoadingElement) {
+            currentLoadingElement.remove();
+            currentLoadingElement = null;
+        }
+    }
+
+    // 重置对话
+    function resetConversation () {
+        // 如果正在确认,则执行重置
+        if (resetButton.classList.contains('confirming')) {
+            // 发送重置请求
+            fetch('/api/reset', {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({ user_id: userId })
+            })
+                .then(response => response.json())
+                .then(data => {
+                    if (data.success) {
+                        // 清空聊天记录,仅保留欢迎消息
+                        while (chatMessages.childNodes.length > 1) {
+                            chatMessages.removeChild(chatMessages.lastChild);
+                        }
+
+                        // 清除当前对话ID
+                        window.currentConversationId = null;
+
+                        // 重置按钮状态
+                        resetButton.classList.remove('confirming');
+                        resetButton.textContent = '重置对话';
+
+                        // 显示提示
+                        addBotMessage('对话已重置,您可以开始新的对话了。');
+                    } else {
+                        addErrorMessage(data.error || '重置失败,请重试');
+                    }
+                })
+                .catch(error => {
+                    addErrorMessage('重置失败,请重试');
+                });
+        } else {
+            // 进入确认状态
+            resetButton.classList.add('confirming');
+            resetButton.textContent = '确认重置';
+
+            // 5秒后如果没有点击,恢复原状
+            setTimeout(() => {
+                if (resetButton.classList.contains('confirming')) {
+                    resetButton.classList.remove('confirming');
+                    resetButton.textContent = '重置对话';
+                }
+            }, 5000);
+        }
+    }
+
+    // 添加事件监听器
+    userInput.addEventListener('input', () => {
+        autoResizeTextarea();
+        updateCharCounter();
+    });
+
+    // 在输入框内按Enter发送消息(Shift+Enter换行),状态与发送按钮一致
+    userInput.addEventListener('keydown', (e) => {
+        if (e.key !== 'Enter') return;
+        if (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey || e.isComposing) return;
+
+        e.preventDefault(); // 阻止默认换行
+
+        if (isAnyConversationStreaming()) {
+            stopRequest(); // 和按钮行为保持一致:正在流式时改为停止
+            return;
+        }
+
+        if (userInput.disabled) return;
+        sendMessage();
+    });
+
+    // 按下Escape键时取消请求
+    document.addEventListener('keydown', (e) => {
+        if (e.key === 'Escape' && isAnyConversationStreaming()) {
+            e.preventDefault();
+            stopRequest();
+        }
+    });
+
+    // 点击发送按钮
+    sendButton.addEventListener('click', () => {
+        if (isAnyConversationStreaming()) {
+            stopRequest();
+        } else {
+            sendMessage();
+        }
+    });
+
+    // 重置按钮点击事件
+    resetButton.addEventListener('click', resetConversation);
+
+    // 监听滚动事件
+    chatMessages.addEventListener('scroll', () => {
+        // 记录当前滚动位置
+        const currentScrollTop = chatMessages.scrollTop;
+        const maxScrollTop = chatMessages.scrollHeight - chatMessages.clientHeight;
+
+        if (currentScrollTop < lastScrollTop - 2) {
+            shouldAutoScroll = false;
+        } else if (maxScrollTop - currentScrollTop <= scrollThreshold) {
+            shouldAutoScroll = true;
+        }
+
+        lastScrollTop = currentScrollTop;
+    });
+
+    // 初始化调整文本区域高度
+    autoResizeTextarea();
+
+    // 聚焦到输入框
+    userInput.focus();
+
+    // 初始化字符计数器
+    updateCharCounter();
+
+    // 向输入框添加水波纹效果
+    function createRipple (event) {
+        const button = event.currentTarget;
+
+        // 如果按钮被禁用,则不创建水波纹
+        if (button.disabled) return;
+
+        const ripple = document.createElement('span');
+        ripple.classList.add('ripple');
+        button.appendChild(ripple);
+
+        const rect = button.getBoundingClientRect();
+        const size = Math.max(rect.width, rect.height);
+        const x = event.clientX - rect.left - size / 2;
+        const y = event.clientY - rect.top - size / 2;
+
+        ripple.style.width = ripple.style.height = `${size}px`;
+        ripple.style.left = `${x}px`;
+        ripple.style.top = `${y}px`;
+
+        // 动画结束后移除水波纹元素
+        setTimeout(() => {
+            ripple.remove();
+        }, 600);
+    }
+
+    // 添加水波纹效果
+    sendButton.addEventListener('click', createRipple);
+    resetButton.addEventListener('click', createRipple);
+
+    // 移除打字动画
+    function removeTypingAnimation () {
+        document.querySelectorAll('.message-content.typing').forEach(el => {
+            el.classList.remove('typing');
+        });
+    }
+
+    // 认证相关函数 - 增强版本
+    async function checkAuthStatusInChat () {
+        try {
+            const token = localStorage.getItem('auth_token');
+            const userInfo = localStorage.getItem('user_info');
+
+            if (!token || !userInfo) {
+                window.location.href = '/login';
+                return;
+            }
+
+            // 检查token是否即将过期(提前1小时检查)
+            if (isTokenExpiringSoon(token)) {
+                const refreshed = await tryRefreshToken();
+                if (!refreshed) {
+                    clearAuthAndRedirect();
+                    return;
+                }
+            }
+
+            // 显示本地用户信息
+            try {
+                const user = JSON.parse(userInfo);
+                displayUserInfo(user);
+                showUserSection();
+                addAuthHeader(token);
+            } catch (e) {
+                clearAuthAndRedirect();
+            }
+        } catch (error) {
+            clearAuthAndRedirect();
+        }
+    }
+
+    // 检查token是否即将过期
+    function isTokenExpiringSoon (token) {
+        try {
+            const payload = JSON.parse(atob(token.split('.')[1]));
+            const exp = payload.exp * 1000; // 转换为毫秒
+            const now = Date.now();
+            const oneHour = 60 * 60 * 1000; // 1小时
+            return (exp - now) < oneHour;
+        } catch (e) {
+            return true; // 解析失败,认为已过期
+        }
+    }
+
+    // 尝试刷新token
+    let isRefreshing = false; // 防止并发刷新
+    async function tryRefreshToken () {
+        // 如果正在刷新,直接返回false
+        if (isRefreshing) {
+            return false;
+        }
+
+        try {
+            isRefreshing = true;
+
+            const token = localStorage.getItem('auth_token');
+            if (!token) {
+                return false;
+            }
+
+            const response = await fetch('/api/auth/refresh', {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                    'Authorization': `Bearer ${token}`
+                }
+            });
+
+            const data = await response.json();
+
+            if (response.ok && data.success && data.token) {
+                localStorage.setItem('auth_token', data.token);
+                return true;
+            } else {
+                // 如果是401错误或token过期相关错误,直接返回false
+                if (response.status === 401 ||
+                    data.code === 'TOKEN_EXPIRED_NO_SESSION' ||
+                    data.code === 'MISSING_TOKEN') {
+                    return false;
+                }
+                return false;
+            }
+        } catch (error) {
+            return false;
+        } finally {
+            isRefreshing = false;
+        }
+    }
+
+    function clearAuthAndRedirect () {
+        if (isRedirecting) {
+            return;
+        }
+        isRedirecting = true;
+
+        // 清除本地存储
+        localStorage.removeItem('auth_token');
+        localStorage.removeItem('user_info');
+
+        // 清除session存储(如果有)
+        sessionStorage.removeItem('auth_token');
+        sessionStorage.removeItem('user_info');
+
+        // 立即跳转,不使用setTimeout
+        try {
+            window.location.replace('/login');
+        } catch (error) {
+            window.location.href = '/login';
+        }
+    }
+
+    function showUserSection () {
+        const userInfo = document.getElementById('user-info');
+        if (userInfo) userInfo.style.display = 'flex';
+    }
+
+    function addAuthHeader (token) {
+        // 为所有fetch请求添加认证头和错误处理
+        const originalFetch = window.fetch;
+        window.fetch = async function (url, options = {}) {
+            if (options.headers === undefined) {
+                options.headers = {};
+            }
+            options.headers['Authorization'] = `Bearer ${token}`;
+
+            try {
+                const response = await originalFetch(url, options);
+
+                // 检查401错误,但不要对refresh接口本身进行重试
+                if (response.status == 401) {
+                    // 如果是refresh接口返回401,直接跳转登录,不要再次尝试刷新
+                    const urlString = typeof url === 'string' ? url : url.toString();
+                    if (urlString.includes('/api/auth/refresh')) {
+                        clearAuthAndRedirect();
+                        return response;
+                    }
+
+                    const refreshed = await tryRefreshToken();
+                    if (refreshed) {
+                        // 更新token后重试请求
+                        options.headers['Authorization'] = `Bearer ${localStorage.getItem('auth_token')}`;
+                        return originalFetch(url, options);
+                    } else {
+                        // 刷新失败,清除认证信息并跳转登录
+                        clearAuthAndRedirect();
+                        return response;
+                    }
+                }
+
+                return response;
+            } catch (error) {
+                throw error;
+            }
+        };
+    }
+
+    // 将addAuthHeader函数暴露到全局作用域
+    window.addAuthHeader = addAuthHeader;
+
+    // 定期更新对话状态
+    function startConversationStatusUpdater () {
+        setInterval(() => {
+            updateAllConversationStatuses();
+        }, 2000); // 每2秒更新一次
+    }
+
+    function updateAllConversationStatuses () {
+        conversationStates.forEach((state, conversationId) => {
+            if (state.isStreaming) {
+                // 检查流式响应是否还在进行
+                const now = Date.now();
+                const timeSinceLastUpdate = now - state.lastUpdateTime;
+
+                // 如果超过30秒没有更新,认为流式响应已结束
+                if (timeSinceLastUpdate > 30000) {
+                    state.isStreaming = false;
+                    state.isPaused = false;
+                    updateConversationItemStatus(conversationId, 'stopped');
+                }
+            }
+        });
+    }
+
+    // 将对话状态管理函数和变量暴露到全局作用域
+    window.conversationStates = conversationStates;
+    window.currentActiveConversationId = currentActiveConversationId;
+    window.setActiveConversation = setActiveConversation;
+    window.pauseConversationStream = pauseConversationStream;
+    window.resumeConversationStream = resumeConversationStream;
+    window.stopConversationStream = stopConversationStream;
+    window.initializeConversationState = initializeConversationState;
+    window.updateConversationItemStatus = updateConversationItemStatus;
+    window.startConversationStatusUpdater = startConversationStatusUpdater;
+    window.isAnyConversationStreaming = isAnyConversationStreaming;
+    window.getStreamingConversationId = getStreamingConversationId;
+    window.updateInputUIForConversation = updateInputUIForConversation;
+    window.renderMarkdown = renderMarkdown;
+    window.renderThoughts = renderThoughts;
+
+    function displayUserInfo (userInfo) {
+        const userInfoElement = document.getElementById('user-info');
+        const userNameElement = document.getElementById('user-name');
+        const userTypeElement = document.getElementById('user-type');
+        const userTitleElement = document.getElementById('user-title');
+        const avatarImg = document.getElementById('avatar-img');
+        const avatarFallback = document.getElementById('avatar-fallback');
+        const userDropdown = document.getElementById('user-dropdown');
+
+        if (!userInfoElement || !userNameElement) return;
+
+        // 显示用户名
+        userNameElement.textContent = userInfo.nickname || userInfo.name || '用户';
+
+        // 显示用户类型
+        if (userTypeElement) {
+            const typeMap = {
+                'dingtalk': '钉钉用户',
+                'wecom': '企业微信用户'
+            };
+            userTypeElement.textContent = typeMap[userInfo.login_type] || '用户';
+        }
+
+        // 显示用户职位
+        if (userTitleElement && userInfo.title) {
+            userTitleElement.textContent = userInfo.title;
+            userTitleElement.style.display = 'block';
+        }
+
+        // 显示用户头像
+        if (avatarImg && avatarFallback) {
+            if (userInfo.avatar) {
+                avatarImg.src = userInfo.avatar;
+                avatarImg.style.display = 'block';
+                avatarFallback.style.display = 'none';
+                // 添加头像加载失败处理
+                avatarImg.onerror = function () {
+                    this.style.display = 'none';
+                    avatarFallback.style.display = 'flex';
+                    const firstChar = (userInfo.name || 'U').charAt(0).toUpperCase();
+                    avatarFallback.textContent = firstChar;
+                };
+            } else {
+                avatarImg.style.display = 'none';
+                avatarFallback.style.display = 'flex';
+                const firstChar = (userInfo.name || 'U').charAt(0).toUpperCase();
+                avatarFallback.textContent = firstChar;
+            }
+        }
+
+        // 显示用户详细信息下拉框
+        if (userDropdown) {
+            displayUserDetails(userInfo);
+        }
+
+        userInfoElement.style.display = 'flex';
+    }
+
+    function displayUserDetails (userInfo) {
+        // 显示邮箱信息
+        const emailElement = document.getElementById('user-email');
+        if (emailElement && userInfo.email) {
+            emailElement.querySelector('.info-value').textContent = userInfo.email;
+            emailElement.style.display = 'flex';
+        }
+
+        // 显示手机信息
+        const mobileElement = document.getElementById('user-mobile');
+        if (mobileElement && userInfo.mobile) {
+            mobileElement.querySelector('.info-value').textContent = userInfo.mobile;
+            mobileElement.style.display = 'flex';
+        }
+
+        // 显示工号信息
+        const jobElement = document.getElementById('user-job');
+        if (jobElement && userInfo.job_number) {
+            jobElement.querySelector('.info-value').textContent = userInfo.job_number;
+            jobElement.style.display = 'flex';
+        }
+
+        // 显示部门信息
+        const deptElement = document.getElementById('user-dept');
+        if (deptElement && userInfo.dept_name) {
+            deptElement.querySelector('.info-value').textContent = userInfo.dept_name;
+            deptElement.style.display = 'flex';
+        }
+
+        // 如果有任何详细信息,显示下拉框
+        const userDropdown = document.getElementById('user-dropdown');
+        if (userDropdown && (userInfo.email || userInfo.mobile || userInfo.job_number || userInfo.dept_name)) {
+            userDropdown.style.display = 'block';
+        }
+    }
+
+    async function handleLogout () {
+        try {
+            const token = localStorage.getItem('auth_token');
+
+            // 调用退出登录API
+            const response = await fetch('/api/auth/logout', {
+                method: 'POST',
+                headers: {
+                    'Authorization': `Bearer ${token}`
+                }
+            });
+
+            // 清除本地存储
+            localStorage.removeItem('auth_token');
+            localStorage.removeItem('user_info');
+
+            // 跳转到登录页
+            window.location.href = '/login';
+
+        } catch (error) {
+            // 即使API调用失败,也清除本地存储并跳转
+            localStorage.removeItem('auth_token');
+            localStorage.removeItem('user_info');
+            window.location.href = '/login';
+        }
+    }
+}); 

+ 176 - 0
static/js/copy-buttons.js

@@ -0,0 +1,176 @@
+// 复制按钮功能
+window.setupCopyButtons = function () {
+    // 复制图标SVG
+    const copySvg = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
+        <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
+    </svg>`;
+
+    // 为所有消息添加复制按钮
+    function addCopyButtonsToMessages () {
+        const messages = document.querySelectorAll('.message');
+
+        messages.forEach(message => {
+            // 如果消息已经有复制按钮,则跳过
+            if (message.querySelector('.copy-button')) return;
+
+            const messageContent = message.querySelector('.message-content');
+            if (!messageContent) return;
+
+            // 创建复制按钮
+            const copyButton = document.createElement('button');
+            copyButton.className = 'copy-button';
+            copyButton.innerHTML = copySvg;
+            copyButton.setAttribute('aria-label', '复制内容');
+            copyButton.title = '复制消息内容';
+
+            // 添加点击事件
+            copyButton.addEventListener('click', (e) => {
+                e.stopPropagation(); // 阻止事件冒泡
+                copyMessageContent(messageContent);
+            });
+
+            // 添加到消息元素中,与message-content同级
+            message.appendChild(copyButton);
+        });
+    }
+
+    // 复制消息内容
+    function copyMessageContent (messageContent) {
+        try {
+            // 获取纯文本内容
+            const textContent = messageContent.textContent;
+
+            // 检查是否支持现代剪贴板API
+            if (navigator.clipboard && navigator.clipboard.writeText) {
+                // 使用现代剪贴板API
+                navigator.clipboard.writeText(textContent)
+                    .then(() => {
+                        showNotification('复制成功!', 'success');
+                    })
+                    .catch(err => {
+                        console.error('现代剪贴板API复制失败:', err);
+                        // 回退到传统方法
+                        fallbackCopyTextToClipboard(textContent);
+                    });
+            } else {
+                // 回退到传统方法
+                fallbackCopyTextToClipboard(textContent);
+            }
+        } catch (err) {
+            console.error('复制过程中出错:', err);
+            // 回退到传统方法
+            fallbackCopyTextToClipboard(messageContent.textContent);
+        }
+    }
+
+    // 传统复制方法(兼容性更好)
+    function fallbackCopyTextToClipboard (text) {
+        try {
+            // 创建临时文本区域
+            const textArea = document.createElement('textarea');
+            textArea.value = text;
+
+            // 设置样式使其不可见
+            textArea.style.position = 'fixed';
+            textArea.style.top = '0';
+            textArea.style.left = '0';
+            textArea.style.width = '2em';
+            textArea.style.height = '2em';
+            textArea.style.padding = '0';
+            textArea.style.border = 'none';
+            textArea.style.outline = 'none';
+            textArea.style.boxShadow = 'none';
+            textArea.style.background = 'transparent';
+            textArea.style.opacity = '0';
+            textArea.style.zIndex = '-1';
+
+            // 添加到页面
+            document.body.appendChild(textArea);
+
+            // 选中文本
+            textArea.focus();
+            textArea.select();
+
+            // 尝试复制
+            const successful = document.execCommand('copy');
+
+            // 移除临时元素
+            document.body.removeChild(textArea);
+
+            if (successful) {
+                showNotification('复制成功!', 'success');
+            } else {
+                showNotification('复制失败,请手动选择文本复制', 'error');
+            }
+        } catch (err) {
+            console.error('传统复制方法也失败:', err);
+            showNotification('复制失败,请手动选择文本复制', 'error');
+        }
+    }
+
+    // 显示通知
+    function showNotification (message, type) {
+        // 移除之前的通知
+        const existingNotification = document.querySelector('.copy-notification');
+        if (existingNotification) {
+            existingNotification.remove();
+        }
+
+        // 创建通知元素
+        const notification = document.createElement('div');
+        notification.className = `copy-notification ${type}`;
+        notification.textContent = message;
+
+        // 将通知定位到页面中央
+        notification.style.top = '50%';
+        notification.style.left = '50%';
+        notification.style.right = 'auto';
+        notification.style.transform = 'translate(-50%, -50%)';
+
+        // 添加到文档
+        document.body.appendChild(notification);
+
+        // 显示通知
+        setTimeout(() => {
+            notification.classList.add('show');
+        }, 10);
+
+        // 0.5秒后隐藏并移除通知
+        setTimeout(() => {
+            notification.classList.remove('show');
+
+            // 等待过渡动画完成后移除元素
+            setTimeout(() => {
+                if (notification.parentNode) {
+                    notification.parentNode.removeChild(notification);
+                }
+            }, 300);
+        }, 500);
+    }
+
+    // 初始调用一次,添加现有消息的复制按钮
+    addCopyButtonsToMessages();
+
+    // 创建观察器监听DOM变化,为新添加的消息添加复制按钮
+    const observer = new MutationObserver((mutations) => {
+        mutations.forEach((mutation) => {
+            if (mutation.addedNodes.length) {
+                addCopyButtonsToMessages();
+            }
+        });
+    });
+
+    // 开始观察聊天容器
+    const chatMessages = document.getElementById('chat-messages');
+    if (chatMessages) {
+        observer.observe(chatMessages, { childList: true, subtree: true });
+    }
+};
+
+// 页面加载完成后自动执行
+document.addEventListener('DOMContentLoaded', () => {
+    if (typeof window.setupCopyButtons === 'function') {
+        window.setupCopyButtons();
+    }
+}); 

File diff suppressed because it is too large
+ 534 - 0
static/js/highlight.min.js


+ 238 - 0
static/js/login.js

@@ -0,0 +1,238 @@
+// 登录页面JavaScript逻辑
+class LoginManager {
+	constructor() {
+		this.init();
+	}
+
+	init () {
+		// 检查登录状态
+		this.checkLoginStatus();
+
+		// 绑定事件
+		this.bindEvents();
+	}
+
+	bindEvents () {
+		// 钉钉登录按钮点击事件
+		const dingtalkBtn = document.getElementById('dingtalk-login');
+		if (dingtalkBtn) {
+			dingtalkBtn.addEventListener('click', () => {
+				this.loginWithDingtalkOAuth();
+			});
+		}
+	}
+
+	async checkLoginStatus () {
+		try {
+			const response = await fetch('/api/auth/ping');
+			const data = await response.json();
+
+			if (data.authenticated) {
+				// 已登录,跳转到主页
+				window.location.href = '/';
+			}
+		} catch (error) {
+			console.error('检查登录状态失败:', error);
+		}
+	}
+
+	async loginWithDingtalkOAuth () {
+		try {
+			this.showLoginStatus();
+			const urlResp = await fetch('/api/auth/dingtalk/login_url');
+			const urlData = await urlResp.json();
+
+			if (!urlResp.ok || !urlData.success) {
+				this.hideLoginStatus();
+				if (urlData.error === 'DingTalk未配置') {
+					this.showError('钉钉登录功能暂未启用,请联系管理员配置钉钉OAuth');
+				} else {
+					this.showError(urlData.error || '钉钉登录失败,请重试');
+				}
+				return;
+			}
+
+			window.location.href = urlData.url;
+			this.hideLoginStatus();
+		} catch (error) {
+			console.error('钉钉登录失败:', error);
+			this.showError(error.message || '登录失败,请重试');
+			this.hideLoginStatus();
+		}
+	}
+
+	showError (message) {
+		const existingError = document.querySelector('.error-message');
+		if (existingError) {
+			existingError.remove();
+		}
+
+		const errorDiv = document.createElement('div');
+		errorDiv.className = 'error-message';
+		errorDiv.textContent = message;
+
+		const loginMethods = document.querySelector('.login-methods');
+		if (loginMethods) {
+			loginMethods.appendChild(errorDiv);
+		}
+
+		// 3秒后自动移除错误信息
+		setTimeout(() => {
+			if (errorDiv.parentNode) {
+				errorDiv.parentNode.removeChild(errorDiv);
+			}
+		}, 3000);
+	}
+
+	showLoginStatus () {
+		const status = document.getElementById('login-status');
+		if (status) {
+			status.style.display = 'flex';
+		}
+	}
+
+	hideLoginStatus () {
+		const status = document.getElementById('login-status');
+		if (status) {
+			status.style.display = 'none';
+		}
+	}
+}
+
+// 页面加载完成后初始化
+document.addEventListener('DOMContentLoaded', () => {
+	// 初始化登录管理器
+	window.loginManager = new LoginManager();
+
+	// 添加视觉优化效果
+	addVisualEffects();
+});
+
+// 视觉优化效果函数
+function addVisualEffects () {
+	// 页面加载动画
+	animatePageLoad();
+
+	// 添加鼠标移动视差效果
+	addParallaxEffect();
+
+	// 添加按钮点击效果
+	addButtonEffects();
+
+	// 添加卡片悬停效果
+	addCardHoverEffects();
+}
+
+// 页面加载动画
+function animatePageLoad () {
+	const elements = document.querySelectorAll('.login-card, .floating-shapes .shape');
+
+	elements.forEach((element, index) => {
+		element.style.opacity = '0';
+		element.style.transform = 'translateY(30px)';
+
+		setTimeout(() => {
+			element.style.transition = 'all 0.8s cubic-bezier(0.4, 0, 0.2, 1)';
+			element.style.opacity = '1';
+			element.style.transform = 'translateY(0)';
+		}, index * 100);
+	});
+}
+
+// 添加视差效果
+function addParallaxEffect () {
+	const shapes = document.querySelectorAll('.shape');
+
+	document.addEventListener('mousemove', (e) => {
+		const mouseX = e.clientX / window.innerWidth;
+		const mouseY = e.clientY / window.innerHeight;
+
+		shapes.forEach((shape, index) => {
+			const speed = (index + 1) * 0.5;
+			const x = (mouseX - 0.5) * speed * 20;
+			const y = (mouseY - 0.5) * speed * 20;
+
+			shape.style.transform = `translate(${x}px, ${y}px)`;
+		});
+	});
+}
+
+// 添加按钮效果
+function addButtonEffects () {
+	const buttons = document.querySelectorAll('.login-btn');
+
+	buttons.forEach(button => {
+		button.addEventListener('mousedown', function () {
+			this.style.transform = 'scale(0.95)';
+		});
+
+		button.addEventListener('mouseup', function () {
+			this.style.transform = 'scale(1)';
+		});
+
+		button.addEventListener('mouseleave', function () {
+			this.style.transform = 'scale(1)';
+		});
+	});
+}
+
+// 添加卡片悬停效果
+function addCardHoverEffects () {
+	const card = document.querySelector('.login-card');
+	const method = document.querySelector('.login-method');
+
+	if (card) {
+		card.addEventListener('mouseenter', function () {
+			this.style.transform = 'scale(1.02)';
+		});
+
+		card.addEventListener('mouseleave', function () {
+			this.style.transform = 'scale(1)';
+		});
+	}
+
+	if (method) {
+		method.addEventListener('mouseenter', function () {
+			this.style.transform = 'translateY(-4px) scale(1.02)';
+		});
+
+		method.addEventListener('mouseleave', function () {
+			this.style.transform = 'translateY(0) scale(1)';
+		});
+	}
+}
+
+// 添加触摸设备支持
+if ('ontouchstart' in window) {
+	document.body.classList.add('touch-device');
+
+	// 为触摸设备优化按钮
+	const buttons = document.querySelectorAll('.login-btn');
+	buttons.forEach(button => {
+		button.addEventListener('touchstart', function () {
+			this.style.transform = 'scale(0.95)';
+		});
+
+		button.addEventListener('touchend', function () {
+			this.style.transform = 'scale(1)';
+		});
+	});
+}
+
+// 性能优化:节流函数
+function throttle (func, limit) {
+	let inThrottle;
+	return function () {
+		const args = arguments;
+		const context = this;
+		if (!inThrottle) {
+			func.apply(context, args);
+			inThrottle = true;
+			setTimeout(() => inThrottle = false, limit);
+		}
+	}
+}
+
+// 优化视差效果性能
+const optimizedParallax = throttle(addParallaxEffect, 16);
+document.addEventListener('mousemove', optimizedParallax); 

File diff suppressed because it is too large
+ 12 - 0
static/js/marked.min.js


File diff suppressed because it is too large
+ 1 - 0
static/js/showdown.min.js


BIN
static/logo.png


+ 3714 - 0
templates/index.html

@@ -0,0 +1,3714 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>聚德策划大师-大健康AI文案撰写</title>
+    <meta name="description" content="聚德AI短视频脚本撰写器 - 专业高效的文案创作平台">
+    <meta name="theme-color" content="#6366f1" media="(prefers-color-scheme: light)">
+    <meta name="theme-color" content="#0f172a" media="(prefers-color-scheme: dark)">
+    <meta name="color-scheme" content="light dark">
+    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
+    <link rel="stylesheet" href="{{ url_for('static', filename='css/modal.css') }}">
+    <link rel="stylesheet" href="{{ url_for('static', filename='css/components/copy-button.css') }}">
+    <link rel="icon" href="{{ url_for('static', filename='logo.png') }}">
+    <link rel="stylesheet" href="{{ url_for('static', filename='css/github.min.css') }}">
+    <link rel="stylesheet" href="{{ url_for('static', filename='css/highlightjs.min.css') }}">
+    <link rel="stylesheet" href="{{ url_for('static', filename='css/fonts.css') }}">
+
+    <!-- 思考过程样式 -->
+    <style>
+        /* AI思考过程样式 */
+        .thought-container {
+            background-color: #f3f0fa;
+            border-left: 3px solid #7952b3;
+            padding: 10px 15px;
+            margin: 10px 0 10px 0;
+            border-radius: 0 4px 4px 0;
+            font-style: italic;
+            color: #7952b3;
+            position: relative;
+            font-size: 0.98em;
+        }
+
+        .thought-container::before {
+            content: "思考过程";
+            display: block;
+            font-size: 0.75rem;
+            color: #7952b3;
+            font-weight: 600;
+            margin-bottom: 4px;
+            font-style: normal;
+        }
+
+        .thought-toggle-btn {
+            position: absolute;
+            top: 8px;
+            right: 12px;
+            background: none;
+            border: none;
+            color: #7952b3;
+            font-size: 1.1em;
+            cursor: pointer;
+            z-index: 2;
+            padding: 2px 6px;
+            border-radius: 3px;
+            transition: background 0.2s;
+        }
+
+        .thought-toggle-btn:hover {
+            background: #e5e1f6;
+        }
+
+        /* 思考过程内容区域样式 - 连续文本块 */
+        .thought-content {
+            overflow-y: auto;
+            max-height: 500px;
+            transition: max-height 0.35s cubic-bezier(.4, 0, .2, 1), padding 0.3s;
+            will-change: max-height;
+            /* 自定义滚动条样式 */
+            scrollbar-width: thin;
+            scrollbar-color: #d1c4e9 #f3f0fa;
+            white-space: normal;
+            /* 正常换行处理 */
+            word-wrap: break-word;
+            /* 长单词换行 */
+            line-height: 1.4;
+            /* 适中的行高 */
+            text-align: left;
+            /* 左对齐 */
+        }
+
+        /* Webkit浏览器的滚动条样式 */
+        .thought-content::-webkit-scrollbar {
+            width: 8px;
+        }
+
+        .thought-content::-webkit-scrollbar-track {
+            background: #f3f0fa;
+            border-radius: 4px;
+        }
+
+        .thought-content::-webkit-scrollbar-thumb {
+            background: linear-gradient(135deg, #d1c4e9, #b39ddb);
+            border-radius: 4px;
+            border: 1px solid #f3f0fa;
+            transition: background 0.2s ease;
+        }
+
+        .thought-content::-webkit-scrollbar-thumb:hover {
+            background: linear-gradient(135deg, #b39ddb, #9575cd);
+        }
+
+        .thought-content::-webkit-scrollbar-thumb:active {
+            background: linear-gradient(135deg, #9575cd, #7e57c2);
+        }
+
+        .thought-container.collapsed .thought-content {
+            max-height: 0;
+            padding: 0 !important;
+        }
+
+        .thought-container.collapsed::before {
+            content: "思考过程(已收起)";
+        }
+
+        .thought-container p {
+            margin: 0;
+            font-style: italic;
+            color: #7952b3;
+            background: none;
+            border-bottom: none;
+            padding-bottom: 0;
+            line-height: 1.6;
+        }
+
+        .thought-container p:last-child {
+            border-bottom: none;
+        }
+
+        /* 对话项样式优化 */
+        .conversation-meta {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-top: 4px;
+            font-size: 0.75rem;
+            color: #666;
+            opacity: 0.8;
+        }
+
+        .message-count {
+            font-size: 0.7rem;
+        }
+
+        .last-time {
+            font-size: 0.7rem;
+            color: #999;
+        }
+
+        .conversation-item.active .conversation-meta {
+            color: rgba(99, 102, 241, 0.8);
+        }
+
+        .conversation-item.active .last-time {
+            color: rgba(99, 102, 241, 0.6);
+        }
+
+        /* 对话状态指示器样式 */
+        .conversation-item.streaming .conversation-title {
+            color: #10b981;
+            font-weight: 500;
+        }
+
+        .conversation-item.paused .conversation-title {
+            color: #f59e0b;
+            font-weight: 500;
+        }
+
+        .conversation-item.streaming .conversation-meta {
+            color: #10b981;
+        }
+
+        .conversation-item.paused .conversation-meta {
+            color: #f59e0b;
+        }
+
+        /* 当前活跃对话指示器 */
+        .conversation-item.active {
+            background: linear-gradient(135deg, rgba(99, 102, 241, 0.1), rgba(99, 102, 241, 0.05));
+            border-left: 3px solid #6366f1;
+            position: relative;
+        }
+
+
+        /* 对话切换动画 */
+        .conversation-item {
+            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+            cursor: pointer;
+        }
+
+        .conversation-item:hover {
+            background: rgba(99, 102, 241, 0.05);
+            transform: translateX(2px);
+        }
+
+        .conversation-item.active:hover {
+            background: linear-gradient(135deg, rgba(99, 102, 241, 0.15), rgba(99, 102, 241, 0.08));
+        }
+
+        /* 对话状态点指示器 */
+        .conversation-status-dot {
+            position: absolute;
+            top: 12px;
+            right: 12px;
+            width: 8px;
+            height: 8px;
+            border-radius: 50%;
+            opacity: 0;
+            transition: opacity 0.3s ease;
+        }
+
+        .conversation-item.streaming .conversation-status-dot {
+            background: #10b981;
+            opacity: 1;
+            animation: pulse-dot 2s infinite;
+        }
+
+        .conversation-item.paused .conversation-status-dot {
+            background: #f59e0b;
+            opacity: 1;
+            animation: pulse-dot 2s infinite;
+        }
+
+        .conversation-item.active .conversation-status-dot {
+            background: #6366f1;
+            opacity: 1;
+        }
+
+        @keyframes pulse-dot {
+            0% {
+                opacity: 1;
+                transform: scale(1);
+            }
+
+            50% {
+                opacity: 0.5;
+                transform: scale(1.2);
+            }
+
+            100% {
+                opacity: 1;
+                transform: scale(1);
+            }
+        }
+
+        /* 编辑按钮样式 - 与复制按钮保持一致 */
+        .edit-button {
+            position: absolute;
+            width: 32px;
+            height: 32px;
+            background: var(--primary-indigo);
+            color: white;
+            border: none;
+            border-radius: 50%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            cursor: pointer;
+            z-index: 1001;
+            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
+            transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
+            opacity: 0;
+            bottom: 8px;
+            right: 48px;
+            /* 位于复制按钮左侧 */
+        }
+
+        /* 悬停时显示编辑按钮 */
+        .message.user:hover .edit-button {
+            opacity: 1;
+        }
+
+        .edit-button:hover {
+            transform: scale(1.1);
+            box-shadow: 0 6px 10px rgba(0, 0, 0, 0.25);
+        }
+
+        .edit-button svg {
+            width: 18px;
+            height: 18px;
+        }
+
+        /* 内联编辑样式 */
+        .message-editing {
+            position: relative;
+        }
+
+        .edit-form {
+            display: flex;
+            flex-direction: column;
+            gap: 12px;
+            padding: 16px;
+            background: #f8f9fa;
+            border: 2px solid #6366f1;
+            border-radius: 12px;
+            margin: 8px 0;
+            width: 600px;
+        }
+
+        .edit-textarea {
+            width: 100%;
+            min-height: 80px;
+            padding: 12px;
+            border: none;
+            border-radius: 8px;
+            font-size: 14px;
+            line-height: 1.5;
+            resize: vertical;
+            font-family: inherit;
+            background: white;
+        }
+
+        .edit-textarea:focus {
+            outline: none;
+            box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
+        }
+
+        .edit-actions {
+            display: flex;
+            gap: 12px;
+            justify-content: flex-end;
+        }
+
+        .edit-cancel-btn {
+            padding: 4px 10px;
+            background: white;
+            border: 1px solid #ddd;
+            border-radius: 6px;
+            color: #666;
+            cursor: pointer;
+            font-size: 14px;
+            transition: all 0.2s ease;
+        }
+
+        .edit-cancel-btn:hover {
+            background: #f5f5f5;
+            border-color: #ccc;
+        }
+
+        .edit-send-btn {
+            padding: 4px 10px;
+            background: #6366f1;
+            border: none;
+            border-radius: 6px;
+            color: white;
+            cursor: pointer;
+            font-size: 14px;
+            transition: all 0.2s ease;
+        }
+
+        .edit-send-btn:hover {
+            background: #5b5cf6;
+        }
+
+        .edit-send-btn:disabled {
+            background: #ccc;
+            cursor: not-allowed;
+        }
+    </style>
+
+</head>
+
+<body>
+    <div class="glass-particles"></div>
+
+    <!-- 移动端侧边栏切换按钮 -->
+    <button class="sidebar-toggle" id="sidebar-toggle" style="display: none;">
+        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <path d="M3 12h18M3 6h18M3 18h18" stroke="currentColor" stroke-width="2" stroke-linecap="round"
+                stroke-linejoin="round" />
+        </svg>
+    </button>
+
+    <!-- 移动端侧边栏遮罩层 -->
+    <div class="sidebar-overlay" id="sidebar-overlay"></div>
+
+    <div class="chat-container">
+        <!-- 历史记录侧边栏 -->
+        <div class="history-sidebar" id="history-sidebar">
+            <div class="sidebar-header">
+                <button class="new-chat-btn" id="new-chat-btn">
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"
+                            stroke-linejoin="round" />
+                    </svg>
+                    开启新对话
+                </button>
+            </div>
+
+            <div class="history-content">
+                <!-- 历史记录将通过JavaScript动态加载 -->
+                <div class="history-loading" id="history-loading"
+                    style="display: flex; align-items: center; justify-content: center; padding: 20px; color: #666;">
+                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"
+                        style="margin-right: 8px; animation: spin 1s linear infinite;">
+                        <path d="M21 12a9 9 0 11-6.219-8.56" stroke="currentColor" stroke-width="2"
+                            stroke-linecap="round" stroke-linejoin="round" />
+                    </svg>
+                    正在加载历史记录...
+                </div>
+                <div class="history-empty" id="history-empty"
+                    style="display: none; text-align: center; padding: 40px 20px; color: #999;">
+                    <svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"
+                        style="margin-bottom: 16px; opacity: 0.5;">
+                        <path d="M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 012 2v14a2 2 0 01-2 2H5a2 2 0 01-2-2V6a2 2 0 012-2z"
+                            stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
+                    </svg>
+                    <div style="font-size: 16px; margin-bottom: 8px;">暂无历史记录</div>
+                    <div style="font-size: 14px; opacity: 0.7;">开始新的对话吧!</div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 主聊天区域 -->
+        <div class="main-chat-area">
+            <header class="glass-header">
+                <div class="logo-title-container">
+                    <img src="{{ url_for('static', filename='logo.png') }}" alt="聚德AI短视频脚本撰写" class="header-logo">
+                    <h1>聚德策划大师-大健康AI文案撰写<span class="version-tag">v 1.6.3</span></h1>
+                    <a class="logo-title-link" href="https://wj.qq.com/s2/23610733/1a16/" target="_blank"
+                        class="jude-link">问题反馈</a>
+                </div>
+                <div class="buttons-container">
+                    <!-- 智能体选择(仅智能策划大师) -->
+                    <div id="agent-dropdown" class="glass-btn" style="position: relative;">
+                        <img id="agent-trigger-icon" src="{{ url_for('static', filename='img/planning.png') }}"
+                            alt="平台图标" width="16" height="16" onerror="this.style.display='none'">
+                        <span id="agent-trigger-text">智能策划大师</span>
+                        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                            <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round"
+                                stroke-linejoin="round" />
+                        </svg>
+                        <div id="agent-menu" class="glass-panel"
+                            style="display:none; position: absolute; top: calc(100% + 6px); left: 0; z-index: 1001; min-width: 220px;">
+                            <ul style="list-style: none; margin: 0; padding: 6px;">
+                                <li class="agent-option" data-agent="planning"
+                                    style="display:flex; align-items:center; gap:8px; padding:12px; border-radius:8px; cursor:pointer; transition: all 0.2s ease; user-select: none;"
+                                    onmouseover="this.style.background='rgba(99, 102, 241, 0.1)'"
+                                    onmouseout="this.style.background=''">
+                                    <img src="{{ url_for('static', filename='img/planning.png') }}" alt="智能策划大师"
+                                        width="16" height="16" onerror="this.style.display='none'">
+                                    <div style="display:flex; flex-direction:column;">
+                                        <span style="font-weight: 500;">智能策划大师</span>
+                                    </div>
+                                </li>
+                                <li class="agent-option" data-agent="videoaccount"
+                                    style="display:flex; align-items:center; gap:8px; padding:12px; border-radius:8px; cursor:pointer; transition: all 0.2s ease; user-select: none;"
+                                    onmouseover="this.style.background='rgba(99, 102, 241, 0.1)'"
+                                    onmouseout="this.style.background=''">
+                                    <img src="{{ url_for('static', filename='img/videoaccount.svg') }}" alt="病理分析"
+                                        width="16" height="16" onerror="this.style.display='none'">
+                                    <div style="display:flex; flex-direction:column;">
+                                        <span style="font-weight: 500;">病理分析</span>
+                                    </div>
+                                </li>
+
+                                <!-- 投放诊断 -->
+                                <li class="agent-option" data-agent="diagnosis"
+                                    style="display:flex; align-items:center; gap:8px; padding:12px; border-radius:8px; cursor:pointer; transition: all 0.2s ease; user-select: none;"
+                                    onmouseover="this.style.background='rgba(99, 102, 241, 0.1)'"
+                                    onmouseout="this.style.background=''">
+                                    <img src="{{ url_for('static', filename='img/diagnosis.svg') }}" alt="投放诊断"
+                                        width="16" height="16" onerror="this.style.display='none'">
+                                    <div style="display:flex; flex-direction:column;">
+                                        <span style="font-weight: 500;">投放诊断</span>
+                                    </div>
+                                </li>
+
+                            </ul>
+                        </div>
+                    </div>
+                    <button id="reset-btn" class="btn glass-btn">
+                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                            <path
+                                d="M4 4V9H4.58152M19.9381 11C19.446 7.05369 16.0796 4 12 4C8.64262 4 5.76829 6.06817 4.58152 9M4.58152 9H9M20 20V15H19.4185M19.4185 15C18.2317 17.9318 15.3574 20 12 20C7.92038 20 4.55399 16.9463 4.06189 13M19.4185 15H15"
+                                stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
+                        </svg>
+                        重置对话
+                    </button>
+                    <!-- 用户信息 -->
+                    <div class="user-info" id="user-info" style="display: none;">
+                        <div class="user-details">
+                            <div class="user-avatar" id="user-avatar">
+                                <img id="avatar-img" src="" alt="用户头像" onerror="this.style.display='none'">
+                                <div id="avatar-fallback" class="avatar-fallback"></div>
+                            </div>
+                            <div class="user-text">
+                                <div class="user-info-row">
+                                    <span class="user-name" id="user-name"></span>
+                                    <span class="user-type" id="user-type"></span>
+                                </div>
+                                <div class="user-info-row">
+                                    <span class="user-title" id="user-title" style="display: none;"></span>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="user-dropdown" id="user-dropdown" style="display: none;">
+                            <div class="user-dropdown-content">
+                                <button id="logout-btn" class="btn glass-btn logout-btn">
+                                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
+                                        xmlns="http://www.w3.org/2000/svg">
+                                        <path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 3l4 4-4 4M21 7H9"
+                                            stroke="currentColor" stroke-width="2" stroke-linecap="round"
+                                            stroke-linejoin="round" />
+                                    </svg>
+                                    退出登录
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </header>
+
+
+            <!-- 移动端菜单切换按钮 -->
+            <div class="chat-main-content">
+                <div id="chat-messages" class="chat-messages glass-panel">
+                    <div class="message bot">
+                        <div class="message-content">请发送你的投放数据。 如果是视频诊断,目前仅能诊断文案,请去聚德神转转为文案(最好是带时间戳的字幕-聚德智能视频处理工具可导出)。
+                            <ul>
+                                <!-- <li>请输入想要分析的疾病或者产品名称</li> -->
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+                <div class="chat-input-container glass-panel">
+                    <textarea id="user-input" placeholder="请输入需要撰写的品类名和要求..." rows="1"></textarea>
+                    <button id="send-btn" class="glass-btn-primary" aria-label="发送消息">
+                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                            <path d="M22 2L11 13M22 2L15 22L11 13M11 13L2 9L22 2" stroke="currentColor" stroke-width="2"
+                                stroke-linecap="round" stroke-linejoin="round" />
+                        </svg>
+                    </button>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <!-- 核心JS库 -->
+    <script src="{{ url_for('static', filename='js/highlight.min.js') }}"></script>
+    <script src="{{ url_for('static', filename='js/marked.min.js') }}"></script>
+    <script src="{{ url_for('static', filename='js/showdown.min.js') }}"></script>
+    <script src="{{ url_for('static', filename='js/copy-buttons.js') }}"></script>
+    <script src="{{ url_for('static', filename='js/chat.js') }}"></script>
+
+    <!-- 认证和用户信息管理 -->
+    <!-- 自定义弹框 -->
+    <!-- 删除确认弹框 -->
+    <div id="delete-modal" class="custom-modal">
+        <div class="modal-overlay"></div>
+        <div class="modal-content">
+            <div class="modal-header">
+                <h3 class="modal-title">删除对话</h3>
+                <button class="modal-close" onclick="closeModal('delete-modal')">
+                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"
+                            stroke-linejoin="round" />
+                    </svg>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="modal-icon delete-icon">
+                    <svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"
+                            stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
+                        <path d="M10 11v6M14 11v6" stroke="currentColor" stroke-width="2" stroke-linecap="round"
+                            stroke-linejoin="round" />
+                    </svg>
+                </div>
+                <p class="modal-message">确定要删除这个对话吗?删除后将无法恢复。</p>
+            </div>
+            <div class="modal-footer">
+                <button class="btn btn-secondary" onclick="closeModal('delete-modal')">取消</button>
+                <button class="btn btn-danger" id="confirm-delete-btn">删除</button>
+            </div>
+        </div>
+    </div>
+
+    <!-- 重命名弹框 -->
+    <div id="rename-modal" class="custom-modal">
+        <div class="modal-overlay"></div>
+        <div class="modal-content">
+            <div class="modal-header">
+                <h3 class="modal-title">重命名对话</h3>
+                <button class="modal-close" onclick="closeModal('rename-modal')">
+                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"
+                            stroke-linejoin="round" />
+                    </svg>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <input type="text" id="rename-input" class="form-input" placeholder="请输入新的对话标题" maxlength="50">
+                    <div class="form-hint">最多50个字符</div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button class="btn btn-secondary" onclick="closeModal('rename-modal')">取消</button>
+                <button class="btn btn-primary" id="confirm-rename-btn">保存</button>
+            </div>
+        </div>
+    </div>
+
+    <!-- 消息提示弹框 -->
+    <div id="message-modal" class="custom-modal">
+        <div class="modal-overlay"></div>
+        <div class="modal-content message-modal-content">
+            <div class="modal-body">
+                <div class="modal-icon message-icon">
+                    <svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M9 12l2 2 4-4M21 12a9 9 0 11-18 0 9 9 0 0118 0z" stroke="currentColor" stroke-width="2"
+                            stroke-linecap="round" stroke-linejoin="round" />
+                    </svg>
+                </div>
+                <p class="modal-message" id="message-text">操作成功</p>
+            </div>
+            <div class="modal-footer">
+                <button class="btn btn-primary" onclick="closeModal('message-modal')">确定</button>
+            </div>
+        </div>
+    </div>
+
+
+    <script>
+        // 弹框控制函数
+        function showModal(modalId) {
+            const modal = document.getElementById(modalId);
+            if (modal) {
+                modal.classList.add('show');
+                document.body.style.overflow = 'hidden'; // 防止背景滚动
+            }
+        }
+
+        function closeModal(modalId) {
+            const modal = document.getElementById(modalId);
+            if (modal) {
+                modal.classList.remove('show');
+                document.body.style.overflow = ''; // 恢复滚动
+            }
+        }
+
+        function showMessage(message, type = 'success') {
+            const messageText = document.getElementById('message-text');
+            const messageIcon = document.querySelector('#message-modal .modal-icon');
+
+            if (messageText) {
+                messageText.textContent = message;
+            }
+
+            if (messageIcon) {
+                // 根据类型设置图标颜色
+                messageIcon.className = 'modal-icon message-icon';
+                if (type === 'error') {
+                    messageIcon.style.color = '#ef4444';
+                } else if (type === 'warning') {
+                    messageIcon.style.color = '#f59e0b';
+                } else {
+                    messageIcon.style.color = '#10b981';
+                }
+            }
+
+            showModal('message-modal');
+        }
+
+        // 点击遮罩层关闭弹框
+        document.addEventListener('click', function (e) {
+            if (e.target.classList.contains('modal-overlay')) {
+                const modal = e.target.closest('.custom-modal');
+                if (modal) {
+                    closeModal(modal.id);
+                }
+            }
+        });
+
+        // ESC键关闭弹框
+        document.addEventListener('keydown', function (e) {
+            if (e.key === 'Escape') {
+                const openModal = document.querySelector('.custom-modal.show');
+                if (openModal) {
+                    closeModal(openModal.id);
+                }
+            }
+        });
+
+        // 防重复重定向标志
+        let isRedirecting = false;
+
+        // 页面加载时检查认证状态
+        document.addEventListener('DOMContentLoaded', function () {
+            checkAuthStatus();
+        });
+
+        // 思考过程折叠/展开函数
+        function toggleThought(button) {
+            const container = button.closest('.thought-container');
+            const content = container.querySelector('.thought-content');
+            const icon = button.querySelector('svg path');
+
+            if (container.dataset.collapsed === 'true') {
+                // 展开
+                container.dataset.collapsed = 'false';
+                content.style.maxHeight = '500px';
+                content.style.padding = '10px 0';
+                icon.setAttribute('d', 'M6 9l6 6 6-6');
+            } else {
+                // 折叠
+                container.dataset.collapsed = 'true';
+                content.style.maxHeight = '0';
+                content.style.padding = '0';
+                icon.setAttribute('d', 'M18 15l-6-6-6 6');
+            }
+        }
+
+        function checkAuthStatus() {
+            // 检查URL参数中是否有登录成功信息
+            const urlParams = new URLSearchParams(window.location.search);
+            const loginSuccess = urlParams.get('login_success');
+            const token = urlParams.get('token');
+            const userInfoJson = urlParams.get('user_info');
+
+            if (loginSuccess && token && userInfoJson) {
+                // 登录成功,处理用户信息
+                try {
+                    const userInfo = JSON.parse(decodeURIComponent(userInfoJson));
+
+                    // 保存到localStorage
+                    localStorage.setItem('auth_token', token);
+                    localStorage.setItem('user_info', JSON.stringify(userInfo));
+
+                    // 显示用户信息
+                    displayUserInfo(userInfo);
+                    showUserSection();
+                    addAuthHeader(token);
+
+                    // 清理URL参数
+                    window.history.replaceState({}, document.title, window.location.pathname);
+
+                    // 初始化历史记录侧边栏
+                    initializeHistorySidebar();
+
+                    // 启动对话状态更新器
+                    if (typeof startConversationStatusUpdater === 'function') {
+                        startConversationStatusUpdater();
+                    }
+
+                } catch (e) {
+                    redirectToLogin();
+                }
+            } else {
+                // 正常检查localStorage中的认证信息
+                const storedToken = localStorage.getItem('auth_token');
+                const storedUserInfo = localStorage.getItem('user_info');
+
+                if (storedToken && storedUserInfo) {
+                    try {
+                        const user = JSON.parse(storedUserInfo);
+
+                        // 验证用户信息的基本字段
+                        if (!user.name && !user.nickname) {
+                            user.name = '钉钉用户';
+                            user.nickname = '钉钉用户';
+                        }
+
+                        displayUserInfo(user);
+                        showUserSection();
+                        addAuthHeader(storedToken);
+
+                        // 初始化历史记录侧边栏
+                        initializeHistorySidebar();
+
+                        // 启动对话状态更新器
+                        if (typeof startConversationStatusUpdater === 'function') {
+                            startConversationStatusUpdater();
+                        }
+
+                    } catch (e) {
+                        redirectToLogin();
+                    }
+                } else {
+                    redirectToLogin();
+                }
+            }
+        }
+
+        function displayUserInfo(user) {
+            // 显示用户头像
+            const avatarImg = document.getElementById('avatar-img');
+            const avatarFallback = document.getElementById('avatar-fallback');
+
+            if (user.avatar && user.avatar.trim() !== '') {
+                avatarImg.src = user.avatar;
+                avatarImg.style.display = 'block';
+                avatarFallback.style.display = 'none';
+            } else {
+                avatarImg.style.display = 'none';
+                avatarFallback.style.display = 'block';
+                // 获取用户名的首字母作为头像
+                const displayName = user.nickname || user.name || 'U';
+                avatarFallback.textContent = displayName.charAt(0).toUpperCase();
+            }
+
+            // 显示用户信息
+            const userName = document.getElementById('user-name');
+            if (userName) {
+                const displayName = user.nickname || user.name || '钉钉用户';
+                userName.textContent = displayName;
+            }
+
+            const userType = document.getElementById('user-type');
+            if (userType) {
+                // 根据登录类型显示不同的标签样式
+                if (user.login_type === 'dingtalk') {
+                    userType.textContent = '钉钉';
+                    userType.style.background = 'rgba(24, 144, 255, 0.2)';
+                    userType.style.color = '#1890ff';
+                    userType.style.border = '1px solid rgba(24, 144, 255, 0.3)';
+                } else {
+                    userType.textContent = user.login_type || '钉钉';
+                    userType.style.background = 'rgba(255, 255, 255, 0.3)';
+                    userType.style.color = '#666';
+                    userType.style.border = '1px solid rgba(255, 255, 255, 0.2)';
+                }
+            }
+
+            const userTitle = document.getElementById('user-title');
+            if (userTitle && user.title) {
+                userTitle.textContent = user.title;
+                userTitle.style.display = 'block';
+            }
+
+            // 显示其他用户信息
+            if (user.email) {
+                const emailItem = document.getElementById('user-email');
+                if (emailItem) {
+                    emailItem.querySelector('.info-value').textContent = user.email;
+                    emailItem.style.display = 'block';
+                }
+            }
+
+            if (user.mobile) {
+                const mobileItem = document.getElementById('user-mobile');
+                if (mobileItem) {
+                    mobileItem.querySelector('.info-value').textContent = user.mobile;
+                    mobileItem.style.display = 'block';
+                }
+            }
+
+            if (user.job_number) {
+                const jobItem = document.getElementById('user-job');
+                if (jobItem) {
+                    jobItem.querySelector('.info-value').textContent = user.job_number;
+                    jobItem.style.display = 'block';
+                }
+            }
+
+            if (user.dept_name) {
+                const deptItem = document.getElementById('user-dept');
+                if (deptItem) {
+                    deptItem.querySelector('.info-value').textContent = user.dept_name;
+                    deptItem.style.display = 'block';
+                }
+            }
+        }
+
+        function showUserSection() {
+            const userInfo = document.getElementById('user-info');
+            if (userInfo) userInfo.style.display = 'flex';
+        }
+
+        function addAuthHeader(token) {
+            // 注意:addAuthHeader函数由chat.js提供,这里不需要重复定义
+            // 如果需要设置认证头,直接调用chat.js中的函数
+            if (typeof window.addAuthHeader === 'function') {
+                window.addAuthHeader(token);
+            }
+        }
+
+        function redirectToLogin() {
+            if (isRedirecting) {
+                return;
+            }
+            isRedirecting = true;
+            localStorage.removeItem('auth_token');
+            localStorage.removeItem('user_info');
+            // 添加延迟,避免频繁重定向
+            setTimeout(() => {
+                window.location.href = '/login';
+            }, 100);
+        }
+
+        // 退出登录
+        document.getElementById('logout-btn').addEventListener('click', function () {
+            fetch('/api/auth/logout', {
+                method: 'POST',
+                headers: {
+                    'Authorization': `Bearer ${localStorage.getItem('auth_token')}`,
+                    'Content-Type': 'application/json'
+                }
+            }).then(() => {
+                redirectToLogin();
+            }).catch(() => {
+                redirectToLogin();
+            });
+        });
+
+        // 用户信息下拉菜单(修复:添加鼠标进入/离开事件,防止移向退出登录按钮时菜单消失)
+        function initializeUserDropdown() {
+            const userInfo = document.getElementById('user-info');
+            const dropdown = document.getElementById('user-dropdown');
+
+            if (!userInfo || !dropdown) {
+                return;
+            }
+
+            // 初始化时确保下拉菜单是隐藏状态(覆盖CSS的:hover效果)
+            dropdown.style.display = 'none';
+            dropdown.style.opacity = '0';
+            dropdown.style.visibility = 'hidden';
+            dropdown.style.transform = 'translateY(-15px) scale(0.95)';
+
+            let isHovering = false;
+            let hideTimer = null;
+
+            function showDropdown() {
+                if (hideTimer) {
+                    clearTimeout(hideTimer);
+                    hideTimer = null;
+                }
+                dropdown.style.display = 'block';
+                dropdown.style.opacity = '1';
+                dropdown.style.visibility = 'visible';
+                dropdown.style.transform = 'translateY(0) scale(1)';
+            }
+
+            function scheduleHide() {
+                if (hideTimer) {
+                    clearTimeout(hideTimer);
+                }
+                hideTimer = setTimeout(() => {
+                    if (!isHovering) {
+                        dropdown.style.display = 'none';
+                        dropdown.style.opacity = '0';
+                        dropdown.style.visibility = 'hidden';
+                        dropdown.style.transform = 'translateY(-15px) scale(0.95)';
+                    }
+                }, 150); // 150ms延迟,给用户足够时间移动到按钮
+            }
+
+            // 鼠标进入用户信息区域
+            userInfo.addEventListener('mouseenter', function () {
+                isHovering = true;
+                showDropdown();
+            });
+
+            // 鼠标离开用户信息区域
+            userInfo.addEventListener('mouseleave', function () {
+                isHovering = false;
+                scheduleHide();
+            });
+
+            // 鼠标进入下拉菜单区域(重要:防止鼠标移动到按钮时菜单消失)
+            dropdown.addEventListener('mouseenter', function () {
+                isHovering = true;
+                showDropdown();
+            });
+
+            // 鼠标离开下拉菜单区域
+            dropdown.addEventListener('mouseleave', function () {
+                isHovering = false;
+                scheduleHide();
+            });
+
+            // 保留点击事件用于移动端
+            document.getElementById('user-avatar').addEventListener('click', function (e) {
+                // 在桌面端,如果已经在悬停显示状态,点击时不处理(避免冲突)
+                if (window.innerWidth > 768 && dropdown.style.display === 'block') {
+                    return;
+                }
+                // 移动端切换显示/隐藏
+                if (dropdown.style.display === 'none' || dropdown.style.display === '') {
+                    showDropdown();
+                } else {
+                    dropdown.style.display = 'none';
+                    dropdown.style.opacity = '0';
+                    dropdown.style.visibility = 'hidden';
+                    dropdown.style.transform = 'translateY(-15px) scale(0.95)';
+                }
+            });
+        }
+
+        // 初始化用户下拉菜单
+        document.addEventListener('DOMContentLoaded', function () {
+            setTimeout(() => {
+                initializeUserDropdown();
+            }, 100);
+        });
+
+        // 智能体选择(悬停展开)
+        document.addEventListener('DOMContentLoaded', function () {
+            setTimeout(() => {
+                initializeAgentHoverDropdown();
+            }, 100);
+
+            // 监听窗口大小变化,重新初始化智能体菜单
+            let resizeTimer;
+            window.addEventListener('resize', function () {
+                clearTimeout(resizeTimer);
+                resizeTimer = setTimeout(() => {
+                    initializeAgentHoverDropdown();
+                }, 250);
+            });
+        });
+
+        function initializeAgentHoverDropdown() {
+            const dropdown = document.getElementById('agent-dropdown');
+            const menu = document.getElementById('agent-menu');
+            const triggerText = document.getElementById('agent-trigger-text');
+            const triggerIcon = document.getElementById('agent-trigger-icon');
+            if (!dropdown || !menu) return;
+
+            const agentMeta = {
+                'planning': {
+                    name: '智能策划大师',
+                    icon: "{{ url_for('static', filename='img/planning.png') }}"
+                },
+                'videoaccount': {
+                    name: '病理分析',
+                    icon: "{{ url_for('static', filename='img/videoaccount.svg') }}"
+                },
+                'diagnosis': {
+                    name: '投放诊断',
+                    icon: "{{ url_for('static', filename='img/diagnosis.svg') }}"
+                },
+            };
+
+            // 兼容历史存储:如果之前存的是已删除的智能体,强制切回智能策划大师
+            const storedAgent = localStorage.getItem('current_agent');
+            let currentAgent = 'planning';
+            if (!storedAgent || !agentMeta[storedAgent]) {
+                // 没有存过,或存的是已不存在的智能体 -> 统一重置为智能策划大师
+                localStorage.setItem('current_agent', 'planning');
+            } else {
+                currentAgent = storedAgent;
+            }
+
+            // 初始化触发器显示
+            triggerText.textContent = agentMeta[currentAgent].name;
+            if (triggerIcon) {
+                triggerIcon.src = agentMeta[currentAgent].icon;
+                triggerIcon.style.display = 'block';
+            }
+
+            // 高亮选中项(仅影响下拉菜单项,不改全局样式)
+            function clearActiveStyles() {
+                menu.querySelectorAll('.agent-option').forEach(item => {
+                    item.removeAttribute('data-active');
+                    item.style.background = '';
+                    item.style.border = '';
+                    item.style.opacity = '';
+                    item.style.pointerEvents = '';
+                });
+            }
+            function setActiveStyles(agentType) {
+                clearActiveStyles();
+                const activeEl = menu.querySelector(`.agent-option[data-agent="${agentType}"]`);
+                if (activeEl) {
+                    activeEl.setAttribute('data-active', 'true');
+                    activeEl.style.background = 'rgba(99, 102, 241, 0.12)';
+                    activeEl.style.border = '1px solid rgba(99, 102, 241, 0.35)';
+                    activeEl.style.opacity = '1';
+                    // 禁止重复点击当前激活项
+                    activeEl.style.pointerEvents = 'none';
+                }
+            }
+            setActiveStyles(currentAgent);
+
+            // 检测是否为移动设备
+            const isMobile = window.innerWidth <= 768;
+
+            if (isMobile) {
+                // 移动端:点击显示/隐藏菜单
+                let isMenuOpen = false;
+
+                function showMenu() {
+                    menu.style.display = 'block';
+                    isMenuOpen = true;
+                    // 添加动画类
+                    setTimeout(() => {
+                        menu.classList.add('show');
+                    }, 10);
+                }
+
+                function hideMenu() {
+                    menu.classList.remove('show');
+                    setTimeout(() => {
+                        menu.style.display = 'none';
+                    }, 300);
+                    isMenuOpen = false;
+                }
+
+                // 点击触发器显示菜单
+                dropdown.addEventListener('click', function (e) {
+                    e.preventDefault();
+                    e.stopPropagation();
+                    if (isMenuOpen) {
+                        hideMenu();
+                    } else {
+                        showMenu();
+                    }
+                });
+
+                // 移除遮罩层点击事件
+
+                // 点击菜单外部隐藏菜单
+                document.addEventListener('click', function (e) {
+                    if (!dropdown.contains(e.target) && !menu.contains(e.target)) {
+                        hideMenu();
+                    }
+                });
+
+            } else {
+                // 桌面端:悬停显示/隐藏(避免从触发区移动到菜单区时闪烁隐藏)
+                let isHovering = false;
+                let hideTimer = null;
+
+                function showMenu() {
+                    if (hideTimer) {
+                        clearTimeout(hideTimer);
+                        hideTimer = null;
+                    }
+                    menu.style.display = 'block';
+                }
+
+                function scheduleHide() {
+                    if (hideTimer) {
+                        clearTimeout(hideTimer);
+                    }
+                    hideTimer = setTimeout(() => {
+                        if (!isHovering) {
+                            menu.style.display = 'none';
+                        }
+                    }, 120);
+                }
+
+                dropdown.addEventListener('mouseenter', function () {
+                    isHovering = true;
+                    showMenu();
+                });
+                dropdown.addEventListener('mouseleave', function () {
+                    isHovering = false;
+                    scheduleHide();
+                });
+
+                menu.addEventListener('mouseenter', function () {
+                    isHovering = true;
+                    showMenu();
+                });
+                menu.addEventListener('mouseleave', function () {
+                    isHovering = false;
+                    scheduleHide();
+                });
+            }
+
+            menu.querySelectorAll('.agent-option').forEach((item, index) => {
+
+
+                item.addEventListener('click', function (e) {
+                    e.preventDefault();
+                    e.stopPropagation();
+
+                    const agentType = this.getAttribute('data-agent');
+                    const current = localStorage.getItem('current_agent') || 'planning';
+
+                    // 已是当前激活则不处理
+                    if (agentType === current) {
+                        return;
+                    }
+
+                    // 检查是否已有对话(不是新对话)且有实际消息内容
+                    const conversationId = window.currentConversationId;
+                    const hasExistingConversation = conversationId &&
+                        conversationId !== 'new' &&
+                        conversationId !== null &&
+                        conversationId !== undefined;
+
+                    // 检查当前对话是否有实际的消息内容(不是空的新对话)
+                    const hasActualMessages = checkConversationHasMessages(conversationId);
+
+                    if (hasExistingConversation && hasActualMessages) {
+                        // 先关闭菜单,避免遮挡对话框
+                        if (isMobile) {
+                            hideMenu();
+                        } else {
+                            isHovering = false;
+                            scheduleHide();
+                        }
+
+                        // 延迟一下再弹出对话框,确保菜单已关闭
+                        setTimeout(() => {
+                            // 弹出确认对话框
+                            showAgentSwitchConfirmDialog(agentType, current, function (confirmed) {
+                                if (confirmed) {
+                                    // 用户确认,创建新对话并切换智能体
+                                    switchAgentWithNewConversation(agentType);
+                                    setActiveStyles(agentType);
+                                    // 更新触发器
+                                    triggerText.textContent = agentMeta[agentType].name;
+                                    if (triggerIcon) {
+                                        triggerIcon.src = agentMeta[agentType].icon;
+                                        triggerIcon.style.display = 'block';
+                                    }
+                                } else {
+                                    // 用户取消,不切换智能体,保持当前智能体
+                                    // 不需要做任何操作
+                                }
+                            });
+                        }, 100);
+                    } else {
+                        // 新对话或空对话(没有实际消息),直接切换智能体,无需确认
+                        switchAgent(agentType);
+                        setActiveStyles(agentType);
+                        // 更新触发器
+                        triggerText.textContent = agentMeta[agentType].name;
+                        if (triggerIcon) {
+                            triggerIcon.src = agentMeta[agentType].icon;
+                            triggerIcon.style.display = 'block';
+                        }
+                        // 移动端点击后隐藏菜单
+                        if (isMobile) {
+                            hideMenu();
+                        } else {
+                            isHovering = false;
+                            scheduleHide();
+                        }
+                    }
+                });
+
+                // 添加触摸事件支持(移动端)
+                item.addEventListener('touchend', function (e) {
+                    e.preventDefault();
+                    this.click(); // 触发点击事件
+                });
+            });
+        }
+
+        // 检查对话是否有实际的消息内容
+        function checkConversationHasMessages(conversationId) {
+            if (!conversationId || conversationId === 'new') {
+                return false;
+            }
+
+            // 方法1: 检查聊天消息区域中是否有用户消息
+            const chatMessages = document.getElementById('chat-messages');
+            if (chatMessages) {
+                const userMessages = chatMessages.querySelectorAll('.message.user');
+                // 如果有用户消息,说明对话有实际内容
+                if (userMessages.length > 0) {
+                    return true;
+                }
+            }
+
+            // 方法2: 检查侧边栏中当前对话的消息数量
+            const conversationItem = document.querySelector(`[data-conversation-id="${conversationId}"]`);
+            if (conversationItem) {
+                const messageCountElement = conversationItem.querySelector('.message-count');
+                if (messageCountElement) {
+                    const messageCount = parseInt(messageCountElement.textContent) || 0;
+                    // 如果消息数量大于0,说明对话有实际内容
+                    if (messageCount > 0) {
+                        return true;
+                    }
+                }
+            }
+
+            // 方法3: 检查是否有正在进行的流式响应(说明用户正在发送消息)
+            if (window.conversationStates && window.conversationStates.has(conversationId)) {
+                const state = window.conversationStates.get(conversationId);
+                if (state && (state.isStreaming || (state.userMessages && state.userMessages.length > 0))) {
+                    return true;
+                }
+            }
+
+            // 如果以上都没有,说明是空的新对话
+            return false;
+        }
+
+        function switchAgent(agentType) {
+            const current = localStorage.getItem('current_agent') || 'planning';
+            if (current === agentType) {
+                return;
+            }
+
+
+            // 保存到本地存储
+            localStorage.setItem('current_agent', agentType);
+
+            // 更新状态提示(如有)
+            updateStatusIndicator(agentType);
+
+            // 显示切换提示
+            showAgentSwitchNotification(agentType);
+        }
+
+        // 切换智能体并创建新对话
+        function switchAgentWithNewConversation(agentType) {
+            const current = localStorage.getItem('current_agent') || 'planning';
+            if (current === agentType) {
+                return;
+            }
+
+            // 保存到本地存储
+            localStorage.setItem('current_agent', agentType);
+
+            // 更新状态提示(如有)
+            updateStatusIndicator(agentType);
+
+            // 创建新对话
+            if (typeof createNewConversation === 'function') {
+                createNewConversation();
+            }
+
+            // 显示切换提示
+            showAgentSwitchNotification(agentType);
+        }
+
+        // 显示智能体切换确认对话框
+        function showAgentSwitchConfirmDialog(newAgentType, currentAgentType, callback) {
+            // 防止重复弹出对话框
+            if (document.querySelector('.agent-switch-dialog-overlay')) {
+                return;
+            }
+
+            const agentNames = {
+                'planning': '智能策划大师',
+                'videoaccount': '病理分析',
+                'diagnosis': '投放诊断',
+                'kuaishou': '快手',
+                'toutiao': '头条'
+            };
+
+            const newAgentName = agentNames[newAgentType] || '新智能体';
+            const currentAgentName = agentNames[currentAgentType] || '当前智能体';
+
+            // 创建对话框遮罩层
+            const overlay = document.createElement('div');
+            overlay.className = 'agent-switch-dialog-overlay';
+            overlay.style.cssText = `
+                position: fixed;
+                top: 0;
+                left: 0;
+                width: 100%;
+                height: 100%;
+                background: rgba(0, 0, 0, 0.5);
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                z-index: 10000;
+                animation: fadeIn 0.2s ease;
+            `;
+
+            // 创建对话框
+            const dialog = document.createElement('div');
+            dialog.className = 'agent-switch-dialog';
+            dialog.style.cssText = `
+                background: white;
+                border-radius: 12px;
+                padding: 24px;
+                max-width: 400px;
+                width: 90%;
+                box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
+                animation: slideUp 0.3s ease;
+            `;
+
+            dialog.innerHTML = `
+                <div style="margin-bottom: 20px;">
+                    <div style="font-size: 18px; font-weight: 600; color: #333; margin-bottom: 12px;">
+                        切换智能体
+                    </div>
+                    <div style="font-size: 14px; color: #666; line-height: 1.6;">
+                        切换将新建对话,是否继续?
+                    </div>
+                </div>
+                <div style="display: flex; gap: 12px; justify-content: flex-end;">
+                    <button class="dialog-btn-cancel" style="
+                        padding: 10px 20px;
+                        border: 1px solid #ddd;
+                        border-radius: 6px;
+                        background: white;
+                        color: #666;
+                        cursor: pointer;
+                        font-size: 14px;
+                        transition: all 0.2s;
+                    ">否</button>
+                    <button class="dialog-btn-confirm" style="
+                        padding: 10px 20px;
+                        border: none;
+                        border-radius: 6px;
+                        background: #007bff;
+                        color: white;
+                        cursor: pointer;
+                        font-size: 14px;
+                        transition: all 0.2s;
+                    ">继续</button>
+                </div>
+            `;
+
+            // 添加动画样式
+            if (!document.getElementById('agent-switch-dialog-styles')) {
+                const style = document.createElement('style');
+                style.id = 'agent-switch-dialog-styles';
+                style.textContent = `
+                    @keyframes fadeIn {
+                        from { opacity: 0; }
+                        to { opacity: 1; }
+                    }
+                    @keyframes fadeOut {
+                        from { opacity: 1; }
+                        to { opacity: 0; }
+                    }
+                    @keyframes slideUp {
+                        from { 
+                            opacity: 0;
+                            transform: translateY(20px);
+                        }
+                        to { 
+                            opacity: 1;
+                            transform: translateY(0);
+                        }
+                    }
+                    @keyframes slideDown {
+                        from { 
+                            opacity: 1;
+                            transform: translateY(0);
+                        }
+                        to { 
+                            opacity: 0;
+                            transform: translateY(20px);
+                        }
+                    }
+                    .agent-switch-dialog-overlay {
+                        backdrop-filter: blur(2px);
+                    }
+                    .dialog-btn-cancel:hover {
+                        background: #f5f5f5 !important;
+                        border-color: #bbb !important;
+                    }
+                    .dialog-btn-confirm:hover {
+                        background: #0056b3 !important;
+                    }
+                    .dialog-btn-cancel:active,
+                    .dialog-btn-confirm:active {
+                        transform: scale(0.98);
+                    }
+                `;
+                document.head.appendChild(style);
+            }
+
+            overlay.appendChild(dialog);
+            document.body.appendChild(overlay);
+
+            // 绑定按钮事件
+            const cancelBtn = dialog.querySelector('.dialog-btn-cancel');
+            const confirmBtn = dialog.querySelector('.dialog-btn-confirm');
+
+            const closeDialog = (result) => {
+                overlay.style.animation = 'fadeOut 0.2s ease';
+                dialog.style.animation = 'slideDown 0.2s ease';
+                setTimeout(() => {
+                    overlay.remove();
+                    callback(result);
+                }, 200);
+            };
+
+            cancelBtn.addEventListener('click', () => closeDialog(false));
+            confirmBtn.addEventListener('click', () => closeDialog(true));
+
+            // 点击遮罩层关闭(取消)
+            overlay.addEventListener('click', (e) => {
+                if (e.target === overlay) {
+                    closeDialog(false);
+                }
+            });
+
+            // ESC键关闭(取消)
+            const handleEsc = (e) => {
+                if (e.key === 'Escape') {
+                    closeDialog(false);
+                    document.removeEventListener('keydown', handleEsc);
+                }
+            };
+            document.addEventListener('keydown', handleEsc);
+        }
+
+        function updateStatusIndicator(agentType) {
+            const agentNames = {
+                'planning': '智能策划大师',
+                'videoaccount': '病理分析',
+                'diagnosis': '投放诊断',
+                'kuaishou': '快手',
+                'toutiao': '头条'
+            };
+
+            const currentPlatformName = document.getElementById('current-platform-name');
+            if (currentPlatformName) {
+                currentPlatformName.textContent = agentNames[agentType] || '智能策划大师';
+            }
+        }
+
+        function showAgentSwitchNotification(agentType) {
+            const agentNames = {
+                'planning': '智能策划大师',
+                'videoaccount': '病理分析',
+                'diagnosis': '投放诊断',
+                'kuaishou': '快手',
+                'toutiao': '头条'
+            };
+
+            const agentName = agentNames[agentType] || '智能策划大师';
+
+            // 创建通知元素
+            const notification = document.createElement('div');
+            notification.className = 'agent-switch-notification';
+            notification.setAttribute('data-platform', agentType);
+            notification.innerHTML = `
+                <div class="notification-content">
+                    <span class="notification-icon">✨</span>
+                    <span class="notification-text">已切换到 ${agentName}</span>
+                </div>
+            `;
+
+            // 添加到页面
+            document.body.appendChild(notification);
+
+            // 显示动画
+            setTimeout(() => {
+                notification.classList.add('show');
+            }, 100);
+
+            // 自动隐藏
+            setTimeout(() => {
+                notification.classList.remove('show');
+                setTimeout(() => {
+                    if (notification.parentNode) {
+                        notification.parentNode.removeChild(notification);
+                    }
+                }, 300);
+            }, 2000);
+        }
+
+        // 获取当前平台类型(供其他功能使用)
+        function getCurrentAgent() {
+            return localStorage.getItem('current_agent') || 'planning';
+        }
+
+        // 动态调整聊天消息区域高度
+        function adjustChatMessagesHeight() {
+            const userInput = document.getElementById('user-input');
+            const chatMessages = document.getElementById('chat-messages');
+
+            if (userInput && chatMessages) {
+                // 获取输入框的实际高度
+                const inputHeight = userInput.scrollHeight;
+                const minHeight = 44; // 最小高度
+                const maxHeight = 200; // 最大高度
+                const actualHeight = Math.max(minHeight, Math.min(inputHeight, maxHeight));
+
+                // 计算聊天消息区域应该的高度
+                const containerHeight = document.querySelector('.chat-container').offsetHeight;
+                const headerHeight = document.querySelector('header').offsetHeight;
+                const inputContainerPadding = 40; // 输入框容器的padding
+                const availableHeight = containerHeight - headerHeight - actualHeight - inputContainerPadding;
+
+                // 设置最小高度为100px,确保有足够的显示空间
+                const finalHeight = Math.max(availableHeight, 100);
+
+                chatMessages.style.height = `${finalHeight}px`;
+            }
+        }
+
+        // 滚动到聊天消息区域底部
+        function scrollToBottom() {
+            const chatMessages = document.getElementById('chat-messages');
+            if (chatMessages) {
+                chatMessages.scrollTop = chatMessages.scrollHeight;
+            }
+        }
+
+        // 平滑滚动到聊天消息区域底部
+        function smoothScrollToBottom() {
+            const chatMessages = document.getElementById('chat-messages');
+            if (chatMessages) {
+                chatMessages.scrollTo({
+                    top: chatMessages.scrollHeight,
+                    behavior: 'smooth'
+                });
+            }
+        }
+
+        // 监听输入框内容变化
+        document.addEventListener('DOMContentLoaded', function () {
+            const userInput = document.getElementById('user-input');
+            if (userInput) {
+                // 监听输入事件
+                userInput.addEventListener('input', adjustChatMessagesHeight);
+
+                // 监听键盘事件(回车等)
+                userInput.addEventListener('keydown', function (e) {
+                    if (e.key === 'Enter' && !e.shiftKey) {
+                        // 如果是回车键且没有按Shift,延迟调整高度
+                        setTimeout(adjustChatMessagesHeight, 100);
+                    }
+                });
+
+                // 监听窗口大小变化
+                window.addEventListener('resize', adjustChatMessagesHeight);
+
+                // 初始化时调整一次高度
+                setTimeout(adjustChatMessagesHeight, 100);
+            }
+
+            // 监听聊天消息区域的内容变化,自动滚动到底部
+            const chatMessages = document.getElementById('chat-messages');
+            if (chatMessages) {
+                // 创建观察器监听内容变化
+                const observer = new MutationObserver(function (mutations) {
+                    // 如果正在编辑消息,不自动滚动
+                    if (window.isEditingMessage) {
+                        return;
+                    }
+
+                    const distanceToBottom = chatMessages.scrollHeight - chatMessages.scrollTop - chatMessages.clientHeight;
+                    const threshold = 50;
+                    if (distanceToBottom > threshold) {
+                        return;
+                    }
+
+                    // 延迟滚动,确保内容完全渲染
+                    setTimeout(() => {
+                        scrollToBottom();
+                    }, 100);
+                });
+
+                // 监听子节点变化和属性变化
+                observer.observe(chatMessages, {
+                    childList: true,
+                    subtree: true,
+                    attributes: false
+                });
+
+                // 初始化时滚动到底部
+                setTimeout(scrollToBottom, 100);
+
+                // 添加双击快速滚动到底部的功能
+                let clickCount = 0;
+                let clickTimer = null;
+
+                chatMessages.addEventListener('click', function () {
+                    clickCount++;
+
+                    if (clickCount === 1) {
+                        clickTimer = setTimeout(() => {
+                            clickCount = 0;
+                        }, 300);
+                    } else if (clickCount === 2) {
+                        clearTimeout(clickTimer);
+                        clickCount = 0;
+                        smoothScrollToBottom();
+                    }
+                });
+            }
+        });
+
+        // 添加键盘快捷键:Ctrl+End 或 Cmd+End 快速滚动到底部
+        document.addEventListener('keydown', function (e) {
+            if ((e.ctrlKey || e.metaKey) && e.key === 'End') {
+                e.preventDefault();
+                smoothScrollToBottom();
+            }
+        });
+
+        // 历史记录侧边栏功能 - 移除自动初始化,由认证成功后调用
+
+        function initializeHistorySidebar() {
+            const sidebar = document.getElementById('history-sidebar');
+            const sidebarToggle = document.getElementById('sidebar-toggle');
+            const sidebarOverlay = document.getElementById('sidebar-overlay');
+            const newChatBtn = document.getElementById('new-chat-btn');
+            const conversationItems = document.querySelectorAll('.conversation-item');
+            const conversationMenus = document.querySelectorAll('.conversation-menu');
+
+            // 检测是否为移动设备
+            const isMobile = window.innerWidth <= 768;
+
+            if (isMobile) {
+                // 移动端显示切换按钮
+                if (sidebarToggle) {
+                    sidebarToggle.style.display = 'flex';
+                }
+
+                // 移动端侧边栏切换功能
+                if (sidebarToggle && sidebar && sidebarOverlay) {
+                    sidebarToggle.addEventListener('click', function () {
+                        sidebar.classList.add('show');
+                        sidebarOverlay.classList.add('show');
+                        document.body.style.overflow = 'hidden';
+                    });
+
+                    sidebarOverlay.addEventListener('click', function () {
+                        sidebar.classList.remove('show');
+                        sidebarOverlay.classList.remove('show');
+                        document.body.style.overflow = '';
+                    });
+                }
+            } else {
+                // 桌面端隐藏切换按钮
+                if (sidebarToggle) {
+                    sidebarToggle.style.display = 'none';
+                }
+            }
+
+            // 监听窗口大小变化
+            window.addEventListener('resize', function () {
+                const isMobileNow = window.innerWidth <= 768;
+
+                if (isMobileNow && !isMobile) {
+                    // 切换到移动端
+                    if (sidebarToggle) {
+                        sidebarToggle.style.display = 'flex';
+                    }
+                    if (sidebar && sidebarOverlay) {
+                        sidebar.classList.remove('show');
+                        sidebarOverlay.classList.remove('show');
+                        document.body.style.overflow = '';
+                    }
+                } else if (!isMobileNow && isMobile) {
+                    // 切换到桌面端
+                    if (sidebarToggle) {
+                        sidebarToggle.style.display = 'none';
+                    }
+                    if (sidebar && sidebarOverlay) {
+                        sidebar.classList.remove('show');
+                        sidebarOverlay.classList.remove('show');
+                        document.body.style.overflow = '';
+                    }
+                }
+            });
+
+            // 新对话按钮功能
+            if (newChatBtn) {
+                newChatBtn.addEventListener('click', function () {
+                    // 立即创建新对话
+                    createNewConversation();
+                });
+            }
+
+            // 对话项点击功能
+            conversationItems.forEach(item => {
+                item.addEventListener('click', function (e) {
+                    // 如果点击的是菜单按钮,不处理
+                    if (e.target.closest('.conversation-menu')) {
+                        return;
+                    }
+
+                    const conversationId = this.getAttribute('data-conversation-id');
+
+                    // 设置激活的对话
+                    if (typeof setActiveConversation === 'function') {
+                        setActiveConversation(conversationId);
+                    }
+
+                    loadConversation(conversationId);
+                    updateConversationSelection(conversationId);
+
+                    // 移动端关闭侧边栏
+                    if (isMobile && sidebar && sidebarOverlay) {
+                        sidebar.classList.remove('show');
+                        sidebarOverlay.classList.remove('show');
+                        document.body.style.overflow = '';
+                    }
+                });
+            });
+
+            // 对话菜单功能
+            conversationMenus.forEach(menu => {
+                menu.addEventListener('click', function (e) {
+                    e.stopPropagation();
+                    const conversationId = this.getAttribute('data-conversation-id');
+                    showConversationMenu(conversationId, this);
+                });
+            });
+
+            // 同步侧边栏用户信息
+            syncSidebarUserInfo();
+
+            // 加载历史记录
+            loadConversationHistory();
+        }
+
+        // 查找现有的空"新对话"
+        function findExistingEmptyConversation() {
+            const conversationItems = document.querySelectorAll('.conversation-item');
+            for (let item of conversationItems) {
+                const messageCountElement = item.querySelector('.message-count');
+                if (messageCountElement) {
+                    const messageCount = parseInt(messageCountElement.textContent);
+                    if (messageCount === 0) {
+                        return {
+                            conversationId: item.getAttribute('data-conversation-id'),
+                            sessionId: item.getAttribute('data-session-id'),
+                            element: item
+                        };
+                    }
+                }
+            }
+            return null;
+        }
+
+        // 切换到现有的空"新对话"
+        function switchToExistingEmptyConversation(conversation) {
+            // 更新当前对话ID和session_id
+            window.currentConversationId = conversation.conversationId;
+            window.currentSessionId = conversation.sessionId;
+
+            // 清除聊天区域并显示提示语
+            clearCurrentConversation();
+
+            // 更新侧边栏选中状态
+            updateConversationSelection(conversation.conversationId);
+
+            // 移动端关闭侧边栏
+            const isMobile = window.innerWidth <= 768;
+            if (isMobile) {
+                const sidebar = document.getElementById('history-sidebar');
+                const sidebarOverlay = document.getElementById('sidebar-overlay');
+                if (sidebar && sidebarOverlay) {
+                    sidebar.classList.remove('show');
+                    sidebarOverlay.classList.remove('show');
+                    document.body.style.overflow = '';
+                }
+            }
+
+            // 确保输入框状态正确
+            const userInput = document.getElementById('user-input');
+            const sendButton = document.getElementById('send-btn');
+            if (userInput && sendButton) {
+                userInput.disabled = false;
+                userInput.placeholder = '请输入需要撰写的品类名和要求...';
+                userInput.classList.remove('input-loading');
+                sendButton.disabled = false;
+                sendButton.classList.remove('stopping');
+            }
+        }
+
+        function createNewConversation() {
+            // 检查认证状态
+            const token = localStorage.getItem('auth_token');
+            if (!token) {
+                redirectToLogin();
+                return;
+            }
+
+            // 先检查是否已经存在一个空的"新对话"
+            const existingEmptyConversation = findExistingEmptyConversation();
+            if (existingEmptyConversation) {
+                // 如果存在空的"新对话",直接切换到该对话
+                switchToExistingEmptyConversation(existingEmptyConversation);
+                return;
+            }
+
+            // 显示加载状态
+            const newChatBtn = document.getElementById('new-chat-btn');
+            if (newChatBtn) {
+                const originalText = newChatBtn.innerHTML;
+                newChatBtn.innerHTML = `
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="animation: spin 1s linear infinite;">
+                        <path d="M21 12a9 9 0 11-6.219-8.56" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
+                    </svg>
+                    创建中...
+                `;
+                newChatBtn.disabled = true;
+            }
+
+            // 调用后端API创建新对话
+            fetch('/api/conversations/new', {
+                method: 'POST',
+                headers: {
+                    'Authorization': `Bearer ${token}`,
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({
+                    title: '新对话'
+                })
+            })
+                .then(response => {
+                    if (response.status === 401) {
+                        redirectToLogin();
+                        return;
+                    }
+                    return response.json();
+                })
+                .then(data => {
+                    if (data && data.success) {
+                        // 更新当前对话ID和session_id
+                        window.currentConversationId = data.conversation.conversation_id;
+                        window.currentSessionId = data.conversation.session_id;
+
+                        // 清除聊天区域并显示提示语
+                        clearCurrentConversation();
+
+
+                        // 更新侧边栏选中状态
+                        updateConversationSelection(data.conversation.conversation_id);
+
+                        // 重新加载历史记录以显示新对话,但不自动加载对话内容
+                        loadConversationHistory(false);
+
+                        // 确保输入框状态正确
+                        const userInput = document.getElementById('user-input');
+                        const sendButton = document.getElementById('send-btn');
+                        if (userInput && sendButton) {
+                            userInput.disabled = false;
+                            userInput.placeholder = '请输入需要撰写的品类名和要求...';
+                            userInput.classList.remove('input-loading');
+                            sendButton.disabled = false;
+                            sendButton.classList.remove('stopping');
+                        }
+
+                        // 移动端关闭侧边栏
+                        const isMobile = window.innerWidth <= 768;
+                        if (isMobile) {
+                            const sidebar = document.getElementById('history-sidebar');
+                            const sidebarOverlay = document.getElementById('sidebar-overlay');
+                            if (sidebar && sidebarOverlay) {
+                                sidebar.classList.remove('show');
+                                sidebarOverlay.classList.remove('show');
+                                document.body.style.overflow = '';
+                            }
+                        }
+
+                    } else {
+                        showMessage('创建新对话失败: ' + (data ? data.error : '未知错误'), 'error');
+                    }
+                })
+                .catch(error => {
+                    showMessage('创建新对话时发生错误: ' + error.message, 'error');
+                })
+                .finally(() => {
+                    // 恢复按钮状态
+                    if (newChatBtn) {
+                        newChatBtn.innerHTML = `
+                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                                <path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
+                            </svg>
+                            开启新对话
+                        `;
+                        newChatBtn.disabled = false;
+                    }
+                });
+        }
+
+        function clearCurrentConversation() {
+            const chatMessages = document.getElementById('chat-messages');
+            if (chatMessages) {
+                chatMessages.innerHTML = `
+                    <div class="message bot">
+                        <div class="message-content">请发送你的投放数据。如果是视频诊断,目前仅能诊断文案,请去聚德神转转为文案(最好是带时间戳的字幕-聚德智能视频处理工具可导出)。
+                        </div>
+                    </div>
+                `;
+            }
+
+            // 清除全局状态管理中的当前激活对话
+            if (typeof currentActiveConversationId !== 'undefined') {
+                currentActiveConversationId = null;
+            }
+
+            // 清除强制创建新对话的标志(因为已经创建了新对话)
+            window.forceNewConversation = false;
+        }
+
+        function loadConversation(conversationId) {
+            // 检查认证状态
+            const token = localStorage.getItem('auth_token');
+            if (!token) {
+                redirectToLogin();
+                return;
+            }
+
+            // 设置激活的对话
+            if (typeof setActiveConversation === 'function') {
+                setActiveConversation(conversationId);
+            }
+
+            // 先切换到指定对话,然后加载内容
+            switchToConversationAndLoad(conversationId, token);
+        }
+
+        function switchToConversationAndLoad(conversationId, token) {
+            // 调用后端API切换到指定对话
+            fetch(`/api/conversations/${conversationId}/switch`, {
+                method: 'POST',
+                headers: {
+                    'Authorization': `Bearer ${token}`,
+                    'Content-Type': 'application/json'
+                }
+            }).then(response => {
+                if (response.status === 401) {
+                    redirectToLogin();
+                    return;
+                }
+                return response.json();
+            }).then(data => {
+                if (data && data.success) {
+                    // 更新当前对话ID和session_id
+                    window.currentConversationId = data.conversation.conversation_id;
+                    window.currentSessionId = data.conversation.session_id;
+
+                    // 加载对话内容
+                    loadConversationSimple(conversationId, token);
+                } else {
+                    showMessage('切换对话失败: ' + (data ? data.error : '未知错误'), 'error');
+                }
+            }).catch(error => {
+                showMessage('切换对话时发生错误: ' + error.message, 'error');
+            });
+        }
+
+        function loadConversationSimple(conversationId, token) {
+            // 检查是否有正在进行的流式响应
+            const hasActiveStream = window.conversationStates && window.conversationStates.has(conversationId);
+            const streamState = hasActiveStream ? window.conversationStates.get(conversationId) : null;
+            // 检查是否真正在流式响应(只要 isStreaming 为 true,无论是否有 messageElement)
+            const isActuallyStreaming = streamState && streamState.isStreaming;
+            // 检查是否有可用的消息元素(用于判断是否可以通过 updateExistingMessages 处理)
+            const hasMessageElement = streamState && streamState.messageElement && streamState.messageElement.parentNode;
+
+            // 返回 Promise
+            return fetch(`/api/conversations/${conversationId}`, {
+                method: 'GET',
+                headers: {
+                    'Authorization': `Bearer ${token}`,
+                    'Content-Type': 'application/json'
+                }
+            })
+                .then(response => {
+                    if (response.status === 401) {
+                        redirectToLogin();
+                        return;
+                    }
+                    return response.json();
+                })
+                .then(data => {
+                    if (data && data.success) {
+                        const chatMessages = document.getElementById('chat-messages');
+
+
+                        // 如果有正在进行的流式响应且有可用的消息元素,保留流式响应元素
+                        if (isActuallyStreaming && hasMessageElement) {
+                            // 保留流式响应的消息元素,只更新历史消息
+                            updateExistingMessages(data.messages, streamState.messageElement);
+                        } else {
+                            // 清空聊天区域并重新渲染所有消息
+                            chatMessages.innerHTML = '';
+
+                            // 如果没有消息,显示提示语
+                            if (!data.messages || data.messages.length === 0) {
+                                chatMessages.innerHTML = `
+                                    <div class="message bot">
+                                        <div class="message-content">请发送你的投放数据。如果是视频诊断,目前仅能诊断文案,请去聚德神转转为文案(最好是带时间戳的字幕-聚德智能视频处理工具可导出)。</div>
+                                    </div>
+                                `;
+                            } else {
+                                renderAllMessages(data.messages, chatMessages);
+                            }
+
+                            // 确保用户消息在切换对话后仍然可见
+                            // 检查是否有未保存的用户消息
+                            if (streamState && streamState.userMessages) {
+                                streamState.userMessages.forEach(userMsg => {
+                                    // 检查用户消息是否已经在DOM中
+                                    const existingUserMsg = Array.from(chatMessages.children).find(msg =>
+                                        msg.classList.contains('user') &&
+                                        msg.querySelector('.message-content')?.textContent === userMsg.content
+                                    );
+
+                                    if (!existingUserMsg) {
+                                        // 如果用户消息不在DOM中,重新添加
+                                        chatMessages.appendChild(userMsg.element);
+                                    }
+                                });
+                            }
+
+                            // ===== 恢复流式响应内容(无论是正在进行还是已完成但未保存到数据库) =====
+                            // 检查数据库中是否已经包含了流式响应的内容
+                            let shouldRestoreStreamContent = false;
+
+                            // 如果流式响应正在进行,必须恢复显示
+                            if (streamState && isActuallyStreaming) {
+                                shouldRestoreStreamContent = true;
+                            }
+                            // 如果流式响应已完成,检查是否需要恢复(内容未保存到数据库)
+                            else if (streamState && !isActuallyStreaming && (streamState.currentMessage || (streamState.currentThoughts && streamState.currentThoughts.length > 0))) {
+                                // 检查数据库返回的最后一条 assistant 消息是否与流式响应的内容匹配
+                                const dbMessages = data.messages || [];
+                                const lastAssistantMsg = dbMessages.filter(msg => msg.role === 'assistant').pop();
+
+                                if (lastAssistantMsg) {
+                                    // 如果最后一条 assistant 消息的内容与流式响应的内容匹配,说明内容已经保存到数据库
+                                    // 使用更精确的匹配:检查消息内容是否相同或包含流式响应的主要内容
+                                    let contentMatches = false;
+                                    if (streamState.currentMessage) {
+                                        const streamContent = streamState.currentMessage.trim();
+                                        const dbContent = lastAssistantMsg.content.trim();
+                                        // 精确匹配或检查前100个字符是否匹配(处理可能的截断)
+                                        contentMatches = (dbContent === streamContent) ||
+                                            (dbContent.length > 50 && streamContent.length > 50 &&
+                                                dbContent.substring(0, 100) === streamContent.substring(0, 100));
+                                    } else {
+                                        // 如果没有流式响应内容,认为匹配
+                                        contentMatches = true;
+                                    }
+
+                                    let thoughtsMatch = false;
+                                    if (streamState.currentThoughts && streamState.currentThoughts.length > 0) {
+                                        const streamThoughts = streamState.currentThoughts.join('').trim();
+                                        const dbThoughts = (lastAssistantMsg.thinking_content || '').trim();
+                                        thoughtsMatch = (dbThoughts === streamThoughts) ||
+                                            (dbThoughts.length > 50 && streamThoughts.length > 50 &&
+                                                dbThoughts.substring(0, 100) === streamThoughts.substring(0, 100));
+                                    } else {
+                                        // 如果流式响应没有思考过程,检查数据库是否有思考过程
+                                        thoughtsMatch = !lastAssistantMsg.thinking_content || lastAssistantMsg.thinking_content.trim() === '';
+                                    }
+
+                                    // 只有当内容不匹配时,才需要恢复流式响应的内容
+                                    shouldRestoreStreamContent = !contentMatches || !thoughtsMatch;
+                                } else {
+                                    // 如果数据库中没有 assistant 消息,说明确实需要恢复流式响应的内容
+                                    shouldRestoreStreamContent = true;
+                                }
+
+                                // 如果内容已保存在数据库中,清除流式响应状态以避免下次重复检查
+                                if (!shouldRestoreStreamContent) {
+                                    streamState.currentMessage = '';
+                                    streamState.currentThoughts = [];
+                                }
+                            }
+
+                            if (shouldRestoreStreamContent && streamState) {
+                                // 如果流式响应正在进行或已完成,但消息元素不在DOM中,需要重新创建或恢复
+                                let restoredMessageElement = streamState.messageElement;
+
+                                // 如果流式响应正在进行,确保消息元素存在
+                                const isStreamingNow = streamState.isStreaming;
+
+                                // 如果消息元素不存在或不在DOM中
+                                if (!restoredMessageElement) {
+                                    // 消息元素不存在,需要创建
+                                    restoredMessageElement = document.createElement('div');
+                                    restoredMessageElement.className = 'message bot';
+
+                                    // 创建思考过程容器(如果有)
+                                    if (streamState.currentThoughts && streamState.currentThoughts.length > 0) {
+                                        const thoughtContainer = document.createElement('div');
+                                        thoughtContainer.className = 'thought-container';
+
+                                        const thoughtToggleBtn = document.createElement('button');
+                                        thoughtToggleBtn.className = 'thought-toggle-btn';
+                                        thoughtToggleBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
+                                        thoughtToggleBtn.title = '收起/展开思考过程';
+                                        thoughtToggleBtn.onclick = function (e) {
+                                            e.stopPropagation();
+                                            thoughtContainer.classList.toggle('collapsed');
+                                        };
+
+                                        const thoughtContent = document.createElement('div');
+                                        thoughtContent.className = 'thought-content';
+                                        if (typeof window.renderThoughts === 'function') {
+                                            thoughtContent.innerHTML = window.renderThoughts(streamState.currentThoughts.join(''));
+                                        } else {
+                                            thoughtContent.textContent = streamState.currentThoughts.join('');
+                                        }
+
+                                        thoughtContainer.appendChild(thoughtToggleBtn);
+                                        thoughtContainer.appendChild(thoughtContent);
+                                        restoredMessageElement.appendChild(thoughtContainer);
+
+                                        // 更新状态中的引用
+                                        streamState.thoughtContainer = thoughtContainer;
+                                    }
+
+                                    // 创建消息内容容器
+                                    const contentContainer = document.createElement('div');
+                                    // 如果流式响应正在进行,添加 typing 类名
+                                    contentContainer.className = isStreamingNow ? 'message-content typing' : 'message-content';
+                                    if (streamState.currentMessage) {
+                                        if (typeof window.renderMarkdown === 'function') {
+                                            contentContainer.innerHTML = window.renderMarkdown(streamState.currentMessage);
+                                            if (typeof window.applyCodeHighlighting === 'function') {
+                                                window.applyCodeHighlighting(contentContainer);
+                                            }
+                                        } else {
+                                            contentContainer.textContent = streamState.currentMessage;
+                                        }
+                                    }
+                                    restoredMessageElement.appendChild(contentContainer);
+
+                                    // 保存 contentContainer 引用到状态中,以便流式响应继续更新
+                                    if (isStreamingNow && contentContainer) {
+                                        // 确保状态中的消息元素引用正确
+                                        streamState.messageElement = restoredMessageElement;
+                                    }
+
+                                    // 添加到聊天区域
+                                    chatMessages.appendChild(restoredMessageElement);
+
+                                    // 添加对话ID标识,便于后续管理
+                                    restoredMessageElement.setAttribute('data-conversation-id', conversationId);
+
+                                    // 更新状态中的引用(确保流式响应能够继续更新)
+                                    streamState.messageElement = restoredMessageElement;
+
+                                    // 如果流式响应正在进行,确保恢复后能够继续更新
+                                    if (isStreamingNow) {
+                                        // 确保消息元素可见
+                                        restoredMessageElement.style.opacity = '1';
+                                        restoredMessageElement.style.transform = 'translateY(0)';
+
+                                        // 滚动到底部以看到实时更新
+                                        setTimeout(() => {
+                                            chatMessages.scrollTop = chatMessages.scrollHeight;
+                                        }, 100);
+                                    }
+                                } else if (!restoredMessageElement.parentNode) {
+                                    // 消息元素存在但不在DOM中,需要重新添加到DOM
+                                    chatMessages.appendChild(restoredMessageElement);
+
+                                    // 确保消息元素可见
+                                    restoredMessageElement.style.opacity = '1';
+                                    restoredMessageElement.style.transform = 'translateY(0)';
+
+                                    // 如果流式响应正在进行,滚动到底部
+                                    if (isStreamingNow) {
+                                        setTimeout(() => {
+                                            chatMessages.scrollTop = chatMessages.scrollHeight;
+                                        }, 100);
+                                    }
+                                } else {
+                                    // 如果消息元素还在DOM中,只需要确保内容是最新的
+                                    const contentContainer = restoredMessageElement.querySelector('.message-content');
+                                    if (contentContainer) {
+                                        // 如果流式响应正在进行,确保有 typing 类名
+                                        if (isStreamingNow && !contentContainer.classList.contains('typing')) {
+                                            contentContainer.classList.add('typing');
+                                        } else if (!isStreamingNow && contentContainer.classList.contains('typing')) {
+                                            contentContainer.classList.remove('typing');
+                                        }
+
+                                        // 更新内容
+                                        if (streamState.currentMessage) {
+                                            if (typeof window.renderMarkdown === 'function') {
+                                                contentContainer.innerHTML = window.renderMarkdown(streamState.currentMessage);
+                                                if (typeof window.applyCodeHighlighting === 'function') {
+                                                    window.applyCodeHighlighting(contentContainer);
+                                                }
+                                            } else {
+                                                contentContainer.textContent = streamState.currentMessage;
+                                            }
+                                        }
+                                    }
+
+                                    // 恢复思考过程
+                                    if (streamState.thoughtContainer && streamState.currentThoughts && streamState.currentThoughts.length > 0) {
+                                        const thoughtContent = streamState.thoughtContainer.querySelector('.thought-content');
+                                        if (thoughtContent) {
+                                            if (typeof window.renderThoughts === 'function') {
+                                                thoughtContent.innerHTML = window.renderThoughts(streamState.currentThoughts.join(''));
+                                            } else {
+                                                thoughtContent.textContent = streamState.currentThoughts.join('');
+                                            }
+                                        }
+                                    }
+                                }
+
+                                // 确保消息元素可见
+                                restoredMessageElement.style.opacity = '1';
+                                restoredMessageElement.style.transform = 'translateY(0)';
+
+                                // 为新恢复的消息添加复制按钮
+                                if (typeof window.setupCopyButtons === 'function') {
+                                    window.setupCopyButtons();
+                                }
+                            }
+                        }
+
+                        // 更新当前对话ID和session_id
+                        window.currentConversationId = conversationId;
+                        if (data.conversation && data.conversation.session_id) {
+                            window.currentSessionId = data.conversation.session_id;
+                        }
+
+                        // 更新输入框状态 - 确保切换对话时输入框状态正确
+                        if (typeof updateInputUIForConversation === 'function') {
+                            updateInputUIForConversation(conversationId);
+                        } else if (typeof resetUIState === 'function') {
+                            resetUIState();
+                        }
+
+                        // 滚动到底部
+                        setTimeout(() => {
+                            chatMessages.scrollTop = chatMessages.scrollHeight;
+                        }, 100);
+                    } else {
+                        showMessage('加载对话失败: ' + (data ? data.error : '未知错误'), 'error');
+                    }
+                })
+                .catch(error => {
+                    showMessage('加载对话时发生错误: ' + error.message, 'error');
+                });
+        }
+
+        function updateExistingMessages(messages, streamElement) {
+            const chatMessages = document.getElementById('chat-messages');
+            const existingMessages = Array.from(chatMessages.children);
+
+            // 找到流式响应元素的位置
+            const streamIndex = existingMessages.indexOf(streamElement);
+
+            // 移除流式响应元素之前的所有消息(这些是历史消息)
+            for (let i = 0; i < streamIndex; i++) {
+                if (existingMessages[i] && existingMessages[i].parentNode) {
+                    existingMessages[i].remove();
+                }
+            }
+
+            // 在流式响应元素之前插入所有历史消息
+            messages.forEach((msg, index) => {
+                // 显示所有历史消息,包括用户消息和AI回复
+                const messageDiv = createMessageElement(msg);
+                chatMessages.insertBefore(messageDiv, streamElement);
+            });
+        }
+
+        function renderAllMessages(messages, chatMessages) {
+            messages.forEach((msg, index) => {
+                const messageDiv = createMessageElement(msg);
+                chatMessages.appendChild(messageDiv);
+            });
+        }
+
+        function createMessageElement(msg) {
+            const messageDiv = document.createElement('div');
+            messageDiv.className = `message ${msg.role}`;
+
+            let content = msg.content;
+
+            // 如果是AI消息且有思考过程,添加思考容器
+            if (msg.role === 'assistant' && msg.thinking_content) {
+                // 使用思考过程专用的渲染函数
+                let thoughtHtml = '';
+                if (typeof window.renderThoughts === 'function') {
+                    thoughtHtml = window.renderThoughts(msg.thinking_content);
+                } else if (typeof window.renderMarkdown === 'function') {
+                    thoughtHtml = window.renderMarkdown(msg.thinking_content);
+                } else if (typeof showdown !== 'undefined') {
+                    const converter = new showdown.Converter({
+                        strikethrough: true,
+                        tables: true,
+                        tasklists: true,
+                        ghCodeBlocks: true,
+                        openLinksInNewWindow: true
+                    });
+                    thoughtHtml = converter.makeHtml(msg.thinking_content);
+                } else {
+                    thoughtHtml = msg.thinking_content;
+                }
+
+                const thoughtContainerHtml = `
+                <div class="thought-container" data-collapsed="false">
+                    <button class="thought-toggle-btn" onclick="toggleThought(this)">
+                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                            <path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                        </svg>
+                    </button>
+                    <div class="thought-content">
+                        ${thoughtHtml}
+                    </div>
+                </div>
+                `;
+                content = thoughtContainerHtml + content;
+            }
+
+            // 使用统一的Markdown渲染函数渲染消息内容
+            let renderedContent = content;
+            if (msg.role === 'assistant') {
+                if (typeof window.renderMarkdown === 'function') {
+                    // 只渲染消息内容部分,思考过程已经渲染过了
+                    const messageContentOnly = msg.content;
+                    renderedContent = msg.thinking_content ?
+                        content.replace(msg.content, window.renderMarkdown(messageContentOnly)) :
+                        window.renderMarkdown(messageContentOnly);
+                } else if (typeof showdown !== 'undefined') {
+                    const converter = new showdown.Converter({
+                        strikethrough: true,
+                        tables: true,
+                        tasklists: true,
+                        ghCodeBlocks: true,
+                        openLinksInNewWindow: true
+                    });
+                    // 只渲染消息内容部分,思考过程已经渲染过了
+                    const messageContentOnly = msg.content;
+                    renderedContent = msg.thinking_content ?
+                        content.replace(msg.content, converter.makeHtml(messageContentOnly)) :
+                        converter.makeHtml(messageContentOnly);
+                }
+            }
+
+            // 为用户消息添加编辑按钮
+            let messageContentHtml = `<div class="message-content">${renderedContent}</div>`;
+            if (msg.role === 'user' && msg.message_id) {
+                // 转义特殊字符,防止XSS攻击
+                const escapedContent = msg.content.replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+                messageContentHtml = `
+                    <div class="message-content">${renderedContent}</div>
+                    <button class="edit-button" onclick="editMessage(${msg.message_id}, '${escapedContent}')" title="编辑消息">
+                        <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                            <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                            <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                        </svg>
+                    </button>
+                `;
+            }
+
+            messageDiv.innerHTML = messageContentHtml;
+
+            // 为用户消息添加data-message-id属性
+            if (msg.role === 'user' && msg.message_id) {
+                messageDiv.setAttribute('data-message-id', msg.message_id);
+            }
+
+            // 确保消息立即可见
+            messageDiv.style.opacity = '1';
+            messageDiv.style.transform = 'translateY(0)';
+
+            // 应用代码高亮和表格处理
+            if (typeof applyCodeHighlighting === 'function') {
+                applyCodeHighlighting(messageDiv);
+            }
+            if (typeof processAuditResultTables === 'function') {
+                processAuditResultTables();
+            }
+
+            // 为历史消息添加复制按钮
+            if (typeof window.setupCopyButtons === 'function') {
+                window.setupCopyButtons();
+            }
+
+            return messageDiv;
+        }
+
+
+
+
+
+
+
+
+        function updateConversationSelection(conversationId) {
+            const conversationItems = document.querySelectorAll('.conversation-item');
+            conversationItems.forEach(item => {
+                const itemId = item.getAttribute('data-conversation-id');
+                if (itemId === conversationId) {
+                    item.classList.add('active');
+                    // 确保状态点显示
+                    const statusDot = item.querySelector('.conversation-status-dot');
+                    if (statusDot) {
+                        statusDot.style.opacity = '1';
+                    }
+                } else {
+                    item.classList.remove('active');
+                    // 移除非活跃对话的状态类
+                    item.classList.remove('streaming', 'paused');
+                    const statusDot = item.querySelector('.conversation-status-dot');
+                    if (statusDot) {
+                        statusDot.style.opacity = '0';
+                    }
+                }
+            });
+        }
+
+        function updateConversationStatus(conversationId, status) {
+            const conversationItem = document.querySelector(`[data-conversation-id="${conversationId}"]`);
+            if (conversationItem) {
+                const statusDot = conversationItem.querySelector('.conversation-status-dot');
+
+                // 移除所有状态类
+                conversationItem.classList.remove('streaming', 'paused');
+
+                // 添加新的状态类
+                if (status === 'streaming') {
+                    conversationItem.classList.add('streaming');
+                    if (statusDot) {
+                        statusDot.style.opacity = '1';
+                        statusDot.style.background = '#10b981';
+                    }
+                } else if (status === 'paused') {
+                    conversationItem.classList.add('paused');
+                    if (statusDot) {
+                        statusDot.style.opacity = '1';
+                        statusDot.style.background = '#f59e0b';
+                    }
+                } else if (status === 'stopped') {
+                    if (statusDot) {
+                        statusDot.style.opacity = '0';
+                    }
+                }
+            }
+        }
+
+        function showConversationMenu(conversationId, button) {
+            // 创建菜单
+            const menu = document.createElement('div');
+            menu.className = 'conversation-context-menu';
+            menu.innerHTML = `
+                <div class="menu-item" data-action="rename">
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                        <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                    </svg>
+                    重命名
+                </div>
+                <div class="menu-item" data-action="delete">
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                    </svg>
+                    删除
+                </div>
+            `;
+
+            // 添加样式
+            menu.style.cssText = `
+                position: absolute;
+                background: rgba(255, 255, 255, 0.95);
+                backdrop-filter: blur(20px);
+                border: 1px solid rgba(255, 255, 255, 0.3);
+                border-radius: 8px;
+                box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
+                padding: 8px 0;
+                min-width: 120px;
+                z-index: 1000;
+                opacity: 0;
+                transform: scale(0.9);
+                transition: all 0.2s ease;
+            `;
+
+            // 定位菜单
+            const rect = button.getBoundingClientRect();
+            menu.style.left = (rect.left - 100) + 'px';
+            menu.style.top = (rect.bottom + 5) + 'px';
+
+            // 添加到页面
+            document.body.appendChild(menu);
+
+            // 显示动画
+            setTimeout(() => {
+                menu.style.opacity = '1';
+                menu.style.transform = 'scale(1)';
+            }, 10);
+
+            // 菜单项点击事件
+            menu.querySelectorAll('.menu-item').forEach(item => {
+                item.addEventListener('click', function () {
+                    const action = this.getAttribute('data-action');
+                    handleConversationAction(action, conversationId);
+                    closeMenu();
+                });
+            });
+
+            // 点击外部关闭菜单
+            function closeMenu() {
+                menu.style.opacity = '0';
+                menu.style.transform = 'scale(0.9)';
+                setTimeout(() => {
+                    if (menu.parentNode) {
+                        menu.parentNode.removeChild(menu);
+                    }
+                }, 200);
+            }
+
+            // 点击外部关闭
+            document.addEventListener('click', function closeOnOutsideClick(e) {
+                if (!menu.contains(e.target) && e.target !== button) {
+                    closeMenu();
+                    document.removeEventListener('click', closeOnOutsideClick);
+                }
+            });
+        }
+
+        function handleConversationAction(action, conversationId) {
+            switch (action) {
+                case 'rename':
+                    renameConversation(conversationId);
+                    break;
+                case 'delete':
+                    deleteConversation(conversationId);
+                    break;
+            }
+        }
+
+        function renameConversation(conversationId) {
+            // 检查认证状态
+            const token = localStorage.getItem('auth_token');
+            if (!token) {
+                redirectToLogin();
+                return;
+            }
+
+            // 获取当前对话标题
+            const conversationItem = document.querySelector(`[data-conversation-id="${conversationId}"]`);
+            const currentTitle = conversationItem ? conversationItem.querySelector('.conversation-title').textContent : '';
+
+            // 设置输入框的值
+            const renameInput = document.getElementById('rename-input');
+            if (renameInput) {
+                renameInput.value = currentTitle;
+                renameInput.focus();
+                renameInput.select();
+            }
+
+            // 显示重命名弹框
+            showModal('rename-modal');
+
+            // 绑定确认按钮事件
+            const confirmBtn = document.getElementById('confirm-rename-btn');
+            if (confirmBtn) {
+                confirmBtn.onclick = function () {
+                    const newTitle = renameInput.value.trim();
+                    if (newTitle && newTitle !== currentTitle) {
+                        performRename(conversationId, newTitle, token);
+                    } else if (!newTitle) {
+                        showMessage('请输入新的对话标题', 'warning');
+                    } else {
+                        closeModal('rename-modal');
+                    }
+                };
+            }
+
+            // 绑定回车键事件
+            if (renameInput) {
+                renameInput.onkeydown = function (e) {
+                    if (e.key === 'Enter') {
+                        confirmBtn.click();
+                    } else if (e.key === 'Escape') {
+                        closeModal('rename-modal');
+                    }
+                };
+            }
+        }
+
+        function performRename(conversationId, newTitle, token) {
+            fetch(`/api/conversations/${conversationId}/rename`, {
+                method: 'POST',
+                headers: {
+                    'Authorization': `Bearer ${token}`,
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({
+                    title: newTitle
+                })
+            })
+                .then(response => {
+                    if (response.status === 401) {
+                        redirectToLogin();
+                        return;
+                    }
+                    return response.json();
+                })
+                .then(data => {
+                    if (data && data.success) {
+                        const conversationItem = document.querySelector(`[data-conversation-id="${conversationId}"]`);
+                        if (conversationItem) {
+                            const titleElement = conversationItem.querySelector('.conversation-title');
+                            if (titleElement) {
+                                titleElement.textContent = newTitle;
+                            }
+                        }
+                        closeModal('rename-modal');
+                        showMessage('对话标题已更新');
+                    } else {
+                        showMessage('重命名失败: ' + (data ? data.error : '未知错误'), 'error');
+                    }
+                })
+                .catch(error => {
+                    showMessage('重命名对话时发生错误: ' + error.message, 'error');
+                });
+        }
+
+        function deleteConversation(conversationId) {
+            // 检查认证状态
+            const token = localStorage.getItem('auth_token');
+            if (!token) {
+                redirectToLogin();
+                return;
+            }
+
+            // 显示删除确认弹框
+            showModal('delete-modal');
+
+            // 绑定确认删除按钮事件
+            const confirmBtn = document.getElementById('confirm-delete-btn');
+            if (confirmBtn) {
+                confirmBtn.onclick = function () {
+                    performDelete(conversationId, token);
+                };
+            }
+        }
+
+        function performDelete(conversationId, token) {
+            fetch(`/api/conversations/${conversationId}`, {
+                method: 'DELETE',
+                headers: {
+                    'Authorization': `Bearer ${token}`,
+                    'Content-Type': 'application/json'
+                }
+            })
+                .then(response => {
+                    if (response.status === 401) {
+                        redirectToLogin();
+                        return;
+                    }
+                    return response.json();
+                })
+                .then(data => {
+                    if (data && data.success) {
+                        const conversationItem = document.querySelector(`[data-conversation-id="${conversationId}"]`);
+                        if (conversationItem) {
+                            conversationItem.remove();
+                        }
+                        closeModal('delete-modal');
+                        showMessage('对话已删除');
+
+                        // 如果删除的是当前对话,清除当前对话ID
+                        if (window.currentConversationId === conversationId) {
+                            window.currentConversationId = null;
+                            // 清空聊天区域
+                            const chatMessages = document.getElementById('chat-messages');
+                            if (chatMessages) {
+                                chatMessages.innerHTML = '';
+                            }
+                        }
+                    } else {
+                        showMessage('删除失败: ' + (data ? data.error : '未知错误'), 'error');
+                    }
+                })
+                .catch(error => {
+                    showMessage('删除对话时发生错误: ' + error.message, 'error');
+                });
+        }
+
+        function loadConversationHistory(autoSelectLatest = false) {
+            // 检查认证状态
+            const token = localStorage.getItem('auth_token');
+            if (!token) {
+                redirectToLogin();
+                return;
+            }
+
+            // 从服务器加载历史记录
+            fetch('/api/conversations', {
+                method: 'GET',
+                headers: {
+                    'Authorization': `Bearer ${token}`,
+                    'Content-Type': 'application/json'
+                }
+            })
+                .then(response => {
+                    if (response.status === 401) {
+                        // 认证失败,清除本地存储并重定向到登录页面
+                        redirectToLogin();
+                        return;
+                    }
+                    return response.json();
+                })
+                .then(data => {
+                    if (data && data.success) {
+                        updateHistorySidebar(data.conversations, autoSelectLatest);
+                    } else {
+                        // 显示空状态
+                        const historyLoading = document.getElementById('history-loading');
+                        const historyEmpty = document.getElementById('history-empty');
+                        if (historyLoading) historyLoading.style.display = 'none';
+                        if (historyEmpty) historyEmpty.style.display = 'block';
+                    }
+                })
+                .catch(error => {
+                    // 显示空状态
+                    const historyLoading = document.getElementById('history-loading');
+                    const historyEmpty = document.getElementById('history-empty');
+                    if (historyLoading) historyLoading.style.display = 'none';
+                    if (historyEmpty) historyEmpty.style.display = 'block';
+                });
+        }
+
+        // 将loadConversationHistory函数暴露到全局作用域,供chat.js调用
+        window.loadConversationHistory = loadConversationHistory;
+
+
+
+
+
+
+
+        function updateHistorySidebar(conversations, autoSelectLatest = false) {
+            // 更新侧边栏的历史记录
+            const historyContent = document.querySelector('.history-content');
+            const historyLoading = document.getElementById('history-loading');
+            const historyEmpty = document.getElementById('history-empty');
+
+            if (!historyContent) {
+                return;
+            }
+
+            // 隐藏加载状态
+            if (historyLoading) {
+                historyLoading.style.display = 'none';
+            }
+
+            // 清空现有内容
+            historyContent.innerHTML = '';
+
+            // 检查是否有任何对话
+            const hasAnyConversations = Object.values(conversations).some(group => group && group.length > 0);
+
+            if (!hasAnyConversations) {
+                // 显示空状态
+                if (historyEmpty) {
+                    historyEmpty.style.display = 'block';
+                }
+                return;
+            }
+
+            // 隐藏空状态
+            if (historyEmpty) {
+                historyEmpty.style.display = 'none';
+            }
+
+            // 按时间分组渲染
+            const timeGroups = [
+                { key: 'today', label: '今天' },
+                { key: 'yesterday', label: '昨天' },
+                { key: 'week', label: '7天内' },
+                { key: 'month', label: '30天内' }
+            ];
+
+            timeGroups.forEach(group => {
+                const groupConversations = conversations[group.key] || [];
+                if (groupConversations.length === 0) return;
+
+                // 创建分组容器
+                const groupDiv = document.createElement('div');
+                groupDiv.className = 'history-group';
+
+                // 分组标题
+                const headerDiv = document.createElement('div');
+                headerDiv.className = 'group-header';
+                headerDiv.textContent = group.label;
+
+                // 对话列表
+                const listDiv = document.createElement('div');
+                listDiv.className = 'conversation-list';
+
+                groupConversations.forEach(conv => {
+                    const itemDiv = document.createElement('div');
+                    itemDiv.className = 'conversation-item';
+                    itemDiv.setAttribute('data-conversation-id', conv.conversation_id);
+                    itemDiv.setAttribute('data-session-id', conv.session_id || '');
+
+                    // 格式化时间显示
+                    const lastMessageTime = conv.last_message_at ? new Date(conv.last_message_at).toLocaleTimeString('zh-CN', {
+                        hour: '2-digit',
+                        minute: '2-digit'
+                    }) : '';
+
+                    itemDiv.innerHTML = `
+                        <div class="conversation-content">
+                            <div class="conversation-title">${conv.title}</div>
+                            <div class="conversation-meta">
+                                <span class="message-count">${conv.message_count}条消息</span>
+                                ${lastMessageTime ? `<span class="last-time">${lastMessageTime}</span>` : ''}
+                            </div>
+                        </div>
+                        <div class="conversation-status-dot"></div>
+                        <button class="conversation-menu" data-conversation-id="${conv.conversation_id}">
+                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                                <circle cx="12" cy="12" r="1" fill="currentColor"/>
+                                <circle cx="19" cy="12" r="1" fill="currentColor"/>
+                                <circle cx="5" cy="12" r="1" fill="currentColor"/>
+                            </svg>
+                        </button>
+                    `;
+
+                    listDiv.appendChild(itemDiv);
+                });
+
+                groupDiv.appendChild(headerDiv);
+                groupDiv.appendChild(listDiv);
+                historyContent.appendChild(groupDiv);
+            });
+
+            // 重新绑定事件
+            bindConversationEvents();
+
+            // 自动选中对话
+            const firstConversationItem = document.querySelector('.conversation-item');
+            if (firstConversationItem) {
+                const conversationId = firstConversationItem.getAttribute('data-conversation-id');
+
+                if (autoSelectLatest) {
+                    // 新对话完成后,选中最新的对话,但不加载内容(保持提示语显示)
+                    updateConversationSelection(conversationId);
+                    // 不调用 loadConversation,保持提示语显示
+                } else if (!window.currentConversationId) {
+                    // 页面初始加载时,选中第一个对话
+                    updateConversationSelection(conversationId);
+                    loadConversation(conversationId);
+                }
+            }
+        }
+
+        function bindConversationEvents() {
+            // 重新绑定对话项点击事件
+            const conversationItems = document.querySelectorAll('.conversation-item');
+            conversationItems.forEach(item => {
+                item.addEventListener('click', function (e) {
+                    if (e.target.closest('.conversation-menu')) {
+                        return;
+                    }
+
+                    const conversationId = this.getAttribute('data-conversation-id');
+                    const sessionId = this.getAttribute('data-session-id');
+
+                    // 设置当前对话的session_id
+                    if (sessionId) {
+                        window.currentSessionId = sessionId;
+                    }
+
+                    // 设置激活的对话
+                    if (typeof setActiveConversation === 'function') {
+                        setActiveConversation(conversationId);
+                    }
+
+                    loadConversation(conversationId);
+                    updateConversationSelection(conversationId);
+
+                    // 移动端关闭侧边栏
+                    const isMobile = window.innerWidth <= 768;
+                    if (isMobile) {
+                        const sidebar = document.getElementById('history-sidebar');
+                        const sidebarOverlay = document.getElementById('sidebar-overlay');
+                        if (sidebar && sidebarOverlay) {
+                            sidebar.classList.remove('show');
+                            sidebarOverlay.classList.remove('show');
+                            document.body.style.overflow = '';
+                        }
+                    }
+                });
+            });
+
+            // 重新绑定对话菜单事件
+            const conversationMenus = document.querySelectorAll('.conversation-menu');
+            conversationMenus.forEach(menu => {
+                menu.addEventListener('click', function (e) {
+                    e.stopPropagation();
+                    const conversationId = this.getAttribute('data-conversation-id');
+                    showConversationMenu(conversationId, this);
+                });
+            });
+        }
+
+        function syncSidebarUserInfo() {
+            const userInfo = localStorage.getItem('user_info');
+            if (userInfo) {
+                try {
+                    const user = JSON.parse(userInfo);
+                    const sidebarUserName = document.getElementById('sidebar-user-name');
+                    const sidebarAvatarImg = document.getElementById('sidebar-avatar-img');
+                    const sidebarAvatarFallback = document.getElementById('sidebar-avatar-fallback');
+
+                    if (sidebarUserName) {
+                        const displayName = user.nickname || user.name || '用户';
+                        sidebarUserName.textContent = displayName;
+                    }
+
+                    if (sidebarAvatarImg && user.avatar && user.avatar.trim() !== '') {
+                        sidebarAvatarImg.src = user.avatar;
+                        sidebarAvatarImg.style.display = 'block';
+                        sidebarAvatarFallback.style.display = 'none';
+                    } else if (sidebarAvatarFallback) {
+                        sidebarAvatarImg.style.display = 'none';
+                        sidebarAvatarFallback.style.display = 'flex';
+                        const displayName = user.nickname || user.name || 'U';
+                        sidebarAvatarFallback.textContent = displayName.charAt(0).toUpperCase();
+                    }
+                } catch (e) {
+                }
+            }
+        }
+
+        // 编辑消息功能
+        let currentEditMessageId = null;
+        let currentEditMessageElement = null;
+        window.isEditingMessage = false; // 设置为全局变量
+
+        window.editMessage = function (messageId, originalContent) {
+            // 如果已经在编辑其他消息,先取消
+            if (currentEditMessageElement) {
+                cancelEdit();
+            }
+
+            currentEditMessageId = messageId;
+
+            // 找到对应的消息元素
+            const messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
+            if (!messageElement) {
+                console.error('找不到消息元素');
+                return;
+            }
+
+            currentEditMessageElement = messageElement;
+            window.isEditingMessage = true; // 设置编辑状态
+
+            // 保存当前滚动位置
+            const chatMessages = document.getElementById('chat-messages');
+            const currentScrollTop = chatMessages.scrollTop;
+
+            // 解码HTML实体
+            const decodedContent = originalContent
+                .replace(/&quot;/g, '"')
+                .replace(/&#39;/g, "'")
+                .replace(/&lt;/g, '<')
+                .replace(/&gt;/g, '>')
+                .replace(/&amp;/g, '&');
+
+            // 创建编辑表单
+            const editForm = document.createElement('div');
+            editForm.className = 'edit-form';
+            editForm.innerHTML = `
+                <textarea class="edit-textarea" placeholder="请输入消息内容...">${decodedContent}</textarea>
+                <div class="edit-actions">
+                    <button class="edit-cancel-btn" onclick="cancelEdit()">取消</button>
+                    <button class="edit-send-btn" onclick="confirmEdit()">发送</button>
+                </div>
+            `;
+
+            // 隐藏原始消息内容,显示编辑表单
+            const messageContent = messageElement.querySelector('.message-content');
+            if (messageContent) {
+                messageContent.style.display = 'none';
+            }
+
+            // 隐藏编辑按钮
+            const editButton = messageElement.querySelector('.edit-button');
+            if (editButton) {
+                editButton.style.display = 'none';
+            }
+
+            // 添加编辑表单到消息元素
+            messageElement.appendChild(editForm);
+            messageElement.classList.add('message-editing');
+
+            // 恢复滚动位置
+            chatMessages.scrollTop = currentScrollTop;
+
+            // 聚焦到文本域
+            const textarea = editForm.querySelector('.edit-textarea');
+            if (textarea) {
+                textarea.focus();
+                textarea.select();
+
+                // 绑定键盘事件:Enter 发送(Shift+Enter 换行),Esc 取消
+                textarea.onkeydown = function (e) {
+                    if (e.key === 'Enter') {
+                        if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey || e.isComposing) {
+                            return; // 保留换行/组合键
+                        }
+                        e.preventDefault();
+                        confirmEdit();
+                    } else if (e.key === 'Escape') {
+                        cancelEdit();
+                    }
+                };
+            }
+        }
+
+        function cancelEdit() {
+            if (currentEditMessageElement) {
+                // 保存当前滚动位置
+                const chatMessages = document.getElementById('chat-messages');
+                const currentScrollTop = chatMessages.scrollTop;
+
+                // 恢复原始消息内容
+                const messageContent = currentEditMessageElement.querySelector('.message-content');
+                if (messageContent) {
+                    messageContent.style.display = 'block';
+                }
+
+                // 显示编辑按钮
+                const editButton = currentEditMessageElement.querySelector('.edit-button');
+                if (editButton) {
+                    editButton.style.display = 'flex';
+                }
+
+                // 移除编辑表单
+                const editForm = currentEditMessageElement.querySelector('.edit-form');
+                if (editForm) {
+                    editForm.remove();
+                }
+
+                currentEditMessageElement.classList.remove('message-editing');
+
+                // 先恢复滚动位置,再清除编辑状态
+                chatMessages.scrollTop = currentScrollTop;
+
+                // 延迟清除编辑状态,确保DOM变化不会触发滚动
+                setTimeout(() => {
+                    currentEditMessageElement = null;
+                    currentEditMessageId = null;
+                    window.isEditingMessage = false;
+                }, 150);
+            }
+        }
+
+        function confirmEdit() {
+            if (!currentEditMessageElement || !currentEditMessageId) {
+                return;
+            }
+
+            const editForm = currentEditMessageElement.querySelector('.edit-form');
+            const textarea = editForm.querySelector('.edit-textarea');
+            const sendBtn = editForm.querySelector('.edit-send-btn');
+
+            if (!textarea || !sendBtn) {
+                return;
+            }
+
+            const newContent = textarea.value.trim();
+            if (!newContent) {
+                showMessage('消息内容不能为空', 'warning');
+                return;
+            }
+
+            // 显示加载状态
+            const originalText = sendBtn.textContent;
+            sendBtn.textContent = '发送中...';
+            sendBtn.disabled = true;
+
+            // 调用后端API编辑消息
+            performEditMessage(currentEditMessageId, newContent);
+        }
+
+        function performEditMessage(messageId, newContent) {
+            // 检查认证状态
+            const token = localStorage.getItem('auth_token');
+            if (!token) {
+                redirectToLogin();
+                return;
+            }
+
+            // 设置输入框为加载状态
+            const userInput = document.getElementById('user-input');
+            const sendButton = document.getElementById('send-btn');
+
+            if (userInput && sendButton) {
+                userInput.classList.add('input-loading');
+                userInput.placeholder = '正在重新生成回复...';
+                userInput.disabled = true;
+                sendButton.innerHTML = `
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <rect x="6" y="6" width="12" height="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                    </svg>`;
+                sendButton.classList.add('stopping');
+            }
+
+            // 隐藏编辑表单
+            if (currentEditMessageElement) {
+                const editForm = currentEditMessageElement.querySelector('.edit-form');
+                if (editForm) {
+                    editForm.style.display = 'none';
+                }
+            }
+
+            // ===== 立即隐藏将被删除的消息(编辑的消息及其之后的所有消息) =====
+            const editMessageElement = currentEditMessageElement;
+            const chatMessages = document.getElementById('chat-messages');
+            let messagesToHide = [];
+            if (editMessageElement && chatMessages) {
+                let foundEditMessage = false;
+                const allMessages = Array.from(chatMessages.querySelectorAll('.message'));
+                allMessages.forEach(msg => {
+                    if (msg === editMessageElement) {
+                        foundEditMessage = true;
+                        messagesToHide.push(msg);
+                        // 立即隐藏消息(但保留DOM元素,后续再删除)
+                        msg.style.display = 'none';
+                    } else if (foundEditMessage) {
+                        messagesToHide.push(msg);
+                        // 立即隐藏消息
+                        msg.style.display = 'none';
+                    }
+                });
+            }
+
+            // 创建新的用户消息显示(立即显示编辑后的内容)
+            function createNewUserMessageImmediately() {
+                const chatMessages = document.getElementById('chat-messages');
+                if (!chatMessages) return null;
+
+                const newUserMessageElement = document.createElement('div');
+                newUserMessageElement.className = 'message user';
+
+                const contentElement = document.createElement('div');
+                contentElement.className = 'message-content';
+                contentElement.textContent = newContent;
+
+                newUserMessageElement.appendChild(contentElement);
+
+                // 重新对话添加编辑按钮
+                if (typeof messageId !== 'undefined' && messageId !== null) {
+                    const editButton = document.createElement('button');
+                    editButton.className = 'edit-button';
+                    editButton.setAttribute('title', '编辑消息');
+                    editButton.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
+                    editButton.onclick = function () {
+                        if (typeof window.editMessage === 'function') window.editMessage(messageId, newContent);
+                    };
+                    newUserMessageElement.appendChild(editButton);
+                    newUserMessageElement.setAttribute('data-message-id', messageId);
+                }
+
+                chatMessages.appendChild(newUserMessageElement);
+
+                // 确保消息立即可见
+                newUserMessageElement.style.opacity = '1';
+                newUserMessageElement.style.transform = 'translateY(0)';
+
+                return newUserMessageElement;
+            }
+
+            // 立即创建并显示新的用户消息
+            const newUserMessageElement = createNewUserMessageImmediately();
+
+            // ===== 初始化对话状态(参考正常发送消息的逻辑) =====
+            const conversationId = window.currentConversationId || 'new';
+
+            // 初始化对话状态(如果不存在)
+            if (typeof window.initializeConversationState === 'function') {
+                window.initializeConversationState(conversationId);
+            }
+
+            // 获取或创建对话状态
+            const conversationStates = window.conversationStates || new Map();
+            const state = conversationStates.get(conversationId) || {
+                isStreaming: false,
+                isPaused: false,
+                currentMessage: '',
+                currentThoughts: [],
+                messageElement: null,
+                thoughtContainer: null,
+                abortController: null,
+                lastUpdateTime: Date.now(),
+                isVisible: false,
+                userMessages: []
+            };
+
+            // 重置流式响应状态
+            state.isStreaming = true;
+            state.isPaused = false;
+            state.currentMessage = '';
+            state.currentThoughts = [];
+            state.lastUpdateTime = Date.now();
+            state.isVisible = true;
+
+            // 保存用户消息到状态
+            if (!state.userMessages) {
+                state.userMessages = [];
+            }
+            state.userMessages.push({
+                content: newContent,
+                timestamp: new Date(),
+                element: newUserMessageElement,
+                messageId: messageId
+            });
+
+            // 更新状态映射
+            conversationStates.set(conversationId, state);
+            window.conversationStates = conversationStates;
+
+            // 更新侧边栏状态
+            if (typeof window.updateConversationItemStatus === 'function') {
+                window.updateConversationItemStatus(conversationId, 'streaming');
+            }
+
+            // ===== 创建AbortController用于取消请求(必须在fetch之前) =====
+            state.abortController = new AbortController();
+            conversationStates.set(conversationId, state);
+
+            // 调用后端API编辑消息(流式响应)
+            fetch(`/api/messages/${messageId}/edit`, {
+                method: 'POST',
+                headers: {
+                    'Authorization': `Bearer ${token}`,
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({
+                    content: newContent,
+                    conversation_id: window.currentConversationId,
+                    agent: localStorage.getItem('current_agent') || 'planning'
+                }),
+                signal: state.abortController.signal
+            })
+                .then(response => {
+                    if (response.status === 401) {
+                        // 恢复UI状态
+                        if (userInput && sendButton) {
+                            userInput.classList.remove('input-loading');
+                            userInput.placeholder = '请输入需要撰写的品类名和要求...';
+                            userInput.disabled = false;
+                            sendButton.innerHTML = `
+                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                                    <path d="M22 2L11 13M22 2L15 22L11 13M11 13L2 9L22 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                                </svg>`;
+                            sendButton.classList.remove('stopping');
+                        }
+                        currentEditMessageElement = null;
+                        currentEditMessageId = null;
+                        window.isEditingMessage = false;
+                        redirectToLogin();
+                        return;
+                    }
+
+                    if (!response.ok) {
+                        throw new Error(`HTTP error! Status: ${response.status}`);
+                    }
+
+                    // 使用流式读取器处理SSE响应
+                    const reader = response.body.getReader();
+                    const decoder = new TextDecoder();
+                    let buffer = '';
+
+                    // 用于实时渲染的变量(保存到state中,便于切换对话时恢复)
+                    let fullMessage = '';
+                    let allThoughts = [];
+                    let thoughtContainer = null;
+                    let thoughtContent = null;
+                    let contentContainer = null;
+                    let messageDiv = null;
+                    // newUserMessageElement 已经在前面创建了
+                    let lastRenderTime = 0;
+                    const RENDER_INTERVAL = 16;
+
+                    // 创建AI回复消息容器
+                    function createBotMessageContainer() {
+                        if (messageDiv) return messageDiv;
+
+                        messageDiv = document.createElement('div');
+                        messageDiv.className = 'message bot';
+
+                        // 添加对话ID标识,便于后续管理
+                        messageDiv.setAttribute('data-conversation-id', conversationId);
+
+                        // 创建思考过程容器
+                        thoughtContainer = document.createElement('div');
+                        thoughtContainer.className = 'thought-container';
+
+                        const thoughtToggleBtn = document.createElement('button');
+                        thoughtToggleBtn.className = 'thought-toggle-btn';
+                        thoughtToggleBtn.innerHTML = '<span class="toggle-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 15L12 9L6 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>';
+                        thoughtToggleBtn.title = '收起/展开思考过程';
+                        thoughtToggleBtn.onclick = function (e) {
+                            e.stopPropagation();
+                            thoughtContainer.classList.toggle('collapsed');
+                            const icon = thoughtToggleBtn.querySelector('.toggle-icon');
+                            if (thoughtContainer.classList.contains('collapsed')) {
+                                icon.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 9L12 15L18 9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
+                            } else {
+                                icon.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 15L12 9L6 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
+                            }
+                        };
+                        thoughtContainer.appendChild(thoughtToggleBtn);
+
+                        thoughtContent = document.createElement('div');
+                        thoughtContent.className = 'thought-content';
+                        thoughtContainer.appendChild(thoughtContent);
+
+                        messageDiv.appendChild(thoughtContainer);
+
+                        const chatMessages = document.getElementById('chat-messages');
+                        if (chatMessages) {
+                            chatMessages.appendChild(messageDiv);
+                        }
+
+                        // ===== 保存到对话状态(关键!) =====
+                        state.messageElement = messageDiv;
+                        state.thoughtContainer = thoughtContainer;
+                        conversationStates.set(conversationId, state);
+
+                        // 确保消息元素可见
+                        messageDiv.style.opacity = '1';
+                        messageDiv.style.transform = 'translateY(0)';
+
+                        return messageDiv;
+                    }
+
+                    function readStream() {
+                        reader.read().then(({ done, value }) => {
+                            if (done) {
+                                // 流式响应完成
+                                state.isStreaming = false;
+                                if (typeof window.updateConversationItemStatus === 'function') {
+                                    window.updateConversationItemStatus(conversationId, 'stopped');
+                                }
+                                conversationStates.set(conversationId, state);
+                                updateUIOnComplete();
+                                return;
+                            }
+
+                            const chunk = decoder.decode(value, { stream: true });
+                            buffer += chunk;
+
+                            const lines = buffer.split('\n\n');
+                            buffer = lines.pop() || '';
+
+                            lines.forEach(line => {
+                                if (line.startsWith('data: ')) {
+                                    try {
+                                        const eventData = JSON.parse(line.substring(6));
+                                        handleEditEventData(eventData);
+                                    } catch (error) {
+                                        console.error('解析SSE数据失败:', error);
+                                    }
+                                }
+                            });
+
+                            // 无论是否暂停,都继续读取流数据
+                            readStream();
+                        }).catch(error => {
+                            if (!state.isPaused) {
+                                console.error('读取流数据失败:', error);
+                                showMessage('编辑消息时发生错误: ' + error.message, 'error');
+                            }
+                            state.isStreaming = false;
+                            if (typeof window.updateConversationItemStatus === 'function') {
+                                window.updateConversationItemStatus(conversationId, 'stopped');
+                            }
+                            conversationStates.set(conversationId, state);
+                            updateUIOnError();
+                        });
+                    }
+
+                    function handleEditEventData(data) {
+                        // 更新最后更新时间
+                        state.lastUpdateTime = Date.now();
+
+                        if (!data.success) {
+                            showMessage('编辑消息失败: ' + (data.error || '未知错误'), 'error');
+                            state.isStreaming = false;
+                            if (typeof window.updateConversationItemStatus === 'function') {
+                                window.updateConversationItemStatus(conversationId, 'stopped');
+                            }
+                            updateUIOnError();
+                            return;
+                        }
+
+                        if (data.thought) {
+                            // AI回复容器已存在或需要创建
+                            allThoughts.push(data.thought);
+                            // ===== 保存思考过程到状态 =====
+                            state.currentThoughts = allThoughts;
+
+                            createBotMessageContainer();
+                            if (thoughtContent) {
+                                const combinedThoughts = allThoughts.join('');
+                                if (typeof window.renderThoughts === 'function') {
+                                    thoughtContent.innerHTML = window.renderThoughts(combinedThoughts);
+                                } else {
+                                    thoughtContent.textContent = combinedThoughts;
+                                }
+                                // 更新状态
+                                conversationStates.set(conversationId, state);
+                                forceScrollToBottom();
+                            }
+                            return;
+                        }
+
+                        if (data.chunk) {
+                            // AI回复容器已存在或需要创建
+                            fullMessage += data.chunk;
+                            // ===== 保存消息内容到状态 =====
+                            state.currentMessage = fullMessage;
+
+                            createBotMessageContainer();
+
+                            if (!contentContainer) {
+                                contentContainer = document.createElement('div');
+                                contentContainer.className = 'message-content typing';
+                                messageDiv.appendChild(contentContainer);
+                            }
+
+                            const now = performance.now();
+                            if (now - lastRenderTime >= RENDER_INTERVAL) {
+                                requestAnimationFrame(() => {
+                                    if (contentContainer) {
+                                        if (typeof window.renderMarkdown === 'function') {
+                                            contentContainer.innerHTML = window.renderMarkdown(fullMessage);
+                                            if (typeof window.applyCodeHighlighting === 'function') {
+                                                window.applyCodeHighlighting(contentContainer);
+                                            }
+                                        } else {
+                                            contentContainer.textContent = fullMessage;
+                                        }
+                                        // 更新状态
+                                        conversationStates.set(conversationId, state);
+                                        if (isUserNearBottom()) {
+                                            forceScrollToBottom();
+                                        }
+                                    }
+                                });
+                                lastRenderTime = now;
+                            }
+                        }
+
+                        if (data.session_id) {
+                            window.currentSessionId = data.session_id;
+                        }
+
+                        if (data.done) {
+                            if (contentContainer) {
+                                contentContainer.classList.remove('typing');
+                                if (typeof window.renderMarkdown === 'function') {
+                                    contentContainer.innerHTML = window.renderMarkdown(fullMessage);
+                                    if (typeof window.applyCodeHighlighting === 'function') {
+                                        window.applyCodeHighlighting(contentContainer);
+                                    }
+                                } else {
+                                    contentContainer.textContent = fullMessage;
+                                }
+                            }
+
+                            // ===== 标记流式响应完成,但保留状态 =====
+                            state.isStreaming = false;
+                            state.currentMessage = fullMessage;
+                            state.currentThoughts = allThoughts;
+                            conversationStates.set(conversationId, state);
+
+                            if (typeof window.updateConversationItemStatus === 'function') {
+                                window.updateConversationItemStatus(conversationId, 'stopped');
+                            }
+
+                            updateUIOnComplete();
+                        }
+                    }
+
+                    function updateUIOnComplete() {
+                        // 删除旧的消息元素(被编辑的消息及其后的所有消息)
+                        // 使用chat-messages容器来查找并删除所有相关消息
+                        const chatMessages = document.getElementById('chat-messages');
+                        if (chatMessages && editMessageElement) {
+                            // 获取所有消息元素
+                            const allMessages = Array.from(chatMessages.querySelectorAll('.message'));
+                            let foundEditMessage = false;
+
+                            allMessages.forEach(msg => {
+                                // 找到被编辑的消息
+                                if (msg === editMessageElement) {
+                                    foundEditMessage = true;
+                                    // 删除编辑表单
+                                    const editForm = msg.querySelector('.edit-form');
+                                    if (editForm) {
+                                        editForm.remove();
+                                    }
+                                    msg.classList.remove('message-editing');
+                                    // 删除整个消息元素(除了新创建的用户消息和AI回复)
+                                    if (msg && msg.parentNode && msg !== messageDiv && msg !== newUserMessageElement) {
+                                        msg.remove();
+                                    }
+                                } else if (foundEditMessage) {
+                                    // 删除被编辑消息之后的所有消息(除了新创建的用户消息和AI回复)
+                                    if (msg && msg.parentNode && msg !== messageDiv && msg !== newUserMessageElement) {
+                                        msg.remove();
+                                    }
+                                }
+                            });
+                        }
+
+                        // 如果editMessageElement还存在但没有被删除,手动删除它
+                        if (editMessageElement && editMessageElement.parentNode) {
+                            editMessageElement.remove();
+                        }
+
+                        currentEditMessageElement = null;
+                        currentEditMessageId = null;
+                        window.isEditingMessage = false;
+
+                        if (userInput && sendButton) {
+                            userInput.classList.remove('input-loading');
+                            userInput.placeholder = '请输入需要撰写的品类名和要求...';
+                            userInput.disabled = false;
+                            sendButton.innerHTML = `
+                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                                    <path d="M22 2L11 13M22 2L15 22L11 13M11 13L2 9L22 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                                </svg>`;
+                            sendButton.classList.remove('stopping');
+                        }
+
+                        // 只刷新侧边栏的对话列表,不重新加载消息(避免旧消息重新出现)
+                        if (window.currentConversationId && typeof loadConversationHistory === 'function') {
+                            setTimeout(() => {
+                                loadConversationHistory(false); // 只更新侧边栏,不重新加载消息
+                            }, 300);
+                        }
+
+                        // 为新创建的消息添加复制按钮
+                        if (typeof window.setupCopyButtons === 'function') {
+                            window.setupCopyButtons();
+                        }
+
+                        forceScrollToBottom();
+                    }
+
+                    function updateUIOnError() {
+                        if (currentEditMessageElement) {
+                            const sendBtn = currentEditMessageElement.querySelector('.edit-send-btn');
+                            if (sendBtn) {
+                                sendBtn.textContent = '发送';
+                                sendBtn.disabled = false;
+                            }
+                        }
+
+                        if (userInput && sendButton) {
+                            userInput.classList.remove('input-loading');
+                            userInput.placeholder = '请输入需要撰写的品类名和要求...';
+                            userInput.disabled = false;
+                            sendButton.innerHTML = `
+                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                                    <path d="M22 2L11 13M22 2L15 22L11 13M11 13L2 9L22 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                                </svg>`;
+                            sendButton.classList.remove('stopping');
+                        }
+
+                        currentEditMessageElement = null;
+                        currentEditMessageId = null;
+                        window.isEditingMessage = false;
+                    }
+
+                    function forceScrollToBottom() {
+                        const chatMessages = document.getElementById('chat-messages');
+                        if (chatMessages) {
+                            chatMessages.scrollTop = chatMessages.scrollHeight;
+                        }
+                    }
+
+                    function isUserNearBottom() {
+                        const chatMessages = document.getElementById('chat-messages');
+                        if (!chatMessages) return true;
+                        const scrollThreshold = 50;
+                        return chatMessages.scrollHeight - chatMessages.scrollTop - chatMessages.clientHeight < scrollThreshold;
+                    }
+
+                    readStream();
+                })
+                .catch(error => {
+                    // 确保在错误时也更新状态
+                    if (error.name !== 'AbortError') {
+                        state.isStreaming = false;
+                        if (typeof window.updateConversationItemStatus === 'function') {
+                            window.updateConversationItemStatus(conversationId, 'stopped');
+                        }
+                        conversationStates.set(conversationId, state);
+                    }
+
+                    console.error('编辑消息时发生错误:', error);
+                    showMessage('编辑消息时发生错误: ' + error.message, 'error');
+
+                    // 恢复UI状态
+                    if (currentEditMessageElement) {
+                        const sendBtn = currentEditMessageElement.querySelector('.edit-send-btn');
+                        if (sendBtn) {
+                            sendBtn.textContent = '发送';
+                            sendBtn.disabled = false;
+                        }
+                    }
+
+                    if (userInput && sendButton) {
+                        userInput.classList.remove('input-loading');
+                        userInput.placeholder = '请输入需要撰写的品类名和要求...';
+                        userInput.disabled = false;
+                        sendButton.innerHTML = `
+                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                                <path d="M22 2L11 13M22 2L15 22L11 13M11 13L2 9L22 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                            </svg>`;
+                        sendButton.classList.remove('stopping');
+                    }
+
+                    currentEditMessageElement = null;
+                    currentEditMessageId = null;
+                    window.isEditingMessage = false;
+                });
+        }
+    </script>
+</body>
+
+</html>

+ 100 - 0
templates/login.html

@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+	<meta charset="UTF-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<title>登录 - 聚德AI短视频脚本撰写</title>
+	<meta name="description" content="登录聚德AI短视频脚本撰写器">
+	<meta name="theme-color" content="#6366f1">
+	<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
+	<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
+	<link rel="icon" href="{{ url_for('static', filename='logo.png') }}">
+	<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts.css') }}">
+</head>
+
+<body>
+	<!-- 背景动画元素 -->
+	<div class="animated-background">
+		<div class="floating-shapes">
+			<div class="shape shape-1"></div>
+			<div class="shape shape-2"></div>
+			<div class="shape shape-3"></div>
+			<div class="shape shape-4"></div>
+			<div class="shape shape-5"></div>
+		</div>
+		<div class="gradient-overlay"></div>
+	</div>
+
+	<div class="login-container">
+		<div class="login-card glass-panel">
+			<!-- 装饰性元素 -->
+			<div class="card-decoration">
+				<div class="decoration-line"></div>
+				<div class="decoration-dot"></div>
+				<div class="decoration-line"></div>
+			</div>
+
+			<div class="login-header">
+				<div class="logo-container">
+					<div class="logo-glow"></div>
+					<img src="{{ url_for('static', filename='logo.png') }}" alt="聚德AI" class="login-logo">
+				</div>
+				<h1>聚德AI短视频脚本撰写</h1>
+				<p class="login-subtitle">智能创作,高效输出</p>
+				<div class="header-underline"></div>
+			</div>
+
+			<div class="login-methods">
+				<!-- 钉钉登录 -->
+				<div class="login-method">
+					<div class="method-icon">
+						<div class="icon-bg"></div>
+						<img src="{{ url_for('static', filename='img/dingding.svg') }}" alt="钉钉" class="method-svg">
+					</div>
+					<div class="method-info">
+						<h3>钉钉登录</h3>
+						<p>钉钉账号快速登录</p>
+					</div>
+					<button class="login-btn dingtalk-btn" id="dingtalk-login">
+						<span class="btn-text">钉钉登录</span>
+						<span class="btn-icon">→</span>
+					</button>
+				</div>
+
+				<!-- 企业微信登录 -->
+				<div class="login-method">
+					<div class="method-icon">
+						<!-- <div class="icon-bg"></div> -->
+						<img style="width: 50px;height: 50px;" src="{{ url_for('static', filename='img/wx.svg') }}" alt="企业微信"
+							class="method-svg">
+					</div>
+					<div class="method-info">
+						<h3>企业微信登录</h3>
+						<p>企业微信账号快速登录</p>
+					</div>
+					<button class="login-btn dingtalk-btn" id="wx-login">
+						<span class="btn-text">企业微信登录</span>
+						<span class="btn-icon">→</span>
+					</button>
+				</div>
+			</div>
+
+			<div class="login-footer">
+				<p>©2025 成都聚德鼎盛文化传播有限公司</p>
+			</div>
+		</div>
+
+		<!-- 登录状态提示 -->
+		<div id="login-status" class="login-status" style="display: none;">
+			<div class="status-content glass-panel">
+				<div class="status-icon">⏳</div>
+				<div class="status-text">正在处理登录...</div>
+			</div>
+		</div>
+	</div>
+
+	<script src="{{ url_for('static', filename='js/login.js') }}"></script>
+</body>
+
+</html>

+ 281 - 0
templates/login_error.html

@@ -0,0 +1,281 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+	<meta charset="UTF-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<title>登录失败 - 聚德AI短视频脚本撰写</title>
+	<meta name="description" content="登录失败 - 聚德AI短视频脚本撰写器">
+	<meta name="theme-color" content="#6366f1">
+	<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
+	<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
+	<link rel="icon" href="{{ url_for('static', filename='logo.png') }}">
+	<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts.css') }}">
+	<style>
+		/* 错误页面特定样式 - 布局优化版本 */
+
+		/* 覆盖登录容器的高度设置 */
+		.login-container {
+			min-height: 40vh !important;
+			/* 减少60vh,从100vh减到40vh */
+			padding: 15px !important;
+			/* 减少内边距 */
+			display: flex !important;
+			/* 确保flex布局 */
+			align-items: center !important;
+			/* 垂直居中 */
+			justify-content: center !important;
+			/* 水平居中 */
+		}
+
+		/* 覆盖登录卡片的宽度设置 */
+		.login-card {
+			max-width: 680px !important;
+			/* 从560px增加到680px */
+			padding: 35px 40px !important;
+			/* 减少上下内边距,从50px减到35px */
+			text-align: center !important;
+			/* 确保文本居中 */
+		}
+
+		/* 优化登录头部间距 */
+		.login-header {
+			margin-bottom: 35px !important;
+			/* 从50px减少到35px */
+		}
+
+		.error-icon {
+			display: inline-flex;
+			justify-content: center;
+			align-items: center;
+			position: relative;
+			margin-bottom: 10px;
+			/* 从25px减少到20px */
+		}
+
+		.error-icon-bg {
+			position: absolute;
+			top: 50%;
+			left: 50%;
+			transform: translate(-50%, -50%);
+			width: 120px;
+			height: 120px;
+			background: radial-gradient(circle, rgba(239, 68, 68, 0.2) 0%, transparent 70%);
+			border-radius: 50%;
+			animation: errorGlow 3s ease-in-out infinite alternate;
+		}
+
+		.error-icon-circle {
+			width: 90px;
+			height: 90px;
+			background: linear-gradient(135deg, #ef4444, #dc2626);
+			border-radius: 50%;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			box-shadow: 0 8px 32px rgba(239, 68, 68, 0.2);
+			position: relative;
+			z-index: 1;
+		}
+
+		.error-icon svg {
+			width: 40px;
+			height: 40px;
+			color: white;
+		}
+
+		@keyframes errorGlow {
+			from {
+				opacity: 0.5;
+				transform: translate(-50%, -50%) scale(1);
+			}
+
+			to {
+				opacity: 0.8;
+				transform: translate(-50%, -50%) scale(1.1);
+			}
+		}
+
+		.error-content h2 {
+			font-size: 28px;
+			font-weight: 700;
+			color: #1e293b;
+			margin-bottom: 10px;
+			background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
+			-webkit-background-clip: text;
+			-webkit-text-fill-color: transparent;
+			background-clip: text;
+		}
+
+		.error-content p {
+			font-size: 16px;
+			color: #64748b;
+			margin-bottom: 15px;
+			/* 从30px减少到25px */
+			font-weight: 400;
+		}
+
+		.retry-button {
+			display: inline-flex;
+			align-items: center;
+			justify-content: center;
+			gap: 12px;
+			width: 100%;
+			padding: 16px 32px;
+			/* 从18px减少到16px */
+			background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
+			color: white;
+			border: none;
+			border-radius: 16px;
+			font-size: 16px;
+			font-weight: 600;
+			text-decoration: none;
+			transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+			box-shadow: 0 8px 25px rgba(59, 130, 246, 0.3);
+			margin-bottom: 12px;
+			/* 从20px减少到18px */
+		}
+
+		.retry-button:hover {
+			transform: translateY(-2px);
+			box-shadow: 0 12px 35px rgba(59, 130, 246, 0.4);
+		}
+
+		.help-links {
+			display: flex;
+			justify-content: center;
+			gap: 20px;
+			margin-top: 10px;
+			/* 从20px减少到15px */
+		}
+
+		.help-link {
+			color: #64748b;
+			text-decoration: none;
+			font-size: 14px;
+			transition: color 0.3s ease;
+		}
+
+		.help-link:hover {
+			color: #3b82f6;
+		}
+
+		/* 响应式设计优化 */
+		@media (max-width: 768px) {
+			.login-container {
+				min-height: 100vh !important;
+				/* 移动端保持全屏高度 */
+				padding: 20px !important;
+				display: flex !important;
+				/* 确保flex布局 */
+				align-items: center !important;
+				/* 垂直居中 */
+				justify-content: center !important;
+				/* 水平居中 */
+			}
+
+			.login-card {
+				max-width: 100% !important;
+				/* 移动端使用全宽 */
+				padding: 30px 25px !important;
+				text-align: center !important;
+				/* 确保文本居中 */
+			}
+		}
+
+		@media (max-width: 480px) {
+			.login-card {
+				padding: 25px 20px !important;
+			}
+
+			.error-content h2 {
+				font-size: 24px;
+			}
+
+			.error-content p {
+				font-size: 14px;
+			}
+		}
+	</style>
+</head>
+
+<body>
+	<!-- 背景动画元素 -->
+	<div class="animated-background">
+		<div class="floating-shapes">
+			<div class="shape shape-1"></div>
+			<div class="shape shape-2"></div>
+			<div class="shape shape-3"></div>
+			<div class="shape shape-4"></div>
+			<div class="shape shape-5"></div>
+		</div>
+		<div class="gradient-overlay"></div>
+	</div>
+
+	<div class="login-container">
+		<div class="login-card glass-panel">
+			<!-- 装饰性元素 -->
+			<div class="card-decoration">
+				<div class="decoration-line"></div>
+				<div class="decoration-dot"></div>
+				<div class="decoration-line"></div>
+			</div>
+
+			<div class="login-header">
+				<div class="logo-container">
+					<div class="logo-glow"></div>
+					<img src="{{ url_for('static', filename='logo.png') }}" alt="聚德AI" class="login-logo">
+				</div>
+				<h1>聚德AI短视频脚本撰写</h1>
+				<p class="login-subtitle">智能创作,高效输出</p>
+				<div class="header-underline"></div>
+			</div>
+
+			<!-- 错误内容 -->
+			<div class="login-methods">
+				<!-- 错误图标 -->
+				<div class="error-icon">
+					<div class="error-icon-bg"></div>
+					<div class="error-icon-circle">
+						<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+							<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
+								d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
+						</svg>
+					</div>
+				</div>
+
+				<!-- 错误内容 -->
+				<div class="error-content">
+					<h2>登录失败</h2>
+					<p>很抱歉,登录过程中遇到了问题</p>
+				</div>
+
+
+				<!-- 重试按钮 -->
+				<a href="{{ url_for('login') }}" class="retry-button">
+					<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+						<path
+							d="M4 4V9H4.58152M19.9381 11C19.446 7.05369 16.0796 4 12 4C8.64262 4 5.76829 6.06817 4.58152 9M4.58152 9H9M20 20V15H19.4185M19.4185 15C18.2317 17.9318 15.3574 20 12 20C7.92038 20 4.55399 16.9463 4.06189 13M19.4185 15H15"
+							stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
+					</svg>
+					重新登录
+				</a>
+
+				<!-- 帮助链接 -->
+				<div class="help-links">
+					<a href="javascript:history.back()" class="help-link">返回上一页</a>
+					<a href="{{ url_for('index') }}" class="help-link">返回首页</a>
+				</div>
+			</div>
+
+			<!-- 页脚信息 -->
+			<div class="login-footer">
+				<p>© 2025 成都聚德鼎盛文化传播有限公司</p>
+			</div>
+		</div>
+	</div>
+
+
+</body>
+
+</html>

+ 221 - 0
utils.py

@@ -0,0 +1,221 @@
+import re
+import time
+import hashlib
+from typing import Dict, List, Optional, Tuple
+from functools import wraps
+from flask import request, jsonify
+from http import HTTPStatus
+from datetime import datetime, timezone, timedelta
+
+def get_beijing_time() -> datetime:
+    """
+    获取北京时间(UTC+8)
+    
+    Returns:
+        北京时间的datetime对象
+    """
+    beijing_tz = timezone(timedelta(hours=8))
+    return datetime.now(beijing_tz)
+
+def sanitize_input(text: str, max_length: int = 20000) -> Tuple[str, Optional[str]]:
+    """
+    清理和验证用户输入
+    
+    Args:
+        text: 输入文本
+        max_length: 最大长度限制
+        
+    Returns:
+        (清理后的文本, 错误信息)
+    """
+    if not text or not text.strip():
+        return "", "消息不能为空"
+    
+    # 移除首尾空白
+    text = text.strip()
+    
+    # 检查长度
+    if len(text) > max_length:
+        return "", f"消息长度不能超过{max_length}字符"
+    
+    # 移除潜在的恶意脚本标签
+    text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.IGNORECASE | re.DOTALL)
+    text = re.sub(r'<iframe[^>]*>.*?</iframe>', '', text, flags=re.IGNORECASE | re.DOTALL)
+    
+    # 限制连续换行
+    text = re.sub(r'\n{3,}', '\n\n', text)
+    
+    return text, None
+
+def validate_user_id(user_id: str) -> Tuple[str, Optional[str]]:
+    """
+    验证用户ID
+    
+    Args:
+        user_id: 用户ID
+        
+    Returns:
+        (验证后的用户ID, 错误信息)
+    """
+    if not user_id:
+        return "", "用户ID不能为空"
+    
+    # 检查用户ID格式(UUID格式)
+    uuid_pattern = re.compile(
+        r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
+        re.IGNORECASE
+    )
+    
+    if not uuid_pattern.match(user_id):
+        return "", "用户ID格式无效"
+    
+    return user_id, None
+
+class RateLimiter:
+    """简单的内存速率限制器"""
+    
+    def __init__(self, max_requests: int, window_seconds: int):
+        self.max_requests = max_requests
+        self.window_seconds = window_seconds
+        self.requests: Dict[str, List[float]] = {}
+    
+    def is_allowed(self, identifier: str) -> bool:
+        """
+        检查请求是否被允许
+        
+        Args:
+            identifier: 请求标识符(如IP地址或用户ID)
+            
+        Returns:
+            是否允许请求
+        """
+        now = time.time()
+        
+        # 清理过期的请求记录
+        if identifier in self.requests:
+            self.requests[identifier] = [
+                req_time for req_time in self.requests[identifier]
+                if now - req_time < self.window_seconds
+            ]
+        else:
+            self.requests[identifier] = []
+        
+        # 检查是否超过限制
+        if len(self.requests[identifier]) >= self.max_requests:
+            return False
+        
+        # 记录新请求
+        self.requests[identifier].append(now)
+        return True
+    
+    def get_remaining(self, identifier: str) -> int:
+        """获取剩余请求次数"""
+        now = time.time()
+        if identifier in self.requests:
+            valid_requests = [
+                req_time for req_time in self.requests[identifier]
+                if now - req_time < self.window_seconds
+            ]
+            return max(0, self.max_requests - len(valid_requests))
+        return self.max_requests
+
+def rate_limit(max_requests: int, window_seconds: int, identifier_func=None):
+    """
+    速率限制装饰器
+    
+    Args:
+        max_requests: 最大请求数
+        window_seconds: 时间窗口(秒)
+        identifier_func: 获取标识符的函数,默认为IP地址
+    """
+    limiter = RateLimiter(max_requests, window_seconds)
+    
+    def decorator(f):
+        @wraps(f)
+        def decorated_function(*args, **kwargs):
+            # 获取请求标识符
+            if identifier_func:
+                identifier = identifier_func()
+            else:
+                identifier = request.remote_addr or 'unknown'
+            
+            if not limiter.is_allowed(identifier):
+                remaining_time = window_seconds
+                return jsonify({
+                    'success': False,
+                    'error': f'请求过于频繁,请等待{remaining_time}秒后重试',
+                    'rate_limit': {
+                        'max_requests': max_requests,
+                        'window_seconds': window_seconds,
+                        'remaining': 0
+                    }
+                }), HTTPStatus.TOO_MANY_REQUESTS
+            
+            # 添加速率限制信息到响应头
+            response = f(*args, **kwargs)
+            if hasattr(response, 'headers'):
+                response.headers['X-RateLimit-Limit'] = str(max_requests)
+                response.headers['X-RateLimit-Remaining'] = str(limiter.get_remaining(identifier))
+                response.headers['X-RateLimit-Reset'] = str(int(time.time() + window_seconds))
+            
+            return response
+        return decorated_function
+    return decorator
+
+def truncate_conversation_history(history: List[Dict[str, str]], max_length: int) -> List[Dict[str, str]]:
+    """
+    截断对话历史,保持最新的消息
+    
+    Args:
+        history: 对话历史列表
+        max_length: 最大历史长度
+        
+    Returns:
+        截断后的对话历史
+    """
+    if len(history) <= max_length:
+        return history
+    
+    # 保留最新的消息,但确保至少保留用户和助手的各一条消息
+    if max_length >= 2:
+        return history[-max_length:]
+    else:
+        return history[-1:] if history else []
+
+def generate_session_id(user_id: int, conversation_id: int = None) -> str:
+    """
+    生成基于时间和用户ID的session_id
+    
+    Args:
+        user_id: 用户ID
+        conversation_id: 对话ID(可选)
+        
+    Returns:
+        生成的session_id字符串(16位)
+    """
+    # 获取当前时间戳(毫秒)
+    timestamp = int(time.time() * 1000)
+    
+    # 构建基础字符串
+    base_string = f"{user_id}_{timestamp}"
+    if conversation_id:
+        base_string += f"_{conversation_id}"
+    
+    # 使用MD5生成哈希值,然后取前16位
+    hash_object = hashlib.md5(base_string.encode())
+    session_id = hash_object.hexdigest()[:16]  # 只取前16位
+    
+    return session_id
+
+def log_request_info(message: str, user_id: str, request_method: str = None):
+    """记录请求信息的辅助函数"""
+    if not request_method:
+        request_method = request.method
+    
+    from app import logger
+    logger.info(
+        f"收到{request_method}请求: "
+        f"消息长度={len(message)}字符, "
+        f"user_id={user_id}, "
+        f"IP={request.remote_addr or 'unknown'}"
+    ) 

Some files were not shown because too many files changed in this diff