1. 项目介绍

DeepSeek AI 智能助手是一款集成了自然语言处理、文件解析、会话管理等功能的智能聊天系统。该项目旨在提供一个功能完备、交互流畅的 AI 助手,支持多轮对话、文件上传解析、联网搜索等核心功能,为用户提供便捷的智能服务体验。

1.1 需求分析

1.1.1 功能需求分析

①核心功能

  • 智能对话
    • 支持多轮对话,保持上下文连贯性
    • 提供流式响应,实时显示 AI 思考过程和回答内容
    • 支持文本输入和语音输入(可选)
  • 文件处理
    • 支持上传多种格式文件:txt、md、pdf、doc、docx、json、csv、xlsx 等
    • 文件内容提取与解析
    • 基于文件内容进行问答和分析
  • 会话管理
    • 创建、重命名、删除会话
    • 会话列表展示和切换
    • 会话历史记录保存和查询
  • 联网搜索
    • 支持开启 / 关闭联网搜索功能
    • 调用外部搜索引擎获取信息
    • 整合搜索结果与 AI 回答

②辅助功能

  • 用户认证
    • 用户注册和登录
    • 密码加密存储
    • JWT 令牌认证
  • 主题切换
    • 支持亮色 / 暗色主题
    • 自动适应系统主题设置
  • 消息管理
    • 消息发送和接收
    • 消息时间戳显示
    • 消息格式支持 Markdown
  • 文件管理
    • 文件上传进度显示
    • 文件列表展示
    • 文件下载和删除

1.1.2 非功能需求分析

性能需求

  • 系统响应时间:平均响应时间不超过 2 秒,最大响应时间不超过 5 秒
  • 并发用户数:支持至少 100 个并发用户同时在线使用
  • 文件处理能力:支持上传最大 10MB 的文件,处理时间不超过 30 秒

安全需求

  • 用户数据加密存储
  • 敏感信息保护
  • 防止 SQL 注入和 XSS 攻击
  • 会话超时管理

可用性需求

  • 系统可用性:99.5% 以上的时间可正常访问
  • 错误处理:提供友好的错误提示信息
  • 恢复能力:在系统故障后能够快速恢复

可扩展性需求

  • 支持添加新的 AI 模型
  • 支持扩展新的文件格式处理能力
  • 支持添加新的功能模块

1.2 技术栈

层级 技术 说明
后端 FastAPI + SQLAlchemy Python 3.10,异步高性能
大模型 通义千问 Qwen-plus-2025-04-28 支持深度思考 / 联网搜索
数据库 SQLite(可替换 MySQL/PostgreSQL) 轻量、零配置
前端 原生 HTML5 + CSS3 + ES6 无框架、易二开
部署 Uvicorn  一条命令即可启动
  • 后端技术

    • 框架:FastAPI(高性能异步 Python Web 框架)
    • 数据库:SQLite(轻量级关系型数据库)
    • ORM:SQLAlchemy(Python SQL 工具包)
    • 认证:JWT(JSON Web Token)
    • 异步处理:asyncio
    • AI 模型接口:基于 OpenAI 兼容 API 的 Qwen 模型
  • 前端技术

    • 页面结构:HTML5
    • 样式:CSS3
    • 交互:JavaScript(ES6+)
    • 网络请求:Axios
    • Markdown 解析:marked.js
  • 开发工具

    • 代码编辑器:支持 Python 和 Web 开发的 IDE
    • 调试工具:浏览器开发者工具、FastAPI 自动生成的 Swagger 文档

 1.3 项目运行效果图

接下来给大家看一下项目大致的运行截图,便于大家更加直观的了解这个项目的基本功能:

①登录注册

②联网搜索

③文件回答

④主题切换

 1.4 项目目录结构

 2. 项目架构分析

2.1 架构图

2.2 架构图内容解析

  1. 用户入口:浏览器

    • 作为前端交互载体,用户通过浏览器界面(如聊天窗口、文件上传按钮、搜索开关等)发起操作请求,包括输入文本、上传文件、切换会话等。
    • 前端将用户行为封装为 HTTP 请求(如 POST/GET),通过 API 调用后端服务。
  2. 核心服务层:FastAPI

    • 作为后端核心框架,承接浏览器发起的所有请求,负责路由分发、参数校验、业务逻辑处理,并协调各功能模块协作。
    • 是连接前端与底层服务的中间枢纽,向下调用认证、会话、文件处理等模块,向上通过 SSE(Server-Sent Events)返回流式响应。
  3. 功能模块拆分

    • [JWT 认证]:负责用户身份验证与权限管理。
      • 登录时验证用户名密码,生成 JWT 令牌;
      • 后续请求通过令牌验证用户合法性,确保数据安全。
    •  [会话管理]:维护用户的聊天会话生命周期。
      • 管理会话的创建、查询、更新(如重命名)、删除(软删除);
      • 关联用户与会话,记录会话标题、创建时间、最后活动时间等元数据。
    • [文件上传 / 解析]:处理用户上传的文件并提取内容。
      • 验证文件大小(≤10MB)和格式(支持 txt、pdf、docx 等);
      • 通过 PyPDF2、python-docx 等工具解析文件内容,为 AI 问答提供上下文。
    • [对话引擎]:系统核心模块,协调 AI 交互逻辑。
      • 接收用户提问,结合上下文(历史消息、文件内容)生成增强查询;
      • 根据用户是否开启 “联网搜索”,决定调用外部 API 或直接使用本地模型;
      • 将 AI 响应通过 SSE 流式返回给前端,实现 “边思考边输出” 的效果。
  4. AI 能力层

    • {联网?}:分支逻辑判断,根据用户需求切换 AI 响应模式。
      • [外部搜索 API]:当用户开启 “联网搜索” 时,调用外部搜索引擎获取实时数据(如新闻、天气等),增强回答时效性
      • [通义千问]:默认模式,使用通义千问大模型进行本地推理,基于历史对话和文件内容生成回答。
  5. 响应机制:[SSE 流式输出]

    • 采用 Server-Sent Events 技术,将 AI 的思考过程和回答内容分块实时推送到前端;
    • 前端逐段渲染内容,避免用户长时间等待,提升交互流畅度。

3.开发的主要功能分析

3.1 登录注册功能分析

登录的成功时候使用JWT将对应用户的token返回给前端,前端获取token,将token存储到浏览器缓存中,后续登录的时候只要拿到对应的token进行校验,判断是否成功登录,才能访问主页的智能聊天页面进行操作。

注册的时候先判断用户是否存在,不存在的时候才能创建用户,用户创建成功的时候使用哈希加密对密码进行加密,避免密码的明文存储。

3.1.1 登录功能

①效果图描述

登录页面采用简洁布局,包含用户名输入框、密码输入框、登录按钮和 “已有账号?去注册” 的跳转链接。页面底部有操作结果提示框(成功 / 错误提示)。用户提交登录后,若信息正确则跳转至聊天页面;若错误则显示 “用户名或密码错误” 提示。

②重要代码示例与注释

后端登录接口(FastAPI)

# 认证和安全
import jwt
from passlib.context import CryptContext
# 密码加密上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

@app.post("/api/login", response_model=ResponseModel)
async def login(user: UserLogin):
    """用户登录接口"""
    db = Session()
    try:
        # 验证用户存在性
        db_user = db.query(User).filter(User.username == user.username).first()
        if not db_user:
            return ResponseModel(code=400, message="用户名或密码错误")
        # 调研哈希解密来验证密码
        if not pwd_context.verify(user.password, db_user.password):
            return ResponseModel(code=400, message="用户名或密码错误")
        # 生成 token
        access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
        # 调用 create_access_token 函数生成token
        access_token = create_access_token(
            data={"sub": user.username},
            expires_delta=access_token_expires
        )
        return ResponseModel(
            code=200,
            message="登录成功",
            data={
                "access_token": access_token,
                "token_type": "bearer"
            }
        )
    except Exception as e:
        print(f"登录错误: {str(e)}")
        return ResponseModel(code=500, message="服务器错误")
    finally:
        db.close()

后端工具函数(FastAPI):create_access_token

