提交 514360da 编写于 作者: DCloud_JSON's avatar DCloud_JSON

1.2.0

上级 d1ebe1f7
## 1.2.0(2023-06-14)
- 【重要】支持通过 uni-ai 计费网关发起调用 [详情参考](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#api)
- 更新 大语言模型provider的值默认为:azure
## 1.1.5(2023-06-13) ## 1.1.5(2023-06-13)
- 修复 提供给AI做出总结的内容,多包含了最后一次提问的内容 - 修复 提供给AI做出总结的内容,多包含了最后一次提问的内容
## 1.1.4(2023-06-13) ## 1.1.4(2023-06-13)
......
...@@ -11,7 +11,6 @@ class Task { ...@@ -11,7 +11,6 @@ class Task {
complete complete
} }
} }
invoke(callbackName, ...args) { invoke(callbackName, ...args) {
if (this.status !== 0) { if (this.status !== 0) {
// console.log('此任务已被终止'); // console.log('此任务已被终止');
......
...@@ -55,11 +55,9 @@ ...@@ -55,11 +55,9 @@
currentModel:'' currentModel:''
}; };
}, },
mounted() {
this.currentModel = uni.getStorageSync('uni-ai-chat-llmModel')
},
methods: { methods: {
open(callback){ open(callback){
this.currentModel = uni.getStorageSync('uni-ai-chat-llmModel')
confirmCallback = callback confirmCallback = callback
this.$refs.popup.open('center') this.$refs.popup.open('center')
}, },
......
...@@ -39,10 +39,6 @@ ...@@ -39,10 +39,6 @@
</template> </template>
</view> </view>
</view> </view>
<!-- <uni-icons v-if="isLastMsg && !msg.isAi && msg.state != 100 && msgStateIcon(msg)"
@click="msg.state == -100 ? retriesSendMsg() : ''" :color="msg.state===0?'#999':'#d22'"
:type="msgStateIcon(msg)" class="msgStateIcon">
</uni-icons> -->
</view> </view>
</view> </view>
</template> </template>
...@@ -56,10 +52,6 @@ ...@@ -56,10 +52,6 @@
adpid adpid
} = config } = config
import {
msgList
} from '@/pages/chat/msgList.js';
// 引入markdown-it库 // 引入markdown-it库
import MarkdownIt from '@/lib/markdown-it.min.js'; import MarkdownIt from '@/lib/markdown-it.min.js';
...@@ -126,18 +118,13 @@ ...@@ -126,18 +118,13 @@
}) })
export default { export default {
name: "msg", name: "uni-ai-msg",
data() { data() {
return { return {
// 悬浮的复制按钮的左边距 // 悬浮的复制按钮的左边距
left: "-100px", left: "-100px",
// 悬浮的复制按钮的上边距 // 悬浮的复制按钮的上边距
top: "-100px", top: "-100px",
msg: {
content: "",
isDelete:false
},
msgIndexList: 0,
adpid, adpid,
showMoreMenu:false showMoreMenu:false
}; };
...@@ -145,7 +132,6 @@ ...@@ -145,7 +132,6 @@
mounted() { mounted() {
}, },
created() { created() {
this.msg = msgList[this.msgIndex]
}, },
props: { props: {
// 是否显示鼠标闪烁的效果 // 是否显示鼠标闪烁的效果
...@@ -155,24 +141,30 @@ ...@@ -155,24 +141,30 @@
return false return false
} }
}, },
msgIndex: {
type: Number,
default () {
return false
}
},
isLastMsg: { isLastMsg: {
type: Boolean, type: Boolean,
default () { default () {
return false return false
} }
} },
msg: {
type: Object,
default () {
return {
content: "",
isDelete:false
}
}
},
}, },
computed: { computed: {
msgContent() { msgContent() {
return this.msg.content return this.msg.content
}, },
nodes() { nodes() {
if(!this.msgContent){
return //处理特殊情况,比如网络异常导致的响应的 content 的值为空
}
let htmlString = '' let htmlString = ''
// 修改转换结果的htmlString值 用于正确给界面增加鼠标闪烁的效果 // 修改转换结果的htmlString值 用于正确给界面增加鼠标闪烁的效果
// 判断markdown中代码块标识符的数量是否为偶数 // 判断markdown中代码块标识符的数量是否为偶数
...@@ -200,27 +192,6 @@ ...@@ -200,27 +192,6 @@
} }
}, },
methods: { methods: {
// 根据消息状态返回对应的图标
msgStateIcon(msg) {
switch (msg.state) {
case 0:
// 发送中
return ''
break;
case -100:
// 发送失败
return 'refresh-filled'
break;
case -200:
// 禁止发送(内容不合法)
return 'info-filled'
break;
default:
// 默认不返回任何图标
return false
break;
}
},
trOnclick(e){ trOnclick(e){
console.log(e); console.log(e);
let {attrs} = e.detail.node let {attrs} = e.detail.node
...@@ -262,8 +233,11 @@ ...@@ -262,8 +233,11 @@
}, },
// 删除消息 // 删除消息
removeMsg(){ removeMsg(){
this.$emit('removeMsg',this.msgIndex) this.$emit('removeMsg')
this.showMoreMenu = false this.showMoreMenu = false
},
onAdClose(e){
this.$emit('onAdClose',e)
} }
} }
} }
...@@ -292,14 +266,6 @@ ...@@ -292,14 +266,6 @@
padding: 0 15px; padding: 0 15px;
padding-bottom: 15px; padding-bottom: 15px;
} }
.msgStateIcon {
position: relative;
top: -5px;
right: 1px;
align-self: center;
}
.avatar { .avatar {
width: 40px; width: 40px;
height: 40px; height: 40px;
...@@ -332,7 +298,8 @@ ...@@ -332,7 +298,8 @@
word-break: break-all; word-break: break-all;
user-select: text; user-select: text;
cursor: text; cursor: text;
/* #endif */ /* #endif */
flex-direction: column;
} }
.menu-box { .menu-box {
......
{ {
"id": "uni-ai-chat", "id": "uni-ai-chat",
"name": "uni-ai-chat", "name": "uni-ai-chat",
"version": "1.1.5", "version": "1.2.0",
"description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体", "description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
......
...@@ -3,18 +3,18 @@ ...@@ -3,18 +3,18 @@
<!-- #ifdef H5 --> <!-- #ifdef H5 -->
<view v-if="isWidescreen" class="header">uni-ai-chat</view> <view v-if="isWidescreen" class="header">uni-ai-chat</view>
<!-- #endif --> <!-- #endif -->
<text class="noData" v-if="msgLength === 0">没有对话记录</text> <text class="noData" v-if="msgList.length === 0">没有对话记录</text>
<scroll-view :scroll-into-view="scrollIntoView" scroll-y="true" class="msg-list" :enable-flex="true"> <scroll-view :scroll-into-view="scrollIntoView" scroll-y="true" class="msg-list" :enable-flex="true">
<uni-ai-msg ref="msg" v-for="(msgIndex,index) in msgLength" :key="index" :msgIndex="index" <uni-ai-msg ref="msg" v-for="(msg,index) in msgList" :key="index" :msg="msg"
@retriesSendMsg="retriesSendMsg" @changeAnswer="changeAnswer" @retriesSendMsg="retriesSendMsg" @changeAnswer="changeAnswer" @onAdClose="onAdClose"
:show-cursor="index == msgLength - 1 && msgLength%2 === 0 && sseIndex" :show-cursor="index == msgList.length - 1 && msgList.length%2 === 0 && sseIndex"
:isLastMsg="index == visibleMsgLength - 1" @removeMsg="removeMsg"></uni-ai-msg> :isLastMsg="index == msgList.length - 1" @removeMsg="removeMsg(index)"></uni-ai-msg>
<template v-if="msgLength%2 !== 0"> <template v-if="msgList.length%2 !== 0">
<view v-if="lastMsgState == -100" class="retries-box"> <view v-if="lastMsgState == -100" class="retries-box">
<text>消息发送失败</text> <text>消息发送失败</text>
<uni-icons @click="retriesSendMsg" color="#d22" type="refresh-filled" class="retries-icon"></uni-icons> <uni-icons @click="retriesSendMsg" color="#d22" type="refresh-filled" class="retries-icon"></uni-icons>
</view> </view>
<view class="tip-ai-ing" v-else-if="msgLength"> <view class="tip-ai-ing" v-else-if="msgList.length">
<text>uni-ai正在思考中...</text> <text>uni-ai正在思考中...</text>
<view v-if="NODE_ENV == 'development' && !enableStream"> <view v-if="NODE_ENV == 'development' && !enableStream">
如需提速,请开通<uni-link class="uni-link" href="https://uniapp.dcloud.net.cn/uniCloud/uni-ai-chat.html" 如需提速,请开通<uni-link class="uni-link" href="https://uniapp.dcloud.net.cn/uniCloud/uni-ai-chat.html"
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
:placeholder="placeholderText" :maxlength="-1" :adjust-position="false" :placeholder="placeholderText" :maxlength="-1" :adjust-position="false"
:disable-default-padding="false" placeholder-class="input-placeholder"></textarea> :disable-default-padding="false" placeholder-class="input-placeholder"></textarea>
</view> </view>
<view class="send-btn-box" :title="(msgLength && msgLength%2 !== 0) ? 'ai正在回复中不能发送':''"> <view class="send-btn-box" :title="(msgList.length && msgList.length%2 !== 0) ? 'ai正在回复中不能发送':''">
<!-- #ifdef H5 --> <!-- #ifdef H5 -->
<text v-if="isWidescreen" class="send-btn-tip">↵ 发送 / shift + ↵ 换行</text> <text v-if="isWidescreen" class="send-btn-tip">↵ 发送 / shift + ↵ 换行</text>
<!-- #endif --> <!-- #endif -->
...@@ -65,10 +65,6 @@ ...@@ -65,10 +65,6 @@
// 引入配置文件 // 引入配置文件
import config from '@/config.js'; import config from '@/config.js';
import {
msgList
} from '@/pages/chat/msgList.js';
// 导入uniCloud云对象task模块 // 导入uniCloud云对象task模块
import uniCoTask from '@/common/unicloud-co-task.js'; import uniCoTask from '@/common/unicloud-co-task.js';
// 收集所有执行云对象的任务列表 // 收集所有执行云对象的任务列表
...@@ -89,14 +85,13 @@ ...@@ -89,14 +85,13 @@
let sseChannel = false; let sseChannel = false;
// 键盘的shift键是否被按下 // 键盘的shift键是否被按下
let shiftKeyPressed = false let shiftKeyPressed = false
export default { export default {
data() { data() {
return { return {
// 使聊天窗口滚动到指定元素id的值 // 使聊天窗口滚动到指定元素id的值
scrollIntoView: "", scrollIntoView: "",
// 消息长度(个数)
msgLength: 0,
// 消息列表数据 // 消息列表数据
msgList: [], msgList: [],
// 输入框的消息内容 // 输入框的消息内容
...@@ -110,8 +105,7 @@ ...@@ -110,8 +105,7 @@
// 广告位id // 广告位id
adpid, adpid,
llmModel: false, llmModel: false,
keyboardHeight: 0, keyboardHeight: 0
visibleMsgLength:0
} }
}, },
computed: { computed: {
...@@ -122,8 +116,8 @@ ...@@ -122,8 +116,8 @@
return true return true
} }
// 如果消息列表长度为奇数,则禁用输入框 // 如果消息列表长度为奇数,则禁用输入框
return !!(this.msgLength && this.msgLength % 2 !== 0) return !!(this.msgList.length && this.msgList.length % 2 !== 0)
}, },
// 输入框占位符文本 // 输入框占位符文本
placeholderText() { placeholderText() {
// #ifdef H5 // #ifdef H5
...@@ -152,16 +146,7 @@ ...@@ -152,16 +146,7 @@
// 监听msgList变化,将其存储到本地缓存中 // 监听msgList变化,将其存储到本地缓存中
watch: { watch: {
msgList: { msgList: {
handler(msgList) { handler(msgList) {
let msgLength = msgList.length
if (msgLength != this.msgLength) {
this.msgLength = msgLength
this.$nextTick(() => {
this.updateLastMsg(msgList[msgLength - 1])
})
}
msgList = msgList.filter(i => i.isDelete !== true)
this.visibleMsgLength = msgList.length
// 将msgList存储到本地缓存中 // 将msgList存储到本地缓存中
uni.setStorage({ uni.setStorage({
"key": "uni-ai-msg", "key": "uni-ai-msg",
...@@ -227,11 +212,7 @@ ...@@ -227,11 +212,7 @@
// } // }
// 获得历史对话记录 // 获得历史对话记录
let _msgList = uni.getStorageSync('uni-ai-msg') || []; this.msgList = uni.getStorageSync('uni-ai-msg') || [];
if (_msgList.length) {
msgList.push(..._msgList)
}
this.msgList = msgList
// 获得之前设置的llmModel // 获得之前设置的llmModel
this.llmModel = uni.getStorageSync('uni-ai-chat-llmModel') this.llmModel = uni.getStorageSync('uni-ai-chat-llmModel')
...@@ -302,7 +283,6 @@ ...@@ -302,7 +283,6 @@
this.showLastMsg() this.showLastMsg()
}) })
} }
} }
// #endif // #endif
...@@ -396,7 +376,8 @@ ...@@ -396,7 +376,8 @@
// 解构出score字段的值,如果没有则默认为undefined // 解构出score字段的值,如果没有则默认为undefined
let { let {
score score
} = res.result.data[0] || {} } = res.result.data[0] || {}
console.log('score',score);
if (score > 0 || i > 5) { if (score > 0 || i > 5) {
// 清除轮询定时器 // 清除轮询定时器
clearInterval(myIntive) clearInterval(myIntive)
...@@ -438,30 +419,24 @@ ...@@ -438,30 +419,24 @@
//删除旧的回答 //删除旧的回答
this.msgList.pop() this.msgList.pop()
// 防止 偶发答案涉及敏感,重复回答时。提问内容 被卡掉无法重新问 this.updateLastMsg({
this.updateLastMsg({ // 防止 偶发答案涉及敏感,重复回答时。提问内容 被卡掉无法重新问
illegal: false illegal: false,
// 多设备登录时其他设备看广告后点击重新回答,insufficientScore应当设置为 false
insufficientScore:false
}) })
this.send() this.send()
}, },
removeMsg(index) { removeMsg(index) {
// #ifdef VUE3 // 如果问题还在回答中需要先关闭
this.msgList[index].isDelete = true if (this.sseIndex) {
if (this.msgList[index].isAi && this.msgList[index - 1]) { this.closeSseChannel()
this.msgList[index - 1].isDelete = true }
} else if (this.msgList[index + 1]) {
this.msgList[index + 1].isDelete = true if (this.msgList[index].isAi) {
} index -= 1
// #endif }
this.msgList.splice(index,2)
// #ifdef VUE2
this.$set(msgList[index], "isDelete", true)
if (msgList[index].isAi && msgList[index - 1]) {
this.$set(msgList[index - 1], "isDelete", true)
} else if (msgList[index + 1]) {
this.$set(msgList[index + 1], "isDelete", true)
}
// #endif
}, },
async beforeSendMsg() { async beforeSendMsg() {
if (this.inputBoxDisabled) { if (this.inputBoxDisabled) {
...@@ -550,11 +525,16 @@ ...@@ -550,11 +525,16 @@
}) })
this.send() // 发送消息 this.send() // 发送消息
}, },
async send() { async send() {
// 流式响应和云对象的请求状态
let state = {
sse:0,
co:0
}
let messages = [] let messages = []
// 复制一份,消息列表数据 // 复制一份,消息列表数据
let msgs = JSON.parse(JSON.stringify(msgList)).filter(i => i.isDelete !== true) let msgs = JSON.parse(JSON.stringify(this.msgList)).filter(i => i.isDelete !== true)
// 带总结的消息 index // 带总结的消息 index
let findIndex = [...msgs].reverse().findIndex(item => item.summarize) let findIndex = [...msgs].reverse().findIndex(item => item.summarize)
// console.log('findIndex', findIndex) // console.log('findIndex', findIndex)
...@@ -563,13 +543,13 @@ ...@@ -563,13 +543,13 @@
// console.log('aiSummaryIndex', aiSummaryIndex) // console.log('aiSummaryIndex', aiSummaryIndex)
// 将带总结的消息的 内容 更换成 总结 // 将带总结的消息的 内容 更换成 总结
msgs[aiSummaryIndex].content = msgs[aiSummaryIndex].summarize msgs[aiSummaryIndex].content = msgs[aiSummaryIndex].summarize
// 拿最后一条带直接的消息作为与ai对话的msg body // 拿最后一条带直接的消息作为与ai对话的msg body
msgs = msgs.splice(aiSummaryIndex, msgs.length - 1) msgs = msgs.splice(aiSummaryIndex)
} else { } else {
// 如果未总结过就直接从末尾拿10条 // 如果未总结过就直接从末尾拿10条
msgs = msgs.splice(-10) msgs = msgs.splice(-10)
} }
// 过滤涉敏问题 // 过滤涉敏问题
msgs = msgs.filter(msg => !msg.illegal) msgs = msgs.filter(msg => !msg.illegal)
...@@ -592,7 +572,7 @@ ...@@ -592,7 +572,7 @@
// 检查是否开通uni-push;决定是否启用enableStream // 检查是否开通uni-push;决定是否启用enableStream
await this.checkIsOpenPush() await this.checkIsOpenPush()
// console.log('this.enableStream',this.enableStream); // console.log('this.enableStream',this.enableStream);
// 判断是否开启了流式响应模式 // 判断是否开启了流式响应模式
if (this.enableStream) { if (this.enableStream) {
...@@ -625,37 +605,18 @@ ...@@ -625,37 +605,18 @@
// 监听end事件,如果云端执行end时传了message,会在客户端end事件内收到传递的消息 // 监听end事件,如果云端执行end时传了message,会在客户端end事件内收到传递的消息
sseChannel.on('end', (e) => { sseChannel.on('end', (e) => {
// console.log('on end', e); console.log('sse 结束',e)
// 如果e存在且包含summarize或insufficientScore属性 state.sse = 1
if (e) { if(state.sse === 1 && state.co === 1){
// 如果e包含summarize属性 // console.error('通过 sse end 结束',state);
if (e.summarize) { //当两个都结束时
// 设置总结 sseChannel.close()
this.setSummarize(e.summarize) // 结束流式响应 将流式响应计数值 设置为 0
}else{ this.sseIndex = 0
// 更新最后一条消息 state = {sse:0,co:0}
this.updateLastMsg(lastMsg => { }else{
// 如果e包含illegal属性 // console.log(1,state);
if (e.illegal) { }
// 将最后一条消息的illegal属性更新为e的illegal属性
lastMsg.illegal = e.illegal
lastMsg.content = "内容涉及敏感"
// 倒数第二条(用户发问内容)也需要设置illegal的值
this.msgList[this.msgList.length - 2].illegal = e.illegal
}
// 如果e包含insufficientScore属性
else if (e.insufficientScore) {
// 将最后一条消息的insufficientScore属性更新为e的insufficientScore属性
lastMsg.insufficientScore = e.insufficientScore
}
})
}
}
// 结束流式响应 将流式响应计数值 设置为 0
this.sseIndex = 0
// 滚动窗口以显示最新的一条消息
this.showLastMsg()
}) })
await sseChannel.open() // 等待通道开启 await sseChannel.open() // 等待通道开启
} }
...@@ -673,67 +634,84 @@ ...@@ -673,67 +634,84 @@
customUI: true customUI: true
}, },
success: res => { success: res => {
// console.log(111,res); console.log("success",res);
if (!sseChannel) { if (!res.data) {
if (!res.data) { return
return }
} // 更新最后一条消息的状态为100(发送成功)
// 更新最后一条消息的状态为100(发送成功) this.updateLastMsg({
this.updateLastMsg({ state: 100
state: 100 })
})
// console.log(res, res.reply); let {
let { "reply": content,
"reply": content, summarize,
summarize, insufficientScore,
insufficientScore, illegal
illegal } = res.data
} = res.data
if (illegal) { // 特殊处理 - start
// 如果返回的数据包含illegal属性,就更新最后一条消息的illegal属性为true if(this.enableStream == false && !content){
this.updateLastMsg({ illegal = true
illegal: true content = "内容涉及敏感"
}) }
} // 特殊处理 - end
// 将从云端接收到的消息添加到消息列表中
this.msgList.push({ if (illegal) {
// 添加消息创建时间 // 如果返回的数据包含illegal属性,就更新最后一条消息(用户输入的问题)的illegal属性为true
create_time: Date.now(), this.updateLastMsg({
// 标记消息为来自AI机器人 // 添加消息涉敏标记
isAi: true, illegal: true
// 添加消息内容
content,
// 添加消息分数不足标记
insufficientScore,
// 添加消息涉敏标记
illegal
}) })
// 如果回调包含总结的内容,就设置总结 }
if(summarize){
this.setSummarize(summarize)
} // 非流式模式 或者流式模式,但列表还没有数据且已经进入异常的情况下
// 滚动窗口以显示最新的一条消息 if (this.enableStream == false || this.sseIndex == 0 && (illegal || insufficientScore)) {
this.$nextTick(() => { // 将从云端接收到的消息添加到消息列表中
this.showLastMsg() this.msgList.push({
}) // 消息创建时间
} else { create_time: Date.now(),
// 处理 sseChannel没结束 云函数提前结束的情况 // 标记消息为来自AI机器人
sseChannel.close() isAi: true,
this.sseIndex = 0 // 消息内容
content,
// 消息是否涉敏标记
illegal,
// 本地对话是否因积分不足而终止
insufficientScore
})
}
// console.log(res, res.reply);
// 如果回调包含总结的内容,就设置总结
if(summarize){
console.log(' 拿到总结',summarize);
this.setSummarize(summarize)
} }
},
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);
}
}
// 滚动窗口以显示最新的一条消息
this.$nextTick(() => {
this.showLastMsg()
})
}, },
fail: e => { fail: e => {
console.log(e); console.error(e);
// 获取消息列表长度
let l = this.msgList.length
// console.log(l,this.msgList[l-1]);
// 如果最后一条消息的来源是人工智能机器人 就将流式响应计数值设置为0
if (l && sseChannel && this.msgList[l - 1].isAi) {
sseChannel.close()
this.sseIndex = 0
}
// 更新最后一条消息的状态为-100(发送失败) // 更新最后一条消息的状态为-100(发送失败)
this.updateLastMsg({ this.updateLastMsg({
state: -100 state: -100
...@@ -783,7 +761,7 @@ ...@@ -783,7 +761,7 @@
// 关闭ssh请求 // 关闭ssh请求
this.closeSseChannel() this.closeSseChannel()
// 将消息列表清空 // 将消息列表清空
this.msgList.splice(0, this.msgLength); this.msgList.splice(0, this.msgList.length);
} }
} }
}); });
......
export const msgList = []
...@@ -26,7 +26,7 @@ async function nextFn(data) { ...@@ -26,7 +26,7 @@ async function nextFn(data) {
pluginId: 'uni-ai-chat' pluginId: 'uni-ai-chat'
}).config() }).config()
console.log('uniAiChatConfig',uniAiChatConfig); console.log('uniAiChatConfig',uniAiChatConfig);
if(!uniAiChatConfig || !uniAiChatConfig.ad || !uniAiChatConfig.earnedScore.ad){ if(!uniAiChatConfig || !uniAiChatConfig.earnedScore || !uniAiChatConfig.earnedScore.ad){
throw new Error('请先完成uni-ai-chat的广告奖励配置') throw new Error('请先完成uni-ai-chat的广告奖励配置')
} }
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
"ad":3, "ad":3,
"price":3 "price":3
}, },
"llm":{}, "llm":{
"provider": "azure"
},
"chatCompletionOptions":{ "chatCompletionOptions":{
"tokensToGenerate":512 "tokensToGenerate":512
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册