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

1.0.17

上级 d47ae269
## 1.0.17(2023-05-29)
- 新增 显示代码行号
- 修复 代码块显示样式错误的问题
- 修复 部分情况下广告位不能正确显示的问题
## 1.0.16(2023-05-26)
- 更新 默认不启用(注释掉)广告组件`uni-ad-rewarded-video`
## 1.0.15(2023-05-26)
......
@import "@/lib/highlight/github-dark.min.css";
/* #ifndef APP-NVUE */
/* #ifndef APP-NVUE */
.rich-text-box {
max-width: 100%;
}
.rich-text-box ::v-deep pre.hljs {
padding: 5px 8px;
margin: 5px 0;
......
<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">
<rich-text v-if="nodes&&nodes.length" space="nbsp" :nodes="nodes"></rich-text>
<!-- #ifdef H5 -->
<view class="copy-box" :style="{left,top}">
<text class="copy" @click="copy">复制</text>
</view>
<!-- #endif -->
</view>
<view v-if="msgIndex == msgIndexList.length-1 && adpid && msg.insufficientScore">
<view style="flex-direction: column;color: red;">
<view>默认不启用广告组件(被注释),如需使用,请"去掉注释"</view>
<view>位置:/components/uni-ai-msg/uni-ai-msg.vue 第28行,或全局搜索 uni-ad-rewarded-video</view>
</view>
<!-- <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>
<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">
<rich-text v-if="nodes&&nodes.length" space="nbsp" :nodes="nodes" @itemclick="trOnclick"></rich-text>
<!-- #ifdef H5 -->
<view class="copy-box" :style="{left,top}">
<text class="copy" @click="copy">复制</text>
</view>
<!-- #endif -->
</view>
<view v-if="isLastMsg && adpid && msg.insufficientScore">
<text style="color: red;">
默认不启用广告组件(被注释),如需使用,请"去掉注释"(“重新运行”后生效)
位置:/components/uni-ai-msg/uni-ai-msg.vue 第28行,或全局搜索 uni-ad-rewarded-video
</text>
<!-- <uni-ad-rewarded-video :adpid="adpid" @onAdClose="onAdClose"></uni-ad-rewarded-video> -->
</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>
<script>
// 引入配置文件
import config from '@/config.js';
// 获取广告id
const {
adpid
} = config
import {msgList} from '@/pages/chat/msgList.js';
<script>
// 引入配置文件
import config from '@/config.js';
// 获取广告id
const {
adpid
} = config
import {
msgList
} from '@/pages/chat/msgList.js';
// 引入markdown-it库
import MarkdownIt from '@/lib/markdown-it.min.js';
......@@ -54,11 +53,14 @@
import hljs from "@/lib/highlight/highlight-uni.min.js";
// 引入html-parser.js库
import parseHtml from '@/lib/html-parser.js';
import parseHtml from '@/lib/html-parser.js';
// console.log('hljs--',hljs);
// console.log('hljs--',hljs.getLanguage('js'));
// 为复制代码功能保留代码内容
let codeDataList = []
// 初始化 MarkdownIt库
const markdownIt = MarkdownIt({
// 在源码中启用 HTML 标签
......@@ -68,38 +70,59 @@
// if (lang && hljs.getLanguage(lang)) {
// console.error('lang', lang)
// try {
// return '<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;"><code>' +
// return '<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;display: block;"><code>' +
// hljs.highlight('lang', str, true).value +
// '</code></pre>';
// } catch (__) {}
// }
// 经过highlight.js处理后的html
let preCode = ""
try {
return '<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;"><code>' +
hljs.highlightAuto(str).value +
'</code></pre>';
} catch (__) {}
preCode = hljs.highlightAuto(str).value
} catch (err) {
// console.log('err',err);
preCode = markdownIt.utils.escapeHtml(str);
}
// 以换行进行分割
const lines = preCode.split(/\n/).slice(0, -1)
// 添加自定义行号
let html = lines.map((item, index) => {
// 去掉空行
if( item == ''){
return ''
}
return '<li><span class="line-num" data-line="' + (index + 1) + '"></span>' + item +'</li>'
}).join('')
html = '<ol>' + html + '</ol>'
// #ifdef APP || H5
// // 复制功能~未完待续
// let tmpDom = document.createElement('div')
// tmpDom.innerHTML = html
// codeDataList.push(tmpDom.innerText)
// let codeDataIndex = Date.now()
// html = `<a class="copy-btn" code-data-index="${codeDataList.length - 1}" style="float:right;">复制</a>` + html
// #endif
return `<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;display: block;"><code>${html}</code></pre>`;
}
})
return '<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;"><code>' +
markdownIt.utils.escapeHtml(str) + '</code></pre>';
}
})
export default {
name: "msg",
data() {
return {
return {
// 悬浮的复制按钮的左边距
left: "-100px",
// 悬浮的复制按钮的上边距
top: "-100px",
msg:{
content:""
},
msgIndexList:0,
top: "-100px",
msg: {
content: ""
},
msgIndexList: 0,
adpid
};
},
},
mounted() {
// #ifdef H5
// web端限制不选中文字时出现系统右键菜单
......@@ -126,9 +149,9 @@
this.left = "-100px"
})
// #endif
},
created() {
this.msg = msgList[this.msgIndex]
},
created() {
this.msg = msgList[this.msgIndex]
},
props: {
// 是否显示鼠标闪烁的效果
......@@ -137,41 +160,81 @@
default () {
return false
}
},
msgIndex:{
},
msgIndex: {
type: Number,
default () {
return false
}
},
isLastMsg: {
type: Boolean,
default () {
return false
}
}
},
computed: {
md(){
return this.msg.content
computed: {
md() {
return this.msg.content
},
nodes() {
let htmlString = ''
// 修改转换结果的htmlString值 用于正确给界面增加鼠标闪烁的效果
// 判断markdown中代码块标识符的数量是否为偶数
if(this.md.split("```").length%2){
htmlString = markdownIt.render(this.md + ' \n <span class="cursor">|</span>');
}else{
htmlString = markdownIt.render(this.md) + ' \n <span class="cursor">|</span>';
}
// #ifndef APP-NVUE
return htmlString
// #endif
// nvue模式下将htmlString转成htmlArray,其他情况rich-text内部转
// 注:本示例项目还没使用nvue编译
// #ifdef APP-NVUE
return parseHtml(htmlString)
// #endif
nodes() {
let htmlString = ''
// 修改转换结果的htmlString值 用于正确给界面增加鼠标闪烁的效果
// 判断markdown中代码块标识符的数量是否为偶数
if (this.md.split("```").length % 2) {
htmlString = markdownIt.render(this.md + ' \n <span class="cursor">|</span>');
} else {
htmlString = markdownIt.render(this.md) + ' \n <span class="cursor">|</span>';
}
// #ifndef APP-NVUE
return htmlString
// #endif
// nvue模式下将htmlString转成htmlArray,其他情况rich-text内部转
// 注:本示例项目还没使用nvue编译
// #ifdef APP-NVUE
return parseHtml(htmlString)
// #endif
}
},
methods: {
methods: {
// 根据消息状态返回对应的图标
msgStateIcon(msg) {
switch (msg.state) {
case 0:
// 发送中
return 'spinner-cycle'
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
// console.log({attrs});
// let {"code-data-index":codeDataIndex,"class":className} = attrs
// if(className == 'copy-btn'){
// console.log('codeDataList[codeDataIndex]',codeDataList[codeDataIndex]);
// uni.setClipboardData({
// data:codeDataList[codeDataIndex],
// showToast:false
// })
// }
},
// #ifdef H5
// 复制文本内容到系统剪切板
copy() {
......@@ -187,109 +250,106 @@
}
</script>
<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;
}
.create_time-box {
margin-top: 15px;
justify-content: center;
}
<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: 85%;
/* #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 ::v-deep rich-text {
max-width: 100%;
overflow: auto;
}
code .l:before {
color: #516363;
position: absolute;
left: 15px;
content: counter(step);
counter-increment: step;
}
/* #endif */
.reverse {
flex-direction: row-reverse;
}
.reverse .content {
margin-left: 0;
margin-right: 10px;
}
.reverse-align {
align-items: flex-end;
}
.create_time-box {
margin-top: 15px;
justify-content: center;
}
/* #ifndef VUE3 && APP-PLUS */
@import "@/components/uni-ai-msg/uni-ai-msg.scss";
/* #endif */
/* #endif */
</style>
\ No newline at end of file
{
"id": "uni-ai-chat",
"name": "uni-ai-chat",
"version": "1.0.16",
"version": "1.0.17",
"description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体",
"main": "main.js",
"scripts": {
......
......@@ -5,7 +5,7 @@
<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"
:show-cursor="index == msgLength - 1 && msgLength%2 === 0 && sseIndex"></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">
<text>uni-ai正在思考中...</text>
<view v-if="NODE_ENV == 'development' && !enableStream">
......@@ -26,12 +26,12 @@
<view class="foot-box-content">
<view v-if="!isWidescreen" class="trash">
<uni-icons @click="clear" type="trash" size="24" color="#888"></uni-icons>
</view>
<view class="textarea-box">
</view>
<view class="textarea-box" @click="focus = true">
<textarea v-model="content" :cursor-spacing="15" class="textarea" :auto-height="!isWidescreen"
@keyup.shift="onKeyup('shift')" @keydown.shift="onKeydown('shift')"
@keydown.enter="onKeydown('enter')" :disabled="inputBoxDisabled"
:placeholder="placeholderText" :maxlength="-1" :focus="focus"
:placeholder="placeholderText" :maxlength="-1" :focus="focus" @blur="focus = false"
placeholder-class="input-placeholder"></textarea>
</view>
<view class="send-btn-box">
......@@ -127,7 +127,6 @@
// #endif
msgList: {
handler(msgList) {
let msgLength = msgList.length
if (msgLength != this.msgLength) {
this.msgLength = msgLength
......@@ -135,7 +134,6 @@
this.updateLastMsg(msgList[msgLength - 1])
})
}
// 将msgList存储到本地缓存中
uni.setStorage({
"key": "uni-ai-msg",
......@@ -145,6 +143,16 @@
// 深度监听msgList变化
deep: true
}
},
beforeMount() {
// #ifdef H5
// 监听屏幕宽度变化,判断是否为宽屏 并设置isWidescreen的值
uni.createMediaQueryObserver(this).observe({
minWidth: 650,
}, matches => {
this.isWidescreen = matches;
})
// #endif
},
async mounted() {
......@@ -201,16 +209,6 @@
this.showLastMsg()
})
// #ifdef H5
// 监听屏幕宽度变化,判断是否为宽屏 并设置isWidescreen的值
uni.createMediaQueryObserver(this).observe({
minWidth: 650,
}, matches => {
this.isWidescreen = matches;
})
// #endif
// 兼容 Vue3下textarea不支持@keydown
// #ifdef H5 && VUE3
//获得消息输入框对象
......@@ -641,27 +639,6 @@
})
})
},
// 根据消息状态返回对应的图标
msgStateIcon(msg) {
switch (msg.state) {
case 0:
// 发送中
return 'spinner-cycle'
break;
case -100:
// 发送失败
return 'refresh-filled'
break;
case -200:
// 禁止发送(内容不合法)
return 'info-filled'
break;
default:
// 默认不返回任何图标
return false
break;
}
},
// 清空消息列表
clear() {
// 弹出确认清空聊天记录的提示框
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册