react和小程序实现流式请求 -- Al对话版

99 阅读1分钟

废话不多说直接上代码

1.PC端

      const controller = new AbortController()
      const timeoutId = setTimeout(() => controller.abort(), 60000) // 60秒超时,内容多可以加
  
      const response = await fetch('https://you.com', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'text/event-stream',
        },
        body: JSON.stringify(params),
        signal: controller.signal
      })
  
      clearTimeout(timeoutId)
  
      if (!response.ok) {
        throw new Error(`请求错误,状态码: ${response.status}`)
      }
  
      if (!response.body) {
        throw new Error('ReadableStream not supported')
      }
  
      const reader = response.body.getReader()
      const decoder = new TextDecoder()
      let result = ''
      const MAX_LENGTH = 500000 // 设置最大字符数限制
  
      const processStream = async () => {
        try {
          const { done, value } = await reader.read()
          if (done) return
  
          const text = decoder.decode(value, { stream: true })
          
          // 限制结果大小
          if (result.length + text.length <= MAX_LENGTH) {
            result += text
            setChatResult(result)
            await processStream()
          } else {
            reader.cancel()
            messageApi.warning('内容已达到最大限制')
          }
        } catch (error) {
          const err = error as { name: string; message: string }
          console.error('生成文章失败:', err)
          messageApi.error(err.name === 'AbortError' ? '请求超时,请重试' : '生成文章失败,请重试')
        } finally {
          setChatLoading(false) 
        }
      }
      
      await processStream()
    } catch (error) {
      const err = error as { name: string; message: string }
      messageApi.error(err.name === 'AbortError' ? '请求超时,请重试' : '生成文章失败,请重试')
    } finally {
      
    }

2. 小程序端

        const requestTask = wx.request({
          url: 'https://your.com',
          method: 'POST',
          data: params, // 请求参数
          enableChunked: true, // 开启分块传输模式
          responseType: 'arraybuffer', // 必须使用二进制格式接收
          header: {
            'Content-Type': 'application/json',
          },
        })
        // 监听分块数据回调
        requestTask.onChunkReceived((res) => {
          const arrayBuffer = res.data

          let rawStr = wx.arrayBufferToBase64(arrayBuffer)
          rawStr = base64ToUtf8(rawStr) // 自定义Base64转UTF-8
          if (rawStr) {
            const result = rawStr
            setAiReplyValue((prev) => prev + result)
          }
        })

base64ToUtf8函数

  function base64ToUtf8(base64Str) {
    try {
      // 确保输入是有效的base64字符串
      const cleanedBase64 = base64Str.replace(/\s/g, '');
      
      // 使用微信小程序的API进行base64解码
      const arrayBuffer = wx.base64ToArrayBuffer(cleanedBase64);
      const uint8Array = new Uint8Array(arrayBuffer);
      
      // 使用TextDecoder解码UTF-8
      if (typeof TextDecoder === 'function') {
        return new TextDecoder('utf-8').decode(uint8Array);
      }
      
      // 备用方案:手动解码UTF-8
      let result = '';
      let i = 0;
      while (i < uint8Array.length) {
        let codePoint;
        
        // UTF-8解码逻辑
        const byte1 = uint8Array[i++];
        if (byte1 < 0x80) {
          // 单字节字符
          codePoint = byte1;
        } else if (byte1 < 0xE0) {
          // 双字节字符
          const byte2 = uint8Array[i++] & 0x3F;
          codePoint = ((byte1 & 0x1F) << 6) | byte2;
        } else if (byte1 < 0xF0) {
          // 三字节字符
          const byte2 = uint8Array[i++] & 0x3F;
          const byte3 = uint8Array[i++] & 0x3F;
          codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
        } else {
          // 四字节字符
          const byte2 = uint8Array[i++] & 0x3F;
          const byte3 = uint8Array[i++] & 0x3F;
          const byte4 = uint8Array[i++] & 0x3F;
          codePoint = ((byte1 & 0x07) << 18) | (byte2 << 12) | (byte3 << 6) | byte4;
        }
        
        result += String.fromCodePoint(codePoint);
      }
      
      return result;
    } catch (e) {
      console.error(`解码失败: ${e.message}`);
      return '';
    }
  }