Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
DCloud
uni-ai-chat
提交
514360da
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看板
提交
514360da
编写于
6月 14, 2023
作者:
DCloud_JSON
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
1.2.0
上级
d1ebe1f7
变更
10
显示空白变更内容
内联
并排
Showing
10 changed file
with
516 addition
and
671 deletion
+516
-671
changelog.md
changelog.md
+3
-0
common/unicloud-co-task.js
common/unicloud-co-task.js
+0
-1
components/llm-config/llm-config.vue
components/llm-config/llm-config.vue
+1
-3
components/uni-ai-msg/uni-ai-msg.vue
components/uni-ai-msg/uni-ai-msg.vue
+21
-54
package.json
package.json
+1
-1
pages/chat/chat.vue
pages/chat/chat.vue
+131
-153
pages/chat/msgList.js
pages/chat/msgList.js
+0
-1
uniCloud-aliyun/cloudfunctions/reward-video-callback/index.js
...loud-aliyun/cloudfunctions/reward-video-callback/index.js
+1
-1
uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
+355
-456
uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-ai-chat/config.json
...unctions/common/uni-config-center/uni-ai-chat/config.json
+3
-1
未找到文件。
changelog.md
浏览文件 @
514360da
## 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)
...
...
common/unicloud-co-task.js
浏览文件 @
514360da
...
...
@@ -11,7 +11,6 @@ class Task {
complete
}
}
invoke
(
callbackName
,
...
args
)
{
if
(
this
.
status
!==
0
)
{
// console.log('此任务已被终止');
...
...
components/llm-config/llm-config.vue
浏览文件 @
514360da
...
...
@@ -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
'
)
},
...
...
components/uni-ai-msg/uni-ai-msg.vue
浏览文件 @
514360da
...
...
@@ -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
,
isLastMsg
:
{
type
:
Boolean
,
default
()
{
return
false
}
},
isLastM
sg
:
{
type
:
Boolean
,
m
sg
:
{
type
:
Object
,
default
()
{
return
false
return
{
content
:
""
,
isDelete
:
false
}
}
},
},
computed
:
{
msgContent
()
{
return
this
.
msg
.
content
},
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
;
...
...
@@ -333,6 +299,7 @@
user-select
:
text
;
cursor
:
text
;
/* #endif */
flex-direction
:
column
;
}
.menu-box
{
...
...
package.json
浏览文件 @
514360da
{
"id"
:
"uni-ai-chat"
,
"name"
:
"uni-ai-chat"
,
"version"
:
"1.
1.5
"
,
"version"
:
"1.
2.0
"
,
"description"
:
"基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体"
,
"main"
:
"main.js"
,
"scripts"
:
{
...
...
pages/chat/chat.vue
浏览文件 @
514360da
...
...
@@ -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=
"msgL
ist.l
ength === 0"
>
没有对话记录
</text>
<scroll-view
:scroll-into-view=
"scrollIntoView"
scroll-y=
"true"
class=
"msg-list"
:enable-flex=
"true"
>
<uni-ai-msg
ref=
"msg"
v-for=
"(msg
Index,index) in msgLength"
:key=
"index"
:msgIndex=
"index
"
@
retriesSendMsg=
"retriesSendMsg"
@
changeAnswer=
"changeAnswer"
:show-cursor=
"index == msgL
ength - 1 && msgL
ength%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 == msgL
ist.length - 1 && msgList.l
ength%2 === 0 && sseIndex"
:isLastMsg=
"index ==
msgList.length - 1"
@
removeMsg=
"removeMsg(index)
"
></uni-ai-msg>
<template
v-if=
"msgL
ist.l
ength%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=
"msgL
ist.l
ength"
>
<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=
"(msgL
ength && msgL
ength%2 !== 0) ? 'ai正在回复中不能发送':''"
>
<view
class=
"send-btn-box"
:title=
"(msgL
ist.length && msgList.l
ength%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
'
;
// 收集所有执行云对象的任务列表
...
...
@@ -90,13 +86,12 @@
// 键盘的shift键是否被按下
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,7 +116,7 @@
return
true
}
// 如果消息列表长度为奇数,则禁用输入框
return
!!
(
this
.
msgL
ength
&&
this
.
msgL
ength
%
2
!==
0
)
return
!!
(
this
.
msgL
ist
.
length
&&
this
.
msgList
.
l
ength
%
2
!==
0
)
},
// 输入框占位符文本
placeholderText
()
{
...
...
@@ -153,15 +147,6 @@
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
// 将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
...
...
@@ -397,6 +377,7 @@
let
{
score
}
=
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
// 防止 偶发答案涉及敏感,重复回答时。提问内容 被卡掉无法重新问
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
// 如果问题还在回答中需要先关闭
if
(
this
.
sseIndex
)
{
this
.
closeSseChannel
()
}
// #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
)
if
(
this
.
msgList
[
index
].
isAi
)
{
index
-=
1
}
// #endif
this
.
msgList
.
splice
(
index
,
2
)
},
async
beforeSendMsg
()
{
if
(
this
.
inputBoxDisabled
)
{
...
...
@@ -551,10 +526,15 @@
this
.
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)
...
...
@@ -564,7 +544,7 @@
// 将带总结的消息的 内容 更换成 总结
msgs
[
aiSummaryIndex
].
content
=
msgs
[
aiSummaryIndex
].
summarize
// 拿最后一条带直接的消息作为与ai对话的msg body
msgs
=
msgs
.
splice
(
aiSummaryIndex
,
msgs
.
length
-
1
)
msgs
=
msgs
.
splice
(
aiSummaryIndex
)
}
else
{
// 如果未总结过就直接从末尾拿10条
msgs
=
msgs
.
splice
(
-
10
)
...
...
@@ -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
}
})
}
}
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
// 滚动窗口以显示最新的一条消息
this
.
showLastMsg
()
state
=
{
sse
:
0
,
co
:
0
}
}
else
{
// console.log(1,state);
}
})
await
sseChannel
.
open
()
// 等待通道开启
}
...
...
@@ -673,8 +634,7 @@
customUI
:
true
},
success
:
res
=>
{
// console.log(111,res);
if
(
!
sseChannel
)
{
console
.
log
(
"
success
"
,
res
);
if
(
!
res
.
data
)
{
return
}
...
...
@@ -682,58 +642,76 @@
this
.
updateLastMsg
({
state
:
100
})
// console.log(res, res.reply);
let
{
"
reply
"
:
content
,
summarize
,
insufficientScore
,
illegal
}
=
res
.
data
// 特殊处理 - start
if
(
this
.
enableStream
==
false
&&
!
content
){
illegal
=
true
content
=
"
内容涉及敏感
"
}
// 特殊处理 - end
if
(
illegal
)
{
// 如果返回的数据包含illegal属性,就更新最后一条消息的illegal属性为true
// 如果返回的数据包含illegal属性,就更新最后一条消息(用户输入的问题)的illegal属性为true
this
.
updateLastMsg
({
// 添加消息涉敏标记
illegal
:
true
})
}
// 非流式模式 或者流式模式,但列表还没有数据且已经进入异常的情况下
if
(
this
.
enableStream
==
false
||
this
.
sseIndex
==
0
&&
(
illegal
||
insufficientScore
))
{
// 将从云端接收到的消息添加到消息列表中
this
.
msgList
.
push
({
//
添加消息创建时间
//
消息创建时间
create_time
:
Date
.
now
(),
// 标记消息为来自AI机器人
isAi
:
true
,
//
添加消息内容
//
消息内容
content
,
//
添加消息分数不足标记
i
nsufficientScore
,
//
添加消息涉敏标记
i
llegal
//
消息是否涉敏标记
i
llegal
,
//
本地对话是否因积分不足而终止
i
nsufficientScore
})
}
// 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
()
})
}
else
{
// 处理 sseChannel没结束 云函数提前结束的情况
sseChannel
.
close
()
this
.
sseIndex
=
0
}
},
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
.
msgL
ist
.
l
ength
);
}
}
});
...
...
pages/chat/msgList.js
已删除
100644 → 0
浏览文件 @
d1ebe1f7
export
const
msgList
=
[]
uniCloud-aliyun/cloudfunctions/reward-video-callback/index.js
浏览文件 @
514360da
...
...
@@ -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的广告奖励配置
'
)
}
...
...
uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
浏览文件 @
514360da
// 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
// jsdoc语法提示教程:https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129
// 引入utils模块中的safeRequire和checkContentSecurityEnable函数
const
{
safeRequire
,
checkContentSecurityEnable
}
=
require
(
'
./utils
'
)
const
{
safeRequire
,
checkContentSecurityEnable
}
=
require
(
'
./utils
'
)
// 引入uni-config-center模块,并创建config对象
const
createConfig
=
safeRequire
(
'
uni-config-center
'
)
const
config
=
createConfig
({
...
...
@@ -16,13 +19,13 @@ const uniIdCommon = require('uni-id-common')
module
.
exports
=
{
_before
:
async
function
()
{
_before
:
async
function
()
{
// 这里是云函数的前置方法,你可以在这里加入你需要逻辑
// 判断否调用量本云对象的send方法
if
(
this
.
getMethodName
()
==
'
send
'
){
if
(
this
.
getMethodName
()
==
'
send
'
)
{
// 从配置中心获取是否需要销毁积分
if
(
config
.
spentScore
){
if
(
config
.
spentScore
)
{
/*先校验token(用户身份令牌)是否有效,并获得用户的_id*/
// 获取客户端信息
...
...
@@ -38,46 +41,68 @@ module.exports = {
if
(
res
.
errCode
)
{
// 如果token校验出错,则抛出错误
throw
res
}
else
{
}
else
{
// 通过token校验则,拿去用户id
this
.
current_uid
=
res
.
uid
}
/* 判断剩余多少积分:拒绝对话、扣除配置的积分数 */
let
{
data
:[{
score
}]}
=
await
userscollection
.
doc
(
this
.
current_uid
).
field
({
'
score
'
:
1
}).
get
()
console
.
log
(
'
score----
'
,
score
);
let
{
data
:
[{
score
}]
}
=
await
userscollection
.
doc
(
this
.
current_uid
).
field
({
'
score
'
:
1
}).
get
()
// 如果积分余额小于与uni-ai对话一次所需消耗的积分数 即 积分不足 则抛出错误提醒客户端
if
(
score
<
config
.
spentScore
){
if
(
score
<
config
.
spentScore
)
{
throw
"
insufficientScore
"
}
// 扣除对应的积分值
await
userscollection
.
doc
(
this
.
current_uid
)
.
update
({
score
:
db
.
command
.
inc
(
-
1
*
config
.
spentScore
)
score
:
db
.
command
.
inc
(
-
1
*
config
.
spentScore
)
})
}
// 从配置中心获取内容安全配置
// console.log('config.contentSecurity',config.contentSecurity);
if
(
config
.
contentSecurity
)
{
// 引入uni-sec-check模块
const
UniSecCheck
=
safeRequire
(
'
uni-sec-check
'
)
// 创建uniSecCheck对象
const
uniSecCheck
=
new
UniSecCheck
({
provider
:
'
mp-weixin
'
,
requestId
:
this
.
getUniCloudRequestId
()
})
// 定义文本安全检测函数
this
.
textSecCheck
=
async
(
content
)
=>
{
const
uniIdconfig
=
createConfig
({
pluginId
:
'
uni-id
'
}).
config
()
// console.log('uniIdconfig',uniIdconfig);
try
{
let
{
appid
,
appsecret
}
=
uniIdconfig
[
"
mp-weixin
"
].
oauth
.
weixin
if
(
!
appid
||
!
appsecret
){
throw
'
启用内容安全,但未配置微信小程序的appid、appsecret,详情参考:https://uniapp.dcloud.net.cn/uniCloud/uni-sec-check.html#config
'
}
}
catch
(
e
){
throw
"
启用内容安全,但未配置微信小程序的appid、appsecret,详情参考:https://uniapp.dcloud.net.cn/uniCloud/uni-sec-check.html#config
"
}
// 获取sseChannel
let
{
sseChannel
}
=
this
.
getParams
()[
0
]
||
{}
let
{
sseChannel
}
=
this
.
getParams
()[
0
]
||
{}
// 如果存在sseChannel,则抛出错误
if
(
sseChannel
)
{
if
(
sseChannel
)
{
throw
{
errSubject
:
'
uni-ai-chat
'
,
errCode
:
"
sec-check
"
,
errMsg
:
"
流式响应模式,内容安全识别功能无效
"
}
}
// 引入uni-sec-check模块
const
UniSecCheck
=
safeRequire
(
'
uni-sec-check
'
)
// 创建uniSecCheck对象
const
uniSecCheck
=
new
UniSecCheck
({
provider
:
'
mp-weixin
'
,
requestId
:
this
.
getUniCloudRequestId
()
})
// 定义文本安全检测函数
this
.
textSecCheck
=
async
(
content
)
=>
{
// 检测文本
const
checkRes
=
await
uniSecCheck
.
textSecCheck
({
// 文本内容,不可超过500KB
...
...
@@ -85,11 +110,11 @@ module.exports = {
// 微信小程序端 开放的唯一用户标识符
// openid,
// 场景值(1 资料;2 评论;3 论坛;4 社交日志)
scene
:
4
,
scene
:
4
,
// 接口版本号,可选1或2,但1的检测能力很弱 支持微信登录的项目,微信小程序端 可改用模式2 详情:https://uniapp.dcloud.net.cn/uniCloud/uni-sec-check.html#%E4%BD%BF%E7%94%A8%E5%89%8D%E5%BF%85%E7%9C%8B
version
:
1
version
:
1
})
console
.
log
(
'
checkRes检测文本
'
,
checkRes
);
// console.log('checkRes检测文本', checkRes);
// 如果检测到风险内容,则抛出错误
if
(
checkRes
.
errCode
===
uniSecCheck
.
ErrorCode
.
RISK_CONTENT
)
{
console
.
error
({
...
...
@@ -100,7 +125,9 @@ module.exports = {
throw
"
uni-sec-check:illegalData
"
// 如果检测出错,则抛出错误
}
else
if
(
checkRes
.
errCode
)
{
console
.
log
(
`其他原因导致此文件未完成自动审核(错误码:
${
checkRes
.
errCode
}
,错误信息:
${
checkRes
.
errMsg
}
),需要人工审核`
);
console
.
log
(
`其他原因导致此文件未完成自动审核(错误码:
${
checkRes
.
errCode
}
,错误信息:
${
checkRes
.
errMsg
}
),需要人工审核`
);
console
.
error
({
errCode
:
checkRes
.
errCode
,
errMsg
:
checkRes
.
errMsg
,
...
...
@@ -111,10 +138,14 @@ module.exports = {
}
// 获取messages参数
let
{
messages
}
=
this
.
getParams
()[
0
]
||
{
"
messages
"
:[]}
let
{
messages
}
=
this
.
getParams
()[
0
]
||
{
"
messages
"
:
[]
}
// 将messages中的content拼接成字符串
let
contentString
=
messages
.
map
(
i
=>
i
.
content
).
join
(
'
'
)
console
.
log
(
'
contentString
'
,
contentString
);
let
contentString
=
messages
.
map
(
i
=>
i
.
content
).
join
(
'
'
)
console
.
log
(
'
contentString
'
,
contentString
);
// 对contentString进行文本安全检测
await
this
.
textSecCheck
(
contentString
)
}
...
...
@@ -124,49 +155,34 @@ module.exports = {
// 打印错误和结果
// console.log('_after',{error,result});
// 如果有错误
if
(
error
){
if
(
error
)
{
// 如果是内容安全检测错误
if
(
error
.
errCode
==
60004
||
error
==
"
uni-sec-check:illegalData
"
)
{
if
(
error
.
errCode
==
60004
||
error
==
"
uni-sec-check:illegalData
"
)
{
// 返回一个包含敏感内容提示和标记的响应体
return
{
"
data
"
:
{
"
reply
"
:
"
内容涉及敏感
"
,
"
illegal
"
:
true
"
illegal
"
:
true
},
"
errCode
"
:
0
}
}
// 其他符合响应体规范的错误,直接返回
else
if
(
error
.
errCode
&&
error
.
errMsg
)
{
else
if
(
error
.
errCode
&&
error
.
errMsg
)
{
return
error
}
// 如果是积分不足错误
else
if
(
error
==
'
insufficientScore
'
){
else
if
(
error
==
'
insufficientScore
'
)
{
// 设置回复内容
let
reply
=
"
积分不足,请看完激励视频广告后再试
"
// 获取sseChannel
let
{
sseChannel
}
=
this
.
getParams
()[
0
]
||
{}
// 如果存在sseChannel
if
(
sseChannel
){
// 反序列化sseChannel
const
channel
=
uniCloud
.
deserializeSSEChannel
(
sseChannel
)
// 向sseChannel写入回复内容
await
channel
.
write
(
reply
)
// 结束sseChannel
await
channel
.
end
({
"
insufficientScore
"
:
true
})
}
else
{
// 如果不存在sseChannel 返回一个包含回复内容和标记的响应体
return
{
"
data
"
:
{
reply
,
"
insufficientScore
"
:
true
"
insufficientScore
"
:
true
},
"
errCode
"
:
0
}
}
}
else
{
}
else
{
// 如果是其他错误
throw
error
// 直接抛出异常
}
...
...
@@ -174,15 +190,15 @@ module.exports = {
// 如果是send方法且开启了内容安全检测
if
(
this
.
getMethodName
()
==
'
send
'
&&
config
.
contentSecurity
)
{
try
{
try
{
// 对回复内容进行文本安全检测
await
this
.
textSecCheck
(
result
.
data
.
reply
)
}
catch
(
e
){
}
catch
(
e
)
{
// 如果检测到敏感内容 返回一个包含敏感内容提示和标记的响应体
return
{
"
data
"
:
{
"
reply
"
:
"
内容涉及敏感
"
,
"
illegal
"
:
true
"
illegal
"
:
true
},
"
errCode
"
:
0
}
...
...
@@ -200,232 +216,115 @@ module.exports = {
// 语言模型
llmModel
})
{
// 初次调试时,可不从客户端获取数据,直接使用下面写死在云函数里的数据
// messages = [{
// role: 'user',
// content: 'uni-app是什么,20个字以内进行说明'
// }]
// 校验客户端提交的参数
// 检查消息是否符合规范
// 校验客户端提交的消息参数是否符合规范
let
res
=
checkMessages
(
messages
)
if
(
res
.
errCode
)
{
throw
new
Error
(
res
.
errMsg
)
}
// 向uni-ai发送消息
// 调用chatCompletion函数,传入messages、sseChannel、llm参数
let
{
llm
,
chatCompletionOptions
}
=
config
// 如果客户端传了llmModel 就覆盖配置的model
if
(
llmModel
){
if
(
llmModel
.
includes
(
'
gpt-
'
)
&&
(
llm
&&
llm
.
provider
!=
"
openai
"
)){
throw
new
Error
(
'
错误:LLM的provider不是openai,但model却选了
'
+
llmModel
+
'
;请参考文档:https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#chat-completion 中model参数的说明
'
)
}
chatCompletionOptions
.
model
=
llmModel
}
// 计算消息总长度,判断是否需要总结
// console.log('messages',messages);
let
lastTimeMessages
=
messages
.
slice
(
0
,
-
1
)
// console.log('lastTimeMessages',lastTimeMessages);
let
needSummarize
=
lastTimeMessages
.
map
(
i
=>
i
.
content
).
join
(
''
).
length
>
800
,
// 总结的内容默认为 false 表示没有内容或者暂未拿到
summarizeData
=
false
,
//成功拿到总结内容的回调函数列表
getSummarizeCallbackList
=
[]
console
.
log
(
'
needSummarize
'
,
needSummarize
);
if
(
needSummarize
)
{
// 获取总结
let
replySummarize
=
getSummarize
(
lastTimeMessages
)
.
then
((
replySummarize
)
=>
{
// console.log('replySummarize1',replySummarize);
summarizeData
=
replySummarize
getSummarizeCallbackList
.
forEach
(
fun
=>
fun
())
})
.
catch
((
error
)
=>
{
// 抛出错误
throw
error
let
promiseTaskList
=
[]
let
promiseTask
=
new
Promise
((
resolve
,
reject
)
=>
{
chatCompletion
(
messages
)
.
then
((
res
)
=>
{
console
.
log
(
'
获取到问题的回答,res.reply:
'
,
res
.
reply
);
//非sse 时才有值,sse 时消息直接发往客户端了
resolve
({
type
:
"
reply
"
,
data
:
res
.
reply
});
})
}
return
await
chatCompletion
({
messages
,
//消息内容
sseChannel
,
//sse渠道对象
llm
.
catch
((
error
)
=>
{
reject
(
error
)
})
});
promiseTaskList
.
push
(
promiseTask
)
// chatCompletion函数:对话完成
async
function
chatCompletion
({
// 消息列表
messages
,
// 是否需要总结
summarize
=
false
,
// sse渠道对象
sseChannel
=
false
,
// 语言模型
llm
})
{
// console.log({llm,chatCompletionOptions});
// 获取语言模型管理器
const
llmManager
=
uniCloud
.
ai
.
getLLMManager
(
llm
)
// 调用chatCompletion方法,传入参数
// console.log('______messages',messages);
let
res
=
await
llmManager
.
chatCompletion
({
...
chatCompletionOptions
,
messages
,
stream
:
sseChannel
!==
false
// 拿到最后一次对话的消息内容(去掉最后一次,还没得到答案的提问)
let
lastTimeMessages
=
messages
.
slice
(
0
,
-
1
)
// 判断是否需要总结 (根据消息总长度是否大于800)
if
(
lastTimeMessages
.
map
(
i
=>
i
.
content
).
join
(
''
).
length
>
800
)
{
// 获取总结
lastTimeMessages
.
push
({
"
content
"
:
"
请简要总结上述全部对话
"
,
"
role
"
:
"
user
"
})
// 如果存在sseChannel
if
(
sseChannel
)
{
let
reply
=
""
return
new
Promise
((
resolve
,
reject
)
=>
{
// 反序列化sseChannel
const
channel
=
uniCloud
.
deserializeSSEChannel
(
sseChannel
)
// 判断如果是open-ai按字返回,否则按行返回
if
(
llm
&&
llm
.
provider
&&
llm
.
provider
==
"
openai
"
){
// 按字返回
res
.
on
(
'
message
'
,
async
(
message
)
=>
{
reply
+=
message
await
channel
.
write
(
message
)
// console.log('---message----', message)
let
promiseTask
=
new
Promise
((
resolve
,
reject
)
=>
{
chatCompletion
(
lastTimeMessages
,
false
).
then
((
res
)
=>
{
console
.
log
(
'
获取到总结,res:
'
,
res
);
resolve
({
type
:
"
summarize
"
,
data
:
res
.
reply
});
})
}
else
{
// 按行返回
res
.
on
(
'
line
'
,
async
(
line
)
=>
{
if
(
reply
.
length
){
line
=
"
\n\n
"
+
line
}
reply
+=
line
await
channel
.
write
(
line
)
// console.log('---line----', line)
.
catch
((
error
)
=>
{
reject
(
error
)
})
});
promiseTaskList
.
push
(
promiseTask
)
}
// 结束返回
res
.
on
(
'
end
'
,
async
()
=>
{
// console.log('---end----',reply)
// 将回复内容添加到消息列表中
messages
.
push
({
"
content
"
:
reply
,
"
role
"
:
"
assistant
"
})
// 计算消息总长度
let
totalTokens
=
messages
.
map
(
i
=>
i
.
content
).
join
(
''
).
length
;
// console.log('totalTokens',totalTokens);
// 判断:是否有‘总结’需要带上
if
(
needSummarize
){
if
(
!
summarizeData
){
// 如果需要等待
await
new
Promise
((
reject
,
resolve
)
=>
{
getSummarizeCallbackList
.
push
(
reject
)
})
// console.log('等到了总结',summarizeData);
}
else
{
// console.log('直接拿到总结',summarizeData);
let
promiseAllRes
=
await
Promise
.
all
(
promiseTaskList
)
console
.
log
(
'
Promise.all promiseRes
'
,
promiseAllRes
);
res
=
{
data
:{},
errCode
:
0
}
promiseAllRes
.
forEach
(
item
=>
{
switch
(
item
.
type
){
case
'
summarize
'
:
res
.
data
.
summarize
=
item
.
data
break
;
case
'
reply
'
:
res
.
data
.
reply
=
item
.
data
break
;
default
:
break
;
}
// 结束sseChannel并返回总结
await
channel
.
end
({
"
summarize
"
:
summarizeData
})
}
else
{
// 结束sseChannel
await
channel
.
end
()
}
return
res
// 返回处理结果
resolve
({
errCode
:
0
})
})
// 返回错误
res
.
on
(
'
error
'
,
async
(
error
)
=>
{
// 特殊处理 uni-ai默认服务商检测到内容涉及敏感的错误
if
(
error
.
errCode
==
"
60004
"
){
await
channel
.
write
(
"
内容涉及敏感
"
)
// 结束sseChannel并返回 illegal:true 表示内容涉及敏感
await
channel
.
end
({
illegal
:
true
})
return
resolve
({
errCode
:
0
})
}
console
.
error
(
'
---error----
'
,
error
)
reject
(
error
)
})
})
}
else
{
// 如果 不是正在总结
if
(
summarize
==
false
)
{
// 将回复内容添加到消息列表中
messages
.
push
({
"
content
"
:
res
.
reply
,
"
role
"
:
"
assistant
"
})
// 判断:是否有‘总结’需要带上
if
(
needSummarize
){
if
(
!
summarizeData
){
// 如果需要等待
await
new
Promise
((
reject
,
resolve
)
=>
{
getSummarizeCallbackList
.
push
(
reject
)
})
// console.log('等到了总结',summarizeData);
}
else
{
// console.log('直接拿到总结',summarizeData);
}
res
.
summarize
=
summarizeData
}
}
// 如果存在错误
if
(
res
.
errCode
){
// 抛出错误
throw
res
}
// 返回处理结果
return
{
data
:
res
,
errCode
:
0
}
// chatCompletion函数:对话完成
/**
* 校验消息内容是否符合规范
* @param {Array} messages - 消息列表
* @param {Boolean} stream - 是否启用流式响应
* @returns {Promise} - 返回结果
*/
function
chatCompletion
(
messages
,
stream
=
true
)
{
// 从uni-config-center config获取 调用chatCompletion函数,传入messages、sseChannel、llm参数
let
{
llm
,
chatCompletionOptions
}
=
config
// 如果客户端传了llmModel 就覆盖配置的model
if
(
llmModel
)
{
if
(
llmModel
.
includes
(
'
gpt-
'
)
&&
(
llm
&&
llm
.
provider
!=
"
openai
"
))
{
throw
new
Error
(
'
错误:LLM的provider不是openai,但model却选了
'
+
llmModel
+
'
;请参考文档:https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#chat-completion 中model参数的说明
'
)
}
chatCompletionOptions
.
model
=
llmModel
}
//获总结
async
function
getSummarize
(
messages
)
{
let
_messages
=
[...
messages
]
_messages
.
push
({
"
content
"
:
"
请简要总结上述全部对话
"
,
"
role
"
:
"
user
"
})
// 调用chatCompletion函数,传入messages、summarize、stream、sseChannel参数
let
res
=
await
chatCompletion
({
// 消息内容
messages
:
_messages
,
// 是否需要总结
summarize
:
true
,
// 是否需要流式返回
stream
:
false
,
// sse渠道对象
sseChannel
:
false
,
// 大语言模型配置
llm
// console.log({llm,chatCompletionOptions});
// 获取语言模型管理器
const
llmManager
=
uniCloud
.
ai
.
getLLMManager
(
llm
)
// 调用chatCompletion方法,传入参数
// console.log('______messages',messages);
return
llmManager
.
chatCompletion
({
...
chatCompletionOptions
,
messages
,
stream
:
stream
&&
sseChannel
!==
false
,
sseChannel
})
//故意延迟看看,总结比答案晚,是否成功进入等的逻辑
//function sleep(time) { return new Promise(resolve => setTimeout(resolve, time)); }
// await sleep(10000)
// console.log('getSummarize',res);
// 返回总结的文字内容
return
res
.
data
.
reply
}
/**
* 校验消息内容是否符合规范
* @param {Array} messages - 消息列表
...
...
uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-ai-chat/config.json
浏览文件 @
514360da
...
...
@@ -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.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录