diff --git a/components/uni-ad-rewarded-video/uni-ad-rewarded-video.vue b/components/uni-ad-rewarded-video/uni-ad-rewarded-video.vue
index a04da3030190d45ec45297e9293f2ec423677e15..d1468e59e9eb35b1f387c4ef9f738a03bc0122a1 100644
--- a/components/uni-ad-rewarded-video/uni-ad-rewarded-video.vue
+++ b/components/uni-ad-rewarded-video/uni-ad-rewarded-video.vue
@@ -9,25 +9,26 @@
-
\ No newline at end of file
diff --git a/package.json b/package.json
index b21a36c01d11b6afd92f8c1713c908b6e5c012ff..b39c41f541317d2cedce00e3b7ca7274f63e0559 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"id": "uni-ai-chat",
"name": "uni-ai-chat",
- "version": "1.0.3",
+ "version": "1.0.4",
"description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体",
"main": "main.js",
"scripts": {
diff --git a/pages/chat/chat.vue b/pages/chat/chat.vue
index 0db3b112a62e5d2d84beb57977bdf9b57ba2628d..253442bccaaa605b6ea16ab456bf08df7d9fbdbe 100644
--- a/pages/chat/chat.vue
+++ b/pages/chat/chat.vue
@@ -1,830 +1,975 @@
-
-
-
-
- 没有对话记录
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- uni-ai正在思考中...
-
- 如需提速,请开通
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js b/uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
index 1c49590401fa4e225dbb3fc322962a0e9d281c6b..cb754b1da20eccfe495bb1b92a1b5e235203de45 100644
--- a/uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
+++ b/uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
@@ -1,25 +1,25 @@
-// 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
+// 云对象教程: 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')
+// 引入uni-config-center模块,并创建config对象
const createConfig = safeRequire('uni-config-center')
const config = createConfig({
pluginId: 'uni-ai-chat'
}).config()
+// 引入uniCloud.database()方法,并创建db对象
const db = uniCloud.database();
+// 创建userscollection对象
const userscollection = db.collection('uni-id-users')
+// 引入uni-id-common模块
const uniIdCommon = require('uni-id-common')
-
-module.exports = {
+
+
+module.exports = {
_before:async function() {
- // 这里是云函数的前置方法,你可以在这里加入你需要逻辑,比如:
- /*
- 例如:使用uni-id-pages(链接地址:https://ext.dcloud.net.cn/plugin?id=8577)搭建账户体系。
- 然后再使用uni-id-common的uniIdCommon.checkToken判断用户端身份,验证不通过你可以直接`throw new Error(“token无效”)`抛出异常拦截访问。
- 如果验证通过了可以获得用户id,可以记录每一个用户id的调用次数来限制,调用多少次后必须充值(推荐用uni-pay,下载地址:https://ext.dcloud.net.cn/plugin?id=1835)
- 或者看一个激励视频广告(详情:https://uniapp.dcloud.net.cn/uni-ad/ad-rewarded-video.html)后才能继续使用
- *** 激励视频是造富神器。行业经常出现几个人的团队,月收入百万的奇迹。 ***
- */
+ // 这里是云函数的前置方法,你可以在这里加入你需要逻辑
+ // 判断否调用量本云对象的send方法
if(this.getMethodName() == 'send'){
// 从配置中心获取是否需要销毁积分
if(config.spentScore){
@@ -33,7 +33,8 @@ module.exports = {
this.uniIdCommon = uniIdCommon.createInstance({
clientInfo: this.clientInfo
})
- let res = await this.uniIdCommon.checkToken(this.clientInfo.uniIdToken)
+ // 校验token(用户身份令牌)是否有效,并获得用户的_id
+ let res = await this.uniIdCommon.checkToken(this.clientInfo.uniIdToken)
if (res.errCode) {
// 如果token校验出错,则抛出错误
throw res
@@ -44,9 +45,12 @@ module.exports = {
/* 判断剩余多少积分:拒绝对话、扣除配置的积分数 */
let {data:[{score}]} = await userscollection.doc(this.current_uid).field({'score':1}).get()
console.log('score----',score);
- if(score == 0 || score < 0){ //并发的情况下可能花超过
+ // 如果积分数小于等于0 则抛出错误提醒客户端
+ // 注意需要判断小于0 因为特殊的情况下可能花超过
+ if(score == 0 || score < 0){
throw "insufficientScore"
}
+ // 扣除对应的积分值
await userscollection.doc(this.current_uid)
.update({
score:db.command.inc(-1 * config.spentScore)
@@ -56,13 +60,18 @@ module.exports = {
// 从配置中心获取内容安全配置
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)=>{
+ // 获取sseChannel
let {sseChannel} = this.getParams()[0]||{}
+ // 如果存在sseChannel,则抛出错误
if(sseChannel){
throw {
errSubject: 'uni-ai-chat',
@@ -72,12 +81,17 @@ module.exports = {
}
// 检测文本
const checkRes = await uniSecCheck.textSecCheck({
+ // 文本内容,不可超过500KB
content,
+ // 微信小程序端 开放的唯一用户标识符
// openid,
+ // 场景值(1 资料;2 评论;3 论坛;4 社交日志)
scene:4,
- version: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
+ // 接口版本号,可选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
})
console.log('checkRes检测文本',checkRes);
+ // 如果检测到风险内容,则抛出错误
if (checkRes.errCode === uniSecCheck.ErrorCode.RISK_CONTENT) {
console.error({
errCode: checkRes.errCode,
@@ -85,7 +99,8 @@ module.exports = {
result: checkRes.result
});
throw "uni-sec-check:illegalData"
- } else if (checkRes.errCode) {
+ // 如果检测出错,则抛出错误
+ } else if (checkRes.errCode) {
console.log(`其他原因导致此文件未完成自动审核(错误码:${checkRes.errCode},错误信息:${checkRes.errMsg}),需要人工审核`);
console.error({
errCode: checkRes.errCode,
@@ -96,40 +111,57 @@ module.exports = {
}
}
+ // 获取messages参数
let {messages} = this.getParams()[0]||{"messages":[]}
+ // 将messages中的content拼接成字符串
let contentString = messages.map(i=>i.content).join(' ')
console.log('contentString',contentString);
+ // 对contentString进行文本安全检测
await this.textSecCheck(contentString)
+
}
}
},
async _after(error, result) {
- console.log('_after',{error,result});
- if(error){
- if(error.errCode == "60004" || error == "uni-sec-check:illegalData" ) {
- return {
+ // 打印错误和结果
+ console.log('_after',{error,result});
+ // 如果有错误
+ if(error){
+ // 如果是内容安全检测错误
+ if(error.errCode == "60004" || error == "uni-sec-check:illegalData" ) {
+ // 返回一个包含敏感内容提示和标记的响应体
+ return {
"data": {
"reply": "内容涉及敏感",
"illegal":true
},
"errCode": 0
}
- }else if(error.errCode && error.errMsg) {
+ }else if(error.errCode && error.errMsg) {
+ // 如果是符合响应体规范的错误
// 符合响应体规范的错误,直接返回
return error
}
- else if(error == 'insufficientScore'){
- let reply = "积分不足,请看完激励视频广告后再试"
- let {sseChannel} = this.getParams()[0]||{}
- if(sseChannel){
- const channel = uniCloud.deserializeSSEChannel(sseChannel)
- await channel.write(reply)
- await channel.end({
+ // 如果是积分不足错误
+ 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{
- return {
+
+ }else{
+ // 如果不存在sseChannel 返回一个包含回复内容和标记的响应体
+ return {
"data": {
reply,
"insufficientScore":true
@@ -137,15 +169,19 @@ module.exports = {
"errCode": 0
}
}
- }else{
- throw error // 直接抛出异常
+ }else{
+ // 如果是其他错误
+ throw error // 直接抛出异常
}
}
-
- if (this.getMethodName() == 'send' && config.contentSecurity) {
+
+ // 如果是send方法且开启了内容安全检测
+ if (this.getMethodName() == 'send' && config.contentSecurity) {
try{
- await this.textSecCheck(result.data.reply)
- }catch(e){
+ // 对回复内容进行文本安全检测
+ await this.textSecCheck(result.data.reply)
+ }catch(e){
+ // 如果检测到敏感内容 返回一个包含敏感内容提示和标记的响应体
return {
"data": {
"reply": "内容涉及敏感",
@@ -155,157 +191,223 @@ module.exports = {
}
}
}
+ // 返回处理后的结果
return result
},
- async send({
- messages,
- sseChannel
- }) {
- // 初次调试时,可不从客户端获取数据,直接使用下面写死在云函数里的数据
- // messages = [{
- // role: 'user',
- // content: 'uni-app是什么,20个字以内进行说明'
- // }]
-
+
+
+ // 发送消息
+ async send({
+ // 消息内容
+ messages,
+ // sse渠道对象
+ sseChannel
+ }) {
+
+ // 初次调试时,可不从客户端获取数据,直接使用下面写死在云函数里的数据
+ // messages = [{
+ // role: 'user',
+ // content: 'uni-app是什么,20个字以内进行说明'
+ // }]
+
// 校验客户端提交的参数
- let res = checkMessages(messages)
- if (res.errCode) {
- throw new Error(res.errMsg)
- }
-
+ // 检查消息是否符合规范
+ let res = checkMessages(messages)
+ if (res.errCode) {
+ throw new Error(res.errMsg)
+ }
+
// 向uni-ai发送消息
+ // 调用chatCompletion函数,传入messages、sseChannel、llm参数
let {llm,chatCompletionOptions} = config
return await chatCompletion({
messages, //消息内容
sseChannel, //sse渠道对象
llm
- })
- async function chatCompletion({
- messages,
- summarize = false,
- sseChannel = false,
- llm
+ })
+
+ // chatCompletion函数:对话完成
+ async function chatCompletion({
+ // 消息列表
+ messages,
+ // 是否需要总结
+ summarize = false,
+ // sse渠道对象
+ sseChannel = false,
+ // 语言模型
+ llm
}) {
- const llmManager = uniCloud.ai.getLLMManager(llm)
+ // 获取语言模型管理器
+ const llmManager = uniCloud.ai.getLLMManager(llm)
+ // 调用chatCompletion方法,传入参数
let res = await llmManager.chatCompletion({
- ...chatCompletionOptions,
- messages,
- stream: sseChannel !== false
- })
-
- if (sseChannel) {
- let reply = ""
- return new Promise((resolve, reject) => {
+ ...chatCompletionOptions,
+ messages,
+ stream: sseChannel !== false
+ })
+
+ // 如果存在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)
})
}else{
+ // 按行返回
res.on('line', async (line) => {
await channel.write(reply? ("\n\n " + line) : line)
reply += line
// console.log('---line----', line)
})
}
- 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 (!summarize && totalTokens > 500) {
- let replySummarize = await getSummarize(messages)
- // console.log('replySummarize',replySummarize)
- await channel.end({
- summarize: replySummarize
- })
- } else {
- await channel.end()
- }
- resolve({
- errCode: 0
- })
- })
- res.on('error', (err) => {
- console.error('---error----', err)
- reject(err)
- })
- })
- } else {
- if (summarize == false) {
- messages.push({
- "content": res.reply,
- "role": "assistant"
- })
- let totalTokens = messages.map(i => i.content).join('').length;
- if (totalTokens > 500) {
- let replySummarize = await getSummarize(messages)
- res.summarize = replySummarize
- }
+ // 结束返回
+ 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);
+ // 如果不需要总结且消息总长度超过500
+ if (!summarize && totalTokens > 500) {
+ // 获取总结
+ let replySummarize = await getSummarize(messages)
+ // console.log('replySummarize',replySummarize)
+ // 结束sseChannel并返回总结
+ await channel.end({
+ summarize: replySummarize
+ })
+ } else {
+ // 结束sseChannel
+ await channel.end()
+ }
+ // 返回处理结果
+ resolve({
+ errCode: 0
+ })
+ })
+ // 返回错误
+ res.on('error', (err) => {
+ console.error('---error----', err)
+ reject(err)
+ })
+ })
+ } else {
+ // 如果不需要总结
+ if (summarize == false) {
+ // 将回复内容添加到消息列表中
+ messages.push({
+ "content": res.reply,
+ "role": "assistant"
+ })
+ // 计算消息总长度
+ let totalTokens = messages.map(i => i.content).join('').length;
+ // 如果消息总长度超过500
+ if (totalTokens > 500) {
+ // 获取总结
+ let replySummarize = await getSummarize(messages)
+ // 将总结添加到返回结果中
+ res.summarize = replySummarize
+ }
}
+ // 如果存在错误
if(res.errCode){
+ // 抛出错误
throw res
}
+ // 返回处理结果
return {
data:res,
errCode: 0
- }
- }
- }
-
- //获总结
- async function getSummarize(messages) {
- messages.push({
- "content": "请简要总结上述全部对话",
- "role": "user"
- })
- // 获取总结不需要再总结summarize和stream
- let res = await chatCompletion({
- messages,
- summarize: true,
- stream: false,
- sseChannel: false
- })
- return res.reply
- }
-
- function checkMessages(messages) {
- try {
- if (messages === undefined) {
- throw "messages为必传参数"
- } else if (!Array.isArray(messages)) {
- throw "参数messages的值类型必须是[object,object...]"
- } else {
- messages.forEach(item => {
- if (typeof item != 'object') {
- throw "参数messages的值类型必须是[object,object...]"
- }
- let itemRoleArr = ["assistant", "user", "system"]
- if (!itemRoleArr.includes(item.role)) {
- throw "参数messages[{role}]的值只能是:" + itemRoleArr.join('或')
- }
- if (typeof item.content != 'string') {
- throw "参数messages[{content}]的值类型必须是字符串"
- }
- })
- }
- return {
- errCode: 0,
- }
- } catch (errMsg) {
- return {
- errSubject: 'ai-demo',
- errCode: 'param-error',
- errMsg
- }
- }
- }
- }
+ }
+ }
+ }
+
+
+ //获总结
+ async function getSummarize(messages) {
+ messages.push({
+ "content": "请简要总结上述全部对话",
+ "role": "user"
+ })
+ // 调用chatCompletion函数,传入messages、summarize、stream、sseChannel参数
+ let res = await chatCompletion({
+ // 消息内容
+ messages,
+ // 是否需要总结
+ summarize: true,
+ // 是否需要流式返回
+ stream: false,
+ // sse渠道对象
+ sseChannel: false
+ })
+ // 返回总结的文字内容
+ return res.reply
+ }
+
+
+ /**
+ * 校验消息内容是否符合规范
+ * @param {Array} messages - 消息列表
+ * @returns {Object} - 返回校验结果
+ */
+ function checkMessages(messages) {
+ try {
+ // 如果messages未定义
+ if (messages === undefined) {
+ // 抛出异常
+ throw "messages为必传参数"
+ // 如果messages不是数组
+ } else if (!Array.isArray(messages)) {
+ // 抛出异常
+ throw "参数messages的值类型必须是[object,object...]"
+ } else {
+ // 否则 遍历messages
+ messages.forEach(item => {
+ // 如果item不是对象
+ if (typeof item != 'object') {
+ // 抛出异常
+ throw "参数messages的值类型必须是[object,object...]"
+ }
+ // 定义itemRoleArr数组
+ let itemRoleArr = ["assistant", "user", "system"]
+ // 如果item的role属性不在itemRoleArr数组中
+ if (!itemRoleArr.includes(item.role)) {
+ // 抛出异常
+ throw "参数messages[{role}]的值只能是:" + itemRoleArr.join('或')
+ }
+ // 如果item的content属性不是字符串
+ if (typeof item.content != 'string') {
+ // 抛出异常
+ throw "参数messages[{content}]的值类型必须是字符串"
+ }
+ })
+ }
+ // 返回校验结果
+ return {
+ errCode: 0,
+ }
+ // 捕获异常
+ } catch (errMsg) {
+ // 返回异常信息
+ return {
+ errSubject: 'ai-demo',
+ errCode: 'param-error',
+ errMsg
+ }
+ }
+ }
+ }
}
\ No newline at end of file