一键搭建 Coze 智能体对话页面:支持流式输出 + 图片直显,开发效率拉满!

一键搭建 Coze 智能体对话页面:支持流式输出 + 图片直显,开发效率拉满!

如果你正在寻找一套能快速对接 Coze 智能体、实现流畅交互体验的前端方案,这份现成的 HTML 代码绝对值得入手。它不仅完整打通了 Coze API 的调用全流程,还优化了流式输出和图片显示等核心体验,无需复杂配置,开箱即用,让开发者聚焦业务逻辑而非底层交互实现。

核心功能亮点

  1. 完整 Coze 智能体调用流程,零门槛对接
    代码已封装好 Coze API 的核心交互逻辑,从会话创建到消息发送全自动化,无需手动处理接口细节。
    自动生成唯一用户 ID,确保会话连贯性,支持多轮对话记忆。
    内置会话创建接口调用,首次发送消息时自动初始化对话,后续直接复用会话 ID。
    严格遵循 Coze API 协议规范,支持文本类型消息传输,参数配置清晰可修改。
  2. 原生流式输出,对话体验无延迟
    采用 SSE(Server-Sent Events)技术处理流式响应,完美还原 Coze 智能体的实时输出能力。
    接收 Coze API 的流式数据时,逐段拼接响应内容并实时更新到界面,避免全量加载等待。
    智能过滤无效数据类型,仅提取并展示role=assistant且type=answer的核心回复内容,减少冗余。
    搭配加载动画过渡,等待期间视觉反馈友好,提升用户感知流畅度。
  3. 图片直显 + 样式优化,视觉体验升级
    针对 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>
相关推荐
吃饺子不吃馅2 小时前
⚡️ Zustand 撤销重做利器:Zundo 实现原理深度解析
前端·javascript·github
远航_3 小时前
10 个被严重低估的 JS 特性,直接少写 500 行代码
前端·javascript
小高0073 小时前
当前端面临百万级 API 请求:从"修 CSS 的"到架构师的进化之路
前端·javascript·面试
LateFrames3 小时前
使用 Winform / WPF / WinUI3 / Electron 实现异型透明窗口
javascript·electron·wpf·winform·winui3
Asort3 小时前
React类组件精要:定义机制与生命周期方法进阶教程
前端·javascript·react.js
陳陈陳3 小时前
从“变量提升”到“调用栈爆炸”:V8 引擎是如何偷偷执行你的 JavaScript 的?
javascript
San303 小时前
深入理解JavaScript执行机制:从变量提升到内存管理
javascript·编程语言·代码规范
用户12039112947263 小时前
深入理解JavaScript执行机制:从变量提升到调用栈全解析
javascript
weixin_438694393 小时前
pnpm 安装依赖后 仍然启动报的问题
开发语言·前端·javascript·经验分享