基于多个大模型自己建造一个AI智能助手(增强版)

基于多个大模型自己建造一个AI智能助手(增强版)

基础版本可见https://blog.csdn.net/cnds123/article/details/155228249

利用开源大模型和API构建一个具有自然语言处理和多模态感知能力的AI智能助手。

此版本最大的改进是:

• 可以上传图片

• 历史记录保存及管理

运行截图如下:

整体架构概览

项目结构

D:/ai-assistant-enhanced/

├── backend/

│ └── main.py

└── frontend/

└── index.html

后端用到的各库的具体作用

需要安装的第三方库:

FastAPI

• 构建RESTful API

• 自动生成交互式API文档(Swagger UI)

• 高性能,基于Starlette和Pydantic

• 支持异步请求处理

Pydantic

• 数据验证和序列化

• 在BaseModel中定义数据结构

• 自动类型转换和验证

HTTPX

• 替代requests库的现代HTTP客户端

• 完全支持异步操作

• 用于调用DeepSeek API等外部服务

DashScope

• 封装阿里云的通义千问API调用

• 简化多模态和文本生成请求

• 处理认证和错误处理

Uvicorn

• ASGI服务器

python-multipart

• FastAPI在处理表单数据时可能需要它。

安装命令

pip install fastapi uvicorn pydantic httpx dashscope python-multipart

Python标准库(无需安装)

SQLite3

• 轻量级数据库

• 存储用户配置、对话历史、任务记录

• 无需额外安装数据库服务

json

• JSON编码解码

base64

• Base64编码解码

os

• 操作系统接口

asyncio

• 异步编程

typing

• 类型注解

datetime

• 日期时间处理

后端:backend/main.py 文件源码:

python 复制代码
# main.py - 修正版后端服务
from fastapi import FastAPI, HTTPException, UploadFile, File, Form
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional, List, Dict, Any
import json
import sqlite3
import asyncio
from datetime import datetime
import os
import base64
import httpx
import dashscope
from dashscope import Generation, MultiModalConversation

# 数据模型
class UserProfile(BaseModel):
    user_id: str
    preferences: Dict[str, Any]
    conversation_style: str = "professional"
    expertise_level: str = "beginner"

class ChatRequest(BaseModel):
    message: str
    user_id: Optional[str] = None
    model_type: str = "auto"
    context: Optional[Dict[str, Any]] = None

app = FastAPI(title="智能AI助手API")

