从流中获取的数据格式如下
小程序调用SSE接口
const requestTask = wx.request({url: `xxx`, // 需要请求的接口地址enableChunked: true, // enableChunked必须为truemethod: "GET",timeout: '120000',success(res) {console.log(res.data)},fail: function (error) {// 请求失败的操作console.error(error);},complete: function () {// 请求完成的操作,无论成功或失败都会执行console.log('请求完成', str);}})// 监听服务端返回的数据requestTask.onChunkReceived(res => {console.log( res, res.data);})
我这边接收到的数据类型为Uint8Array,需要处理成text文本(如上图)
// 监听服务端返回的数据requestTask.onChunkReceived(res => {console.log( res, res.data);// Uint8Array转为text格式let arrayBuffer = res.data;let decoder = new TextDecoder('utf-8');let text = decoder.decode(arrayBuffer);//正则匹配上所有event:data后面的文字const eventRegex = /event:data\ndata:"data:(.*?)"/g;const eventRegexErr = /event:600\ndata:"(.*?)"/g;let matches = [];let match;if (text.indexOf('600') != -1) {//如果获取响应失败while ((match = eventRegexErr.exec(text)) !== null) {wx.showToast({title: match[1],})matches.push(match[1]);}str = str + matches.join('')} else {//如果获取响应成功while ((match = eventRegex.exec(text)) !== null) {matches.push(match[1]);}//处理成字符串str = str + matches.join('')console.log(text, str);}})
使对话有打字机效果
参考自:小程序实现 ChatGPT 聊天打字兼自动滚动效果
handleRequestResolve(result) {this.setData({currentContent: ''})const contentCharArr = result.trim().split("")this.showText(0, contentCharArr);},showText(key = 0, value) {/* 所有内容展示完成 */if (key >= value.length) {// wx.vibrateShort()//判断字是否展示完this.setData({isShowFinish: true})return;}/* 渲染回话内容 */this.setData({currentContent: this.data.currentContent + value[key],})setTimeout(() => {/* 递归渲染内容 */this.showText(key + 1, value);}, 50);},
对话滚动到可视区域内
handleScollTop() {return new Promise((resolve) => {const query = wx.createSelectorQuery()query.select('.page-content').boundingClientRect()query.select('.scroll-view-content').boundingClientRect()query.exec((res) => {const scrollViewHeight = res[0].heightconst scrollContentHeight = res[1].heightif (scrollContentHeight > (scrollViewHeight - 200)) {const scrollTop = scrollContentHeight - scrollViewHeight + 200this.setData({scrollTop}, () => {resolve()})} else {resolve()}})})},showText(key = 0, value) {/* 所有内容展示完成 */if (key >= value.length) {// wx.vibrateShort()this.setData({isShowFinish: true})return;}/* 渲染回话内容 */this.setData({currentContent: this.data.currentContent + value[key],}, () => {this.handleScollTop().then(() => {setTimeout(() => {this.showText(key + 1, value);}, 20);})})},
完整代码
.wxml
<scroll-view scroll-y scroll-top="{{scrollTop}}" wx:else class="page-content {{isFirst ? '' : 'page-content-bg'}}"><view class="scroll-view-content"><view wx:for="{{talkArr}}" wx:key="index" class="talk-box1"><view class="talk-box-question" wx:if="{{item.isAnswer=='0'}}"><view class="left"><text class="left-content">{{item.content}}</text></view><image class="right" src="../images/user-icon.png" mode="aspectFill" /></view><view class="talk-box-reply" wx:else><image class="left" src="../images/ai-icon.png" mode="aspectFill" /><view class="right"><view class="right-content"><view wx:if="{{(index!=talkArr.length-1)}}">{{item.content}}</view><view wx:else><view wx:if="{{loading}}"><image class="loading" src="../images/loading-1.png" mode="aspectFill" /></view><view wx:else>{{currentContent}}</view></view></view></view></view></view></view></scroll-view>
.wxss
.page-content {width: 100%;margin-top: 48rpx;padding-top: 150rpx;
}.page-content-bg {background: #F5F6F7;height: 75%;padding-bottom: 280rpx;overflow: scroll;padding-top: 0;
}.scroll-view-content {padding-top: 50rpx;
}.talk-box {display: flex;
}.talk-box1 {width: 90%;margin: 0 auto;
}.talk-box .left {width: 80rpx;height: 80rpx;
}.talk-box .right {margin-left: 30rpx;flex: 1;}.talk-item {height: 92rpx;background: #F6FFF9;border-radius: 0rpx 20rpx 20rpx 20rpx;font-family: PingFang SC, PingFang SC;font-weight: 500;font-size: 28rpx;color: rgba(51, 51, 51, 0.9);text-align: left;display: flex;align-items: center;padding: 0 38rpx;
}.talk-box-question,
.talk-box-reply {width: 100%;display: flex;margin-bottom: 32rpx;
}.talk-box-question .left {flex: 1;display: flex;align-items: center;justify-content: flex-end;
}.left-content {background: linear-gradient(273deg, #44BE35 0%, #6ECB63 100%);box-shadow: 0rpx 2rpx 8rpx 0rpx rgba(0, 0, 0, 0.05);border-radius: 24rpx 0rpx 24rpx 24rpx;padding: 24rpx;font-family: PingFang SC, PingFang SC;font-weight: 400;font-size: 28rpx;color: #FFFFFF;line-height: 44rpx;text-align: left;
}.talk-box-question .right {margin-left: 30rpx;width: 80rpx;height: 80rpx;
}.talk-box-reply .left {width: 80rpx;height: 80rpx;
}.talk-box-reply .right {margin-left: 30rpx;flex: 1;display: flex;align-items: center;justify-content: flex-start;
}.right-content {background: #FFFFFF;box-shadow: 0rpx 2rpx 8rpx 0rpx rgba(0, 0, 0, 0.05);border-radius: 0rpx 24rpx 24rpx 24rpx;border: 2rpx solid #6ECB63;padding: 24rpx;font-family: PingFang SC, PingFang SC;font-weight: 400;font-size: 28rpx;color: rgba(0, 0, 0, 0.9);line-height: 46rpx;text-align: left;
}
.js
data: {isShowFinish: false,scrollTop: '',currentContent: '',loading: false,talkArr: []},getDataStream(data) {let str = ''let that = thisthis.setData({loading: true,})// 基础库为2.33.0const requestTask = wx.request({enableChunked: true, // 开启分片模式url: `xxx`, // 需要请求的接口地址enableChunked: true, // enableChunked必须为truemethod: "GET",responseType: "arraybuffer",timeout: '120000',success(res) {},fail: function (error) {// 请求失败的操作console.error(error);},complete: function () {// 请求完成的操作,无论成功或失败都会执行that.handleRequestResolve(str)let index = that.data.talkArr.length - 1let answerContent = `talkArr[${index}].content`that.setData({[answerContent]: str,loading: false})}})// 监听服务端返回的数据requestTask.onChunkReceived(res => {// Uint8Array转为text格式let arrayBuffer = res.data;let decoder = new TextDecoder('utf-8');let text = decoder.decode(arrayBuffer);//正则匹配上所有event:data后面的文字const eventRegex = /event:data\ndata:"data:(.*?)"/g;const eventRegexErr = /event:600\ndata:"(.*?)"/g;let matches = [];let match;if (text.indexOf('600') != -1) { //如果获取响应失败while ((match = eventRegexErr.exec(text)) !== null) {wx.showToast({title: match[1],icon: 'none'})matches.push(match[1]);}str = str + matches.join('')} else { //如果获取响应成功while ((match = eventRegex.exec(text)) !== null) {matches.push(match[1]);}//处理成字符串str = str + matches.join('')}})requestTask.offChunkReceived(res => {})},handleScollTop() {return new Promise((resolve) => {const query = wx.createSelectorQuery()query.select('.page-content').boundingClientRect()query.select('.scroll-view-content').boundingClientRect()query.exec((res) => {const scrollViewHeight = res[0].heightconst scrollContentHeight = res[1].heightif (scrollContentHeight > (scrollViewHeight - 200)) {const scrollTop = scrollContentHeight - scrollViewHeight + 200this.setData({scrollTop}, () => {resolve()})} else {resolve()}})})},handleRequestResolve(result) {this.setData({currentContent: ''})const contentCharArr = result.trim().split("")this.setData({isShowFinish: false})this.showText(0, contentCharArr);},showText(key = 0, value) {/* 所有内容展示完成 */if (key >= value.length) {// wx.vibrateShort()this.setData({isShowFinish: true})return;}/* 渲染回话内容 */this.setData({currentContent: this.data.currentContent + value[key],}, () => {this.handleScollTop().then(() => {setTimeout(() => {this.showText(key + 1, value);}, 20);})})},