Day 93:【99天精通Python】终极项目 - AI 聊天机器人 (下) - 前端界面与部署

前言

欢迎来到第93天!

在过去的两个章节中,我们已经搭建了一个功能强大的后端:

  • 支持流式对话和多轮记忆。
  • 支持上传 PDF 作为知识库进行 RAG 问答。

今天,我们要为这个强大的"大脑"配上一副漂亮的"面孔"。我们将使用原生 HTML, CSS, JavaScript 编写一个聊天界面,并学习如何将整个项目部署上线。

本节内容:

  • 聊天界面 HTML 结构
  • CSS 美化
  • JavaScript 核心逻辑 (EventSource)
  • 文件上传交互
  • 项目最终部署

一、HTML 骨架 (templates/chat.html)

我们需要一个聊天记录框、一个输入框、一个发送按钮和一个文件上传按钮。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>AI 聊天机器人</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <div class="chat-container">
        <div class="chat-header">
            <h2>Python AI Bot</h2>
        </div>
        <div class="chat-messages" id="chat-messages">
            <!-- 聊天记录放这里 -->
        </div>
        <div class="chat-input-form">
            <input type="file" id="file-input" style="display: none;">
            <button id="upload-btn">📎</button>
            <input type="text" id="user-input" placeholder="输入消息...">
            <button id="send-btn">发送</button>
        </div>
    </div>
    <script src="{{ url_for('static', filename='app.js') }}"></script>
</body>
</html>

二、CSS 美化 (static/style.css)

让界面看起来像一个真正的聊天软件。

/* 省略基础样式 */
.chat-container { display: flex; flex-direction: column; height: 90vh; max-width: 800px; ... }
.chat-messages { flex-grow: 1; overflow-y: auto; padding: 20px; border-bottom: 1px solid #ddd; }
.message { margin-bottom: 15px; display: flex; flex-direction: column; }
.message.user { align-items: flex-end; }
.message.bot { align-items: flex-start; }
.message .bubble { max-width: 70%; padding: 10px 15px; border-radius: 18px; }
.message.user .bubble { background-color: #007bff; color: white; }
.message.bot .bubble { background-color: #f1f0f0; }
/* ... 更多样式 */

三、JS 核心逻辑 (static/app.js)

这是前端的灵魂,负责与后端 API 交互。

3.1 监听事件

const chatMessages = document.getElementById('chat-messages');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const uploadBtn = document.getElementById('upload-btn');
const fileInput = document.getElementById('file-input');

// 随机生成一个 Session ID,或者从 localStorage 读取
let sessionId = localStorage.getItem('sessionId') || 'session_' + Date.now();
localStorage.setItem('sessionId', sessionId);

sendBtn.addEventListener('click', sendMessage);
userInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter') sendMessage();
});

uploadBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', uploadFile);

3.2 发送消息与接收流式响应 (SSE)

我们使用 EventSource 来接收后端的流式数据。

function sendMessage() {
    const message = userInput.value.trim();
    if (!message) return;

    appendMessage('user', message);
    userInput.value = '';

    // 创建 AI 的消息容器,准备接收流式数据
    const botMessageContainer = appendMessage('bot', '');
    const bubble = botMessageContainer.querySelector('.bubble');
    
    // 使用 EventSource 连接流式 API
    const eventSource = new EventSource(`/api/chat?session_id=${sessionId}&input=${encodeURIComponent(message)}`); // 注意:GET 请求示例,POST 更佳
    
    eventSource.onmessage = function(event) {
        const data = JSON.parse(event.data);
        // 逐字追加到 bubble 中
        bubble.textContent += data.token;
        chatMessages.scrollTop = chatMessages.scrollHeight; // 自动滚动
    };

    eventSource.onerror = function() {
        eventSource.close();
    };
}

(注:为简化演示,这里将 POST 改为 GET 传参,生产环境中应保持 POST)

3.3 文件上传

function uploadFile() {
    const file = fileInput.files[0];
    if (!file) return;

    const formData = new FormData();
    formData.append('file', file);
    formData.append('session_id', sessionId);
    
    appendMessage('system', `正在上传并学习文件: ${file.name}...`);

    fetch('/api/upload', {
        method: 'POST',
        body: formData
    })
    .then(response => response.json())
    .then(data => {
        appendMessage('system', data.message || '处理完成!');
    })
    .catch(error => {
        appendMessage('system', '上传失败: ' + error);
    });
}

四、部署到生产环境

我们使用 Gunicorn + Nginx 的经典组合。

4.1 Gunicorn 启动

gunicorn -w 4 -k gevent -b 127.0.0.1:5001 "app:app"
  • -k gevent: 使用 gevent 作为 worker 类型,非常适合流式 IO 场景。
    (需 pip install gevent)

4.2 Nginx 配置

流式响应对 Nginx 配置有特殊要求,需要禁用代理缓冲。

server {
    # ...
    location /api/chat {
        proxy_pass http://127.0.0.1:5001;
        proxy_buffering off; # 关闭缓冲!
        proxy_cache off;
        proxy_set_header Connection '';
        proxy_http_version 1.1;
        chunked_transfer_encoding off;
    }

    location / {
        proxy_pass http://127.0.0.1:5001;
        # ...
    }
}

五、项目总结与展望

至此,我们的全栈 AI 聊天机器人就完成了!

我们实现了

  • 一个能理解上下文的 AI 大脑 (LangChain Memory)。
  • 一个能读取外部知识的 RAG 系统 (VectorDB)。
  • 一个支持流式打字效果的 Web 界面 (Flask SSE + JS EventSource)。
  • 一套可部署到生产环境的架构。

未来可扩展的方向

  • 用户系统:集成 Day 61 的用户认证,实现多用户隔离。
  • 工具调用 (Agent):让 AI 能调用外部 API(如查询天气、计算器)。
  • 模型切换:增加一个下拉框,允许用户在 GPT-4, 文心一言, 通义千问之间切换。

六、小结

这个项目是对我们过去 92 天学习成果的一次大阅兵。它不仅是一个酷炫的玩具,更是一个可以不断迭代、具备商业潜力的产品原型。

希望通过这个项目,你不仅学会了如何"用" Python,更学会了如何组合运用各种技术,去创造一个完整的产品。


下节预告

Day 94:Python 开发最佳实践 - 项目做完了,但代码写得好不好是另一回事。明天我们总结一些 Python 开发中的最佳实践,如代码风格 (PEP8)、文档编写、版本控制 (Git) 等。


系列导航

  • 上一篇:Day 92 - AI聊天机器人(中)
  • 下一篇:Day 94 - Python开发最佳实践(待更新)
Logo

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

更多推荐