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

1.0.19

上级 a2fb542e
## 1.0.19(2023-06-05)
- 新增 支持停止响应(终止接收ai返回的答案)
- 新增 支持一键换答案(让ai重新回答一遍,得到新的答案)
- 新增 支持一键复制答案全文
- 优化 为提升用户体验;在收到ai的回答之前,不可编辑并发送新问题 改成 可以先编辑但不能发送
- 修复 由`1.0.12`重构部分逻辑引起的,发送失败后点击重复报`this.retriesSendMsg is not a function"`的问题
## 1.0.18(2023-06-02) ## 1.0.18(2023-06-02)
- 新增 支持一键复制代码块 - 新增 支持一键复制代码块
- 修复 部分情况下偶发,ai回复的内容会串到用户的提问内容中的问题 - 修复 部分情况下偶发,ai回复的内容会串到用户的提问内容中的问题
......
...@@ -10,19 +10,20 @@ ...@@ -10,19 +10,20 @@
<view class="content"> <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" @itemclick="trOnclick"></rich-text> <rich-text v-if="nodes&&nodes.length" space="nbsp" :nodes="nodes" @itemclick="trOnclick"></rich-text>
</view>
<!-- #ifdef H5 --> <view v-if="isLastMsg && adpid && msg.insufficientScore">
<view class="copy-box" :style="{left,top}"> <text style="color: red;">
<text class="copy" @click="copy">复制</text> 默认不启用广告组件(被注释),如需使用,请"去掉注释"(“重新运行”后生效)
</view> 位置:/components/uni-ai-msg/uni-ai-msg.vue 第28行,或全局搜索 uni-ad-rewarded-video
<!-- #endif --> </text>
</view> <!-- <uni-ad-rewarded-video :adpid="adpid" @onAdClose="onAdClose"></uni-ad-rewarded-video> -->
<view v-if="isLastMsg && adpid && msg.insufficientScore"> </view>
<text style="color: red;"> <view v-if="msg.isAi" class="controller">
默认不启用广告组件(被注释),如需使用,请"去掉注释"(“重新运行”后生效) <text v-if="isLastMsg" title="换一个答案" @click="changeAnswer" class="retry-icon"></text>
位置:/components/uni-ai-msg/uni-ai-msg.vue 第28行,或全局搜索 uni-ad-rewarded-video <view @click="copy" title="复制" class="copy-icon">
</text> <view class="copy-icon-a"></view>
<!-- <uni-ad-rewarded-video :adpid="adpid" @onAdClose="onAdClose"></uni-ad-rewarded-video> --> <view class="copy-icon-b"></view>
</view>
</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)"
...@@ -119,31 +120,6 @@ ...@@ -119,31 +120,6 @@
}; };
}, },
mounted() { mounted() {
// #ifdef H5
// web端限制不选中文字时出现系统右键菜单
let richTextBox = this.$refs['rich-text-box']
if (richTextBox) {
// 监听鼠标右键事件
richTextBox.$el.addEventListener('contextmenu', (e) => {
// 判断是否选中了文字内容,如果没有则限制系统默认行为(禁止弹出右键菜单)
if (!document.getSelection().toString()) {
// console.log(e);
// 设置悬浮的复制按钮的坐标值
this.top = e.y + 'px'
this.left = e.x + 'px'
// console.log(e.x);
// console.log(e.y);
// 禁止系统默认行为(禁止弹出右键菜单)
e.preventDefault()
}
})
}
// 监听全局点击事件,隐藏悬浮的复制按钮的坐标
document.addEventListener('click', () => {
this.left = "-100px"
})
// #endif
}, },
created() { created() {
this.msg = msgList[this.msgIndex] this.msg = msgList[this.msgIndex]
...@@ -201,7 +177,7 @@ ...@@ -201,7 +177,7 @@
switch (msg.state) { switch (msg.state) {
case 0: case 0:
// 发送中 // 发送中
return 'spinner-cycle' return ''
break; break;
case -100: case -100:
// 发送失败 // 发送失败
...@@ -235,13 +211,25 @@ ...@@ -235,13 +211,25 @@
} }
}) })
} }
},
changeAnswer(){
this.$emit('changeAnswer')
},
retriesSendMsg(){
this.$emit('retriesSendMsg')
}, },
// #ifdef H5 // #ifdef H5
// 复制文本内容到系统剪切板 // 复制文本内容到系统剪切板
copy() { copy() {
uni.setClipboardData({ uni.setClipboardData({
data: this.md, data: this.md,
showToast: false, showToast: false,
success() {
uni.showToast({
title: '复制成功',
icon: 'none'
});
}
}) })
// 设置悬浮的复制按钮的坐标值,使其隐藏 // 设置悬浮的复制按钮的坐标值,使其隐藏
this.left = "-100px" this.left = "-100px"
...@@ -301,9 +289,10 @@ ...@@ -301,9 +289,10 @@
justify-content: center; justify-content: center;
} }
.content { .content {
position: relative;
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
max-width: 85%; max-width: calc(85% - 15px);
/* #endif */ /* #endif */
background-color: #FFF; background-color: #FFF;
border-radius: 5px; border-radius: 5px;
...@@ -314,6 +303,56 @@ ...@@ -314,6 +303,56 @@
user-select: text; user-select: text;
cursor: text; cursor: text;
/* #endif */ /* #endif */
}
.controller {
position: absolute;
right: -25px;
bottom: 0;
width: 20px;
flex-direction: column;
height: 40px;
justify-content: flex-end;
}
.retry-icon {
font-size: 26px;
color: #d4d4d4;
height: 25px;
line-height: 15px;
margin-bottom: 5px;
}
.retry-icon:hover {
color: #BBB;
}
.retry-icon,.copy-icon {
cursor: pointer;
}
.copy-icon {
position: relative;
height: 25px;
}
.copy-icon-a,
.copy-icon-b {
position: absolute;
border: 1.5px solid #d4d4d4;
width: 10px;
height: 12px;
background-color: #FFF;
left: 2px;
top: 2px;
border-radius: 3px;
}
.copy-icon:hover .copy-icon-a,
.copy-icon:hover .copy-icon-b,{
border-color:#bbb;
}
.copy-icon-b {
top: 5px;
left: 5px;
} }
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
......
{ {
"id": "uni-ai-chat", "id": "uni-ai-chat",
"name": "uni-ai-chat", "name": "uni-ai-chat",
"version": "1.0.18", "version": "1.0.19",
"description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体", "description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
......
...@@ -4,16 +4,17 @@ ...@@ -4,16 +4,17 @@
<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="msgLength === 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">
<uni-ai-msg ref="msg" v-for="(msgIndex,index) in msgLength" :key="index" :msgIndex="index" <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"></uni-ai-msg> :show-cursor="index == msgLength - 1 && msgLength%2 === 0 && sseIndex" :isLastMsg="index == msgLength - 1"></uni-ai-msg>
<view class="tip-ai-ing" v-if="msgLength && msgLength%2 !== 0"> <view class="tip-ai-ing" v-if="msgLength && msgLength%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" 如需提速,请开通<uni-link class="uni-link" href="https://uniapp.dcloud.net.cn/uniCloud/uni-ai-chat.html"
text="[流式响应]"></uni-link> text="[流式响应]"></uni-link>
</view> </view>
</view> </view>
<view id="last-msg-item"></view> <view @click="closeSseChannel" class="stop-responding" v-if="enableStream && sseIndex"> ▣ 停止响应</view>
<view id="last-msg-item" style="height: 1px;"></view>
</scroll-view> </scroll-view>
<view class="foot-box"> <view class="foot-box">
...@@ -29,10 +30,10 @@ ...@@ -29,10 +30,10 @@
</view> </view>
<view class="textarea-box" @click="focus = true"> <view class="textarea-box" @click="focus = true">
<textarea v-model="content" :cursor-spacing="15" class="textarea" :auto-height="!isWidescreen" <textarea v-model="content" :cursor-spacing="15" class="textarea" :auto-height="!isWidescreen"
:disabled="inputBoxDisabled" :placeholder="placeholderText" :maxlength="-1" :focus="focus" @blur="focus = false" :placeholder="placeholderText" :maxlength="-1" :focus="focus" @blur="focus = false"
placeholder-class="input-placeholder"></textarea> placeholder-class="input-placeholder"></textarea>
</view> </view>
<view class="send-btn-box"> <view class="send-btn-box" :title="(msgLength && msgLength%2 !== 0) ? 'ai正在回复中不能发送':''">
<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" <button @click="beforeSendMsg" :disabled="inputBoxDisabled || !content" class="send"
type="primary">发送</button> type="primary">发送</button>
...@@ -97,16 +98,11 @@ ...@@ -97,16 +98,11 @@
}, },
// 输入框占位符文本 // 输入框占位符文本
placeholderText() { placeholderText() {
// 如果输入框被禁用,则显示“uni-ai正在回复中” // #ifdef H5
if (this.inputBoxDisabled) { // 如果屏幕宽度大于960,则显示“请输入内容,ctrl + enter 发送”,否则显示“请输入要发给uni-ai的内容”
return 'uni-ai正在回复中' return window.innerWidth > 960 ? '请输入内容,ctrl + enter 发送' : '请输入要发给uni-ai的内容'
} else { // #endif
// #ifdef H5 return '请输入要发给uni-ai的内容'
// 如果屏幕宽度大于960,则显示“请输入内容,ctrl + enter 发送”,否则显示“请输入要发给uni-ai的内容”
return window.innerWidth > 960 ? '请输入内容,ctrl + enter 发送' : '请输入要发给uni-ai的内容'
// #endif
return '请输入要发给uni-ai的内容'
}
}, },
// 获取当前环境 // 获取当前环境
NODE_ENV() { NODE_ENV() {
...@@ -327,8 +323,23 @@ ...@@ -327,8 +323,23 @@
}) })
// 发送消息 // 发送消息
this.send() this.send()
},
async changeAnswer(){
this.msgList.pop()
// 防止 偶发答案涉及敏感,重复回答时。提问内容 被卡掉无法重新问
this.updateLastMsg({
illegal:false
})
this.send()
}, },
async beforeSendMsg() { async beforeSendMsg() {
if(this.inputBoxDisabled){
return uni.showToast({
title: 'ai正在回复中不能发送',
icon: 'none'
});
}
// 如果开启了广告位需要登录 // 如果开启了广告位需要登录
if (this.adpid) { if (this.adpid) {
// 获取本地缓存的token // 获取本地缓存的token
...@@ -603,7 +614,17 @@ ...@@ -603,7 +614,17 @@
showCancel: false showCancel: false
}); });
}) })
}, },
closeSseChannel(){
if(sseChannel){
sseChannel.close()
sseChannel = false
}
// 将skip_callback设置为true,以便下一次请求可以正常回调
skip_callback = true
// 将流式响应计数值归零
this.sseIndex = 0
},
// 滚动窗口以显示最新的一条消息 // 滚动窗口以显示最新的一条消息
showLastMsg() { showLastMsg() {
// 等待DOM更新 // 等待DOM更新
...@@ -658,9 +679,29 @@ ...@@ -658,9 +679,29 @@
box-sizing: border-box; box-sizing: border-box;
} }
/* #endif */ /* #endif */
.stop-responding {
font-size: 14px;
border-radius: 3px;
margin-bottom:15px;
background-color: #f0a020;
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 */ /* #ifndef APP-NVUE */
page, page,
/* #endif */ /* #endif */
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册