# CORS配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 数据库初始化
def init_db():
    conn = sqlite3.connect('ai_assistant.db')
    cursor = conn.cursor()
    
    # 用户配置表
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS user_profiles (
            user_id TEXT PRIMARY KEY,
            preferences TEXT,
            conversation_style TEXT,
            expertise_level TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # 对话历史表
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS conversation_history (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id TEXT,
            message TEXT,
            response TEXT,
            timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            model_used TEXT
        )
    ''')
    
    # 任务执行记录表
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS task_history (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id TEXT,
            task_type TEXT,
            task_description TEXT,
            status TEXT,
            result TEXT,
            executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    conn.commit()
    conn.close()

# 模型管理器
class ModelManager:
    def __init__(self):
        self.models = {
            "text": {
                "qwen": {"provider": "dashscope", "model": "qwen-plus"},
                "deepseek": {"provider": "deepseek", "model": "deepseek-chat"},
            },
            "vision": {
                "qwen-vl": {"provider": "dashscope", "model": "qwen-vl-plus"}
            }
        }
    
    async def process_text(self, message: str, user_context: Dict) -> str:
        # 智能模型选择
        model_type = self.select_best_model(message, user_context)
        return await self.call_model(message, model_type, user_context)
    
    def select_best_model(self, message: str, user_context: Dict) -> str:
        # 基于内容和用户偏好选择最佳模型
        if "代码" in message or "programming" in message.lower():
            return "deepseek"
        elif "图像" in message or "图片" in message or "视觉" in message:
            return "qwen-vl"
        else:
            return user_context.get("preferred_model", "qwen")
    
    async def call_model(self, message: str, model_type: str, user_context: Dict) -> str:
        try:
            if model_type == "deepseek":
                return await self.call_deepseek(message)
            elif model_type == "qwen":
                return await self.call_qwen(message)
            elif model_type == "qwen-vl":
                # 对于视觉模型,返回提示信息,因为实际处理在multimodal端点
                return "请上传图片以使用视觉模型功能"
            else:
                return await self.call_qwen(message)
        except Exception as e:
            return f"模型调用错误: {str(e)}"
    
    async def call_deepseek(self, message: str) -> str:
        """调用DeepSeek API"""
        api_key = os.getenv("DEEPSEEK_API_KEY")
        if not api_key:
            return "DeepSeek API Key 未设置"
        
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }
        
        payload = {
            "model": "deepseek-chat",
            "messages": [{"role": "user", "content": message}],
            "stream": False
        }
        
        try:
            async with httpx.AsyncClient() as client:
                response = await client.post(
                    "https://api.deepseek.com/v1/chat/completions",
                    headers=headers,
                    json=payload,
                    timeout=30.0
                )
                
                if response.status_code == 200:
                    data = response.json()
                    return data["choices"][0]["message"]["content"]
                else:
                    return f"DeepSeek API 错误: {response.status_code} - {response.text}"
        except Exception as e:
            return f"DeepSeek API 调用异常: {str(e)}"
    
    async def call_qwen(self, message: str) -> str:
        """调用通义千问 API"""
        api_key = os.getenv("DASHSCOPE_API_KEY")
        if not api_key:
            return "DashScope API Key 未设置"
        
        dashscope.api_key = api_key
        
        try:
            response = Generation.call(
                model="qwen-plus",
                prompt=message
            )
            
            if response.status_code == 200:
                return response.output.text
            else:
                return f"通义千问 API 错误: {response.status_code} - {response.message}"
        except Exception as e:
            return f"通义千问 API 调用异常: {str(e)}"
    
    async def process_multimodal(self, text: str, images: List[UploadFile] = None) -> str:
        """处理多模态输入"""
        if not images:
            return await self.process_text(text, {})
        
        # 处理图像和文本
        try:
            api_key = os.getenv("DASHSCOPE_API_KEY")
            if not api_key:
                return "DashScope API Key 未设置,无法处理图像"
            
            dashscope.api_key = api_key
            
            # 读取第一张图片
            image_data = await images[0].read()
            image_base64 = base64.b64encode(image_data).decode('utf-8')
            
            messages = [
                {
                    "role": "user",
                    "content": [
                        {"image": f"data:image/jpeg;base64,{image_base64}"},
                        {"text": text if text else "请描述这张图片"}
                    ]
                }
            ]
            
            response = MultiModalConversation.call(model="qwen-vl-plus", messages=messages)
            
            if response.status_code == 200:
                return response.output.choices[0].message.content[0]['text']
            else:
                return f"多模态API错误: {response.code} - {response.message}"
                
        except Exception as e:
            return f"多模态处理异常: {str(e)}"

# 个性化服务管理器
class PersonalizationManager:
    def __init__(self):
        self.user_profiles = {}
    
    def get_user_profile(self, user_id: str) -> UserProfile:
        conn = sqlite3.connect('ai_assistant.db')
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM user_profiles WHERE user_id = ?', (user_id,))
        row = cursor.fetchone()
        conn.close()
        
        if row:
            return UserProfile(
                user_id=row[0],
                preferences=json.loads(row[1]),
                conversation_style=row[2],
                expertise_level=row[3]
            )
        else:
            # 创建默认用户配置
            default_profile = UserProfile(
                user_id=user_id,
                preferences={
                    "language": "zh-CN",
                    "preferred_model": "qwen",
                    "response_length": "medium"
                }
            )
            self.save_user_profile(default_profile)
            return default_profile
    
    def save_user_profile(self, profile: UserProfile):
        conn = sqlite3.connect('ai_assistant.db')
        cursor = conn.cursor()
        cursor.execute('''
            INSERT OR REPLACE INTO user_profiles 
            (user_id, preferences, conversation_style, expertise_level)
            VALUES (?, ?, ?, ?)
        ''', (profile.user_id, json.dumps(profile.preferences), 
              profile.conversation_style, profile.expertise_level))
        conn.commit()
        conn.close()

# 任务执行器
class TaskExecutor:
    async def execute_task(self, task_description: str, user_id: str) -> Dict[str, Any]:
        # 解析任务描述并执行相应操作
        if "搜索" in task_description or "search" in task_description.lower():
            return await self.web_search(task_description)
        elif "计算" in task_description or "calculate" in task_description:
            return await self.calculate(task_description)
        elif "提醒" in task_description or "remind" in task_description:
            return await self.set_reminder(task_description, user_id)
        else:
            return {"status": "unsupported", "message": "暂不支持此任务类型"}
    
    async def web_search(self, query: str) -> Dict[str, Any]:
        # 集成搜索引擎API
        return {"status": "completed", "results": f"搜索功能待实现: {query}"}
    
    async def calculate(self, expression: str) -> Dict[str, Any]:
        try:
            # 安全计算 - 移除"计算:"前缀
            clean_expression = expression.replace("计算:", "").strip()
            # 简单安全计算,仅支持基本运算
            allowed_chars = set('0123456789+-*/.() ')
            if all(c in allowed_chars for c in clean_expression):
                result = eval(clean_expression)
                return {"status": "completed", "result": str(result)}
            else:
                return {"status": "error", "message": "计算表达式包含不安全字符"}
        except:
            return {"status": "error", "message": "计算表达式无效"}
    
    async def set_reminder(self, reminder_text: str, user_id: str) -> Dict[str, Any]:
        # 设置提醒逻辑
        clean_reminder = reminder_text.replace("提醒:", "").strip()
        return {"status": "completed", "message": f"已设置提醒: {clean_reminder}"}

# 初始化组件
init_db()
model_manager = ModelManager()
personalization_manager = PersonalizationManager()
task_executor = TaskExecutor()

@app.post("/chat")
async def chat_endpoint(request: ChatRequest):
    """处理聊天请求"""
    try:
        # 获取用户配置
        user_profile = personalization_manager.get_user_profile(
            request.user_id or "anonymous"
        )
        
        # 处理消息
        response = await model_manager.process_text(
            request.message,
            user_profile.dict()
        )
        
        # 保存对话历史
        save_conversation_history(
            request.user_id or "anonymous",
            request.message,
            response,
            request.model_type
        )
        
        return {
            "response": response,
            "user_id": request.user_id,
            "timestamp": datetime.now().isoformat()
        }
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/multimodal")
async def multimodal_endpoint(
    text: str = Form(None),
    user_id: str = Form(...),
    images: List[UploadFile] = File(None)
):
    """处理多模态请求"""
    try:
        # 处理多模态输入
        response = await model_manager.process_multimodal(text, images)
        
        # 保存对话历史
        save_conversation_history(
            user_id,
            text or "[图片分析请求]",
            response,
            "qwen-vl"  # 多模态请求使用视觉模型
        )
        
        return {
            "response": response,
            "user_id": user_id,
            "timestamp": datetime.now().isoformat()
        }
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/execute_task")
async def execute_task_endpoint(task_description: str, user_id: str):
    """执行用户任务"""
    try:
        result = await task_executor.execute_task(task_description, user_id)
        
        # 记录任务执行
        save_task_history(user_id, task_description, result)
        
        return result
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/user_profile/{user_id}")
async def get_user_profile(user_id: str):
    """获取用户配置"""
    return personalization_manager.get_user_profile(user_id)

@app.put("/user_profile/{user_id}")
async def update_user_profile(user_id: str, profile: UserProfile):
    """更新用户配置"""
    personalization_manager.save_user_profile(profile)
    return {"status": "success", "message": "用户配置已更新"}

def save_conversation_history(user_id: str, message: str, response: str, model_used: str):
    """保存对话历史"""
    conn = sqlite3.connect('ai_assistant.db')
    cursor = conn.cursor()
    cursor.execute('''
        INSERT INTO conversation_history (user_id, message, response, model_used)
        VALUES (?, ?, ?, ?)
    ''', (user_id, message, response, model_used))
    conn.commit()
    conn.close()

def save_task_history(user_id: str, task_description: str, result: Dict[str, Any]):
    """保存任务执行记录"""
    conn = sqlite3.connect('ai_assistant.db')
    cursor = conn.cursor()
    cursor.execute('''
        INSERT INTO task_history (user_id, task_type, task_description, status, result)
        VALUES (?, ?, ?, ?, ?)
    ''', (user_id, "general", task_description, result.get("status", "unknown"), 
          json.dumps(result)))
    conn.commit()
    conn.close()

@app.get("/")
async def root():
    return {"message": "智能AI助手后端服务运行中"}

@app.get("/chat_history/{user_id}")
async def get_chat_history(user_id: str, limit: int = 20):
    """获取用户的聊天历史"""
    conn = sqlite3.connect('ai_assistant.db')
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT id, message, response, model_used, timestamp 
        FROM conversation_history 
        WHERE user_id = ? 
        ORDER BY timestamp DESC 
        LIMIT ?
    ''', (user_id, limit))
    
    rows = cursor.fetchall()
    conn.close()
    
    history = []
    for row in rows:
        history.append({
            "id": row[0],  # 添加ID字段
            "user_message": row[1],
            "ai_response": row[2],
            "model_used": row[3],
            "timestamp": row[4]
        })
    
    return {"history": history}

