恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
零基础前端实现AI对话:SSE技术5分钟极简Demo
首页
资讯中心
/
零基础前端实现AI对话:SSE技术5分钟极简Demo
零基础前端实现AI对话:SSE技术5分钟极简Demo
发布时间:2026/8/8 13:51:29
1. 项目概述零基础快速实现AI对话界面去年在团队内部做技术分享时我演示过一个让新手快速上手的AI对话Demo方案。这个方案不需要后端开发经验仅用前端技术栈就能在浏览器里实现完整的对话交互。今天要分享的就是这个经过多次迭代的五分钟极简版实现方案。这个Demo的核心价值在于使用纯前端技术模拟AI对话效果特别适合以下场景前端开发者快速验证对话型UI设计产品经理制作可交互原型面试时展示基础AI交互实现能力教学演示事件流(SSE)的实际应用2. 技术选型与准备2.1 为什么选择SSE技术Server-Sent Events(SSE)是这个Demo的技术核心相比WebSocket它有三大优势协议简单基于HTTP协议不需要额外握手自动重连内置连接恢复机制浏览器兼容性好主流浏览器都支持// 典型SSE连接代码 const eventSource new EventSource(/api/stream); eventSource.onmessage (event) { console.log(event.data); };2.2 前端工程初始化推荐使用Vite快速搭建环境npm create vitelatest ai-chat-demo --template vanilla cd ai-chat-demo npm install关键依赖说明eventsource-polyfill解决部分浏览器兼容问题marked可选用于Markdown格式回复渲染highlight.js可选代码高亮支持3. 核心实现步骤3.1 模拟AI回复逻辑在没有真实AI接口的情况下我们可以设计一个简单的回复生成器const responses [ 这是一个有趣的提问, 让我思考一下..., 根据我的理解这个问题涉及以下几个方面, 很抱歉我暂时无法回答这个问题 ]; function getAIResponse(prompt) { const typingDelay Math.random() * 2000 1000; const responseIndex Math.floor(Math.random() * responses.length); return new Promise(resolve { setTimeout(() { resolve(responses[responseIndex]); }, typingDelay); }); }3.2 SSE服务模拟实现在public文件夹下创建mock-server.jsconst http require(http); const fs require(fs); http.createServer((req, res) { if (req.url /api/chat) { res.writeHead(200, { Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive }); let counter 0; const interval setInterval(() { const data {text: AI回复消息 ${counter}}; res.write(data: ${JSON.stringify(data)}\n\n); if (counter 5) { clearInterval(interval); res.end(); } }, 1000); req.on(close, () { clearInterval(interval); }); } }).listen(3001);3.3 前端界面整合完整HTML示例div classchat-container div idmessage-area/div input typetext iduser-input placeholder输入你的问题... button idsend-btn发送/button /div script const messageArea document.getElementById(message-area); const userInput document.getElementById(user-input); const sendBtn document.getElementById(send-btn); function addMessage(content, isAI false) { const msgDiv document.createElement(div); msgDiv.className isAI ? ai-message : user-message; msgDiv.textContent content; messageArea.appendChild(msgDiv); messageArea.scrollTop messageArea.scrollHeight; } sendBtn.addEventListener(click, async () { const prompt userInput.value; if (!prompt) return; addMessage(prompt); userInput.value ; const response await getAIResponse(prompt); addMessage(response, true); }); /script4. 进阶优化方案4.1 增加打字机效果function typewriterEffect(text, element) { let i 0; element.textContent ; const timer setInterval(() { if (i text.length) { element.textContent text.charAt(i); i; } else { clearInterval(timer); } }, 50); }4.2 添加历史记录功能// 使用localStorage保存对话历史 function saveConversation(messages) { localStorage.setItem(chatHistory, JSON.stringify(messages)); } function loadConversation() { return JSON.parse(localStorage.getItem(chatHistory)) || []; }4.3 集成真实AI API以OpenAI为例的适配代码async function getRealAIResponse(prompt) { const response await fetch(https://api.openai.com/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_KEY} }, body: JSON.stringify({ model: gpt-3.5-turbo, messages: [{role: user, content: prompt}], stream: true }) }); const reader response.body.getReader(); const decoder new TextDecoder(); let result ; while (true) { const {done, value} await reader.read(); if (done) break; const chunk decoder.decode(value); const lines chunk.split(\n); for (const line of lines) { if (line.startsWith(data:) !line.includes([DONE])) { const data JSON.parse(line.replace(data: , )); const content data.choices[0].delta.content; if (content) { result content; // 实时更新UI document.getElementById(ai-response).textContent result; } } } } return result; }5. 常见问题与调试技巧5.1 SSE连接失败排查检查响应头是否正确Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive确保每条消息以\n\n结尾跨域问题解决方案// vite.config.js export default defineConfig({ server: { proxy: { /api: http://localhost:3001 } } })5.2 性能优化建议节流处理高频消息let lastUpdate 0; function throttleUpdate(content) { const now Date.now(); if (now - lastUpdate 200) { updateUI(content); lastUpdate now; } }使用Web Worker处理复杂计算虚拟滚动优化长对话列表5.3 移动端适配要点视口设置meta nameviewport contentwidthdevice-width, initial-scale1, maximum-scale1输入框优化#user-input { -webkit-appearance: none; font-size: 16px; /* 防止iOS缩放 */ }防止键盘遮挡window.addEventListener(resize, () { setTimeout(() { messageArea.scrollTop messageArea.scrollHeight; }, 300); });6. 样式与交互增强6.1 基础CSS方案.chat-container { max-width: 600px; margin: 0 auto; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; } #message-area { height: 400px; overflow-y: auto; padding: 10px; background: #f9f9f9; } .user-message { background: #e3f2fd; padding: 8px 12px; border-radius: 18px 18px 0 18px; margin: 5px 0 5px auto; max-width: 80%; } .ai-message { background: white; padding: 8px 12px; border-radius: 18px 18px 18px 0; margin: 5px auto 5px 0; max-width: 80%; }6.2 动画效果增强/* 消息入场动画 */ keyframes messageIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .user-message, .ai-message { animation: messageIn 0.3s ease-out; } /* 打字光标效果 */ .typing-cursor::after { content: |; animation: blink 1s infinite; } keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }7. 项目扩展方向7.1 多轮对话上下文let conversationContext []; async function getContextAwareResponse(prompt) { conversationContext.push({role: user, content: prompt}); const response await fetch(/api/chat, { method: POST, body: JSON.stringify({ messages: conversationContext }) }); const aiResponse await response.json(); conversationContext.push({role: assistant, content: aiResponse}); return aiResponse; }7.2 语音输入支持const recognition new webkitSpeechRecognition(); recognition.continuous false; recognition.interimResults false; document.getElementById(voice-btn).addEventListener(click, () { recognition.start(); // 显示录音中状态 }); recognition.onresult (event) { const transcript event.results[0][0].transcript; document.getElementById(user-input).value transcript; };7.3 文件上传处理document.getElementById(file-input).addEventListener(change, (e) { const file e.target.files[0]; const reader new FileReader(); reader.onload (event) { const content event.target.result; // 处理文件内容 addMessage(已上传文件: ${file.name}, false); }; reader.readAsText(file); });在实现过程中我发现最影响体验的往往是细节处理比如网络不稳定时的重连策略、长文本的自动换行、移动端输入框的聚焦处理等。建议在基础功能完成后花些时间打磨这些交互细节。