流式文本输出的实现方式

发布于:2024-06-15 ⋅ 阅读:(21) ⋅ 点赞:(0)

 

后端:

def query():
    #response是生成的一个结果
    # 创建一个生成器函数,用于逐个生成JSON对象
    results=response.split("\n")
    def generate():
        # 遍历搜索结果
        for response in results:
            # 创建一个部分响应的字典
            part_response = {
                'message': response+"\n",
            }
            # 将字典转换为JSON字符串,并确保每个JSON对象都是独立的
            yield (json.dumps(part_response, ensure_ascii=False) + '\n\n').encode('utf-8')
            time.sleep(1)  # 模拟延迟

    return Response(stream_with_context(generate()), content_type='text/event-stream')
  1. query()函数定义了一个生成器函数generate,它将一个名为response的字符串分割成多行,并将每一行作为一个单独的JSON对象逐个生成。

  2. response.split("\n")response字符串按换行符\n分割成一个列表results

  3. generate()函数是一个生成器,它遍历results列表中的每个元素(即每行文本)。

  4. 对于results中的每一行,代码创建一个包含这一行文本的字典part_response

  5. json.dumps(part_response, ensure_ascii=False)将字典转换为JSON格式的字符串。ensure_ascii=False允许JSON中包含非ASCII字符。

  6. yield (json.dumps(part_response, ensure_ascii=False) + '\n\n').encode('utf-8')这行代码将每个JSON对象作为字节字符串生成,并在对象之间添加两个换行符,以符合SSE的格式要求。

  7. time.sleep(1)模拟延迟,每生成一个JSON对象后,函数将暂停1秒钟。这在实际应用中可能用于模拟实时数据推送的延迟。

  8. Response(stream_with_context(generate()), content_type='text/event-stream')创建了一个Django响应对象,它将generate()生成器作为流来处理。stream_with_context是一个辅助函数,它确保在生成器运行时,Django的请求上下文是激活的。content_type='text/event-stream'指定了响应的MIME类型,这样客户端浏览器会识别这是一个SSE响应。

前端代码:

 // 使用 fetch API 发送 POST 请求
      fetch(url, {
        method: 'POST',
        body: JSON.stringify(data),
        headers: {
          'Content-Type': 'application/json',
        },
        responseType: 'json', // 设置响应类型为 json
      })
        .then((response) => {
          // 检查响应状态
          if (!response.ok) {
            throw new Error('Network response was not ok')
          }
          return response.body // 获取流式响应
        })
        .then((stream) => {
          // 使用 ReadableStream 来处理流式数据
          const reader = stream.getReader()
          const decoder = new TextDecoder('utf-8')

          // 使用箭头函数来定义 read 函数,确保正确的 this 上下文
          const read = () => {
            return reader.read().then((result) => {
              if (result.done) {
                console.log('Stream complete')
                return
              }
              const chunk = decoder.decode(result.value, { stream: true })
              console.log(`Received chunk: ${chunk}`)
              // 更新前端内容
              this.content += JSON.parse(chunk).message
              if (this.searchResults == '') {
                this.searchResults = JSON.parse(chunk).searchResults
              }
              this.loading = false

              // 如果需要,可以在这里处理数据,更新界面等
              return read() // 继续读取下一块数据
            })
          }

          read().catch((error) => {
            console.error('Error occurred while reading stream:', error)
          })
        })
        .catch((error) => {
          console.error('Error during POST request:', error)
          this.loading = false
        })

 

  1. 读取流式数据

    • const reader = stream.getReader()从响应流中获取一个读取器。
    • const decoder = new TextDecoder('utf-8')创建一个文本解码器,用于将流中的二进制数据解码为文本。
    • const read = () => {...}定义一个递归函数read,用于连续读取流中的数据块。
    • reader.read()从流中读取下一个数据块。
    • 如果读取完成(result.donetrue),则打印消息并结束。
    • 否则,使用decoder.decode()将数据块解码为文本,并打印接收到的数据块。
    • this.content += JSON.parse(chunk).message将解码后的消息更新到Vue组件的数据属性content中。
    • 如果this.searchResults为空,则将其更新为从流中解析出的搜索结果。
    • this.loading = false更新Vue组件的数据属性loading,可能用于表示加载状态。