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

1.2.0

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