uniapp sse 客户端组件,支持v2、v3、安卓、ios、浏览器

前言

废话

这两周我经历了太多,

这个想法大概两周前就有了,本来当时想写一篇博客记录一下,但是女朋友跟我分手了...

这两周悲痛欲绝,从来没有这么哭过,这种滋味太难受了 ┭┮﹏┭┮

但是在我不懈的努力下,似乎有复合的倾向,趁着今晚高兴,将这个想法记录下来,希望可以帮到其他人!

正言

最近公司做一个跨平台的app,技术使用uniapp,用到chat,需要对接sse接口。

vue使用的是v3,想查一下是否有v3的sse解决方案,查了一圈发现两种办法,renderjs 和 原生插件,因为renderjs不兼容v3,最后想用原生插件解决。

结果去uniapp插件市场花20块买了个sse原生插件不兼容ios...

同事关于v3的chat代码涉及几千行,这咋办呢。

后来我在看一个uniapp组件库源码的时候给了我灵感,可以将renderjs用v2的语法封装到一个组件里,然后将事件提供给父组件,这样就可以兼容v3和v2了,详情请看下文,

renderjs

renderjs介绍

那么renderjs是什么呢?下面是官方的解释:

emmmm,简单点说就是可以在renderjs跑js的代码。

renderjs 语法

js 复制代码
<script>
export default {
  data() {
    return {
      hadnleData: {
        key: 1,
        text: "你好,世界!"
      }
    }
  },
  methods: {
    start() {
      this.hadnleData = Object.assign({}, this.hadnleData, {
        ...this.hadnleData,
        key: this.hadnleData.key + 1,
      });
    },
    receive(text) {
      console.log("处理好的结果:", text)
    }
  }
}
</script>

<script module="demo" lang="renderjs">
export default {
  methods: {
   hadnle(data) {
      let utf8Str = new TextEncoder().encode("Hello, World!");
      let base64Str = btoa(String.fromCharCode(...utf8Str));
      this.$ownerInstance.callMethod('receive', base64Str);
   },
  }
}
</script>

<template>
  <view
    :hadnleData="hadnleData"
    :change:hadnleData="demo.hadnle"
  >
    <button @click="start">start</button>
  </view>
</template>

上面是一个简单的字符串转base64代码,可以看到我们在renderjs使用了new TextEncoder(),它是一个web api,如果在app环境使用会报错:ReferenceError: TextEncoder is not defined,但是可以在renderjs正常运行。

我们可以在app环境用renderjs运行所有js库,有了以上的认识,我们只需要在renderjs使用@microsoft/fetch-event-source即可。

@microsoft/fetch-event-sources 是一个由微软开发的 npm 库,它提供了一个改进的 API,用于发起 Event Source 请求,也称为服务器发送事件(Server-Sent Events)。这个库基于 Fetch API,提供了比默认浏览器 EventSource API 更多的功能。

实现代码

核心组件

下面的代码非常简单,大家自己看吧

js 复制代码
<script>
export default {
  props: {},
  data() {
   return {
    stopCount: 0,
    renderjsData: {
     url: "",
     key: 0,
     body: "" 
    }
   }
  },
  methods: {
   stopChat() {
    this.stopCount += 1
   },
   /**
    * 开始chat对话
    * @param body
    * @param url
     * @param headers
    */
   startChat({ body, url, headers = {} }) {
    this.renderjsData = Object.assign({}, this.renderjsData, {
     key: this.renderjsData.key + 1,
     body: JSON.stringify(body),
     url: url,
        headers
    });
   },

   open() {
    this.$emit("onOpen")
   },
   message(msg) {
    this.$emit("onMessage", msg)
   },
   error(err) {
    this.$emit("onError", err)
   },
   finish() {
    this.$emit("onFinish")
   }
  },
}
</script>

<script module="chat" lang="renderjs">
import { fetchEventSource } from '@microsoft/fetch-event-source';

