三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

零基础前端实现AI对话:SSE技术5分钟极简Demo

零基础前端实现AI对话:SSE技术5分钟极简Demo

1. 项目概述:零基础快速实现AI对话界面

去年在团队内部做技术分享时,我演示过一个让新手快速上手的AI对话Demo方案。这个方案不需要后端开发经验,仅用前端技术栈就能在浏览器里实现完整的对话交互。今天要分享的就是这个经过多次迭代的"五分钟极简版"实现方案。

这个Demo的核心价值在于:使用纯前端技术模拟AI对话效果,特别适合以下场景:

  • 前端开发者快速验证对话型UI设计
  • 产品经理制作可交互原型
  • 面试时展示基础AI交互实现能力
  • 教学演示事件流(SSE)的实际应用

2. 技术选型与准备

2.1 为什么选择SSE技术

Server-Sent Events(SSE)是这个Demo的技术核心,相比WebSocket它有三大优势:

  1. 协议简单:基于HTTP协议,不需要额外握手
  2. 自动重连:内置连接恢复机制
  3. 浏览器兼容性好:主流浏览器都支持
// 典型SSE连接代码 const eventSource = new EventSource('/api/stream'); eventSource.onmessage = (event) => { console.log(event.data); };

2.2 前端工程初始化

推荐使用Vite快速搭建环境:

npm create vite@latest 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.js:

const 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 class="chat-container"> <div id="message-area"></div> <input type="text" id="user-input" placeholder="输入你的问题..."> <button id="send-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); }); </script>

4. 进阶优化方案

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连接失败排查

  1. 检查响应头是否正确:

    Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive
  2. 确保每条消息以\n\n结尾

  3. 跨域问题解决方案:

    // vite.config.js export default defineConfig({ server: { proxy: { '/api': 'http://localhost:3001' } } })

5.2 性能优化建议

  1. 节流处理高频消息:

    let lastUpdate = 0; function throttleUpdate(content) { const now = Date.now(); if (now - lastUpdate > 200) { updateUI(content); lastUpdate = now; } }
  2. 使用Web Worker处理复杂计算

  3. 虚拟滚动优化长对话列表

5.3 移动端适配要点

  1. 视口设置:

    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  2. 输入框优化:

    #user-input { -webkit-appearance: none; font-size: 16px; /* 防止iOS缩放 */ }
  3. 防止键盘遮挡:

    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); });

在实现过程中,我发现最影响体验的往往是细节处理:比如网络不稳定时的重连策略、长文本的自动换行、移动端输入框的聚焦处理等。建议在基础功能完成后,花些时间打磨这些交互细节。

← 返回列表