从零搭建 AI 问答网页:用 Python、JS 和 HTML 打造你的智能交互工具
完整的用户系统:实现安全的注册登录流程,密码加盐哈希存储智能对话管理:支持多轮对话历史保存和检索增强AI能力:深度思考模式 、联网搜索 等高级功能多样化交互:语音输入 、风格切换 等提升用户体验前后端分离架构:清晰的接口设计和模块化开发添加文件上传解析功能实现多模态交互(图片理解)增加用户自定义提示模板部署更强大的AI模型通过本系统,开发者可以快速搭建一个功能丰富、安全可靠的AI对话平台,适用于客
在 AI 技术飞速发展的今天,拥有一个属于自己的 AI 问答工具不再是难事。本文将手把手教你用 Python 搭建后端接口、HTML 构建前端界面、JavaScript 实现交互逻辑,从零到一打造一个简单却实用的 AI 问答网页。无论你是前端开发者想拓展后端技能,还是 Python 爱好者想尝试全栈开发,这篇教程都能为你提供清晰的思路。
一、项目背景与核心技术栈
1.为什么要做 AI 问答网页?
在日常工作中,我们经常需要快速获取信息、解决技术问题或进行创意 brainstorm—— 一个轻量化的 AI 问答工具能极大提升效率。相比调用第三方 APP,自建工具更灵活(可自定义 prompt、限制使用场景)、更安全(数据不经过第三方服务器)。
二、系统架构设计
1. 技术栈选型
本系统采用分层架构设计,各层技术选型如下:
- 前端层:基于原生HTML/CSS/JavaScript实现响应式界面,不依赖React/Vue等框架,确保轻量级和快速加载
- 后端层:Python Flask框架提供RESTful API服务,处理业务逻辑和AI接口调用
- 数据库层:SQLite轻量级数据库存储用户信息和对话历史
- AI服务:集成阿里云Qwen3或DeepSeek-R1等大模型API,支持深度思考模式
- 辅助服务:SerpAPI实现联网搜索功能 ,Web Speech API实现语音输入
2. 系统功能模块
图1:系统架构示意图
主要功能模块包括:
- 用户认证模块(登录/注册)
- 对话会话管理模块
- AI交互核心模块
- 功能增强模块(语音/搜索/风格)
- 数据持久化模块
三、核心代码
1.数据库设计
1.1用户数据库 (users.db):
-
包含一个 users 表,用于存储用户信息
-
表结构包括:id(主键)、username(唯一用户名)、password(密码)、email(邮箱)、created_at(创建时间)
def init_user_db():
conn = sqlite3.connect("users.db")
c = conn.cursor()
c.execute(
"""CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
email TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)"""
)
conn.commit()
conn.close()
1.2.聊天数据库 (chatbot.db):
包含两个表:
chat_sessions:存储聊天会话信息,包括会话ID、标题和创建时间
-
chat_sessions:存储聊天会话信息,包括会话ID、标题和创建时间
-
chat_messages:存储具体的聊天消息,包括会话ID、角色(用户或AI)、消息内容和时间戳
-
还为 chat_messages 表的 session_id 字段创建了索引以提高查询性能
def get_db():
conn = sqlite3.connect("chatbot.db")
return conn
def init_chat_db():
with get_db() as conn:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS chat_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS chat_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)"""
)
conn.execute(
"""
CREATE INDEX IF NOT EXISTS idx_chat_messages_session_id
ON chat_messages (session_id)
"""
)
conn.commit()
2.登录/注册
2.1登录界面
2.1.1.前端登录页面核心代码:
<form id="loginForm">
<h2>用户登录</h2>
<input type="text" name="username" placeholder="请输入用户名" required/>
<input type="password" name="password" placeholder="请输入密码" required/>
<input type="submit" value="登录"/>
<p>没有账户? <a href="register.html">立即注册</a></p>
<div id="errorMessage" class="error"></div>
<div id="successMessage" class="success"></div>
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
document.getElementById('loginForm').addEventListener('submit', function (e) {
e.preventDefault();
const errorMessage = document.getElementById('errorMessage');
const successMessage = document.getElementById('successMessage');
errorMessage.textContent = '';
successMessage.textContent = '';
const formData = new FormData(this);
const data = {};
formData.forEach((value, key) => data[key] = value);
axios.post('http://localhost:8000/api/login', data)
.then(response => {
if (response.data.success) {
successMessage.textContent = '登录成功!';
setTimeout(() => {
window.location.href = 'al_show.html';
}, 1000);
} else {
errorMessage.textContent = response.data.message;
}
})
.catch(error => {
const msg = error.response?.data?.detail || '网络错误,请重试';
errorMessage.textContent = msg;
});
});
</script>
2.1.2后端登录API核心代码:
class LoginRequest(BaseModel):
username: str
password: str
@app.post("/api/login")
async def login_user(login_data: LoginRequest):
username = login_data.username
password = login_data.password
try:
conn = sqlite3.connect("users.db")
cursor = conn.cursor()
cursor.execute(
"SELECT username, password FROM users WHERE username=?", (username,)
)
user = cursor.fetchone()
conn.close()
if not user:
raise HTTPException(status_code=400, detail="用户不存在")
stored_hash = user[1].encode("utf-8")
if not bcrypt.checkpw(password.encode("utf-8"), stored_hash):
raise HTTPException(status_code=400, detail="密码错误")
return {"success": True, "message": "登录成功", "data": {"username": username}}
except Exception as e:
raise HTTPException(status_code=500, detail=f"登录失败:{str(e)}")
2.1.3登录功能的核心流程如下:
①前端页面提供用户名和密码输入表单
②用户提交表单时,使用JavaScript拦截默认提交行为
③通过Axios将用户输入的凭据发送到后端API (/api/login)
④后端接收到请求后,查询数据库验证用户是否存在
⑤使用bcrypt库验证密码是否正确
⑥如果验证成功,返回成功响应,前端跳转到主页面 (al_show.html)
⑦如果验证失败,返回相应的错误信息
2.2注册界面
2.2.1前端注册页面核心代码:
<form id="registerForm">
<h2>注册新账户</h2>
<input type="text" name="username" placeholder="请输入用户名" required/>
<input type="password" name="password" placeholder="请输入密码" required/>
<input type="password" name="repassword" placeholder="请再次输入密码" required/>
<input type="email" name="email" placeholder="请输入QQ邮箱" required/>
<input type="submit" value="注册">
<p>已有账户? <a href="login.html">立即登录</a></p>
<div id="errorMessage" class="error"></div>
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
document.getElementById('registerForm').addEventListener('submit', function (e) {
e.preventDefault();
const errorMessage = document.getElementById('errorMessage');
errorMessage.textContent = '';
const formData = new FormData(this);
const data = {};
formData.forEach((value, key) => data[key] = value);
if (!data.username || data.username.length < 3) {
errorMessage.textContent = '用户名至少需要3个字符';
return;
}
if (!data.password || data.password.length < 6) {
errorMessage.textContent = '密码长度不能少于6位';
return;
}
if (data.password !== data.repassword) {
errorMessage.textContent = '两次输入的密码不一致';
return;
}
axios.post('http://localhost:8000/api/register', data)
.then(response => {
if (response.data.success) {
alert('注册成功!');
window.location.href = 'login.html';
} else {
errorMessage.textContent = '注册失败:' + response.data.message;
}
})
.catch(error => {
errorMessage.textContent = '网络错误:' + error;
});
});
</script>
2.2.2后端注册API核心代码:
class RegisterRequest(BaseModel):
username: str
password: str
repassword: str
email: str
@app.post("/api/register")
async def register_user(data: RegisterRequest):
try:
hashed_password = bcrypt.hashpw(data.password.encode("utf-8"), bcrypt.gensalt())
logging.debug(f"Connecting to DB...")
conn = sqlite3.connect("users.db")
cursor = conn.cursor()
logging.debug(f"Inserting user: {data.username}")
cursor.execute(
"INSERT INTO users (username, password, email) VALUES (?, ?, ?)",
(data.username, hashed_password.decode("utf-8"), data.email),
)
conn.commit()
conn.close()
return {"success": True, "message": "注册成功"}
except Exception as e:
traceback.print_exc()
raise HTTPException(status_code=500, detail=f"注册失败:{str(e)}")
2.2.3 注册功能的核心流程如下:
①前端页面提供用户名、密码、确认密码和邮箱输入表单
②用户提交表单时,使用JavaScript进行前端验证:
③用户名至少需要3个字符
④密码长度不能少于6位
⑤两次输入的密码必须一致
⑥验证通过后,通过Axios将用户数据发送到后端API (/api/register)
⑦后端接收到请求后,使用bcrypt对密码进行哈希处理
⑧将用户信息(用户名、哈希后的密码、邮箱)存储到数据库
⑨如果注册成功,返回成功响应,前端跳转到登录页面
⑩如果注册失败(如用户名已存在),返回相应的错误信息
3.ai问答界面
3.1前端AI问答主页面核心代码:
<div class="main-layout">
<!-- 主布局容器,定义页面整体结构 -->
<div id="historyList" class="bg-gray-100 rounded-lg p-4 h-60 overflow-y-auto"></div>
<!-- 历史记录区域,用于展示用户与AI的交互历史 -->
<div class="content-area flex-1 flex flex-col">
<!-- 内容区域容器,包含聊天窗口和输入区域 -->
<div id="chatBox" class="flex-1 bg-white rounded-lg p-5 shadow-sm mb-5 min-h-[400px] overflow-y-auto"></div>
<!-- 聊天窗口区域,展示用户与AI的对话内容 -->
<div class="input-area bg-white rounded-lg p-4 shadow-sm">
<div class="flex gap-2 items-center">
<input type="text"
id="userInput"
placeholder="您可以在这开始提问"
autofocus
onkeypress="if(event.key === 'Enter') sendMessage()"
class="!flex-1 p-3 border border-gray-300 rounded-md focus:border-blue-500 outline-none transition duration-300">
<button id="sendBtn"
onclick="sendMessage()"
class="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition duration-300 whitespace-nowrap">
发送
</button>
</div>
</div>
</div>
</div>
<div class="control-panel bg-white p-4 rounded-lg shadow-md">
<div class="control-group flex items-center gap-4 mb-2">
<label class="flex items-center gap-2">
<input type="checkbox" id="enableReasoning" class="form-checkbox text-blue-500">
启用深度思考
<span class="tooltip" title="启用后AI将提供更详细的推理过程">ℹ️</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" id="enableSearch" class="form-checkbox text-blue-500">
启用联网搜索
<span class="tooltip" title="启用后AI将联网获取最新信息">🌐</span>
</label>
</div>
<div class="control-buttons flex gap-2 mt-4">
<button onclick="startNewChat()"
class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition duration-300">
新对话
</button>
<button id="voiceBtn" onclick="toggleVoiceInput()"
class="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 transition duration-300">
🎤
</button>
<button id="themeBtn" onclick="toggleTheme()"
class="px-4 py-2 bg-purple-500 text-white rounded-md hover:bg-purple-600 transition duration-300">
🌙
</button>
<button id="logoutBtn1" onclick="logout()"
class="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 transition duration-300 ml-auto">
退出登录
</button>
<button onclick="loadHistory()"
class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition duration-300">
刷新历史
</button>
</div>
</div>
function generateSessionId() {
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
let currentSessionId = generateSessionId();
let firstQuestionSet = false;
let darkMode = false;
// 页面加载时初始化欢迎消息
document.addEventListener('DOMContentLoaded', function () {
const chatBox = document.getElementById('chatBox');
if (chatBox) {
const welcomeMsg = document.createElement('div');
welcomeMsg.className = 'msg bot';
welcomeMsg.textContent = '您好!我是惜月AI,一个通用AI助手。我可以帮助您回答问题、提供信息和进行各种讨论。请随时告诉我您需要什么帮助!';
chatBox.appendChild(welcomeMsg);
chatBox.scrollTop = chatBox.scrollHeight;
}
// 加载历史记录
loadHistory();
});
// 发送消息函数
window.sendMessage = function () {
const input = document.getElementById('userInput');
const sendBtn = document.getElementById('sendBtn');
const userText = input.value.trim();
if (!userText) return;
sendBtn.disabled = true;
sendBtn.textContent = "发送中...";
appendMessage(userText, 'user');
input.value = '';
if (!firstQuestionSet) {
const shortTitle = userText.substring(0, 30);
document.getElementById('pageTitle').textContent = '惜月AI - ' + shortTitle + (userText.length > 30 ? '...' : '');
updateTitle(shortTitle);
firstQuestionSet = true;
}
let enableReasoning = document.getElementById('enableReasoning').checked;
let enableSearch = false;
if (document.getElementById('enableSearch').checked) {
enableSearch = confirm("您已选择启用联网搜索。\n此功能将帮助您从互联网获取最新信息。\n是否确认启用?");
}
const statusDiv = document.createElement('div');
statusDiv.style.fontSize = '0.9em';
statusDiv.style.color = '#888';
statusDiv.style.marginBottom = '5px';
statusDiv.style.textAlign = 'right';
let statusText = '';
if (enableReasoning) statusText += '[🧠 深度思考已开启] ';
if (enableSearch) statusText += '[🌐 联网搜索已开启] ';
statusDiv.textContent = statusText || '[💡 当前为普通模式]';
chatBox.appendChild(statusDiv);
// 创建机器人消息容器
const botMsgDiv = document.createElement('div');
botMsgDiv.className = 'msg bot';
// 替换为直接添加空消息
chatBox.appendChild(botMsgDiv);
botMsgDiv.textContent = 'AI回答正在加载中,请稍等一下哦...';
const data = {
session_id: currentSessionId,
user_input: userText,
reasoning: enableReasoning,
search: enableSearch
};
console.log('发送消息,使用会话ID:', currentSessionId);
fetch('http://localhost:8000/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(async (response) => {
if (!response) {
throw new Error("网络响应为空,请检查服务器是否运行");
}
if (response.headers.get("content-type")?.includes("application/json")) {
return await response.json();
} else {
const text = await response.text();
return {content: text};
}
})
.then(data => {
if (data.error) {
throw new Error(data.error || "未知错误");
}
const botMsg = data.content || "无返回内容";
botMsgDiv.innerHTML = '';
botMsgDiv.textContent = '加载完成啦';
setTimeout(() => {
appendMessage(botMsg, 'bot');
}, 1000);
sendBtn.disabled = false;
sendBtn.textContent = "发送";
// 保存聊天记录到数据库
// setTimeout(() => {
// saveChatHistory();
// }, 1500);
})
.catch(error => {
console.error('发送消息失败:', error);
alert(`发送消息失败: ${error.message}`);
botMsgDiv.innerHTML = '';
appendMessage('发送消息失败,请稍后重试', 'bot');
sendBtn.disabled = false;
sendBtn.textContent = "发送";
});
};
function appendMessage(content, role) {
const chatBox = document.getElementById('chatBox');
if (!chatBox) {
console.error('找不到聊天框元素');
return;
}
const msgDiv = document.createElement('div');
msgDiv.className = 'msg ' + role;
msgDiv.textContent = content;
chatBox.appendChild(msgDiv);
chatBox.scrollTop = chatBox.scrollHeight;
}
function startNewChat() {
const chatBox = document.getElementById('chatBox');
if (!chatBox) {
console.error('找不到聊天框元素');
return;
}
chatBox.innerHTML = '';
document.getElementById('pageTitle').textContent = '惜月AI';
firstQuestionSet = false;
// 生成新的会话ID
currentSessionId = generateSessionId();
// 添加欢迎消息
const welcomeMsg = document.createElement('div');
welcomeMsg.className = 'msg bot';
welcomeMsg.textContent = '您好!我是惜月AI,一个通用AI助手。我可以帮助您回答问题、提供信息和进行各种讨论。请随时告诉我您需要什么帮助!';
chatBox.appendChild(welcomeMsg);
chatBox.scrollTop = chatBox.scrollHeight;
console.log('新建会话,ID为:', currentSessionId);
}
function logout() {
fetch('http://localhost:8000/logout', {
method: 'POST',
})
.then(response => {
if (response.ok) {
window.location.href = 'login.html';
} else {
throw new Error('登出失败');
}
})
.catch(error => {
console.error('登出失败:', error);
alert('无法登出,请检查服务器是否运行');
window.location.href = 'login.html';
});
}
// 加载历史记录
async function loadHistory() {
const historyList = document.getElementById('historyList');
if (!historyList) {
console.error('找不到 id 为 historyList 的元素');
alert('页面结构异常,请刷新重试');
return;
}
try {
const res = await fetch('http://localhost:8000/history');
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
const list = await res.json();
historyList.innerHTML = '';
// 按时间排序
list.sort((a, b) => {
// 处理不同格式的时间戳
const timeA = new Date(a.created_at).getTime() || 0;
const timeB = new Date(b.created_at).getTime() || 0;
return timeB - timeA;
});
list.forEach(sess => {
const div = document.createElement('div');
div.textContent = sess.title;
div.style.cursor = 'pointer';
div.onclick = () => showDetail(sess.session_id);
const delBtn = document.createElement('span');
delBtn.textContent = '🗑️';
delBtn.className = 'delete-btn';
delBtn.onclick = (e) => {
e.stopPropagation();
if (confirm(`确认删除对话:\"${sess.title}\"?`) !== false) {
deleteHistory(sess.session_id);
}
};
div.appendChild(delBtn);
historyList.prepend(div);
});
if (list.length > 0) {
showDetail(list[0].session_id);
} else {
// 当没有历史记录时,显示默认会话
showDetail("default");
const div = document.createElement('div');
div.textContent = "新对话";
div.style.cursor = 'pointer';
div.onclick = () => showDetail("default");
historyList.prepend(div);
}
} catch (error) {
console.error('加载历史记录失败:', error);
alert('无法加载历史记录,请检查服务器是否运行: ' + error.message);
}
}
// 显示对话详情
async function showDetail(session_id) {
currentSessionId = session_id;
// 如果是默认会话,显示提示信息
if (session_id === "default") {
const chatBox = document.getElementById('chatBox');
if (chatBox) {
chatBox.innerHTML = '';
const infoDiv = document.createElement('div');
infoDiv.className = 'msg bot';
infoDiv.textContent = '这是新对话,尚未保存历史记录。开始聊天后会自动保存。';
chatBox.appendChild(infoDiv);
}
document.getElementById('pageTitle').textContent = '惜月AI - 新对话';
return;
}
try {
const res = await fetch(`http://localhost:8000/history/${session_id}`);
if (!res.ok) {
throw new Error(`加载历史记录失败: HTTP ${res.status}`);
}
const history = await res.json();
const chatBox = document.getElementById('chatBox');
if (!chatBox) throw new Error('找不到聊天框元素');
chatBox.innerHTML = '';
console.log('获取到的对话详情:', history);
if (!history || !Array.isArray(history) || history.length === 0) {
const infoDiv = document.createElement('div');
infoDiv.className = 'msg bot';
infoDiv.textContent = '该对话暂无内容,请开始新对话';
chatBox.appendChild(infoDiv);
} else {
const validMessages = history.filter(msg =>
msg &&
typeof msg.content === 'string' &&
msg.content.trim() !== '' &&
msg.role
);
console.log('有效消息:', validMessages);
if (validMessages.length > 0) {
validMessages.forEach(msg => {
appendMessage(msg.content, msg.role === 'user' ? 'user' : 'bot');
});
} else {
const infoDiv = document.createElement('div');
infoDiv.className = 'msg bot';
infoDiv.textContent = '该对话暂无有效内容,请开始新对话';
chatBox.appendChild(infoDiv);
}
}
const pageTitle = document.getElementById('pageTitle');
if (pageTitle) {
if (history && history.length > 0) {
const firstUserMessage = history.find(msg => msg.role === 'user');
if (firstUserMessage) {
pageTitle.textContent = `惜月AI - ${firstUserMessage.content.substring(0, 30)}...`;
} else {
pageTitle.textContent = '惜月AI - 空对话';
}
} else {
pageTitle.textContent = '惜月AI - 新对话';
}
}
} catch (error) {
console.error('加载对话详情失败:', error);
alert(`无法加载对话详情: ${error.message}`);
const chatBox = document.getElementById('chatBox');
if (chatBox) {
chatBox.innerHTML = '';
appendMessage('无法加载历史记录,请稍后重试', 'bot');
}
}
}
// 删除历史记录
function deleteHistory(session_id) {
fetch(`http://localhost:8000/history/${session_id}`, {
method: 'DELETE',
})
.then(response => {
if (response.ok) {
loadHistory();
} else {
throw new Error('删除历史记录失败');
}
})
.catch(error => {
console.error('删除历史记录失败:', error);
alert('无法删除历史记录,请检查服务器是否运行');
});
}
3.2后端AI问答API核心代码:
class ChatRequest(BaseModel):
session_id: str
user_input: str
reasoning: bool = False
search: bool = False
class SaveHistoryRequest(BaseModel):
session_id: str
title: str
# 模拟短期会话缓存
sessions: dict[str, list[dict]] = {}
SYSTEM_PROMPT = """你是一个通用AI助手,请根据用户问题进行温柔、友好、准确的回复。"""
def init_session(session_id: str):
sessions[session_id] = [
# {
# "role": "system",
# "content": "你是一个通用AI助手,请根据用户的问题进行温柔、友好、准确的回复。",
# }
]
def generate_thinking_stream(user_input, session_id, use_reasoning, use_search):
client = OpenAI(
# Api-key
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
messages = sessions.get(session_id, [])
messages.append({"role": "user", "content": user_input})
model_name = "qwen-plus-2025-04-28"
extra_body = {}
if use_reasoning:
extra_body["enable_thinking"] = True
extra_body["thinking_budget"] = 50
extra_body["enable_search"] = use_search
extra_body["search_options"] = {
"forced_search": True,
"enable_source": True,
"enable_citation": True,
"citation_format": "[ref_<number>]",
"search_strategy": "pro",
}
completion = client.chat.completions.create(
model=model_name, messages=messages, stream=True, extra_body=extra_body
)
thinking_content = ""
answer_content = ""
is_answering = False
is_first_chunk = True
for chunk in completion:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
if (
is_first_chunk
and hasattr(chunk, "output")
and hasattr(chunk.output, "search_info")
):
search_results = chunk.output.search_info.get("search_results", [])
if search_results:
yield "[🔍 搜索结果]\n"
for web in search_results:
yield f"[{web['index']}]: [{web['title']}]({web['url']})\n"
yield "\n[🧠 开始思考]\n"
is_first_chunk = False
if hasattr(delta, "reasoning_content") and delta.reasoning_content:
thinking_content += delta.reasoning_content
if use_reasoning:
yield delta.reasoning_content
if hasattr(delta, "content") and delta.content:
if not is_answering:
yield "\n\n"
is_answering = True
answer_content += delta.content
yield delta.content
full_response = ""
if use_reasoning:
full_response += f"[🧠 深度思考]\n{thinking_content}\n\n"
full_response += answer_content + "\n"
messages.append({"role": "assistant", "content": full_response})
sessions[session_id] = messages
# 保存会话到数据库
def save_session_to_db(session_id: str):
"""将当前会话保存到数据库"""
print(f"尝试保存会话 {session_id} 到数据库")
try:
with get_db() as conn:
cur = conn.cursor()
# 获取会话标题(使用第一条用户消息)
title = "新对话"
if session_id in sessions:
for msg in sessions[session_id]:
if msg["role"] == "user":
title = msg["content"][:30] + (
"..." if len(msg["content"]) > 30 else ""
)
break
print(f"会话标题: {title}")
print(f"会话消息数: {len(sessions.get(session_id, []))}")
# 检查会话是否已存在
cur.execute(
"SELECT COUNT(1) FROM chat_sessions WHERE session_id=?", (session_id,)
)
exists = cur.fetchone()[0] > 0
if exists:
# 如果会话已存在,只更新标题(如果有必要)
cur.execute(
"UPDATE chat_sessions SET title=? WHERE session_id=?",
(title, session_id),
)
print(f"已更新会话记录: {session_id}, {title}")
else:
# 如果会话不存在,插入新会话记录
cur.execute(
"INSERT INTO chat_sessions (session_id, title) VALUES (?, ?)",
(session_id, title),
)
print(f"已插入新的会话记录: {session_id}, {title}")
# 只插入新的消息而不是删除所有消息再重新插入
if session_id in sessions:
# 获取数据库中已有的消息数量
cur.execute(
"SELECT COUNT(*) FROM chat_messages WHERE session_id=?",
(session_id,),
)
db_msg_count = cur.fetchone()[0]
# 只插入新增的消息
session_messages = sessions[session_id]
if db_msg_count < len(session_messages):
for i in range(db_msg_count, len(session_messages)):
msg = session_messages[i]
print(
f"插入消息 {i}: role={msg['role']}, content={msg['content'][:50] if msg['content'] else ''}..."
)
cur.execute(
"INSERT INTO chat_messages (session_id, role, content) VALUES (?, ?, ?)",
(
session_id,
msg["role"],
msg["content"] if msg["content"] else "",
),
)
conn.commit()
print(f"会话 {session_id} 成功保存到数据库")
except Exception as e:
print(f"保存会话到数据库时出错: {e}")
import traceback
traceback.print_exc()
@app.post("/chat")
async def chat_endpoint(req: ChatRequest):
session_id = req.session_id
user_input = req.user_input
use_reasoning = req.reasoning
use_search = req.search
if session_id not in sessions:
init_session(session_id)
# 创建一个包装生成器,在结束时保存会话
def generate_and_save():
try:
yield from generate_thinking_stream(
user_input, session_id, use_reasoning, use_search
)
finally:
# 在生成完成后保存会话
save_session_to_db(session_id)
return StreamingResponse(
generate_and_save(),
media_type="text/event-stream",
)
@app.get("/history")
async def get_history():
try:
with get_db() as conn:
cur = conn.cursor()
cur.execute(
"SELECT session_id, title, created_at FROM chat_sessions ORDER BY created_at DESC"
)
rows = cur.fetchall()
# 确保返回的数据格式正确
result = []
for r in rows:
result.append(
{
"session_id": r[0],
"title": r[1],
"created_at": r[2] if r[2] else "",
}
)
print(f"返回历史记录: {result}") # 调试信息
return result
except Exception as e:
print(f"获取历史记录时出错: {e}")
import traceback
traceback.print_exc()
return [] # 返回空数组而不是抛出异常
@app.get("/history/{session_id}")
async def get_history_detail(session_id: str):
# 对于默认会话,直接返回空数组
if session_id == "default":
return []
try:
with get_db() as conn:
cur = conn.cursor()
cur.execute(
"SELECT role, content, timestamp FROM chat_messages WHERE session_id=? ORDER BY timestamp ASC",
(session_id,),
)
rows = cur.fetchall()
# 即使没有找到记录也返回空数组而不是抛出异常
result = []
for r in rows:
result.append(
{
"role": r[0],
"content": r[1] if r[1] else "",
"timestamp": r[2] if r[2] else "",
}
)
print(f"返回会话详情: {result}") # 调试信息
return result
except Exception as e:
print(f"获取会话详情时出错: {e}")
import traceback
traceback.print_exc()
return [] # 返回空数组而不是抛出异常
@app.delete("/history/{session_id}")
async def delete_history(session_id: str):
with get_db() as conn:
cur = conn.cursor()
cur.execute("DELETE FROM chat_messages WHERE session_id= ?", (session_id,))
cur.execute("DELETE FROM chat_sessions WHERE session_id= ?", (session_id,))
conn.commit()
return {"status": "success"}
@app.post("/logout")
async def logout_user():
# 如果你使用的是 session-based 登录,可以在这里清除 session
return {"status": "success", "message": "登出成功"}
3.3 AI问答主页面的核心功能包括:
3.3.1用户界面部分:
①聊天窗口显示用户和AI的对话历史
②输入框供用户输入问题
③控制面板提供深度思考、联网搜索等功能选项
④历史记录面板显示之前的对话
⑤新对话、语音输入、主题切换、退出登录等功能按钮
3.3.2核心交互流程:
①用户输入问题并发送
②前端通过fetch API将问题发送到后端/chat接口
③后端调用通义千问API处理问题
④支持启用深度思考和联网搜索功能
⑤后端以流式响应方式返回AI的回答
⑥对话历史保存到数据库中
⑦用户可以查看、删除历史对话
3.3.3特色功能:
①实时流式显示AI回答
②深度思考模式(显示AI的推理过程)
③联网搜索功能(获取最新信息)
④对话历史管理
⑤深色/浅色主题切换
⑥语音输入支持
四、生产环境建议
4.1前端优化:
- 使用Nginx部署静态资源,开启gzip压缩
- 配置合适的缓存策略减少重复请求
4.2后端优化
- 使用Gunicorn或uWSGI部署Flask应用
- 配置数据库连接池提高性能
4.3安全加固
- 启用HTTPS加密通信
- 实现API速率限制防止滥用
- 对用户输入进行严格验证和过滤
五、总结与扩展
本文详细介绍了如何构建一个功能完善的AI智能问答系统,主要特点包括:
- 完整的用户系统:实现安全的注册登录流程,密码加盐哈希存储
- 智能对话管理:支持多轮对话历史保存和检索
- 增强AI能力:深度思考模式 、联网搜索 等高级功能
- 多样化交互:语音输入 、风格切换 等提升用户体验
- 前后端分离架构:清晰的接口设计和模块化开发
扩展方向:
- 添加文件上传解析功能
- 实现多模态交互(图片理解)
- 增加用户自定义提示模板
- 部署更强大的AI模型
通过本系统,开发者可以快速搭建一个功能丰富、安全可靠的AI对话平台,适用于客服、教育、娱乐等多种场景。
更多推荐
所有评论(0)