Uniapp仿ChatGPT Stream流式输出(非Websocket)-uniapp+see接收推送示例
前言#
最近写一个chagpt小程序,流式输出可以使用websocket也可以使用stream来实现,这里就不折腾websocket的了,我发现uniapp实现流式输出的方式挺多的,主要是有些小程序还不兼容,花了点时间研究了一下。
EventSource#
EventSource也称为SSE(Server-Sent Events),是服务器推送的一个网络事件接口,一个EventSource会对http服务开启一个持久化链接,它发送的事件格式是‘text/stream’,开启EventSource事件后,它会一直保持开启状态,直到被要求关闭
后端php,原生实现个流式输出即可
/** * @function 与客户端server send event通信方式 * @param $callback callable 回调,若返回数组代表要输出json,返回null代表本次循环不进行输出 * @param $millisecond int 数据分发间隔,单位:毫秒 * @return string * @other void */ function sse($callback, $millisecond = 1000) { set_time_limit(0); ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', false); while (@ob_end_flush()) {} header('Content-Type: text/event-stream; Charset=UTF-8'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); header('X-Accel-Buffering: no'); header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Credentials: true"); header('Access-Control-Allow-Methods: *'); header('Access-Control-Allow-Headers: *'); ob_start(); while (true) { $callback_res = $callback(); if($callback_res !== null) { $data = json_encode($callback_res, 320); echo "data:{$data}\n\n"; } ob_flush(); flush(); usleep($millisecond * 1000); } } 调用 示例 public function test_flush(){ sse(function() { $one=Db::name('vpnlink')->where('create_time','>',time()-300)->find(); if($one) { return $one; } return null; }, 1000); }
uniapp前端如何连接
XHR方式#
uniapp自带的uni.request()不支持stream方式,所以可以使用XHR来实现,但是小程序又不支持XHR
const requestChat = (msg)=>{
// 在页面中创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest()
// 设置 XMLHttpRequest 对象
xhr.open('GET', 'http://localhost:8090/chat?msg=' + msg)
xhr.responseType = 'text'
xhr.timeout = 0
const msgNum = messageList.value.length;
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.LOADING) {
// 处理 HTTP 数据块
console.log(xhr.responseText)
}
}
xhr.send()
}
EventSource方式#
uniapp中也可以直接使用EventSource
来实现流式输出,可惜小程序也不支持(难顶)
const chatStream = () => {
const es = new EventSource("http://localhost:8090/sse")
es.onmessage = function(event) {
console.log(event)
if (event.lastEventId=="[DONE]") {
console.log(event.data);
return es.close()
}
console.log(event.data);
};
}
Transfer-Encoding Chunk#
uniapp中使用uni.request()
,配置enableChunked: true
即可--推荐
const requestTask = uni.request({
url: 'http://localhost:8090/sse',
timeout: 15000,
responseType: 'text',
method: 'GET',
enableChunked: true, //配置这里
data: {},
success: response => {
console.log(response)
},
fail: error => {}
})
requestTask.onHeadersReceived(function(res) {
console.log(res.header);
});
// 这里监听消息
requestTask.onChunkReceived(function(res) {
let decoder = new TextDecoder('utf-8');
let text = decoder.decode(new Uint8Array(res.data));
console.log(text)
})
由于接收到的数据是
arraybuffer
,所以我们要转换为字符串类型,但是编码会出现一点问题,上面这段代码使用TextDecoder
来转换编码,但是小程序又不支持TextDecoder
。。。
再换一种转换方式
requestTask.onChunkReceived(function(res) {
const uint8Array = new Uint8Array(res.data);
let text = String.fromCharCode.apply(null, uint8Array);
text = decodeURIComponent(escape(text));
console.log(text);
})
这样就可以在小程序中把Arraybuffer
转换为字符串了
还有许多方式,比如调用第三方库来代替
TextDecoder
,我就不去尝试了
注意:
1.enableChunked: true
流式响应开关,会自动在header
中加入transfer-encoding chunked
2.arraybuffer
转字符串问题,有TextDecoder
就很好处理,没有也可以参照我上面的示例。
版权声明:
作者:admin
链接:http://blog.mryxh.cn/3518.html
文章版权归作者所有,未经允许请勿转载。
THE END