875 字
4 分钟
AI对话打字效果
Random Cover
之前已经学习了DeepSeek API的使用,如何搭建本地API对话,但是实现的效果比较呆,输入后果好一会才会出现内容,让人以为卡死了,所以这期来做一个类似ChatGPT的打字效果
实现AI流式输出(逐字显示效果)
要实现像ChatGPT那样一边思考一边逐字显示的效果,需要使用流式API(Streaming API)。以下是完整的实现方案:
1. 后端修改 (app.js)
app.post('/chat-stream', async (req, res) => { const {message, sessionId} = req.body
// 设置响应头为流式传输 res.setHeader('Content-Type', 'text/event-stream') res.setHeader('Cache-Control', 'no-cache') res.setHeader('Connection', 'keep-alive')
// 获取或初始化用户会话 if (!userSessions.has(sessionId)) { userSessions.set(sessionId, [{role: 'system', content: '你是一个乐于助人的AI助手'}]) }
const messages = userSessions.get(sessionId) messages.push({role: 'user', content: message})
try { const stream = await openai.chat.completions.create({ model: 'deepseek-reasoner', messages: messages, stream: true, // 启用流式响应 })
let fullResponse = ''
for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || '' fullResponse += content
// 发送每个数据块到前端 res.write(`data: ${JSON.stringify({content})}\n\n`) }
// 保存完整响应到对话历史 messages.push({role: 'assistant', content: fullResponse}) res.end() } catch (error) { console.error('Error:', error) res.write(`data: ${JSON.stringify({error: error.message})}\n\n`) res.end() }})2. 前端修改 (script.js)
async function sendMessage() { const message = userInput.value.trim() if (!message) return
// 显示用户消息 displayMessage(message, 'user') userInput.value = ''
// 创建AI消息占位符 const aiMessageDiv = document.createElement('div') aiMessageDiv.className = 'message assistant-message' chatContainer.appendChild(aiMessageDiv)
// 滚动到底部 chatContainer.scrollTop = chatContainer.scrollHeight
try { const sessionId = getOrCreateSessionId() const response = await fetch('/chat-stream', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({message, sessionId}), })
if (!response.ok) throw new Error('网络响应不正常')
// 处理流式数据 const reader = response.body.getReader() const decoder = new TextDecoder() let aiResponse = ''
while (true) { const {done, value} = await reader.read() if (done) break
const chunk = decoder.decode(value) const lines = chunk.split('\n\n').filter(line => line.trim())
for (const line of lines) { if (line.startsWith('data: ')) { const data = JSON.parse(line.substring(6))
if (data.error) { aiMessageDiv.textContent = `错误: ${data.error}` return }
if (data.content) { aiResponse += data.content aiMessageDiv.textContent = aiResponse
// 平滑滚动效果 chatContainer.scrollTop = chatContainer.scrollHeight } } } } } catch (error) { aiMessageDiv.textContent = `抱歉,发生了错误: ${error.message}` console.error('Error:', error) }}3. CSS 添加打字动画效果
/* 添加打字光标效果 */.assistant-message { border-right: 2px solid #666; animation: blink-caret 0.75s step-end infinite;}
@keyframes blink-caret { from, to { border-color: transparent; } 50% { border-color: #666; }}4. 优化版本(使用EventSource)
如果你更喜欢使用EventSource而不是fetch API:
function sendMessageWithEventSource() { const message = userInput.value.trim() if (!message) return
displayMessage(message, 'user') userInput.value = ''
const aiMessageDiv = document.createElement('div') aiMessageDiv.className = 'message assistant-message' chatContainer.appendChild(aiMessageDiv)
const sessionId = getOrCreateSessionId() const eventSource = new EventSource(`/chat-stream?message=${encodeURIComponent(message)}&sessionId=${sessionId}`)
let aiResponse = ''
eventSource.onmessage = event => { const data = JSON.parse(event.data)
if (data.error) { aiMessageDiv.textContent = `错误: ${data.error}` eventSource.close() return }
aiResponse += data.content aiMessageDiv.textContent = aiResponse chatContainer.scrollTop = chatContainer.scrollHeight }
eventSource.onerror = () => { eventSource.close() if (!aiResponse) { aiMessageDiv.textContent = '连接中断,请重试' } }}关键实现要点
-
后端必须设置正确的响应头:
res.setHeader('Content-Type', 'text/event-stream')res.setHeader('Cache-Control', 'no-cache')res.setHeader('Connection', 'keep-alive') -
OpenAI API必须启用流式:
stream: true -
前端处理流式数据的两种方式:
- 使用Fetch API + ReadableStream
- 使用EventSource (SSE)
-
性能优化:
- 使用防抖处理快速更新
- 考虑添加”停止生成”按钮
完整工作流程
- 用户发送消息
- 前端立即显示用户消息
- 后端接收请求并开始流式调用AI
- AI每生成一个数据块就立即发送到前端
- 前端逐步显示每个数据块
- 流结束时后端保存完整对话历史
这样就能实现类似ChatGPT的逐字输出效果,用户体验会更加自然流畅。
注意事项
- 确保你的服务器支持长连接
- 考虑添加超时处理
- 生产环境应该添加错误重试机制
- 如果使用HTTPS,确保SSL配置正确
- 对于移动端,考虑网络不稳定的处理
参考资料
最后更新于 2025-05-25,距今已过 168 天
部分内容可能已过时
March7th