# 在 main.py 中修改删除聊天历史接口
@app.delete("/chat_history/{user_id}")
async def delete_chat_history(user_id: str, history_id: Optional[int] = None):
    """删除聊天历史"""
    conn = sqlite3.connect('ai_assistant.db')
    cursor = conn.cursor()
    
    if history_id:
        # 删除单条记录
        cursor.execute('DELETE FROM conversation_history WHERE user_id = ? AND id = ?', (user_id, history_id))
    else:
        # 删除用户所有历史
        cursor.execute('DELETE FROM conversation_history WHERE user_id = ?', (user_id,))
    
    conn.commit()
    conn.close()
    
    return {"status": "success", "message": "历史记录已删除"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

前端:frontend/index.html 文件源码

html 复制代码
<!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>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
    <style>
        :root {
            --primary-color: #4361ee;
            --secondary-color: #3f37c9;
            --success-color: #4cc9f0;
            --warning-color: #f72585;
            --light-bg: #f8f9fa;
            --dark-bg: #212529;
            --text-dark: #343a40;
            --text-light: #f8f9fa;
            --border-radius: 12px;
            --shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            --transition: all 0.3s ease;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: var(--text-dark);
            line-height: 1.6;
            min-height: 100vh;
            display: flex;
            flex-direction: column;
        }

        .app-container {
            display: flex;
            flex: 1;
            max-width: 1400px;
            margin: 20px auto;
            background: white;
            border-radius: var(--border-radius);
            box-shadow: var(--shadow);
            overflow: hidden;
            height: calc(100vh - 40px);
        }

        /* 侧边栏样式 */
        .sidebar {
            width: 300px;
            background: var(--dark-bg);
            color: var(--text-light);
            padding: 20px;
            display: flex;
            flex-direction: column;
            transition: var(--transition);
        }

        .sidebar-header {
            display: flex;
            align-items: center;
            margin-bottom: 30px;
        }

        .sidebar-header h1 {
            font-size: 1.5rem;
            margin-left: 10px;
        }

        .user-profile {
            display: flex;
            align-items: center;
            margin-bottom: 20px;
            padding: 10px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: var(--border-radius);
        }

        .user-avatar {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background: var(--primary-color);
            display: flex;
            align-items: center;
            justify-content: center;
            margin-right: 15px;
            font-size: 1.5rem;
        }

        .user-info h3 {
            font-size: 1rem;
            margin-bottom: 5px;
        }

        .user-info p {
            font-size: 0.8rem;
            opacity: 0.7;
        }

        .sidebar-section {
            margin-bottom: 25px;
        }

        .sidebar-section h3 {
            font-size: 1rem;
            margin-bottom: 15px;
            padding-bottom: 8px;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }

        /* 模型选择器样式优化 */
        .model-selector, .style-selector {
            width: 100%;
            padding: 10px;
            border-radius: var(--border-radius);
            background: rgba(255, 255, 255, 0.1);
            border: none;
            color: white;
            margin-bottom: 10px;
            cursor: pointer;
            font-size: 0.9rem;
        }

        /* 确保下拉选项在深色背景下可见 */
        .model-selector option, .style-selector option {
            background: var(--dark-bg);
            color: white;
            padding: 8px;
        }

        .model-info {
            font-size: 0.8rem;
            color: #aaa;
            margin-top: -5px;
            margin-bottom: 15px;
            padding: 5px 10px;
            background: rgba(255, 255, 255, 0.05);
            border-radius: 4px;
        }

        .history-section {
            flex: 1;
            display: flex;
            flex-direction: column;
        }

        .history-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
        }

        .clear-history {
            background: none;
            border: none;
            color: #ff6b6b;
            cursor: pointer;
            font-size: 0.8rem;
            display: flex;
            align-items: center;
        }

        .clear-history i {
            margin-right: 5px;
        }

        .clear-history:hover {
            color: #ff5252;
        }

        .history-list {
            flex: 1;
            overflow-y: auto;
            max-height: 300px;
        }

        .history-item {
            padding: 12px;
            margin-bottom: 8px;
            background: rgba(255, 255, 255, 0.05);
            border-radius: var(--border-radius);
            cursor: pointer;
            transition: var(--transition);
            font-size: 0.9rem;
            position: relative;
            border-left: 3px solid transparent;
        }

        .history-item:hover {
            background: rgba(255, 255, 255, 0.1);
            border-left-color: var(--primary-color);
        }

        .history-item.active {
            background: rgba(67, 97, 238, 0.2);
            border-left-color: var(--primary-color);
        }

        .history-content {
            margin-bottom: 8px;
            line-height: 1.4;
            max-height: 40px;
            overflow: hidden;
            text-overflow: ellipsis;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
        }

        .history-meta {
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-size: 0.75rem;
            color: #aaa;
        }

        .model-tag {
            background: var(--primary-color);
            color: white;
            padding: 2px 6px;
            border-radius: 10px;
            font-size: 0.7rem;
        }

        .history-actions {
            position: absolute;
            top: 8px;
            right: 8px;
            display: none;
        }

        .history-item:hover .history-actions {
            display: flex;
        }

        .delete-history {
            background: rgba(255, 107, 107, 0.8);
            color: white;
            border: none;
            border-radius: 50%;
            width: 20px;
            height: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 0.7rem;
        }

        .delete-history:hover {
            background: #ff5252;
        }

        /* 主内容区域样式 */
        .main-content {
            flex: 1;
            display: flex;
            flex-direction: column;
            background: white;
        }

        .chat-header {
            padding: 20px;
            border-bottom: 1px solid #e9ecef;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .chat-header h2 {
            color: var(--primary-color);
        }

        .status-indicator {
            display: flex;
            align-items: center;
            font-size: 0.9rem;
        }

        .status-dot {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background: #4ade80;
            margin-right: 8px;
        }

        #chat-box {
            flex: 1;
            padding: 20px;
            overflow-y: auto;
            background: #f8f9fa;
        }

        .message {
            margin-bottom: 20px;
            display: flex;
            align-items: flex-start;
        }

        .user-message {
            justify-content: flex-end;
        }

        .ai-message {
            justify-content: flex-start;
        }

        .message-avatar {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 0 10px;
            flex-shrink: 0;
        }

        .user-avatar-small {
            background: var(--primary-color);
            color: white;
        }

        .ai-avatar-small {
            background: var(--success-color);
            color: white;
        }

        .message-content {
            max-width: 70%;
            padding: 15px;
            border-radius: var(--border-radius);
            box-shadow: var(--shadow);
        }

        .user-message .message-content {
            background: var(--primary-color);
            color: white;
            border-top-right-radius: 5px;
        }

        .ai-message .message-content {
            background: white;
            color: var(--text-dark);
            border-top-left-radius: 5px;
        }

        .message-content pre {
            background: #2d2d2d !important;
            border-radius: 6px;
            padding: 12px !important;
            margin: 8px 0;
            overflow-x: auto;
            font-size: 14px;
            color: white;
        }

        .message-content code {
            font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
            font-size: 13px;
        }

        .multimodal-preview {
            margin-top: 10px;
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
        }

        .preview-item {
            position: relative;
            max-width: 200px;
            border-radius: 8px;
            overflow: hidden;
        }

        .preview-item img, .preview-item audio {
            width: 100%;
            display: block;
        }

        .remove-preview {
            position: absolute;
            top: 5px;
            right: 5px;
            background: rgba(0, 0, 0, 0.5);
            color: white;
            border: none;
            border-radius: 50%;
            width: 25px;
            height: 25px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
        }

        /* 输入区域样式 */
        .input-area {
            padding: 20px;
            border-top: 1px solid #e9ecef;
            background: white;
        }

        .multimodal-controls {
            display: flex;
            gap: 10px;
            margin-bottom: 15px;
        }

        .control-btn {
            padding: 10px 15px;
            border-radius: var(--border-radius);
            background: #e9ecef;
            border: none;
            cursor: pointer;
            display: flex;
            align-items: center;
            transition: var(--transition);
        }

        .control-btn i {
            margin-right: 8px;
        }

        .control-btn:hover {
            background: #dee2e6;
        }

        .input-container {
            display: flex;
            gap: 10px;
            align-items: flex-end;
        }

        #user-input {
            flex: 1;
            padding: 15px;
            border: 1px solid #ced4da;
            border-radius: var(--border-radius);
            resize: vertical;
            min-height: 60px;
            max-height: 150px;
            font-family: inherit;
            font-size: 1rem;
            transition: var(--transition);
        }

        #user-input:focus {
            outline: none;
            border-color: var(--primary-color);
            box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
        }

        #send-btn {
            padding: 15px 25px;
            background: var(--primary-color);
            color: white;
            border: none;
            border-radius: var(--border-radius);
            cursor: pointer;
            display: flex;
            align-items: center;
            transition: var(--transition);
        }

        #send-btn i {
            margin-right: 8px;
        }

        #send-btn:hover {
            background: var(--secondary-color);
            transform: translateY(-2px);
        }

        #send-btn:disabled {
            background: #6c757d;
            cursor: not-allowed;
            transform: none;
        }

        .input-hint {
            font-size: 0.8rem;
            color: #6c757d;
            margin-top: 8px;
            text-align: center;
        }

        /* 图片预览区域样式 */
        #image-previews {
            margin-bottom: 15px;
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
        }

        .preview-item {
            position: relative;
            max-width: 120px;
            border-radius: 8px;
            overflow: hidden;
            background: #f8f9fa;
            padding: 8px;
            border: 1px solid #e9ecef;
        }

        .preview-item img {
            width: 100%;
            height: auto;
            border-radius: 4px;
            display: block;
        }

        .preview-item .file-name {
            font-size: 0.7rem;
            margin-top: 5px;
            text-align: center;
            max-width: 100px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* 模态框样式 */
        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            z-index: 1000;
            align-items: center;
            justify-content: center;
        }

        .modal-content {
            background: white;
            border-radius: var(--border-radius);
            padding: 30px;
            width: 90%;
            max-width: 500px;
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
        }

        .modal-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
        }

        .modal-header h3 {
            color: var(--primary-color);
        }

        .close-modal {
            background: none;
            border: none;
            font-size: 1.5rem;
            cursor: pointer;
            color: #6c757d;
        }

        .settings-group {
            margin-bottom: 20px;
        }

        .settings-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
        }

        .settings-group select, .settings-group input {
            width: 100%;
            padding: 10px;
            border: 1px solid #ced4da;
            border-radius: var(--border-radius);
        }

        .modal-actions {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
            margin-top: 20px;
        }

        .btn-primary {
            padding: 10px 20px;
            background: var(--primary-color);
            color: white;
            border: none;
            border-radius: var(--border-radius);
            cursor: pointer;
        }

        .btn-secondary {
            padding: 10px 20px;
            background: #6c757d;
            color: white;
            border: none;
            border-radius: var(--border-radius);
            cursor: pointer;
        }

        /* 响应式设计 */
        @media (max-width: 768px) {
            .app-container {
                flex-direction: column;
                height: auto;
                margin: 10px;
            }
            .sidebar {
                width: 100%;
                order: 2;
            }
            .main-content {
                order: 1;
            }
            .message-content {
                max-width: 85%;
            }
        }
    </style>
