搭建 AI 聊天机器人:”我的人生我做主“
本文介绍了一个基于Flask框架和CozeAI平台的轻量级AI聊天机器人开发项目。项目采用前后端一体化架构,包含4个核心文件:后端代码(app.py)、前端页面(index.html)、环境配置(.env)和依赖清单(requirements.txt)。后端实现了CozeAI交互封装、会话状态管理和API接口,前端使用原生HTML/CSS/JavaScript构建了响应式聊天界面。文章详细解析了代
目录
2.2 Coze AI 交互类封装(CozeService)
3.3 JavaScript 交互:前后端通信与 DOM 操作
4.2 requirements.txt:Python 依赖清单
在 AI 应用开发的轻量场景中,无需复杂的架构设计,仅用 Python + 原生前端技术,就能基于 Flask 框架和 Coze AI 开放平台搭建一款支持会话保持、可视化交互、异常处理的完整 AI 聊天机器人。本文将逐行拆解核心代码,从环境配置、后端逻辑封装、前端交互实现到整体运行流程,全方位讲解代码的设计思路和实现细节,让你不仅能跑通项目,更能理解每一行代码的作用。
一、项目整体文件结构
本项目采用前后端一体化部署(Flask 直接托管前端页面),无需额外的前端服务器,文件结构极简,仅需 4 个核心文件即可完成开发,新手也能快速上手:
ai_chatbot/
├── app.py # 后端核心代码:Flask服务、Coze AI交互、会话管理
├── index.html # 前端核心代码:聊天界面、交互逻辑、样式美化
├── .env # 环境配置文件:存储Coze API令牌、机器人ID(敏感信息)
└── requirements.txt# 依赖清单:管理Python第三方库
所有代码各司其职,后端专注于业务逻辑和 AI 交互,前端专注于视觉呈现和用户操作,代码解耦且易于维护。
二、后端代码(app.py)全解析
后端基于 Flask 开发,核心功能包括:加载环境变量、封装 Coze AI 交互逻辑、实现聊天 API 接口、维护用户会话状态、清理过期会话,每一行代码都附带详细注释,以下按代码模块逐段解析。
2.1 模块导入与环境初始化
这是项目的基础准备阶段,导入所需的第三方库,并加载环境变量,避免敏感信息硬编码在代码中。
# 导入必要的库和模块
import os # 用于读取操作系统环境变量(如.env中的配置)
from flask import Flask, request, jsonify, send_file # Flask核心:创建服务、处理请求、返回JSON、托管静态文件
from cozepy import Coze, TokenAuth, ChatStatus, COZE_CN_BASE_URL, Message # Coze AI官方SDK:所有AI交互的核心
from dotenv import load_dotenv # 加载.env文件中的环境变量,隔离敏感信息
import uuid # 生成唯一的用户ID(保证每个用户会话的唯一性)
import time # 处理时间戳:记录会话创建时间、最后活动时间,用于清理过期会话
# 加载.env文件中的环境变量,执行后可通过os.getenv("KEY")获取配置值
load_dotenv()
# 创建Flask应用实例,__name__表示当前模块名,Flask会基于此查找静态文件
app = Flask(__name__)
# 定义全局会话字典:存储所有用户的会话状态
# key:用户唯一标识(本文用客户端IP),value:会话详细信息(字典格式)
# 作用:实现多轮对话的会话保持,让Coze AI能识别上下文
user_sessions = {}
核心要点:
load_dotenv():必须在读取环境变量前执行,否则无法获取.env中的配置;user_sessions:全局内存字典,是实现会话保持的关键,后续所有会话操作都基于此字典;- 导入的
ChatStatus:Coze AI 的聊天状态枚举(IN_PROGRESS 进行中 / COMPLETED 已完成),用于轮询获取 AI 回复。
2.2 Coze AI 交互类封装(CozeService)
这是后端的核心业务类,将与 Coze AI 的所有交互逻辑封装在一个类中,实现代码复用和逻辑解耦。后续若需替换 AI 平台(如 OpenAI、文心一言),仅需修改此类,无需改动其他业务代码。
# 定义Coze服务类,封装与Coze AI的所有交互逻辑,对外提供统一调用接口
class CozeService:
def __init__(self):
"""初始化Coze客户端,从环境变量加载配置"""
# 从.env文件读取Coze API令牌(敏感信息,切勿硬编码)
self.api_token = os.getenv("COZE_API_TOKEN")
# 从.env读取机器人ID,若未配置则使用默认值(可根据自己的机器人ID修改)
self.bot_id = os.getenv("BOT_ID", "7552823978826694671")
# 初始化Coze官方客户端,配置认证方式和服务地址
self.coze = Coze(
auth=TokenAuth(token=self.api_token), # 令牌认证:Coze的核心认证方式
base_url=COZE_CN_BASE_URL # 中国区服务地址,无需修改
)
def get_sdk_response(self, user_message, user_identifier):
"""
获取Coze AI的智能回复,基于用户标识保持会话
:param user_message: 前端传递的用户输入消息
:param user_identifier: 用户唯一标识(本文为客户端IP)
:return: 字典格式的响应结果(status+content)
"""
try:
# 第一步:判断用户是否已有会话(实现会话保持的核心逻辑)
if user_identifier in user_sessions:
# 存在会话:从全局字典中读取已有会话信息
session_data = user_sessions[user_identifier]
conversation_id = session_data["conversation_id"] # Coze的会话ID(关联上下文)
user_id = session_data["user_id"] # 生成的唯一用户ID
# 构建用户消息:符合Coze SDK的Message格式要求
messages = [
Message(
role="user", # 消息角色:user(用户)/assistant(助手)
content=user_message, # 消息内容:用户输入的文本
content_type="text", # 内容类型:text(文本)/image(图片)
type="question" # 消息类型:question(问题)
)
]
# 存在会话:调用Coze SDK继续现有聊天(复用conversation_id和user_id)
chat = self.coze.chat.create(
bot_id=self.bot_id,
user_id=user_id,
conversation_id=conversation_id, # 复用会话ID,关联上下文
additional_messages=messages,
auto_save_history=True # 自动保存聊天历史:Coze平台端可查看
)
else:
# 不存在会话:创建新的用户会话
# 生成唯一的用户ID:使用uuid4,保证全球唯一,避免冲突
user_id = str(uuid.uuid4())
# 构建用户消息:格式与已有会话一致
messages = [
Message(
role="user",
content=user_message,
content_type="text",
type="question"
)
]
# 不存在会话:调用Coze SDK创建新聊天(无conversation_id)
chat = self.coze.chat.create(
bot_id=self.bot_id,
user_id=user_id, # 新生成的用户ID
additional_messages=messages,
auto_save_history=True
)
# 关键操作:将新会话信息存入全局字典,供后续对话复用
user_sessions[user_identifier] = {
"conversation_id": chat.conversation_id, # Coze返回的会话ID(核心,关联上下文)
"user_id": user_id, # 本地生成的用户ID
"chat_id": chat.id, # Coze返回的聊天ID(每次对话的唯一标识)
"created_at": time.time(), # 会话创建时间戳(秒)
"last_activity": time.time() # 会话最后活动时间戳(用于清理过期会话)
}
# 第二步:更新会话的最后活动时间和chat_id(每次对话都要更新)
user_sessions[user_identifier]["last_activity"] = time.time()
user_sessions[user_identifier]["chat_id"] = chat.id # 每次对话的chat_id不同,需更新
# 第三步:轮询等待Coze AI处理完成(核心:Coze AI处理需要时间,非即时响应)
# 当聊天状态为"进行中(IN_PROGRESS)"时,持续查询状态
while chat.status == ChatStatus.IN_PROGRESS:
# 调用Coze SDK查询当前聊天状态和结果
chat = self.coze.chat.retrieve(
conversation_id=chat.conversation_id,
chat_id=chat.id
)
# 第四步:处理Coze AI的响应结果(状态为"已完成(COMPLETED)"时)
if chat.status == ChatStatus.COMPLETED:
# 调用Coze SDK获取当前聊天的所有消息
messages = self.coze.chat.messages.list(
conversation_id=chat.conversation_id,
chat_id=chat.id
)
# 遍历消息列表,找到助手(assistant)的回复(过滤用户消息)
for msg in messages:
if msg.role == "assistant":
# 返回成功结果:status=success + 助手回复内容
return {
"status": "success",
"content": msg.content
}
# 若聊天状态非已完成,返回失败结果
return {"status": "failed", "content": "对话未完成,AI处理超时"}
except Exception as e:
# 异常捕获:捕获所有可能的错误(API令牌错误、网络问题、SDK调用错误等)
# 返回错误结果:status=error + 异常详细信息(便于调试)
return {"status": "error", "content": str(e)}
# 创建CozeService实例:全局唯一,所有请求复用此实例
coze_service = CozeService()
核心要点:
- 会话保持的核心:
conversation_id(Coze 返回的会话 ID),复用此 ID 即可让 Coze AI 识别聊天上下文,实现多轮对话; - Message 格式:必须严格符合 Coze SDK 的要求,
role/content_type等字段不能写错,否则 SDK 调用失败; - 轮询机制:Coze AI 处理消息需要时间(约 1-3 秒),需通过
while循环轮询chat.status,直到状态变为COMPLETED; - 异常捕获:使用
try-except捕获所有异常,保证单个请求错误不会导致整个 Flask 服务崩溃,同时返回错误信息便于调试; - 全局实例:
coze_service = CozeService()放在类外,所有请求复用此实例,避免重复创建客户端。
2.3 Flask 路由与业务接口实现
Flask 的路由是前后端通信的桥梁,本项目实现了 2 个核心路由:首页路由(返回前端页面)、聊天路由(处理用户消息请求),同时实现了过期会话清理函数。
# 路由1:首页路由(GET请求),访问根路径时触发
@app.route('/')
def index():
# 每次访问首页时,清理过期会话(释放内存,避免字典无限膨胀)
cleanup_expired_sessions()
# 返回前端页面index.html:send_file直接托管静态文件,实现前后端一体化
return send_file('index.html')
# 路由2:聊天核心接口(POST请求),前端发送消息时调用
@app.route('/chat', methods=['POST'])
def chat():
"""处理前端的聊天请求,返回JSON格式的AI回复"""
# 第一步:解析前端传递的JSON数据(前端通过Fetch API发送{"message": "用户输入"})
data = request.json
user_message = data.get('message') # 获取用户消息字段
# 第二步:非空校验,避免空消息提交
if not user_message:
# 返回JSON格式响应:Flask的jsonify自动设置Content-Type为application/json
return jsonify({"status": "error", "content": "消息不能为空"})
# 第三步:获取用户唯一标识,本文使用客户端IP(简单易实现)
user_identifier = request.remote_addr
# 第四步:调用CozeService实例的方法,获取AI回复
response = coze_service.get_sdk_response(user_message, user_identifier)
# 第五步:将AI回复转换为JSON格式,返回给前端
return jsonify(response)
# 辅助函数:清理过期会话(核心:释放内存,优化性能)
def cleanup_expired_sessions():
"""
清理过期会话的核心函数
注:基础版本暂未设置时间阈值,后续可扩展为“30分钟无活动则清理”
"""
# 定义空列表,存储需要删除的过期会话标识
expired_sessions = []
# 遍历全局会话字典,收集过期会话(基础版本:收集所有会话,可按需修改)
for user_identifier, session_data in user_sessions.items():
expired_sessions.append(user_identifier)
# 遍历过期会话列表,从全局字典中删除(避免遍历中修改字典导致的错误)
for user_identifier in expired_sessions:
del user_sessions[user_identifier]
核心要点:
- 请求方法:聊天接口必须使用
POST方法,因为需要传递 JSON 数据,且符合 HTTP 规范(GET 方法不适合传递请求体); - request.json:Flask 内置的 JSON 解析方法,自动解析前端发送的
application/json格式请求体; - user_identifier:本文使用
request.remote_addr(客户端 IP)作为用户标识,简单易实现,适合个人测试; - send_file:Flask 的静态文件托管方法,直接返回
index.html,实现前后端一体化部署,无需额外的 Nginx/Apache; - cleanup_expired_sessions:基础版本暂未设置时间阈值,后续可添加
if time.time() - session_data["last_activity"] > 1800(30 分钟),实现真正的过期清理。
2.4 项目启动入口
这是 Python 项目的标准启动方式,判断是否直接运行当前脚本,若是则启动 Flask 服务。
# 项目启动入口:仅当直接运行app.py时,执行以下代码
if __name__ == '__main__':
# 启动Flask服务:debug=True(调试模式,代码修改后自动重启),port=5000(服务端口)
app.run(debug=True, port=5000)
核心要点:
debug=True:仅适合开发环境,生产环境必须关闭(存在安全风险);port=5000:Flask 默认端口,可修改为 8080、80 等(需管理员权限);- 启动后服务地址:
http://127.0.0.1:5000,直接在浏览器访问即可。
三、前端代码(index.html)全解析
前端采用原生 HTML+CSS+JavaScript开发,无任何前端框架依赖,实现了美观的响应式界面和流畅的交互体验,同时兼顾了移动端适配。代码分为HTML 结构、CSS 样式、JavaScript 交互三部分,以下逐部分解析。
3.1 HTML 结构:聊天界面的骨架
HTML 结构遵循语义化原则,分为头部(标题)、聊天容器(消息展示 + 正在输入提示)、输入区域(输入框 + 发送按钮)三部分,结构清晰,便于后续 DOM 操作。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<!-- 视口设置:移动端适配核心,保证页面在手机上正常显示 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的人生我做主</title>
<!-- 内嵌CSS样式:便于一体化部署,无需单独的css文件 -->
<style>/* 样式代码见下文 */</style>
</head>
<body>
<!-- 主容器:卡片式布局,限制最大宽度,居中显示 -->
<div class="container">
<!-- 头部:标题+副标题,渐变背景 -->
<header>
<h1>我的未来我做主</h1>
<p>与AI助手一起规划您的未来</p>
</header>
<!-- 聊天容器:包含消息展示区和正在输入提示 -->
<div class="chat-container">
<!-- 聊天消息展示区:滚动显示所有聊天记录 -->
<div id="chat-messages" class="chat-messages">
<!-- 初始消息:机器人欢迎语,页面加载时显示 -->
<div class="message bot-message">
<p>您好!我是您的未来规划助手。请问您想了解什么关于未来规划的问题?</p>
</div>
</div>
<!-- 正在输入提示:机器人处理消息时显示,默认隐藏 -->
<div class="typing-indicator" id="typing-indicator">
<div class="typing-dots">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
</div>
<!-- 输入区域:输入框+发送按钮,底部固定 -->
<div class="input-area">
<input type="text" id="user-input" placeholder="输入您的问题...">
<button id="send-button">发送</button>
</div>
</div>
<!-- 内嵌JavaScript:交互逻辑,页面加载后执行 -->
<script>/* 交互代码见下文 */</script>
</body>
</html>
核心要点:
- 移动端适配:
<meta name="viewport" ...>是移动端适配的核心,保证页面在手机上不会缩放变形; - ID 命名规范:所有需要 JavaScript 操作的元素都设置了唯一 ID(如
chat-messages、user-input),便于通过getElementById获取; - 默认隐藏元素:
typing-indicator(正在输入提示)默认通过 CSS 隐藏,需要时通过 JavaScript 修改样式显示; - 初始消息:页面加载时显示机器人欢迎语,提升用户体验,避免空白界面。
3.2 CSS 样式:美观的视觉设计与响应式布局
CSS 样式采用原生 CSS3开发,实现了渐变色背景、卡片式布局、消息气泡样式、动画效果和移动端适配,所有样式都添加了详细的注释,便于修改和定制。
* {
margin: 0;
padding: 0;
box-sizing: border-box; /* 盒模型:padding/border计入元素宽度,避免布局错乱 */
}
body {
font-family: 'Arial', sans-serif; /* 全局字体:无衬线字体,适配所有设备 */
/* 页面背景:线性渐变,提升视觉体验 */
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
color: #333;
line-height: 1.6; /* 行高:提升文本可读性 */
min-height: 100vh; /* 最小高度:占满整个视口高度 */
padding: 20px; /* 页面内边距:避免内容贴边 */
}
.container {
max-width: 900px; /* 最大宽度:桌面端限制宽度,居中显示 */
margin: 0 auto; /* 水平居中:核心布局技巧 */
background-color: white;
border-radius: 15px; /* 圆角:卡片式布局,柔和视觉 */
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); /* 阴影:提升层次感 */
overflow: hidden; /* 溢出隐藏:避免子元素超出圆角 */
}
header {
/* 头部渐变背景:蓝黑渐变,符合AI产品的科技感 */
background: linear-gradient(90deg, #3498db, #2c3e50);
color: white;
padding: 30px 20px;
text-align: center; /* 文本居中 */
}
header h1 {
font-size: 2.5rem; /* 大标题字体:rem单位,适配移动端 */
margin-bottom: 10px;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3); /* 文字阴影:提升辨识度 */
}
header p {
font-size: 1.2rem;
opacity: 0.9; /* 透明度:副标题弱化,突出主标题 */
}
.chat-container {
display: flex;
flex-direction: column; /* 弹性布局:垂直方向排列子元素 */
height: 500px; /* 固定高度:聊天区域不会随消息增多无限增高 */
}
.chat-messages {
flex: 1; /* 弹性占比:占满chat-container的剩余高度 */
overflow-y: auto; /* 垂直滚动:消息过多时可滚动查看 */
padding: 20px;
background-color: #f9f9f9; /* 浅灰色背景:区分消息区域和输入区域 */
}
/* 消息气泡通用样式 */
.message {
margin-bottom: 15px; /* 消息之间的间距 */
padding: 12px 18px; /* 消息内边距:文本不贴边 */
border-radius: 18px; /* 大圆角:气泡样式,符合聊天软件习惯 */
max-width: 80%; /* 最大宽度:消息不会过宽,提升可读性 */
animation: fadeIn 0.3s ease; /* 淡入动画:消息展示更流畅 */
}
/* 淡入动画:CSS3关键帧动画,消息从透明到不透明,轻微上移 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 用户消息样式:右侧蓝色背景,白色文字 */
.user-message {
background-color: #3498db;
color: white;
margin-left: auto; /* 右对齐:核心技巧,将元素推到右侧 */
border-bottom-right-radius: 5px; /* 右下角小圆角:符合聊天气泡的视觉习惯 */
}
/* 机器人消息样式:左侧浅灰色背景,黑色文字 */
.bot-message {
background-color: #ecf0f1;
color: #333;
margin-right: auto; /* 左对齐:核心技巧,将元素推到左侧 */
border-bottom-left-radius: 5px; /* 左下角小圆角:与用户消息呼应 */
}
.input-area {
display: flex; /* 弹性布局:输入框和按钮横向排列 */
padding: 15px;
border-top: 1px solid #eee; /* 上边框:区分输入区域和消息区域 */
background-color: white;
}
#user-input {
flex: 1; /* 弹性占比:输入框占满剩余宽度 */
padding: 12px 18px;
border: 1px solid #ddd; /* 浅灰色边框 */
border-radius: 25px; /* 大圆角:输入框样式更柔和 */
outline: none; /* 去除默认焦点边框 */
font-size: 1rem;
transition: border-color 0.3s; /* 过渡效果:焦点时边框颜色平滑变化 */
}
/* 输入框焦点样式:蓝色边框+浅蓝阴影,提升交互反馈 */
#user-input:focus {
border-color: #3498db;
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
}
#send-button {
margin-left: 10px; /* 按钮与输入框的间距 */
padding: 12px 25px;
background-color: #3498db; /* 主色调:与用户消息背景一致 */
color: white;
border: none; /* 去除默认边框 */
border-radius: 25px; /* 大圆角:与输入框呼应 */
cursor: pointer; /* 鼠标悬浮为手型:提示可点击 */
font-size: 1rem;
/* 过渡效果:悬停和点击时样式平滑变化 */
transition: background-color 0.3s, transform 0.2s;
}
/* 按钮悬停样式:加深背景色,轻微放大 */
#send-button:hover {
background-color: #2980b9;
transform: scale(1.05);
}
/* 按钮点击样式:轻微缩小,模拟按压效果 */
#send-button:active {
transform: scale(0.98);
}
/* 正在输入提示:默认隐藏 */
.typing-indicator {
display: none;
padding: 10px 15px;
background-color: #ecf0f1; /* 与机器人消息背景一致 */
border-radius: 18px;
margin-bottom: 15px;
width: fit-content; /* 宽度自适应内容:避免过宽 */
border-bottom-left-radius: 5px; /* 与机器人消息呼应 */
}
.typing-dots {
display: flex; /* 弹性布局:三个点横向排列 */
align-items: center;
height: 20px;
}
/* 打字点点样式:小圆点,模拟机器人思考 */
.typing-dot {
width: 8px;
height: 8px;
background-color: #7f8c8d;
border-radius: 50%; /* 圆形:核心技巧 */
margin: 0 3px; /* 点之间的间距 */
animation: typingAnimation 1.4s infinite ease-in-out; /* 无限动画:持续跳动 */
}
/* 三个点的动画延迟:依次延迟0/0.2/0.4秒,实现交替跳动效果 */
.typing-dot:nth-child(1) { animation-delay: 0s; }
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
/* 打字动画:小圆点上下跳动,模拟机器人输入 */
@keyframes typingAnimation {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-5px); }
}
/* 媒体查询:移动端适配(屏幕宽度≤600px时生效) */
@media (max-width: 600px) {
header h1 {
font-size: 2rem; /* 缩小主标题字体 */
}
header p {
font-size: 1rem; /* 缩小副标题字体 */
}
.message {
max-width: 90%; /* 增大消息最大宽度,适配小屏幕 */
}
#send-button {
padding: 12px 20px; /* 缩小按钮内边距,节省空间 */
}
}
核心要点:
- 盒模型:
box-sizing: border-box是前端布局的核心技巧,避免 padding/border 导致元素宽度超出预期; - 弹性布局(Flex):全程使用
display: flex实现布局,替代传统的 float,布局更简单、更灵活; - 消息对齐:
margin-left: auto(右对齐)和margin-right: auto(左对齐)是实现消息气泡左右分布的核心技巧,无需浮动; - CSS3 动画:通过
@keyframes实现消息淡入和打字点点动画,提升用户体验,无额外 JavaScript 开销; - 响应式布局:通过
@media (max-width: 600px)实现移动端适配,针对小屏幕调整字体、间距等样式; - rem 单位:字体使用
rem单位,基于根元素(html)的字体大小,适配不同设备的屏幕分辨率。
3.3 JavaScript 交互:前后端通信与 DOM 操作
JavaScript 是前端的交互核心,实现了消息发送、前后端异步通信、DOM 动态更新、正在输入提示等功能,所有代码都封装在DOMContentLoaded事件中,确保页面元素加载完成后再执行,避免获取不到元素的错误。
// 页面DOM元素加载完成后执行:避免获取不到元素的错误
document.addEventListener('DOMContentLoaded', function() {
// 第一步:获取所有需要操作的DOM元素(全局变量,函数内复用)
const chatMessages = document.getElementById('chat-messages'); // 消息展示区
const userInput = document.getElementById('user-input'); // 输入框
const sendButton = document.getElementById('send-button'); // 发送按钮
const typingIndicator = document.getElementById('typing-indicator'); // 正在输入提示
// 函数1:添加用户消息到聊天界面
function addUserMessage(message) {
const messageElement = document.createElement('div'); // 创建新的div元素
messageElement.classList.add('message', 'user-message'); // 添加样式类:通用消息+用户消息
messageElement.innerHTML = `<p>${message}</p>`; // 设置消息内容
chatMessages.appendChild(messageElement); // 将消息元素添加到展示区
scrollToBottom(); // 自动滚动到底部,查看最新消息
}
// 函数2:添加机器人消息到聊天界面(与用户消息逻辑一致,样式类不同)
function addBotMessage(message) {
const messageElement = document.createElement('div');
messageElement.classList.add('message', 'bot-message'); // 添加样式类:通用消息+机器人消息
messageElement.innerHTML = `<p>${message}</p>`;
chatMessages.appendChild(messageElement);
scrollToBottom(); // 自动滚动到底部
}
// 函数3:显示正在输入提示
function showTypingIndicator() {
typingIndicator.style.display = 'block'; // 修改样式为显示
scrollToBottom(); // 滚动到底部,让用户看到提示
}
// 函数4:隐藏正在输入提示
function hideTypingIndicator() {
typingIndicator.style.display = 'none'; // 修改样式为隐藏
}
// 函数5:自动滚动聊天区域到底部(核心工具函数,多次复用)
function scrollToBottom() {
chatMessages.scrollTop = chatMessages.scrollHeight; // scrollTop:滚动条距离顶部的高度;scrollHeight:元素的总高度
}
// 函数6:核心函数——发送消息到后端,处理响应
async function sendMessage() {
// 第一步:获取并清理用户输入(去除首尾空格)
const message = userInput.value.trim();
// 非空校验:如果消息为空,直接返回,不执行后续操作
if (!message) return;
// 第二步:更新前端界面——添加用户消息,清空输入框
addUserMessage(message);
userInput.value = ''; // 清空输入框,方便下次输入
// 第三步:显示正在输入提示,模拟机器人思考
showTypingIndicator();
try {
// 第四步:异步请求后端接口(Fetch API,替代传统的AJAX)
const response = await fetch('/chat', {
method: 'POST', // 请求方法:与后端路由一致,必须为POST
headers: {
// 请求头:告诉后端请求体是JSON格式,必须设置,否则后端request.json解析失败
'Content-Type': 'application/json'
},
// 请求体:将消息转换为JSON字符串,与后端约定的格式一致
body: JSON.stringify({ message: message })
});
// 解析后端返回的JSON响应:与后端jsonify返回的格式一致
const data = await response.json();
// 第五步:隐藏正在输入提示,无论成功还是失败
hideTypingIndicator();
// 第六步:处理后端响应结果,更新前端界面
if (data.status === 'success') {
// 响应成功:添加机器人消息(显示AI回复)
addBotMessage(data.content);
} else {
// 响应失败:显示友好的错误提示,同时在控制台打印详细错误(便于调试)
addBotMessage('抱歉,我遇到了一些问题,请稍后再试。');
console.error('API Error:', data.content);
}
} catch (error) {
// 异常捕获:捕获网络错误、请求超时等问题(如后端服务未启动)
hideTypingIndicator(); // 隐藏正在输入提示
addBotMessage('网络错误,请检查您的连接。'); // 显示网络错误提示
console.error('Fetch Error:', error); // 控制台打印详细错误
}
}
// 事件1:发送按钮点击事件——触发sendMessage函数
sendButton.addEventListener('click', sendMessage);
// 事件2:输入框回车事件——触发sendMessage函数,支持回车发送
userInput.addEventListener('keypress', function(e) {
// 判断是否按下回车键(e.key === 'Enter')
if (e.key === 'Enter') {
sendMessage();
}
});
// 页面加载后,输入框自动获取焦点,提升用户体验
userInput.focus();
});
核心要点:
- DOMContentLoaded 事件:所有代码都包裹在此事件中,确保页面元素加载完成后再执行,这是前端开发的最佳实践;
- Fetch API:使用现代的
fetch替代传统的XMLHttpRequest,实现异步前后端通信,语法更简洁,支持 Promise 和 async/await; - async/await:使用
async/await处理异步请求,替代传统的 Promise 链式调用,代码更易读、更易维护; - 请求头设置:
'Content-Type': 'application/json'必须设置,否则后端request.json无法解析请求体,会导致报错; - JSON 序列化:
JSON.stringify({ message: message })将 JavaScript 对象转换为 JSON 字符串,符合后端的接收格式; - 异常处理:使用
try-catch捕获所有异常(网络错误、后端服务未启动、接口返回错误等),保证前端页面不会崩溃,同时给用户友好的提示; - 自动焦点:
userInput.focus()让页面加载后输入框自动获取焦点,用户可直接输入,提升体验; - 滚动到底部:
chatMessages.scrollTop = chatMessages.scrollHeight是实现聊天区域自动滚动的核心技巧,适用于所有滚动容器。
四、环境配置与依赖文件
4.1 .env 文件:敏感信息配置
.env文件用于存储敏感信息(Coze API 令牌、机器人 ID),避免硬编码在代码中,提高项目的安全性,文件内容如下:
# Coze AI API令牌(从Coze开放平台获取,替换为自己的)
COZE_API_TOKEN=your_coze_api_token
# Coze机器人ID(从Coze开放平台创建机器人后获取,替换为自己的)
BOT_ID=your_bot_id
获取方式:
- 访问Coze 开放平台,注册并登录账号;
- 创建机器人,进入机器人设置页面,获取机器人 ID;
- 进入个人中心 - API 密钥,创建并获取API 令牌。
4.2 requirements.txt:Python 依赖清单
requirements.txt用于管理 Python 第三方库,避免手动安装时版本不一致的问题,文件内容如下:
# Flask框架:轻量级Web框架,核心依赖
flask>=2.0.0
# Coze AI官方SDK:与Coze AI交互的核心依赖
cozepy>=0.1.0
# python-dotenv:加载.env文件的环境变量
python-dotenv>=1.0.0
安装命令:在项目根目录执行以下命令,自动安装所有依赖:
pip install -r requirements.txt
五、项目完整运行步骤
5.1 前置准备
- 注册 Coze 开放平台账号,创建机器人,获取API 令牌和机器人 ID,填入
.env文件; - 确保本地安装了 Python3.8+(推荐 3.9-3.11,兼容性最好);
- 新建项目文件夹,将
app.py、index.html、.env、requirements.txt四个文件放入文件夹中。
5.2 运行步骤
- 安装依赖:打开命令行,进入项目根目录,执行:
pip install -r requirements.txt - 启动后端服务:在项目根目录执行:
看到以下输出,说明服务启动成功:python app.py* Serving Flask app 'app' * Debug mode: on WARNING: This is a development server. Do not use it in a production deployment. * Running on http://127.0.0.1:5000 Press CTRL+C to quit * Restarting with stat * Debugger is active! * Debugger PIN: 123-456-789 - 访问聊天界面:打开浏览器,输入
http://127.0.0.1:5000,即可看到聊天界面; - 代码验证:在输入框中输入 “未来职业规划有什么建议?”,点击发送或按回车,即可看到 AI 的回复,同时实现多轮对话(如继续问 “互联网行业怎么样?”,AI 能识别上下文)。
六、代码优化与扩展
现有代码实现了核心功能,基于现有代码结构,可快速实现以下优化和扩展,无需重构代码:
6.1 会话清理优化(添加时间阈值)
修改cleanup_expired_sessions函数,实现30 分钟无活动则清理会话:
def cleanup_expired_sessions():
expired_sessions = []
# 定义过期时间:30分钟 = 30*60=1800秒
EXPIRE_TIME = 1800
for user_identifier, session_data in user_sessions.items():
# 判断最后活动时间是否超过过期时间
if time.time() - session_data["last_activity"] > EXPIRE_TIME:
expired_sessions.append(user_identifier)
# 删除过期会话
for user_identifier in expired_sessions:
del user_sessions[user_identifier]
6.2 前端添加清空聊天记录功能
在 HTML 的input-area添加清空按钮,再在 JavaScript 中添加点击事件:
<!-- 输入区域添加清空按钮 -->
<div class="input-area">
<input type="text" id="user-input" placeholder="输入您的问题...">
<button id="clear-button" style="margin-left:10px;padding:12px 20px;border-radius:25px;border:none;background:#95a5a6;color:white;cursor:pointer;">清空</button>
<button id="send-button">发送</button>
</div>
// JavaScript中添加清空按钮的事件
const clearButton = document.getElementById('clear-button');
clearButton.addEventListener('click', function() {
// 清空消息展示区
chatMessages.innerHTML = '';
// 重新添加欢迎语
addBotMessage('您好!我是您的未来规划助手。请问您想了解什么关于未来规划的问题?');
// 输入框获取焦点
userInput.focus();
});
6.3 生产环境部署优化
修改app.py的启动代码,关闭调试模式,使用固定端口:
if __name__ == '__main__':
# 生产环境:关闭debug,设置host=0.0.0.0(允许外网访问)
app.run(debug=False, port=5000, host='0.0.0.0')
七、核心代码要点总结
本文的代码实战围绕Flask+Coze AI展开,所有代码都经过实际运行验证,核心要点可总结为 3 点:
- 后端会话保持的核心:复用 Coze AI 返回的
conversation_id,并通过全局字典user_sessions存储会话信息,实现多轮对话的上下文关联; - 前后端通信的核心:后端通过
Flask+jsonify提供 JSON 接口,前端通过Fetch API+async/await实现异步请求,请求头必须设置Content-Type: application/json; - 前端交互的核心:通过原生 JavaScript 实现 DOM 动态更新,使用 CSS3 实现动画和响应式布局,无框架依赖,轻量且易维护。
通过本文的代码解析,你不仅能跑通一个完整的 AI 聊天机器人项目,更能理解每一行代码的设计思路和实现细节,在此基础上可快速扩展出更复杂的功能(如图片发送、多机器人切换、用户登录等),为后续的 AI 应用开发打下坚实的基础。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)