跳过

AI对话打字效果

发布时间: at 16:21
本文收录在以下合集中:

之前已经学习了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 = '连接中断,请重试'
    }
  }
}

关键实现要点

  1. 后端必须设置正确的响应头

    res.setHeader('Content-Type', 'text/event-stream')
    res.setHeader('Cache-Control', 'no-cache')
    res.setHeader('Connection', 'keep-alive')
    
  2. OpenAI API必须启用流式

    stream: true
    
  3. 前端处理流式数据的两种方式

    • 使用Fetch API + ReadableStream
    • 使用EventSource (SSE)
  4. 性能优化

    • 使用防抖处理快速更新
    • 考虑添加”停止生成”按钮

完整工作流程

  1. 用户发送消息
  2. 前端立即显示用户消息
  3. 后端接收请求并开始流式调用AI
  4. AI每生成一个数据块就立即发送到前端
  5. 前端逐步显示每个数据块
  6. 流结束时后端保存完整对话历史

这样就能实现类似ChatGPT的逐字输出效果,用户体验会更加自然流畅。

注意事项

  1. 确保你的服务器支持长连接
  2. 考虑添加超时处理
  3. 生产环境应该添加错误重试机制
  4. 如果使用HTTPS,确保SSL配置正确
  5. 对于移动端,考虑网络不稳定的处理

参考资料