</head>
<body>
    <div class="app-container">
        <!-- 侧边栏 -->
        <div class="sidebar">
            <div class="sidebar-header">
                <div class="user-avatar">
                    <i class="fas fa-robot"></i>
                </div>
                <h1>AI助手</h1>
            </div>
            <div class="user-profile">
                <div class="user-avatar">
                    <i class="fas fa-user"></i>
                </div>
                <div class="user-info">
                    <h3 id="username">用户</h3>
                </div>
            </div>
            <div class="sidebar-section">
                <h3>模型选择</h3>
                <select id="model-select" class="model-selector">
                    <option value="auto">智能选择 (推荐)</option>
                    <option value="qwen">通义千问 (通用对话)</option>
                    <option value="deepseek">DeepSeek (代码编程)</option>
                    <option value="qwen-vl">通义千问VL (图像分析)</option>
                </select>
                <div class="model-info">
                    💡 智能选择会根据内容自动选择最佳模型
                </div>

            </div>
            <div class="sidebar-section history-section">
                <div class="history-header">
                    <h3>对话历史</h3>
                    <button class="clear-history" id="clear-history-btn" title="清空所有历史记录">
                        <i class="fas fa-trash"></i> 清空
                    </button>
                </div>
                <div class="history-list" id="history-list">
                    <!-- 历史记录将通过JS动态添加 -->
                    <div class="history-item" style="text-align: center; color: #aaa; font-style: italic;">
                        加载中...
                    </div>
                </div>
            </div>
            <button class="task-btn" id="settings-btn" style="margin-top: auto; background: var(--primary-color); color: white; border: none; padding: 12px; border-radius: var(--border-radius); cursor: pointer; display: flex; align-items: center; transition: var(--transition);">
                <i class="fas fa-cog" style="margin-right: 10px;"></i> 个性化设置
            </button>
        </div>

        <!-- 主内容区域 -->
        <div class="main-content">
            <div class="chat-header">
                <h2>智能AI助手</h2>
                <div class="status-indicator">
                    <div class="status-dot"></div>
                    <span>服务运行中</span>
                </div>
            </div>
            <div id="chat-box">
                <!-- 聊天消息将通过JS动态添加 -->
                <div class="message ai-message">
                    <div class="message-avatar ai-avatar-small">
                        <i class="fas fa-robot"></i>
                    </div>
                    <div class="message-content">
                        <p>您好!我是您的AI助手,支持文本和图像的多模态交互。</p>
                        <p>我可以帮您回答问题、分析图片,并根据您的偏好提供个性化服务。</p>
                        <p><strong>可用模型:</strong></p>
                        <ul>
                            <li><strong>智能选择</strong>:自动根据问题选择最佳模型</li>
                            <li><strong>通义千问</strong>:通用对话和问题解答</li>
                            <li><strong>DeepSeek</strong>:编程和代码相关任务</li>
                            <li><strong>通义千问VL</strong>:图像分析和理解</li>
                        </ul>
                        <p><strong>历史记录功能:</strong></p>
                        <ul>
                            <li>点击左侧历史记录可以重新加载完整对话</li>
                            <li>鼠标悬停可显示删除按钮</li>
                            <li>使用"清空"按钮可删除所有历史记录</li>
                            <li>所有对话都会自动保存到后端数据库,刷新页面后不会丢失</li>
                        </ul>
                    </div>
                </div>
            </div>
            <div class="input-area">
                <div class="multimodal-controls">
                    <button class="control-btn" id="image-upload-btn">
                        <i class="fas fa-image"></i> 上传图片
                    </button>
                    <input type="file" id="image-input" accept="image/*" multiple style="display: none;">
                </div>
                
                <!-- 图片预览区域 -->
                <div id="image-previews"></div>
                
                <div class="input-container">
                    <textarea id="user-input" placeholder="请输入您的问题或指令...(支持多行输入,Ctrl+Enter发送)"></textarea>
                    <button id="send-btn">
                        <i class="fas fa-paper-plane"></i> 发送
                    </button>
                </div>
                <div class="input-hint">
                    💡 提示:按 <kbd>Ctrl+Enter</kbd>(Windows/Linux)或 <kbd>Cmd+Enter</kbd>(Mac)快速发送
                </div>
            </div>
        </div>
    </div>

    <!-- 设置模态框 -->
    <div class="modal" id="settings-modal">
        <div class="modal-content">
            <div class="modal-header">
                <h3>个性化设置</h3>
                <button class="close-modal">&times;</button>
            </div>
            <div class="settings-group">
                <label for="user-name">用户名</label>
                <input type="text" id="user-name" placeholder="请输入您的用户名">
            </div>
            <div class="modal-actions">
                <button class="btn-secondary" id="cancel-settings">取消</button>
                <button class="btn-primary" id="save-settings">保存设置</button>
            </div>
        </div>
    </div>

    <!-- 引入第三方库 -->
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.5/purify.min.js"></script>
    <script>
        // 配置Markdown解析器
        marked.setOptions({
            highlight: function(code, lang) {
                const language = hljs.getLanguage(lang) ? lang : 'plaintext';
                return hljs.highlight(code, { language }).value;
            },
            langPrefix: 'hljs language-'
        });

        // DOM元素引用
        const chatBox = document.getElementById('chat-box');
        const userInput = document.getElementById('user-input');
        const sendBtn = document.getElementById('send-btn');
        const modelSelect = document.getElementById('model-select');
        //const styleSelect = document.getElementById('style-select');
        const imageUploadBtn = document.getElementById('image-upload-btn');
        const imageInput = document.getElementById('image-input');
        const settingsBtn = document.getElementById('settings-btn');
        const settingsModal = document.getElementById('settings-modal');
        const closeModal = document.querySelector('.close-modal');
        const cancelSettings = document.getElementById('cancel-settings');
        const saveSettings = document.getElementById('save-settings');
        const historyList = document.getElementById('history-list');
        const clearHistoryBtn = document.getElementById('clear-history-btn');
        const imagePreviews = document.getElementById('image-previews');

        // 状态变量
        let uploadedImages = [];
        
        // 使用固定用户ID,确保历史记录一致性
        let currentUserId = localStorage.getItem('aiAssistantUserId') || 'default_user';
        localStorage.setItem('aiAssistantUserId', currentUserId);
        
        let userSettings = {
            name: '用户'
        };
        
        let currentHistoryId = null;

        // 模型显示名称映射
        const modelDisplayNames = {
            'auto': '智能选择',
            'qwen': '通义千问',
            'deepseek': 'DeepSeek',
            'qwen-vl': '通义千问VL'
        };

        // 初始化
        document.addEventListener('DOMContentLoaded', function() {
            loadUserSettings();
            updateUserProfile();
            loadChatHistoryFromBackend(); // 从后端加载历史记录
            
            // 设置事件监听器
            setupEventListeners();
        });

        // 设置事件监听器
        function setupEventListeners() {
            // 发送消息
            sendBtn.addEventListener('click', sendMessage);
            
            // 快捷键发送
            userInput.addEventListener('keydown', (e) => {
                if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
                    e.preventDefault();
                    sendMessage();
                }
            });
            
            // 自动调整输入框高度
            userInput.addEventListener('input', function() {
                this.style.height = 'auto';
                this.style.height = Math.min(this.scrollHeight, 150) + 'px';
            });
            
            // 多模态输入
            imageUploadBtn.addEventListener('click', () => imageInput.click());
            imageInput.addEventListener('change', handleImageUpload);
            
            // 设置模态框
            settingsBtn.addEventListener('click', openSettingsModal);
            closeModal.addEventListener('click', closeSettingsModal);
            cancelSettings.addEventListener('click', closeSettingsModal);
            saveSettings.addEventListener('click', saveUserSettings);
            
            // 历史记录操作
            clearHistoryBtn.addEventListener('click', clearAllHistory);
        }

        // 发送消息到后端
        async function sendMessage() {
            const message = userInput.value.trim();
            if (!message && uploadedImages.length === 0) {
                alert('请输入消息或上传图片');
                return;
            }
            
            const selectedModel = modelSelect.value;
            //const style = styleSelect.value;
            
            // 确定实际使用的模型
            let actualModel = selectedModel;
            if (uploadedImages.length > 0) {
                // 如果有图片,强制使用多模态模型
                actualModel = 'qwen-vl';
            }
            
            // 显示用户消息
            displayUserMessage(message, uploadedImages, selectedModel);
            
            // 清空输入
            userInput.value = '';
            userInput.style.height = 'auto';
            
            // 禁用发送按钮
            sendBtn.disabled = true;
            sendBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 发送中';
            
            // 显示AI思考中
            const thinkingDiv = displayThinkingMessage();
            
            try {
                let response;
                
                // 判断是否为多模态请求(有图片)
                if (uploadedImages.length > 0) {
                    // 多模态请求 - 使用FormData
                    const formData = new FormData();
                    
                    // 添加文本(如果有)
                    if (message) {
                        formData.append('text', message);
                    }
                    
                    // 添加用户ID
                    formData.append('user_id', currentUserId);
                    
                    // 添加图片文件
                    uploadedImages.forEach((image, index) => {
                        formData.append('images', image.file);
                    });
                    
                    response = await fetch('http://localhost:8000/multimodal', {
                        method: 'POST',
                        body: formData
                    });
                    
                    // 清空已上传的图片
                    clearImagePreviews();
                } else {
                    // 纯文本请求
                    response = await fetch('http://localhost:8000/chat', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({
                            message: message,
                            user_id: currentUserId,
                            model_type: actualModel
                        })
                    });
                }
                
                const data = await response.json();
                
                if (response.ok) {
                    // 解析Markdown并安全渲染
                    const cleanHtml = DOMPurify.sanitize(marked.parse(data.response));
                    thinkingDiv.innerHTML = `
                        <div class="message-avatar ai-avatar-small">
                            <i class="fas fa-robot"></i>
                        </div>
                        <div class="message-content">
                            ${cleanHtml}
                            <div style="margin-top: 10px; font-size: 0.8rem; color: #666;">
                                <i class="fas fa-microchip"></i> 使用模型: ${modelDisplayNames[actualModel]}
                            </div>
                        </div>
                    `;
                    
                    // 重新加载历史记录(从后端获取最新记录)
                    setTimeout(() => {
                        loadChatHistoryFromBackend();
                    }, 500);
                } else {
                    thinkingDiv.innerHTML = `
                        <div class="message-avatar ai-avatar-small">
                            <i class="fas fa-robot"></i>
                        </div>
                        <div class="message-content">
                            <p>❌ 出错了:${data.detail || '未知错误'}</p>
                        </div>
                    `;
                }
            } catch (error) {
                console.error('发送消息出错:', error);
                thinkingDiv.innerHTML = `
                    <div class="message-avatar ai-avatar-small">
                        <i class="fas fa-robot"></i>
                    </div>
                    <div class="message-content">
                        <p>⚠️ 无法连接到后端服务,请确保服务正在运行。</p>
                    </div>
                `;
            } finally {
                sendBtn.disabled = false;
                sendBtn.innerHTML = '<i class="fas fa-paper-plane"></i> 发送';
            }
        }

        // 显示用户消息
        function displayUserMessage(message, images, model) {
            const messageDiv = document.createElement('div');
            messageDiv.className = 'message user-message';
            
            let contentHTML = '';
            
            // 添加文本消息(如果有)
            if (message) {
                contentHTML += `<p>${DOMPurify.sanitize(message)}</p>`;
            }
            
            // 添加图片预览
            if (images.length > 0) {
                contentHTML += '<div class="multimodal-preview">';
                images.forEach((image, index) => {
                    contentHTML += `
                        <div class="preview-item">
                            <img src="${image.url}" alt="上传的图片">
                            <button class="remove-preview" data-index="${index}">
                                <i class="fas fa-times"></i>
                            </button>
                        </div>
                    `;
                });
                contentHTML += '</div>';
            }
            
            // 添加模型信息
            contentHTML += `<div style="margin-top: 5px; font-size: 0.8rem; opacity: 0.8;">
                <i class="fas fa-microchip"></i> 选择模型: ${modelDisplayNames[model]}
            </div>`;
            
            messageDiv.innerHTML = `
                <div class="message-content">
                    ${contentHTML}
                </div>
                <div class="message-avatar user-avatar-small">
                    <i class="fas fa-user"></i>
                </div>
            `;
            
            chatBox.appendChild(messageDiv);
            chatBox.scrollTop = chatBox.scrollHeight;
            
            // 添加删除图片按钮的事件监听
            if (images.length > 0) {
                messageDiv.querySelectorAll('.remove-preview').forEach(button => {
                    button.addEventListener('click', function() {
                        const index = parseInt(this.getAttribute('data-index'));
                        removeImage(index);
                        messageDiv.remove();
                    });
                });
            }
        }

        // 显示AI思考中消息
        function displayThinkingMessage() {
            const thinkingDiv = document.createElement('div');
            thinkingDiv.className = 'message ai-message';
            thinkingDiv.innerHTML = `
                <div class="message-avatar ai-avatar-small">
                    <i class="fas fa-robot"></i>
                </div>
                <div class="message-content">
                    <p><i class="fas fa-spinner fa-spin"></i> 正在思考...</p>
                </div>
            `;
            
            chatBox.appendChild(thinkingDiv);
            chatBox.scrollTop = chatBox.scrollHeight;
            
            return thinkingDiv;
        }

        // 处理图片上传
        function handleImageUpload(event) {
            const files = event.target.files;
            if (!files.length) return;
            
            Array.from(files).forEach(file => {
                // 检查文件类型
                if (!file.type.startsWith('image/')) {
                    alert('请上传图片文件');
                    return;
                }
                
                // 检查文件大小(限制为5MB)
                if (file.size > 5 * 1024 * 1024) {
                    alert('图片大小不能超过5MB');
                    return;
                }
                
                const reader = new FileReader();
                reader.onload = function(e) {
                    uploadedImages.push({
                        file: file,
                        url: e.target.result
                    });
                    // 显示图片预览
                    displayImagePreview(file, e.target.result, uploadedImages.length - 1);
                    // 显示上传成功提示
                    displayUploadNotification(`图片 "${file.name}" 上传成功`);
                };
                reader.readAsDataURL(file);
            });
            
            // 重置文件输入
            event.target.value = '';
        }

        // 显示图片预览
        function displayImagePreview(file, dataUrl, index) {
            const previewItem = document.createElement('div');
            previewItem.className = 'preview-item';
            previewItem.innerHTML = `
                <img src="${dataUrl}" alt="上传的图片">
                <button class="remove-preview" data-index="${index}" title="删除图片">
                    <i class="fas fa-times"></i>
                </button>
                <div class="file-name">${file.name}</div>
            `;
            
            // 添加删除事件
            const removeBtn = previewItem.querySelector('.remove-preview');
            removeBtn.addEventListener('click', function() {
                const removeIndex = parseInt(this.getAttribute('data-index'));
                removeImage(removeIndex);
                previewItem.remove();
                
                displayUploadNotification('图片已删除');
            });
            
            imagePreviews.appendChild(previewItem);
        }

        // 移除图片
        function removeImage(index) {
            if (index >= 0 && index < uploadedImages.length) {
                uploadedImages.splice(index, 1);
                
                // 更新剩余图片的索引
                const previewItems = document.querySelectorAll('.preview-item');
                previewItems.forEach((item, newIndex) => {
                    const removeBtn = item.querySelector('.remove-preview');
                    removeBtn.setAttribute('data-index', newIndex);
                });
            }
        }

        // 清空图片预览
        function clearImagePreviews() {
            uploadedImages = [];
            imagePreviews.innerHTML = '';
        }

        // 显示上传成功通知
        function displayUploadNotification(message) {
            const notification = document.createElement('div');
            notification.className = 'message ai-message';
            notification.innerHTML = `
                <div class="message-avatar ai-avatar-small">
                    <i class="fas fa-robot"></i>
                </div>
                <div class="message-content">
                    <p>✅ ${message}</p>
                </div>
            `;
            
            chatBox.appendChild(notification);
            chatBox.scrollTop = chatBox.scrollHeight;
            
            // 3秒后自动移除通知
            setTimeout(() => {
                notification.remove();
            }, 3000);
        }

        // 打开设置模态框
        function openSettingsModal() {
            // 填充当前设置
            document.getElementById('user-name').value = userSettings.name;
            
            settingsModal.style.display = 'flex';
        }

        // 关闭设置模态框
        function closeSettingsModal() {
            settingsModal.style.display = 'none';
        }

        // 保存用户设置
        function saveUserSettings() {
            userSettings.name = document.getElementById('user-name').value || '用户';
            
            // 保存到本地存储
            localStorage.setItem('aiAssistantSettings', JSON.stringify(userSettings));
            
            // 更新用户资料显示
            updateUserProfile();
            
            // 关闭模态框
            closeSettingsModal();
            
            // 显示保存成功消息
            displayUploadNotification('设置已保存');
        }

        // 加载用户设置
        function loadUserSettings() {
            const savedSettings = localStorage.getItem('aiAssistantSettings');
            if (savedSettings) {
                userSettings = { ...userSettings, ...JSON.parse(savedSettings) };
            }
        }

        // 更新用户资料显示
        function updateUserProfile() {
            document.getElementById('username').textContent = userSettings.name;
        }

        // 从后端加载聊天历史
        async function loadChatHistoryFromBackend() {
            try {
                const response = await fetch(`http://localhost:8000/chat_history/${currentUserId}?limit=20`);
                const data = await response.json();
                
                if (response.ok) {
                    // 清空历史记录列表
                    historyList.innerHTML = '';
                    
                    if (data.history && data.history.length > 0) {
                        // 添加历史记录项
                        data.history.forEach((record, index) => {
                            addHistoryItemFromBackend(record, index);
                        });
                    } else {
                        // 没有历史记录
                        historyList.innerHTML = '<div class="history-item" style="text-align: center; color: #aaa; font-style: italic;">暂无历史记录</div>';
                    }
                } else {
                    console.error('加载历史记录失败:', data);
                    historyList.innerHTML = '<div class="history-item" style="text-align: center; color: #ff6b6b;">加载历史记录失败</div>';
                }
            } catch (error) {
                console.error('加载历史记录出错:', error);
                historyList.innerHTML = '<div class="history-item" style="text-align: center; color: #ff6b6b;">无法连接到后端服务</div>';
            }
        }

        // 从后端数据添加历史记录项
        function addHistoryItemFromBackend(record, index) {
            const historyItem = document.createElement('div');
            historyItem.className = 'history-item';
            historyItem.setAttribute('data-history-index', index);
            
            // 格式化时间
            const timestamp = new Date(record.timestamp).toLocaleString();
            
            historyItem.innerHTML = `
                <div class="history-content">${record.user_message}</div>
                <div class="history-meta">
                    <span class="model-tag">${modelDisplayNames[record.model_used] || '未知'}</span>
                    <span>${timestamp}</span>
                </div>
                <div class="history-actions">
                    <button class="delete-history" title="删除此记录">
                        <i class="fas fa-times"></i>
                    </button>
                </div>
            `;
            
            // 点击历史记录项加载完整对话
            historyItem.addEventListener('click', (e) => {
                if (!e.target.closest('.delete-history')) {
                    loadHistoryConversationFromBackend(record);
                    // 移除其他项的active类
                    document.querySelectorAll('.history-item').forEach(item => {
                        item.classList.remove('active');
                    });
                    // 添加active类到当前项
                    historyItem.classList.add('active');
                }
            });
            
            // 删除按钮事件
            const deleteBtn = historyItem.querySelector('.delete-history');
            deleteBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                deleteHistoryItem(record.id);
            });
            
            historyList.appendChild(historyItem);
        }

        // 从后端数据加载历史对话
        function loadHistoryConversationFromBackend(record) {
            // 清空当前聊天框
            chatBox.innerHTML = '';
            
            // 显示用户消息
            const userMessageDiv = document.createElement('div');
            userMessageDiv.className = 'message user-message';
            userMessageDiv.innerHTML = `
                <div class="message-content">
                    <p>${DOMPurify.sanitize(record.user_message)}</p>
                    <div style="margin-top: 5px; font-size: 0.8rem; opacity: 0.8;">
                        <i class="fas fa-microchip"></i> 选择模型: ${modelDisplayNames[record.model_used] || '未知'}
                    </div>
                </div>
                <div class="message-avatar user-avatar-small">
                    <i class="fas fa-user"></i>
                </div>
            `;
            chatBox.appendChild(userMessageDiv);
            
            // 显示AI回复
            const aiMessageDiv = document.createElement('div');
            aiMessageDiv.className = 'message ai-message';
            const cleanHtml = DOMPurify.sanitize(marked.parse(record.ai_response));
            aiMessageDiv.innerHTML = `
                <div class="message-avatar ai-avatar-small">
                    <i class="fas fa-robot"></i>
                </div>
                <div class="message-content">
                    ${cleanHtml}
                    <div style="margin-top: 10px; font-size: 0.8rem; color: #666;">
                        <i class="fas fa-microchip"></i> 使用模型: ${modelDisplayNames[record.model_used] || '未知'}
                    </div>
                </div>
            `;
            chatBox.appendChild(aiMessageDiv);
            
            // 滚动到底部
            chatBox.scrollTop = chatBox.scrollHeight;
        }

        // 删除历史记录项
        async function deleteHistoryItem(historyId) {
            try {
                const response = await fetch(`http://localhost:8000/chat_history/${currentUserId}?history_id=${historyId}`, {
                    method: 'DELETE',
                    headers: {
                        'Content-Type': 'application/json'
                    }
                });
                
                if (response.ok) {
                    // 重新加载历史记录
                    loadChatHistoryFromBackend();
                    // 清空聊天框
                    chatBox.innerHTML = '';
                    displayUploadNotification('历史记录已删除');
                } else {
                    console.error('删除历史记录失败');
                    displayUploadNotification('删除历史记录失败');
                }
            } catch (error) {
                console.error('删除历史记录出错:', error);
                displayUploadNotification('删除历史记录失败');
            }
        }

        // 清空所有历史记录
        async function clearAllHistory() {
            if (confirm('确定要清空所有历史记录吗?此操作不可撤销。')) {
                try {
                    const response = await fetch(`http://localhost:8000/chat_history/${currentUserId}`, {
                        method: 'DELETE',
                        headers: {
                            'Content-Type': 'application/json'
                        }
                    });
                    
                    if (response.ok) {
                        // 清空历史记录列表
                        historyList.innerHTML = '<div class="history-item" style="text-align: center; color: #aaa; font-style: italic;">暂无历史记录</div>';
                        // 清空聊天框
                        chatBox.innerHTML = '';
                        displayUploadNotification('历史记录已清空');
                    } else {
                        console.error('清空历史记录失败');
                        displayUploadNotification('清空历史记录失败');
                    }
                } catch (error) {
                    console.error('清空历史记录出错:', error);
                    displayUploadNotification('清空历史记录失败');
                }
            }
        }
    </script>
