Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
DCloud
uni-ai-chat
提交
dd36ebcc
U
uni-ai-chat
项目概览
DCloud
/
uni-ai-chat
通知
898
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看板
提交
dd36ebcc
编写于
4月 23, 2023
作者:
DCloud_JSON
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
修复 不兼容Vue3的问题,代码高亮不换行的问题,云函数超时问题
上级
c5879ea5
变更
15
展开全部
隐藏空白更改
内联
并排
Showing
15 changed file
with
54212 addition
and
88 deletion
+54212
-88
.hbuilderx/launch.json
.hbuilderx/launch.json
+1
-1
components/uni-ai-msg/uni-ai-msg.vue
components/uni-ai-msg/uni-ai-msg.vue
+46
-17
manifest.json
manifest.json
+2
-2
node_modules/.vite/deps/_metadata.json
node_modules/.vite/deps/_metadata.json
+23
-0
node_modules/.vite/deps/chunk-DFKQJ226.js
node_modules/.vite/deps/chunk-DFKQJ226.js
+31
-0
node_modules/.vite/deps/chunk-DFKQJ226.js.map
node_modules/.vite/deps/chunk-DFKQJ226.js.map
+7
-0
node_modules/.vite/deps/highlight__js.js
node_modules/.vite/deps/highlight__js.js
+49040
-0
node_modules/.vite/deps/highlight__js.js.map
node_modules/.vite/deps/highlight__js.js.map
+7
-0
node_modules/.vite/deps/markdown-it.js
node_modules/.vite/deps/markdown-it.js
+4988
-0
node_modules/.vite/deps/markdown-it.js.map
node_modules/.vite/deps/markdown-it.js.map
+7
-0
node_modules/.vite/deps/package.json
node_modules/.vite/deps/package.json
+1
-0
pages/chat/chat.vue
pages/chat/chat.vue
+20
-33
redme.md
redme.md
+6
-6
uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
+22
-21
uniCloud-aliyun/cloudfunctions/uni-ai-chat/package.json
uniCloud-aliyun/cloudfunctions/uni-ai-chat/package.json
+11
-8
未找到文件。
.hbuilderx/launch.json
浏览文件 @
dd36ebcc
...
...
@@ -12,7 +12,7 @@
},
"h5"
:
{
"launchtype"
:
"
local
"
"launchtype"
:
"
remote
"
},
"mp-weixin"
:
{
...
...
components/uni-ai-msg/uni-ai-msg.vue
浏览文件 @
dd36ebcc
<
template
>
<view
class=
"rich-text-box"
>
<view
class=
"rich-text-box"
:class=
"
{'show-cursor':showCursor}"
>
<rich-text
v-if=
"nodes&&nodes.length"
space=
"nbsp"
:nodes=
"nodes"
></rich-text>
</view>
</
template
>
<
script
>
import
parseHtml
from
'
./html-parser.js
'
;
const
MarkdownIt
=
require
(
'
markdown-it
'
)
const
hljs
=
require
(
'
highlight.js
'
)
import
MarkdownIt
from
"
markdown-it
"
;
import
hljs
from
'
highlight.js
'
;
const
md
=
new
MarkdownIt
({
html
:
fals
e
,
html
:
tru
e
,
highlight
:
function
(
str
,
lang
)
{
if
(
lang
&&
hljs
.
getLanguage
(
lang
))
{
try
{
return
`<pre class="hljs"><code>
${
hljs
.
highlight
(
lang
,
str
,
true
).
value
}
</code></pre>`
return
'
<pre class="hljs"><code>
'
+
hljs
.
highlight
(
lang
,
str
,
true
).
value
+
'
</code></pre>
'
;
}
catch
(
__
)
{}
}
return
'
<pre class="hljs"><code>
'
+
md
.
utils
.
escapeHtml
(
str
)
+
'
</code></pre>
'
;
}
})
export
default
{
name
:
"
msg
"
,
data
()
{
return
{
};
return
{};
},
props
:
{
md
:
{
type
:
String
,
default
()
{
return
'
# H1
\r
```js
'
+
'
\r
alert(1);
\r
```
'
return
''
}
},
showCursor
:
{
type
:
[
Boolean
,
Number
],
default
()
{
return
false
}
}
},
computed
:
{
html
()
{
// req.body.content 代表md代码
return
md
.
render
(
this
.
md
)
// req.body.content 代表md代码
return
md
.
render
(
this
.
md
+
'
<span class="cursor">|</span>
'
)
},
nodes
()
{
nodes
()
{
return
parseHtml
(
this
.
html
)
}
}
,
}
}
}
</
script
>
<
style
lang=
"scss"
>
@import
"highlight.js/styles/agate.css"
;
@import
"highlight.js/styles/a11y-dark.css"
;
/* #ifndef APP-NVUE */
.hljs
code
{
padding
:
5px
;
pre
.hljs
{
padding
:
5px
8px
;
margin
:
5px
0
;
display
:
flex
;
overflow
:
scroll
;
}
.cursor
{
display
:
none
;
}
.show-cursor
.cursor
{
display
:
inline-block
;
color
:
blue
;
font-weight
:
bold
;
animation
:
blinking
1s
infinite
;
}
@keyframes
blinking
{
from
{
opacity
:
1
.0
;
}
to
{
opacity
:
0
.0
;
}
}
/* #endif */
...
...
manifest.json
浏览文件 @
dd36ebcc
{
"name"
:
"
hello uni-ai
"
,
"name"
:
"
uni-ai-chat
"
,
"appid"
:
"__UNI__8F14B14"
,
"description"
:
""
,
"versionName"
:
"1.0.0"
,
...
...
@@ -68,7 +68,7 @@
"uniStatistics"
:
{
"enable"
:
false
},
"vueVersion"
:
"
2
"
,
"vueVersion"
:
"
3
"
,
"h5"
:
{
"unipush"
:
{
"enable"
:
true
...
...
node_modules/.vite/deps/_metadata.json
0 → 100644
浏览文件 @
dd36ebcc
{
"hash"
:
"2dbbc430"
,
"browserHash"
:
"bb2ca09f"
,
"optimized"
:
{
"markdown-it"
:
{
"src"
:
"../../markdown-it/index.js"
,
"file"
:
"markdown-it.js"
,
"fileHash"
:
"a4c68cee"
,
"needsInterop"
:
true
},
"highlight.js"
:
{
"src"
:
"../../highlight.js/es/index.js"
,
"file"
:
"highlight__js.js"
,
"fileHash"
:
"7d4e05cb"
,
"needsInterop"
:
false
}
},
"chunks"
:
{
"chunk-DFKQJ226"
:
{
"file"
:
"chunk-DFKQJ226.js"
}
}
}
\ No newline at end of file
node_modules/.vite/deps/chunk-DFKQJ226.js
0 → 100644
浏览文件 @
dd36ebcc
var
__create
=
Object
.
create
;
var
__defProp
=
Object
.
defineProperty
;
var
__getOwnPropDesc
=
Object
.
getOwnPropertyDescriptor
;
var
__getOwnPropNames
=
Object
.
getOwnPropertyNames
;
var
__getProtoOf
=
Object
.
getPrototypeOf
;
var
__hasOwnProp
=
Object
.
prototype
.
hasOwnProperty
;
var
__commonJS
=
(
cb
,
mod
)
=>
function
__require
()
{
return
mod
||
(
0
,
cb
[
__getOwnPropNames
(
cb
)[
0
]])((
mod
=
{
exports
:
{}
}).
exports
,
mod
),
mod
.
exports
;
};
var
__copyProps
=
(
to
,
from
,
except
,
desc
)
=>
{
if
(
from
&&
typeof
from
===
"
object
"
||
typeof
from
===
"
function
"
)
{
for
(
let
key
of
__getOwnPropNames
(
from
))
if
(
!
__hasOwnProp
.
call
(
to
,
key
)
&&
key
!==
except
)
__defProp
(
to
,
key
,
{
get
:
()
=>
from
[
key
],
enumerable
:
!
(
desc
=
__getOwnPropDesc
(
from
,
key
))
||
desc
.
enumerable
});
}
return
to
;
};
var
__toESM
=
(
mod
,
isNodeMode
,
target
)
=>
(
target
=
mod
!=
null
?
__create
(
__getProtoOf
(
mod
))
:
{},
__copyProps
(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode
||
!
mod
||
!
mod
.
__esModule
?
__defProp
(
target
,
"
default
"
,
{
value
:
mod
,
enumerable
:
true
})
:
target
,
mod
));
export
{
__commonJS
,
__toESM
};
//# sourceMappingURL=chunk-DFKQJ226.js.map
node_modules/.vite/deps/chunk-DFKQJ226.js.map
0 → 100644
浏览文件 @
dd36ebcc
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}
node_modules/.vite/deps/highlight__js.js
0 → 100644
浏览文件 @
dd36ebcc
此差异已折叠。
点击以展开。
node_modules/.vite/deps/highlight__js.js.map
0 → 100644
浏览文件 @
dd36ebcc
此差异已折叠。
点击以展开。
node_modules/.vite/deps/markdown-it.js
0 → 100644
浏览文件 @
dd36ebcc
此差异已折叠。
点击以展开。
node_modules/.vite/deps/markdown-it.js.map
0 → 100644
浏览文件 @
dd36ebcc
因为 它太大了无法显示 source diff 。你可以改为
查看blob
。
node_modules/.vite/deps/package.json
0 → 100644
浏览文件 @
dd36ebcc
{
"type"
:
"module"
}
\ No newline at end of file
pages/chat/chat.vue
浏览文件 @
dd36ebcc
...
...
@@ -11,9 +11,8 @@
</view>
<view
style=
"flex-direction: column;"
>
<view
class=
"content"
>
<uni-ai-msg
:md=
"msg.content"
></uni-ai-msg>
<uni-ai-msg
:md=
"msg.content"
:show-cursor=
"index == msgList.length-1 && msg.isAi && sseIndex"
></uni-ai-msg>
<!--
<text
:selectable=
"true"
>
{{
msg
.
content
}}
</text>
-->
<text
v-if=
"index == msgList.length-1 && msg.isAi && sseIndex"
class=
"blinking-cursor"
>
|
</text>
</view>
</view>
<uni-icons
v-if=
"index == msgList.length-1 && !msg.isAi && msg.state != 100 && msgStateIcon(msg)"
...
...
@@ -37,8 +36,8 @@
</view>
</view>
<label
class=
"set-stream"
>
<text
style=
"font-size: 12px;"
>
stream
:
</text>
<switch
style=
"transform: scale(0.7);"
checked=
"true"
:checked=
"stream"
@
change=
"changeStream"
/>
<text
style=
"font-size: 12px;"
>
流式响应
:
</text>
<switch
style=
"transform: scale(0.7);"
:checked=
"stream"
@
change=
"changeStream"
/>
</label>
</view>
</
template
>
...
...
@@ -78,9 +77,12 @@
}
}
},
watch
:
{
msgList
(
msgList
)
{
uni
.
setStorageSync
(
'
uni-ai-msg
'
,
msgList
)
watch
:
{
msgList
:{
handler
(
msgList
)
{
uni
.
setStorageSync
(
'
uni-ai-msg
'
,
msgList
)
},
deep
:
true
}
},
async
mounted
()
{
...
...
@@ -94,8 +96,7 @@
this
.
msgList
=
uni
.
getStorageSync
(
'
uni-ai-msg
'
)
||
[]
// this.msgList.pop()
console
.
log
(
'
this.msgList
'
,
this
.
msgList
);
// console.log('this.msgList', this.msgList);
this
.
showLastMsg
()
// #ifdef H5
...
...
@@ -124,8 +125,11 @@
// #endif
},
methods
:
{
// updateLastMsg(){
// },
changeStream
(
e
){
console
.
log
(
'
e
'
,
e
.
detail
.
value
);
//
console.log('e',e.detail.value);
this
.
stream
=
e
.
detail
.
value
},
retriesSendMsg
()
{
...
...
@@ -158,10 +162,10 @@
let
msgs
=
[...
this
.
msgList
]
// 带总结的消息 index
let
findIndex
=
[...
msgs
].
reverse
().
findIndex
(
item
=>
item
.
summarize
)
console
.
log
(
'
findIndex
'
,
findIndex
)
//
console.log('findIndex', findIndex)
if
(
findIndex
!=
-
1
)
{
let
aiSummaryIndex
=
msgs
.
length
-
findIndex
-
1
console
.
log
(
'
aiSummaryIndex
'
,
aiSummaryIndex
)
//
console.log('aiSummaryIndex', aiSummaryIndex)
msgs
[
aiSummaryIndex
].
content
=
msgs
[
aiSummaryIndex
].
summarize
// 拿最后一条带直接的消息作为与ai对话的msg body
msgs
=
msgs
.
splice
(
aiSummaryIndex
,
msgs
.
length
-
1
)
...
...
@@ -187,8 +191,9 @@
let
SSEChannel
=
false
;
if
(
this
.
stream
){
SSEChannel
=
new
uniCloud
.
SSEChannel
()
// 创建消息通道
// console.log('SSEChannel',SSEChannel);
SSEChannel
.
on
(
'
message
'
,
(
message
)
=>
{
// 监听message事件
console
.
log
(
'
on message
'
,
message
);
//
console.log('on message', message);
if
(
this
.
sseIndex
===
0
)
{
this
.
msgList
.
push
({
isAi
:
true
,
...
...
@@ -206,7 +211,7 @@
this
.
sseIndex
++
})
SSEChannel
.
on
(
'
end
'
,
(
e
)
=>
{
// 监听end事件,如果云端执行end时传了message,会在客户端end事件内收到传递的消息
console
.
log
(
'
on end
'
,
e
);
//
console.log('on end', e);
if
(
e
&&
e
.
summarize
){
let
length
=
this
.
msgList
.
length
,
lastMsg
=
this
.
msgList
[
length
-
1
]
...
...
@@ -234,7 +239,7 @@
this
.
msgList
.
splice
(
index
,
1
,
lastItem
)
if
(
!
SSEChannel
)
{
console
.
log
(
res
,
res
.
reply
);
//
console.log(res, res.reply);
this
.
msgList
.
push
({
isAi
:
true
,
content
:
res
.
reply
,
...
...
@@ -459,7 +464,6 @@
max-width
:
500rpx
;
background-color
:
#FFF
;
border-radius
:
5px
;
/* border-top-left-radius: 0; */
padding
:
12px
10px
;
margin-left
:
10px
;
/* #ifndef APP-NVUE */
...
...
@@ -486,8 +490,6 @@
.reverse
.content
{
margin-left
:
0
;
margin-right
:
10px
;
border-top-left-radius
:
15px
;
// border-top-right-radius: 0;
}
.reverse-align
{
...
...
@@ -502,21 +504,6 @@
font-size
:
12px
;
justify-content
:
center
;
}
.blinking-cursor
{
color
:
blue
;
font-weight
:
bold
;
animation
:
blinking
1s
infinite
;
}
@keyframes
blinking
{
from
{
opacity
:
1
.0
;
}
to
{
opacity
:
0
.0
;
}
}
.set-stream
{
position
:
fixed
;
...
...
redme.md
浏览文件 @
dd36ebcc
...
...
@@ -4,21 +4,21 @@ uni-ai-chat是基于[uni-ai](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html)
视频效果:
<video
controls
src=
"https://web-assets.dcloud.net.cn/unidoc/zh/uni-ai-chat/uni-ai-stream.mov"
style=
"max-width: 100%; max-height: 70vh;"
></video>
包含一个前端页面(路径:
`/pages/chat/chat.vue`
)和一个云对象(路径:
`uniCloud
-aliyun
/cloudfunctions/uni-ai-chat/index.obj.js`
)
包含一个前端页面(路径:
`/pages/chat/chat.vue`
)和一个云对象(路径:
`uniCloud/cloudfunctions/uni-ai-chat/index.obj.js`
)
## 体验步骤
1.
如之前未使用过uni-app,那请
重
头学起。
[
uni-app官网
](
https://uniapp.dcloud.net.cn
)
1.
如之前未使用过uni-app,那请
从
头学起。
[
uni-app官网
](
https://uniapp.dcloud.net.cn
)
2.
如果你还没有开通uniCloud,需要登录
[
https://unicloud.dcloud.net.cn/
](
https://unicloud.dcloud.net.cn/
)
,创建一个服务空间。
3.
打开
`uni-ai-chat`
插件下载地址:
[
https://ext.dcloud.net.cn/plugin?name=uni-ai-chat
](
https://ext.dcloud.net.cn/plugin?name=uni-ai-chat
)
4.
点击
`使用HBuilderX导入示例项目`
5.
对项目根目录uniCloud点右键选择“云服务空间初始化向导”界面按提示部署项目
6.
在uni-app项目点右键
创建uniCloud环境
,关联之前创建的服务空间。
6.
在uni-app项目点右键,关联之前创建的服务空间。
## 注意事项
uni-ai-chat支持
[
stream
](
https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#%E6%B5%81%E5%BC%8F%E5%93%8D%E5%BA%94-chat-completion-stream
)
模式,该模式会接收uni-ai的流式响应的数据,
再通过
[
sse-channel
](
https://uniapp.dcloud.net.cn/uniCloud/sse-channel.html
)
即:云函数(云对象)请求中的中间状态通知通道向客户端推送消息
uni-ai-chat支持
[
流式响应
](
https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#%E6%B5%81%E5%BC%8F%E5%93%8D%E5%BA%94-chat-completion-stream
)
模式,该模式会接收uni-ai的流式响应的数据,
通过sse-channel
[
(云函数(云对象)请求中的中间状态通知通道)
](
https://uniapp.dcloud.net.cn/uniCloud/sse-channel.html
)
向客户端推送消息,
而使用
`sse-channel`
需要先
[
开通uni-push
](
https://uniapp.dcloud.net.cn/unipush-v2.html#%E7%AC%AC%E4%B8%80%E6%AD%A5-%E5%BC%80%E9%80%9A
)
,目前uni-push2.0不支持本地调试(后续版本会支持),需要
再
在HBuilderX控制台,更改
`连接本地云函数`
为
`连接云端云函数`
。
,目前uni-push2.0不支持本地调试(后续版本会支持),需要在HBuilderX控制台,更改
`连接本地云函数`
为
`连接云端云函数`
。
uniCloud-aliyun/cloudfunctions/uni-ai-chat/index.obj.js
浏览文件 @
dd36ebcc
...
...
@@ -11,12 +11,6 @@ module.exports = {
*** 激励视频是造富神器。行业经常出现几个人的团队,月收入百万的奇迹。 ***
*/
},
/**
* send方法描述
* @param {messages}
* @param {SSEChannel}
* @returns {object} 返回值描述
*/
async
send
({
messages
,
SSEChannel
})
{
// 初次调试时,可不从客户端获取数据,直接使用下面写死在云函数里的数据
// messages = [{
...
...
@@ -32,29 +26,36 @@ module.exports = {
// 向uni-ai发送消息
return
await
chatCompletion
({
messages
,
SSEChannel
messages
,
//消息内容
SSEChannel
,
//sse渠道对象
// 以下参数参考:https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#get-llm-manager
// provider:"minimax",//llm服务商,目前支持openai、baidu、minimax。不指定时由uni-ai自动分配
// apiKey:"",//llm服务商的apiKey,如不填则使用uni-ai的key。如指定openai和baidu则必填
// accessToken:"",//llm服务商的accessToken。目前百度文心一言是必填
// proxy:""//可有效连接openai服务器的、可被uniCloud云函数连接的代理服务器地址。格式为IP或域名,域名不包含http前缀,协议层面仅支持https。配置为openai时必填
})
async
function
chatCompletion
({
messages
,
summarize
=
false
,
SSEChannel
=
false
SSEChannel
=
false
,
provider
,
apiKey
,
accessToken
,
proxy
})
{
const
llmManager
=
uniCloud
.
ai
.
getLLMManager
({
// provider: 'openai',
// apiKey: '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',
// proxy: 'sgoa.dcloud.io',
provider
:
'
minimax
'
,
// provider: 'baidu',
// accessToken: '24.3989d9fc6f2ff40813f9f9f8b006d72b.2592000.1682688547.282335-31576157'
provider
,
apiKey
,
accessToken
,
proxy
})
let
res
=
await
llmManager
.
chatCompletion
({
messages
,
tokensToGenerate
:
3000
,
tokensToGenerate
:
512
,
stream
:
SSEChannel
!==
false
})
...
...
@@ -72,7 +73,7 @@ module.exports = {
// console.log('---line----', line)
})
res
.
on
(
'
end
'
,
async
()
=>
{
console
.
log
(
'
---end----
'
,
reply
)
//
console.log('---end----',reply)
messages
.
push
({
"
content
"
:
reply
,
...
...
@@ -80,10 +81,10 @@ module.exports = {
})
let
totalTokens
=
messages
.
map
(
i
=>
i
.
content
).
join
(
''
).
length
;
console
.
error
(
'
totalTokens
'
,
totalTokens
);
// console.log
('totalTokens',totalTokens);
if
(
!
summarize
&&
totalTokens
>
500
)
{
let
replySummarize
=
await
getSummarize
(
messages
)
console
.
error
(
'
replySummarize
'
,
replySummarize
)
// console.log
('replySummarize',replySummarize)
await
channel
.
end
({
summarize
:
replySummarize
})
}
else
{
await
channel
.
end
()
...
...
uniCloud-aliyun/cloudfunctions/uni-ai-chat/package.json
浏览文件 @
dd36ebcc
{
"name"
:
"uni-ai-chat"
,
"dependencies"
:
{},
"extensions"
:
{
"uni-cloud-jql"
:
{},
"uni-cloud-ai"
:
{},
"uni-cloud-push"
:
{}
}
{
"name"
:
"uni-ai-chat"
,
"dependencies"
:
{},
"extensions"
:
{
"uni-cloud-jql"
:
{},
"uni-cloud-ai"
:
{},
"uni-cloud-push"
:
{}
},
"cloudfunction-config"
:
{
"timeout"
:
60
}
}
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录