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

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

相关推荐
hackeroink1 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者3 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人7 小时前
前端知识补充—CSS
前端·css