no message

cyl/master-im
jscyl13849007907 5 months ago
parent 206da6685f
commit b541ee05da

3644
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -10,7 +10,6 @@
"author": "",
"license": "ISC",
"dependencies": {
"html2canvas": "^1.4.1",
"@xkit-yx/im-store-v2": "^0.4.0",
"@xkit-yx/utils": "^0.5.6",
"dayjs": "^1.11.7",

@ -98,9 +98,6 @@
</template>
<script>
// #ifdef H5 || APP-PLUS
import html2canvas from "html2canvas";
// #endif
// #ifdef H5
import { saveAs } from "file-saver";
// #endif
@ -415,12 +412,6 @@ export default {
saveH5ImageCanvas() {
let that = this;
// #ifdef H5
const container = document.querySelector("#canvas-card"); //
html2canvas(container).then((canvas) => {
canvas.toBlob((blob) => {
saveAs(blob, that.agencyName + ".png");
}, "image/png");
});
// #endif
},
// APP

@ -96,9 +96,6 @@
</template>
<script>
// #ifdef H5 || APP-PLUS
import html2canvas from "html2canvas";
// #endif
// #ifdef H5
import { saveAs } from "file-saver";
// #endif
@ -401,12 +398,6 @@ export default {
saveH5ImageCanvas() {
let that = this;
// #ifdef H5
const container = document.querySelector("#canvas-card"); //
html2canvas(container).then((canvas) => {
canvas.toBlob((blob) => {
saveAs(blob, that.agencyName + ".png");
}, "image/png");
});
// #endif
},
// APP

@ -1,38 +1,28 @@
// uViewuni.scss
// uni.scss
// uni.scssscssmain.jsApp.vue
$u-main-color: #303133;
$u-content-color: #606266;
$u-tips-color: #909399;
$u-light-color: #c0c4cc;
$u-border-color: #e4e7ed;
$u-bg-color: #f3f4f6;
$u-type-primary: #2979ff;
$u-type-primary-light: #ecf5ff;
$u-type-primary-disabled: #a0cfff;
$u-type-primary-dark: #2b85e4;
$u-type-warning: #ff9900;
$u-type-warning-disabled: #fcbd71;
$u-type-warning-dark: #f29100;
$u-type-warning-light: #fdf6ec;
$u-type-success: #19be6b;
$u-type-success-disabled: #71d5a1;
$u-type-success-dark: #18b566;
$u-type-success-light: #dbf1e1;
$u-type-error: #fa3534;
$u-type-error-disabled: #fab6b6;
$u-type-error-dark: #dd6161;
$u-type-error-light: #fef0f0;
$u-type-info: #909399;
$u-type-info-disabled: #c8c9cc;
$u-type-info-dark: #82848a;
$u-type-info-light: #f4f4f5;
$u-form-item-height: 70rpx;
$u-form-item-border-color: #dcdfe6;

@ -1,13 +0,0 @@
## 1.5.02025-04-01
返回完整的message、小程序解析逻辑优化改为使用fetch-event-source的解析逻辑
## 1.4.12025-03-28
onOepn回调方法返回sse请求的response对象、及返回err错误信息
## 1.4.02025-03-24
解析微信小程序返回的数据
## 1.3.22025-03-16
1. 示例项目添加了sse server供调试。
2. 发生了错误接口会无限运行的问题解决现在发生了错误会调用stop方法停止。
## 1.3.12025-03-10
修复了get请求无法stop的bug
## 1.3.02025-03-06
插件修改为 uni_modules 模式

@ -1,126 +0,0 @@
<script>
export default {
props: {},
data() {
return {
stopCount: 0,
renderjsData: {
url: "",
key: 0,
body: "",
method: ""
}
}
},
methods: {
stopChat() {
this.stopCount += 1
},
/**
* 开始chat对话
*/
startChat(config) {
const { body } = config;
this.renderjsData = Object.assign({}, this.renderjsData, {
key: this.renderjsData.key + 1,
...config,
body: body ? JSON.stringify(body) : 0,
});
},
open(...args) {
this.$emit("onInnerOpen", ...args)
},
message(msg) {
this.$emit("onInnerMessage", msg)
},
error(...args) {
this.$emit("onInnerError", ...args)
this.stopChat();
},
finish() {
this.$emit("onInnerFinish")
}
},
}
</script>
<script module="chat" lang="renderjs">
import { fetchEventSource } from '../fetch-event-source';
export default {
data() {
return {
ctrl: null,
}
},
methods: {
objToJson(obj) {
const json = {};
for (const key in obj) {
const val = obj[key];
if (typeof val === "string" || typeof val === 'number' || typeof val === 'boolean') {
json[key] = val;
}
}
return json;
},
/**
* 停止生成
*/
stopChatCore() {
this.ctrl?.abort();
},
/**
* 开始对话
*/
startChatCore({ url, body, headers, method }) {
if (!url) return;
try {
this.ctrl = new AbortController();
fetchEventSource(
url,
{
readJson: true,
method,
openWhenHidden: true,
signal: this.ctrl.signal,
headers: {
"Content-Type": "application/json",
...headers,
},
body: body ? body : undefined,
onopen: (response) => {
this.$ownerInstance.callMethod('open', this.objToJson(response));
},
onmessage: (data) => {
this.$ownerInstance.callMethod('message', data);
},
onerror: (err) => {
console.log(err)
this.$ownerInstance.callMethod('error', JSON.stringify(err));
},
}).then(() => {
this.$ownerInstance.callMethod('finish');
}).catch(err => {
console.log(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>

@ -1,76 +0,0 @@
<script>
import {getLines, getMessages} from "../fetch-event-source/parse"
let requestTask;
export default {
props: {},
data() {
return {
onChunk: undefined
}
},
mounted() {
const onLine = getMessages(() => {}, () => {}, (line) => {
this.$emit("onInnerMessage", line)
})
this.onChunk = getLines(onLine);
},
methods: {
stopChat() {
requestTask.offChunkReceived(this.listener)
requestTask.abort();
},
decode(data) {
return decodeURIComponent(escape(String.fromCharCode(...data)));
},
/**
* 开始chat对话
* @param body
* @param url
* @param headers
* @param method
*/
startChat({ body, url, headers, method }) {
requestTask = uni.request({
url: url,
method,
header: {
Accept: 'text/event-stream',
...headers,
},
data: body,
enableChunked: true,
responseType: 'arraybuffer',
success: (res) => {
console.log('0000000 success',res,' url:',url)
},
fail: (error) => {
console.log('0000000 fail',error,' url:',url)
this.$emit("onInnerError", error)
},
complete: () => {
this.$emit("onInnerFinish")
},
});
requestTask.onChunkReceived(this.listener)
this.$emit("onInnerOpen", requestTask)
},
listener({ data }) {
const type = Object.prototype.toString.call(data);
if (type ==="[object Uint8Array]") {
} else if (data instanceof ArrayBuffer) {
data = new Uint8Array(data);
}
this.onChunk(data)
},
},
}
</script>
<template>
<view />
</template>

@ -1,89 +0,0 @@
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { getBytes, getLines, getMessages } from './parse';
export const EventStreamContentType = 'text/event-stream';
const DefaultRetryInterval = 1000;
const LastEventId = 'last-event-id';
export function fetchEventSource(input, _a) {
var { signal: inputSignal, headers: inputHeaders, onopen: inputOnOpen, onmessage, onclose, onerror, openWhenHidden, fetch: inputFetch } = _a, rest = __rest(_a, ["signal", "headers", "onopen", "onmessage", "onclose", "onerror", "openWhenHidden", "fetch"]);
return new Promise((resolve, reject) => {
const headers = Object.assign({}, inputHeaders);
if (!headers.accept) {
headers.accept = EventStreamContentType;
}
let curRequestController;
function onVisibilityChange() {
curRequestController.abort();
if (!document.hidden) {
create();
}
}
if (!openWhenHidden) {
document.addEventListener('visibilitychange', onVisibilityChange);
}
let retryInterval = DefaultRetryInterval;
let retryTimer = 0;
function dispose() {
document.removeEventListener('visibilitychange', onVisibilityChange);
window.clearTimeout(retryTimer);
curRequestController.abort();
}
inputSignal === null || inputSignal === void 0 ? void 0 : inputSignal.addEventListener('abort', () => {
dispose();
resolve();
});
const fetch = inputFetch !== null && inputFetch !== void 0 ? inputFetch : window.fetch;
const onopen = inputOnOpen !== null && inputOnOpen !== void 0 ? inputOnOpen : defaultOnOpen;
async function create() {
var _a;
curRequestController = new AbortController();
try {
const response = await fetch(input, Object.assign(Object.assign({}, rest), { headers, signal: curRequestController.signal }));
await onopen(response);
await getBytes(response.body, getLines(getMessages(id => {
if (id) {
headers[LastEventId] = id;
}
else {
delete headers[LastEventId];
}
}, retry => {
retryInterval = retry;
}, onmessage)));
onclose === null || onclose === void 0 ? void 0 : onclose();
dispose();
resolve();
}
catch (err) {
if (!curRequestController.signal.aborted) {
try {
const interval = (_a = onerror === null || onerror === void 0 ? void 0 : onerror(err)) !== null && _a !== void 0 ? _a : retryInterval;
window.clearTimeout(retryTimer);
retryTimer = window.setTimeout(create, interval);
}
catch (innerErr) {
dispose();
reject(innerErr);
}
}
}
}
create();
});
}
function defaultOnOpen(response) {
const contentType = response.headers.get('content-type');
if (!(contentType === null || contentType === void 0 ? void 0 : contentType.startsWith(EventStreamContentType))) {
throw new Error(`Expected content-type to be ${EventStreamContentType}, Actual: ${contentType}`);
}
}
//# sourceMappingURL=fetch.js.map

@ -1,2 +0,0 @@
export { fetchEventSource, EventStreamContentType } from './fetch';
//# sourceMappingURL=index.js.map

@ -1,128 +0,0 @@
export async function getBytes(stream, onChunk) {
const reader = stream.getReader();
let result;
while (!(result = await reader.read()).done) {
onChunk(result.value);
}
}
export function getLines(onLine) {
let buffer;
let position;
let fieldLength;
let discardTrailingNewline = false;
return function onChunk(arr) {
if (buffer === undefined) {
buffer = arr;
position = 0;
fieldLength = -1;
}
else {
buffer = concat(buffer, arr);
}
const bufLength = buffer.length;
let lineStart = 0;
while (position < bufLength) {
if (discardTrailingNewline) {
if (buffer[position] === 10) {
lineStart = ++position;
}
discardTrailingNewline = false;
}
let lineEnd = -1;
for (; position < bufLength && lineEnd === -1; ++position) {
switch (buffer[position]) {
case 58:
if (fieldLength === -1) {
fieldLength = position - lineStart;
}
break;
case 13:
discardTrailingNewline = true;
case 10:
lineEnd = position;
break;
}
}
if (lineEnd === -1) {
break;
}
onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
lineStart = position;
fieldLength = -1;
}
if (lineStart === bufLength) {
buffer = undefined;
}
else if (lineStart !== 0) {
buffer = buffer.subarray(lineStart);
position -= lineStart;
}
};
}
export function getMessages(onId, onRetry, onMessage) {
let message = newMessage();
let decoder;
// #ifdef MP-WEIXIN
decoder = {
decode(arraybuffer) {
return decodeURIComponent(escape(String.fromCharCode(...arraybuffer)))
}
};
// #endif
// #ifdef APP-PLUS || H5
decoder = new TextDecoder();
// #endif
return function onLine(line, fieldLength) {
if (line.length === 0) {
onMessage === null || onMessage === void 0 ? void 0 : onMessage(message);
message = newMessage();
}
else if (fieldLength > 0) {
const field = decoder.decode(line.subarray(0, fieldLength));
const valueOffset = fieldLength + (line[fieldLength + 1] === 32 ? 2 : 1);
const value = decoder.decode(line.subarray(valueOffset));
switch (field) {
case 'data':
message.data = message.data
? message.data + '\n' + value
: value;
break;
case 'event':
message.event = value;
break;
case 'id':
onId(message.id = value);
break;
case 'retry':
const retry = parseInt(value, 10);
if (!isNaN(retry)) {
onRetry(message.retry = retry);
}
break;
default:
const msg = decoder.decode(line, { stream: true });
message.data = msg
onMessage(message);
break;
}
}
};
}
function concat(a, b) {
const res = new Uint8Array(a.length + b.length);
res.set(a);
res.set(b, a.length);
return res;
}
function newMessage() {
return {
data: '',
event: '',
id: '',
retry: undefined,
};
}
//# sourceMappingURL=parse.js.map

@ -1,58 +0,0 @@
<template>
<!-- #ifdef MP-WEIXIN-->
<ChatWxApplet ref="chatRef" @onInnerOpen="open" @onInnerError="error" @onInnerMessage="message" @onInnerFinish="finish" />
<!-- #endif-->
<!-- #ifdef APP-PLUS || H5-->
<ChatAppAndWeb ref="chatRef" @onInnerOpen="open" @onInnerError="error" @onInnerMessage="message" @onInnerFinish="finish" />
<!-- #endif-->
</template>
<script>
// #ifdef MP-WEIXIN
import ChatWxApplet from "./children/ChatWxApplet.vue";
// #endif
// #ifdef APP-PLUS || H5
import ChatAppAndWeb from "./children/ChatAppAndWeb.vue";
// #endif
export default {
components: {
// #ifdef MP-WEIXIN
ChatWxApplet,
// #endif
// #ifdef APP-PLUS || H5
ChatAppAndWeb,
// #endif
},
methods: {
startChat(config) {
config["method"] = (config["method"] || "post").toUpperCase();
config["headers"] = config["headers"] || {};
console.log("this.$refs['chatRef']", this.$refs["chatRef"]);
this.$refs["chatRef"].startChat(config);
},
stopChat(...args) {
console.log("this.$refs['chatRef']stopChat", this.$refs["chatRef"]);
this.$refs["chatRef"].stopChat(...args);
},
open(...args) {
this.$emit("onOpen", ...args);
},
message(msg) {
this.$emit("onMessage", msg);
},
error(...args) {
this.$emit("onError", ...args);
},
finish() {
this.$emit("onFinish");
},
},
};
</script>

@ -1,45 +0,0 @@
// 检索字符,并且加上该字符的长度
const indexOfLen = (str) => {
const startIndex = str.indexOf(str);
if (startIndex !== -1) {
return startIndex + str.length
}
return -1;
}
/**
* 解析SSE数据
* sse返回的数据可能会有多个消息相连这里处理字符串返回数组
* @param data sse字符串数据
* @returns {*} 处理过后的数组
*/
export const parseSseData = (data) => {
try {
let lines = data.split("\n");
lines = lines.map(v => {
if (!v) return null;
let startInd = -1;
for (const ind of [indexOfLen("data: "), v.indexOf("{")]) {
if (ind !== -1) {
startInd = ind;
break;
}
}
if (startInd === -1) {
return v;
} else {
return v.substring(startInd, v.length).trim();
}
}).filter(Boolean);
return lines;
} catch (e) {
console.warn("解析失败:", e);
return data;
}
}

@ -1,65 +0,0 @@
{
"id": "gao-ChatSSEClient",
"name": "sse 客户端组件支持兼容v2、v3、安卓、ios、浏览器、微信小程序",
"displayName": "sse 客户端组件支持兼容v2、v3、安卓、ios、浏览器、微信小程序",
"version": "1.5.0",
"description": "sse 客户端组件支持兼容v2、v3、安卓、ios、浏览器、微信小程序",
"repository": "https://github.com/gaozhenqiang/uniapp-chatSSEClient",
"keywords": [
"sse",
"chat",
"微信小程序sse",
"流式接口",
"流式输出"
],
"dcloudext": {
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"contact": {
"qq": "1933669775"
},
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
}
},
"uni_modules": {
"platforms": {
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-harmony": "u",
"app-nvue": "u",
"app-uvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y"
}
}
}
}
}

@ -1,125 +0,0 @@
# sse 客户端组件支持v2、v3、安卓、ios、浏览器、微信小程序
## 使用说明
### 导入组件
点击右上角 `下载插件并导入HBuilderX`
uniapp插件地址https://ext.dcloud.net.cn/plugin?id=20971
或者你可以参考我的示例
### 示例代码
```javascript
<template>
<button @click="start">开始</button>
<button @click="stop">停止</button>
<template v-if="loading">
<view>{{ openLoading ? "正在连接sse..." : '连接完成!' }}</view>
<view>{{ loading ? "加载中..." : '' }}</view>
</template>
<view>
{{ responseText }}
</view>
<gao-ChatSSEClient
ref="chatSSEClientRef"
@onOpen="openCore"
@onError="errorCore"
@onMessage="messageCore"
@onFinish="finishCore"
/>
</template>
<script setup>
import { ref } from 'vue'
const chatSSEClientRef = ref(null);
const responseText = ref("");
const loading = ref(false);
const openLoading = ref(false);
const openCore = (response) => {
openLoading.value = false;
console.log("open sse", response);
}
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({
/**
* 将它换成你的地址
* 注意:
* 如果使用 sse-server.js 要在手机端使用的话请确保你的手机和电脑处在一个局域网下并且是正常的ip地址
*/
url: import.meta.env.VITE_CHAT_URL || 'http://localhost:3000/sse',
// 请求头
headers: {
Authorization: import.meta.env.VITE_CHAT_AUTHORIZATION,
},
// 默认为 post
method: 'post',
body: {
"stream":true,
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "你是来自艺咖科技的数字员工,你的名字叫小咖。"}]
}
})
}
const stop = () => {
chatSSEClientRef.value.stopChat()
console.log("stop");
}
</script>
```
# 温馨提示
示例项目根目录的`sse-server.js`文件提供了一个简单的sse测试服务使用 `node sse-server.js`运行
**提出问题之前请先确保你的接口没有问题**
---
**请仔细阅读我提供的示例代码。**
**如果你的程序有问题请先下载我提供的示例项目调试!**
---
如果想了解原理请看我掘金的文章: [点击前往](https://juejin.cn/post/7435632766375084082)
本插件依赖于 `fetch-event-source`将编辑后的js集成因为我修改了原来库解析的逻辑使其更适用于中国宝宝体质。
---
**如果这个组件解决了你的问题,麻烦去[github](https://github.com/gaozhenqiang/uniapp-chatSSEClient/) 帮我点个赞,谢谢大家**
有新需求或者bug可以在github上提issues或者加我q `1933669775`
# 常见问题
## ios报错TypeError: Load failed
后端接口处理一下跨域即可解决。
Loading…
Cancel
Save