在 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智能问答系统,主要特点包括:

  1. 完整的用户系统:实现安全的注册登录流程,密码加盐哈希存储 
  2. 智能对话管理:支持多轮对话历史保存和检索 
  3. 增强AI能力:深度思考模式 、联网搜索 等高级功能
  4. 多样化交互:语音输入 、风格切换 等提升用户体验
  5. 前后端分离架构:清晰的接口设计和模块化开发

扩展方向:

  • 添加文件上传解析功能
  • 实现多模态交互(图片理解)
  • 增加用户自定义提示模板
  • 部署更强大的AI模型

通过本系统,开发者可以快速搭建一个功能丰富、安全可靠的AI对话平台,适用于客服、教育、娱乐等多种场景。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