SECURITY_KEY = "asdfghjklzxcvbnm"   // 秘钥自己设计
ALGORITHM = "HS256"  // 加密的算法
ACCESS_TOKEN_EXPIRE_MINUTES = 30   // 过期的时间
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
    """创建 JWT token"""
    to_encode = data.copy()

    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)

    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECURITY_KEY, algorithm=ALGORITHM)
    return encoded_jwt

代码解析: 

1、pwd_context.verify(user.password, db_user.password):调用 from passlib.context import CryptContext中的verify方法验证解密数据库加密的密码,判断是否正确。

2、create_access_token:主要是用来生成token的函数

真正的加密语句:

 encoded_jwt = jwt.encode(to_encode, SECURITY_KEY, algorithm=ALGORITHM)

JWT需要三部分加密内容:

头部:也就是这里的toencode(包括用户信息和过期时间的键值对)

加密算法:ALGORITHM = "HS256" 

秘钥:SECURITY_KEY = "asdfghjklzxcvbnm"

前端登录逻辑(JavaScript)

<!-- 引入 axios -->
document.getElementById('loginForm').addEventListener('submit', function (e) {
e.preventDefault();
const form = e.target;
const username = form.username.value.trim();
const password = form.password.value.trim();
const successDiv = document.getElementById('successAlert');
const errorDiv = document.getElementById('errorAlert');
// 清空上次提示
successDiv.style.display = 'none';
errorDiv.style.display = 'none';
successDiv.textContent = '';
errorDiv.textContent = '';
// 发送登录请求
axios({
    url:'http://127.0.0.1:8080/api/login',
    method: "post",
    data:{
        username: username,
        password: password
    }
}).then(response => {
    if (response.data.code === 200) {
        successDiv.style.display = 'block';
        successDiv.textContent = response.data.message;
        // 如果返回了 token,将其保存到 浏览器localStorage 中
        if (response.data.data && response.data.data.access_token) {
            // 将登录成功后服务器返回的 token 保存到浏览器的本地存储中,以便后续请求时使用
            localStorage.setItem('token', response.data.data.access_token);  //localStorage.setItem(key, value) 是浏览器提供的一个用于持久化存储数据的方法
            // 做身份验证只要localStorage.getItem('token'):如果能够获取到token,则表示已登录
        }
        // 跳转页面
        setTimeout(() => {
            window.location.href = 'chat.html';
        }, 1000);
    } else {
        errorDiv.style.display = 'block';
        errorDiv.textContent = response.data.message;
    }
})
.catch(error => {
    errorDiv.style.display = 'block';
    errorDiv.textContent = "登录失败:" +
        (error.response?.data?.message || error.message);
});
});

 代码注释:

通过localStorage.setItem('token', response.data.data.access_token)将后端返回的token,存储到浏览器的缓存中,便于后续对token进行校验。

3.1.2 JWT验证

前端(Javascript)

// 在chat.js开头添加axios请求拦截器
axios.interceptors.request.use(
    config => {
        const token = localStorage.getItem('token');
        if (token) {
            // 在每个请求头中添加Authorization: Bearer <token>(如果token存在)。
            config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
    },
    error => {
        // 若拦截过程中出错,则返回一个被拒绝的 Promise
        return Promise.reject(error);
    }
);

// 添加响应拦截器处理token过期
axios.interceptors.response.use(
    response => response,
    error => {
        if (error.response && error.response.status === 401) {
            // 移除本地存储中的 token;
            localStorage.removeItem('token');
            alert('登录已过期,请重新登录');
            window.location.href = 'login.html';
        }
        return Promise.reject(error);
    }
);

 后端(FastApi):工具函数

def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> str:
    """验证 JWT token 并返回用户名"""
    token = credentials.credentials
    try:
        payload = jwt.decode(token, SECURITY_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="无效的认证凭据",
                headers={"WWW-Authenticate": "Bearer"},
            )
        return username
    except jwt.PyJWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="无效的认证凭据",
            headers={"WWW-Authenticate": "Bearer"},
        )


def get_current_user(username: str = Depends(verify_token)) -> User:
    """获取当前用户信息"""
    db = Session()
    try:
        user = db.query(User).filter(User.username == username).first()
        if user is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="用户不存在"
            )
        return user
    finally:
        db.close()

 在对应的智能聊天助手的页面对应的接口设置参数中设置依赖注入

 current_user: User = Depends(get_current_user

确保每次在智能聊天界面发送请求的时候都要验证token。

③ 重要代码解析

  1. 前端流程

    • 监听登录表单提交事件,阻止默认刷新行为;
    • 获取用户名和密码并校验格式;
    • 通过 Axios 发送 POST 请求至/api/login
    • 登录成功后,将 JWT 令牌存入localStorage,并跳转至聊天页面;
    • 失败时显示错误提示(如用户名不存在、密码错误)。
  2. 后端流程

    • 接收UserLogin模型参数(用户名、密码);
    • 从数据库查询用户,验证用户是否存在;
    • 使用passlibverify方法校验密码(与数据库中加密存储的密码比对);
    • 验证通过后,调用create_access_token生成 JWT 令牌(有效期 30 分钟);
    • 返回包含令牌的成功响应。
  3. 安全性

    • 密码不明文传输,通过 HTTPS(实际部署时)加密传输;
    • 后端使用 bcrypt 算法加密存储密码,无法反向解密;
    • 采用 JWT 令牌认证,避免频繁验证数据库。

3.1.3 注册功能

注册页面包含用户名输入框、密码输入框、确认密码输入框、注册按钮和 “已有账号?去登录” 的跳转链接。前端实时校验输入(如用户名长度≥8、密码包含字母和数字),底部提示框显示注册结果(成功 / 错误)。注册成功后自动跳转至登录页面。

登录成功自动跳转到登录界面: 

 

②重要代码示例与解析

后端注册接口(FastAPI)

@app.post("/api/register", response_model=ResponseModel)
async def register(user: UserRegister):
    """用户注册接口"""
    db = Session()
    try:
        # 检查用户名是否已存在
        existing_user = db.query(User).filter(User.username == user.username).first()
        if existing_user:
            raise HTTPException(status_code=400, detail="用户名已存在")

        # 创建新用户
        hashed_password = pwd_context.hash(user.password)
        new_user = User(
            username=user.username,
            password=hashed_password,
            create_time=user.create_time.isoformat()
        )

        db.add(new_user)
        db.commit()
        db.refresh(new_user)

        return ResponseModel(code=200, message="注册成功")

    except HTTPException as e:
        return ResponseModel(code=e.status_code, message=e.detail)
    except Exception as e:
        print(f"注册错误: {str(e)}")
        db.rollback()
        return ResponseModel(code=500, message="服务器错误")
    finally:
        db.close()

代码注释: 

hashed_password = pwd_context.hash(user.password):调用CryptContext中的hash方法将原来的密码进行哈希解密,最后存储到数据库中,大大提升了系统的安全性。

前端注册逻辑(JavaScript)

document.querySelector('.register-form').onsubmit = function (e) {
e.preventDefault();

// 前端校验
if (username.length < 8) {
    errorDiv.style.display = 'block';
    errorDiv.textContent = '用户名至少8个字符';
    return;
}
if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20}$/.test(password)) {
    errorDiv.style.display = 'block';
    errorDiv.textContent = '密码必须为8-20位,且包含字母和数字';
    return;
}
if (password !== confirmPassword) {
    errorDiv.style.display = 'block';
    errorDiv.textContent = '两次密码不一致';
    return;
}
// 发送请求
axios({
    url: 'http://127.0.0.1:8080/api/register',
    method: 'post',
    data: {
        username: username,
        password: password
    }
})
.then(result => {
    if (result.data.code === 200) {
        successDiv.style.display = 'block';
        successDiv.textContent = result.data.message;
        setTimeout(function () {
            window.location.href = 'login.html';
        }, 1000)
        // 注册成功后清空表单
        document.querySelector('#username').value = "";
        document.querySelector('#password').value = "";
        document.querySelector('#password_isok').value = "";
    } else {
        errorDiv.style.display = 'block';
        errorDiv.textContent = result.data.message;
    }
})
.catch(error => {
    errorDiv.style.display = 'block';
    errorDiv.textContent = "注册失败:" +
        (error.response?.data?.detail || error.response?.data?.message || error.message);
});
};

