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