之前已经学习了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配置正确
- 对于移动端,考虑网络不稳定的处理