从流中获取的数据格式如下
小程序调用SSE接口
javascript
复制代码
const requestTask = wx.request({
url: `xxx`, // 需要请求的接口地址
enableChunked: true, // enableChunked必须为true
method: "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文本(如上图)
javascript
复制代码
// 监听服务端返回的数据
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 聊天打字兼自动滚动效果
javascript
复制代码
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);
},
对话滚动到可视区域内
javascript
复制代码
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].height
const scrollContentHeight = res[1].height
if (scrollContentHeight > (scrollViewHeight - 200)) {
const scrollTop = scrollContentHeight - scrollViewHeight + 200
this.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
javascript
复制代码
<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
css
复制代码
.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
javascript
复制代码
data: {
isShowFinish: false,
scrollTop: '',
currentContent: '',
loading: false,
talkArr: []
},
getDataStream(data) {
let str = ''
let that = this
this.setData({
loading: true,
})
// 基础库为2.33.0
const requestTask = wx.request({
enableChunked: true, // 开启分片模式
url: `xxx`, // 需要请求的接口地址
enableChunked: true, // enableChunked必须为true
method: "GET",
responseType: "arraybuffer",
timeout: '120000',
success(res) {},
fail: function (error) {
// 请求失败的操作
console.error(error);
},
complete: function () {
// 请求完成的操作,无论成功或失败都会执行
that.handleRequestResolve(str)
let index = that.data.talkArr.length - 1
let 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].height
const scrollContentHeight = res[1].height
if (scrollContentHeight > (scrollViewHeight - 200)) {
const scrollTop = scrollContentHeight - scrollViewHeight + 200
this.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);
})
})
},