数据库设计: 

class User(Base):
    """用户表模型"""
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)  # 主键ID
    username = Column(String(255), unique=True, index=True, nullable=False)  # 用户名(唯一)
    password = Column(String(255), nullable=False)  # 加密后的密码
    create_time = Column(String(255), nullable=False)  # 注册时间(ISO格式字符串)

重要代码解析

  1. 前端流程

    • 监听注册表单提交事件,阻止默认行为;
    • 前端校验:用户名长度≥8、密码为 8-20 位且包含字母和数字、两次密码一致;
    • 校验通过后发送 POST 请求至/api/register
    • 注册成功后显示提示并跳转登录页面,失败则显示错误信息(如用户名已存在)。
  2. 后端流程

    • 接收UserRegister模型参数(用户名、密码、创建时间);
    • 检查用户名唯一性(查询数据库是否已存在);
    • 使用passlibhash方法对密码加密(bcrypt 算法);
    • 创建新用户记录并写入数据库,返回成功响应。
  3. 校验逻辑

    • 前端校验减少无效请求,提升用户体验;
    • 后端通过pydantic模型再次校验(如用户名格式^[a-zA-Z0-9\u4e00-\u9fa5]+$、密码长度≥8);
    • 用户名唯一性校验防止重复注册。

3.2 主题切换功能分析

3.2.1 效果图描述

界面支持 “亮色 / 暗色” 两种主题:

  • 亮色主题:白色背景、深色文字、浅灰色边框;
  • 暗色主题:深灰色背景、浅色文字、深灰色边框。
    切换按钮位于侧边栏顶部(图标为 “🌙”(暗色)或 “☀️”(亮色)),点击后实时切换主题,刷新页面后保持用户选择的主题。

 3.2.2 重要代码示例

主题切换核心函数(JavaScript)

// 主题点击事件,更改主题函数
function toggleTheme() {
    // 切换当前主题,如果当前主题是'light',则改为'dark',否则改为'light'
    let change_CurrentTheme = currentTheme=== 'light' ? 'dark' : 'light';
    currentTheme = change_CurrentTheme;
    // 设置body的'data-theme'属性为当前主题,以应用对应的主题样式
    document.body.setAttribute('data-theme', currentTheme);
    // 获取主题切换按钮,如果存在,则更新其文本内容以反映当前主题
    const toggleBtn = document.getElementById('themeToggle');
    if (toggleBtn) toggleBtn.textContent = currentTheme === 'light' ? '🌙' : '☀️';
    // 将当前主题存储在本地存储中,以便持久化用户的选择
    localStorage.setItem('deepseek-theme', currentTheme);
}

// 每次打开时候,自动加载主题设置
function loadTheme() {
    // 从localStorage获取已保存的主题设置
    const savedTheme = localStorage.getItem('deepseek-theme');
    // 如果已保存主题设置,则应用该主题
    if (savedTheme) {
        currentTheme = savedTheme;
        // 设置自定义属性
        document.body.setAttribute('data-theme', currentTheme);
        // 根据当前主题,更新主题切换按钮的显示图标
        const toggleBtn = document.getElementById('themeToggle');
        if (toggleBtn) toggleBtn.textContent = currentTheme === 'light' ? '🌙' : '☀️';
    }
}

 页面初始化时加载主题

window.addEventListener("DOMContentLoaded", async function () {
    // 其他初始化逻辑...
    loadTheme();  // 加载主题
});

3.2.3  重要代码解析

  1. 主题切换逻辑

    • 通过currentTheme变量记录当前主题(默认light);
    • 点击切换按钮时,反转currentTheme状态(lightdarkdarklight);
    • 通过修改bodydata-theme属性(light/dark)触发 CSS 样式变化(如背景色、文字色)。
  2. 主题持久化

    • 使用localStorage.setItem('deepseek-theme', currentTheme)保存用户选择;
    • 页面加载时调用loadTheme(),从localStorage读取保存的主题并应用,实现 “刷新不丢失”。
  3. UI 适配

    • 按钮图标随主题动态变化(“🌙” 表示当前为亮色,点击切换暗色;“☀️” 表示当前为暗色,点击切换亮色);
    • 所有界面元素样式通过 CSS 变量(如--bg-primary--text-primary)定义,主题切换时自动适配。

3.2.4 数据库表设计

主题切换功能基于前端本地存储(localStorage)实现,不涉及数据库表。

3.3 智能对话功能分析

3.3.1 效果图描述

聊天界面分为左侧会话列表和右侧聊天区:

  • 右侧聊天区顶部显示当前会话标题,中间为消息列表(用户消息居右,AI 消息居左),底部为输入区(文本框、发送按钮、文件上传、联网搜索开关);
  • 用户发送消息后,AI 先显示 “思考过程”(可展开 / 收起),再实时流式输出回答内容(逐字显示);
  • 消息支持 Markdown 格式(如代码块、列表、表格),自动渲染为富文本。

 3.3.2 重要代码示例

工具函数:Multiple_conversations(Python)

封装多轮对话函数,chat方法只要传入用户输入的问题(prompt),实现实现代码复用,减少编程的工作量:

import os
import logging

from openai import OpenAI
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

class MultipleConversations:
    def __init__(self,
                 api_key=os.getenv('QWEN_API_KEY'),
                 url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                 model="qwen-plus-2025-04-28",
                 timeout=30,
                ):
        if not api_key:
            raise ValueError("API key 不能为空")
        self.client = OpenAI(api_key=api_key, base_url=url, timeout=timeout)
        self.model = model
        # 初始化 messages 列表,保存对话历史
        self.messages = [
            {"role": "system", "content": "你现在是一个智能聊天助手,你需要尽可能详细地回答问题。"}
        ]

    def chat(self, prompt,webthink =  False):
        """发送用户消息并获取流式响应"""
        try:
            # 添加用户消息
            self.messages.append({"role": "user", "content": prompt})

            response = self.client.chat.completions.create(
                model=self.model,
                messages=self.messages,
                stream=True,
                extra_body={
                            "enable_thinking": True,    # 是否开启深度思考的参数
                            "thinking_budget": 50,
                            "enable_search": webthink,    # 开启联网搜索的参数
                            # "search_options": {
                            #     "forced_search": True,  # 强制搜索
                            #     "search_strategy": "pro",  # 专业搜索模式
                            #     "max_results": 10  # 最多10条结果
                            # }
                        }
                # extra_body={"deep_think": True}   # deepseek-r1深度思考模型
            )
            return response
        except Exception as e:
            logging.error(f"调用 Chat API 失败: {e}")
            return None

    def add_assistant_response(self, content):
        """将模型回复添加到 messages 中"""
        self.messages.append({"role": "assistant", "content": content})

后端智能对话接口(FastAPI)

