微信小程序通过SSE接入AI

4,069 阅读3分钟

前言

近期忙于将公司内部的AI产品接入到小程序中

调研

发现通过调用内部AI接口发现接口返回的格式是event stream的形式,而小程序则是通过开启wx.request中的enableChunked的方式来实现
关于wx.request的官方说明

image.png

关于Event Stream(SSE)

相比于SSE之前更熟悉Websocket,那么两种有什么区别呢?

  1. SSE本质上还是属于http协议,而Websocket并不属于http协议
  2. SSE是单向通信,只能由服务端向客户端发送请求
  3. 以及SSE是通过保持一个持久的 HTTP 连接来实现推送,属于长连接,而Websocket是全双工的。

在微信小程序中如何处理SSE

1. 在小程序中相应的API

前文已经提到,核心在于配置wx.request中的enableChunked参数

 const requestTask = wx.request({
      url,
      method: 'POST',
      data: params,
      enableChunked: true,
      header: {
        token,
      }
})

微信官方并且提供了监听取消的API

   //监听的api
   requestTask.onChunkReceived((res) => {
        //xxxxxxxxxx

   });
    //取消的api
    requestTask.offChunkReceived()

2.关于如何处理onChunkReceived回调的结果

可以看到返回的结果为Uint8Array,而在小程序中由于不支持TextEncoder/TextDecoder对象 我的解决方式

  1. 先将unit8Array转化为十六进制
  2. 将十六进制转为中文字符串
    其中需要引入encoding.js和encoding-indexes.js两个文件
    这两个文件的来源为text-encoding下lib文件下的两个文件。
import encoding from './encoding.js'
  decodeUint8Array(uint8Array) {
    const data16 = this.buf2hex(uint8Array);
    const requestData = this.hexToStr(data16)
    return requestData
  },
  buf2hex(arrayBuffer) {
    return Array.prototype.map.call(new Uint8Array(arrayBuffer), x => ('00' + x.toString(16)).slice(-2)).join('');
  },
  /**
   * 十六进制字符串转中文
   * @param {String} hex 为十六进制字符串
   * @return {String} 包含中文的字符串
   */
  hexToStr(hex) {
    // 去掉字符串首尾空格
    let trimedStr = hex.trim()
    // 判断trimedStr前两个字符是否为0x,如果是则截取从第三个字符及后面所有,否则返回全部字符
    let rawStr = trimedStr.substr(0, 2).toLowerCase() === "0x" ? trimedStr.substr(2) : trimedStr
    // 得到rawStr的长度
    let len = rawStr.length
    // 如果长度不能被2整除,那么传入的十六进制值有误,返回空字符
    if (len % 2 !== 0) {
      return ""
    }
    let curCharCode // 接收每次循环得到的字符
    let resultStr = [] // 存转换后的十进制值数组
    for (let i = 0; i < len; i = i + 2) {
      curCharCode = parseInt(rawStr.substr(i, 2), 16)
      resultStr.push(curCharCode)
    }
    // encoding为空时默认为utf-8
    let bytesView = new Uint8Array(resultStr) // 8 位无符号整数值的类型化数组
    let str = new encoding.TextDecoder().decode(bytesView)
    return str
  },

最终调用的伪代码,其中的chunkText为处理好的字符串。

    requestTask.onChunkReceived((res) => {
      let chunkText = this.decodeUint8Array(res.data);
       

    });

tips:不知道为什么在微信开发者工具的控制台中,即使接口响应成功还是看不到SSE的返回,如下图,只能通过console的方式来查看返回的数据。

image.png

如何实现打字机效果

在我们熟悉的大多AI产品中,它们的回答都是逐字出现的,这种效果我们也称为打字机效果 打字机的核心代码

startTyping(text) {
    const strLength = text.length;
    let currentIndex = 0;
    const intervalTime = 100;
    let msg = '';
    let typingInterval = setInterval(() => {
      if (currentIndex < strLength) {
         //获取当前索引对应的字
        let currentChar = text.charAt(currentIndex);
        msg += currentChar
       //将msg赋值给你需要的数据项即可
       this.setData({
         test:msg
       });
        currentIndex++;
      } else {
        clearInterval(typingInterval);
      }
    }, intervalTime);
  },

最终效果

文章.gif