手把手带你手搓一个DeepSeek AI 智能助手——FastAPI + 通义千问 全栈对话系统(全网最详细)
DeepSeek AI 智能助手项目是一款基于前后端分离架构的智能聊天系统,旨在为用户提供自然、高效的人机交互体验。DeepSeek AI 智能助手项目通过扎实的技术实现与用户体验设计,构建了一个功能完备的智能交互系统。未来,通过持续优化核心功能、拓展多模态交互与商业化生态,有望从工具型产品升级为覆盖个人与企业场景的智能服务平台,为用户创造更高效、更个性化的智能体验。
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 架构图内容解析
-
用户入口:浏览器
- 作为前端交互载体,用户通过浏览器界面(如聊天窗口、文件上传按钮、搜索开关等)发起操作请求,包括输入文本、上传文件、切换会话等。
- 前端将用户行为封装为 HTTP 请求(如 POST/GET),通过 API 调用后端服务。
-
核心服务层:FastAPI
- 作为后端核心框架,承接浏览器发起的所有请求,负责路由分发、参数校验、业务逻辑处理,并协调各功能模块协作。
- 是连接前端与底层服务的中间枢纽,向下调用认证、会话、文件处理等模块,向上通过 SSE(Server-Sent Events)返回流式响应。
-
功能模块拆分
- [JWT 认证]:负责用户身份验证与权限管理。
- 登录时验证用户名密码,生成 JWT 令牌;
- 后续请求通过令牌验证用户合法性,确保数据安全。
- [会话管理]:维护用户的聊天会话生命周期。
- 管理会话的创建、查询、更新(如重命名)、删除(软删除);
- 关联用户与会话,记录会话标题、创建时间、最后活动时间等元数据。
- [文件上传 / 解析]:处理用户上传的文件并提取内容。
- 验证文件大小(≤10MB)和格式(支持 txt、pdf、docx 等);
- 通过 PyPDF2、python-docx 等工具解析文件内容,为 AI 问答提供上下文。
- [对话引擎]:系统核心模块,协调 AI 交互逻辑。
- 接收用户提问,结合上下文(历史消息、文件内容)生成增强查询;
- 根据用户是否开启 “联网搜索”,决定调用外部 API 或直接使用本地模型;
- 将 AI 响应通过 SSE 流式返回给前端,实现 “边思考边输出” 的效果。
- [JWT 认证]:负责用户身份验证与权限管理。
-
AI 能力层
- {联网?}:分支逻辑判断,根据用户需求切换 AI 响应模式。
- [外部搜索 API]:当用户开启 “联网搜索” 时,调用外部搜索引擎获取实时数据(如新闻、天气等),增强回答时效性;
- [通义千问]:默认模式,使用通义千问大模型进行本地推理,基于历史对话和文件内容生成回答。
- {联网?}:分支逻辑判断,根据用户需求切换 AI 响应模式。
-
响应机制:[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。
③ 重要代码解析
-
前端流程:
- 监听登录表单提交事件,阻止默认刷新行为;
- 获取用户名和密码并校验格式;
- 通过 Axios 发送 POST 请求至
/api/login; - 登录成功后,将 JWT 令牌存入
localStorage,并跳转至聊天页面; - 失败时显示错误提示(如用户名不存在、密码错误)。
-
后端流程:
- 接收
UserLogin模型参数(用户名、密码); - 从数据库查询用户,验证用户是否存在;
- 使用
passlib的verify方法校验密码(与数据库中加密存储的密码比对); - 验证通过后,调用
create_access_token生成 JWT 令牌(有效期 30 分钟); - 返回包含令牌的成功响应。
- 接收
-
安全性:
- 密码不明文传输,通过 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格式字符串)
重要代码解析
-
前端流程:
- 监听注册表单提交事件,阻止默认行为;
- 前端校验:用户名长度≥8、密码为 8-20 位且包含字母和数字、两次密码一致;
- 校验通过后发送 POST 请求至
/api/register; - 注册成功后显示提示并跳转登录页面,失败则显示错误信息(如用户名已存在)。
-
后端流程:
- 接收
UserRegister模型参数(用户名、密码、创建时间); - 检查用户名唯一性(查询数据库是否已存在);
- 使用
passlib的hash方法对密码加密(bcrypt 算法); - 创建新用户记录并写入数据库,返回成功响应。
- 接收
-
校验逻辑:
- 前端校验减少无效请求,提升用户体验;
- 后端通过
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 重要代码解析
-
主题切换逻辑:
- 通过
currentTheme变量记录当前主题(默认light); - 点击切换按钮时,反转
currentTheme状态(light→dark或dark→light); - 通过修改
body的data-theme属性(light/dark)触发 CSS 样式变化(如背景色、文字色)。
- 通过
-
主题持久化:
- 使用
localStorage.setItem('deepseek-theme', currentTheme)保存用户选择; - 页面加载时调用
loadTheme(),从localStorage读取保存的主题并应用,实现 “刷新不丢失”。
- 使用
-
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 重要代码解析
-
核心流程:
- 用户发送消息:前端获取输入内容,调用
sendMessage(),添加用户消息到界面; - 后端处理:验证会话合法性,保存用户消息,构建增强问题(结合文件内容),调用 AI 模型接口;
- 流式响应:后端通过
StreamingResponse实时返回 AI 的 “思考过程” 和 “答案片段”; - 前端渲染:逐段接收并显示思考过程和答案,完成后保存 AI 消息到数据库。
- 用户发送消息:前端获取输入内容,调用
-
关键技术:
- SSE(Server-Sent Events):实现服务器向客户端的单向流式通信,避免频繁轮询;
- 上下文管理:通过
chat_memory_pool维护每个会话的上下文(MultipleConversations实例),支持多轮对话; - Markdown 渲染:使用
marked.js将 AI 返回的 Markdown 内容转换为 HTML,提升可读性。
-
用户体验优化:
- 显示 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 重要代码解析
-
功能触发逻辑:
- 前端通过复选框
id="searchToggle"控制是否开启联网搜索,checked属性表示状态; - 发送消息时,将
enable_search参数(布尔值)传递给后端/api/chat接口; - 后端调用 AI 模型时,通过
webthink=enable_search参数控制是否启用联网功能(extra_body={"enable_search": webthink})。
- 前端通过复选框
-
AI 交互流程:
- 当
enable_search=True时,AI 模型会触发联网搜索,获取外部数据(如实时新闻、天气、知识库更新等); - 搜索结果会被 AI 整合到回答中,提升内容的时效性和准确性;
- 思考过程中会包含 “搜索到 xxx 信息” 等描述,明确告知用户哪些内容基于搜索。
- 当
-
适用场景:
- 需要实时数据的问题(如 “今天的天气如何?”“某股票最新价格是多少?”);
- 需要最新信息的问题(如 “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 重要代码解析
-
文件上传流程:
- 前端:用户选择文件后,验证大小(≤10MB)和类型(支持 txt、pdf 等),通过
FormData构造请求,调用/api/upload接口; - 后端:接收文件,生成唯一文件名(避免冲突),保存到
uploads目录,调用extract_file_content提取内容; - 存储:文件记录(路径、原始名、内容等)存入
FileRecord表,前端保存文件 ID 供后续选择。
- 前端:用户选择文件后,验证大小(≤10MB)和类型(支持 txt、pdf 等),通过
-
文件内容提取:
- 针对不同类型文件使用专用库:
- txt/md/json/csv:直接读取文本;
- pdf:使用
PyPDF2提取文字; - doc/docx:使用
python-docx读取段落; - xlsx:使用
pandas读取表格内容;
- 提取失败时返回错误信息(如 “PDF 解析失败”)。
- 针对不同类型文件使用专用库:
-
与 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);
}
③重要代码解析
-
创建流程:
- 前端调用
generateId()生成唯一chat_id(避免重复); - 发送 POST 请求至
/api/chat-sessions,携带chat_id和默认标题 “新对话”; - 后端验证会话 ID 唯一性,创建
ChatSession记录,并添加初始 AI 消息(message_order=1); - 前端更新会话列表并切换到新会话。
- 前端调用
-
初始消息:
- 新会话创建时自动添加 AI 欢迎消息,确保聊天区有初始内容;
- 消息顺序
message_order从 1 开始,后续消息依次递增(确保排序正确)。
-
用户体验:
- 新会话默认标题为 “新对话”,发送第一条用户消息后自动更新为消息前 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(); // 刷新侧边栏列表
}
}
③重要代码解析
-
重命名流程:
- 前端通过
prompt获取用户输入的新标题(简单直观); - 调用
PUT /api/chat-sessions/{chat_id}接口,传递新标题; - 后端验证会话所有权(仅创建者可修改),更新
title和last_activity; - 前端同步更新本地会话数据和 UI(侧边栏列表、聊天区标题)。
- 前端通过
-
权限控制:
- 后端通过
current_user.id与ChatSession.user_id比对,确保用户只能修改自己的会话; - 若会话不存在或无权限,返回 404 错误。
- 后端通过
-
自动命名机制:
- 新会话默认标题为 “新对话”;
- 发送第一条用户消息后,系统自动将标题更新为消息前 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);
③重要代码解析
-
搜索逻辑:
- 监听搜索框
input事件,实时获取输入的关键词(转为小写,支持不区分大小写搜索); - 遍历所有会话项,提取标题(
chat-title)和最后一条消息预览(chat-preview); - 若标题或预览包含关键词,显示会话项(
display: flex);否则隐藏(display: none)。
- 监听搜索框
-
用户体验:
- 实时过滤(输入即生效),无需点击搜索按钮;
- 支持模糊匹配(如搜索 “python” 可匹配 “Python 编程”“学习 python”);
- 搜索范围覆盖标题和消息内容,提升查找准确性。
-
性能优化:
- 仅操作 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(); // 刷新侧边栏
}
}
③重要代码解析
-
删除机制:
- 采用软删除(而非物理删除):后端将
ChatSession.is_deleted设为 1,查询时过滤已删除会话(is_deleted=0); - 优势:支持数据恢复(若需恢复,只需将
is_deleted改回 0),避免误删后无法恢复。
- 采用软删除(而非物理删除):后端将
-
删除流程:
- 前端显示确认框,防止误操作;
- 验证会话数量(至少保留 1 个),避免无会话状态;
- 调用
DELETE /api/chat-sessions/{chat_id}接口,后端标记会话为删除; - 前端从本地列表移除会话,若删除的是当前会话,自动切换到其他会话。
-
数据清理:
- 前端删除
chat_memory_pool中该会话的上下文数据,释放内存; - 后端不删除关联的
ChatMessage和FileRecord(便于后续可能的恢复)。
- 前端删除
④数据库表设计
依赖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})
③重要代码解析
-
角色与历史记录绑定逻辑:
- 角色区分:
ChatMessage.sender字段标记消息发送者(user/ai),前端据此显示不同头像(用户 =“用户”,AI=“🐋”); - 会话绑定:所有消息通过
chat_id关联到具体会话,查询时仅返回该会话的消息; - 顺序保证:
message_order字段记录消息顺序,确保加载时按发送顺序显示。
- 角色区分:
-
上下文理解:
- 后端通过
MultipleConversations类的messages列表维护每个会话的历史消息; - 每次调用
chat()方法时,将完整messages(系统提示 + 用户消息 + AI 回复)传递给 AI 模型,使其理解上下文; - 新消息发送后,通过
add_assistant_response将 AI 回复添加到messages,确保多轮对话连贯。
- 后端通过
-
数据加载:
- 切换会话时,前端调用
loadChat(chatId),获取该会话的所有消息; - 清空当前聊天区并重新渲染新会话的消息,实现 “会话隔离”(不同会话消息不混合)。
- 切换会话时,前端调用
④数据库表设计
- ChatMessage 表(核心,绑定角色和会话):同 3.3 节,通过
chat_id关联会话,sender区分角色。 - 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 重要代码解析
-
退出流程:
- 点击 “退出登录” 按钮,显示确认框防止误操作;
- 确认后调用
localStorage.removeItem('token')清除 JWT 令牌; - 跳转至登录页面(
login.html),确保退出后无法访问受保护资源。
-
登录状态验证:
- 前端:页面加载时检查
localStorage中是否有token,无则提示并跳转登录页; - 后端:所有需要登录的接口(如
/api/chat、/api/chat-sessions)都依赖get_current_user,该依赖通过verify_token验证Authorization头中的 token; - 拦截器:前端请求拦截器自动为请求添加 token,响应拦截器处理 401 错误(token 无效 / 过期),清除 token 并跳转登录。
- 前端:页面加载时检查
-
安全性:
- 退出后彻底清除本地 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 智能助手项目通过扎实的技术实现与用户体验设计,构建了一个功能完备的智能交互系统。未来,通过持续优化核心功能、拓展多模态交互与商业化生态,有望从工具型产品升级为覆盖个人与企业场景的智能服务平台,为用户创造更高效、更个性化的智能体验。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)