</body>
</html>

运行步骤

1.启动后端服务

在cmd中(下行若在Windows PowerShell中运行,去掉 /d):

cd /d D:/ai-assistant-multiple/backend

$env:DASHSCOPE_API_KEY="真实的DASHSCOPE_API_KEY "

$env:DEEPSEEK_API_KEY="真实的DEEPSEEK_API_KEY"

uvicorn main:app --port 8000

如果你嫌输入比较麻烦,可以使用批处理文件(.bat)

在D:\ai-assistant-multiple\backend目录中用记事本建立名称为start_backend.bat批处理文件,以后只需双击这个 .bat 文件就能运行后端,无需手动输入命令。内容如下:

@echo off

cd /d "D:\ai-assistant-multiple\backend"

:: Set API Key

set DASHSCOPE_API_KEY=真实的DASHSCOPE_API_KEY

set DEEPSEEK_API_KEY=真实的DEEPSEEK_API_KEY

echo Starting AI assistant backend...

echo Visit http://localhost:8000 to check status

echo Open frontend/index.html in your browser

echo.

uvicorn main:app --port 8000

pause

注意,在使用时不要关闭后端服务。

2.打开前端

直接双击 frontend/index.html 用浏览器打开

3.聊天

现在可以在前端网页中开始聊天!

