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

1.1.0

- 【重要】优化`uni-ai-chat`云对象的`send`方法性能(并发:“普通消息对话”和“获得内容总结”),大幅提高会话响应速度。
- 修复 手机端 当消息列表数据不满一屏幕时,键盘弹出后看不到历史消息
- 修复 单独删除最后一次对话后,新的最后一条对话消息没有`换答案`按钮
- 修复 当消息发送失败 重新发送消息按钮图标与 复制和删除消息按钮 样式重叠的问题
上级 4948c67c
## 1.1.0(2023-06-09)
- 【重要】优化`uni-ai-chat`云对象的`send`方法性能(并发:“普通消息对话”和“获得内容总结”),大幅提高会话响应速度。
- 修复 手机端 当消息列表数据不满一屏幕时,键盘弹出后看不到历史消息
- 修复 单独删除最后一次对话后,新的最后一条对话消息没有`换答案`按钮
- 修复 当消息发送失败 重新发送消息按钮图标与 复制和删除消息按钮 样式重叠的问题
## 1.0.23(2023-06-08) ## 1.0.23(2023-06-08)
- 新增 支持单独删除某一次对话(注意:一次对话包含提问和回答2条消息) - 新增 支持单独删除某一次对话(注意:一次对话包含提问和回答2条消息)
- 更新 客户端网络请求超时时间(`manifest.json`->`networkTimeout`->`request`)设置为`600000`毫秒 [详情参考](https://uniapp.dcloud.net.cn/collocation/manifest.html#networktimeout) - 更新 客户端网络请求超时时间(`manifest.json`->`networkTimeout`->`request`)设置为`600000`毫秒 [详情参考](https://uniapp.dcloud.net.cn/collocation/manifest.html#networktimeout)
......
...@@ -39,10 +39,10 @@ ...@@ -39,10 +39,10 @@
</template> </template>
</view> </view>
</view> </view>
<uni-icons v-if="isLastMsg && !msg.isAi && msg.state != 100 && msgStateIcon(msg)" <!-- <uni-icons v-if="isLastMsg && !msg.isAi && msg.state != 100 && msgStateIcon(msg)"
@click="msg.state == -100 ? retriesSendMsg() : ''" :color="msg.state===0?'#999':'#d22'" @click="msg.state == -100 ? retriesSendMsg() : ''" :color="msg.state===0?'#999':'#d22'"
:type="msgStateIcon(msg)" class="msgStateIcon"> :type="msgStateIcon(msg)" class="msgStateIcon">
</uni-icons> </uni-icons> -->
</view> </view>
</view> </view>
</template> </template>
...@@ -370,11 +370,19 @@ ...@@ -370,11 +370,19 @@
.more-icon { .more-icon {
color: #d4d4d4; color: #d4d4d4;
transform: rotate(90deg); transform: rotate(270deg);
position: relative; position: relative;
left: 4px; left: -8px;
font-size: 16px; font-size: 16px;
z-index: 999; z-index: 999;
padding-bottom: 5px;
height: 10px;
}
.menu-box-ai .more-icon{
left: 7px;
top: -2px;
transform: rotate(90deg);
} }
.more-menu { .more-menu {
......
{ {
"id": "uni-ai-chat", "id": "uni-ai-chat",
"name": "uni-ai-chat", "name": "uni-ai-chat",
"version": "1.0.23", "version": "1.1.0",
"description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体", "description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
......
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
"path": "pages/chat/chat", "path": "pages/chat/chat",
"style": { "style": {
"navigationBarTitleText": "uni-ai-chat", "navigationBarTitleText": "uni-ai-chat",
"enablePullDownRefresh": false "enablePullDownRefresh": false
// #ifdef H5
,"navigationStyle": "custom"
//#endif
} }
}, { }, {
"path": "uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate", "path": "uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate",
......
<template> <template>
<view class="page"> <view class="container">
<view class="container"> <!-- #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 -->
<text class="noData" v-if="msgLength === 0">没有对话记录</text>
<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"
@retriesSendMsg="retriesSendMsg" @changeAnswer="changeAnswer"
:show-cursor="index == msgLength - 1 && msgLength%2 === 0 && sseIndex"
:isLastMsg="index == visibleMsgLength - 1" @removeMsg="removeMsg"></uni-ai-msg>
<view v-if="lastMsgState == -100" class="retries-box">
<text>消息发送失败</text>
<uni-icons @click="retriesSendMsg" color="#d22" type="refresh-filled" class="retries-icon"></uni-icons>
</view>
<view class="tip-ai-ing" v-else-if="msgLength && msgLength%2 !== 0">
<text>uni-ai正在思考中...</text>
<view v-if="NODE_ENV == 'development' && !enableStream">
如需提速,请开通<uni-link class="uni-link" href="https://uniapp.dcloud.net.cn/uniCloud/uni-ai-chat.html"
text="[流式响应]"></uni-link>
</view>
</view>
<view @click="closeSseChannel" class="stop-responding" v-if="enableStream && sseIndex"> ▣ 停止响应</view>
<view id="last-msg-item" style="height: 1px;"></view>
</scroll-view>
<view class="foot-box" :style="{'padding-bottom':footBoxPaddingBottom}">
<!-- #ifdef H5 -->
<view class="pc-menu" v-if="isWidescreen">
<view class="pc-trash pc-menu-item" @click="clearAllMsg" title="删除">
<image src="@/static/remove.png" mode="heightFix"></image>
</view>
<view class="settings pc-menu-item" @click="setLLMmodel" title="设置">
<uni-icons color="#555" size="20px" type="settings"></uni-icons>
</view>
</view>
<!-- #endif --> <!-- #endif -->
<text class="noData" v-if="msgLength === 0">没有对话记录</text> <view class="foot-box-content">
<scroll-view :scroll-into-view="scrollIntoView" scroll-y="true" class="msg-list" :enable-flex="true"> <view v-if="!isWidescreen" class="menu">
<uni-ai-msg ref="msg" v-for="(msgIndex,index) in msgLength" :key="index" :msgIndex="index" @retriesSendMsg="retriesSendMsg" @changeAnswer="changeAnswer" <uni-icons class="menu-item" @click="clearAllMsg" type="trash" size="24" color="#888"></uni-icons>
:show-cursor="index == msgLength - 1 && msgLength%2 === 0 && sseIndex" :isLastMsg="index == msgLength - 1" @removeMsg="removeMsg"></uni-ai-msg> <uni-icons class="menu-item" @click="setLLMmodel" color="#555" size="20px"
<view class="tip-ai-ing" v-if="msgLength && msgLength%2 !== 0 && lastMsgState != -100"> type="settings"></uni-icons>
<text>uni-ai正在思考中...</text> </view>
<view v-if="NODE_ENV == 'development' && !enableStream"> <view class="textarea-box">
如需提速,请开通<uni-link class="uni-link" href="https://uniapp.dcloud.net.cn/uniCloud/uni-ai-chat.html" <textarea v-model="content" :cursor-spacing="15" class="textarea" :auto-height="!isWidescreen"
text="[流式响应]"></uni-link> :placeholder="placeholderText" :maxlength="-1" :adjust-position="false"
</view> :disable-default-padding="false" placeholder-class="input-placeholder"></textarea>
</view> </view>
<view @click="closeSseChannel" class="stop-responding" v-if="enableStream && sseIndex"> ▣ 停止响应</view> <view class="send-btn-box" :title="(msgLength && msgLength%2 !== 0) ? 'ai正在回复中不能发送':''">
<view id="last-msg-item" style="height: 1px;"></view> <!-- #ifdef H5 -->
</scroll-view> <text v-if="isWidescreen" class="send-btn-tip">↵ 发送 / shift + ↵ 换行</text>
<!-- #endif -->
<view class="foot-box"> <button @click="beforeSendMsg" :disabled="inputBoxDisabled || !content" class="send"
<!-- #ifdef H5 --> type="primary">发送</button>
<view class="pc-menu" v-if="isWidescreen">
<view class="pc-trash pc-menu-item" @click="clearAllMsg" title="删除">
<image src="@/static/remove.png" mode="heightFix"></image>
</view>
<view class="settings pc-menu-item" @click="setLLMmodel" title="设置">
<uni-icons color="#555" size="20px" type="settings"></uni-icons>
</view>
</view>
<!-- #endif -->
<view class="foot-box-content">
<view v-if="!isWidescreen" class="menu">
<uni-icons class="menu-item" @click="clearAllMsg" type="trash" size="24" color="#888"></uni-icons>
<uni-icons class="menu-item" @click="setLLMmodel" color="#555" size="20px" type="settings"></uni-icons>
</view>
<view class="textarea-box">
<textarea v-model="content" :cursor-spacing="15" class="textarea" :auto-height="!isWidescreen"
:placeholder="placeholderText" :maxlength="-1"
placeholder-class="input-placeholder"></textarea>
</view>
<view class="send-btn-box" :title="(msgLength && msgLength%2 !== 0) ? 'ai正在回复中不能发送':''">
<!-- #ifdef H5 -->
<text v-if="isWidescreen" class="send-btn-tip">↵ 发送 / shift + ↵ 换行</text>
<!-- #endif -->
<button @click="beforeSendMsg" :disabled="inputBoxDisabled || !content" class="send"
type="primary">发送</button>
</view>
</view> </view>
</view> </view>
</view> </view>
<llm-config ref="llm-config"></llm-config> <llm-config ref="llm-config"></llm-config>
</view> </view>
</template> </template>
...@@ -60,19 +65,19 @@ ...@@ -60,19 +65,19 @@
import { import {
msgList msgList
} from '@/pages/chat/msgList.js'; } 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';
// 收集所有执行云对象的任务列表 // 收集所有执行云对象的任务列表
let uniCoTaskList = [] let uniCoTaskList = []
// 定义终止并清空 云对象的任务列表中所有 任务的方法 // 定义终止并清空 云对象的任务列表中所有 任务的方法
uniCoTaskList.clear = function(){ uniCoTaskList.clear = function() {
// 执行数组内的所有任务 // 执行数组内的所有任务
uniCoTaskList.forEach(task=>task.abort()) uniCoTaskList.forEach(task => task.abort())
// 清空数组 // 清空数组
uniCoTaskList.slice(0,uniCoTaskList.length) uniCoTaskList.slice(0, uniCoTaskList.length)
} }
// 获取广告id // 获取广告id
const { const {
...@@ -101,8 +106,10 @@ ...@@ -101,8 +106,10 @@
// 当前屏幕是否为宽屏 // 当前屏幕是否为宽屏
isWidescreen: false, isWidescreen: false,
// 广告位id // 广告位id
adpid, adpid,
llmModel:false llmModel: false,
keyboardHeight: 0,
visibleMsgLength:0
} }
}, },
computed: { computed: {
...@@ -117,24 +124,27 @@ ...@@ -117,24 +124,27 @@
}, },
// 输入框占位符文本 // 输入框占位符文本
placeholderText() { placeholderText() {
// #ifdef H5 // #ifdef H5
// 如果屏幕宽度大于960,则显示“请输入内容,ctrl + enter 发送”,否则显示“请输入要发给uni-ai的内容” // 如果屏幕宽度大于960,则显示“请输入内容,ctrl + enter 发送”,否则显示“请输入要发给uni-ai的内容”
return window.innerWidth > 960 ? '请输入内容,ctrl + enter 发送' : '请输入要发给uni-ai的内容' return window.innerWidth > 960 ? '请输入内容,ctrl + enter 发送' : '请输入要发给uni-ai的内容'
// #endif // #endif
return '请输入要发给uni-ai的内容' return '请输入要发给uni-ai的内容'
}, },
// 获取当前环境 // 获取当前环境
NODE_ENV() { NODE_ENV() {
return process.env.NODE_ENV return process.env.NODE_ENV
}, },
//最后一条消息的状态 //最后一条消息的状态
lastMsgState(){ lastMsgState() {
let mLength = this.msgList.length let mLength = this.msgList.length
if(mLength){ if (mLength) {
return this.msgList[mLength - 1].state return this.msgList[mLength - 1].state
}else{ } else {
return false return false
} }
},
footBoxPaddingBottom() {
return (this.keyboardHeight || 10) + 'px'
} }
}, },
// 监听msgList变化,将其存储到本地缓存中 // 监听msgList变化,将其存储到本地缓存中
...@@ -147,8 +157,9 @@ ...@@ -147,8 +157,9 @@
this.$nextTick(() => { this.$nextTick(() => {
this.updateLastMsg(msgList[msgLength - 1]) this.updateLastMsg(msgList[msgLength - 1])
}) })
} }
msgList = msgList.filter(i=>i.isDelete !== true) 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",
...@@ -157,36 +168,35 @@ ...@@ -157,36 +168,35 @@
}, },
// 深度监听msgList变化 // 深度监听msgList变化
deep: true deep: true
}, },
llmModel(llmModel){ llmModel(llmModel) {
let title = 'uni-ai-chat' let title = 'uni-ai-chat'
if(llmModel){ if (llmModel) {
title += ` (${llmModel})` title += ` (${llmModel})`
} }
uni.setNavigationBarTitle({title}) // uni.setNavigationBarTitle({title})
// #ifdef H5 // #ifdef H5
if(this.isWidescreen){ if (this.isWidescreen) {
document.querySelector('.header').innerText = title document.querySelector('.header').innerText = title
} }
// #endif // #endif
uni.setStorage({ uni.setStorage({
key:'uni-ai-chat-llmModel', key: 'uni-ai-chat-llmModel',
data:llmModel data: llmModel
}) })
} }
}, },
beforeMount() { beforeMount() {
// #ifdef H5 // #ifdef H5
// 监听屏幕宽度变化,判断是否为宽屏 并设置isWidescreen的值 // 监听屏幕宽度变化,判断是否为宽屏 并设置isWidescreen的值
uni.createMediaQueryObserver(this).observe({ uni.createMediaQueryObserver(this).observe({
minWidth: 650, minWidth: 650,
}, matches => { }, matches => {
this.isWidescreen = matches; this.isWidescreen = matches;
}) })
// #endif // #endif
}, },
async mounted() { async mounted() {
// 如果存在广告位id且用户token未过期 // 如果存在广告位id且用户token未过期
if (this.adpid && uniCloud.getCurrentUserInfo().tokenExpired > Date.now()) { if (this.adpid && uniCloud.getCurrentUserInfo().tokenExpired > Date.now()) {
// 查询当前用户的积分 // 查询当前用户的积分
...@@ -218,10 +228,10 @@ ...@@ -218,10 +228,10 @@
let _msgList = uni.getStorageSync('uni-ai-msg') || []; let _msgList = uni.getStorageSync('uni-ai-msg') || [];
if (_msgList.length) { if (_msgList.length) {
msgList.push(..._msgList) msgList.push(..._msgList)
} }
this.msgList = msgList this.msgList = msgList
// 获得之前设置的llmModel // 获得之前设置的llmModel
this.llmModel = uni.getStorageSync('uni-ai-chat-llmModel') this.llmModel = uni.getStorageSync('uni-ai-chat-llmModel')
// 如果上一次对话中 最后一条消息ai未回复。则一启动就自动重发。 // 如果上一次对话中 最后一条消息ai未回复。则一启动就自动重发。
...@@ -233,7 +243,6 @@ ...@@ -233,7 +243,6 @@
} }
} }
// this.msgList.pop() // this.msgList.pop()
// console.log('this.msgList', this.msgList); // console.log('this.msgList', this.msgList);
...@@ -255,11 +264,11 @@ ...@@ -255,11 +264,11 @@
adjunctKeydown = true; adjunctKeydown = true;
} }
if (e.keyCode == 13 && !adjunctKeydown) { if (e.keyCode == 13 && !adjunctKeydown) {
e.preventDefault() e.preventDefault()
// 执行发送 // 执行发送
setTimeout(()=>{ setTimeout(() => {
this.beforeSendMsg(); this.beforeSendMsg();
},300) }, 300)
} }
}; };
textareaDom.onkeyup = e => { textareaDom.onkeyup = e => {
...@@ -268,15 +277,49 @@ ...@@ -268,15 +277,49 @@
adjunctKeydown = false; adjunctKeydown = false;
} }
}; };
// 可视窗口高
let initialInnerHeight = window.innerHeight;
if (uni.getSystemInfoSync().platform == "ios") {
textareaDom.addEventListener('focus', () => {
let interval = setInterval(function() {
if (window.innerHeight !== initialInnerHeight) {
clearInterval(interval)
// 触发相应的回调函数
document.querySelector('.container').style.height = window.innerHeight + 'px'
window.scrollTo(0, 0);
this.showLastMsg()
}
}, 1);
})
textareaDom.addEventListener('blur', () => {
document.querySelector('.container').style.height = initialInnerHeight + 'px'
})
}else{
window.addEventListener('resize',(e)=>{
this.showLastMsg()
})
}
} }
// #endif // #endif
// #ifndef H5
uni.onKeyboardHeightChange(e => {
this.keyboardHeight = e.height
// 在dom渲染完毕后 使聊天窗口滚动到最后一条消息
this.$nextTick(() => {
this.showLastMsg()
})
})
// #endif
}, },
methods: { methods: {
setLLMmodel(){ setLLMmodel() {
this.$refs['llm-config'].open(model=>{ this.$refs['llm-config'].open(model => {
console.log('model',model); console.log('model', model);
this.llmModel = model this.llmModel = model
}) })
}, },
// 此(惰性)函数,检查是否开通uni-push;决定是否启用enableStream // 此(惰性)函数,检查是否开通uni-push;决定是否启用enableStream
async checkIsOpenPush() { async checkIsOpenPush() {
...@@ -291,27 +334,41 @@ ...@@ -291,27 +334,41 @@
} }
}, },
// 更新最后一条消息 // 更新最后一条消息
updateLastMsg(param) { updateLastMsg(param) {
let length = this.msgList.length let length = this.msgList.length
if (length === 0) { if (length === 0) {
return return
} }
let lastMsg = this.msgList[length - 1] let lastMsg = this.msgList[length - 1]
// 如果param是函数,则将最后一条消息作为参数传入该函数 // 如果param是函数,则将最后一条消息作为参数传入该函数
if (typeof param == 'function') { if (typeof param == 'function') {
let callback = param; let callback = param;
callback(lastMsg) callback(lastMsg)
} else { } else {
// 否则,将参数解构为data和cover两个变量 // 否则,将参数解构为data和cover两个变量
const [data, cover = false] = arguments const [data, cover = false] = arguments
if (cover) { if (cover) {
lastMsg = data lastMsg = data
} else { } else {
lastMsg = Object.assign(lastMsg, data) lastMsg = Object.assign(lastMsg, data)
} }
} }
this.msgList.splice(length - 1, 1, lastMsg) 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) { onAdClose(e) {
...@@ -368,49 +425,48 @@ ...@@ -368,49 +425,48 @@
}) })
// 发送消息 // 发送消息
this.send() this.send()
},
// 换一个答案
async changeAnswer(){
// 如果问题还在回答中需要先关闭
if(this.sseIndex){
this.closeSseChannel()
}
//删除旧的回答
this.msgList.pop()
// 防止 偶发答案涉及敏感,重复回答时。提问内容 被卡掉无法重新问
this.updateLastMsg({
illegal:false
})
this.send()
},
removeMsg(index){
// #ifdef VUE3
this.msgList[index].isDelete = true
if(this.msgList[index].isAi && this.msgList[index - 1]){
this.msgList[index - 1].isDelete = true
}else if(this.msgList[index + 1]){
this.msgList[index + 1].isDelete = true
}
// #endif
// #ifdef VUE2
this.$set(msgList[index],"isDelete",true)
msgList.slice(index,1,msgList[index])
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() { // 换一个答案
if(this.inputBoxDisabled){ async changeAnswer() {
return uni.showToast({ // 如果问题还在回答中需要先关闭
title: 'ai正在回复中不能发送', if (this.sseIndex) {
icon: 'none' this.closeSseChannel()
}); }
//删除旧的回答
this.msgList.pop()
// 防止 偶发答案涉及敏感,重复回答时。提问内容 被卡掉无法重新问
this.updateLastMsg({
illegal: false
})
this.send()
},
removeMsg(index) {
// #ifdef VUE3
this.msgList[index].isDelete = true
if (this.msgList[index].isAi && this.msgList[index - 1]) {
this.msgList[index - 1].isDelete = true
} else if (this.msgList[index + 1]) {
this.msgList[index + 1].isDelete = true
}
// #endif
// #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() {
if (this.inputBoxDisabled) {
return uni.showToast({
title: 'ai正在回复中不能发送',
icon: 'none'
});
} }
// 如果开启了广告位需要登录 // 如果开启了广告位需要登录
if (this.adpid) { if (this.adpid) {
...@@ -488,15 +544,15 @@ ...@@ -488,15 +544,15 @@
this.showLastMsg() this.showLastMsg()
// dom加载完成后 清空文本内容 // dom加载完成后 清空文本内容
this.$nextTick(() => { this.$nextTick(() => {
this.content = '' this.content = ''
}) })
this.send() // 发送消息 this.send() // 发送消息
}, },
async send() { async send() {
let messages = [] let messages = []
// 复制一份,消息列表数据 // 复制一份,消息列表数据
let msgs = msgList.filter(i=>i.isDelete !== true) let msgs = 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)
...@@ -531,11 +587,11 @@ ...@@ -531,11 +587,11 @@
// 在控制台输出 向ai机器人发送的完整消息内容 // 在控制台输出 向ai机器人发送的完整消息内容
console.log('send to ai messages:', messages); console.log('send to ai messages:', messages);
// 检查是否开通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) {
// 创建消息通道 // 创建消息通道
...@@ -569,28 +625,29 @@ ...@@ -569,28 +625,29 @@
sseChannel.on('end', (e) => { sseChannel.on('end', (e) => {
// console.log('on end', e); // console.log('on end', e);
// 如果e存在且包含summarize或insufficientScore属性 // 如果e存在且包含summarize或insufficientScore属性
if (e) { if (e) {
// 更新最后一条消息 // 如果e包含summarize属性
this.updateLastMsg(lastMsg => { if (e.summarize) {
// 如果e包含illegal属性 // 设置总结
if (e.illegal) { this.setSummarize(e.summarize)
// 将最后一条消息的illegal属性更新为e的illegal属性 }else{
lastMsg.illegal = e.illegal // 更新最后一条消息
lastMsg.content = "内容涉及敏感" this.updateLastMsg(lastMsg => {
// 倒数第二条(用户发问内容)也需要设置illegal的值 // 如果e包含illegal属性
this.msgList[this.msgList.length - 2].illegal = e.illegal if (e.illegal) {
} // 将最后一条消息的illegal属性更新为e的illegal属性
// 如果e包含summarize属性 lastMsg.illegal = e.illegal
else if (e.summarize) { lastMsg.content = "内容涉及敏感"
// 将最后一条消息的summarize属性更新为e的summarize属性 // 倒数第二条(用户发问内容)也需要设置illegal的值
lastMsg.summarize = e.summarize this.msgList[this.msgList.length - 2].illegal = e.illegal
} }
// 如果e包含insufficientScore属性 // 如果e包含insufficientScore属性
else if (e.insufficientScore) { else if (e.insufficientScore) {
// 将最后一条消息的insufficientScore属性更新为e的insufficientScore属性 // 将最后一条消息的insufficientScore属性更新为e的insufficientScore属性
lastMsg.insufficientScore lastMsg.insufficientScore
} }
}) })
}
} }
// 结束流式响应 将流式响应计数值 设置为 0 // 结束流式响应 将流式响应计数值 设置为 0
...@@ -600,20 +657,20 @@ ...@@ -600,20 +657,20 @@
}) })
await sseChannel.open() // 等待通道开启 await sseChannel.open() // 等待通道开启
} }
// 导入uni-ai-chat模块,并设置customUI为true // 导入uni-ai-chat模块,并设置customUI为true
let task = uniCoTask({ let task = uniCoTask({
coName:"uni-ai-chat", coName: "uni-ai-chat",
funName:"send", funName: "send",
param:[{ param: [{
messages, // 消息列表 messages, // 消息列表
sseChannel, // 消息通道 sseChannel, // 消息通道
llmModel:this.llmModel llmModel: this.llmModel
}], }],
config:{ config: {
customUI: true customUI: true
}, },
success:res => { success: res => {
// console.log(111,res); // console.log(111,res);
if (!sseChannel) { if (!sseChannel) {
if (!res.data) { if (!res.data) {
...@@ -624,44 +681,46 @@ ...@@ -624,44 +681,46 @@
state: 100 state: 100
}) })
// console.log(res, res.reply); // console.log(res, res.reply);
let { let {
"reply": content, "reply": content,
summarize, summarize,
insufficientScore, insufficientScore,
illegal illegal
} = res.data } = res.data
if (illegal) { if (illegal) {
// 如果返回的数据包含illegal属性,就更新最后一条消息的illegal属性为true // 如果返回的数据包含illegal属性,就更新最后一条消息的illegal属性为true
this.updateLastMsg({ this.updateLastMsg({
illegal: true illegal: true
}) })
} }
// 将从云端接收到的消息添加到消息列表中 // 将从云端接收到的消息添加到消息列表中
this.msgList.push({ this.msgList.push({
// 添加消息创建时间 // 添加消息创建时间
create_time: Date.now(), create_time: Date.now(),
// 标记消息为来自AI机器人 // 标记消息为来自AI机器人
isAi: true, isAi: true,
// 添加消息内容 // 添加消息内容
content, content,
// 添加消息总结 // 添加消息分数不足标记
summarize, insufficientScore,
// 添加消息分数不足标记 // 添加消息涉敏标记
insufficientScore, illegal
// 添加消息涉敏标记
illegal
})
// 滚动窗口以显示最新的一条消息
this.$nextTick(() => {
this.showLastMsg()
}) })
// 如果回调包含总结的内容,就设置总结
if(summarize){
this.setSummarize(summarize)
}
// 滚动窗口以显示最新的一条消息
this.$nextTick(() => {
this.showLastMsg()
})
} else { } else {
// 处理 sseChannel没结束 云函数提前结束的情况 // 处理 sseChannel没结束 云函数提前结束的情况
sseChannel.close() sseChannel.close()
this.sseIndex = 0 this.sseIndex = 0
} }
}, },
fail:e => { fail: e => {
console.log(e); console.log(e);
// 获取消息列表长度 // 获取消息列表长度
let l = this.msgList.length let l = this.msgList.length
...@@ -682,21 +741,21 @@ ...@@ -682,21 +741,21 @@
content: JSON.stringify(e.message), content: JSON.stringify(e.message),
showCancel: false showCancel: false
}); });
} }
}) })
uniCoTaskList.push(task) uniCoTaskList.push(task)
}, },
closeSseChannel(){ closeSseChannel() {
// 如果存在消息通道,就关闭消息通道 // 如果存在消息通道,就关闭消息通道
if(sseChannel){ if (sseChannel) {
sseChannel.close() sseChannel.close()
sseChannel = false sseChannel = false
} }
// 清空历史网络请求(调用云对象)任务 // 清空历史网络请求(调用云对象)任务
uniCoTaskList.clear() uniCoTaskList.clear()
// 将流式响应计数值归零 // 将流式响应计数值归零
this.sseIndex = 0 this.sseIndex = 0
}, },
// 滚动窗口以显示最新的一条消息 // 滚动窗口以显示最新的一条消息
showLastMsg() { showLastMsg() {
// 等待DOM更新 // 等待DOM更新
...@@ -711,14 +770,14 @@ ...@@ -711,14 +770,14 @@
}) })
}, },
// 清空消息列表 // 清空消息列表
clearAllMsg(e) { clearAllMsg(e) {
// 弹出确认清空聊天记录的提示框 // 弹出确认清空聊天记录的提示框
uni.showModal({ uni.showModal({
title: "确认要清空聊天记录?", title: "确认要清空聊天记录?",
content: '本操作不可撤销', content: '本操作不可撤销',
complete: (e) => { complete: (e) => {
// 如果用户确认清空聊天记录 // 如果用户确认清空聊天记录
if (e.confirm) { if (e.confirm) {
// 关闭ssh请求 // 关闭ssh请求
this.closeSseChannel() this.closeSseChannel()
// 将消息列表清空 // 将消息列表清空
...@@ -737,68 +796,51 @@ ...@@ -737,68 +796,51 @@
/* #endif */ /* #endif */
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
.container *,
view, view,
textarea, textarea,
button, button {
.page {
display: flex; display: flex;
box-sizing: border-box; box-sizing: border-box;
} }
/* #endif */ page {
height: 100%;
.stop-responding { width: 100%;
font-size: 14px; }
border-radius: 3px;
margin-bottom:15px;
background-color: #f0b00a;
color: #FFF;
width: 90px;
height: 30px;
line-height: 30px;
margin:0 auto;
justify-content: center;
margin-bottom: 15px;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.stop-responding:hover{
box-shadow: 0 0 10px #aaa;
}
/* #ifndef APP-NVUE */
page,
/* #endif */
.page,
.container {
background-color: #efefef;
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
/* #ifndef APP-NVUE */ /* #endif */
height: 100vh;
/* #endif */
.stop-responding {
font-size: 14px;
border-radius: 3px;
margin-bottom: 15px;
background-color: #f0b00a;
color: #FFF;
width: 90px;
height: 30px;
line-height: 30px;
margin: 0 auto;
justify-content: center;
margin-bottom: 15px;
/* #ifdef H5 */ /* #ifdef H5 */
height: calc(100vh - 44px); cursor: pointer;
/* #endif */ /* #endif */
}
flex-direction: column; .stop-responding:hover {
align-items: center; box-shadow: 0 0 10px #aaa;
justify-content: center;
} }
/* #ifndef APP-NVUE */
.container { .container {
height: 100%;
background-color: #FAFAFA; background-color: #FAFAFA;
flex-direction: column;
align-items: center;
justify-content: center;
// border: 1px solid blue;
} }
/* #endif */
.foot-box { .foot-box {
width: 750rpx; width: 750rpx;
display: flex; display: flex;
...@@ -824,7 +866,7 @@ ...@@ -824,7 +866,7 @@
overflow: auto; overflow: auto;
/* #endif */ /* #endif */
width: 450rpx; width: 450rpx;
font-size: 14px; font-size: 14px;
} }
/* #ifdef H5 */ /* #ifdef H5 */
...@@ -852,16 +894,17 @@ ...@@ -852,16 +894,17 @@
.trash { .trash {
width: 30rpx; width: 30rpx;
margin-left: 10rpx; margin-left: 10rpx;
} }
.menu { .menu {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
} }
.menu-item{
width: 30rpx; .menu-item {
margin:0 10rpx; width: 30rpx;
margin: 0 10rpx;
} }
.send { .send {
...@@ -883,9 +926,10 @@ ...@@ -883,9 +926,10 @@
.msg-list { .msg-list {
height: 0; //不可省略,先设置为0 再由flex: 1;撑开才是一个滚动容器
flex: 1; flex: 1;
height: 1px;
width: 750rpx; width: 750rpx;
// border: 1px solid red;
} }
.noData { .noData {
...@@ -916,17 +960,11 @@ ...@@ -916,17 +960,11 @@
border-top: solid 1px #dde0e2; border-top: solid 1px #dde0e2;
} }
.page { .container * {
width: 100vw;
flex-direction: row;
}
.page * {
max-width: 950px; max-width: 950px;
} }
.container .container {
{
box-shadow: 0 0 5px #e0e1e7; box-shadow: 0 0 5px #e0e1e7;
margin-top: 44px; margin-top: 44px;
border-radius: 10px; border-radius: 10px;
...@@ -1026,6 +1064,15 @@ ...@@ -1026,6 +1064,15 @@
line-height: 28px; line-height: 28px;
} }
} }
/* #endif */
/* #endif */ .retries-box{
justify-content: center;
align-items: center;
font-size: 12px;
color: #d2071b;
}
.retries-icon{
margin-top: 1px;
margin-left: 5px;
}
</style> </style>
\ No newline at end of file
...@@ -191,8 +191,6 @@ module.exports = { ...@@ -191,8 +191,6 @@ module.exports = {
// 返回处理后的结果 // 返回处理后的结果
return result return result
}, },
// 发送消息 // 发送消息
async send({ async send({
// 消息内容 // 消息内容
...@@ -226,6 +224,31 @@ module.exports = { ...@@ -226,6 +224,31 @@ module.exports = {
} }
chatCompletionOptions.model = llmModel chatCompletionOptions.model = llmModel
} }
// 判断是否需要总结,如果需要就开始总结
// 计算消息总长度,判断是否需要总结
let needSummarize = messages.map(i => i.content).join('').length > 50,
// 总结的内容默认为 false 表示没有内容或者暂未拿到
summarizeData = false,
//成功拿到总结内容的回调函数列表
getSummarizeCallbackList = []
console.log('needSummarize',needSummarize);
if (needSummarize) {
// 获取总结
let replySummarize = getSummarize(messages)
.then((replySummarize)=>{
// console.log('replySummarize1',replySummarize);
summarizeData = replySummarize
getSummarizeCallbackList.forEach(fun=>fun())
})
.catch((error)=>{
// 抛出错误
throw error
})
}
return await chatCompletion({ return await chatCompletion({
messages, //消息内容 messages, //消息内容
sseChannel, //sse渠道对象 sseChannel, //sse渠道对象
...@@ -247,6 +270,7 @@ module.exports = { ...@@ -247,6 +270,7 @@ module.exports = {
// 获取语言模型管理器 // 获取语言模型管理器
const llmManager = uniCloud.ai.getLLMManager(llm) const llmManager = uniCloud.ai.getLLMManager(llm)
// 调用chatCompletion方法,传入参数 // 调用chatCompletion方法,传入参数
// console.log('______messages',messages);
let res = await llmManager.chatCompletion({ let res = await llmManager.chatCompletion({
...chatCompletionOptions, ...chatCompletionOptions,
messages, messages,
...@@ -290,19 +314,27 @@ module.exports = { ...@@ -290,19 +314,27 @@ module.exports = {
// 计算消息总长度 // 计算消息总长度
let totalTokens = messages.map(i => i.content).join('').length; let totalTokens = messages.map(i => i.content).join('').length;
// console.log('totalTokens',totalTokens); // console.log('totalTokens',totalTokens);
// 如果不需要总结且消息总长度超过500
if (!summarize && totalTokens > 500) { // 判断:是否有‘总结’需要带上
// 获取总结 if(needSummarize){
let replySummarize = await getSummarize(messages) if(!summarizeData){
// console.log('replySummarize',replySummarize) // 如果需要等待
await new Promise((reject,resolve)=>{
getSummarizeCallbackList.push(reject)
})
// console.log('等到了总结',summarizeData);
} else{
// console.log('直接拿到总结',summarizeData);
}
// 结束sseChannel并返回总结 // 结束sseChannel并返回总结
await channel.end({ await channel.end({
summarize: replySummarize "summarize": summarizeData
}) })
} else { }else{
// 结束sseChannel // 结束sseChannel
await channel.end() await channel.end()
} }
// 返回处理结果 // 返回处理结果
resolve({ resolve({
errCode: 0 errCode: 0
...@@ -326,21 +358,26 @@ module.exports = { ...@@ -326,21 +358,26 @@ module.exports = {
}) })
}) })
} else { } else {
// 如果不需要总结 // 如果 不是正在总结
if (summarize == false) { if (summarize == false) {
// 将回复内容添加到消息列表中 // 将回复内容添加到消息列表中
messages.push({ messages.push({
"content": res.reply, "content": res.reply,
"role": "assistant" "role": "assistant"
}) })
// 计算消息总长度
let totalTokens = messages.map(i => i.content).join('').length; // 判断:是否有‘总结’需要带上
// 如果消息总长度超过500 if(needSummarize){
if (totalTokens > 500) { if(!summarizeData){
// 获取总结 // 如果需要等待
let replySummarize = await getSummarize(messages) await new Promise((reject,resolve)=>{
// 将总结添加到返回结果中 getSummarizeCallbackList.push(reject)
res.summarize = replySummarize })
// console.log('等到了总结',summarizeData);
}else{
// console.log('直接拿到总结',summarizeData);
}
res.summarize = summarizeData
} }
} }
// 如果存在错误 // 如果存在错误
...@@ -359,23 +396,32 @@ module.exports = { ...@@ -359,23 +396,32 @@ module.exports = {
//获总结 //获总结
async function getSummarize(messages) { async function getSummarize(messages) {
messages.push({ let _messages = [...messages]
_messages.push({
"content": "请简要总结上述全部对话", "content": "请简要总结上述全部对话",
"role": "user" "role": "user"
}) })
// 调用chatCompletion函数,传入messages、summarize、stream、sseChannel参数 // 调用chatCompletion函数,传入messages、summarize、stream、sseChannel参数
let res = await chatCompletion({ let res = await chatCompletion({
// 消息内容 // 消息内容
messages, messages:_messages,
// 是否需要总结 // 是否需要总结
summarize: true, summarize: true,
// 是否需要流式返回 // 是否需要流式返回
stream: false, stream: false,
// sse渠道对象 // sse渠道对象
sseChannel: false sseChannel: false ,
// 大语言模型配置
llm
}) })
//故意延迟看看,总结比答案晚,是否成功进入等的逻辑
//function sleep(time) { return new Promise(resolve => setTimeout(resolve, time)); }
// await sleep(10000)
// console.log('getSummarize',res);
// 返回总结的文字内容 // 返回总结的文字内容
return res.reply return res.data.reply
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册