export default {
  data() {
   return {
    ctrl: null,
   }
  },
  methods: {
   /**
    * 停止生成
    */
   stopChatCore() {
    this.ctrl?.abort();
   },

   /**
    * 开始对话
    */
   startChatCore({ url, body, headers }) {
    if (!url) return;
    try {
     this.ctrl = new AbortController();
     fetchEventSource(
      url,
      {
       method: 'POST',
       openWidthHidden: true,
       signal: this.ctrl.signal,
       headers: {
        "Content-Type": "application/json",
        ...headers,
       },
       body: body,
       onopen: () => {
        this.$ownerInstance.callMethod('open');
       },
       onmessage: ({ data }) => {
        this.$ownerInstance.callMethod('message', data);
       },
       onerror: (err) => {
        this.$ownerInstance.callMethod('error', err);
       },
      }).then(() => {
       this.$ownerInstance.callMethod('finish');
     }).catch(err => {
      this.$ownerInstance.callMethod('error', err);
     })
    } catch (e) {
     console.log(e);
    }
   }
  }
}
</script>

<template>
  <view
   :renderjsData="renderjsData"
   :change:renderjsData="chat.startChatCore"
   :stopCount="stopCount"
   :change:stopCount="chat.stopChatCore"
  />
</template>

使用demo

js 复制代码
<template>
  <ChatSSEClient
      ref="chatSSEClientRef"
      @onOpen="openCore"
      @onError="errorCore"
      @onMessage="messageCore"
      @onFinish="finishCore"
  />
  <button @click="start">开始</button>
  <button @click="stop">停止</button>
  <template v-if="loading">
    <view>{{ openLoading ? "正在连接sse..." : '连接完成!' }}</view>
    <view>{{ loading ? "加载中..." : '' }}</view>
  </template>

  <view>
    {{ responseText }}
  </view>
</template>

<script setup>
import ChatSSEClient from "../../components/gao-ChatSSEClient.vue";
import { ref } from 'vue'

const chatSSEClientRef = ref(null);
const responseText = ref("");
const loading = ref(false);
const openLoading = ref(false);

const openCore = () => {
  openLoading.value = false;
  console.log("open sse");
}
const errorCore = (err) => {
  console.log("error sse:", err);
}
const messageCore = (msg) => {
  console.log("message sse:", msg);
  responseText.value += `${msg}\n`
}
const finishCore = () => {
  console.log("finish sse")
  loading.value = false;
}

const start = () => {
  if (loading.value) return;

  openLoading.value = true;
  loading.value = true;
  responseText.value = "";
  chatSSEClientRef.value.startChat({
    // 将它换成你的地址
    url: "",
    headers: {
      authorization: "",
    },
    body: {
      "messages":[
        { "role":"user", "content": "你好!" }
      ],
      "stream":true,
      "model":"deepseek-chat",
      "temperature":1,
      "presence_penalty":0,
      "frequency_penalty":0,
      "top_p":1
    },
  })
}
const stop = () => {
  chatSSEClientRef.value.stopChat()
  console.log("stop");
}
</script>

uniapp 插件

代码上传到了uniapp插件市场,有需要的同学可以去这上面下载,包含了完整demo项目。

sse 客户端组件,支持v2、v3、安卓、ios、浏览器 - DCloud 插件市场

最后,希望对大家有帮助,再见!

相关推荐
陈天伟教授10 小时前
人工智能训练师认证教程(2)Python os入门教程
前端·数据库·python
信看11 小时前
NMEA-GNSS-RTK 定位html小工具
前端·javascript·html
Tony Bai11 小时前
【API 设计之道】04 字段掩码模式:让前端决定后端返回什么
前端
苏打水com11 小时前
第十四篇:Day40-42 前端架构设计入门——从“功能实现”到“架构思维”(对标职场“大型项目架构”需求)
前端·架构
king王一帅11 小时前
流式渲染 Incremark、ant-design-x markdown、streammarkdown-vue 全流程方案对比
前端·javascript·人工智能
苏打水com11 小时前
第十八篇:Day52-54 前端跨端开发进阶——从“多端适配”到“跨端统一”(对标职场“全栈化”需求)
前端
Bigger12 小时前
后端拒写接口?前端硬核自救:纯前端实现静态资源下载全链路解析
前端·浏览器·vite
BD_Marathon12 小时前
【JavaWeb】路径问题_前端绝对路径问题
前端
whyfail12 小时前
Vue原理(暴力版)
前端·vue.js