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 插件市场

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

相关推荐
裴嘉靖1 分钟前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw2824264 分钟前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽1 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁1 小时前
Angular【router路由】
前端·javascript·angular.js
时间的情敌1 小时前
Vite 大型项目优化方案
vue.js
brzhang1 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构
西洼工作室2 小时前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
广州华水科技2 小时前
北斗形变监测传感器在水库安全中的应用及技术优势分析
前端
开发者如是说2 小时前
Compose 开发桌面程序的一些问题
前端·架构
旺代2 小时前
Token 存储与安全防护
前端