收藏
回答

微信小程序AI对话-流式响应-enableChunked - onChunkReceived ?


出现的问题:1.ios苹果手机都没问题

2.安卓手机:开发版本没问题,在开发版本真机预览安卓手机也没问题。但是在体验版安卓手机真机测试不行。

安卓手机上流式数据参考资料吐完,后面的内容就会中断,直接走fail回调了。而且是必现的。

//这是 @/utils/chunk-res文件下的代码
/**
 * 微信http流式响应处理
 * ```
 * // Example:
 * const chunkRes = ChunkRes()
 * // can`t use ref() to save task; it will lost task info
 * const task = wx.request({
 *  //...other params
 *  enableChunked: true,
 *  success: (res) => {
 *    const lastResTexts:string[] | undefined = chunkRes.onComplateReturn()
 *    // dosomething
 *   }
 * })
 * task.onChunkReceived(res => {
 *   const resTexts:string[] | undefined = chunkRes.onChunkReceivedReturn(res)
 *   // dosomething
 * })
 * ```
 * @returns
 */
export const ChunkRes = () => {
  /**
   * 分段返回开始
   */
  const CHUNK_START = "data:";
  /**
   * 分段返回中断
   */
  const SPLIT_WORD = "\ndata:";
  /**
   * 保存返回文本
   */
  let lastText = "";
  /**
   * 保存解码异常的数据
   */
  let lastData = new Uint8Array();
  /**
   * 返回数据转文本
   * @param res
   * @returns
   */
  const getChunkText = (data) => {
    // let data = res.data;
    // console.log('getSeeResData:', data)
    // 兼容处理,真机返回的的是 ArrayBuffer
    if (data instanceof ArrayBuffer) {
      data = new Uint8Array(data);
    }
    let text = data;
    // Uint8Array转码
    if (typeof data != "string") {
      // 兼容处理  微信小程序不支持TextEncoder/TextDecoder
      try {
        //但是,请注意,如果数据量很大(比如你提供的Uint8Array有1163374个元素),使用`String.fromCharCode.apply(null, uint8Array)`可能会因为参数过多而报错(因为apply的参数个数有限制)
        console.log("lastData", lastData);
        text = decodeURIComponent(escape(String.fromCharCode(...lastData, ...data)));
        lastData = new Uint8Array();
      } catch (error) {
        text = "";
        console.log("解码异常", data);
        // Uint8Array 拼接
        let swap = new Uint8Array(lastData.length + data.length);
        swap.set(lastData, 0);
        swap.set(data, lastData.length);
        // lastData = lastData.concat(data)
        lastData = swap;
      }
    }
    return text;
  };
  /**
   * 判断是否被拆分
   * @param text
   * @returns
   */
  const isStartString = (text) => {
    return text.substring(0, 5) == CHUNK_START;
  };
  /**
   * 对被合并的多段请求拆分
   * @param text
   */
  const splitText = (text) => {
    return text
      .replaceAll(`\n\n${SPLIT_WORD}`, `\n${SPLIT_WORD}`)
      .replaceAll(`\n${SPLIT_WORD}`, `${SPLIT_WORD}`)
      .split(SPLIT_WORD)
      .filter((str) => !!str);
  };
  /**
   * 返回数据集
   * @param res
   * @param onSuccess
   */
  const onChunkReceived = (res, onSuccess) => {
    let text = getChunkText(res);
    console.log("onChunkReceived", text);
    if (isStartString(text) && lastText) {
      console.log("onSuccess", lastText);
      onSuccess(splitText(removeStartText(lastText)));
      // 存储本次的数据
      lastText = text;
    } else {
      lastText = lastText + text;
    }
  };
  /**
   * 返回数据集(返回数据)
   * @param res
   * @param onSuccess
   */
  const onChunkReceivedReturn = function (res) {
    let text = getChunkText(res);
    console.log("onChunkReceived", text);
    if (isStartString(text) && lastText) {
      // console.log("onSuccess", lastText);
      // onSuccess();
      let swap = lastText;
      // 存储本次的数据
      lastText = text;
      return splitText(removeStartText(swap));
    } else {
      lastText = lastText + text;
    }
  };
  /**
   * 删除文本的开始的 data:
   * @param text
   * @returns
   */
  const removeStartText = (text) => {
    if (text.substring(0, CHUNK_START.length) == CHUNK_START) {
      return text.substring(CHUNK_START.length);
    }
    return text;
  };
  /**
   * 请求完成调用一下
   * @param onSuccess
   */
  const onComplate = (onSuccess) => {
    if (lastText) {
      onSuccess(splitText(removeStartText(lastText)));
      lastText = "";
    }
  };
  /**
   * 请求完成调用一下(返回数据)
   * @param onSuccess
   */
  const onComplateReturn = () => {
    if (lastText) {
      let swap = lastText;
      lastText = "";
      return splitText(removeStartText(swap));
    }
  };
  return {
    getChunkText,
    onChunkReceived,
    onChunkReceivedReturn,
    onComplateReturn,
    onComplate
  };
};


