一键搭建 Coze 智能体对话页面:支持流式输出 + 图片直显,开发效率拉满!
如果你正在寻找一套能快速对接 Coze 智能体、实现流畅交互体验的前端方案,这份现成的 HTML 代码绝对值得入手。它不仅完整打通了 Coze API 的调用全流程,还优化了流式输出和图片显示等核心体验,无需复杂配置,开箱即用,让开发者聚焦业务逻辑而非底层交互实现。
核心功能亮点
- 完整 Coze 智能体调用流程,零门槛对接
代码已封装好 Coze API 的核心交互逻辑,从会话创建到消息发送全自动化,无需手动处理接口细节。
自动生成唯一用户 ID,确保会话连贯性,支持多轮对话记忆。
内置会话创建接口调用,首次发送消息时自动初始化对话,后续直接复用会话 ID。
严格遵循 Coze API 协议规范,支持文本类型消息传输,参数配置清晰可修改。 - 原生流式输出,对话体验无延迟
采用 SSE(Server-Sent Events)技术处理流式响应,完美还原 Coze 智能体的实时输出能力。
接收 Coze API 的流式数据时,逐段拼接响应内容并实时更新到界面,避免全量加载等待。
智能过滤无效数据类型,仅提取并展示role=assistant且type=answer的核心回复内容,减少冗余。
搭配加载动画过渡,等待期间视觉反馈友好,提升用户感知流畅度。 - 图片直显 + 样式优化,视觉体验升级
针对 Coze 智能体返回的图片链接,实现自动解析与优化显示,无需额外处理。
支持 Markdown 格式图片语法解析,提取图片 URL 后自动渲染为图片元素。
图片添加自适应样式,最大宽度贴合对话气泡,搭配圆角设计和 hover 放大效果,交互更细腻。
区分用户与 AI 消息样式,色彩对比清晰,聊天气泡设计符合主流 IM 产品体验,降低用户适应成本。
输入框支持回车发送、Shift + 回车换行,自动调整高度适配长文本输入。
内置调试日志输出,关键流程(如会话创建、流式数据接收)可通过控制台查看详情,便于问题排查。
错误处理机制完善,API 配置错误、网络异常等场景均有明确提示,降低运维成本。
快速上手优势
零框架依赖:纯 HTML+CSS+JavaScript 实现,无需引入 Vue、React 等框架,部署简单。
配置极简:仅需替换COZE_API_TOKEN和COZE_BOT_ID两个核心参数,即可对接自己的 Coze 智能体。
响应式设计:适配桌面端与移动端,聊天容器高度自适应屏幕,在不同设备上均有良好表现。
无论是用于快速验证 Coze 智能体效果,还是作为正式产品的对话交互模块,这份代码都能大幅节省开发时间。它兼顾了功能完整性与体验流畅度,让你无需从零搭建底层交互,专注于智能体的能力优化与业务场景落地。
css
<!--
解决图片显示问题;
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Coze API 对话页面</title>
<style>
/* 保持原有样式不变 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", sans-serif;
}
body {
background-color: #f5f7fa;
padding: 20px;
}
.chat-container {
max-width: 800px;
margin: 0 auto;
height: calc(100vh - 120px);
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
overflow: hidden;
display: flex;
flex-direction: column;
}
.chat-history {
flex: 1;
padding: 20px;
overflow-y: auto;
}
.message {
margin-bottom: 16px;
max-width: 70%;
line-height: 1.5;
}
.user-message {
margin-left: auto;
}
.ai-message {
margin-right: auto;
}
.message-content {
padding: 12px 18px;
border-radius: 18px;
word-wrap: break-word;
}
.user-message .message-content {
background: #0071e3;
color: #fff;
border-bottom-right-radius: 4px;
}
.ai-message .message-content {
background: #f2f2f7;
color: #1d1d1f;
border-bottom-left-radius: 4px;
white-space: pre-line;
}
.ai-message .message-content .ai-image {
max-width: 100%;
border-radius: 8px;
margin: 8px 0;
display: block;
cursor: pointer;
transition: transform 0.2s;
}
.ai-message .message-content .ai-image:hover {
transform: scale(1.02);
}
.ai-message .message-content .ai-link {
color: #0071e3;
text-decoration: underline;
margin: 0 2px;
}
.ai-message .message-content .ai-link:hover {
color: #0066cc;
}
.chat-input {
padding: 16px;
border-top: 1px solid #e5e5ea;
display: flex;
gap: 10px;
}
#message-input {
flex: 1;
padding: 12px 16px;
border: 1px solid #e5e5ea;
border-radius: 24px;
outline: none;
font-size: 16px;
resize: none;
height: 48px;
}
#send-btn {
width: 48px;
height: 48px;
border-radius: 50%;
background: #0071e3;
color: #fff;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
#send-btn:disabled {
background: #c7c7cc;
cursor: not-allowed;
}
.loading {
display: inline-flex;
gap: 4px;
align-items: center;
justify-content: center;
}
.loading-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #86868b;
animation: load 1s infinite alternate;
}
.loading-dot:nth-child(2) {
animation-delay: 0.2s;
}
.loading-dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes load {
from { opacity: 0.5; transform: scale(0.8); }
to { opacity: 1; transform: scale(1.2); }
}
.error-log {
font-size: 12px;
color: #ff3b30;
margin-top: 4px;
max-width: 70%;
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-history" id="chat-history"></div>
<div class="chat-input">
<textarea id="message-input" placeholder="请输入消息..."
onkeydown="if(event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendMessage(); }"></textarea>
<button id="send-btn" onclick="sendMessage()">→</button>
</div>
</div>
<script>
// 1. API配置
const COZE_API_TOKEN = "xxxx";
const COZE_BOT_ID = "xxx";
const USER_ID = "test_user_" + Math.random().toString(36).substr(2, 9);
// 2. API端点
const API_ENDPOINTS = {
createConversation: "https://api.coze.cn/v1/conversation/create",
sendMessage: "https://api.coze.cn/v3/chat",
retrieveStatus: "https://api.coze.cn/v3/chat/retrieve",
getMessages: "https://api.coze.cn/v3/chat/message/list"
};
// 3. 全局变量
let conversationId = "";
const chatHistory = document.getElementById("chat-history");
const messageInput = document.getElementById("message-input");
const sendBtn = document.getElementById("send-btn");
let currentAiMessageElement = null;
let currentFullContent = "";
// 4. 发送消息主函数
async function sendMessage() {
const userInput = messageInput.value.trim();
if (!userInput) return;
messageInput.value = "";
sendBtn.disabled = true;
addMessageToUI("user", userInput);
const loadingId = addLoadingToUI();
currentAiMessageElement = null;
currentFullContent = "";
try {
if (!COZE_API_TOKEN || COZE_API_TOKEN === "xxx") {
throw new Error("请填写有效的API Token");
}
if (!COZE_BOT_ID || COZE_BOT_ID === "xxx") {
throw new Error("请填写有效的Bot ID");
}
if (!conversationId) {
addDebugLog("开始创建会话...");
conversationId = await createConversation();
addDebugLog(`会话创建成功,ID:${conversationId.substring(0, 8)}...`);
}
addDebugLog("发送流式消息到Coze API...");
await sendChatMessage(conversationId, userInput, loadingId);
} catch (error) {
removeLoadingFromUI(loadingId);
addMessageToUI("ai", `请求出错:${error.message}`);
addDebugLog(`错误详情:${error.stack}`);
} finally {
sendBtn.disabled = false;
}
}
// 5. 创建会话
async function createConversation() {
try {
const response = await fetch(API_ENDPOINTS.createConversation, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${COZE_API_TOKEN}`
},
body: JSON.stringify({
bot_id: COZE_BOT_ID,
user_id: USER_ID,
stream: false,
auto_save_history: true,
additional_messages: []
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code !== 0 || !data.data?.id) {
throw new Error(`创建会话失败(code: ${data.code}):${data.msg || '缺少会话ID'}`);
}
return data.data.id;
} catch (error) {
console.error("创建会话失败:", error);
throw new Error(`创建会话出错:${error.message}`);
}
}
// 6. 发送聊天消息(核心修改:仅拼接 type: 'answer' 的内容)
async function sendChatMessage(conversationId, query, loadingId) {
try {
const controller = new AbortController();
const response = await fetch(API_ENDPOINTS.sendMessage, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${COZE_API_TOKEN}`,
'Accept': 'text/event-stream'
},
body: JSON.stringify({
bot_id: COZE_BOT_ID,
user_id: USER_ID,
additional_messages: [{
"role": "user",
"content": query,
"content_type": "text"
}],
stream: true,
auto_save_history: true,
conversation_id: conversationId
}),
signal: controller.signal
});
if (!response.ok || !response.body) {
throw new Error(`HTTP 错误: ${response.status},不支持SSE流式响应`);
}
removeLoadingFromUI(loadingId);
currentAiMessageElement = addStreamingMessageToUI();
const decoder = new TextDecoder('utf-8');
const reader = response.body.getReader();
const SSE_DELIMITER = '\n\n';
let buffer = '';
let isStreamEnd = false; // 标记流是否结束
while (!isStreamEnd) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const sseEvents = buffer.split(SSE_DELIMITER);
buffer = sseEvents.pop() || '';
for (const event of sseEvents) {
if (!event.trim()) continue;
const lines = event.split('\n').filter(line => line.trim());
let sseData = null;
// 1. 解析 data 中的 JSON 数据
for (const line of lines) {
if(line.startsWith('event:'))
if (line.includes('completed')) break;
if (line.startsWith('data:')) {
const jsonStr = line.substring('data:'.length).trim();
if (!jsonStr) continue;
try {
sseData = JSON.parse(jsonStr);
} catch (e) {
addDebugLog(`解析SSE data失败:${e.message},原始数据:${jsonStr}`);
continue;
}
}
}
// 2. 核心过滤:仅处理 role=assistant 且 type=answer 的数据
if (sseData && sseData.role === "assistant") {
// 仅拼接 type: 'answer' 的内容
if (sseData.type === "answer" && sseData.content !== undefined) {
currentFullContent += sseData.content;
const htmlContent = convertMarkdownToHtml(currentFullContent);
updateStreamingMessage(currentAiMessageElement, htmlContent);
// addDebugLog(`拼接 type=answer 内容,当前长度:${currentFullContent.length}`);
}
// 流结束判断(非 answer 类型,仅用于停止流)
else if (sseData.type === "follow_up") {
// addDebugLog("收到流结束信号(follow_up),停止拼接");
isStreamEnd = true;
break;
}
// 其他类型(如 tool_response、verbose 等)直接跳过
else {
// addDebugLog(`跳过非 answer 类型:${sseData.type}`);
continue;
}
}
}
}
// 流结束后关闭读取器
await reader.cancel();
} catch (error) {
if (error.name !== 'AbortError') {
console.error("流式发送消息失败:", error);
throw new Error(`流式请求出错:${error.message}`);
}
}
}
// 7. Markdown转HTML(保持不变)
function convertMarkdownToHtml(markdownText) {
if (!markdownText) return '';
let html = markdownText;
// 处理图片
const imageRegex = /!\[(.*?)\]\((https?:\/\/[^\s)]+)\)/g;
html = html.replace(imageRegex, (match, altText, imgUrl) => {
if (imgUrl.endsWith('.jpeg') || imgUrl.endsWith('.jpg') || imgUrl.endsWith('.png') || imgUrl.endsWith('.gif')) {
return `<img src="${imgUrl}" alt="${altText || 'AI生成图片'}" class="ai-image">`;
}
return match;
});
// 处理链接
const linkRegex = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
html = html.replace(linkRegex, (match, linkText, linkUrl) => {
return `<a href="${linkUrl}" class="ai-link" target="_blank">${linkText}</a>`;
});
// 处理换行
html = html.replace(/\n/g, '<br>');
return html;
}
// 8. 界面辅助函数(保持不变)
function addMessageToUI(role, content) {
const messageDiv = document.createElement("div");
messageDiv.className = `message ${role}-message`;
const contentDiv = document.createElement("div");
contentDiv.className = "message-content";
contentDiv.textContent = content;
messageDiv.appendChild(contentDiv);
chatHistory.appendChild(messageDiv);
chatHistory.scrollTop = chatHistory.scrollHeight;
}
function addStreamingMessageToUI() {
const messageDiv = document.createElement("div");
messageDiv.className = "message ai-message";
const contentDiv = document.createElement("div");
contentDiv.className = "message-content";
contentDiv.innerHTML = "";
messageDiv.appendChild(contentDiv);
chatHistory.appendChild(messageDiv);
chatHistory.scrollTop = chatHistory.scrollHeight;
return messageDiv;
}
function updateStreamingMessage(messageElement, htmlContent) {
const contentDiv = messageElement.querySelector(".message-content");
if (contentDiv) {
contentDiv.innerHTML = htmlContent;
chatHistory.scrollTop = chatHistory.scrollHeight;
}
}
function addDebugLog(log) {
console.log("[Coze Debug]", log);
}
function addLoadingToUI() {
const loadingId = `loading-${Date.now()}`;
const loadingDiv = document.createElement("div");
loadingDiv.className = "message ai-message";
loadingDiv.id = loadingId;
const loadingContent = document.createElement("div");
loadingContent.className = "message-content loading";
loadingContent.innerHTML = `
<span class="loading-dot"></span>
<span class="loading-dot"></span>
<span class="loading-dot"></span>
`;
loadingDiv.appendChild(loadingContent);
chatHistory.appendChild(loadingDiv);
chatHistory.scrollTop = chatHistory.scrollHeight;
return loadingId;
}
function removeLoadingFromUI(loadingId) {
const loadingDiv = document.getElementById(loadingId);
if (loadingDiv) loadingDiv.remove();
}
// 输入框自动调整高度
messageInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
</script>
</body>
</html>