对接DeepSeek前端不使用SSE流式输出方案

文章类型:Vue

发布者:hp

发布时间:2026-07-02

1. 核心技术栈

Fetch API + ReadableStream:使用原生 Fetch API 的 res读取响应流

  • 自定义协议解析:后端返回的不是标准 SSE 格式,而是自定义的 JSON 流
  • 前端打字机效果:使用队列 + 定时器实现逐字显示

2. 流式传输实现(不依赖 SSE)

// 关键代码片段
const response = await fetch(AI_ENDPOINT, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ question: prompt })
})

const reader = response.body.getReader()  // 获取流读取器
const decoder = new TextDecoder('utf-8')   // 文本解码器

工作原理:

  • 后端以 分块传输编码(chunked transfer encoding) 返回数据

前端通过 API 持续读取数据块

  • 无需 SSE 的 格式

3. 数据解析机制

后端返回的数据格式类似:

data: {"data":{"reasoning":"思考内容..."}}
data: {"data":{"content":"回答片段..."}}
data: {"data":{"done":true}}

三步解析流程:

步骤1:标准化数据块

const normalizeChunk = (raw: string) => {
  return raw
    .split(/\r?\n/)
    .map((line) => {
      const dataMatch = line.match(/^\s*data:\s*(.*)$/i)
      const content = dataMatch ? dataMatch[1] : line
      const braceIndex = content.indexOf('{')
      return braceIndex >= 0 ? content.slice(braceIndex) : ''
    })
    .join('')
}

移除 前缀

  • 提取 JSON 部分

步骤2:提取完整 JSON 对象

const extractPayloads = (buffer: string) => {
  // 通过括号配对找到完整的 JSON 对象
  // 处理字符串转义、嵌套等边界情况
  // 返回完整的 JSON 数组和未完成的残留数据
}
  • 智能处理 JSON 边界(括号匹配)
  • 支持跨数据块的 JSON 拼接

步骤3:业务逻辑处理

for (const payloadText of payloads) {
  const payload = JSON.parse(payloadText)
  
  // 处理思考过程(reasoning 字段)
  if (payload?.data?.reasoning) {
    enqueueThinkingTyping(botMessage, payload.data.reasoning)
  }
  
  // 处理回答内容(content 字段)
  if (payload?.data?.content) {
    enqueueTyping(botMessage, payload.data.content)
  }
  
  // 处理结束标志
  if (payload?.data?.done) {
    shouldStop = true
  }
}

4. 打字机效果实现

使用双队列系统:

:思考过程的待显示文本

:回答内容的待显示文本

核心机制:

const typewriterStep = () => {
  // 1. 优先处理思考过程
  if (thinkingTypewriterQueue.size > 0) {
    // 每次显示 2 个字符,速度 50ms
    const chunkSize = Math.min(2, Math.ceil(pendingText.length / 20) || 1)
    message.thinking += chunk
  } 
  // 2. 思考完成后处理回答内容
  else if (typewriterQueue.size > 0) {
    message.content += chunk
  }
  
  // 3. 递归调用,持续显示
  setTimeout(typewriterStep, 50)
}

5. 为什么不是 SSE?

标准 SSE 需要:

  • 响应头:Content-Type: text/event-stream
  • 特定格式:event: message\ndata: {...}\n\n

浏览器 API

这个实现:

  • 使用普通 HTTP POST + 分块编码
  • 自定义数据格式(类似 SSE 但不完全相同)
  • 手动解析流数据

6. 优势与特点

优点:

  • 无需服务器支持 SSE 协议
  • 更灵活的数据格式
  • 支持 POST 请求(SSE 只能 GET)
  • 可携带复杂请求体

创新点:

  • 思考过程与回答内容分离显示
  • 智能的 JSON 边界检测
  • 流畅的打字机视觉效果
  • 请求中断支持(AbortController)

7. 流程图总结

用户输入问题
    ↓
Fetch API 发送请求
    ↓
ReadableStream 持续读取数据块
    ↓
normalizeChunk 标准化
    ↓
extractPayloads 提取完整 JSON
    ↓
解析 reasoning/content/done 字段
    ↓
入队到打字机队列
    ↓
定时器逐字显示(50ms 间隔)
    ↓
完成后保存对话记录

这个实现本质上是 手动实现了类似 SSE 的流式处理,但更加定制化和灵活,特别适合需要自定义协议格式的场景。


评论
0条评论遵守法律,文明用语,共同建设文明评论区

暂无评论,快来发表第一条评论吧~