// 这是核心的实现类似sse流式数据对话封装的组件
// chat-sse-client 组件
<script>
// import { ChunkRes } from "chunk-res";
import { ChunkRes } from "@/utils/chunk-res";
let requestTask;
let isReqAborted = false; //设置请求中止标志
let chunkRes;
export default {
  props: {},
  data() {
    return {
      lastText: ""
    };
  },
  methods: {
    stopChat() {
      console.log("requestTask", requestTask);
      //设置终止标志
      isReqAborted = true;
      requestTask.offChunkReceived(this.listener);
      requestTask.abort();
      this.$emit("onFinish");
      requestTask = null;
    },
    /**
     * 开始chat对话
     * @param body
     * @param url
     * @param headers
     */
    startChat({ body, url, headers = {} }) {
      isReqAborted = false;
      chunkRes = ChunkRes();
      requestTask = uni.request({
        url: url,
        method: "POST",
        header: {
          Accept: "text/event-stream",
          ...headers
        },
        data: body,
        enableChunked: true,
        responseType: "arraybuffer",
        enableHttp2: true,
        timeout: 1000 * 60 * 50,
        success: (res) => {
          //这个是流式数据的最后一条 会触发这个回调
          if (this.isReqAbortedHandle("忽略success响应")) return;
          const lastResTexts = chunkRes.onComplateReturn();
          console.log("[ success  lastResTexts] >", lastResTexts);
          lastResTexts?.forEach((item) => {
            this.$emit("onMessage", item);
          });
          // dosomething
        },
        fail: (error) => {
          if (this.isReqAbortedHandle("忽略错误")) return;
          console.log("[ error ] >", error);
          this.$emit("onError", error);
        },
        complete: (res) => {
          //完成了 success之后就走这个
          if (this.isReqAbortedHandle("忽略完成事件")) return;
          console.log("[ complete 请求完成 ] >", res);
          this.$emit("onFinish");
        }
      });
      requestTask.onChunkReceived(this.listener);
      this.$emit("onOpen");
    },
    isReqAbortedHandle(info) {
      if (isReqAborted) {
        console.log(`请求已中止,${info}`);
        return true;
      }
      return false;
    },
    listener(res) {
      if (this.isReqAbortedHandle("忽略分块数据")) return;
      const resTexts = chunkRes.onChunkReceivedReturn(res.data);
      // console.log("[ resTexts ] >", resTexts);
      resTexts?.forEach((item) => {
        this.$emit("onMessage", item);
      });
    }
  }
};
</script>


<template>
  <view />
</template>


//调用
<chat-sse-client
          ref="chatSSEClientRef"
          @onOpen="openChat"
          @onError="errorChat"
          @onFinish="finishChat"
          @onMessage="messageChat"></chat-sse-client>


回答关注问题邀请回答
收藏

1 个回答

  • 智能回答 智能回答 本次回答由AI生成
    2025-09-24
    有用
登录 后发表内容