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

1.0.12

上级 fa34d5a7
## 1.0.12(2023-05-22)
- 优化 提升性能
- 更新 更改`uni-ai-chat`云对象的`runtime`版本为`Nodejs12`
## 1.0.11(2023-05-17) ## 1.0.11(2023-05-17)
- 修复 流式响应模式,微信小程序端,部分情况下:消息内容突然变空白的问题 - 修复 流式响应模式,微信小程序端,部分情况下:消息内容突然变空白的问题
- 优化 uni-ai问题回复完成后,消息输入框自动获取焦点 - 优化 uni-ai问题回复完成后,消息输入框自动获取焦点
......
<template> <template>
<view class="msg-item">
<view class="create_time-box">
<uni-dateformat class="create_time" :date="msg.create_time" format="MM/dd hh:mm:ss"></uni-dateformat>
</view>
<view :class="{reverse:!msg.isAi}">
<view class="userInfo">
<image class="avatar" :src="msg.isAi?'../../static/uni-ai.png':'../../static/avatar.png'" mode="widthFix"></image>
</view>
<view class="content">
<view class="rich-text-box" :class="{'show-cursor':showCursor}" ref="rich-text-box"> <view class="rich-text-box" :class="{'show-cursor':showCursor}" ref="rich-text-box">
<rich-text v-if="nodes&&nodes.length" space="nbsp" :nodes="nodes"></rich-text> <rich-text v-if="nodes&&nodes.length" space="nbsp" :nodes="nodes"></rich-text>
<!-- #ifdef H5 --> <!-- #ifdef H5 -->
<view class="copy-box" :style="{left,top}"> <view class="copy-box" :style="{left,top}">
<text class="copy" @click="copy">复制</text> <text class="copy" @click="copy">复制</text>
<!-- <view v-if="left != '-100px'" class="copy-mask" @click="left = '-100px'"></view> -->
</view> </view>
<!-- #endif --> <!-- #endif -->
</view> </view>
<view v-if="msgIndex == msgIndexList.length-1 && adpid && msg.insufficientScore">
<uni-ad-rewarded-video :adpid="adpid" @onAdClose="onAdClose"></uni-ad-rewarded-video>
</view>
</view>
<uni-icons v-if="msgIndex == msgIndexList.length-1 && !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> </template>
<script> <script>
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';
...@@ -57,7 +80,11 @@ ...@@ -57,7 +80,11 @@
// 悬浮的复制按钮的左边距 // 悬浮的复制按钮的左边距
left: "-100px", left: "-100px",
// 悬浮的复制按钮的上边距 // 悬浮的复制按钮的上边距
top: "-100px" top: "-100px",
msg:{
content:""
},
msgIndexList:0
}; };
}, },
mounted() { mounted() {
...@@ -87,23 +114,35 @@ ...@@ -87,23 +114,35 @@
}) })
// #endif // #endif
}, },
props: { created() {
// 传入的markdown内容 this.msg = msgList[this.msgIndex]
md: {
type: String,
default () {
return ''
}
}, },
props: {
// // 传入的markdown内容
// md: {
// type: String,
// default () {
// return ''
// }
// },
// 是否显示鼠标闪烁的效果 // 是否显示鼠标闪烁的效果
showCursor: { showCursor: {
type: [Boolean, Number], type: [Boolean, Number],
default () { default () {
return false return false
} }
},
msgIndex:{
type: Number,
default () {
return false
}
} }
}, },
computed: { computed: {
md(){
return this.msg.content
},
nodes() { nodes() {
let htmlString = '' let htmlString = ''
// 修改转换结果的htmlString值 用于正确给界面增加鼠标闪烁的效果 // 修改转换结果的htmlString值 用于正确给界面增加鼠标闪烁的效果
...@@ -143,6 +182,103 @@ ...@@ -143,6 +182,103 @@
</script> </script>
<style lang="scss"> <style lang="scss">
/* #ifndef APP-NVUE */
view,
textarea,
button,
.page {
display: flex;
box-sizing: border-box;
}
/* #endif */
.userInfo {
flex-direction: column;
}
.msg-item {
position: relative;
width: 750rpx;
flex-direction: column;
padding: 0 15px;
padding-bottom: 15px;
}
.msgStateIcon {
position: relative;
top: 5px;
right: 5px;
align-self: center;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 2px;
}
.create_time {
font-size: 12px;
padding: 5px 0;
padding-top: 0;
color: #aaa;
text-align: center;
width: 750rpx;
/* #ifdef MP */
display: flex;
/* #endif */
justify-content: center;
}
.content {
/* #ifndef APP-NVUE */
max-width: 550rpx;
/* #endif */
background-color: #FFF;
border-radius: 5px;
padding: 12px 10px;
margin-left: 10px;
/* #ifndef APP-NVUE */
word-break: break-all;
user-select: text;
cursor: text;
/* #endif */
}
/* #ifndef APP-NVUE */
.content {
display: inline;
}
.content ::v-deep rich-text {
max-width: 550rpx;
overflow: auto;
}
/* #endif */
/* #ifdef H5 */
.content * {
display: inline;
}
/* #endif */
.reverse {
flex-direction: row-reverse;
}
.reverse .content {
margin-left: 0;
margin-right: 10px;
}
.reverse-align {
align-items: flex-end;
}
/* #ifndef VUE3 && APP-PLUS */ /* #ifndef VUE3 && APP-PLUS */
@import "@/components/uni-ai-msg/uni-ai-msg.scss"; @import "@/components/uni-ai-msg/uni-ai-msg.scss";
/* #endif */ /* #endif */
......
{ {
"id": "uni-ai-chat", "id": "uni-ai-chat",
"name": "uni-ai-chat", "name": "uni-ai-chat",
"version": "1.0.11", "version": "1.0.12",
"description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体", "description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
......
...@@ -2,30 +2,12 @@ ...@@ -2,30 +2,12 @@
<view class="page"> <view class="page">
<view class="container"> <view class="container">
<view v-if="isWidescreen" class="header">uni-ai-chat</view> <view v-if="isWidescreen" class="header">uni-ai-chat</view>
<text class="noData" v-if="msgList.length === 0">没有对话记录</text> <text class="noData" v-if="msgLength === 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">
<view v-for="(msg,index) in msgList" class="msg-item" :key="index"> <uni-ai-msg ref="msg" v-for="(msgIndex,index) in msgLength" :key="index" :msgIndex="index"
<view class="create_time-box"> :show-cursor="index == msgLength - 1 && msgLength%2 === 0 && sseIndex"
<uni-dateformat class="create_time" :date="msg.create_time" format="MM/dd hh:mm:ss"></uni-dateformat> ></uni-ai-msg>
</view> <view class="tip-ai-ing" v-if="msgLength && msgLength%2 !== 0">
<view :class="{reverse:!msg.isAi}">
<view class="userInfo">
<image class="avatar" :src="msg.isAi?'../../static/uni-ai.png':'../../static/avatar.png'" mode="widthFix"></image>
</view>
<view class="content">
<!-- <text class="copy" @click="copy">复制</text> -->
<uni-ai-msg :md="msg.content" :show-cursor="index == msgList.length-1 && msg.isAi && sseIndex"></uni-ai-msg>
<view v-if="index == msgList.length-1 && adpid && msg.insufficientScore">
<uni-ad-rewarded-video :adpid="adpid" @onAdClose="onAdClose"></uni-ad-rewarded-video>
</view>
</view>
<uni-icons v-if="index == msgList.length-1 && !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 class="tip-ai-ing" v-if="msgList.length && msgList.length%2 !== 0">
<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" text="[流式响应]"></uni-link> 如需提速,请开通<uni-link class="uni-link" href="https://uniapp.dcloud.net.cn/uniCloud/uni-ai-chat.html" text="[流式响应]"></uni-link>
...@@ -47,13 +29,15 @@ ...@@ -47,13 +29,15 @@
</view> </view>
<view class="textarea-box"> <view class="textarea-box">
<textarea v-model="content" :cursor-spacing="15" class="textarea" :auto-height="!isWidescreen" <textarea v-model="content" :cursor-spacing="15" class="textarea" :auto-height="!isWidescreen"
@keyup.shift="onKeyup('shift')" @keydown.shift="onKeydown('shift')" @keydown.enter="onKeydown('enter')" @keyup.shift="onKeyup('shift')" @keydown.shift="onKeydown('shift')"
:disabled="inputBoxDisabled" :placeholder="placeholderText" :maxlength="-1" :focus="focus" @keydown.enter="onKeydown('enter')" :disabled="inputBoxDisabled"
:placeholder="placeholderText" :maxlength="-1" :focus="focus"
placeholder-class="input-placeholder"></textarea> placeholder-class="input-placeholder"></textarea>
</view> </view>
<view class="send-btn-box"> <view class="send-btn-box">
<text v-if="isWidescreen" class="send-btn-tip">↵ 发送 / shift + ↵ 换行</text> <text v-if="isWidescreen" class="send-btn-tip">↵ 发送 / shift + ↵ 换行</text>
<button @click="beforeSendMsg" :disabled="inputBoxDisabled || !content" class="send" type="primary">发送</button> <button @click="beforeSendMsg" :disabled="inputBoxDisabled || !content" class="send"
type="primary">发送</button>
</view> </view>
</view> </view>
</view> </view>
...@@ -64,6 +48,9 @@ ...@@ -64,6 +48,9 @@
<script> <script>
// 引入配置文件 // 引入配置文件
import config from '@/config.js'; import config from '@/config.js';
import {msgList} from '@/pages/chat/msgList.js';
// 获取广告id // 获取广告id
const { const {
adpid adpid
...@@ -81,6 +68,8 @@ ...@@ -81,6 +68,8 @@
return { return {
// 使聊天窗口滚动到指定元素id的值 // 使聊天窗口滚动到指定元素id的值
scrollIntoView: "", scrollIntoView: "",
// 消息长度(个数)
msgLength:0,
// 消息列表数据 // 消息列表数据
msgList: [], msgList: [],
// 输入框的消息内容 // 输入框的消息内容
...@@ -93,7 +82,7 @@ ...@@ -93,7 +82,7 @@
isWidescreen: false, isWidescreen: false,
// 广告位id // 广告位id
adpid, adpid,
focus:false focus: false
} }
}, },
computed: { computed: {
...@@ -104,7 +93,7 @@ ...@@ -104,7 +93,7 @@
return true return true
} }
// 如果消息列表长度为奇数,则禁用输入框 // 如果消息列表长度为奇数,则禁用输入框
return !!(this.msgList.length && this.msgList.length % 2 !== 0) return !!(this.msgLength && this.msgLength % 2 !== 0)
}, },
// 输入框占位符文本 // 输入框占位符文本
placeholderText() { placeholderText() {
...@@ -127,15 +116,24 @@ ...@@ -127,15 +116,24 @@
// 监听msgList变化,将其存储到本地缓存中 // 监听msgList变化,将其存储到本地缓存中
watch: { watch: {
// #ifdef H5 // #ifdef H5
inputBoxDisabled(val){ inputBoxDisabled(val) {
this.$nextTick(()=>{ this.$nextTick(() => {
this.focus = !val this.focus = !val
console.log('this.focus',this.focus); // console.log('this.focus', this.focus);
}) })
}, },
// #endif // #endif
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存储到本地缓存中
uni.setStorage({ uni.setStorage({
"key":"uni-ai-msg", "key":"uni-ai-msg",
...@@ -147,6 +145,7 @@ ...@@ -147,6 +145,7 @@
} }
}, },
async mounted() { async mounted() {
// 如果存在广告位id且用户token未过期 // 如果存在广告位id且用户token未过期
if (this.adpid && uniCloud.getCurrentUserInfo().tokenExpired > Date.now()) { if (this.adpid && uniCloud.getCurrentUserInfo().tokenExpired > Date.now()) {
// 查询当前用户的积分 // 查询当前用户的积分
...@@ -174,8 +173,13 @@ ...@@ -174,8 +173,13 @@
// }) // })
// } // }
this.msgList = uni.getStorageSync('uni-ai-msg') || []
let _msgList = uni.getStorageSync('uni-ai-msg') || [];
if(_msgList.length){
msgList.push(..._msgList)
}
this.msgList = msgList
// 如果上一次对话中 最后一条消息ai未回复。则一启动就自动重发。 // 如果上一次对话中 最后一条消息ai未回复。则一启动就自动重发。
let length = this.msgList.length let length = this.msgList.length
...@@ -236,20 +240,20 @@ ...@@ -236,20 +240,20 @@
}, },
methods: { methods: {
// #ifdef H5 && VUE2 // #ifdef H5 && VUE2
onKeydown(keyname){ onKeydown(keyname) {
if(keyname == 'shift'){ if (keyname == 'shift') {
//按下了shift键 //按下了shift键
shiftKeyPressed = true; shiftKeyPressed = true;
} }
// 按下了回车 且 之前没按下 shift // 按下了回车 且 之前没按下 shift
if (keyname == 'enter' && !shiftKeyPressed) { if (keyname == 'enter' && !shiftKeyPressed) {
this.$nextTick(()=>{ this.$nextTick(() => {
this.beforeSendMsg(); this.beforeSendMsg();
}) })
} }
}, },
onKeyup(keyname){ onKeyup(keyname) {
if(keyname == 'shift'){ if (keyname == 'shift') {
//按下了shift键 //按下了shift键
shiftKeyPressed = false; shiftKeyPressed = false;
} }
...@@ -269,7 +273,7 @@ ...@@ -269,7 +273,7 @@
}, },
// 更新最后一条消息 // 更新最后一条消息
updateLastMsg(param) { updateLastMsg(param) {
let length = this.msgList.length let length = this.msgLength
if (length === 0) { if (length === 0) {
return return
} }
...@@ -476,7 +480,7 @@ ...@@ -476,7 +480,7 @@
// console.log('sseChannel',sseChannel); // console.log('sseChannel',sseChannel);
// 监听message事件 // 监听message事件
sseChannel.on('message',(message) => { sseChannel.on('message', (message) => {
// console.log('on message', message); // console.log('on message', message);
// 将从云端接收到的消息添加到消息列表中 // 将从云端接收到的消息添加到消息列表中
...@@ -556,10 +560,17 @@ ...@@ -556,10 +560,17 @@
// console.log(res, res.reply); // console.log(res, res.reply);
// 判断是否要跳过本次回调,防止请求未返回时,历史对话已被清空。引起对话顺序错误 导致 对话输入框卡住 // 判断是否要跳过本次回调,防止请求未返回时,历史对话已被清空。引起对话顺序错误 导致 对话输入框卡住
if (!skip_callback) { if (!skip_callback) {
let {"reply": content,summarize,insufficientScore,illegal} = res.data let {
"reply": content,
summarize,
insufficientScore,
illegal
} = res.data
if (illegal) { if (illegal) {
// 如果返回的数据包含illegal属性,就更新最后一条消息的illegal属性为true // 如果返回的数据包含illegal属性,就更新最后一条消息的illegal属性为true
this.updateLastMsg({illegal: true}) this.updateLastMsg({
illegal: true
})
} }
// 将从云端接收到的消息添加到消息列表中 // 将从云端接收到的消息添加到消息列表中
this.msgList.push({ this.msgList.push({
...@@ -577,12 +588,16 @@ ...@@ -577,12 +588,16 @@
illegal illegal
}) })
// 滚动窗口以显示最新的一条消息 // 滚动窗口以显示最新的一条消息
this.$nextTick(()=>{ this.$nextTick(() => {
this.showLastMsg() this.showLastMsg()
}) })
} else { } else {
console.log('用户点击了清空按钮,跳过前一次请求的回调。内容:', res.data.reply); console.log('用户点击了清空按钮,跳过前一次请求的回调。内容:', res.data.reply);
} }
}else{
// 处理 sseChannel没结束 云函数提前结束的情况
sseChannel.close()
this.sseIndex = 0
} }
}) })
.catch(e => { .catch(e => {
...@@ -593,6 +608,7 @@ ...@@ -593,6 +608,7 @@
// 如果最后一条消息的来源是人工智能机器人 就将流式响应计数值设置为0 // 如果最后一条消息的来源是人工智能机器人 就将流式响应计数值设置为0
if (l && sseChannel && this.msgList[l - 1].isAi) { if (l && sseChannel && this.msgList[l - 1].isAi) {
sseChannel.close()
this.sseIndex = 0 this.sseIndex = 0
} }
...@@ -659,7 +675,7 @@ ...@@ -659,7 +675,7 @@
// 将流式响应计数值归零 // 将流式响应计数值归零
this.sseIndex = 0 this.sseIndex = 0
// 将消息列表清空 // 将消息列表清空
this.msgList = [] this.msgList.splice(0,this.msgLength);
} }
} }
}); });
...@@ -748,6 +764,7 @@ ...@@ -748,6 +764,7 @@
.textarea-box .textarea::-webkit-scrollbar { .textarea-box .textarea::-webkit-scrollbar {
width: 0; width: 0;
} }
/* #endif */ /* #endif */
.input-placeholder { .input-placeholder {
...@@ -783,7 +800,6 @@ ...@@ -783,7 +800,6 @@
.send::after { .send::after {
display: none; display: none;
} }
/* #endif */ /* #endif */
...@@ -792,92 +808,6 @@ ...@@ -792,92 +808,6 @@
height: 1px; height: 1px;
width: 750rpx; width: 750rpx;
} }
.userInfo {
flex-direction: column;
}
.msg-item {
position: relative;
width: 750rpx;
flex-direction: column;
padding: 0 15px;
padding-bottom: 15px;
}
.msgStateIcon {
position: relative;
top: 5px;
right: 5px;
align-self: center;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 2px;
}
.create_time {
font-size: 12px;
padding: 5px 0;
padding-top: 0;
color: #aaa;
text-align: center;
width: 750rpx;
/* #ifdef MP */
display: flex;
/* #endif */
justify-content: center;
}
.content {
/* #ifndef APP-NVUE */
max-width: 550rpx;
/* #endif */
background-color: #FFF;
border-radius: 5px;
padding: 12px 10px;
margin-left: 10px;
/* #ifndef APP-NVUE */
word-break: break-all;
user-select: text;
cursor: text;
/* #endif */
}
/* #ifndef APP-NVUE */
.content {
display: inline;
}
.content ::v-deep rich-text {
max-width: 550rpx;
overflow: auto;
}
/* #endif */
/* #ifdef H5 */
.content * {
display: inline;
}
/* #endif */
.reverse {
flex-direction: row-reverse;
}
.reverse .content {
margin-left: 0;
margin-right: 10px;
}
.reverse-align {
align-items: flex-end;
}
.noData { .noData {
margin-top: 15px; margin-top: 15px;
text-align: center; text-align: center;
...@@ -886,7 +816,6 @@ ...@@ -886,7 +816,6 @@
font-size: 12px; font-size: 12px;
justify-content: center; justify-content: center;
} }
.tip-ai-ing { .tip-ai-ing {
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
......
export const msgList = []
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"uni-cloud-ai": {} "uni-cloud-ai": {}
}, },
"cloudfunction-config": { "cloudfunction-config": {
"timeout": 60 "timeout": 60,
"runtime":"Nodejs12"
} }
} }
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册