可以在同一个界面中体验不同 AI 模型的回答风格和能力了!

OK!

相关推荐
骥龙1 小时前
4.12、隐私保护机器学习:联邦学习在安全数据协作中的应用
人工智能·安全·网络安全
天硕国产存储技术站1 小时前
DualPLP 双重掉电保护赋能 天硕工业级SSD筑牢关键领域安全存储方案
大数据·人工智能·安全·固态硬盘
腾讯云开发者1 小时前
AI独孤九剑:AI没有场景,无法落地?不存在的。
人工智能
光影少年1 小时前
node.js和nest.js做智能体开发需要会哪些东西
开发语言·javascript·人工智能·node.js
落798.1 小时前
基于CANN与MindSpore的AI算力体验:从异构计算到应用落地的实战探索
人工智能·cann
audyxiao0011 小时前
期刊研究热点扫描|一文了解计算机视觉顶刊TIP的研究热点
人工智能·计算机视觉·transformer·图像分割·多模态
paopao_wu1 小时前
目标检测YOLO[04]:跑通最简单的YOLO模型训练
人工智能·yolo·目标检测
XINVRY-FPGA2 小时前
XCVP1802-2MSILSVC4072 AMD Xilinx Versal Premium Adaptive SoC FPGA
人工智能·嵌入式硬件·fpga开发·数据挖掘·云计算·硬件工程·fpga
撸码猿2 小时前
《Python AI入门》第9章 让机器读懂文字——NLP基础与情感分析实战
人工智能·python·自然语言处理