diff --git a/changelog.md b/changelog.md
index 96c3dcca48d67232eb83431042dc6b34259090fe..755772b59fe168ce215226025090b6fa1bede3fa 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,7 @@
+## 1.2.2(2023-06-16)
+- 修复 部分情况下,通过 uni-ai 计费网关发起调用,“服务商接口抛出错误”会导致界面卡住的问题
+- 修复 部分情况下,客户端关闭广告事件触发多次的问题
+- 修复 AI返回表格格式的文档,没有边框线的问题
## 1.2.1(2023-06-14)
- 更新 大语言模型provider的值默认为:minimax
## 1.2.0(2023-06-14)
diff --git a/components/uni-ai-msg/uni-ai-msg.scss b/components/uni-ai-msg/uni-ai-msg.scss
index 229b783ee38fb290dcd59dfb0a30a097e6640255..4449ff8aa61b1fc0412b738111eb561429eb6231 100644
--- a/components/uni-ai-msg/uni-ai-msg.scss
+++ b/components/uni-ai-msg/uni-ai-msg.scss
@@ -9,6 +9,17 @@
margin: 5px 0;
overflow: auto;
}
+
+.rich-text-box ::v-deep table {
+ border-spacing: 0;
+}
+
+.rich-text-box ::v-deep th,
+.rich-text-box ::v-deep td
+{
+ border: 1px solid #666;
+ padding:3px 5px;
+}
.cursor {
display: none;
diff --git a/components/uni-ai-msg/uni-ai-msg.vue b/components/uni-ai-msg/uni-ai-msg.vue
index bead518339bf3e2a91616601b1f06bca54dbc8da..2b524c61e314ae9d3b1296e7a839d7d54610849b 100644
--- a/components/uni-ai-msg/uni-ai-msg.vue
+++ b/components/uni-ai-msg/uni-ai-msg.vue
@@ -11,17 +11,7 @@
-
- {{msgContent}}
-
-
-
-
- 默认不启用广告组件(被注释),如需使用,请"去掉注释"(“重新运行”后生效)
- 位置:/components/uni-ai-msg/uni-ai-msg.vue 第28行,或全局搜索 uni-ad-rewarded-video
-
-
-
+ {{msgContent}}
@@ -93,7 +99,11 @@
// 使聊天窗口滚动到指定元素id的值
scrollIntoView: "",
// 消息列表数据
- msgList: [],
+ msgList: [],
+ // 通讯请求状态
+ requestState:0,//0发送中 100发送成功 -100发送失败
+ // 本地对话是否因积分不足而终止
+ insufficientScore:false,
// 输入框的消息内容
content: "",
// 记录流式响应次数
@@ -130,15 +140,6 @@
NODE_ENV() {
return process.env.NODE_ENV
},
- //最后一条消息的状态
- lastMsgState() {
- let mLength = this.msgList.length
- if (mLength) {
- return this.msgList[mLength - 1].state
- } else {
- return false
- }
- },
footBoxPaddingBottom() {
return (this.keyboardHeight || 10) + 'px'
}
@@ -155,6 +156,12 @@
},
// 深度监听msgList变化
deep: true
+ },
+ insufficientScore(insufficientScore){
+ uni.setStorage({
+ "key": "uni-ai-chat-insufficientScore",
+ "data": insufficientScore
+ })
},
llmModel(llmModel) {
let title = 'uni-ai-chat'
@@ -215,14 +222,17 @@
this.msgList = uni.getStorageSync('uni-ai-msg') || [];
// 获得之前设置的llmModel
- this.llmModel = uni.getStorageSync('uni-ai-chat-llmModel')
+ this.llmModel = uni.getStorageSync('uni-ai-chat-llmModel')
+
+ // 获得之前设置的uni-ai-chat-insufficientScore
+ this.insufficientScore = uni.getStorageSync('uni-ai-chat-insufficientScore') || false
// 如果上一次对话中 最后一条消息ai未回复。则一启动就自动重发。
let length = this.msgList.length
if (length) {
let lastMsg = this.msgList[length - 1]
if (!lastMsg.isAi) {
- this.retriesSendMsg()
+ this.send()
}
}
@@ -250,7 +260,7 @@
e.preventDefault()
// 执行发送
setTimeout(() => {
- this.beforeSendMsg();
+ this.beforeSend();
}, 300)
}
};
@@ -338,20 +348,6 @@
}
this.msgList.splice(length - 1, 1, lastMsg)
},
- setSummarize(summarizeData) {
- // console.log('setSummarize');
- let length = this.msgList.length;
- let index = length - 2
- // 从 v1.1.0起,总结可能是总结倒数第二条之前的内容
- if (index < 0) {
- index = 0
- }
-
- let msg = this.msgList[index]
- msg.summarize = summarizeData
- this.msgList.splice(index, 1, msg)
- // console.log('setSummarize this.msgList',this.msgList);
- },
// 广告关闭事件
onAdClose(e) {
console.log('onAdClose e.detail.isEnded', e.detail.isEnded);
@@ -383,12 +379,13 @@
clearInterval(myIntive)
// 隐藏加载提示
uni.hideLoading()
- if (score > 0) {
+ if (score > 0) {
+ this.insufficientScore = false
// 移除最后一条消息
this.msgList.pop()
this.$nextTick(() => {
// 重发消息
- this.retriesSendMsg()
+ this.send()
uni.showToast({
title: '积分余额:' + score,
icon: 'none'
@@ -399,34 +396,23 @@
}, 2000);
}
},
- async retriesSendMsg() {
- // 检查是否开通uni-push;决定是否启用enableStream
- await this.checkIsOpenPush()
- // 更新最后一条消息的状态为0 表示消息正在发送中
- this.updateLastMsg({
- state: 0
- })
- // 发送消息
- this.send()
- },
// 换一个答案
async changeAnswer() {
// 如果问题还在回答中需要先关闭
if (this.sseIndex) {
this.closeSseChannel()
}
-
//删除旧的回答
this.msgList.pop()
-
this.updateLastMsg({
// 防止 偶发答案涉及敏感,重复回答时。提问内容 被卡掉无法重新问
illegal: false,
- // 多设备登录时其他设备看广告后点击重新回答,insufficientScore应当设置为 false
- insufficientScore:false
- })
+ })
+ // 多设备登录时其他设备看广告后点击重新回答,insufficientScore应当设置为 false
+ this.insufficientScore = false
this.send()
- },
+ },
+ //当消息涉及敏感
removeMsg(index) {
// 如果问题还在回答中需要先关闭
if (this.sseIndex) {
@@ -438,7 +424,7 @@
}
this.msgList.splice(index,2)
},
- async beforeSendMsg() {
+ async beforeSend() {
if (this.inputBoxDisabled) {
return uni.showToast({
title: 'ai正在回复中不能发送',
@@ -511,8 +497,6 @@
isAi: false,
// 消息内容
content: this.content,
- // 消息状态为0,表示正在发送中
- state: 0,
// 消息创建时间
create_time: Date.now()
})
@@ -524,17 +508,18 @@
this.content = ''
})
this.send() // 发送消息
- },
+ },
async send() {
- // 流式响应和云对象的请求状态
- let state = {
- sse:0,
- co:0
- }
-
+ // 请求状态归零
+ this.requestState = 0
+ // 防止重复发起,关闭之前的
+ uniCoTaskList.clear()
+ // 清除旧的afterChatCompletion(如果存在)
+ if(this.afterChatCompletion && this.afterChatCompletion.clear) this.afterChatCompletion.clear()
+
let messages = []
// 复制一份,消息列表数据
- let msgs = JSON.parse(JSON.stringify(this.msgList)).filter(i => i.isDelete !== true)
+ let msgs = JSON.parse(JSON.stringify(this.msgList))
// 带总结的消息 index
let findIndex = [...msgs].reverse().findIndex(item => item.summarize)
// console.log('findIndex', findIndex)
@@ -549,10 +534,8 @@
// 如果未总结过就直接从末尾拿10条
msgs = msgs.splice(-10)
}
-
// 过滤涉敏问题
msgs = msgs.filter(msg => !msg.illegal)
-
// 根据数据内容设置角色
messages = msgs.map(item => {
// 角色默认为用户
@@ -574,12 +557,13 @@
await this.checkIsOpenPush()
// console.log('this.enableStream',this.enableStream);
+ // 流式响应和云对象的请求结束回调函数
+ let sseEnd,requestEnd;
// 判断是否开启了流式响应模式
if (this.enableStream) {
// 创建消息通道
sseChannel = new uniCloud.SSEChannel()
// console.log('sseChannel',sseChannel);
-
// 监听message事件
sseChannel.on('message', (message) => {
// console.log('on message', message);
@@ -592,13 +576,12 @@
content: message,
create_time: Date.now()
})
- this.showLastMsg()
} else {
this.updateLastMsg(lastMsg => {
lastMsg.content += message
})
- this.showLastMsg()
- }
+ }
+ this.showLastMsg()
// 让流式响应计数值递增
this.sseIndex++
})
@@ -606,21 +589,85 @@
// 监听end事件,如果云端执行end时传了message,会在客户端end事件内收到传递的消息
sseChannel.on('end', (e) => {
console.log('sse 结束',e)
- state.sse = 1
- if(state.sse === 1 && state.co === 1){
- // console.error('通过 sse end 结束',state);
+ if(e && typeof e == 'object' && e.errCode){
+ let setLastAiMsgContent = (content)=>{
+ // console.log(content);
+ // 如果最后一项不是ai的就添加,否则就执行更新最后一条消息
+ if (this.sseIndex === 0) {
+ this.msgList.push({
+ isAi: true,
+ content,
+ create_time: Date.now()
+ })
+ } else {
+ this.updateLastMsg(lastMsg => {
+ lastMsg.content += content
+ })
+ }
+ this.showLastMsg()
+ }
+ if(e.errCode == 60004){
+ //服务商检测到AI输出了敏感内容
+ let length = this.msgList.length
+ //如果最后一项不是ai,就创建一项
+ if(length % 2){
+ this.msgList.push({
+ isAi: true,
+ content:"内容涉及敏感",
+ illegal:true,
+ create_time: Date.now()
+ })
+ length += 1
+ }
+ // 更新倒数第2项 用户提的问题
+ this.msgList[length - 2].illegal = true
+ // 更新倒数第1项 ai 回答的内容
+ this.msgList[length - 1].illegal = true
+ this.msgList[length - 1].content = "内容涉及敏感"
+
+ }else{
+ setLastAiMsgContent(e.errMsg)
+ }
+ }
+ sseEnd()
+ })
+ await sseChannel.open(); // 等待通道开启
+
+ // 等待对话完成(云函数请求完成,sse 执行了 end)之后
+ (function fnSelf(that){
+ fnSelf.clear = ()=>{
+ // console.log('do fnSelf.clear');
+ if(fnSelf.clear.sse){
+ // console.log('fnSelf.clear.sse();')
+ fnSelf.clear.sse();
+ }
+ if(fnSelf.clear.request){
+ // console.log('fnSelf.clear.request();')
+ fnSelf.clear.request();
+ }
+ }
+ Promise.all([
+ new Promise((resolve,reject)=>{
+ sseEnd = resolve;
+ fnSelf.clear.sse = reject;
+ }),
+ new Promise((resolve,reject)=>{
+ requestEnd = resolve;
+ fnSelf.clear.request = reject;
+ })
+ ]).then((e)=>{
+ // console.log('sseEnd && requestEnd');
//当两个都结束时
sseChannel.close()
// 结束流式响应 将流式响应计数值 设置为 0
- this.sseIndex = 0
- state = {sse:0,co:0}
- }else{
- // console.log(1,state);
- }
- })
- await sseChannel.open() // 等待通道开启
- }
-
+ that.sseIndex = 0
+ }).catch((err)=>{
+ // console.log('afterChatCompletion is close',err);
+ })
+ that.afterChatCompletion = fnSelf
+ })(this)
+ }
+
// 导入uni-ai-chat模块,并设置customUI为true
let task = uniCoTask({
coName: "uni-ai-chat",
@@ -633,41 +680,29 @@
config: {
customUI: true
},
- success: res => {
- console.log("success",res);
- if (!res.data) {
- return
- }
- // 更新最后一条消息的状态为100(发送成功)
- this.updateLastMsg({
- state: 100
- })
+ success: res => {
+ // 更新 通讯状态为100(发送成功)
+ this.requestState = 100
+ // console.log("success",res);
+ if (!res.data) return
let {
- "reply": content,
+ reply,
summarize,
insufficientScore,
illegal
} = res.data
// 特殊处理 - start
- if(this.enableStream == false && !content){
+ if(this.enableStream == false && !reply){
+ //服务商检测到AI输出了敏感内容
illegal = true
- content = "内容涉及敏感"
+ reply = "内容涉及敏感"
}
// 特殊处理 - end
-
- if (illegal) {
- // 如果返回的数据包含illegal属性,就更新最后一条消息(用户输入的问题)的illegal属性为true
- this.updateLastMsg({
- // 添加消息涉敏标记
- illegal: true
- })
- }
-
-
+
// 非流式模式 或者流式模式,但列表还没有数据且已经进入异常的情况下
- if (this.enableStream == false || this.sseIndex == 0 && (illegal || insufficientScore)) {
+ if (this.enableStream == false || this.sseIndex == 0 && (illegal||insufficientScore)) {
// 将从云端接收到的消息添加到消息列表中
this.msgList.push({
// 消息创建时间
@@ -675,36 +710,50 @@
// 标记消息为来自AI机器人
isAi: true,
// 消息内容
- content,
+ content:reply,
// 消息是否涉敏标记
- illegal,
- // 本地对话是否因积分不足而终止
- insufficientScore
+ illegal
})
+ }
+
+ if(insufficientScore){
+ // 积分不足
+ this.insufficientScore = true
}
- // console.log(res, res.reply);
-
// 如果回调包含总结的内容,就设置总结
if(summarize){
console.log(' 拿到总结',summarize);
- this.setSummarize(summarize)
+ // 总结的内容是上一轮对话的
+ // console.log('setSummarize');
+ let index = this.msgList.length;
+ // 如果最后一项是ai就往前退2项,否则退一项(流式响应的时候,回答可能晚于总结)
+ if(index%2 === 0){
+ index -= 2
+ }else{
+ index -= 1
+ }
+ // 假如第一次提问就需要总结
+ if (index < 0) {
+ index = 0
+ }
+ let msg = this.msgList[index]
+ msg.summarize = summarize
+ this.msgList.splice(index, 1, msg)
+ // console.log('setSummarize this.msgList',this.msgList);
+ }
+ if (illegal) {
+ console.error('内容涉及敏感');
+ this.updateLastMsg({
+ // 添加消息涉敏标记
+ illegal: true
+ })
}
},
complete:e=>{
- // console.log('complete:',e);
- if (sseChannel) {
- state.co = 1
- if(state.sse === 1 && state.co === 1){
- // console.error('通过 co complete 结束');
- //当两个都结束时
- sseChannel.close()
- // 结束流式响应 将流式响应计数值 设置为 0
- this.sseIndex = 0
- state = {sse:0,co:0}
- }else{
- // console.log(2,state);
- }
+ if (this.enableStream) {
+ requestEnd()
}
+ // console.log('complete:',e);
// 滚动窗口以显示最新的一条消息
this.$nextTick(() => {
this.showLastMsg()
@@ -712,15 +761,17 @@
},
fail: e => {
console.error(e);
- // 更新最后一条消息的状态为-100(发送失败)
- this.updateLastMsg({
- state: -100
- })
+ // 更新 通讯状态为-100(发送失败)
+ this.requestState = -100
// 弹框提示用户错误原因
uni.showModal({
content: JSON.stringify(e.message),
showCancel: false
- });
+ });
+ // 如果启用流式,云函数出错了,sse 也应当被终止
+ if (this.enableStream) {
+ sseEnd()
+ }
}
})
uniCoTaskList.push(task)
@@ -728,7 +779,8 @@
closeSseChannel() {
// 如果存在消息通道,就关闭消息通道
if (sseChannel) {
- sseChannel.close()
+ sseChannel.close()
+ // 设置为 false 防止重复调用closeSseChannel时出错
sseChannel = false
}
// 清空历史网络请求(调用云对象)任务
@@ -922,6 +974,11 @@
color: #aaa;
font-size: 12px;
justify-content: center;
+ }
+
+ .open-ad-btn-box{
+ justify-content: center;
+ margin: 10px 0;
}
.tip-ai-ing {
diff --git a/uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js b/uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
index cc5f5312920c566604ad5a479e765554e3288250..6357ed84fe07af5125c07956cba88a090c0a3a1c 100644
--- a/uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
+++ b/uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
@@ -174,10 +174,9 @@ module.exports = {
// 如果是积分不足错误
else if (error == 'insufficientScore') {
// 设置回复内容
- let reply = "积分不足,请看完激励视频广告后再试"
return {
"data": {
- reply,
+ "reply":"积分不足,请看完激励视频广告后再试",
"insufficientScore": true
},
"errCode": 0