Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
DCloud
uni-ai-chat
提交
a0bf4c43
U
uni-ai-chat
项目概览
DCloud
/
uni-ai-chat
通知
893
Star
11
Fork
8
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
2
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
U
uni-ai-chat
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
2
Issue
2
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
提交
a0bf4c43
编写于
5月 29, 2023
作者:
DCloud_JSON
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
1.0.17
上级
d47ae269
变更
5
显示空白变更内容
内联
并排
Showing
5 changed file
with
282 addition
and
238 deletion
+282
-238
changelog.md
changelog.md
+4
-0
components/uni-ai-msg/uni-ai-msg.scss
components/uni-ai-msg/uni-ai-msg.scss
+4
-1
components/uni-ai-msg/uni-ai-msg.vue
components/uni-ai-msg/uni-ai-msg.vue
+259
-199
package.json
package.json
+1
-1
pages/chat/chat.vue
pages/chat/chat.vue
+14
-37
未找到文件。
changelog.md
浏览文件 @
a0bf4c43
## 1.0.17(2023-05-29)
-
新增 显示代码行号
-
修复 代码块显示样式错误的问题
-
修复 部分情况下广告位不能正确显示的问题
## 1.0.16(2023-05-26)
## 1.0.16(2023-05-26)
-
更新 默认不启用(注释掉)广告组件
`uni-ad-rewarded-video`
-
更新 默认不启用(注释掉)广告组件
`uni-ad-rewarded-video`
## 1.0.15(2023-05-26)
## 1.0.15(2023-05-26)
...
...
components/uni-ai-msg/uni-ai-msg.scss
浏览文件 @
a0bf4c43
@import
"@/lib/highlight/github-dark.min.css"
;
@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
{
.
rich-text-box
:
:
v-deep
pre
.
hljs
{
padding
:
5px
8px
;
padding
:
5px
8px
;
margin
:
5px
0
;
margin
:
5px
0
;
...
...
components/uni-ai-msg/uni-ai-msg.vue
浏览文件 @
a0bf4c43
...
@@ -8,9 +8,8 @@
...
@@ -8,9 +8,8 @@
<image
class=
"avatar"
:src=
"msg.isAi?'../../static/uni-ai.png':'../../static/avatar.png'"
mode=
"widthFix"
></image>
<image
class=
"avatar"
:src=
"msg.isAi?'../../static/uni-ai.png':'../../static/avatar.png'"
mode=
"widthFix"
></image>
</view>
</view>
<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"
></rich-text>
<rich-text
v-if=
"nodes&&nodes.length"
space=
"nbsp"
:nodes=
"nodes"
@
itemclick=
"trOnclick"
></rich-text>
<!-- #ifdef H5 -->
<!-- #ifdef H5 -->
<view
class=
"copy-box"
:style=
"
{left,top}">
<view
class=
"copy-box"
:style=
"
{left,top}">
...
@@ -18,17 +17,15 @@
...
@@ -18,17 +17,15 @@
</view>
</view>
<!-- #endif -->
<!-- #endif -->
</view>
</view>
<view
v-if=
"isLastMsg && adpid && msg.insufficientScore"
>
<text
style=
"color: red;"
>
<view
v-if=
"msgIndex == msgIndexList.length-1 && adpid && msg.insufficientScore"
>
默认不启用广告组件(被注释),如需使用,请"去掉注释"(“重新运行”后生效)
<view
style=
"flex-direction: column;color: red;"
>
位置:/components/uni-ai-msg/uni-ai-msg.vue 第28行,或全局搜索 uni-ad-rewarded-video
<view>
默认不启用广告组件(被注释),如需使用,请"去掉注释"
</view>
</text>
<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>
-->
<!--
<uni-ad-rewarded-video
:adpid=
"adpid"
@
onAdClose=
"onAdClose"
></uni-ad-rewarded-video>
-->
</view>
</view>
</view>
</view>
<uni-icons
v-if=
"
msgIndex == msgIndexList.length-1 && !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'"
@
click=
"msg.state == -100 ? retriesSendMsg() : ''"
:color=
"msg.state===0?'#999':'#d22'"
:type=
"msgStateIcon(msg)"
class=
"msgStateIcon"
>
:type=
"msgStateIcon(msg)"
class=
"msgStateIcon"
>
</uni-icons>
</uni-icons>
...
@@ -45,7 +42,9 @@
...
@@ -45,7 +42,9 @@
adpid
adpid
}
=
config
}
=
config
import
{
msgList
}
from
'
@/pages/chat/msgList.js
'
;
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
'
;
...
@@ -59,6 +58,9 @@
...
@@ -59,6 +58,9 @@
// console.log('hljs--',hljs);
// console.log('hljs--',hljs);
// console.log('hljs--',hljs.getLanguage('js'));
// console.log('hljs--',hljs.getLanguage('js'));
// 为复制代码功能保留代码内容
let
codeDataList
=
[]
// 初始化 MarkdownIt库
// 初始化 MarkdownIt库
const
markdownIt
=
MarkdownIt
({
const
markdownIt
=
MarkdownIt
({
// 在源码中启用 HTML 标签
// 在源码中启用 HTML 标签
...
@@ -68,20 +70,41 @@
...
@@ -68,20 +70,41 @@
// if (lang && hljs.getLanguage(lang)) {
// if (lang && hljs.getLanguage(lang)) {
// console.error('lang', lang)
// console.error('lang', lang)
// try {
// 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 +
// hljs.highlight(
'
lang
'
, str, true).value +
//
'
<
/code></
pre
>
'
;
//
'
<
/code></
pre
>
'
;
// } catch (__) {}
// } catch (__) {}
// }
// }
// 经过highlight.js处理后的html
let preCode = ""
try {
try {
return
'
<
pre
class
=
"
hljs
"
style
=
"
padding: 5px 8px;margin: 5px 0;overflow: auto;
"
><
code
>
'
+
preCode = hljs.highlightAuto(str).value
hljs.highlightAuto(str).value +
} catch (err) {
'
<
/code></
pre
>
'
;
// console.log(
'
err
'
,err);
} catch (__) {}
preCode = markdownIt.utils.escapeHtml(str);
}
return
'
<
pre
class
=
"
hljs
"
style
=
"
padding: 5px 8px;margin: 5px 0;overflow: auto;
"
><
code
>
'
+
// 以换行进行分割
markdownIt.utils.escapeHtml(str) +
'
<
/code></
pre
>
'
;
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>` + htm
l
// #endif
return
`<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;display: block;"><code>
${
html
}
</code></pre>`
;
}
}
})
})
...
@@ -93,10 +116,10 @@
...
@@ -93,10 +116,10 @@
left
:
"
-100px
"
,
left
:
"
-100px
"
,
// 悬浮的复制按钮的上边距
// 悬浮的复制按钮的上边距
top
:
"
-100px
"
,
top
:
"
-100px
"
,
msg:
{
msg
:
{
content:
""
content
:
""
},
},
msgIndexList:
0,
msgIndexList
:
0
,
adpid
adpid
};
};
},
},
...
@@ -138,24 +161,30 @@
...
@@ -138,24 +161,30 @@
return
false
return
false
}
}
},
},
msgIndex:{
msgIndex
:
{
type
:
Number
,
type
:
Number
,
default
()
{
default
()
{
return
false
return
false
}
}
},
isLastMsg
:
{
type
:
Boolean
,
default
()
{
return
false
}
}
}
},
},
computed
:
{
computed
:
{
md()
{
md
()
{
return
this
.
msg
.
content
return
this
.
msg
.
content
},
},
nodes
()
{
nodes
()
{
let
htmlString
=
''
let
htmlString
=
''
// 修改转换结果的htmlString值 用于正确给界面增加鼠标闪烁的效果
// 修改转换结果的htmlString值 用于正确给界面增加鼠标闪烁的效果
// 判断markdown中代码块标识符的数量是否为偶数
// 判断markdown中代码块标识符的数量是否为偶数
if
(this.md.split("```").length%2){
if
(
this
.
md
.
split
(
"
```
"
).
length
%
2
)
{
htmlString
=
markdownIt
.
render
(
this
.
md
+
'
\n
<span class="cursor">|</span>
'
);
htmlString
=
markdownIt
.
render
(
this
.
md
+
'
\n
<span class="cursor">|</span>
'
);
}
else
{
}
else
{
htmlString
=
markdownIt
.
render
(
this
.
md
)
+
'
\n
<span class="cursor">|</span>
'
;
htmlString
=
markdownIt
.
render
(
this
.
md
)
+
'
\n
<span class="cursor">|</span>
'
;
}
}
...
@@ -172,6 +201,40 @@
...
@@ -172,6 +201,40 @@
}
}
},
},
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
// #ifdef H5
// 复制文本内容到系统剪切板
// 复制文本内容到系统剪切板
copy
()
{
copy
()
{
...
@@ -196,6 +259,7 @@
...
@@ -196,6 +259,7 @@
display
:
flex
;
display
:
flex
;
box-sizing
:
border-box
;
box-sizing
:
border-box
;
}
}
/* #endif */
/* #endif */
.userInfo
{
.userInfo
{
...
@@ -238,7 +302,7 @@
...
@@ -238,7 +302,7 @@
.content
{
.content
{
/* #ifndef APP-NVUE */
/* #ifndef APP-NVUE */
max-width
:
550rpx
;
max-width
:
85%
;
/* #endif */
/* #endif */
background-color
:
#FFF
;
background-color
:
#FFF
;
border-radius
:
5px
;
border-radius
:
5px
;
...
@@ -252,24 +316,20 @@
...
@@ -252,24 +316,20 @@
}
}
/* #ifndef APP-NVUE */
/* #ifndef APP-NVUE */
.content
{
display
:
inline
;
}
.
content
:
:
v-deep
rich-text
{
.
content
:
:
v-deep
rich-text
{
max-width
:
550rpx
;
max-width
:
100%
;
overflow
:
auto
;
overflow
:
auto
;
}
}
code
.l
:before
{
/* #endif */
color
:
#516363
;
position
:
absolute
;
/* #ifdef H5 */
left
:
15px
;
.content
*
{
content
:
counter
(
step
);
display
:
inline
;
counter-increment
:
step
;
}
}
/* #endif */
/* #endif */
.reverse
{
.reverse
{
flex-direction
:
row-reverse
;
flex-direction
:
row-reverse
;
}
}
...
...
package.json
浏览文件 @
a0bf4c43
{
{
"id"
:
"uni-ai-chat"
,
"id"
:
"uni-ai-chat"
,
"name"
:
"uni-ai-chat"
,
"name"
:
"uni-ai-chat"
,
"version"
:
"1.0.1
6
"
,
"version"
:
"1.0.1
7
"
,
"description"
:
"基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体"
,
"description"
:
"基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体"
,
"main"
:
"main.js"
,
"main"
:
"main.js"
,
"scripts"
:
{
"scripts"
:
{
...
...
pages/chat/chat.vue
浏览文件 @
a0bf4c43
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
<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"
: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"
>
<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"
>
...
@@ -27,11 +27,11 @@
...
@@ -27,11 +27,11 @@
<view
v-if=
"!isWidescreen"
class=
"trash"
>
<view
v-if=
"!isWidescreen"
class=
"trash"
>
<uni-icons
@
click=
"clear"
type=
"trash"
size=
"24"
color=
"#888"
></uni-icons>
<uni-icons
@
click=
"clear"
type=
"trash"
size=
"24"
color=
"#888"
></uni-icons>
</view>
</view>
<view
class=
"textarea-box"
>
<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"
@
keyup.shift=
"onKeyup('shift')"
@
keydown.shift=
"onKeydown('shift')"
@
keyup.shift=
"onKeyup('shift')"
@
keydown.shift=
"onKeydown('shift')"
@
keydown.enter=
"onKeydown('enter')"
:disabled=
"inputBoxDisabled"
@
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>
placeholder-class=
"input-placeholder"
></textarea>
</view>
</view>
<view
class=
"send-btn-box"
>
<view
class=
"send-btn-box"
>
...
@@ -127,7 +127,6 @@
...
@@ -127,7 +127,6 @@
// #endif
// #endif
msgList
:
{
msgList
:
{
handler
(
msgList
)
{
handler
(
msgList
)
{
let
msgLength
=
msgList
.
length
let
msgLength
=
msgList
.
length
if
(
msgLength
!=
this
.
msgLength
)
{
if
(
msgLength
!=
this
.
msgLength
)
{
this
.
msgLength
=
msgLength
this
.
msgLength
=
msgLength
...
@@ -135,7 +134,6 @@
...
@@ -135,7 +134,6 @@
this
.
updateLastMsg
(
msgList
[
msgLength
-
1
])
this
.
updateLastMsg
(
msgList
[
msgLength
-
1
])
})
})
}
}
// 将msgList存储到本地缓存中
// 将msgList存储到本地缓存中
uni
.
setStorage
({
uni
.
setStorage
({
"
key
"
:
"
uni-ai-msg
"
,
"
key
"
:
"
uni-ai-msg
"
,
...
@@ -146,6 +144,16 @@
...
@@ -146,6 +144,16 @@
deep
:
true
deep
:
true
}
}
},
},
beforeMount
()
{
// #ifdef H5
// 监听屏幕宽度变化,判断是否为宽屏 并设置isWidescreen的值
uni
.
createMediaQueryObserver
(
this
).
observe
({
minWidth
:
650
,
},
matches
=>
{
this
.
isWidescreen
=
matches
;
})
// #endif
},
async
mounted
()
{
async
mounted
()
{
// 如果存在广告位id且用户token未过期
// 如果存在广告位id且用户token未过期
...
@@ -201,16 +209,6 @@
...
@@ -201,16 +209,6 @@
this
.
showLastMsg
()
this
.
showLastMsg
()
})
})
// #ifdef H5
// 监听屏幕宽度变化,判断是否为宽屏 并设置isWidescreen的值
uni
.
createMediaQueryObserver
(
this
).
observe
({
minWidth
:
650
,
},
matches
=>
{
this
.
isWidescreen
=
matches
;
})
// #endif
// 兼容 Vue3下textarea不支持@keydown
// 兼容 Vue3下textarea不支持@keydown
// #ifdef H5 && VUE3
// #ifdef H5 && VUE3
//获得消息输入框对象
//获得消息输入框对象
...
@@ -641,27 +639,6 @@
...
@@ -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
()
{
clear
()
{
// 弹出确认清空聊天记录的提示框
// 弹出确认清空聊天记录的提示框
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录