@app.post("/api/chat")
async def chat_with_ai(
        request: ChatRequest,
        current_user: User = Depends(get_current_user)
):
    """智能助手对话接口 - 支持流式输出"""
    question = request.question
    chat_id = request.chat_id
    enable_search = request.enable_search
    file_content = request.file_content

    # 参数验证
    if not question:
        raise HTTPException(status_code=400, detail="缺少问题内容")
    if not chat_id:
        raise HTTPException(status_code=400, detail="缺少chat_id")

    # 验证聊天会话所有权
    db = Session()
    try:
        session = db.query(ChatSession).filter(
            ChatSession.id == chat_id,
            ChatSession.user_id == current_user.id
        ).first()

        if not session:
            # 如果会话不存在,创建新会话
            session = ChatSession(
                id=chat_id,
                user_id=current_user.id,
                title="新对话",
                created_at=datetime.utcnow(),
                last_activity=datetime.utcnow()
            )
            db.add(session)
            db.commit()
    finally:
        db.close()

    # 保存用户消息到数据库
    await save_user_message(chat_id, question, current_user.id)

    # 构建增强问题(如果有文件内容)
    enhanced_question = question
    if file_content:
        enhanced_question = f"用户问题:{question}\n相关文件内容:\n{file_content}\n请根据用户的问题和提供的文件内容进行回答。"

    # 获取或创建对话实例
    if chat_id not in chat_memory_pool:
        chat_memory_pool[chat_id] = Multiple_conversations.MultipleConversations()

    conversation = chat_memory_pool[chat_id]

    async def event_generator():
        """事件生成器 - 处理流式输出"""
        try:
            response_stream = conversation.chat(enhanced_question, webthink=enable_search)
            full_answer = ""

            for chunk in response_stream:
                reasoning_chunk = chunk.choices[0].delta.reasoning_content
                answer_chunk = chunk.choices[0].delta.content

                # 发送思考过程
                if reasoning_chunk:
                    yield f"data: {json.dumps({'type': 'reasoning', 'content': reasoning_chunk}, ensure_ascii=False)}\n\n"
                    await asyncio.sleep(0.005)

                # 发送答案内容
                if answer_chunk:
                    full_answer += answer_chunk
                    yield f"data: {json.dumps({'type': 'answer', 'content': answer_chunk}, ensure_ascii=False)}\n\n"
                    await asyncio.sleep(0.005)

            # 发送完整答案
            yield f"data: {json.dumps({'type': 'final', 'content': full_answer}, ensure_ascii=False)}\n\n"

            # 保存AI回答到数据库
            await save_ai_message(chat_id, full_answer, current_user.id)

            # 将回答添加到对话历史
            conversation.add_assistant_response(full_answer)
            yield f"data: [DONE]\n\n"

        except Exception as e:
            yield f"data: {json.dumps({'type': 'error', 'content': str(e)}, ensure_ascii=False)}\n\n"

    return StreamingResponse(
        event_generator(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
            "Access-Control-Allow-Headers": "Content-Type, Authorization"
        }
    )

 前端流式消息处理(JavaScript)

async function sendMessage() {
    const message = document.getElementById('messageInput').value.trim();
    const chatId = currentChatId;
    const enableSearch = document.getElementById('searchToggle').checked;

    // 添加用户消息到界面
    addMessage('user', message);

    // 创建思考过程和答案容器
    currentThinkingMessage = createThinkingMessage();
    currentAnswerMessage = createAnswerMessage();

    try {
        // 发送请求
        const response = await fetch('http://127.0.0.1:8080/api/chat', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${localStorage.getItem('token')}`
            },
            body: JSON.stringify({
                question: message,
                chat_id: chatId,
                enable_search: enableSearch,
                file_content: currentFileContent  // 若有选中文件则传递内容
            })
        });

        // 处理流式响应
        const reader = response.body.getReader();
        const decoder = new TextDecoder('utf-8');
        let buffer = '';

        while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            buffer += decoder.decode(value, { stream: true });
            const lines = buffer.split('\n');
            buffer = lines.pop() || '';

            for (const line of lines) {
                if (line.trim().startsWith('data:')) {
                    const content = line.slice(5).trim();
                    if (content === '[DONE]') continue;
                    const parsed = JSON.parse(content);
                    // 追加思考过程
                    if (parsed.type === 'reasoning' && parsed.content) {
                        appendToThinkingMessage(parsed.content);
                    }
                    // 追加答案内容
                    if (parsed.type === 'answer' && parsed.content) {
                        appendToAnswerMessage(parsed.content);
                    }
                }
            }
        }
    } catch (error) {
        // 错误处理
    }
}

3.3.3 重要代码解析

  1. 核心流程

    • 用户发送消息:前端获取输入内容,调用sendMessage(),添加用户消息到界面;
    • 后端处理:验证会话合法性,保存用户消息,构建增强问题(结合文件内容),调用 AI 模型接口;
    • 流式响应:后端通过StreamingResponse实时返回 AI 的 “思考过程” 和 “答案片段”;
    • 前端渲染:逐段接收并显示思考过程和答案,完成后保存 AI 消息到数据库。
  2. 关键技术

    • SSE(Server-Sent Events):实现服务器向客户端的单向流式通信,避免频繁轮询;
    • 上下文管理:通过chat_memory_pool维护每个会话的上下文(MultipleConversations实例),支持多轮对话;
    • Markdown 渲染:使用marked.js将 AI 返回的 Markdown 内容转换为 HTML,提升可读性。
  3. 用户体验优化

    • 显示 AI 思考过程,增强交互透明度;
    • 答案逐字输出,模拟 “实时打字” 效果;
    • 自动滚动到最新消息,避免手动翻页。

3.3.4 数据库表设计

1.ChatSession 表(会话元数据)

class ChatSession(Base):
    __tablename__ = "chat_sessions"
    id = Column(String(255), primary_key=True)  # 会话ID
    user_id = Column(Integer, nullable=False)  # 关联用户ID
    title = Column(String(500), default="新对话")  # 会话标题
    created_at = Column(DateTime, default=datetime.utcnow)  # 创建时间
    last_activity = Column(DateTime, default=datetime.utcnow)  # 最后活动时间
    is_deleted = Column(Integer, default=0)  # 软删除标记(0=未删除,1=已删除)

2. ChatMessage 表(消息内容)

class ChatMessage(Base):
    __tablename__ = "chat_messages"
    id = Column(Integer, primary_key=True, index=True)  # 消息ID
    chat_id = Column(String(255), nullable=False)  # 关联会话ID
    sender = Column(String(50), nullable=False)  # 发送者(user/ai)
    content = Column(Text, nullable=False)  # 消息内容
    timestamp = Column(DateTime, default=datetime.utcnow)  # 时间戳
    message_order = Column(Integer, nullable=False)  # 消息顺序(确保排序正确)

3.4联网搜索功能分析

3.4.1 效果图描述

聊天输入区有 “联网搜索” 复选框(默认未勾选),用户勾选后,AI 在回答时会联网获取最新信息。功能触发后,AI 思考过程中会显示 “正在联网搜索...” 提示,回答内容中会包含基于搜索结果的信息(如时效性数据、新闻等)。如果不启用联网搜索功能,大模型默认是从知识库中间获取信息,然后回答给用户,这样子的,回答的内容通常是不具有时效性的。

3.4.2 重要代码示例

前端联网搜索开关(HTML + JavaScript)

<!-- 前端复选框 -->
<div class="search-toggle-container">
    <input type="checkbox" id="searchToggle" class="toggle-switch">
    <label for="searchToggle" class="toggle-label">联网搜索</label>
</div>
// 发送消息时传递联网状态
async function sendMessage() {
    // 其他逻辑...
    const enableSearch = document.getElementById('searchToggle').checked;  // 获取开关状态
    const response = await fetch('http://127.0.0.1:8080/api/chat', {
        method: 'POST',
        body: JSON.stringify({
            // 其他参数...
            enable_search: enableSearch  // 传递联网搜索状态
        })
    });
}

 后端 AI 接口调用(MultipleConversations 类)

通过获取前端按钮的选中情况,将enable_search传到后端的@app.post("/api/chat")接口,接口中使用response_stream = conversation.chat(enhanced_question, webthink=enable_search)修改webthink参数的值,实现联网搜索。

class MultipleConversations:
    def chat(self, prompt, webthink=False):
        """发送用户消息并获取流式响应"""
        try:
            self.messages.append({"role": "user", "content": prompt})
            response = self.client.chat.completions.create(
                model=self.model,
                messages=self.messages,
                stream=True,
                extra_body={
                    "enable_thinking": True,
                    "thinking_budget": 50,
                    "enable_search": webthink  # 控制是否开启联网搜索
                }
            )
            return response
        except Exception as e:
            logging.error(f"调用 Chat API 失败: {e}")
            return None

 3.4.3 重要代码解析

  1. 功能触发逻辑

    • 前端通过复选框id="searchToggle"控制是否开启联网搜索,checked属性表示状态;
    • 发送消息时,将enable_search参数(布尔值)传递给后端/api/chat接口;
    • 后端调用 AI 模型时,通过webthink=enable_search参数控制是否启用联网功能(extra_body={"enable_search": webthink})。
  2. AI 交互流程

    • enable_search=True时,AI 模型会触发联网搜索,获取外部数据(如实时新闻、天气、知识库更新等);
    • 搜索结果会被 AI 整合到回答中,提升内容的时效性和准确性;
    • 思考过程中会包含 “搜索到 xxx 信息” 等描述,明确告知用户哪些内容基于搜索。
  3. 适用场景

    • 需要实时数据的问题(如 “今天的天气如何?”“某股票最新价格是多少?”);
    • 需要最新信息的问题(如 “2025 年最新政策是什么?”);
    • 本地知识未覆盖的领域(如特定行业动态、新兴技术等)。

3.4.4 数据库表设计

联网搜索功能是 AI 模型的动态行为,不直接涉及数据库存储(搜索结果会随 AI 回答存入ChatMessage表的content字段)。

3.5 文件上传功能分析

3.5.1 效果图描述

聊天输入区有 “📁 选择文件” 按钮,点击后可选择本地文件(支持 txt、pdf、docx 等格式)。上传时显示 “📤 上传中...” 提示,成功后显示 “文件 xxx 上传成功” 系统消息,并在输入区下方显示 “已上传文件” 下拉框(可选择文件、清除文件)。选择文件后,AI 回答会结合文件内容。

 

3.5.2 重要代码示例

 后端文件上传接口(FastAPI)

@app.post("/api/upload", response_model=ResponseModel)
async def upload_file(
        chat_id: str = Form(...),
        file: UploadFile = File(...),
        current_user: User = Depends(get_current_user)
):
    """文件上传接口"""
    try:
        # 验证文件大小
        contents = await file.read()
        if len(contents) > MAX_FILE_SIZE:
            return ResponseModel(code=400, message="文件大小超过限制(10MB)")

        # 验证文件类型
        file_ext = os.path.splitext(file.filename)[1].lower()
        if file_ext not in ALLOWED_EXTENSIONS:
            return ResponseModel(
                code=400,
                message=f"不支持的文件类型,支持的类型:{', '.join(ALLOWED_EXTENSIONS)}"
            )

        # 生成唯一文件名
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        unique_filename = f"{timestamp}_{file.filename}"
        file_path = os.path.join(UPLOAD_FOLDER, unique_filename)

        # 保存文件
        with open(file_path, "wb") as f:
            f.write(contents)

        # 提取文件内容
        file_content = extract_file_content(file_path, file_ext)

        # 保存文件记录到数据库
        db = Session()
        try:
            file_record = FileRecord(
                chat_id=chat_id,
                filename=unique_filename,
                original_filename=file.filename,
                file_path=file_path,
                file_content=file_content,
                upload_time=datetime.now().isoformat()
            )
            db.add(file_record)
            db.commit()
            db.refresh(file_record)

            return ResponseModel(
                code=200,
                message="文件上传成功",
                data={
                    "file_id": file_record.id,
                    "filename": file.filename,
                    "content_preview": file_content
                }
            )
        finally:
            db.close()

    except Exception as e:
        print(f"文件上传错误: {str(e)}")
        return ResponseModel(code=500, message=f"文件上传失败: {str(e)}")


@app.get("/api/files/{chat_id}", response_model=ResponseModel)
async def get_chat_files(
        chat_id: str,
        current_user: User = Depends(get_current_user)
):
    """获取指定聊天会话的文件列表"""
    db = Session()
    try:
        files = db.query(FileRecord).filter(FileRecord.chat_id == chat_id).all()
        file_list = []

        for file_record in files:
            content_preview = file_record.file_content
            if len(content_preview) > 100:
                content_preview = content_preview[:100] + "..."

            file_list.append({
                "id": file_record.id,
                "filename": file_record.original_filename,
                "upload_time": file_record.upload_time,
                "content_preview": content_preview
            })

        return ResponseModel(
            code=200,
            message="获取成功",
            data={"files": file_list}
        )
    finally:
        db.close()


@app.get("/api/file/{file_id}", response_model=ResponseModel)
async def get_file_content(
        file_id: int,
        current_user: User = Depends(get_current_user)
):
    """获取单个文件的完整内容"""
    db = Session()
    try:
        file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first()
        if not file_record:
            return ResponseModel(code=404, message="文件不存在")

        return ResponseModel(
            code=200,
            message="获取文件内容成功",
            data={
                "content": file_record.file_content,
                "filename": file_record.original_filename
            }
        )
    except Exception as e:
        print(f"获取文件内容错误: {str(e)}")
        return ResponseModel(code=500, message=f"获取文件内容失败: {str(e)}")
    finally:
        db.close()

前端文件上传与处理(JavaScript) 

// 处理文件上传
async function handleFileUpload(event) {
    const file = event.target.files[0];
    if (!file) return;
    
    // 验证文件大小和类型
    const maxSize = 10 * 1024 * 1024;  // 10MB
    if (file.size > maxSize) {
        alert('文件大小不能超过 10MB');
        return;
    }
    const allowedTypes = ['.txt', '.md', '.pdf', '.doc', '.docx', '.json', '.csv', '.xlsx'];
    const fileName = file.name.toLowerCase();
    const isValidType = allowedTypes.some(type => fileName.endsWith(type));
    if (!isValidType) {
        alert(`不支持的文件类型。支持的类型:${allowedTypes.join(', ')}`);
        return;
    }
    
    // 发送上传请求
    const formData = new FormData();
    formData.append('file', file);
    formData.append('chat_id', currentChatId);
    const response = await axios.post('http://127.0.0.1:8080/api/upload', formData, {
        headers: {'Content-Type': 'multipart/form-data'}
    });
    
    // 处理上传结果
    if (response.data.code === 200) {
        uploadedFiles.push({  // 保存文件信息到本地数组
            id: response.data.data.file_id,
            name: response.data.data.filename,
            content: response.data.data.content_preview
        });
        updateFileDisplay();  // 更新文件下拉框
        showUploadSuccess(response.data.data.filename);  // 显示成功消息
    }
}

// 选择文件后获取内容(供AI参考)
async function selectFile(fileId) {
    const response = await axios.get(`http://127.0.0.1:8080/api/file/${fileId}`);
    if (response.data.code === 200) {
        currentFileContent = response.data.data.content;  // 保存文件内容
        showFileSelectedMessage(response.data.data.filename);  // 提示用户
    }
}

 3.5.3 重要代码解析

  1. 文件上传流程

    • 前端:用户选择文件后,验证大小(≤10MB)和类型(支持 txt、pdf 等),通过FormData构造请求,调用/api/upload接口;
    • 后端:接收文件,生成唯一文件名(避免冲突),保存到uploads目录,调用extract_file_content提取内容;
    • 存储:文件记录(路径、原始名、内容等)存入FileRecord表,前端保存文件 ID 供后续选择。
  2. 文件内容提取

    • 针对不同类型文件使用专用库:
      • txt/md/json/csv:直接读取文本;
      • pdf:使用PyPDF2提取文字;
      • doc/docx:使用python-docx读取段落;
      • xlsx:使用pandas读取表格内容;
    • 提取失败时返回错误信息(如 “PDF 解析失败”)。
  3. 与 AI 交互

    • 用户选择文件后,前端调用/api/file/{fileId}获取完整内容,保存到currentFileContent
    • 发送消息时,将currentFileContent作为file_content参数传递给后端;
    • 后端构建增强问题(用户问题+文件内容),AI 基于此生成回答。

3.5.4 数据库表设计(FileRecord 表)

class FileRecord(Base):
    __tablename__ = "file_records"
    id = Column(Integer, primary_key=True, index=True)  # 文件记录ID
    chat_id = Column(String(255), nullable=False)  # 关联会话ID
    filename = Column(String(255), nullable=False)  # 服务器保存的文件名(唯一)
    original_filename = Column(String(255), nullable=False)  # 原始文件名
    file_path = Column(String(500), nullable=False)  # 文件存储路径
    file_content = Column(Text, nullable=True)  # 提取的文件内容
    upload_time = Column(String(255), nullable=False)  # 上传时间(ISO格式)

3.6 会话管理功能分析

3.6.1 创建新对话(增)

①效果图描述

侧边栏顶部有 “➕ 新建对话” 按钮,点击后创建一个标题为 “新对话” 的会话,自动切换到该会话,聊天区显示初始 AI 消息(“您好!我是 DeepSeek AI 智能助手...”)。新会话会添加到侧边栏会话列表顶部(按最后活动时间排序)。

 ②重要代码示例

后端创建会话接口(FastAPI)

@app.post("/api/chat-sessions", response_model=ResponseModel)
async def create_chat_session(
        session_data: ChatSessionCreate,
        current_user: User = Depends(get_current_user)
):
    db = Session()
    try:
        # 检查会话是否已存在
        existing_session = db.query(ChatSession).filter(
            ChatSession.id == session_data.chat_id,
            ChatSession.user_id == current_user.id
        ).first()
        if existing_session:
            return ResponseModel(code=400, message="聊天会话已存在")
        
        # 创建新会话
        chat_session = ChatSession(
            id=session_data.chat_id,
            user_id=current_user.id,
            title=session_data.title,
            created_at=datetime.utcnow(),
            last_activity=datetime.utcnow()
        )
        db.add(chat_session)
        db.flush()  # 暂存会话,获取ID
        
        # 添加初始AI消息
        initial_message = ChatMessage(
            chat_id=session_data.chat_id,
            sender="ai",
            content="您好!我是 DeepSeek AI 智能助手...",
            timestamp=datetime.utcnow(),
            message_order=1
        )
        db.add(initial_message)
        db.commit()
        
        return ResponseModel(
            code=200,
            message="聊天会话创建成功",
            data={"chat_id": chat_session.id, "title": chat_session.title}
        )
    finally:
        db.close()

前端创建新对话(JavaScript)

async function startNewChat() {
    const newChatId = generateId();  // 生成唯一ID(如时间戳+随机字符串)
    const response = await axios.post('http://127.0.0.1:8080/api/chat-sessions', {
        chat_id: newChatId,
        title: '新对话'
    });
    if (response.data.code === 200) {
        await loadChatSessions();  // 重新加载会话列表
        currentChatId = newChatId;  // 切换到新会话
        await loadChat(newChatId);  // 加载新会话消息
        renderChatHistory();  // 更新侧边栏列表
    }
}

// 生成唯一会话ID
function generateId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
③重要代码解析
  1. 创建流程

    • 前端调用generateId()生成唯一chat_id(避免重复);
    • 发送 POST 请求至/api/chat-sessions,携带chat_id和默认标题 “新对话”;
    • 后端验证会话 ID 唯一性,创建ChatSession记录,并添加初始 AI 消息(message_order=1);
    • 前端更新会话列表并切换到新会话。
  2. 初始消息

    • 新会话创建时自动添加 AI 欢迎消息,确保聊天区有初始内容;
    • 消息顺序message_order从 1 开始,后续消息依次递增(确保排序正确)。
  3. 用户体验

    • 新会话默认标题为 “新对话”,发送第一条用户消息后自动更新为消息前 20 字(提升辨识度);
    • 新会话在列表中置顶(按last_activity倒序),方便用户快速找到。
④数据库表设计

涉及ChatSession表(存储会话元数据)和ChatMessage表(存储初始消息),结构同 3.3 节。

3.6.2 重命名会话(改)

①效果图描述

侧边栏会话项 hover 时显示 “✏️”(重命名)按钮,点击后弹出输入框(默认填充当前标题),输入新标题后确认,会话列表和聊天区标题同步更新。

②重要代码示例

后端更新会话标题接口(FastAPI)

@app.put("/api/chat-sessions/{chat_id}", response_model=ResponseModel)
async def update_chat_session(
        chat_id: str,
        session_data: ChatSessionUpdate,
        current_user: User = Depends(get_current_user)
):
    db = Session()
    try:
        # 验证会话所有权
        session = db.query(ChatSession).filter(
            ChatSession.id == chat_id,
            ChatSession.user_id == current_user.id
        ).first()
        if not session:
            return ResponseModel(code=404, message="聊天会话不存在")
        
        # 更新标题和最后活动时间
        session.title = session_data.title
        session.last_activity = datetime.utcnow()
        db.commit()
        return ResponseModel(code=200, message="会话标题更新成功")
    finally:
        db.close()

前端重命名逻辑(JavaScript)

// 触发重命名(显示输入框)
function renameChatPrompt(chatId) {
    const chat = chatSessions.find(c => c.id === chatId);
    const newTitle = prompt('请输入新的对话标题:', chat.title);
    if (newTitle && newTitle.trim() && newTitle !== chat.title) {
        renameChat(chatId, newTitle.trim());  // 调用更新函数
    }
}

// 执行重命名
async function renameChat(chatId, newTitle) {
    const response = await axios.put(`http://127.0.0.1:8080/api/chat-sessions/${chatId}`, {
        title: newTitle
    });
    if (response.data.code === 200) {
        // 更新本地会话数据
        const chat = chatSessions.find(c => c.id === chatId);
        if (chat) chat.title = newTitle;
        // 更新当前会话标题显示
        if (chatId === currentChatId) {
            document.getElementById('currentChatTitle').textContent = newTitle;
        }
        renderChatHistory();  // 刷新侧边栏列表
    }
}
 ③重要代码解析
  1. 重命名流程

    • 前端通过prompt获取用户输入的新标题(简单直观);
    • 调用PUT /api/chat-sessions/{chat_id}接口,传递新标题;
    • 后端验证会话所有权(仅创建者可修改),更新titlelast_activity
    • 前端同步更新本地会话数据和 UI(侧边栏列表、聊天区标题)。
  2. 权限控制

    • 后端通过current_user.idChatSession.user_id比对,确保用户只能修改自己的会话;
    • 若会话不存在或无权限,返回 404 错误。
  3. 自动命名机制

    • 新会话默认标题为 “新对话”;
    • 发送第一条用户消息后,系统自动将标题更新为消息前 20 字(通过updateChatTitle函数实现),减少用户手动命名操作。
④数据库表设计

依赖ChatSession表的title字段(更新该字段实现重命名),结构同 3.3 节。

3.6.3 查找会话(查)

①效果图描述

侧边栏顶部有搜索框(placeholder:“搜索对话...”),输入关键词后,会话列表实时过滤:仅显示标题或最后一条消息包含关键词的会话,其他会话隐藏。支持模糊匹配(不区分大小写)。

②重要代码示例

前端搜索会话逻辑(JavaScript)

function searchChats() {
    // 获取搜索关键词(转为小写,不区分大小写)
    const searchTerm = document.getElementById('searchBox').value.toLowerCase();
    // 获取所有会话项
    const chatItems = document.querySelectorAll('.chat-item');
    
    // 遍历过滤
    chatItems.forEach(item => {
        // 获取会话标题和预览文本(最后一条消息)
        const title = item.querySelector('.chat-title').textContent.toLowerCase();
        const preview = item.querySelector('.chat-preview').textContent.toLowerCase();
        // 显示包含关键词的会话,隐藏不包含的
        item.style.display = (title.includes(searchTerm) || preview.includes(searchTerm)) 
            ? 'flex' 
            : 'none';
    });
}

// 绑定搜索框输入事件
document.getElementById('searchBox').addEventListener('input', searchChats);
③重要代码解析
  1. 搜索逻辑

    • 监听搜索框input事件,实时获取输入的关键词(转为小写,支持不区分大小写搜索);
    • 遍历所有会话项,提取标题(chat-title)和最后一条消息预览(chat-preview);
    • 若标题或预览包含关键词,显示会话项(display: flex);否则隐藏(display: none)。
  2. 用户体验

    • 实时过滤(输入即生效),无需点击搜索按钮;
    • 支持模糊匹配(如搜索 “python” 可匹配 “Python 编程”“学习 python”);
    • 搜索范围覆盖标题和消息内容,提升查找准确性。
  3. 性能优化

    • 仅操作 DOM 样式(显示 / 隐藏),不重新渲染列表,减少性能消耗;
    • 关键词为空时,所有会话默认显示。
④数据库表设计

查找会话功能基于前端本地数据过滤实现,不涉及数据库查询(会话数据已通过/api/chat-sessions接口加载到前端)。

3.6.4 删除会话(删)

①效果图描述

侧边栏会话项 hover 时显示 “🗑️”(删除)按钮,点击后弹出确认框(“确定要删除对话 xxx 吗?此操作无法撤销”),确认后会话从列表中移除,自动切换到下一个会话(若删除的是最后一个会话,自动创建新会话)。

 ②重要代码示例

后端删除会话接口(FastAPI)

@app.delete("/api/chat-sessions/{chat_id}", response_model=ResponseModel)
async def delete_chat_session(
        chat_id: str,
        current_user: User = Depends(get_current_user)
):
    db = Session()
    try:
        # 验证会话存在且未被删除
        session = db.query(ChatSession).filter(
            ChatSession.id == chat_id,
            ChatSession.user_id == current_user.id,
            ChatSession.is_deleted == 0
        ).first()
        if not session:
            return ResponseModel(code=404, message="聊天会话不存在")
        
        # 软删除(标记is_deleted=1)
        session.is_deleted = 1
        db.commit()
        return ResponseModel(code=200, message="聊天会话删除成功")
    finally:
        db.close()

 前端删除会话逻辑(JavaScript)

// 显示删除确认框
function deleteChatPrompt(chatId) {
    // 确保至少保留一个会话
    if (chatSessions.length <= 1) {
        alert('至少需要保留一个对话');
        return;
    }
    const chat = chatSessions.find(c => c.id === chatId);
    // 显示确认对话框
    showConfirmDialog(
        '删除对话',
        `确定要删除对话"${chat.title}"吗?此操作无法撤销。`,
        function() {
            deleteChat(chatId);  // 确认后执行删除
        }
    );
}

// 执行删除
async function deleteChat(chatId) {
    const response = await axios.delete(`http://127.0.0.1:8080/api/chat-sessions/${chatId}`);
    if (response.data.code === 200) {
        // 从本地列表移除会话
        const index = chatSessions.findIndex(c => c.id === chatId);
        if (index !== -1) chatSessions.splice(index, 1);
        
        // 清理内存中的会话数据
        if (chat_memory_pool[chatId]) delete chat_memory_pool[chatId];
        
        // 切换会话(若删除的是当前会话)
        if (chatId === currentChatId) {
            if (chatSessions.length > 0) {
                currentChatId = chatSessions[0].id;  // 切换到第一个会话
                await loadChat(currentChatId);
            } else {
                await startNewChat();  // 若没有会话,创建新会话
            }
        }
        renderChatHistory();  // 刷新侧边栏
    }
}
 ③重要代码解析
  1. 删除机制

    • 采用软删除(而非物理删除):后端将ChatSession.is_deleted设为 1,查询时过滤已删除会话(is_deleted=0);
    • 优势:支持数据恢复(若需恢复,只需将is_deleted改回 0),避免误删后无法恢复。
  2. 删除流程

    • 前端显示确认框,防止误操作;
    • 验证会话数量(至少保留 1 个),避免无会话状态;
    • 调用DELETE /api/chat-sessions/{chat_id}接口,后端标记会话为删除;
    • 前端从本地列表移除会话,若删除的是当前会话,自动切换到其他会话。
  3. 数据清理

    • 前端删除chat_memory_pool中该会话的上下文数据,释放内存;
    • 后端不删除关联的ChatMessageFileRecord(便于后续可能的恢复)。
④数据库表设计

依赖ChatSession表的is_deleted字段(0 = 未删除,1 = 已删除),结构同 3.3 节。

3.7 角色绑定历史聊天记录功能分析

①效果图描述

所有聊天记录与当前会话绑定,切换会话时,聊天区仅显示该会话的消息(用户消息标记 “用户”,AI 消息标记 “🐋”),消息按时间顺序排列。AI 回答时会参考当前会话的历史消息(如 “之前提到的 xxx...”),保持上下文连贯。

账号为:qqqqqqqq 的历史记录

 账号为:admin123 的历史记录

②重要代码示例

后端会话消息查询接口(FastAPI)

@app.get("/api/chat-sessions/{chat_id}/messages", response_model=ResponseModel)
async def get_chat_messages(
        chat_id: str,
        current_user: User = Depends(get_current_user)
):
    db = Session()
    try:
        # 验证会话所有权
        session = db.query(ChatSession).filter(
            ChatSession.id == chat_id,
            ChatSession.user_id == current_user.id
        ).first()
        if not session:
            return ResponseModel(code=404, message="聊天会话不存在")
        
        # 查询该会话的所有消息(按顺序排列)
        messages = db.query(ChatMessage).filter(
            ChatMessage.chat_id == chat_id
        ).order_by(ChatMessage.message_order.asc()).all()
        
        # 格式化消息列表
        message_list = []
        for msg in messages:
            message_list.append({
                "sender": msg.sender,
                "content": msg.content,
                "timestamp": msg.timestamp.isoformat()
            })
        
        return ResponseModel(
            code=200,
            data={
                "chat_id": chat_id,
                "title": session.title,
                "messages": message_list
            }
        )
    finally:
        db.close()

前端加载会话消息(JavaScript)

async function loadChat(chatId) {
    const response = await axios.get(`http://127.0.0.1:8080/api/chat-sessions/${chatId}/messages`);
    if (response.data.code === 200) {
        const chatData = response.data.data;
        // 更新当前会话的消息列表
        const currentChat = chatSessions.find(c => c.id === chatId);
        if (currentChat) {
            currentChat.messages = chatData.messages.map(msg => ({
                sender: msg.sender,
                content: msg.content,
                timestamp: new Date(msg.timestamp)
            }));
            currentChat.title = chatData.title;
        }
        // 渲染消息到聊天区
        const container = document.getElementById('messagesContainer');
        container.innerHTML = '';  // 清空现有消息
        chatData.messages.forEach(message => {
            addMessageToDOM(message.sender, message.content, new Date(message.timestamp));
        });
        // 滚动到底部
        container.scrollTop = container.scrollHeight;
    }
}

AI 上下文管理(MultipleConversations 类)

class MultipleConversations:
    def __init__(self, api_key, url, model):
        self.client = OpenAI(api_key=api_key, base_url=url)
        self.model = model
        # 初始化消息列表(保存会话历史,用于上下文理解)
        self.messages = [
            {"role": "system", "content": "你现在是一个智能聊天助手,需要尽可能详细地回答问题。"}
        ]
    
    def chat(self, prompt, webthink=False):
        # 添加用户消息到上下文
        self.messages.append({"role": "user", "content": prompt})
        # 调用AI模型(携带完整上下文)
        response = self.client.chat.completions.create(
            model=self.model,
            messages=self.messages,  # 传递完整历史消息
            stream=True,
            extra_body={"enable_thinking": True, "enable_search": webthink}
        )
        return response
    
    def add_assistant_response(self, content):
        # 添加AI回复到上下文(维护多轮对话)
        self.messages.append({"role": "assistant", "content": content})
③重要代码解析
  1. 角色与历史记录绑定逻辑

    • 角色区分ChatMessage.sender字段标记消息发送者(user/ai),前端据此显示不同头像(用户 =“用户”,AI=“🐋”);
    • 会话绑定:所有消息通过chat_id关联到具体会话,查询时仅返回该会话的消息;
    • 顺序保证message_order字段记录消息顺序,确保加载时按发送顺序显示。
  2. 上下文理解

    • 后端通过MultipleConversations类的messages列表维护每个会话的历史消息;
    • 每次调用chat()方法时,将完整messages(系统提示 + 用户消息 + AI 回复)传递给 AI 模型,使其理解上下文;
    • 新消息发送后,通过add_assistant_response将 AI 回复添加到messages,确保多轮对话连贯。
  3. 数据加载

    • 切换会话时,前端调用loadChat(chatId),获取该会话的所有消息;
    • 清空当前聊天区并重新渲染新会话的消息,实现 “会话隔离”(不同会话消息不混合)。
④数据库表设计
  1. ChatMessage 表(核心,绑定角色和会话):同 3.3 节,通过chat_id关联会话,sender区分角色。
  2. ChatSession 表:通过id唯一标识会话,确保消息与会话的绑定关系。

3.8 退出登录功能分析

3.8.1 效果图描述

聊天区顶部有 “退出登录” 按钮,点击后弹出确认框(“是否确定退出登录?退出后将无法使用系统功能”),确认后清除登录状态,自动跳转至登录页面。再次访问聊天页面时,会提示 “您还没有登录!请先登录” 并跳转。

 3.8.2 重要代码示例

前端退出登录逻辑(JavaScript)

// 退出登录函数
function Sign_out(){
    // 显示确认对话框
    showConfirmDialog(
        '是否确定退出登录',
        '退出登录之后将无法使用系统功能,请确认!',
        function () {
            // 清除本地token
            localStorage.removeItem('token');
            // 跳转登录页面
            window.location.href = 'login.html';
        }
    );
}

// 页面加载时验证登录状态
window.addEventListener("DOMContentLoaded", async function () {
    const token = localStorage.getItem('token');
    if (!token || token.trim() === "") {
        alert("您还没有登录!请先登录.....");
        setTimeout(() => {
            window.location.href = 'login.html';  // 未登录则跳转
        }, 1500);
        return;
    }
    // 已登录,初始化应用
    await initApp();
});

 后端认证拦截(FastAPI)

# 验证token依赖
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> str:
    token = credentials.credentials
    try:
        payload = jwt.decode(token, SECURITY_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=401, detail="无效的认证凭据")
        return username
    except jwt.PyJWTError:
        raise HTTPException(status_code=401, detail="无效的认证凭据")

# 所有需要登录的接口都依赖get_current_user
@app.get("/api/chat-sessions", response_model=ResponseModel)
async def get_chat_sessions(current_user: User = Depends(get_current_user)):
    # 接口逻辑...

 前端请求拦截器(统一添加 token)

axios.interceptors.request.use(
    config => {
        const token = localStorage.getItem('token');
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;  // 添加token到请求头
        }
        return config;
    },
    error => Promise.reject(error)
);

// 响应拦截器(处理token过期)
axios.interceptors.response.use(
    response => response,
    error => {
        if (error.response && error.response.status === 401) {
            localStorage.removeItem('token');  // 清除无效token
            alert('登录已过期,请重新登录');
            window.location.href = 'login.html';  // 跳转登录
        }
        return Promise.reject(error);
    }
);

3.8.3 重要代码解析

  1. 退出流程

    • 点击 “退出登录” 按钮,显示确认框防止误操作;
    • 确认后调用localStorage.removeItem('token')清除 JWT 令牌;
    • 跳转至登录页面(login.html),确保退出后无法访问受保护资源。
  2. 登录状态验证

    • 前端:页面加载时检查localStorage中是否有token,无则提示并跳转登录页;
    • 后端:所有需要登录的接口(如/api/chat/api/chat-sessions)都依赖get_current_user,该依赖通过verify_token验证Authorization头中的 token;
    • 拦截器:前端请求拦截器自动为请求添加 token,响应拦截器处理 401 错误(token 无效 / 过期),清除 token 并跳转登录。
  3. 安全性

    • 退出后彻底清除本地 token,避免他人使用设备访问;
    • 后端严格验证 token 有效性,确保未登录用户无法调用接口;
    • token 有过期时间(30 分钟),即使未手动退出,过期后也需重新登录。

3.8.4 数据库表设计

退出登录功能基于 JWT 令牌的清除和验证实现,不涉及数据库表操作。

4. 项目总结

DeepSeek AI 智能助手项目是一款基于前后端分离架构的智能聊天系统,旨在为用户提供自然、高效的人机交互体验。项目整合了自然语言处理、文件解析、会话管理等核心功能,通过模块化设计实现了功能的灵活扩展,最终达成了以下目标:

4.1. 核心功能实现

  • 完整的用户体系:实现了注册、登录、权限验证等功能,基于 JWT 令牌机制保障用户数据安全,支持会话过期自动处理。
  • 智能对话交互:通过流式响应(SSE)技术实现 AI 思考过程与回答内容的实时展示,支持多轮对话上下文理解,结合 Markdown 渲染提升内容可读性。
  • 文件处理能力:支持 txt、md、pdf、doc、xlsx 等多种格式文件的上传与解析,可基于文件内容进行精准问答,满足用户结合本地数据交互的需求。
  • 会话全生命周期管理:实现了会话的创建、切换、重命名、删除(软删除)等功能,支持会话搜索与历史记录持久化,方便用户多场景切换。
  • 用户体验优化:提供明暗主题切换、响应式布局、操作确认提示等细节设计,确保不同设备、不同使用习惯的用户均能获得流畅体验。

4.2. 技术亮点

  • 架构设计:采用 FastAPI 作为后端框架,利用其异步特性提升并发处理能力;前端通过原生 JavaScript 实现轻量交互,减少第三方依赖,提升加载速度。
  • 数据持久化:基于 SQLAlchemy ORM 实现数据库操作,设计 User、ChatSession、ChatMessage、FileRecord 等表结构,通过外键关联确保数据一致性。
  • 扩展性设计:通过MultipleConversations类封装 AI 模型接口,支持快速切换底层模型;文件解析逻辑模块化,便于新增文件格式支持。
  • 安全性保障:密码加密存储(bcrypt 算法)、接口权限校验、文件类型与大小限制等措施,降低数据泄露与恶意攻击风险。

4.3. 存在的挑战与解决方案

  • 流式响应处理:初期面临前端接收不完整、渲染延迟等问题,通过缓冲区拼接与逐行解析优化,实现了稳定的实时输出。
  • 文件解析兼容性:不同格式文件(如加密 PDF、复杂排版 docx)解析易出错,通过异常捕获与友好提示机制,确保用户明确错误原因。
  • 会话上下文一致性:多轮对话中存在上下文丢失问题,通过内存池维护会话状态与数据库持久化结合,兼顾性能与数据可靠性。

5.未来展望

基于现有功能基础,项目可在以下方向进一步拓展,提升产品竞争力与用户价值:

5.1. 功能拓展

  • 多模态交互支持:新增图片、音频等输入方式,结合 OCR 技术提取图片文字,支持语音转文字输入,突破纯文本交互限制。
  • 多模态的实现
    • 支持模型切换(如通义千问、GPT 系列、开源模型等),允许用户根据需求选择算力与精度;
    • 新增 AI 功能模块,如代码生成与运行、数据可视化、文档摘要生成等,提升工具属性。
  • 会话协作与分享:支持多用户共享会话、实时协作编辑,新增会话链接分享功能,便于用户跨场景同步信息。
  • 文生图和图生视频功能的引入:除了文本的处理,扩展图片和视频放心的业务。

5.2. 性能与体验优化

  • 前端架构升级:采用 Vue 或 React 重构前端,引入状态管理与组件化设计,提升复杂场景下的交互流畅度(如大量历史消息加载、文件批量上传)。
  • 后端性能优化
    • 引入 Redis 缓存高频访问数据(如会话列表、用户信息),减少数据库查询压力;
    • 实现文件上传断点续传与异步解析,支持 GB 级大文件处理。
  • 个性化推荐:基于用户对话历史与偏好,推荐相关功能或内容(如常用文件类型、高频使用的 AI 能力),提升用户粘性。

5.3. 生态与商业化

  • 开放平台建设:提供 API 接口与 SDK,支持第三方应用集成,形成开发者生态(如嵌入企业内部系统、个人工具链)。
  • 分层服务模式
    • 免费版:基础对话与文件处理功能;
    • 付费版:高级模型调用、大文件处理、多设备同步等增值服务;
    • 企业版:私有部署、数据隔离、定制化训练等解决方案。
  • 数据安全与合规:完善数据脱敏、隐私政策与合规认证(如 GDPR),满足企业级用户的数据安全需求。

5.4. 技术探索

  • 本地部署支持:优化代码结构,支持轻量化本地部署,满足无网络环境或数据敏感场景的使用需求。
  • AI 模型微调:基于用户对话数据进行模型微调,提升特定领域(如医疗、教育、编程)的回答精度。

6. 总结

DeepSeek AI 智能助手项目通过扎实的技术实现与用户体验设计,构建了一个功能完备的智能交互系统。未来,通过持续优化核心功能、拓展多模态交互与商业化生态,有望从工具型产品升级为覆盖个人与企业场景的智能服务平台,为用户创造更高效、更个性化的智能体验。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