mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 23:53:55 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27e57631a7 | ||
|
|
b1e39da9b8 | ||
|
|
e87fc766d2 | ||
|
|
942d55118b | ||
|
|
f47f3b050b | ||
|
|
e3453b1a87 | ||
|
|
d760465408 | ||
|
|
bdb2538239 | ||
|
|
ea6c144e43 | ||
|
|
a79aa455d6 | ||
|
|
6e92d169ec | ||
|
|
ed89c05245 | ||
|
|
b500d1c6bd | ||
|
|
0598b09629 | ||
|
|
e4907121f4 | ||
|
|
7c58fa37b2 | ||
|
|
2a618d2f8f | ||
|
|
10a67c772f | ||
|
|
7e8e5d34b8 | ||
|
|
c6bc20c05e | ||
|
|
1998807b69 | ||
|
|
55c0495661 | ||
|
|
dbeda01697 | ||
|
|
023dea2441 | ||
|
|
ad3fb4d8c8 | ||
|
|
176785830a | ||
|
|
d78ecfce6f | ||
|
|
179281e49c | ||
|
|
bc1aa1a089 | ||
|
|
c5b64c3ef0 | ||
|
|
2aa6c5ee49 | ||
|
|
687b6db96b | ||
|
|
a5aee109fe | ||
|
|
8ec5851027 | ||
|
|
48d629c79c | ||
|
|
14e57a1add | ||
|
|
634967b2fe | ||
|
|
e648453c67 | ||
|
|
d11cfc6923 | ||
|
|
32fbbf92fe | ||
|
|
bd981aac79 | ||
|
|
adee070bb0 | ||
|
|
03ec3bac3f | ||
|
|
af5fbb5282 | ||
|
|
9301c1889f | ||
|
|
ef2ed07c1c | ||
|
|
1e2c4c6a28 | ||
|
|
a32500a6a4 | ||
|
|
772af6c918 | ||
|
|
2a917c1e62 | ||
|
|
c32fbe69c5 | ||
|
|
3b6e8f0e4b | ||
|
|
aae2ef2152 | ||
|
|
c82b351457 | ||
|
|
04c10ef225 | ||
|
|
0b8bb728e2 | ||
|
|
35ec3ba803 | ||
|
|
8808b69303 | ||
|
|
fc573a72ea | ||
|
|
59f6566cfb | ||
|
|
d8c97a9f81 | ||
|
|
3d204fb181 | ||
|
|
209fd3fbc8 | ||
|
|
bc0ffd1bd3 | ||
|
|
f0d61442b8 | ||
|
|
e6c2fac7ec | ||
|
|
5a9b5af441 | ||
|
|
16e5dc8c78 | ||
|
|
f77970dd7d | ||
|
|
af3ca4f609 | ||
|
|
1ebfcd1f13 | ||
|
|
3ca322961e | ||
|
|
0eb0a600a7 | ||
|
|
3ed06a9e34 | ||
|
|
2fae96d60c | ||
|
|
67dde7cc47 | ||
|
|
efedc18f02 | ||
|
|
0270de1844 | ||
|
|
06b0ff139b | ||
|
|
799ea28d82 | ||
|
|
5a68252d22 | ||
|
|
97404e53fe | ||
|
|
b052158cee | ||
|
|
979ef24b79 | ||
|
|
0f4f0f01fd | ||
|
|
18e1de4f45 | ||
|
|
aa846a096f | ||
|
|
a679412230 | ||
|
|
cf3e14bb07 | ||
|
|
3a73a17253 | ||
|
|
3b27ebcd35 | ||
|
|
fa6074d284 | ||
|
|
d809cfcb01 | ||
|
|
eb2c09bbdc | ||
|
|
2cf8f79482 | ||
|
|
1c94844bb9 | ||
|
|
0c53b8f543 | ||
|
|
1518dfca30 | ||
|
|
7529c500a4 | ||
|
|
b6ed220e48 | ||
|
|
b152c5dc23 | ||
|
|
04bfddf457 | ||
|
|
9d78ea29c3 | ||
|
|
f82f364189 | ||
|
|
9705149b2b | ||
|
|
7986148013 | ||
|
|
19f5cb5b4f | ||
|
|
ea48b948bf | ||
|
|
3c5ca9f143 | ||
|
|
da72ecaa1b | ||
|
|
f297e1b648 | ||
|
|
a1973e07b7 | ||
|
|
cd58b2c3cb | ||
|
|
a5be67d04b | ||
|
|
e6dc6187c8 | ||
|
|
7f4011f9de | ||
|
|
211cf891af | ||
|
|
89a1229b53 | ||
|
|
071ed99a32 | ||
|
|
ea14be27e6 | ||
|
|
a834f163b7 | ||
|
|
7849e6b030 | ||
|
|
0d869baaa0 | ||
|
|
164eb5481c | ||
|
|
969ee3cb12 | ||
|
|
ee54d87383 | ||
|
|
849026c015 | ||
|
|
e5bb3e7791 | ||
|
|
1ffc40e71d | ||
|
|
45fd850757 | ||
|
|
8a5dac181e | ||
|
|
ecf20300ff | ||
|
|
74def844f0 | ||
|
|
a5a9bd1abe | ||
|
|
2d9b915d51 | ||
|
|
5f87fc8361 | ||
|
|
6859fdc90e | ||
|
|
b8ae0568e9 | ||
|
|
1dc6f143f3 | ||
|
|
67532dc230 | ||
|
|
b2876def99 | ||
|
|
0c73a8b6a4 | ||
|
|
c857a3164d | ||
|
|
a964192eb0 | ||
|
|
32da03818e | ||
|
|
7f17e3e08e | ||
|
|
c237e1f323 | ||
|
|
087a202e31 | ||
|
|
4b1460d938 | ||
|
|
f2aec524d2 | ||
|
|
1a97ca137c | ||
|
|
2b6f457ae6 | ||
|
|
3f4e0b4aa0 | ||
|
|
e30ded4de9 | ||
|
|
07242a84ff | ||
|
|
a2074dc0d8 | ||
|
|
28b6c85650 | ||
|
|
af300138eb | ||
|
|
2bf5725048 | ||
|
|
3d73141788 | ||
|
|
945ab29bc3 | ||
|
|
fe0f7dcf75 | ||
|
|
1a3ffbc8e2 | ||
|
|
71278b1898 | ||
|
|
0173c7abe1 | ||
|
|
8a50eea4ce | ||
|
|
b9891bb795 | ||
|
|
b903a88cd5 | ||
|
|
f3fcf2fedf | ||
|
|
a825f0874c | ||
|
|
8a5ca46418 |
@@ -112,12 +112,17 @@ celerybeat.pid
|
|||||||
# Environments
|
# Environments
|
||||||
.env
|
.env
|
||||||
.venv
|
.venv
|
||||||
|
/.venv-*/
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
|
||||||
|
# Repository-local runtimes and package-manager caches
|
||||||
|
/.runtime/
|
||||||
|
/main/manager-web/.npm-cache/
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
.spyproject
|
.spyproject
|
||||||
|
|||||||
@@ -55,110 +55,95 @@ Spearheaded by Professor Siyuan Liu's Team (South China University of Technology
|
|||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="响应速度感受" src="docs/images/demo9.png" />
|
<img alt="响应速度感受" src="docs/images/demo9.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="速度优化秘诀" src="docs/images/demo6.png" />
|
<img alt="速度优化秘诀" src="docs/images/demo6.png" /></picture>
|
||||||
</picture>
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1WEcxzFEAT" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="小智数字人 支持语音唤醒" src="docs/images/demo8.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1CKVz6UEuB" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="设备呼叫设备,打电话" src="docs/images/demo0.png" /></picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="复杂医疗场景" src="docs/images/demo1.png" />
|
<img alt="复杂医疗场景" src="docs/images/demo1.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="MQTT指令下发" src="docs/images/demo4.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="声纹识别" src="docs/images/demo14.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="控制家电开关" src="docs/images/demo5.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="MCP接入点" src="docs/images/demo13.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="多指令任务" src="docs/images/demo11.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="播放音乐" src="docs/images/demo7.png" />
|
<img alt="播放音乐查询天气播报新闻" src="docs/images/demo7.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1Z8XuYZEAS" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="天气插件" src="docs/images/demo8.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="实时打断" src="docs/images/demo10.png" />
|
<img alt="实时打断" src="docs/images/demo10.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="拍照识物品" src="docs/images/demo12.png" />
|
<img alt="拍照识物品" src="docs/images/demo12.png" /></picture>
|
||||||
</picture>
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="控制家电开关" src="docs/images/demo5.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="多指令任务" src="docs/images/demo11.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="MCP接入点" src="docs/images/demo13.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="MQTT指令下发" src="docs/images/demo4.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="声纹识别" src="docs/images/demo14.png" /></picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="自定义音色" src="docs/images/demo2.png" />
|
<img alt="自定义音色" src="docs/images/demo2.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="使用粤语交流" src="docs/images/demo3.png" />
|
<img alt="使用粤语交流" src="docs/images/demo3.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="播报新闻" src="docs/images/demo0.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ VAD:
|
|||||||
3、[如何部署MQTT网关开启MQTT+UDP协议](./mqtt-gateway-integration.md)<br/>
|
3、[如何部署MQTT网关开启MQTT+UDP协议](./mqtt-gateway-integration.md)<br/>
|
||||||
4、[如何自动拉取本项目最新代码自动编译和启动](./dev-ops-integration.md)<br/>
|
4、[如何自动拉取本项目最新代码自动编译和启动](./dev-ops-integration.md)<br/>
|
||||||
5、[如何与Nginx集成](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/791)<br/>
|
5、[如何与Nginx集成](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/791)<br/>
|
||||||
|
6、[修改代码后怎么编译自己的Docker镜像](./docker-build.md)<br/>
|
||||||
|
|
||||||
### 8、编译固件相关教程
|
### 8、编译固件相关教程
|
||||||
1、[如何自己编译小智固件](./firmware-build.md)<br/>
|
1、[如何自己编译小智固件](./firmware-build.md)<br/>
|
||||||
@@ -85,6 +86,7 @@ VAD:
|
|||||||
11、[如何集成PowerMem智能记忆](./powermem-integration.md)<br/>
|
11、[如何集成PowerMem智能记忆](./powermem-integration.md)<br/>
|
||||||
12、[如何配置天气插件查询天气](./weather-integration.md)<br/>
|
12、[如何配置天气插件查询天气](./weather-integration.md)<br/>
|
||||||
13、[如何开启设备呼叫插件](./device-call-guide.md)<br/>
|
13、[如何开启设备呼叫插件](./device-call-guide.md)<br/>
|
||||||
|
14、[如何开启联网搜索功能](./web-search-integration.md)<br/>
|
||||||
|
|
||||||
### 10、数字人相关教程
|
### 10、数字人相关教程
|
||||||
1、[数字人digital-human启动方法](./digital-human-wakeword.md)<br/>
|
1、[数字人digital-human启动方法](./digital-human-wakeword.md)<br/>
|
||||||
|
|||||||
+66
-13
@@ -1,21 +1,74 @@
|
|||||||
# 本地编译docker镜像方法
|
# 本地编译docker镜像方法
|
||||||
|
|
||||||
现在本项目已经使用github自动编译docker功能,本文档是提供给有本地编译docker镜像需求的朋友准备的。
|
现在本项目已经使用`github`的`自动编译docker镜像`功能,如果您拉取的是项目发行的镜像,您没有自己编译镜像的需求,那就忽略本文档。
|
||||||
|
|
||||||
1、安装docker
|
如果您修改了源码,然后想采用`docker`的方式部署运行,可以参照以下步骤操作:
|
||||||
```
|
|
||||||
|
## 1、环境准备
|
||||||
|
|
||||||
|
安装docker:
|
||||||
|
```bash
|
||||||
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
```
|
```
|
||||||
2、编译docker镜像
|
|
||||||
```
|
|
||||||
#进入项目根目录
|
|
||||||
# 编译server
|
|
||||||
docker build -t xiaozhi-esp32-server:server_latest -f ./Dockerfile-server .
|
|
||||||
# 编译web
|
|
||||||
docker build -t xiaozhi-esp32-server:web_latest -f ./Dockerfile-web .
|
|
||||||
|
|
||||||
# 编译完成后,可以使用docker-compose启动项目
|
## 2、编译镜像
|
||||||
# docker-compose.yml你需要修改成自己编译的镜像版本
|
|
||||||
|
当你修改好代码后,需要编译新的镜像时,需要按照以下步骤操作:
|
||||||
|
|
||||||
|
准备好你的`你的用户名`和`新的版本号`。
|
||||||
|
- 这个`你的用户名`是你在`docker hub`注册的用户名,例如`xiaozhi`。当然,如果你不需要推送到`docker hub`,你可以自由定义。
|
||||||
|
- 这个`新的版本号`是你编译的镜像版本,例如`1.2.3`,你可以根据需要自定义或者使用日期格式(例如`20260609`)主要是方便和现在运行的版本号做区分,同时也方便下次回忆你是什么时候构建的,不要和现在你本机运行的版本号相同。
|
||||||
|
|
||||||
|
进入`xiaozhi-esp32-server`项目根目录,编译 server 和 web 两个镜像:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd 项目根目录
|
||||||
|
|
||||||
|
# 编译server镜像
|
||||||
|
docker build -f Dockerfile-server -t 你的用户名/xiaozhi-esp32-server:新的版本号 .
|
||||||
|
|
||||||
|
# 编译web镜像
|
||||||
|
docker build -f Dockerfile-web -t 你的用户名/xiaozhi-esp32-server-web:新的版本号 .
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3、修改docker-compose配置
|
||||||
|
|
||||||
|
```bash
|
||||||
cd main/xiaozhi-server
|
cd main/xiaozhi-server
|
||||||
docker compose up -d
|
```
|
||||||
|
|
||||||
|
编辑 `docker-compose_all.yml` 文件,将镜像版本替换为你刚才编译的版本:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
xiaozhi-esp32-server:
|
||||||
|
image: 你的用户名/xiaozhi-esp32-server:新的版本号 # 修改为你的镜像地址
|
||||||
|
...
|
||||||
|
|
||||||
|
xiaozhi-esp32-server-web:
|
||||||
|
image: 你的用户名/xiaozhi-esp32-server-web:新的版本号 #修改为你的镜像地址
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4、重启服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 停止旧容器
|
||||||
|
docker compose -f docker-compose_all.yml down
|
||||||
|
|
||||||
|
# 启动新容器
|
||||||
|
docker compose -f docker-compose_all.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5、验证
|
||||||
|
|
||||||
|
查看日志确认服务启动正常:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看server日志
|
||||||
|
docker logs -f -n 50 xiaozhi-esp32-server
|
||||||
|
|
||||||
|
# 查看web日志
|
||||||
|
docker logs -f -n 50 xiaozhi-esp32-server-web
|
||||||
```
|
```
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 289 KiB After Width: | Height: | Size: 152 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 143 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 108 KiB |
+52
-67
@@ -54,110 +54,95 @@ Möchten Sie die Nutzungseffekte sehen? Klicken Sie auf die Videos unten 🎥
|
|||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="响应速度感受" src="../images/demo9.png" />
|
<img alt="响应速度感受" src="docs/images/demo9.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="速度优化秘诀" src="../images/demo6.png" />
|
<img alt="速度优化秘诀" src="docs/images/demo6.png" /></picture>
|
||||||
</picture>
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1WEcxzFEAT" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="小智数字人 支持语音唤醒" src="docs/images/demo8.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1CKVz6UEuB" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="设备呼叫设备,打电话" src="docs/images/demo0.png" /></picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="复杂医疗场景" src="../images/demo1.png" />
|
<img alt="复杂医疗场景" src="docs/images/demo1.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="MQTT指令下发" src="../images/demo4.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="声纹识别" src="../images/demo14.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="控制家电开关" src="../images/demo5.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="MCP接入点" src="../images/demo13.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="多指令任务" src="../images/demo11.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="播放音乐" src="../images/demo7.png" />
|
<img alt="播放音乐查询天气播报新闻" src="docs/images/demo7.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1Z8XuYZEAS" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="天气插件" src="../images/demo8.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="实时打断" src="../images/demo10.png" />
|
<img alt="实时打断" src="docs/images/demo10.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="拍照识物品" src="../images/demo12.png" />
|
<img alt="拍照识物品" src="docs/images/demo12.png" /></picture>
|
||||||
</picture>
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="控制家电开关" src="docs/images/demo5.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="多指令任务" src="docs/images/demo11.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="MCP接入点" src="docs/images/demo13.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="MQTT指令下发" src="docs/images/demo4.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="声纹识别" src="docs/images/demo14.png" /></picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="自定义音色" src="../images/demo2.png" />
|
<img alt="自定义音色" src="docs/images/demo2.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="使用粤语交流" src="../images/demo3.png" />
|
<img alt="使用粤语交流" src="docs/images/demo3.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="播报新闻" src="../images/demo0.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
+52
-67
@@ -54,110 +54,95 @@ Want to see the usage effects? Click the videos below 🎥
|
|||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="响应速度感受" src="../images/demo9.png" />
|
<img alt="响应速度感受" src="docs/images/demo9.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="速度优化秘诀" src="../images/demo6.png" />
|
<img alt="速度优化秘诀" src="docs/images/demo6.png" /></picture>
|
||||||
</picture>
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1WEcxzFEAT" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="小智数字人 支持语音唤醒" src="docs/images/demo8.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1CKVz6UEuB" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="设备呼叫设备,打电话" src="docs/images/demo0.png" /></picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="复杂医疗场景" src="../images/demo1.png" />
|
<img alt="复杂医疗场景" src="docs/images/demo1.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="MQTT指令下发" src="../images/demo4.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="声纹识别" src="../images/demo14.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="控制家电开关" src="../images/demo5.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="MCP接入点" src="../images/demo13.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="多指令任务" src="../images/demo11.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="播放音乐" src="../images/demo7.png" />
|
<img alt="播放音乐查询天气播报新闻" src="docs/images/demo7.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1Z8XuYZEAS" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="天气插件" src="../images/demo8.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="实时打断" src="../images/demo10.png" />
|
<img alt="实时打断" src="docs/images/demo10.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="拍照识物品" src="../images/demo12.png" />
|
<img alt="拍照识物品" src="docs/images/demo12.png" /></picture>
|
||||||
</picture>
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="控制家电开关" src="docs/images/demo5.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="多指令任务" src="docs/images/demo11.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="MCP接入点" src="docs/images/demo13.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="MQTT指令下发" src="docs/images/demo4.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="声纹识别" src="docs/images/demo14.png" /></picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="自定义音色" src="../images/demo2.png" />
|
<img alt="自定义音色" src="docs/images/demo2.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="使用粤语交流" src="../images/demo3.png" />
|
<img alt="使用粤语交流" src="docs/images/demo3.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="播报新闻" src="../images/demo0.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
+52
-67
@@ -54,110 +54,95 @@ Quer ver os efeitos de uso? Clique nos vídeos abaixo 🎥
|
|||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Experiência de velocidade de resposta" src="../images/demo9.png" />
|
<img alt="响应速度感受" src="docs/images/demo9.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Segredo da otimização de velocidade" src="../images/demo6.png" />
|
<img alt="速度优化秘诀" src="docs/images/demo6.png" /></picture>
|
||||||
</picture>
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1WEcxzFEAT" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="小智数字人 支持语音唤醒" src="docs/images/demo8.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1CKVz6UEuB" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="设备呼叫设备,打电话" src="docs/images/demo0.png" /></picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Cenário médico complexo" src="../images/demo1.png" />
|
<img alt="复杂医疗场景" src="docs/images/demo1.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="Envio de comandos MQTT" src="../images/demo4.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="Reconhecimento de impressão vocal" src="../images/demo14.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="Controle de interruptores de eletrodomésticos" src="../images/demo5.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="Ponto de acesso MCP" src="../images/demo13.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="Tarefas com múltiplos comandos" src="../images/demo11.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Reproduzir música" src="../images/demo7.png" />
|
<img alt="播放音乐查询天气播报新闻" src="docs/images/demo7.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1Z8XuYZEAS" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="Plugin de clima" src="../images/demo8.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Interrupção em tempo real" src="../images/demo10.png" />
|
<img alt="实时打断" src="docs/images/demo10.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Fotografar e identificar objetos" src="../images/demo12.png" />
|
<img alt="拍照识物品" src="docs/images/demo12.png" /></picture>
|
||||||
</picture>
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="控制家电开关" src="docs/images/demo5.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="多指令任务" src="docs/images/demo11.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="MCP接入点" src="docs/images/demo13.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="MQTT指令下发" src="docs/images/demo4.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="声纹识别" src="docs/images/demo14.png" /></picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Timbre de voz personalizado" src="../images/demo2.png" />
|
<img alt="自定义音色" src="docs/images/demo2.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Comunicação em cantonês" src="../images/demo3.png" />
|
<img alt="使用粤语交流" src="docs/images/demo3.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="Transmissão de notícias" src="../images/demo0.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
+52
-67
@@ -54,110 +54,95 @@ Muốn xem hiệu quả sử dụng? Hãy xem video 🎥
|
|||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="响应速度感受" src="../images/demo9.png" />
|
<img alt="响应速度感受" src="docs/images/demo9.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="速度优化秘诀" src="../images/demo6.png" />
|
<img alt="速度优化秘诀" src="docs/images/demo6.png" /></picture>
|
||||||
</picture>
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1WEcxzFEAT" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="小智数字人 支持语音唤醒" src="docs/images/demo8.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1CKVz6UEuB" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="设备呼叫设备,打电话" src="docs/images/demo0.png" /></picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="复杂医疗场景" src="../images/demo1.png" />
|
<img alt="复杂医疗场景" src="docs/images/demo1.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="MQTT指令下发" src="../images/demo4.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="声纹识别" src="../images/demo14.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="控制家电开关" src="../images/demo5.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="MCP接入点" src="../images/demo13.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="多指令任务" src="../images/demo11.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="播放音乐" src="../images/demo7.png" />
|
<img alt="播放音乐查询天气播报新闻" src="docs/images/demo7.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV1Z8XuYZEAS" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="天气插件" src="../images/demo8.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="实时打断" src="../images/demo10.png" />
|
<img alt="实时打断" src="docs/images/demo10.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="拍照识物品" src="../images/demo12.png" />
|
<img alt="拍照识物品" src="docs/images/demo12.png" /></picture>
|
||||||
</picture>
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="控制家电开关" src="docs/images/demo5.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="多指令任务" src="docs/images/demo11.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="MCP接入点" src="docs/images/demo13.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="MQTT指令下发" src="docs/images/demo4.png" /></picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="声纹识别" src="docs/images/demo14.png" /></picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="自定义音色" src="../images/demo2.png" />
|
<img alt="自定义音色" src="docs/images/demo2.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="使用粤语交流" src="../images/demo3.png" />
|
<img alt="使用粤语交流" src="docs/images/demo3.png" /></picture>
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
|
|
||||||
<picture>
|
|
||||||
<img alt="播报新闻" src="../images/demo0.png" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# 联网搜索插件使用指南
|
||||||
|
|
||||||
|
## 功能简介
|
||||||
|
|
||||||
|
联网搜索插件 `web_search` 支持在对话过程中实时联网搜索信息并返回结果。插件支持两个搜索源:秘塔(Metaso)和Tavily,用户可根据需要选择其中一个。
|
||||||
|
|
||||||
|
## API Key申请指南
|
||||||
|
|
||||||
|
目前我们适配了`秘塔搜索`和`Tavily搜索`。
|
||||||
|
- Tavily搜索:每个月1000次免费额度。
|
||||||
|
- 秘塔搜索:拥有较为优质的国内数据源。
|
||||||
|
|
||||||
|
## API Key申请指南
|
||||||
|
|
||||||
|
### 方式一:使用秘塔搜索
|
||||||
|
|
||||||
|
- 访问 [秘塔搜索API](https://metaso.cn/search-api/api-keys),注册并登录账号
|
||||||
|
- 在API密钥管理页面,点击"创建新的Key"
|
||||||
|
- 复制生成的API Key(以 `mk-` 为前缀),这是配置所需的关键信息
|
||||||
|
|
||||||
|
### 方式二:使用Tavily搜索
|
||||||
|
|
||||||
|
- 访问 [Tavily控制台](https://app.tavily.com/home),注册并登录账号
|
||||||
|
- 在控制台中创建API Key
|
||||||
|
- 复制生成的API Key(以 `tvly-` 为前缀),这是配置所需的关键信息
|
||||||
|
|
||||||
|
## 配置方式
|
||||||
|
|
||||||
|
### 方式1. 使用智控台部署(推荐)
|
||||||
|
|
||||||
|
- 登录智控台
|
||||||
|
- 进入"配置角色"页面,选择要配置的智能体
|
||||||
|
- 点击"编辑功能"按钮,在右侧参数配置区域找到"联网搜索"插件
|
||||||
|
- 勾选"联网搜索"
|
||||||
|
- 填入搜索源(`metaso`或`tavily`),并将对应的`API Key`填入配置项
|
||||||
|
- 保存配置,再保存智能体配置
|
||||||
|
|
||||||
|
### 方式2. 单模块xiaozhi-server部署
|
||||||
|
|
||||||
|
在 `data/.config.yaml` 中配置:
|
||||||
|
|
||||||
|
- 将搜索源填入 `provider`,可选值为 `metaso` 或 `tavily`
|
||||||
|
- 将申请到的API Key填入 `api_key`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
plugins:
|
||||||
|
web_search:
|
||||||
|
provider: "metaso"
|
||||||
|
api_key: "你的API Key"
|
||||||
|
```
|
||||||
|
|
||||||
|
如需自定义返回结果数量和工具描述,可额外配置 `max_results` 和 `description`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
plugins:
|
||||||
|
web_search:
|
||||||
|
provider: "metaso"
|
||||||
|
description: "联网搜索工具。当用户明确需要联网搜索问题时使用此工具。"
|
||||||
|
max_results: 5
|
||||||
|
api_key: "你的API Key"
|
||||||
|
```
|
||||||
|
|
||||||
|
同时在 `functions` 列表中确保已启用 `web_search`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
plugins:
|
||||||
|
functions:
|
||||||
|
- web_search
|
||||||
|
```
|
||||||
|
|
||||||
|
配置完成后重启服务即可生效。
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<java.version>21</java.version>
|
<java.version>21</java.version>
|
||||||
<junit.version>5.10.1</junit.version>
|
<junit.version>5.10.1</junit.version>
|
||||||
<druid.version>1.2.20</druid.version>
|
<druid.version>1.2.20</druid.version>
|
||||||
<mybatisplus.version>3.5.5</mybatisplus.version>
|
<mybatisplus.version>3.5.17</mybatisplus.version>
|
||||||
<hutool.version>5.8.24</hutool.version>
|
<hutool.version>5.8.24</hutool.version>
|
||||||
<jsoup.version>1.19.1</jsoup.version>
|
<jsoup.version>1.19.1</jsoup.version>
|
||||||
<knife4j.version>4.6.0</knife4j.version>
|
<knife4j.version>4.6.0</knife4j.version>
|
||||||
@@ -32,6 +32,8 @@
|
|||||||
<liquibase-core.version>4.20.0</liquibase-core.version>
|
<liquibase-core.version>4.20.0</liquibase-core.version>
|
||||||
<aliyun-sms-version>4.1.0</aliyun-sms-version>
|
<aliyun-sms-version>4.1.0</aliyun-sms-version>
|
||||||
<okio-version>3.4.0</okio-version>
|
<okio-version>3.4.0</okio-version>
|
||||||
|
<skipTests>true</skipTests>
|
||||||
|
<argLine></argLine>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -175,13 +177,13 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
<version>${mybatisplus.version}</version>
|
<version>${mybatisplus.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mybatis</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-spring</artifactId>
|
<artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
|
||||||
<version>3.0.3</version>
|
<version>${mybatisplus.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
@@ -271,11 +273,22 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<proc>full</proc>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-Xlint:deprecation,unchecked</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>${skipTests}</skipTests>
|
||||||
|
<argLine>@{argLine} -Xshare:off -javaagent:"${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar"</argLine>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ public interface Constant {
|
|||||||
/**
|
/**
|
||||||
* 版本号
|
* 版本号
|
||||||
*/
|
*/
|
||||||
public static final String VERSION = "0.9.4";
|
public static final String VERSION = "0.9.5";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无效固件URL
|
* 无效固件URL
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@ public class DataFilterInterceptor implements InnerInterceptor {
|
|||||||
private String getSelect(String buildSql, DataScope scope) {
|
private String getSelect(String buildSql, DataScope scope) {
|
||||||
try {
|
try {
|
||||||
Select select = (Select) CCJSqlParserUtil.parse(buildSql);
|
Select select = (Select) CCJSqlParserUtil.parse(buildSql);
|
||||||
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
|
PlainSelect plainSelect = select.getPlainSelect();
|
||||||
|
|
||||||
Expression expression = plainSelect.getWhere();
|
Expression expression = plainSelect.getWhere();
|
||||||
if (expression == null) {
|
if (expression == null) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
import org.apache.ibatis.binding.MapperMethod;
|
import org.apache.ibatis.binding.MapperMethod;
|
||||||
import org.apache.ibatis.logging.Log;
|
import org.apache.ibatis.logging.Log;
|
||||||
@@ -81,8 +81,8 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
|||||||
// 处理排序字段
|
// 处理排序字段
|
||||||
if (orderField instanceof String) {
|
if (orderField instanceof String) {
|
||||||
orderFields.add((String) orderField);
|
orderFields.add((String) orderField);
|
||||||
} else if (orderField instanceof List) {
|
} else if (orderField instanceof List<?> fields) {
|
||||||
orderFields.addAll((List<String>) orderField);
|
fields.forEach(field -> orderFields.add(String.class.cast(field)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 有排序字段则排序
|
// 有排序字段则排序
|
||||||
@@ -142,11 +142,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
|||||||
return SqlHelper.retBool(result);
|
return SqlHelper.retBool(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Class<M> currentMapperClass() {
|
protected Class<?> currentMapperClass() {
|
||||||
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
|
return ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public Class<T> currentModelClass() {
|
public Class<T> currentModelClass() {
|
||||||
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1);
|
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1);
|
||||||
}
|
}
|
||||||
@@ -181,8 +182,9 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
|||||||
* 执行批量操作
|
* 执行批量操作
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
|
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiFunction<SqlSession, E, Integer> operation) {
|
||||||
return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer);
|
return SqlHelper.executeBatch(SqlHelper.sqlSessionFactory(this.currentModelClass()), this.log, list, batchSize,
|
||||||
|
operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -209,7 +211,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
|||||||
return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
|
return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
|
||||||
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
|
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
|
||||||
param.put(Constants.ENTITY, entity);
|
param.put(Constants.ENTITY, entity);
|
||||||
sqlSession.update(sqlStatement, param);
|
return sqlSession.update(sqlStatement, param);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,6 +227,6 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteBatchIds(Collection<? extends Serializable> idList) {
|
public boolean deleteBatchIds(Collection<? extends Serializable> idList) {
|
||||||
return SqlHelper.retBool(baseDao.deleteBatchIds(idList));
|
return SqlHelper.retBool(baseDao.deleteByIds(idList));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@ import xiaozhi.common.utils.ConvertUtils;
|
|||||||
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T>
|
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T>
|
||||||
implements CrudService<T, D> {
|
implements CrudService<T, D> {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
protected Class<D> currentDtoClass() {
|
protected Class<D> currentDtoClass() {
|
||||||
return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
|
return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
|
||||||
}
|
}
|
||||||
@@ -70,6 +71,6 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(Serializable[] ids) {
|
public void delete(Serializable[] ids) {
|
||||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
baseDao.deleteByIds(Arrays.asList(ids));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package xiaozhi.common.utils;
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@@ -16,6 +18,10 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
*/
|
*/
|
||||||
public class JsonUtils {
|
public class JsonUtils {
|
||||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<>() {
|
||||||
|
};
|
||||||
|
private static final TypeReference<List<Map<String, Object>>> STRING_OBJECT_MAP_LIST = new TypeReference<>() {
|
||||||
|
};
|
||||||
|
|
||||||
public static String toJsonString(Object object) {
|
public static String toJsonString(Object object) {
|
||||||
try {
|
try {
|
||||||
@@ -67,4 +73,59 @@ public class JsonUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Map<String, Object> parseMap(String text) {
|
||||||
|
if (StrUtil.isEmpty(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parseObject(text, STRING_OBJECT_MAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Map<String, Object>> parseMapList(String text) {
|
||||||
|
if (StrUtil.isEmpty(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parseObject(text, STRING_OBJECT_MAP_LIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Object> toStringObjectMap(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!(value instanceof Map<?, ?> map)) {
|
||||||
|
throw new ClassCastException("Expected Map but got " + value.getClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>(map.size());
|
||||||
|
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||||
|
result.put(String.class.cast(entry.getKey()), entry.getValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Map<String, Object>> toStringObjectMapList(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<?> list = List.class.cast(value);
|
||||||
|
List<Map<String, Object>> result = new ArrayList<>(list.size());
|
||||||
|
for (Object item : list) {
|
||||||
|
result.add(toStringObjectMap(item));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> toList(Object value, Class<T> elementType) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<?> list = List.class.cast(value);
|
||||||
|
List<T> result = new ArrayList<>(list.size());
|
||||||
|
for (Object item : list) {
|
||||||
|
result.add(elementType.cast(item));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,11 +75,11 @@ public class SensitiveDataUtils {
|
|||||||
Object value = jsonObject.get(key);
|
Object value = jsonObject.get(key);
|
||||||
|
|
||||||
if (SENSITIVE_FIELDS.contains(key.toLowerCase()) && value instanceof String) {
|
if (SENSITIVE_FIELDS.contains(key.toLowerCase()) && value instanceof String) {
|
||||||
result.put(key, maskMiddle((String) value));
|
result.set(key, maskMiddle((String) value));
|
||||||
} else if (value instanceof JSONObject) {
|
} else if (value instanceof JSONObject) {
|
||||||
result.put(key, maskSensitiveFields((JSONObject) value));
|
result.set(key, maskSensitiveFields((JSONObject) value));
|
||||||
} else {
|
} else {
|
||||||
result.put(key, value);
|
result.set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ public class SqlFilter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// 去掉'|"|;|\字符
|
// 去掉'|"|;|\字符
|
||||||
str = StringUtils.replace(str, "'", "");
|
str = str.replace("'", "");
|
||||||
str = StringUtils.replace(str, "\"", "");
|
str = str.replace("\"", "");
|
||||||
str = StringUtils.replace(str, ";", "");
|
str = str.replace(";", "");
|
||||||
str = StringUtils.replace(str, "\\", "");
|
str = str.replace("\\", "");
|
||||||
|
|
||||||
// 转换成小写
|
// 转换成小写
|
||||||
str = str.toLowerCase();
|
str = str.toLowerCase();
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package xiaozhi.modules.agent.Enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotDataDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum AgentSnapshotField {
|
||||||
|
AGENT_CODE("agentCode", AgentSnapshotDataDTO::getAgentCode, AgentUpdateDTO::getAgentCode,
|
||||||
|
(agent, data) -> agent.setAgentCode(data.getAgentCode())),
|
||||||
|
AGENT_NAME("agentName", AgentSnapshotDataDTO::getAgentName, AgentUpdateDTO::getAgentName,
|
||||||
|
(agent, data) -> agent.setAgentName(data.getAgentName())),
|
||||||
|
ASR_MODEL_ID("asrModelId", AgentSnapshotDataDTO::getAsrModelId, AgentUpdateDTO::getAsrModelId,
|
||||||
|
(agent, data) -> agent.setAsrModelId(data.getAsrModelId())),
|
||||||
|
VAD_MODEL_ID("vadModelId", AgentSnapshotDataDTO::getVadModelId, AgentUpdateDTO::getVadModelId,
|
||||||
|
(agent, data) -> agent.setVadModelId(data.getVadModelId())),
|
||||||
|
LLM_MODEL_ID("llmModelId", AgentSnapshotDataDTO::getLlmModelId, AgentUpdateDTO::getLlmModelId,
|
||||||
|
(agent, data) -> agent.setLlmModelId(data.getLlmModelId())),
|
||||||
|
SLM_MODEL_ID("slmModelId", AgentSnapshotDataDTO::getSlmModelId, AgentUpdateDTO::getSlmModelId,
|
||||||
|
(agent, data) -> agent.setSlmModelId(data.getSlmModelId())),
|
||||||
|
VLLM_MODEL_ID("vllmModelId", AgentSnapshotDataDTO::getVllmModelId, AgentUpdateDTO::getVllmModelId,
|
||||||
|
(agent, data) -> agent.setVllmModelId(data.getVllmModelId())),
|
||||||
|
TTS_MODEL_ID("ttsModelId", AgentSnapshotDataDTO::getTtsModelId, AgentUpdateDTO::getTtsModelId,
|
||||||
|
(agent, data) -> agent.setTtsModelId(data.getTtsModelId())),
|
||||||
|
TTS_VOICE_ID("ttsVoiceId", AgentSnapshotDataDTO::getTtsVoiceId, AgentUpdateDTO::getTtsVoiceId,
|
||||||
|
(agent, data) -> agent.setTtsVoiceId(data.getTtsVoiceId())),
|
||||||
|
TTS_LANGUAGE("ttsLanguage", AgentSnapshotDataDTO::getTtsLanguage, AgentUpdateDTO::getTtsLanguage,
|
||||||
|
(agent, data) -> agent.setTtsLanguage(data.getTtsLanguage())),
|
||||||
|
TTS_VOLUME("ttsVolume", AgentSnapshotDataDTO::getTtsVolume, AgentUpdateDTO::getTtsVolume,
|
||||||
|
(agent, data) -> agent.setTtsVolume(data.getTtsVolume())),
|
||||||
|
TTS_RATE("ttsRate", AgentSnapshotDataDTO::getTtsRate, AgentUpdateDTO::getTtsRate,
|
||||||
|
(agent, data) -> agent.setTtsRate(data.getTtsRate())),
|
||||||
|
TTS_PITCH("ttsPitch", AgentSnapshotDataDTO::getTtsPitch, AgentUpdateDTO::getTtsPitch,
|
||||||
|
(agent, data) -> agent.setTtsPitch(data.getTtsPitch())),
|
||||||
|
MEM_MODEL_ID("memModelId", AgentSnapshotDataDTO::getMemModelId, AgentUpdateDTO::getMemModelId,
|
||||||
|
(agent, data) -> agent.setMemModelId(data.getMemModelId())),
|
||||||
|
INTENT_MODEL_ID("intentModelId", AgentSnapshotDataDTO::getIntentModelId, AgentUpdateDTO::getIntentModelId,
|
||||||
|
(agent, data) -> agent.setIntentModelId(data.getIntentModelId())),
|
||||||
|
CHAT_HISTORY_CONF("chatHistoryConf", AgentSnapshotDataDTO::getChatHistoryConf, AgentUpdateDTO::getChatHistoryConf,
|
||||||
|
(agent, data) -> agent.setChatHistoryConf(data.getChatHistoryConf())),
|
||||||
|
SYSTEM_PROMPT("systemPrompt", AgentSnapshotDataDTO::getSystemPrompt, AgentUpdateDTO::getSystemPrompt,
|
||||||
|
(agent, data) -> agent.setSystemPrompt(data.getSystemPrompt())),
|
||||||
|
SUMMARY_MEMORY("summaryMemory", AgentSnapshotDataDTO::getSummaryMemory, AgentUpdateDTO::getSummaryMemory,
|
||||||
|
(agent, data) -> agent.setSummaryMemory(data.getSummaryMemory())),
|
||||||
|
LANG_CODE("langCode", AgentSnapshotDataDTO::getLangCode, AgentUpdateDTO::getLangCode,
|
||||||
|
(agent, data) -> agent.setLangCode(data.getLangCode())),
|
||||||
|
LANGUAGE("language", AgentSnapshotDataDTO::getLanguage, AgentUpdateDTO::getLanguage,
|
||||||
|
(agent, data) -> agent.setLanguage(data.getLanguage())),
|
||||||
|
SORT("sort", AgentSnapshotDataDTO::getSort, AgentUpdateDTO::getSort,
|
||||||
|
(agent, data) -> agent.setSort(data.getSort())),
|
||||||
|
FUNCTIONS("functions", AgentSnapshotDataDTO::getFunctions, AgentUpdateDTO::getFunctions, null),
|
||||||
|
CONTEXT_PROVIDERS("contextProviders", AgentSnapshotDataDTO::getContextProviders,
|
||||||
|
AgentUpdateDTO::getContextProviders, null),
|
||||||
|
CORRECT_WORD_FILE_IDS("correctWordFileIds", AgentSnapshotDataDTO::getCorrectWordFileIds,
|
||||||
|
AgentUpdateDTO::getCorrectWordFileIds, null),
|
||||||
|
TAG_NAMES("tagNames", AgentSnapshotDataDTO::getTagNames, AgentUpdateDTO::getTagNames, null);
|
||||||
|
|
||||||
|
private final String fieldName;
|
||||||
|
private final Function<AgentSnapshotDataDTO, Object> snapshotGetter;
|
||||||
|
private final Function<AgentUpdateDTO, Object> updateGetter;
|
||||||
|
private final BiConsumer<AgentEntity, AgentSnapshotDataDTO> restoreApplier;
|
||||||
|
|
||||||
|
AgentSnapshotField(String fieldName, Function<AgentSnapshotDataDTO, Object> snapshotGetter,
|
||||||
|
Function<AgentUpdateDTO, Object> updateGetter,
|
||||||
|
BiConsumer<AgentEntity, AgentSnapshotDataDTO> restoreApplier) {
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
this.snapshotGetter = snapshotGetter;
|
||||||
|
this.updateGetter = updateGetter;
|
||||||
|
this.restoreApplier = restoreApplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> names() {
|
||||||
|
return Arrays.stream(values()).map(AgentSnapshotField::getFieldName).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String canonicalName(String fieldName) {
|
||||||
|
return "tags".equals(fieldName) ? TAG_NAMES.getFieldName() : fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object snapshotValue(AgentSnapshotDataDTO data) {
|
||||||
|
return data == null ? null : snapshotGetter.apply(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object updateValue(AgentUpdateDTO data) {
|
||||||
|
return data == null ? null : updateGetter.apply(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRestorableAgentField() {
|
||||||
|
return restoreApplier != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyTo(AgentEntity agent, AgentSnapshotDataDTO data) {
|
||||||
|
if (restoreApplier != null) {
|
||||||
|
restoreApplier.accept(agent, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
-35
@@ -28,10 +28,13 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.user.UserDetail;
|
import xiaozhi.common.user.UserDetail;
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.common.utils.ResultUtils;
|
import xiaozhi.common.utils.ResultUtils;
|
||||||
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||||
@@ -48,15 +51,10 @@ import xiaozhi.modules.agent.service.AgentTagService;
|
|||||||
import xiaozhi.modules.agent.service.AgentChatAudioService;
|
import xiaozhi.modules.agent.service.AgentChatAudioService;
|
||||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||||
import xiaozhi.modules.agent.service.AgentChatSummaryService;
|
import xiaozhi.modules.agent.service.AgentChatSummaryService;
|
||||||
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
|
||||||
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
|
||||||
import xiaozhi.modules.agent.service.AgentService;
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
import xiaozhi.modules.correctword.service.CorrectWordFileService;
|
|
||||||
import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO;
|
import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO;
|
||||||
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
|
||||||
@Tag(name = "智能体管理")
|
@Tag(name = "智能体管理")
|
||||||
@@ -64,17 +62,39 @@ import xiaozhi.modules.security.user.SecurityUser;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/agent")
|
@RequestMapping("/agent")
|
||||||
public class AgentController {
|
public class AgentController {
|
||||||
|
private static final long AUDIO_PLAY_TOKEN_EXPIRE_SECONDS = 300L;
|
||||||
|
|
||||||
private final AgentService agentService;
|
private final AgentService agentService;
|
||||||
private final AgentTemplateService agentTemplateService;
|
private final AgentTemplateService agentTemplateService;
|
||||||
private final DeviceService deviceService;
|
|
||||||
private final AgentChatHistoryService agentChatHistoryService;
|
private final AgentChatHistoryService agentChatHistoryService;
|
||||||
private final AgentChatAudioService agentChatAudioService;
|
private final AgentChatAudioService agentChatAudioService;
|
||||||
private final AgentPluginMappingService agentPluginMappingService;
|
|
||||||
private final AgentContextProviderService agentContextProviderService;
|
|
||||||
private final AgentChatSummaryService agentChatSummaryService;
|
private final AgentChatSummaryService agentChatSummaryService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final AgentTagService agentTagService;
|
private final AgentTagService agentTagService;
|
||||||
private final CorrectWordFileService correctWordFileService;
|
|
||||||
|
private void requireAgentPermission(String agentId) {
|
||||||
|
if (!agentService.checkAgentPermission(agentId, SecurityUser.getUserId())) {
|
||||||
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requireSessionAgent(String sessionId) {
|
||||||
|
String agentId = agentChatHistoryService.getAgentIdBySessionId(sessionId);
|
||||||
|
if (StringUtils.isBlank(agentId)) {
|
||||||
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
agentService.getAgentById(agentId);
|
||||||
|
return agentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requireAudioPermission(String audioId) {
|
||||||
|
String agentId = agentChatHistoryService.getAgentIdByAudioId(audioId);
|
||||||
|
if (StringUtils.isBlank(agentId)) {
|
||||||
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
|
}
|
||||||
|
requireAgentPermission(agentId);
|
||||||
|
return agentId;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "获取用户智能体列表")
|
@Operation(summary = "获取用户智能体列表")
|
||||||
@@ -106,7 +126,7 @@ public class AgentController {
|
|||||||
@Operation(summary = "获取智能体详情")
|
@Operation(summary = "获取智能体详情")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<AgentInfoVO> getAgentById(@PathVariable("id") String id) {
|
public Result<AgentInfoVO> getAgentById(@PathVariable("id") String id) {
|
||||||
AgentInfoVO agent = agentService.getAgentById(id);
|
AgentInfoVO agent = agentService.getAgentById(id, SecurityUser.getUserId());
|
||||||
return ResultUtils.success(agent);
|
return ResultUtils.success(agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,20 +140,16 @@ public class AgentController {
|
|||||||
|
|
||||||
@PutMapping("/saveMemory/{macAddress}")
|
@PutMapping("/saveMemory/{macAddress}")
|
||||||
@Operation(summary = "根据设备id更新智能体")
|
@Operation(summary = "根据设备id更新智能体")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> updateByDeviceId(@PathVariable String macAddress, @RequestBody @Valid AgentMemoryDTO dto) {
|
public Result<Void> updateByDeviceId(@PathVariable String macAddress, @RequestBody @Valid AgentMemoryDTO dto) {
|
||||||
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
agentService.updateAgentMemoryByDeviceMacAddress(macAddress, dto, SecurityUser.getUserId());
|
||||||
if (device == null) {
|
return new Result<Void>().ok(null);
|
||||||
return new Result<>();
|
|
||||||
}
|
|
||||||
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
|
|
||||||
agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
|
|
||||||
agentService.updateAgentById(device.getAgentId(), agentUpdateDTO);
|
|
||||||
return new Result<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/chat-summary/{sessionId}/save")
|
@PostMapping("/chat-summary/{sessionId}/save")
|
||||||
@Operation(summary = "根据会话ID生成聊天记录总结并保存(异步执行)")
|
@Operation(summary = "根据会话ID生成聊天记录总结并保存(异步执行)")
|
||||||
public Result<Void> generateAndSaveChatSummary(@PathVariable String sessionId) {
|
public Result<Void> generateAndSaveChatSummary(@PathVariable String sessionId) {
|
||||||
|
requireSessionAgent(sessionId);
|
||||||
try {
|
try {
|
||||||
// 异步执行总结生成任务,立即返回成功响应
|
// 异步执行总结生成任务,立即返回成功响应
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
@@ -155,6 +171,7 @@ public class AgentController {
|
|||||||
@PostMapping("/chat-title/{sessionId}/generate")
|
@PostMapping("/chat-title/{sessionId}/generate")
|
||||||
@Operation(summary = "根据会话ID生成聊天标题")
|
@Operation(summary = "根据会话ID生成聊天标题")
|
||||||
public Result<Void> generateAndSaveChatTitle(@PathVariable String sessionId) {
|
public Result<Void> generateAndSaveChatTitle(@PathVariable String sessionId) {
|
||||||
|
requireSessionAgent(sessionId);
|
||||||
agentChatSummaryService.generateAndSaveChatTitle(sessionId);
|
agentChatSummaryService.generateAndSaveChatTitle(sessionId);
|
||||||
return new Result<Void>().ok(null);
|
return new Result<Void>().ok(null);
|
||||||
}
|
}
|
||||||
@@ -163,7 +180,7 @@ public class AgentController {
|
|||||||
@Operation(summary = "更新智能体")
|
@Operation(summary = "更新智能体")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
|
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
|
||||||
agentService.updateAgentById(id, dto);
|
agentService.updateAgentById(id, dto, SecurityUser.getUserId());
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,18 +188,7 @@ public class AgentController {
|
|||||||
@Operation(summary = "删除智能体")
|
@Operation(summary = "删除智能体")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> delete(@PathVariable String id) {
|
public Result<Void> delete(@PathVariable String id) {
|
||||||
// 先删除关联的设备
|
agentService.deleteAgentById(id, SecurityUser.getUserId());
|
||||||
deviceService.deleteByAgentId(id);
|
|
||||||
// 删除关联的聊天记录
|
|
||||||
agentChatHistoryService.deleteByAgentId(id, true, true);
|
|
||||||
// 删除关联的插件
|
|
||||||
agentPluginMappingService.deleteByAgentId(id);
|
|
||||||
// 删除关联的上下文源配置
|
|
||||||
agentContextProviderService.deleteByAgentId(id);
|
|
||||||
// 删除关联的替换词文件关联记录
|
|
||||||
correctWordFileService.deleteMappingsByAgentId(id);
|
|
||||||
// 再删除智能体
|
|
||||||
agentService.deleteById(id);
|
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +211,7 @@ public class AgentController {
|
|||||||
public Result<PageData<AgentChatSessionDTO>> getAgentSessions(
|
public Result<PageData<AgentChatSessionDTO>> getAgentSessions(
|
||||||
@PathVariable("id") String id,
|
@PathVariable("id") String id,
|
||||||
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
requireAgentPermission(id);
|
||||||
params.put("agentId", id);
|
params.put("agentId", id);
|
||||||
PageData<AgentChatSessionDTO> page = agentChatHistoryService.getSessionListByAgentId(params);
|
PageData<AgentChatSessionDTO> page = agentChatHistoryService.getSessionListByAgentId(params);
|
||||||
return new Result<PageData<AgentChatSessionDTO>>().ok(page);
|
return new Result<PageData<AgentChatSessionDTO>>().ok(page);
|
||||||
@@ -252,6 +259,7 @@ public class AgentController {
|
|||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<String> getContentByAudioId(
|
public Result<String> getContentByAudioId(
|
||||||
@PathVariable("id") String id) {
|
@PathVariable("id") String id) {
|
||||||
|
requireAudioPermission(id);
|
||||||
// 查询聊天记录
|
// 查询聊天记录
|
||||||
String data = agentChatHistoryService.getContentByAudioId(id);
|
String data = agentChatHistoryService.getContentByAudioId(id);
|
||||||
return new Result<String>().ok(data);
|
return new Result<String>().ok(data);
|
||||||
@@ -261,12 +269,13 @@ public class AgentController {
|
|||||||
@Operation(summary = "获取音频下载ID")
|
@Operation(summary = "获取音频下载ID")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<String> getAudioId(@PathVariable("audioId") String audioId) {
|
public Result<String> getAudioId(@PathVariable("audioId") String audioId) {
|
||||||
|
requireAudioPermission(audioId);
|
||||||
byte[] audioData = agentChatAudioService.getAudio(audioId);
|
byte[] audioData = agentChatAudioService.getAudio(audioId);
|
||||||
if (audioData == null) {
|
if (audioData == null) {
|
||||||
return new Result<String>().error("音频不存在");
|
return new Result<String>().error("音频不存在");
|
||||||
}
|
}
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId);
|
redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId, AUDIO_PLAY_TOKEN_EXPIRE_SECONDS);
|
||||||
return new Result<String>().ok(uuid);
|
return new Result<String>().ok(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,6 +331,7 @@ public class AgentController {
|
|||||||
@Operation(summary = "获取智能体的标签")
|
@Operation(summary = "获取智能体的标签")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<List<AgentTagDTO>> getAgentTags(@PathVariable String id) {
|
public Result<List<AgentTagDTO>> getAgentTags(@PathVariable String id) {
|
||||||
|
requireAgentPermission(id);
|
||||||
List<AgentTagDTO> tags = agentTagService.getTagsByAgentId(id);
|
List<AgentTagDTO> tags = agentTagService.getTagsByAgentId(id);
|
||||||
return new Result<List<AgentTagDTO>>().ok(tags);
|
return new Result<List<AgentTagDTO>>().ok(tags);
|
||||||
}
|
}
|
||||||
@@ -330,9 +340,13 @@ public class AgentController {
|
|||||||
@Operation(summary = "保存智能体的标签")
|
@Operation(summary = "保存智能体的标签")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> saveAgentTags(@PathVariable String id, @RequestBody Map<String, Object> params) {
|
public Result<Void> saveAgentTags(@PathVariable String id, @RequestBody Map<String, Object> params) {
|
||||||
List<String> tagIds = (List<String>) params.get("tagIds");
|
requireAgentPermission(id);
|
||||||
List<String> tagNames = (List<String>) params.get("tagNames");
|
List<String> tagIds = JsonUtils.toList(params.get("tagIds"), String.class);
|
||||||
agentTagService.saveAgentTags(id, tagIds, tagNames);
|
List<String> tagNames = JsonUtils.toList(params.get("tagNames"), String.class);
|
||||||
|
AgentUpdateDTO dto = new AgentUpdateDTO();
|
||||||
|
dto.setTagIds(tagIds);
|
||||||
|
dto.setTagNames(tagNames);
|
||||||
|
agentService.updateAgentById(id, dto);
|
||||||
return new Result<Void>().ok(null);
|
return new Result<Void>().ok(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
package xiaozhi.modules.agent.controller;
|
||||||
|
|
||||||
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
|
import org.springdoc.core.annotations.ParameterObject;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import xiaozhi.common.exception.RenException;
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.user.UserDetail;
|
||||||
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotPageDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotRestoreDTO;
|
||||||
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentSnapshotService;
|
||||||
|
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
||||||
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
|
||||||
|
@Tag(name = "智能体快照")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/agent/{agentId}/snapshots")
|
||||||
|
public class AgentSnapshotController {
|
||||||
|
private final AgentSnapshotService agentSnapshotService;
|
||||||
|
private final AgentService agentService;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "获取智能体快照列表")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<PageData<AgentSnapshotVO>> page(
|
||||||
|
@PathVariable String agentId,
|
||||||
|
@ParameterObject AgentSnapshotPageDTO params) {
|
||||||
|
checkPermission(agentId);
|
||||||
|
return new Result<PageData<AgentSnapshotVO>>().ok(agentSnapshotService.page(agentId, params));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{snapshotId}")
|
||||||
|
@Operation(summary = "获取智能体快照详情")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<AgentSnapshotVO> getSnapshot(@PathVariable String agentId, @PathVariable String snapshotId) {
|
||||||
|
checkPermission(agentId);
|
||||||
|
return new Result<AgentSnapshotVO>().ok(agentSnapshotService.getSnapshot(agentId, snapshotId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{snapshotId}/restore")
|
||||||
|
@Operation(summary = "恢复智能体快照")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Void> restore(@PathVariable String agentId, @PathVariable String snapshotId,
|
||||||
|
@RequestBody @Valid AgentSnapshotRestoreDTO request) {
|
||||||
|
checkPermission(agentId);
|
||||||
|
agentSnapshotService.restoreSnapshot(agentId, snapshotId, request.getCurrentStateToken());
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{snapshotId}")
|
||||||
|
@Operation(summary = "删除智能体历史快照")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Void> deleteSnapshot(@PathVariable String agentId, @PathVariable String snapshotId) {
|
||||||
|
checkPermission(agentId);
|
||||||
|
agentSnapshotService.deleteSnapshot(agentId, snapshotId);
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPermission(String agentId) {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
if (user == null || !agentService.checkAgentPermission(agentId, user.getId())) {
|
||||||
|
throw new RenException("没有权限访问该智能体快照");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,4 +36,20 @@ public interface AgentDao extends BaseDao<AgentEntity> {
|
|||||||
* @param agentId 智能体ID
|
* @param agentId 智能体ID
|
||||||
*/
|
*/
|
||||||
AgentInfoVO selectAgentInfoById(@Param("agentId") String agentId);
|
AgentInfoVO selectAgentInfoById(@Param("agentId") String agentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁定智能体主记录,用于串行化同一智能体的配置写入
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
*/
|
||||||
|
AgentEntity selectByIdForUpdate(@Param("agentId") String agentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 精确写入快照覆盖的智能体字段,包括目标快照中的 null 值。
|
||||||
|
* 不更新所属用户、创建信息等不属于快照的字段。
|
||||||
|
*
|
||||||
|
* @param agent 已应用目标快照的智能体
|
||||||
|
* @return 受影响行数
|
||||||
|
*/
|
||||||
|
int updateSnapshotFields(@Param("agent") AgentEntity agent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package xiaozhi.modules.agent.dao;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import xiaozhi.common.dao.BaseDao;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface AgentSnapshotDao extends BaseDao<AgentSnapshotEntity> {
|
||||||
|
Integer selectMaxVersionNo(@Param("agentId") String agentId);
|
||||||
|
|
||||||
|
AgentSnapshotEntity selectLatestSnapshot(@Param("agentId") String agentId);
|
||||||
|
|
||||||
|
AgentSnapshotEntity selectNextSnapshot(@Param("agentId") String agentId, @Param("versionNo") Integer versionNo);
|
||||||
|
|
||||||
|
int insertWithNextVersion(@Param("snapshot") AgentSnapshotEntity snapshot);
|
||||||
|
|
||||||
|
int deleteOlderThanKeepLimit(@Param("agentId") String agentId, @Param("keepLimit") int keepLimit);
|
||||||
|
|
||||||
|
List<AgentSnapshotEntity> selectLegacyRedactionBatch(@Param("afterId") String afterId,
|
||||||
|
@Param("limit") int limit,
|
||||||
|
@Param("targetRedactionVersion") int targetRedactionVersion);
|
||||||
|
|
||||||
|
int updateRedactedSnapshots(@Param("snapshots") List<AgentSnapshotEntity> snapshots,
|
||||||
|
@Param("redactionVersion") int redactionVersion);
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ public class AgentDTO {
|
|||||||
private String systemPrompt;
|
private String systemPrompt;
|
||||||
|
|
||||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
||||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||||
private String summaryMemory;
|
private String summaryMemory;
|
||||||
|
|
||||||
@Schema(description = "最后连接时间", example = "2024-03-20 10:00:00")
|
@Schema(description = "最后连接时间", example = "2024-03-20 10:00:00")
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ public class AgentMemoryDTO implements Serializable {
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
||||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||||
private String summaryMemory;
|
private String summaryMemory;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "智能体快照数据")
|
||||||
|
public class AgentSnapshotDataDTO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private String agentCode;
|
||||||
|
private String agentName;
|
||||||
|
private String asrModelId;
|
||||||
|
private String vadModelId;
|
||||||
|
private String llmModelId;
|
||||||
|
private String slmModelId;
|
||||||
|
private String vllmModelId;
|
||||||
|
private String ttsModelId;
|
||||||
|
private String ttsVoiceId;
|
||||||
|
private String ttsLanguage;
|
||||||
|
private Integer ttsVolume;
|
||||||
|
private Integer ttsRate;
|
||||||
|
private Integer ttsPitch;
|
||||||
|
private String memModelId;
|
||||||
|
private String intentModelId;
|
||||||
|
private Integer chatHistoryConf;
|
||||||
|
private String systemPrompt;
|
||||||
|
private String summaryMemory;
|
||||||
|
private String langCode;
|
||||||
|
private String language;
|
||||||
|
private Integer sort;
|
||||||
|
private List<AgentUpdateDTO.FunctionInfo> functions;
|
||||||
|
private List<ContextProviderDTO> contextProviders;
|
||||||
|
private List<String> correctWordFileIds;
|
||||||
|
private List<String> tagNames;
|
||||||
|
private List<AgentSnapshotTagDTO> tags;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "智能体快照分页查询参数")
|
||||||
|
public class AgentSnapshotPageDTO {
|
||||||
|
@Schema(description = "当前页码,从1开始", example = "1")
|
||||||
|
private Integer page = 1;
|
||||||
|
|
||||||
|
@Schema(description = "每页数量", example = "10")
|
||||||
|
private Integer limit = 10;
|
||||||
|
|
||||||
|
@Schema(description = "版本锚点,只查询小于等于该版本号的历史快照", example = "20")
|
||||||
|
private Integer maxVersionNo;
|
||||||
|
|
||||||
|
public int pageOrDefault() {
|
||||||
|
return page == null || page < 1 ? 1 : page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int limitOrDefault() {
|
||||||
|
if (limit == null || limit < 1) {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "智能体快照恢复请求")
|
||||||
|
public class AgentSnapshotRestoreDTO {
|
||||||
|
@NotBlank
|
||||||
|
@Schema(description = "预览时由服务端生成的当前配置状态指纹")
|
||||||
|
private String currentStateToken;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "智能体快照标签")
|
||||||
|
public class AgentSnapshotTagDTO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String tagName;
|
||||||
|
private Integer sort;
|
||||||
|
}
|
||||||
@@ -4,9 +4,12 @@ import java.io.Serializable;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 智能体更新DTO
|
* 智能体更新DTO
|
||||||
@@ -36,10 +39,10 @@ public class AgentUpdateDTO implements Serializable {
|
|||||||
@Schema(description = "小模型标识", example = "slm_model_02", nullable = true)
|
@Schema(description = "小模型标识", example = "slm_model_02", nullable = true)
|
||||||
private String slmModelId;
|
private String slmModelId;
|
||||||
|
|
||||||
@Schema(description = "VLLM模型标识", example = "vllm_model_02", required = false)
|
@Schema(description = "VLLM模型标识", example = "vllm_model_02", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||||
private String vllmModelId;
|
private String vllmModelId;
|
||||||
|
|
||||||
@Schema(description = "语音合成模型标识", example = "tts_model_02", required = false)
|
@Schema(description = "语音合成模型标识", example = "tts_model_02", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||||
private String ttsModelId;
|
private String ttsModelId;
|
||||||
|
|
||||||
@Schema(description = "音色标识", example = "voice_02", nullable = true)
|
@Schema(description = "音色标识", example = "voice_02", nullable = true)
|
||||||
@@ -91,14 +94,49 @@ public class AgentUpdateDTO implements Serializable {
|
|||||||
@Schema(description = "替换词文件ID列表", nullable = true)
|
@Schema(description = "替换词文件ID列表", nullable = true)
|
||||||
private List<String> correctWordFileIds;
|
private List<String> correctWordFileIds;
|
||||||
|
|
||||||
|
@Schema(description = "标签名称列表", nullable = true)
|
||||||
|
private List<String> tagNames;
|
||||||
|
|
||||||
|
@Schema(description = "标签ID列表", nullable = true)
|
||||||
|
private List<String> tagIds;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "插件函数信息")
|
@Schema(description = "插件函数信息")
|
||||||
public static class FunctionInfo implements Serializable {
|
public static class FunctionInfo implements Serializable {
|
||||||
|
private static final TypeReference<HashMap<String, Object>> PARAM_INFO_TYPE = new TypeReference<>() {
|
||||||
|
};
|
||||||
|
|
||||||
@Schema(description = "插件ID", example = "plugin_01")
|
@Schema(description = "插件ID", example = "plugin_01")
|
||||||
private String pluginId;
|
private String pluginId;
|
||||||
|
|
||||||
@Schema(description = "函数参数信息", nullable = true)
|
@Schema(description = "函数参数信息", nullable = true)
|
||||||
private HashMap<String, Object> paramInfo;
|
private HashMap<String, Object> paramInfo = new HashMap<>();
|
||||||
|
|
||||||
|
public void setParamInfo(Object paramInfo) {
|
||||||
|
this.paramInfo = normalizeParamInfo(paramInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashMap<String, Object> normalizeParamInfo(Object paramInfo) {
|
||||||
|
if (paramInfo == null) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
if (paramInfo instanceof String value) {
|
||||||
|
if (value.trim().isEmpty()) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
return JsonUtils.parseObject(value, PARAM_INFO_TYPE);
|
||||||
|
}
|
||||||
|
if (paramInfo instanceof Map<?, ?> value) {
|
||||||
|
HashMap<String, Object> normalized = new HashMap<>();
|
||||||
|
value.forEach((key, val) -> {
|
||||||
|
if (key != null) {
|
||||||
|
normalized.put(String.valueOf(key), val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
return JsonUtils.parseObject(JsonUtils.toJsonString(paramInfo), PARAM_INFO_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
package xiaozhi.modules.agent.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MCP JSON-RPC 响应 DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class McpJsonRpcResponse {
|
|
||||||
private String jsonrpc = "2.0";
|
|
||||||
private Integer id;
|
|
||||||
private McpResult result;
|
|
||||||
private McpError error;
|
|
||||||
|
|
||||||
public McpJsonRpcResponse() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class McpResult {
|
|
||||||
private String type;
|
|
||||||
private String message;
|
|
||||||
private String agent_id;
|
|
||||||
private McpTool[] tools;
|
|
||||||
|
|
||||||
public McpResult() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class McpTool {
|
|
||||||
private String name;
|
|
||||||
private String description;
|
|
||||||
private Object inputSchema;
|
|
||||||
|
|
||||||
public McpTool() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class McpError {
|
|
||||||
private Integer code;
|
|
||||||
private String message;
|
|
||||||
private Object data;
|
|
||||||
|
|
||||||
public McpError() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -7,11 +7,11 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import xiaozhi.modules.agent.dto.ContextProviderDTO;
|
import xiaozhi.modules.agent.dto.ContextProviderDTO;
|
||||||
|
import xiaozhi.modules.agent.typehandler.ContextProviderListTypeHandler;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@TableName(value = "ai_agent_context_provider", autoResultMap = true)
|
@TableName(value = "ai_agent_context_provider", autoResultMap = true)
|
||||||
@@ -26,7 +26,7 @@ public class AgentContextProviderEntity {
|
|||||||
private String agentId;
|
private String agentId;
|
||||||
|
|
||||||
@Schema(description = "上下文源配置")
|
@Schema(description = "上下文源配置")
|
||||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
@TableField(typeHandler = ContextProviderListTypeHandler.class)
|
||||||
private List<ContextProviderDTO> contextProviders;
|
private List<ContextProviderDTO> contextProviders;
|
||||||
|
|
||||||
@Schema(description = "创建者")
|
@Schema(description = "创建者")
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class AgentEntity {
|
|||||||
private String systemPrompt;
|
private String systemPrompt;
|
||||||
|
|
||||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
||||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||||
private String summaryMemory;
|
private String summaryMemory;
|
||||||
|
|
||||||
@Schema(description = "语言编码")
|
@Schema(description = "语言编码")
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package xiaozhi.modules.agent.entity;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("ai_agent_snapshot")
|
||||||
|
@Schema(description = "智能体配置快照")
|
||||||
|
public class AgentSnapshotEntity {
|
||||||
|
|
||||||
|
@TableId(type = IdType.ASSIGN_UUID)
|
||||||
|
@Schema(description = "快照ID")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "智能体ID")
|
||||||
|
private String agentId;
|
||||||
|
|
||||||
|
@Schema(description = "所属用户ID")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "版本号")
|
||||||
|
private Integer versionNo;
|
||||||
|
|
||||||
|
@Schema(description = "快照数据JSON")
|
||||||
|
private String snapshotData;
|
||||||
|
|
||||||
|
@Schema(description = "变更字段JSON")
|
||||||
|
private String changedFields;
|
||||||
|
|
||||||
|
@Schema(description = "快照来源")
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
@Schema(description = "恢复来源快照ID")
|
||||||
|
private String restoreFromSnapshotId;
|
||||||
|
|
||||||
|
@Schema(description = "恢复来源版本号")
|
||||||
|
private Integer restoreFromVersionNo;
|
||||||
|
|
||||||
|
@Schema(description = "创建者")
|
||||||
|
private Long creator;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "快照数据脱敏规则版本")
|
||||||
|
private Integer redactionVersion;
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package xiaozhi.modules.agent.service;
|
package xiaozhi.modules.agent.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||||
|
|
||||||
import xiaozhi.modules.agent.entity.AgentChatAudioEntity;
|
import xiaozhi.modules.agent.entity.AgentChatAudioEntity;
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ import xiaozhi.modules.agent.entity.AgentChatAudioEntity;
|
|||||||
* @version 1.0, 2025/5/8
|
* @version 1.0, 2025/5/8
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public interface AgentChatAudioService extends IService<AgentChatAudioEntity> {
|
public interface AgentChatAudioService extends IRepository<AgentChatAudioEntity> {
|
||||||
/**
|
/**
|
||||||
* 保存音频数据
|
* 保存音频数据
|
||||||
*
|
*
|
||||||
|
|||||||
+18
-2
@@ -3,7 +3,7 @@ package xiaozhi.modules.agent.service;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||||
@@ -18,7 +18,7 @@ import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO;
|
|||||||
* @version 1.0, 2025/4/30
|
* @version 1.0, 2025/4/30
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public interface AgentChatHistoryService extends IService<AgentChatHistoryEntity> {
|
public interface AgentChatHistoryService extends IRepository<AgentChatHistoryEntity> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据智能体ID获取会话列表
|
* 根据智能体ID获取会话列表
|
||||||
@@ -37,6 +37,14 @@ public interface AgentChatHistoryService extends IService<AgentChatHistoryEntity
|
|||||||
*/
|
*/
|
||||||
List<AgentChatHistoryDTO> getChatHistoryBySessionId(String agentId, String sessionId);
|
List<AgentChatHistoryDTO> getChatHistoryBySessionId(String agentId, String sessionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据会话ID获取智能体ID
|
||||||
|
*
|
||||||
|
* @param sessionId 会话ID
|
||||||
|
* @return 智能体ID
|
||||||
|
*/
|
||||||
|
String getAgentIdBySessionId(String sessionId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据智能体ID删除聊天记录
|
* 根据智能体ID删除聊天记录
|
||||||
*
|
*
|
||||||
@@ -62,6 +70,14 @@ public interface AgentChatHistoryService extends IService<AgentChatHistoryEntity
|
|||||||
*/
|
*/
|
||||||
String getContentByAudioId(String audioId);
|
String getContentByAudioId(String audioId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据音频ID获取智能体ID
|
||||||
|
*
|
||||||
|
* @param audioId 音频ID
|
||||||
|
* @return 智能体ID
|
||||||
|
*/
|
||||||
|
String getAgentIdByAudioId(String audioId);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询此音频id是否属于此智能体
|
* 查询此音频id是否属于此智能体
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ package xiaozhi.modules.agent.service;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||||
|
|
||||||
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
|||||||
* @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Service
|
* @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Service
|
||||||
* @createDate 2025-05-25 22:33:17
|
* @createDate 2025-05-25 22:33:17
|
||||||
*/
|
*/
|
||||||
public interface AgentPluginMappingService extends IService<AgentPluginMapping> {
|
public interface AgentPluginMappingService extends IRepository<AgentPluginMapping> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据智能体id获取插件参数
|
* 根据智能体id获取插件参数
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import xiaozhi.common.page.PageData;
|
|||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentDTO;
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentMemoryDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
@@ -35,6 +36,15 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
*/
|
*/
|
||||||
AgentInfoVO getAgentById(String id);
|
AgentInfoVO getAgentById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获取当前用户有权访问的智能体
|
||||||
|
*
|
||||||
|
* @param id 智能体ID
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
* @return 智能体实体
|
||||||
|
*/
|
||||||
|
AgentInfoVO getAgentById(String id, Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入智能体
|
* 插入智能体
|
||||||
*
|
*
|
||||||
@@ -50,6 +60,13 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
*/
|
*/
|
||||||
void deleteAgentByUserId(Long userId);
|
void deleteAgentByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除智能体及其关联数据
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
*/
|
||||||
|
void deleteAgent(String agentId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户智能体列表
|
* 获取用户智能体列表
|
||||||
*
|
*
|
||||||
@@ -93,6 +110,41 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
*/
|
*/
|
||||||
void updateAgentById(String agentId, AgentUpdateDTO dto);
|
void updateAgentById(String agentId, AgentUpdateDTO dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前用户有权访问的智能体
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
* @param dto 更新智能体所需的信息
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
*/
|
||||||
|
void updateAgentById(String agentId, AgentUpdateDTO dto, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备MAC地址更新当前用户有权访问的智能体记忆
|
||||||
|
*
|
||||||
|
* @param macAddress 设备MAC地址
|
||||||
|
* @param dto 智能体记忆
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
*/
|
||||||
|
void updateAgentMemoryByDeviceMacAddress(String macAddress, AgentMemoryDTO dto, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除当前用户有权访问的智能体
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
*/
|
||||||
|
void deleteAgentById(String agentId, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新智能体
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
* @param dto 更新智能体所需的信息
|
||||||
|
* @param createSnapshot 是否创建配置快照
|
||||||
|
*/
|
||||||
|
void updateAgentById(String agentId, AgentUpdateDTO dto, boolean createSnapshot);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建智能体
|
* 创建智能体
|
||||||
*
|
*
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package xiaozhi.modules.agent.service;
|
||||||
|
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.service.BaseService;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotPageDTO;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
||||||
|
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
||||||
|
|
||||||
|
public interface AgentSnapshotService extends BaseService<AgentSnapshotEntity> {
|
||||||
|
void createSnapshot(String agentId, String source);
|
||||||
|
|
||||||
|
PageData<AgentSnapshotVO> page(String agentId, AgentSnapshotPageDTO params);
|
||||||
|
|
||||||
|
AgentSnapshotVO getSnapshot(String agentId, String snapshotId);
|
||||||
|
|
||||||
|
void restoreSnapshot(String agentId, String snapshotId, String currentStateToken);
|
||||||
|
|
||||||
|
void deleteSnapshot(String agentId, String snapshotId);
|
||||||
|
|
||||||
|
Integer getCurrentVersionNo(String agentId);
|
||||||
|
|
||||||
|
void deleteByAgentId(String agentId);
|
||||||
|
|
||||||
|
long redactLegacySnapshots();
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package xiaozhi.modules.agent.service;
|
package xiaozhi.modules.agent.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||||
|
|
||||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
|||||||
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service
|
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service
|
||||||
* @createDate 2025-03-22 11:48:18
|
* @createDate 2025-03-22 11:48:18
|
||||||
*/
|
*/
|
||||||
public interface AgentTemplateService extends IService<AgentTemplateEntity> {
|
public interface AgentTemplateService extends IRepository<AgentTemplateEntity> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取默认模板
|
* 获取默认模板
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ package xiaozhi.modules.agent.service.impl;
|
|||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.spring.repository.CrudRepository;
|
||||||
|
|
||||||
import xiaozhi.modules.agent.dao.AiAgentChatAudioDao;
|
import xiaozhi.modules.agent.dao.AiAgentChatAudioDao;
|
||||||
import xiaozhi.modules.agent.entity.AgentChatAudioEntity;
|
import xiaozhi.modules.agent.entity.AgentChatAudioEntity;
|
||||||
@@ -16,7 +16,7 @@ import xiaozhi.modules.agent.service.AgentChatAudioService;
|
|||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class AgentChatAudioServiceImpl extends ServiceImpl<AiAgentChatAudioDao, AgentChatAudioEntity>
|
public class AgentChatAudioServiceImpl extends CrudRepository<AiAgentChatAudioDao, AgentChatAudioEntity>
|
||||||
implements AgentChatAudioService {
|
implements AgentChatAudioService {
|
||||||
@Override
|
@Override
|
||||||
public String saveAudio(byte[] audioData) {
|
public String saveAudio(byte[] audioData) {
|
||||||
|
|||||||
+29
-3
@@ -14,7 +14,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.spring.repository.CrudRepository;
|
||||||
|
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
@@ -39,7 +39,7 @@ import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO;
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryDao, AgentChatHistoryEntity>
|
public class AgentChatHistoryServiceImpl extends CrudRepository<AiAgentChatHistoryDao, AgentChatHistoryEntity>
|
||||||
implements AgentChatHistoryService {
|
implements AgentChatHistoryService {
|
||||||
|
|
||||||
private final AgentChatTitleService agentChatTitleService;
|
private final AgentChatTitleService agentChatTitleService;
|
||||||
@@ -88,6 +88,19 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
|||||||
return ConvertUtils.sourceToTarget(historyList, AgentChatHistoryDTO.class);
|
return ConvertUtils.sourceToTarget(historyList, AgentChatHistoryDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAgentIdBySessionId(String sessionId) {
|
||||||
|
if (sessionId == null || sessionId.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AgentChatHistoryEntity entity = baseMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<AgentChatHistoryEntity>()
|
||||||
|
.select(AgentChatHistoryEntity::getAgentId)
|
||||||
|
.eq(AgentChatHistoryEntity::getSessionId, sessionId)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
return entity == null ? null : entity.getAgentId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText) {
|
public void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText) {
|
||||||
@@ -154,7 +167,7 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
|||||||
|
|
||||||
// 尝试解析为 JSON
|
// 尝试解析为 JSON
|
||||||
try {
|
try {
|
||||||
Map<String, Object> jsonMap = JsonUtils.parseObject(content, Map.class);
|
Map<String, Object> jsonMap = JsonUtils.parseMap(content);
|
||||||
if (jsonMap != null && jsonMap.containsKey("content")) {
|
if (jsonMap != null && jsonMap.containsKey("content")) {
|
||||||
Object contentObj = jsonMap.get("content");
|
Object contentObj = jsonMap.get("content");
|
||||||
return contentObj != null ? contentObj.toString() : content;
|
return contentObj != null ? contentObj.toString() : content;
|
||||||
@@ -176,6 +189,19 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
|||||||
return agentChatHistoryEntity == null ? null : agentChatHistoryEntity.getContent();
|
return agentChatHistoryEntity == null ? null : agentChatHistoryEntity.getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAgentIdByAudioId(String audioId) {
|
||||||
|
if (audioId == null || audioId.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AgentChatHistoryEntity entity = baseMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<AgentChatHistoryEntity>()
|
||||||
|
.select(AgentChatHistoryEntity::getAgentId)
|
||||||
|
.eq(AgentChatHistoryEntity::getAudioId, audioId)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
return entity == null ? null : entity.getAgentId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAudioOwnedByAgent(String audioId, String agentId) {
|
public boolean isAudioOwnedByAgent(String audioId, String agentId) {
|
||||||
// 查询是否有指定音频id和智能体id的数据,如果有且只有一条说明此数据属性此智能体
|
// 查询是否有指定音频id和智能体id的数据,如果有且只有一条说明此数据属性此智能体
|
||||||
|
|||||||
+1
-1
@@ -118,7 +118,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
{
|
{
|
||||||
setSummaryMemory(summaryDTO.getSummary());
|
setSummaryMemory(summaryDTO.getSummary());
|
||||||
}
|
}
|
||||||
});
|
}, false);
|
||||||
log.info("成功保存会话 {} 的聊天记录总结到智能体 {}", sessionId, agentId);
|
log.info("成功保存会话 {} 的聊天记录总结到智能体 {}", sessionId, agentId);
|
||||||
} else {
|
} else {
|
||||||
log.info("生成总结失败: {}", summaryDTO.getErrorMessage());
|
log.info("生成总结失败: {}", summaryDTO.getErrorMessage());
|
||||||
|
|||||||
+9
-9
@@ -76,7 +76,7 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
|||||||
// 等待初始化响应 (id=1) - 移除固定延迟,改为响应驱动
|
// 等待初始化响应 (id=1) - 移除固定延迟,改为响应驱动
|
||||||
List<String> initResponses = client.listenerWithoutClose(response -> {
|
List<String> initResponses = client.listenerWithoutClose(response -> {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
Map<String, Object> jsonMap = JsonUtils.parseMap(response);
|
||||||
if (jsonMap != null && Integer.valueOf(1).equals(jsonMap.get("id"))) {
|
if (jsonMap != null && Integer.valueOf(1).equals(jsonMap.get("id"))) {
|
||||||
// 检查是否有result字段,表示初始化成功
|
// 检查是否有result字段,表示初始化成功
|
||||||
return jsonMap.containsKey("result") && !jsonMap.containsKey("error");
|
return jsonMap.containsKey("result") && !jsonMap.containsKey("error");
|
||||||
@@ -92,7 +92,7 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
|||||||
boolean initSucceeded = false;
|
boolean initSucceeded = false;
|
||||||
for (String response : initResponses) {
|
for (String response : initResponses) {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
Map<String, Object> jsonMap = JsonUtils.parseMap(response);
|
||||||
if (jsonMap != null && Integer.valueOf(1).equals(jsonMap.get("id"))) {
|
if (jsonMap != null && Integer.valueOf(1).equals(jsonMap.get("id"))) {
|
||||||
if (jsonMap.containsKey("result")) {
|
if (jsonMap.containsKey("result")) {
|
||||||
log.info("MCP初始化成功,智能体ID: {}", id);
|
log.info("MCP初始化成功,智能体ID: {}", id);
|
||||||
@@ -123,7 +123,7 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
|||||||
// 等待工具列表响应 (id=2)
|
// 等待工具列表响应 (id=2)
|
||||||
List<String> toolsResponses = client.listener(response -> {
|
List<String> toolsResponses = client.listener(response -> {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
Map<String, Object> jsonMap = JsonUtils.parseMap(response);
|
||||||
return jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id"));
|
return jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("解析工具列表响应失败: {}", response, e);
|
log.warn("解析工具列表响应失败: {}", response, e);
|
||||||
@@ -134,18 +134,18 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
|||||||
// 处理工具列表响应
|
// 处理工具列表响应
|
||||||
for (String response : toolsResponses) {
|
for (String response : toolsResponses) {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
Map<String, Object> jsonMap = JsonUtils.parseMap(response);
|
||||||
if (jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id"))) {
|
if (jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id"))) {
|
||||||
// 检查是否有result字段
|
// 检查是否有result字段
|
||||||
Object resultObj = jsonMap.get("result");
|
Object resultObj = jsonMap.get("result");
|
||||||
if (resultObj instanceof Map) {
|
if (resultObj instanceof Map<?, ?>) {
|
||||||
Map<String, Object> resultMap = (Map<String, Object>) resultObj;
|
Map<String, Object> resultMap = JsonUtils.toStringObjectMap(resultObj);
|
||||||
Object toolsObj = resultMap.get("tools");
|
Object toolsObj = resultMap.get("tools");
|
||||||
if (toolsObj instanceof List) {
|
if (toolsObj instanceof List<?>) {
|
||||||
List<Map<String, Object>> toolsList = (List<Map<String, Object>>) toolsObj;
|
List<Map<String, Object>> toolsList = JsonUtils.toStringObjectMapList(toolsObj);
|
||||||
// 提取工具名称列表
|
// 提取工具名称列表
|
||||||
List<String> result = toolsList.stream()
|
List<String> result = toolsList.stream()
|
||||||
.map(tool -> (String) tool.get("name"))
|
.map(tool -> String.class.cast(tool.get("name")))
|
||||||
.filter(name -> name != null)
|
.filter(name -> name != null)
|
||||||
.sorted()
|
.sorted()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.spring.repository.CrudRepository;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -29,7 +29,7 @@ import xiaozhi.modules.model.service.ModelConfigService;
|
|||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AgentPluginMappingServiceImpl extends ServiceImpl<AgentPluginMappingMapper, AgentPluginMapping>
|
public class AgentPluginMappingServiceImpl extends CrudRepository<AgentPluginMappingMapper, AgentPluginMapping>
|
||||||
implements AgentPluginMappingService {
|
implements AgentPluginMappingService {
|
||||||
private final AgentPluginMappingMapper agentPluginMappingMapper;
|
private final AgentPluginMappingMapper agentPluginMappingMapper;
|
||||||
private final KnowledgeBaseService knowledgeBaseService;
|
private final KnowledgeBaseService knowledgeBaseService;
|
||||||
|
|||||||
+169
-29
@@ -15,8 +15,8 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
@@ -34,6 +34,7 @@ import xiaozhi.modules.agent.dao.AgentDao;
|
|||||||
import xiaozhi.modules.agent.dao.AgentTagDao;
|
import xiaozhi.modules.agent.dao.AgentTagDao;
|
||||||
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentDTO;
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentMemoryDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentTagDTO;
|
import xiaozhi.modules.agent.dto.AgentTagDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
|
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
|
||||||
@@ -45,6 +46,7 @@ import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
|||||||
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
||||||
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||||
import xiaozhi.modules.agent.service.AgentService;
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentSnapshotService;
|
||||||
import xiaozhi.modules.agent.service.AgentTagService;
|
import xiaozhi.modules.agent.service.AgentTagService;
|
||||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
@@ -76,6 +78,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
private final AgentContextProviderService agentContextProviderService;
|
private final AgentContextProviderService agentContextProviderService;
|
||||||
private final AgentTagService agentTagService;
|
private final AgentTagService agentTagService;
|
||||||
private final CorrectWordFileService correctWordFileService;
|
private final CorrectWordFileService correctWordFileService;
|
||||||
|
private final AgentSnapshotService agentSnapshotService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
|
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
|
||||||
@@ -92,6 +95,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
if (agent == null) {
|
if (agent == null) {
|
||||||
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
requireCurrentUserPermissionIfPresent(agent);
|
||||||
|
|
||||||
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||||
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
|
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
|
||||||
@@ -109,11 +113,71 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
// 查询替换词文件ID列表
|
// 查询替换词文件ID列表
|
||||||
List<String> correctWordFileIds = correctWordFileService.getAgentCorrectWordFileIds(id);
|
List<String> correctWordFileIds = correctWordFileService.getAgentCorrectWordFileIds(id);
|
||||||
agent.setCorrectWordFileIds(correctWordFileIds);
|
agent.setCorrectWordFileIds(correctWordFileIds);
|
||||||
|
agent.setCurrentVersionNo(agentSnapshotService.getCurrentVersionNo(id));
|
||||||
|
|
||||||
// 无需额外查询插件列表,已通过SQL查询出来
|
// 无需额外查询插件列表,已通过SQL查询出来
|
||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgentInfoVO getAgentById(String id, Long userId) {
|
||||||
|
AgentInfoVO agent = getAgentById(id);
|
||||||
|
requireAgentPermission(agent, userId);
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentEntity getAgentEntityOrThrow(String agentId) {
|
||||||
|
AgentEntity agent = agentDao.selectById(agentId);
|
||||||
|
if (agent == null) {
|
||||||
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCurrentUserSuperAdmin() {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
return user != null && Integer.valueOf(SuperAdminEnum.YES.value()).equals(user.getSuperAdmin());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireCurrentUserPermissionIfPresent(AgentEntity agent) {
|
||||||
|
Long userId = SecurityUser.getUserId();
|
||||||
|
if (userId != null) {
|
||||||
|
requireAgentPermission(agent, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasAgentPermission(AgentEntity agent, Long userId) {
|
||||||
|
if (agent == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isCurrentUserSuperAdmin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return userId != null && userId.equals(agent.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireAgentPermission(AgentEntity agent, Long userId) {
|
||||||
|
if (!hasAgentPermission(agent, userId)) {
|
||||||
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasDevicePermission(DeviceEntity device, Long userId) {
|
||||||
|
if (device == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isCurrentUserSuperAdmin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return userId != null && userId.equals(device.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireDevicePermission(DeviceEntity device, Long userId) {
|
||||||
|
if (!hasDevicePermission(device, userId)) {
|
||||||
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean insert(AgentEntity entity) {
|
public boolean insert(AgentEntity entity) {
|
||||||
// 如果ID为空,自动生成一个UUID作为ID
|
// 如果ID为空,自动生成一个UUID作为ID
|
||||||
@@ -135,10 +199,30 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void deleteAgentByUserId(Long userId) {
|
public void deleteAgentByUserId(Long userId) {
|
||||||
UpdateWrapper<AgentEntity> wrapper = new UpdateWrapper<>();
|
List<AgentEntity> agents = baseDao.selectList(new QueryWrapper<AgentEntity>()
|
||||||
wrapper.eq("user_id", userId);
|
.select("id")
|
||||||
baseDao.delete(wrapper);
|
.eq("user_id", userId));
|
||||||
|
for (AgentEntity agent : agents) {
|
||||||
|
deleteAgent(agent.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteAgent(String agentId) {
|
||||||
|
if (agentDao.selectByIdForUpdate(agentId) == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deviceService.deleteByAgentId(agentId);
|
||||||
|
agentChatHistoryService.deleteByAgentId(agentId, true, true);
|
||||||
|
agentPluginMappingService.deleteByAgentId(agentId);
|
||||||
|
agentContextProviderService.deleteByAgentId(agentId);
|
||||||
|
correctWordFileService.deleteMappingsByAgentId(agentId);
|
||||||
|
agentTagService.deleteAgentTags(agentId);
|
||||||
|
agentSnapshotService.deleteByAgentId(agentId);
|
||||||
|
deleteById(agentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -254,33 +338,47 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean checkAgentPermission(String agentId, Long userId) {
|
public boolean checkAgentPermission(String agentId, Long userId) {
|
||||||
if (SecurityUser.getUser() == null || SecurityUser.getUser().getId() == null) {
|
AgentEntity agent = agentDao.selectById(agentId);
|
||||||
return false;
|
return hasAgentPermission(agent, userId);
|
||||||
}
|
|
||||||
// 获取智能体信息
|
|
||||||
AgentEntity agent = getAgentById(agentId);
|
|
||||||
if (agent == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是超级管理员,直接返回true
|
|
||||||
if (SecurityUser.getUser().getSuperAdmin() == SuperAdminEnum.YES.value()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是智能体的所有者
|
|
||||||
return userId.equals(agent.getUserId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据id更新智能体信息
|
// 根据id更新智能体信息
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void updateAgentById(String agentId, AgentUpdateDTO dto) {
|
public void updateAgentById(String agentId, AgentUpdateDTO dto) {
|
||||||
// 先查询现有实体
|
updateAgentById(agentId, dto, true);
|
||||||
AgentEntity existingEntity = this.getAgentById(agentId);
|
}
|
||||||
if (existingEntity == null) {
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateAgentById(String agentId, AgentUpdateDTO dto, Long userId) {
|
||||||
|
updateAgentById(agentId, dto, userId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据id更新智能体信息
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateAgentById(String agentId, AgentUpdateDTO dto, boolean createSnapshot) {
|
||||||
|
updateAgentById(agentId, dto, null, createSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAgentById(String agentId, AgentUpdateDTO dto, Long userId, boolean createSnapshot) {
|
||||||
|
AgentEntity lockedAgent = agentDao.selectByIdForUpdate(agentId);
|
||||||
|
if (lockedAgent == null) {
|
||||||
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
if (userId == null) {
|
||||||
|
requireCurrentUserPermissionIfPresent(lockedAgent);
|
||||||
|
} else {
|
||||||
|
requireAgentPermission(lockedAgent, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 锁定后查询现有实体和关联配置
|
||||||
|
AgentEntity existingEntity = this.getAgentById(agentId);
|
||||||
|
if (createSnapshot) {
|
||||||
|
int currentVersionNo = agentSnapshotService.getCurrentVersionNo(agentId);
|
||||||
|
agentSnapshotService.createSnapshot(agentId, currentVersionNo == 0 ? "initial" : "current");
|
||||||
|
}
|
||||||
|
|
||||||
// 只更新提供的非空字段
|
// 只更新提供的非空字段
|
||||||
if (dto.getAgentName() != null) {
|
if (dto.getAgentName() != null) {
|
||||||
@@ -385,10 +483,10 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (!toUpdate.isEmpty()) {
|
if (!toUpdate.isEmpty()) {
|
||||||
agentPluginMappingService.updateBatchById(toUpdate);
|
agentPluginMappingService.updateBatchById(toUpdate, IRepository.DEFAULT_BATCH_SIZE);
|
||||||
}
|
}
|
||||||
if (!toInsert.isEmpty()) {
|
if (!toInsert.isEmpty()) {
|
||||||
agentPluginMappingService.saveBatch(toInsert);
|
agentPluginMappingService.saveBatch(toInsert, IRepository.DEFAULT_BATCH_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 删除本次不在提交列表里的插件映射
|
// 5. 删除本次不在提交列表里的插件映射
|
||||||
@@ -397,7 +495,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
.map(AgentPluginMapping::getId)
|
.map(AgentPluginMapping::getId)
|
||||||
.toList();
|
.toList();
|
||||||
if (!toDelete.isEmpty()) {
|
if (!toDelete.isEmpty()) {
|
||||||
agentPluginMappingService.removeBatchByIds(toDelete);
|
agentPluginMappingService.removeByIds(toDelete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,11 +528,42 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
correctWordFileService.saveAgentCorrectWords(agentId, dto.getCorrectWordFileIds());
|
correctWordFileService.saveAgentCorrectWords(agentId, dto.getCorrectWordFileIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean b = validateLLMIntentParams(dto.getLlmModelId(), dto.getIntentModelId());
|
// 更新智能体标签
|
||||||
|
if (dto.getTagNames() != null || dto.getTagIds() != null) {
|
||||||
|
agentTagService.saveAgentTags(agentId, dto.getTagIds(), dto.getTagNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean b = validateLLMIntentParams(existingEntity.getLlmModelId(), existingEntity.getIntentModelId());
|
||||||
if (!b) {
|
if (!b) {
|
||||||
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
|
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
|
||||||
}
|
}
|
||||||
this.updateById(existingEntity);
|
this.updateById(existingEntity);
|
||||||
|
if (createSnapshot) {
|
||||||
|
agentSnapshotService.createSnapshot(agentId, "config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateAgentMemoryByDeviceMacAddress(String macAddress, AgentMemoryDTO dto, Long userId) {
|
||||||
|
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
||||||
|
if (device == null || StringUtils.isBlank(device.getAgentId()) || dto == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requireDevicePermission(device, userId);
|
||||||
|
|
||||||
|
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
|
||||||
|
agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
|
||||||
|
updateAgentById(device.getAgentId(), agentUpdateDTO, userId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteAgentById(String agentId, Long userId) {
|
||||||
|
AgentEntity agent = getAgentEntityOrThrow(agentId);
|
||||||
|
requireAgentPermission(agent, userId);
|
||||||
|
deleteAgent(agentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -490,10 +619,16 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
entity.setTtsVoiceId(template.getTtsVoiceId());
|
entity.setTtsVoiceId(template.getTtsVoiceId());
|
||||||
|
entity.setTtsLanguage(defaultIfBlank(template.getTtsLanguage(),
|
||||||
|
timbreModelService.getDefaultLanguageById(entity.getTtsVoiceId())));
|
||||||
entity.setMemModelId(template.getMemModelId());
|
entity.setMemModelId(template.getMemModelId());
|
||||||
entity.setIntentModelId(template.getIntentModelId());
|
entity.setIntentModelId(template.getIntentModelId());
|
||||||
entity.setSystemPrompt(template.getSystemPrompt());
|
entity.setSystemPrompt(template.getSystemPrompt());
|
||||||
entity.setSummaryMemory(template.getSummaryMemory());
|
entity.setSummaryMemory(template.getSummaryMemory());
|
||||||
|
if (Constant.MEMORY_NO_MEM.equals(entity.getMemModelId())
|
||||||
|
|| Constant.MEMORY_MEM_REPORT_ONLY.equals(entity.getMemModelId())) {
|
||||||
|
entity.setSummaryMemory("");
|
||||||
|
}
|
||||||
|
|
||||||
// 根据记忆模型类型设置默认的chatHistoryConf值
|
// 根据记忆模型类型设置默认的chatHistoryConf值
|
||||||
if (template.getMemModelId() != null) {
|
if (template.getMemModelId() != null) {
|
||||||
@@ -542,7 +677,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
mapping.setPluginId(pluginId);
|
mapping.setPluginId(pluginId);
|
||||||
|
|
||||||
Map<String, Object> paramInfo = new HashMap<>();
|
Map<String, Object> paramInfo = new HashMap<>();
|
||||||
List<Map<String, Object>> fields = JsonUtils.parseObject(provider.getFields(), List.class);
|
List<Map<String, Object>> fields = JsonUtils.parseMapList(provider.getFields());
|
||||||
if (fields != null) {
|
if (fields != null) {
|
||||||
for (Map<String, Object> field : fields) {
|
for (Map<String, Object> field : fields) {
|
||||||
paramInfo.put((String) field.get("key"), field.get("default"));
|
paramInfo.put((String) field.get("key"), field.get("default"));
|
||||||
@@ -553,10 +688,15 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
toInsert.add(mapping);
|
toInsert.add(mapping);
|
||||||
}
|
}
|
||||||
// 保存默认插件
|
// 保存默认插件
|
||||||
agentPluginMappingService.saveBatch(toInsert);
|
agentPluginMappingService.saveBatch(toInsert, IRepository.DEFAULT_BATCH_SIZE);
|
||||||
|
agentSnapshotService.createSnapshot(entity.getId(), "initial");
|
||||||
return entity.getId();
|
return entity.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String defaultIfBlank(String value, String defaultValue) {
|
||||||
|
return StringUtils.isBlank(value) ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
private String getDefaultLLMModelId() {
|
private String getDefaultLLMModelId() {
|
||||||
try {
|
try {
|
||||||
List<ModelConfigEntity> llmConfigs = modelConfigService.getEnabledModelsByType("LLM");
|
List<ModelConfigEntity> llmConfigs = modelConfigService.getEnabledModelsByType("LLM");
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package xiaozhi.modules.agent.service.impl;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xiaozhi.modules.agent.service.AgentSnapshotService;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AgentSnapshotRedactionRunner implements SmartInitializingSingleton {
|
||||||
|
static final long ROLLING_DEPLOYMENT_INITIAL_DELAY_MILLIS = 5_000;
|
||||||
|
static final long ROLLING_DEPLOYMENT_FIXED_DELAY_MILLIS = 15_000;
|
||||||
|
|
||||||
|
private final AgentSnapshotService agentSnapshotService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterSingletonsInstantiated() {
|
||||||
|
redactAndReport("startup");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(initialDelay = ROLLING_DEPLOYMENT_INITIAL_DELAY_MILLIS,
|
||||||
|
fixedDelay = ROLLING_DEPLOYMENT_FIXED_DELAY_MILLIS)
|
||||||
|
public void redactLateRollingDeploymentWrites() {
|
||||||
|
redactAndReport("rolling-deployment");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void redactAndReport(String trigger) {
|
||||||
|
long startedAt = System.nanoTime();
|
||||||
|
try {
|
||||||
|
long migrated = agentSnapshotService.redactLegacySnapshots();
|
||||||
|
long durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startedAt);
|
||||||
|
if (migrated > 0) {
|
||||||
|
log.warn("Agent snapshot legacy redaction trigger={} migrated={} durationMs={}. Rotate credentials "
|
||||||
|
+ "that may have appeared in historical snapshot URLs, cookies, sessions, or structured "
|
||||||
|
+ "headers.", trigger, migrated, durationMillis);
|
||||||
|
} else if ("startup".equals(trigger)) {
|
||||||
|
log.info("Agent snapshot legacy redaction startup pass completed: migrated=0 durationMs={}; "
|
||||||
|
+ "rolling-deployment compensation starts after {} ms and repeats every {} ms.",
|
||||||
|
durationMillis, ROLLING_DEPLOYMENT_INITIAL_DELAY_MILLIS,
|
||||||
|
ROLLING_DEPLOYMENT_FIXED_DELAY_MILLIS);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
if ("startup".equals(trigger)) {
|
||||||
|
log.error("Agent snapshot legacy redaction failed during startup; blocking application startup "
|
||||||
|
+ "before it can accept traffic.", exception);
|
||||||
|
} else {
|
||||||
|
log.error("Agent snapshot legacy redaction failed during rolling-deployment compensation; the "
|
||||||
|
+ "scheduler will retry on its next run.", exception);
|
||||||
|
}
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1772
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -133,7 +133,7 @@ public class AgentTagServiceImpl extends BaseServiceImpl<AgentTagDao, AgentTagEn
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tagIds != null && !tagIds.isEmpty()) {
|
if (tagIds != null && !tagIds.isEmpty()) {
|
||||||
List<AgentTagEntity> tagIdEntities = baseDao.selectBatchIds(tagIds);
|
List<AgentTagEntity> tagIdEntities = baseDao.selectByIds(tagIds);
|
||||||
for (AgentTagEntity tag : tagIdEntities) {
|
for (AgentTagEntity tag : tagIdEntities) {
|
||||||
if (tag != null && (currentTagNames.contains(tag.getTagName()) ||
|
if (tag != null && (currentTagNames.contains(tag.getTagName()) ||
|
||||||
newTagNames.contains(tag.getTagName()))) {
|
newTagNames.contains(tag.getTagName()))) {
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.spring.repository.CrudRepository;
|
||||||
|
|
||||||
import xiaozhi.modules.agent.dao.AgentTemplateDao;
|
import xiaozhi.modules.agent.dao.AgentTemplateDao;
|
||||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||||
@@ -21,7 +21,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|||||||
* @createDate 2025-03-22 11:48:18
|
* @createDate 2025-03-22 11:48:18
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class AgentTemplateServiceImpl extends ServiceImpl<AgentTemplateDao, AgentTemplateEntity>
|
public class AgentTemplateServiceImpl extends CrudRepository<AgentTemplateDao, AgentTemplateEntity>
|
||||||
implements AgentTemplateService {
|
implements AgentTemplateService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+2
-2
@@ -22,7 +22,7 @@ import org.springframework.util.MultiValueMap;
|
|||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.spring.repository.CrudRepository;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
@@ -46,7 +46,7 @@ import xiaozhi.modules.sys.service.SysParamsService;
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao, AgentVoicePrintEntity>
|
public class AgentVoicePrintServiceImpl extends CrudRepository<AgentVoicePrintDao, AgentVoicePrintEntity>
|
||||||
implements AgentVoicePrintService {
|
implements AgentVoicePrintService {
|
||||||
private final AgentChatAudioService agentChatAudioService;
|
private final AgentChatAudioService agentChatAudioService;
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package xiaozhi.modules.agent.typehandler;
|
||||||
|
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.ibatis.type.BaseTypeHandler;
|
||||||
|
import org.apache.ibatis.type.JdbcType;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
import xiaozhi.modules.agent.dto.ContextProviderDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON type handler for context providers.
|
||||||
|
*
|
||||||
|
* <p>Do not extend MyBatis-Plus {@code AbstractJsonTypeHandler}: its constructor and JSON handler
|
||||||
|
* contract changed in MyBatis-Plus 3.5.6. {@link BaseTypeHandler} is part of MyBatis itself and
|
||||||
|
* keeps this handler compatible with both 3.5.5 and newer MyBatis-Plus releases.</p>
|
||||||
|
*/
|
||||||
|
public class ContextProviderListTypeHandler extends BaseTypeHandler<List<ContextProviderDTO>> {
|
||||||
|
private static final TypeReference<List<ContextProviderDTO>> CONTEXT_PROVIDER_LIST_TYPE = new TypeReference<>() {
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonNullParameter(PreparedStatement ps, int i, List<ContextProviderDTO> parameter,
|
||||||
|
JdbcType jdbcType) throws SQLException {
|
||||||
|
ps.setString(i, JsonUtils.toJsonString(parameter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ContextProviderDTO> getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||||
|
return parseNullable(rs.getString(columnName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ContextProviderDTO> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||||
|
return parseNullable(rs.getString(columnIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ContextProviderDTO> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||||
|
return parseNullable(cs.getString(columnIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ContextProviderDTO> parseNullable(String json) {
|
||||||
|
if (StringUtils.isBlank(json)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<ContextProviderDTO> providers = JsonUtils.parseObject(json, CONTEXT_PROVIDER_LIST_TYPE);
|
||||||
|
return providers == null ? Collections.emptyList() : providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package xiaozhi.modules.agent.vo;
|
package xiaozhi.modules.agent.vo;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
|
||||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -20,7 +18,6 @@ import java.util.List;
|
|||||||
public class AgentInfoVO extends AgentEntity
|
public class AgentInfoVO extends AgentEntity
|
||||||
{
|
{
|
||||||
@Schema(description = "插件列表Id")
|
@Schema(description = "插件列表Id")
|
||||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
|
||||||
private List<AgentPluginMapping> functions;
|
private List<AgentPluginMapping> functions;
|
||||||
|
|
||||||
@Schema(description = "上下文源配置")
|
@Schema(description = "上下文源配置")
|
||||||
@@ -28,4 +25,7 @@ public class AgentInfoVO extends AgentEntity
|
|||||||
|
|
||||||
@Schema(description = "替换词文件ID列表")
|
@Schema(description = "替换词文件ID列表")
|
||||||
private List<String> correctWordFileIds;
|
private List<String> correctWordFileIds;
|
||||||
|
|
||||||
|
@Schema(description = "当前配置版本号")
|
||||||
|
private Integer currentVersionNo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package xiaozhi.modules.agent.vo;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotDataDTO;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "智能体配置快照")
|
||||||
|
public class AgentSnapshotVO {
|
||||||
|
private String id;
|
||||||
|
private String agentId;
|
||||||
|
@Schema(description = "所属用户ID,表示该快照归属的智能体所有者")
|
||||||
|
private Long userId;
|
||||||
|
private Integer versionNo;
|
||||||
|
private List<String> changedFields;
|
||||||
|
private List<String> fieldOrder;
|
||||||
|
private String source;
|
||||||
|
@Schema(description = "恢复来源快照ID,仅恢复结果版本有值")
|
||||||
|
private String restoreFromSnapshotId;
|
||||||
|
@Schema(description = "恢复来源版本号,仅恢复结果版本有值")
|
||||||
|
private Integer restoreFromVersionNo;
|
||||||
|
@Schema(description = "创建者,表示触发本次快照写入的操作人")
|
||||||
|
private Long creator;
|
||||||
|
private Date createdAt;
|
||||||
|
private AgentSnapshotDataDTO snapshotData;
|
||||||
|
private AgentSnapshotDataDTO afterSnapshotData;
|
||||||
|
@Schema(description = "恢复预览对应的脱敏当前配置,仅详情接口有值")
|
||||||
|
private AgentSnapshotDataDTO currentSnapshotData;
|
||||||
|
@Schema(description = "恢复预览对应的当前配置状态指纹,仅详情接口有值")
|
||||||
|
private String currentStateToken;
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ public interface ConfigService {
|
|||||||
* @param isCache 是否缓存
|
* @param isCache 是否缓存
|
||||||
* @return 配置信息
|
* @return 配置信息
|
||||||
*/
|
*/
|
||||||
Object getConfig(Boolean isCache);
|
Map<String, Object> getConfig(Boolean isCache);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取智能体模型配置
|
* 获取智能体模型配置
|
||||||
|
|||||||
+7
-7
@@ -65,12 +65,12 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
private final CorrectWordFileService correctWordFileService;
|
private final CorrectWordFileService correctWordFileService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getConfig(Boolean isCache) {
|
public Map<String, Object> getConfig(Boolean isCache) {
|
||||||
if (isCache) {
|
if (isCache) {
|
||||||
// 先从Redis获取配置
|
// 先从Redis获取配置
|
||||||
Object cachedConfig = redisUtils.get(RedisKeys.getServerConfigKey());
|
Object cachedConfig = redisUtils.get(RedisKeys.getServerConfigKey());
|
||||||
if (cachedConfig != null) {
|
if (cachedConfig != null) {
|
||||||
return cachedConfig;
|
return JsonUtils.toStringObjectMap(cachedConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
if (isAdminRequest != null && "true".equals(isAdminRequest)) {
|
if (isAdminRequest != null && "true".equals(isAdminRequest)) {
|
||||||
// 管理控制台请求,返回getConfig的结果
|
// 管理控制台请求,返回getConfig的结果
|
||||||
redisUtils.delete(redisKey); // 使用后清理
|
redisUtils.delete(redisKey); // 使用后清理
|
||||||
return (Map<String, Object>) getConfig(true);
|
return getConfig(true);
|
||||||
}
|
}
|
||||||
// 根据MAC地址查找设备
|
// 根据MAC地址查找设备
|
||||||
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
||||||
@@ -277,10 +277,10 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
// 遍历除最后一个key之外的所有key
|
// 遍历除最后一个key之外的所有key
|
||||||
for (int i = 0; i < keys.length - 1; i++) {
|
for (int i = 0; i < keys.length - 1; i++) {
|
||||||
String key = keys[i];
|
String key = keys[i];
|
||||||
if (!current.containsKey(key)) {
|
Object nestedConfig = current.computeIfAbsent(key, ignored -> new HashMap<String, Object>());
|
||||||
current.put(key, new HashMap<String, Object>());
|
Map<String, Object> nestedMap = JsonUtils.toStringObjectMap(nestedConfig);
|
||||||
}
|
current.put(key, nestedMap);
|
||||||
current = (Map<String, Object>) current.get(key);
|
current = nestedMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理最后一个key
|
// 处理最后一个key
|
||||||
|
|||||||
+21
-14
@@ -8,6 +8,7 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
|
|||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.PutMapping;
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -124,7 +125,9 @@ public class DeviceController {
|
|||||||
return new Result<Void>().error("设备不存在");
|
return new Result<Void>().error("设备不存在");
|
||||||
}
|
}
|
||||||
BeanUtils.copyProperties(deviceUpdateDTO, entity);
|
BeanUtils.copyProperties(deviceUpdateDTO, entity);
|
||||||
deviceService.updateById(entity);
|
if (!deviceService.updateById(entity)) {
|
||||||
|
return new Result<Void>().error(ErrorCode.UPDATE_DATA_FAILED);
|
||||||
|
}
|
||||||
return new Result<Void>();
|
return new Result<Void>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,20 +177,26 @@ public class DeviceController {
|
|||||||
return new Result<Object>().ok(deviceAddressBookService.getAddressBookList(macAddress));
|
return new Result<Object>().ok(deviceAddressBookService.getAddressBookList(macAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/address-book/lookup")
|
@GetMapping("/address-book/call")
|
||||||
@Operation(summary = "根据昵称查找目标设备")
|
@Operation(summary = "根据昵称发起呼叫")
|
||||||
public Result<Map<String, String>> lookupByNickname(String callerMac, String nickname) {
|
public Result<Map<String, Object>> callByNickname(String callerMac, String nickname,
|
||||||
Map<String, String> result = deviceAddressBookService.lookupByNickname(callerMac, nickname);
|
@RequestParam(required = false, defaultValue = "false") boolean answer) {
|
||||||
|
Map<String, Object> result = deviceAddressBookService.callByNickname(callerMac, nickname, answer);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return new Result<Map<String, String>>().error("未找到对应设备");
|
return new Result<Map<String, Object>>().error("未找到对应设备");
|
||||||
}
|
}
|
||||||
return new Result<Map<String, String>>().ok(result);
|
return new Result<Map<String, Object>>().ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/address-book/alias")
|
@PutMapping("/address-book/alias")
|
||||||
@Operation(summary = "更新设备通讯录别名")
|
@Operation(summary = "更新设备通讯录别名")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> updateAlias(@Valid @RequestBody DeviceAddressBookAliasDTO dto) {
|
public Result<Void> updateAlias(@Valid @RequestBody DeviceAddressBookAliasDTO dto) {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
DeviceEntity callerDevice = deviceService.getDeviceByMacAddress(dto.getMacAddress());
|
||||||
|
if (callerDevice == null || !callerDevice.getUserId().equals(user.getId())) {
|
||||||
|
return new Result<Void>().error("无权限操作该设备");
|
||||||
|
}
|
||||||
deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), dto.getAlias(), null);
|
deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), dto.getAlias(), null);
|
||||||
return new Result<Void>();
|
return new Result<Void>();
|
||||||
}
|
}
|
||||||
@@ -196,14 +205,12 @@ public class DeviceController {
|
|||||||
@Operation(summary = "更新设备通讯录权限")
|
@Operation(summary = "更新设备通讯录权限")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> updatePermission(@Valid @RequestBody DeviceAddressBookPermissionDTO dto) {
|
public Result<Void> updatePermission(@Valid @RequestBody DeviceAddressBookPermissionDTO dto) {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
DeviceEntity callerDevice = deviceService.getDeviceByMacAddress(dto.getMacAddress());
|
||||||
|
if (callerDevice == null || !callerDevice.getUserId().equals(user.getId())) {
|
||||||
|
return new Result<Void>().error("无权限操作该设备");
|
||||||
|
}
|
||||||
deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), null, dto.getHasPermission());
|
deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), null, dto.getHasPermission());
|
||||||
return new Result<Void>();
|
return new Result<Void>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/call/forward")
|
|
||||||
@Operation(summary = "转发呼叫请求到网关")
|
|
||||||
public Result<Map<String, Object>> forwardCallRequest(String callerMac, String targetMac, String callerNickname) {
|
|
||||||
Map<String, Object> result = deviceService.forwardCallRequest(callerMac, targetMac, callerNickname);
|
|
||||||
return new Result<Map<String, Object>>().ok(result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,11 @@ import xiaozhi.modules.device.entity.DeviceAddressBookEntity;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public interface DeviceAddressBookDao extends BaseMapper<DeviceAddressBookEntity> {
|
public interface DeviceAddressBookDao extends BaseMapper<DeviceAddressBookEntity> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增设备通讯录记录
|
||||||
|
*/
|
||||||
|
int insertAddressBook(DeviceAddressBookEntity entity);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取设备通讯录列表
|
* 获取设备通讯录列表
|
||||||
*/
|
*/
|
||||||
@@ -26,4 +31,9 @@ public interface DeviceAddressBookDao extends BaseMapper<DeviceAddressBookEntity
|
|||||||
* 更新权限
|
* 更新权限
|
||||||
*/
|
*/
|
||||||
void updatePermission(@Param("macAddress") String macAddress, @Param("targetMac") String targetMac, @Param("hasPermission") Boolean hasPermission);
|
void updatePermission(@Param("macAddress") String macAddress, @Param("targetMac") String targetMac, @Param("hasPermission") Boolean hasPermission);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除设备相关的通讯录记录
|
||||||
|
*/
|
||||||
|
void deleteByMacAddresses(@Param("macAddresses") List<String> macAddresses);
|
||||||
}
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package xiaozhi.modules.device.dto;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设备绑定的DTO
|
|
||||||
*
|
|
||||||
* @author zjy
|
|
||||||
* @since 2025-3-28
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "设备连接头信息")
|
|
||||||
public class DeviceBindDTO {
|
|
||||||
|
|
||||||
@Schema(description = "mac地址")
|
|
||||||
private String macAddress;
|
|
||||||
|
|
||||||
@Schema(description = "所属用户id")
|
|
||||||
private Long userId;
|
|
||||||
|
|
||||||
@Schema(description = "智能体id")
|
|
||||||
private String agentId;
|
|
||||||
|
|
||||||
}
|
|
||||||
+8
-3
@@ -38,10 +38,15 @@ public interface DeviceAddressBookService {
|
|||||||
void refreshCache();
|
void refreshCache();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据昵称查找目标设备信息
|
* 根据昵称发起呼叫
|
||||||
* @param callerMac 主叫方MAC地址
|
* @param callerMac 主叫方MAC地址
|
||||||
* @param nickname 被叫方昵称
|
* @param nickname 被叫方昵称
|
||||||
* @return {targetMac: 目标MAC, callerNickname: 目标如何称呼主叫方}
|
* @param isAnswer 是否为接听模式(跳过权限检查)
|
||||||
*/
|
*/
|
||||||
Map<String, String> lookupByNickname(String callerMac, String nickname);
|
Map<String, Object> callByNickname(String callerMac, String nickname, boolean isAnswer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除设备相关的通讯录记录
|
||||||
|
*/
|
||||||
|
void deleteByMacAddresses(List<String> macAddresses);
|
||||||
}
|
}
|
||||||
@@ -138,10 +138,4 @@ public interface DeviceService extends BaseService<DeviceEntity> {
|
|||||||
*/
|
*/
|
||||||
Object callDeviceTool(String deviceId, String toolName, Map<String, Object> arguments);
|
Object callDeviceTool(String deviceId, String toolName, Map<String, Object> arguments);
|
||||||
|
|
||||||
/**
|
}
|
||||||
* 转发呼叫请求到网关
|
|
||||||
* @return 网关响应 {status, message}
|
|
||||||
*/
|
|
||||||
Map<String, Object> forwardCallRequest(String callerMac, String targetMac, String callerNickname);
|
|
||||||
|
|
||||||
}
|
|
||||||
+136
-34
@@ -1,20 +1,26 @@
|
|||||||
package xiaozhi.modules.device.service.impl;
|
package xiaozhi.modules.device.service.impl;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.modules.device.dao.DeviceAddressBookDao;
|
import xiaozhi.modules.device.dao.DeviceAddressBookDao;
|
||||||
import xiaozhi.modules.device.entity.DeviceAddressBookEntity;
|
import xiaozhi.modules.device.entity.DeviceAddressBookEntity;
|
||||||
import xiaozhi.modules.device.service.DeviceAddressBookService;
|
import xiaozhi.modules.device.service.DeviceAddressBookService;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class DeviceAddressBookServiceImpl implements DeviceAddressBookService {
|
public class DeviceAddressBookServiceImpl implements DeviceAddressBookService {
|
||||||
@@ -22,12 +28,14 @@ public class DeviceAddressBookServiceImpl implements DeviceAddressBookService {
|
|||||||
private final DeviceAddressBookDao deviceAddressBookDao;
|
private final DeviceAddressBookDao deviceAddressBookDao;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final DeviceService deviceService;
|
private final DeviceService deviceService;
|
||||||
|
private final SysParamsService sysParamsService;
|
||||||
|
|
||||||
public DeviceAddressBookServiceImpl(DeviceAddressBookDao deviceAddressBookDao, RedisUtils redisUtils,
|
public DeviceAddressBookServiceImpl(DeviceAddressBookDao deviceAddressBookDao, RedisUtils redisUtils,
|
||||||
DeviceService deviceService) {
|
@Lazy DeviceService deviceService, SysParamsService sysParamsService) {
|
||||||
this.deviceAddressBookDao = deviceAddressBookDao;
|
this.deviceAddressBookDao = deviceAddressBookDao;
|
||||||
this.redisUtils = redisUtils;
|
this.redisUtils = redisUtils;
|
||||||
this.deviceService = deviceService;
|
this.deviceService = deviceService;
|
||||||
|
this.sysParamsService = sysParamsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -47,33 +55,46 @@ public class DeviceAddressBookServiceImpl implements DeviceAddressBookService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> lookupByNickname(String callerMac, String nickname) {
|
public Map<String, Object> callByNickname(String callerMac, String nickname, boolean isAnswer) {
|
||||||
Map<String, Map<String, String>> allBooks = getAllAddressBooks();
|
Map<String, Map<String, String>> allBooks = getAllAddressBooks();
|
||||||
|
|
||||||
|
if (isAnswer) {
|
||||||
|
return postToMqtt("/api/call/accept", Map.of("mac", callerMac), "接听");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主动呼叫模式
|
||||||
Map<String, String> callerBook = allBooks.get(callerMac.toLowerCase());
|
Map<String, String> callerBook = allBooks.get(callerMac.toLowerCase());
|
||||||
if (callerBook == null) {
|
if (callerBook == null) {
|
||||||
return null;
|
return errorResult("未找到备注为'" + nickname + "'的设备");
|
||||||
}
|
}
|
||||||
// 从缓存获取 targetMac,格式: "mac|permission"
|
|
||||||
String targetMacWithPerm = callerBook.get(nickname);
|
String targetMacWithPerm = callerBook.get(nickname);
|
||||||
if (targetMacWithPerm == null) {
|
if (targetMacWithPerm == null) {
|
||||||
return null;
|
return errorResult("未找到备注为'" + nickname + "'的设备");
|
||||||
}
|
}
|
||||||
// 解析 targetMac 和 permission
|
|
||||||
String[] parts = targetMacWithPerm.split("\\|");
|
String[] parts = targetMacWithPerm.split("\\|");
|
||||||
String targetMac = parts[0];
|
String targetMac = parts[0];
|
||||||
boolean hasPermission = parts.length > 1 && "1".equals(parts[1]);
|
boolean hasPermission = parts.length > 1 && "1".equals(parts[1]);
|
||||||
|
|
||||||
Map<String, String> targetBook = allBooks.get(targetMac.toLowerCase());
|
if (!hasPermission) {
|
||||||
if (targetBook == null) {
|
return errorResult("呼叫失败,您没有权限呼叫该设备");
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
// 检查双向关系:目标设备是否也添加了主叫方为联系人
|
|
||||||
String callerNickname = targetBook.get(callerMac.toLowerCase());
|
// 获取目标设备如何称呼主叫方
|
||||||
Map<String, String> result = new HashMap<>();
|
Map<String, String> targetBook = allBooks.get(targetMac.toLowerCase());
|
||||||
result.put("targetMac", targetMac);
|
String callerNickname = null;
|
||||||
result.put("callerNickname", callerNickname);
|
if (targetBook != null) {
|
||||||
result.put("hasPermission", hasPermission ? "true" : "false");
|
callerNickname = targetBook.get(callerMac.toLowerCase());
|
||||||
return result;
|
}
|
||||||
|
if (StringUtils.isBlank(callerNickname)) {
|
||||||
|
callerNickname = deviceService.getDeviceByMacAddress(callerMac).getAlias();
|
||||||
|
if (StringUtils.isBlank(callerNickname)) {
|
||||||
|
callerNickname = formatMacAsDeviceName(callerMac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return postToMqtt("/api/call/request",
|
||||||
|
Map.of("caller_mac", callerMac, "target_mac", targetMac, "caller_nickname", callerNickname),
|
||||||
|
"呼叫");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -81,43 +102,44 @@ public class DeviceAddressBookServiceImpl implements DeviceAddressBookService {
|
|||||||
Map<String, Map<String, String>> result = new HashMap<>();
|
Map<String, Map<String, String>> result = new HashMap<>();
|
||||||
List<DeviceAddressBookEntity> allRecords = deviceAddressBookDao.selectList(null);
|
List<DeviceAddressBookEntity> allRecords = deviceAddressBookDao.selectList(null);
|
||||||
Map<String, String> reverseMap = new HashMap<>();
|
Map<String, String> reverseMap = new HashMap<>();
|
||||||
Map<String, Boolean> permissionMap = new HashMap<>();
|
|
||||||
for (DeviceAddressBookEntity entity : allRecords) {
|
for (DeviceAddressBookEntity entity : allRecords) {
|
||||||
String macA = entity.getMacAddress().toLowerCase();
|
String macA = entity.getMacAddress().toLowerCase();
|
||||||
String macB = entity.getTargetMac().toLowerCase();
|
String macB = entity.getTargetMac().toLowerCase();
|
||||||
String alias = entity.getAlias();
|
String alias = entity.getAlias();
|
||||||
Boolean hasPermission = entity.getHasPermission();
|
Boolean hasPermission = entity.getHasPermission();
|
||||||
|
|
||||||
|
// 构建正向映射: A对B的映射 nickname -> macB|permission
|
||||||
|
if (alias != null && !alias.isEmpty()) {
|
||||||
|
result.computeIfAbsent(macA, k -> new HashMap<>());
|
||||||
|
String permStr = (hasPermission != null && hasPermission) ? "1" : "0";
|
||||||
|
result.get(macA).put(alias, macB + "|" + permStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建反向记录用于查找B对A的称呼
|
||||||
if (alias != null && !alias.isEmpty()) {
|
if (alias != null && !alias.isEmpty()) {
|
||||||
// 反向记录: (macB, macA) = B对A的称呼
|
|
||||||
reverseMap.put(macB + ":" + macA, alias);
|
reverseMap.put(macB + ":" + macA, alias);
|
||||||
}
|
}
|
||||||
// 记录 A 对 B 是否有权限呼叫
|
|
||||||
permissionMap.put(macA + ":" + macB, hasPermission != null && hasPermission);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建反向映射: B对A的映射 macA -> nickname
|
||||||
for (DeviceAddressBookEntity entity : allRecords) {
|
for (DeviceAddressBookEntity entity : allRecords) {
|
||||||
String macA = entity.getMacAddress().toLowerCase();
|
String macA = entity.getMacAddress().toLowerCase();
|
||||||
String macB = entity.getTargetMac().toLowerCase();
|
String macB = entity.getTargetMac().toLowerCase();
|
||||||
String aliasAtoB = entity.getAlias();
|
|
||||||
result.computeIfAbsent(macA, k -> new HashMap<>());
|
|
||||||
result.computeIfAbsent(macB, k -> new HashMap<>());
|
|
||||||
if (aliasAtoB != null && !aliasAtoB.isEmpty()) {
|
|
||||||
// A对B的映射: nickname -> macB|permission
|
|
||||||
Boolean perm = entity.getHasPermission();
|
|
||||||
String permStr = (perm != null && perm) ? "1" : "0";
|
|
||||||
result.get(macA).put(aliasAtoB, macB + "|" + permStr);
|
|
||||||
}
|
|
||||||
// B对A的映射: macA -> nickname(查反向记录)
|
|
||||||
String aliasBtoA = reverseMap.get(macA + ":" + macB);
|
String aliasBtoA = reverseMap.get(macA + ":" + macB);
|
||||||
if (aliasBtoA != null && !aliasBtoA.isEmpty()) {
|
if (aliasBtoA != null && !aliasBtoA.isEmpty()) {
|
||||||
|
result.computeIfAbsent(macB, k -> new HashMap<>());
|
||||||
result.get(macB).put(macA, aliasBtoA);
|
result.get(macB).put(macA, aliasBtoA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redisUtils.set(RedisKeys.getAddressBookKey(), result);
|
redisUtils.set(RedisKeys.getAddressBookKey(), result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAlias(String macAddress, String targetMac, String alias) {
|
public void updateAlias(String macAddress, String targetMac, String alias) {
|
||||||
deviceAddressBookDao.updateAlias(macAddress, targetMac, alias);
|
String finalAlias = generateUniqueAlias(macAddress, targetMac, alias);
|
||||||
|
deviceAddressBookDao.updateAlias(macAddress, targetMac, finalAlias);
|
||||||
refreshCache();
|
refreshCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,20 +154,23 @@ public class DeviceAddressBookServiceImpl implements DeviceAddressBookService {
|
|||||||
QueryWrapper<DeviceAddressBookEntity> wrapper = new QueryWrapper<>();
|
QueryWrapper<DeviceAddressBookEntity> wrapper = new QueryWrapper<>();
|
||||||
wrapper.eq("mac_address", macAddress).eq("target_mac", targetMac);
|
wrapper.eq("mac_address", macAddress).eq("target_mac", targetMac);
|
||||||
DeviceAddressBookEntity record = deviceAddressBookDao.selectOne(wrapper);
|
DeviceAddressBookEntity record = deviceAddressBookDao.selectOne(wrapper);
|
||||||
|
System.out.println("saveOrUpdate - mac=" + macAddress + ", targetMac=" + targetMac + ", alias=" + alias + ", record=" + (record == null ? "null" : "not null"));
|
||||||
if (record == null) {
|
if (record == null) {
|
||||||
DeviceAddressBookEntity entity = new DeviceAddressBookEntity();
|
DeviceAddressBookEntity entity = new DeviceAddressBookEntity();
|
||||||
entity.setMacAddress(macAddress);
|
entity.setMacAddress(macAddress);
|
||||||
entity.setTargetMac(targetMac);
|
entity.setTargetMac(targetMac);
|
||||||
// 如果 alias 为空,就用默认使用设备名称
|
// 如果 alias 为空,就默认使用设备名称
|
||||||
if (StringUtils.isBlank(alias)) {
|
if (StringUtils.isBlank(alias)) {
|
||||||
alias = deviceService.getDeviceByMacAddress(targetMac).getAlias();
|
alias = deviceService.getDeviceByMacAddress(targetMac).getAlias();
|
||||||
}
|
}
|
||||||
|
// 检查重名
|
||||||
|
alias = generateUniqueAlias(macAddress, targetMac, alias);
|
||||||
entity.setAlias(alias);
|
entity.setAlias(alias);
|
||||||
entity.setHasPermission(hasPermission);
|
entity.setHasPermission(hasPermission);
|
||||||
deviceAddressBookDao.insert(entity);
|
deviceAddressBookDao.insertAddressBook(entity);
|
||||||
} else {
|
} else {
|
||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
deviceAddressBookDao.updateAlias(macAddress, targetMac, alias);
|
updateAlias(macAddress, targetMac, alias);
|
||||||
}
|
}
|
||||||
if (hasPermission != null) {
|
if (hasPermission != null) {
|
||||||
deviceAddressBookDao.updatePermission(macAddress, targetMac, hasPermission);
|
deviceAddressBookDao.updatePermission(macAddress, targetMac, hasPermission);
|
||||||
@@ -153,4 +178,81 @@ public class DeviceAddressBookServiceImpl implements DeviceAddressBookService {
|
|||||||
}
|
}
|
||||||
refreshCache();
|
refreshCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteByMacAddresses(List<String> macAddresses) {
|
||||||
|
if (macAddresses == null || macAddresses.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deviceAddressBookDao.deleteByMacAddresses(macAddresses);
|
||||||
|
refreshCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> errorResult(String message) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("status", "error");
|
||||||
|
result.put("message", message);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> postToMqtt(String path, Map<String, Object> body, String action) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("status", "error");
|
||||||
|
|
||||||
|
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
|
||||||
|
String mqttSignatureKey = sysParamsService.getValue(Constant.SERVER_MQTT_SECRET, true);
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)
|
||||||
|
|| MqttGatewayAuthorization.isMissingSignatureKey(mqttSignatureKey)) {
|
||||||
|
result.put("message", action + "失败,网关配置缺失");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String url = "http://" + mqttGatewayUrl + path;
|
||||||
|
String response = MqttGatewayAuthorization.postJson(
|
||||||
|
url,
|
||||||
|
JSONUtil.toJsonStr(body),
|
||||||
|
mqttSignatureKey,
|
||||||
|
Instant.now(),
|
||||||
|
5000);
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(response)) {
|
||||||
|
Map<String, Object> gwResult = JSONUtil.parseObj(response);
|
||||||
|
result.put("status", gwResult.get("status"));
|
||||||
|
result.put("message", gwResult.get("message"));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.put("message", action + "失败,请稍后再试");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatMacAsDeviceName(String mac) {
|
||||||
|
if (StringUtils.isBlank(mac) || mac.length() < 2) {
|
||||||
|
return mac;
|
||||||
|
}
|
||||||
|
String lastTwo = mac.substring(mac.length() - 2);
|
||||||
|
return "尾号为" + lastTwo + "的设备";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateUniqueAlias(String macAddress, String targetMac, String alias) {
|
||||||
|
QueryWrapper<DeviceAddressBookEntity> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.eq("mac_address", macAddress);
|
||||||
|
List<DeviceAddressBookEntity> existing = deviceAddressBookDao.selectList(wrapper);
|
||||||
|
List<String> existNames = existing.stream()
|
||||||
|
.map(DeviceAddressBookEntity::getAlias)
|
||||||
|
.filter(a -> a != null && !a.isEmpty())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!existNames.contains(alias)) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
int suffix = 1;
|
||||||
|
String newAlias;
|
||||||
|
while (existNames.contains(newAlias = alias + suffix)) {
|
||||||
|
suffix++;
|
||||||
|
}
|
||||||
|
return newAlias;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+49
-125
@@ -6,6 +6,7 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -30,15 +31,9 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
|
||||||
import cn.hutool.core.date.DatePattern;
|
|
||||||
import cn.hutool.core.date.DateUtil;
|
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.crypto.digest.DigestUtil;
|
|
||||||
import cn.hutool.http.ContentType;
|
|
||||||
import cn.hutool.http.Header;
|
|
||||||
import cn.hutool.http.HttpRequest;
|
|
||||||
import cn.hutool.json.JSONArray;
|
import cn.hutool.json.JSONArray;
|
||||||
import cn.hutool.json.JSONObject;
|
import cn.hutool.json.JSONObject;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
@@ -55,6 +50,7 @@ import xiaozhi.common.service.impl.BaseServiceImpl;
|
|||||||
import xiaozhi.common.user.UserDetail;
|
import xiaozhi.common.user.UserDetail;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.common.utils.DateUtils;
|
import xiaozhi.common.utils.DateUtils;
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
import xiaozhi.common.utils.ToolUtil;
|
import xiaozhi.common.utils.ToolUtil;
|
||||||
import xiaozhi.modules.device.dao.DeviceDao;
|
import xiaozhi.modules.device.dao.DeviceDao;
|
||||||
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
||||||
@@ -63,6 +59,7 @@ import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
|||||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
import xiaozhi.modules.device.entity.OtaEntity;
|
import xiaozhi.modules.device.entity.OtaEntity;
|
||||||
|
import xiaozhi.modules.device.service.DeviceAddressBookService;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
import xiaozhi.modules.device.service.OtaService;
|
import xiaozhi.modules.device.service.OtaService;
|
||||||
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||||
@@ -80,6 +77,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
private final SysParamsService sysParamsService;
|
private final SysParamsService sysParamsService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final OtaService otaService;
|
private final OtaService otaService;
|
||||||
|
private final DeviceAddressBookService deviceAddressBookService;
|
||||||
|
|
||||||
@Async
|
@Async
|
||||||
public void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion) {
|
public void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion) {
|
||||||
@@ -112,7 +110,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
String deviceId = (String) cacheDeviceId;
|
String deviceId = (String) cacheDeviceId;
|
||||||
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
||||||
String cacheDeviceKey = RedisKeys.getOtaDeviceActivationInfo(safeDeviceId);
|
String cacheDeviceKey = RedisKeys.getOtaDeviceActivationInfo(safeDeviceId);
|
||||||
Map<String, Object> cacheMap = (Map<String, Object>) redisUtils.get(cacheDeviceKey);
|
Map<String, Object> cacheMap = JsonUtils.toStringObjectMap(redisUtils.get(cacheDeviceKey));
|
||||||
if (ToolUtil.isEmpty(cacheMap)) {
|
if (ToolUtil.isEmpty(cacheMap)) {
|
||||||
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
||||||
}
|
}
|
||||||
@@ -184,14 +182,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
.put("clientIds", deviceIds).build();
|
.put("clientIds", deviceIds).build();
|
||||||
|
|
||||||
if (ToolUtil.isNotEmpty(deviceIds)) {
|
if (ToolUtil.isNotEmpty(deviceIds)) {
|
||||||
// 发送请求
|
return postToMqttGateway(url, params);
|
||||||
String resultMessage = HttpRequest.post(url)
|
|
||||||
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
|
|
||||||
.header(Header.AUTHORIZATION, "Bearer " + generateBearerToken())
|
|
||||||
.body(JSONUtil.toJsonStr(params))
|
|
||||||
.timeout(10000) // 超时,毫秒
|
|
||||||
.execute().body();
|
|
||||||
return resultMessage;
|
|
||||||
}
|
}
|
||||||
// 返回响应
|
// 返回响应
|
||||||
return "";
|
return "";
|
||||||
@@ -211,8 +202,8 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
firmware.setUrl(Constant.INVALID_FIRMWARE_URL);
|
firmware.setUrl(Constant.INVALID_FIRMWARE_URL);
|
||||||
response.setFirmware(firmware);
|
response.setFirmware(firmware);
|
||||||
} else {
|
} else {
|
||||||
// 只有在设备已绑定且autoUpdate不为0的情况下才返回固件升级信息
|
// 只有在设备已绑定且明确开启自动升级时才返回固件升级信息
|
||||||
if (deviceById.getAutoUpdate() != 0) {
|
if (Integer.valueOf(1).equals(deviceById.getAutoUpdate())) {
|
||||||
String type = deviceReport.getBoard() == null ? null : deviceReport.getBoard().getType();
|
String type = deviceReport.getBoard() == null ? null : deviceReport.getBoard().getType();
|
||||||
DeviceReportRespDTO.Firmware firmware = buildFirmwareInfo(type,
|
DeviceReportRespDTO.Firmware firmware = buildFirmwareInfo(type,
|
||||||
deviceReport.getApplication() == null ? null : deviceReport.getApplication().getVersion());
|
deviceReport.getApplication() == null ? null : deviceReport.getApplication().getVersion());
|
||||||
@@ -301,24 +292,31 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
@Override
|
@Override
|
||||||
public List<UserShowDeviceListVO> getUserDeviceList(Long userId, String agentId) {
|
public List<UserShowDeviceListVO> getUserDeviceList(Long userId, String agentId) {
|
||||||
List<DeviceEntity> devices = getUserDevices(userId, agentId);
|
List<DeviceEntity> devices = getUserDevices(userId, agentId);
|
||||||
return devices.stream().map(device -> {
|
return devices.stream().map(this::toUserShowDeviceListVO).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserShowDeviceListVO toUserShowDeviceListVO(DeviceEntity device) {
|
||||||
UserShowDeviceListVO vo = ConvertUtils.sourceToTarget(device, UserShowDeviceListVO.class);
|
UserShowDeviceListVO vo = ConvertUtils.sourceToTarget(device, UserShowDeviceListVO.class);
|
||||||
vo.setDeviceType(device.getBoard());
|
vo.setDeviceType(device.getBoard());
|
||||||
// 设置UTC时间戳供前端使用时区转换
|
vo.setBoard(device.getBoard());
|
||||||
if (device.getLastConnectedAt() != null) {
|
vo.setAutoUpdate(device.getAutoUpdate());
|
||||||
vo.setLastConnectedAtTimestamp(device.getLastConnectedAt().getTime());
|
vo.setCreateDateTimestamp(toTimestamp(device.getCreateDate()));
|
||||||
}
|
vo.setLastConnectedAtTimestamp(toTimestamp(device.getLastConnectedAt()));
|
||||||
return vo;
|
return vo;
|
||||||
}).toList();
|
}
|
||||||
|
|
||||||
|
private Long toTimestamp(Date date) {
|
||||||
|
return date == null ? null : date.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unbindDevice(Long userId, String deviceId) {
|
public void unbindDevice(Long userId, String deviceId) {
|
||||||
// 先查询设备信息,获取agentId
|
// 先查询设备信息,获取agentId和macAddress
|
||||||
DeviceEntity device = baseDao.selectById(deviceId);
|
DeviceEntity device = baseDao.selectById(deviceId);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
String macAddress = device.getMacAddress();
|
||||||
if (StringUtils.isNotBlank(device.getAgentId())) {
|
if (StringUtils.isNotBlank(device.getAgentId())) {
|
||||||
// 清除智能体设备数量缓存
|
// 清除智能体设备数量缓存
|
||||||
redisUtils.delete(RedisKeys.getAgentDeviceCountById(device.getAgentId()));
|
redisUtils.delete(RedisKeys.getAgentDeviceCountById(device.getAgentId()));
|
||||||
@@ -328,6 +326,9 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
wrapper.eq("user_id", userId);
|
wrapper.eq("user_id", userId);
|
||||||
wrapper.eq("id", deviceId);
|
wrapper.eq("id", deviceId);
|
||||||
baseDao.delete(wrapper);
|
baseDao.delete(wrapper);
|
||||||
|
|
||||||
|
// 删除设备相关的通讯录权限记录
|
||||||
|
deviceAddressBookService.deleteByMacAddresses(Collections.singletonList(macAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -346,9 +347,23 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteByAgentId(String agentId) {
|
public void deleteByAgentId(String agentId) {
|
||||||
|
// 先查询该智能体下的所有设备,获取mac地址用于删除通讯录记录
|
||||||
|
QueryWrapper<DeviceEntity> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("agent_id", agentId);
|
||||||
|
List<DeviceEntity> devices = baseDao.selectList(queryWrapper);
|
||||||
|
|
||||||
|
// 删除设备
|
||||||
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
|
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
|
||||||
wrapper.eq("agent_id", agentId);
|
wrapper.eq("agent_id", agentId);
|
||||||
baseDao.delete(wrapper);
|
baseDao.delete(wrapper);
|
||||||
|
|
||||||
|
// 批量删除这些设备相关的所有通讯录权限记录
|
||||||
|
if (!devices.isEmpty()) {
|
||||||
|
List<String> macAddresses = devices.stream()
|
||||||
|
.map(DeviceEntity::getMacAddress)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
deviceAddressBookService.deleteByMacAddresses(macAddresses);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -364,17 +379,11 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
.like(StringUtils.isNotBlank(dto.getKeywords()), "alias", dto.getKeywords()));
|
.like(StringUtils.isNotBlank(dto.getKeywords()), "alias", dto.getKeywords()));
|
||||||
// 循环处理page获取回来的数据,返回需要的字段
|
// 循环处理page获取回来的数据,返回需要的字段
|
||||||
List<UserShowDeviceListVO> list = page.getRecords().stream().map(device -> {
|
List<UserShowDeviceListVO> list = page.getRecords().stream().map(device -> {
|
||||||
UserShowDeviceListVO vo = ConvertUtils.sourceToTarget(device, UserShowDeviceListVO.class);
|
UserShowDeviceListVO vo = toUserShowDeviceListVO(device);
|
||||||
// 把最后修改的时间,改为简短描述的时间
|
// 把最后修改的时间,改为简短描述的时间
|
||||||
vo.setRecentChatTime(DateUtils.getShortTime(device.getUpdateDate()));
|
vo.setRecentChatTime(DateUtils.getShortTime(device.getUpdateDate()));
|
||||||
sysUserUtilService.assignUsername(device.getUserId(),
|
sysUserUtilService.assignUsername(device.getUserId(),
|
||||||
vo::setBindUserName);
|
vo::setBindUserName);
|
||||||
vo.setDeviceType(device.getBoard());
|
|
||||||
vo.setBoard(device.getBoard());
|
|
||||||
// 设置UTC时间戳供前端使用时区转换
|
|
||||||
if (device.getLastConnectedAt() != null) {
|
|
||||||
vo.setLastConnectedAtTimestamp(device.getLastConnectedAt().getTime());
|
|
||||||
}
|
|
||||||
return vo;
|
return vo;
|
||||||
}).toList();
|
}).toList();
|
||||||
// 计算页数
|
// 计算页数
|
||||||
@@ -404,7 +413,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
public String geCodeByDeviceId(String deviceId) {
|
public String geCodeByDeviceId(String deviceId) {
|
||||||
String dataKey = getDeviceCacheKey(deviceId);
|
String dataKey = getDeviceCacheKey(deviceId);
|
||||||
|
|
||||||
Map<String, Object> cacheMap = (Map<String, Object>) redisUtils.get(dataKey);
|
Map<String, Object> cacheMap = JsonUtils.toStringObjectMap(redisUtils.get(dataKey));
|
||||||
if (cacheMap != null && cacheMap.containsKey("activation_code")) {
|
if (cacheMap != null && cacheMap.containsKey("activation_code")) {
|
||||||
String cachedCode = (String) cacheMap.get("activation_code");
|
String cachedCode = (String) cacheMap.get("activation_code");
|
||||||
return cachedCode;
|
return cachedCode;
|
||||||
@@ -678,20 +687,13 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
return mqtt;
|
return mqtt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private String postToMqttGateway(String url, Object requestBody) {
|
||||||
* 生成BearerToken
|
|
||||||
*/
|
|
||||||
private String generateBearerToken() {
|
|
||||||
try {
|
|
||||||
String dateStr = DateUtil.format(new Date(), DatePattern.NORM_DATE_PATTERN);
|
|
||||||
String signatureKey = sysParamsService.getValue(Constant.SERVER_MQTT_SECRET, false);
|
String signatureKey = sysParamsService.getValue(Constant.SERVER_MQTT_SECRET, false);
|
||||||
if (ToolUtil.isEmpty(signatureKey)) {
|
return MqttGatewayAuthorization.postJson(
|
||||||
return null;
|
url,
|
||||||
}
|
JSONUtil.toJsonStr(requestBody),
|
||||||
return DigestUtil.sha256Hex(dateStr + signatureKey);
|
signatureKey,
|
||||||
} catch (Exception e) {
|
Instant.now());
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -752,13 +754,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
.put("payload", payload)
|
.put("payload", payload)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 发送请求
|
String resultMessage = postToMqttGateway(url, requestBody);
|
||||||
String resultMessage = HttpRequest.post(url)
|
|
||||||
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
|
|
||||||
.header(Header.AUTHORIZATION, "Bearer " + generateBearerToken())
|
|
||||||
.body(JSONUtil.toJsonStr(requestBody))
|
|
||||||
.timeout(10000) // 超时,毫秒
|
|
||||||
.execute().body();
|
|
||||||
|
|
||||||
// 解析响应
|
// 解析响应
|
||||||
if (StringUtils.isBlank(resultMessage)) {
|
if (StringUtils.isBlank(resultMessage)) {
|
||||||
@@ -849,13 +845,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
.put("payload", payload)
|
.put("payload", payload)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 发送请求
|
String resultMessage = postToMqttGateway(url, requestBody);
|
||||||
String resultMessage = HttpRequest.post(url)
|
|
||||||
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
|
|
||||||
.header(Header.AUTHORIZATION, "Bearer " + generateBearerToken())
|
|
||||||
.body(JSONUtil.toJsonStr(requestBody))
|
|
||||||
.timeout(10000) // 超时,毫秒
|
|
||||||
.execute().body();
|
|
||||||
|
|
||||||
// 解析响应
|
// 解析响应
|
||||||
if (StringUtils.isNotBlank(resultMessage)) {
|
if (StringUtils.isNotBlank(resultMessage)) {
|
||||||
@@ -891,70 +881,4 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> forwardCallRequest(String callerMac, String targetMac, String callerNickname) {
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
result.put("status", "error");
|
|
||||||
result.put("message", "网关配置缺失");
|
|
||||||
|
|
||||||
// 从系统参数获取网关配置
|
|
||||||
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
|
|
||||||
String mqttSignatureKey = sysParamsService.getValue("server.mqtt_signature_key", true);
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
|
|
||||||
log.error("MQTT网关地址未配置");
|
|
||||||
result.put("message", "MQTT网关地址未配置");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (StringUtils.isBlank(mqttSignatureKey) || "null".equals(mqttSignatureKey)) {
|
|
||||||
log.error("MQTT签名密钥未配置");
|
|
||||||
result.put("message", "MQTT签名密钥未配置");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成token: SHA256(date + secret)
|
|
||||||
String dateStr = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
|
||||||
try {
|
|
||||||
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
|
|
||||||
byte[] hash = md.digest((dateStr + mqttSignatureKey).getBytes(StandardCharsets.UTF_8));
|
|
||||||
StringBuilder hexString = new StringBuilder();
|
|
||||||
for (byte b : hash) {
|
|
||||||
String hex = Integer.toHexString(0xff & b);
|
|
||||||
if (hex.length() == 1) {
|
|
||||||
hexString.append('0');
|
|
||||||
}
|
|
||||||
hexString.append(hex);
|
|
||||||
}
|
|
||||||
String token = hexString.toString();
|
|
||||||
|
|
||||||
// 构建请求体
|
|
||||||
Map<String, Object> body = new HashMap<>();
|
|
||||||
body.put("caller_mac", callerMac);
|
|
||||||
body.put("target_mac", targetMac);
|
|
||||||
body.put("caller_nickname", callerNickname);
|
|
||||||
|
|
||||||
// 发送请求并获取响应
|
|
||||||
String url = "http://" + mqttGatewayUrl + "/api/call/request";
|
|
||||||
String response = cn.hutool.http.HttpRequest.post(url)
|
|
||||||
.header("Authorization", "Bearer " + token)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.body(JSONUtil.toJsonStr(body))
|
|
||||||
.timeout(5000)
|
|
||||||
.execute()
|
|
||||||
.body();
|
|
||||||
|
|
||||||
// 解析网关响应
|
|
||||||
if (StringUtils.isNotBlank(response)) {
|
|
||||||
Map<String, Object> gwResult = JSONUtil.parseObj(response);
|
|
||||||
result.put("status", gwResult.get("status"));
|
|
||||||
result.put("message", gwResult.get("message"));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("转发呼叫请求失败: {}", e.getMessage());
|
|
||||||
result.put("message", "呼叫请求转发失败: " + e.getMessage());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
package xiaozhi.modules.device.service.impl;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import cn.hutool.crypto.digest.DigestUtil;
|
||||||
|
import cn.hutool.http.ContentType;
|
||||||
|
import cn.hutool.http.Header;
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
|
||||||
|
final class MqttGatewayAuthorization {
|
||||||
|
|
||||||
|
private static final int HTTP_UNAUTHORIZED = 401;
|
||||||
|
private static final int DEFAULT_TIMEOUT_MILLIS = 10000;
|
||||||
|
|
||||||
|
private MqttGatewayAuthorization() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static String postJson(String url, String jsonBody, String signatureKey, Instant now) {
|
||||||
|
return postJson(url, jsonBody, signatureKey, now, DEFAULT_TIMEOUT_MILLIS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String postJson(String url, String jsonBody, String signatureKey, Instant now, int timeoutMillis) {
|
||||||
|
GatewayResponse response = executeWithDateFallback(
|
||||||
|
signatureKey,
|
||||||
|
now,
|
||||||
|
token -> executeRequest(url, jsonBody, token, timeoutMillis));
|
||||||
|
|
||||||
|
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
||||||
|
throw new GatewayRequestException(
|
||||||
|
"MQTT Gateway request failed with HTTP status " + response.statusCode(),
|
||||||
|
response.statusCode());
|
||||||
|
}
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> generateDailyTokens(String signatureKey, Instant now) {
|
||||||
|
if (isMissingSignatureKey(signatureKey)) {
|
||||||
|
throw new GatewayRequestException("MQTT Gateway signature key is empty", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDate utcDate = now.atZone(ZoneOffset.UTC).toLocalDate();
|
||||||
|
return List.of(
|
||||||
|
tokenFor(utcDate, signatureKey),
|
||||||
|
tokenFor(utcDate.minusDays(1), signatureKey),
|
||||||
|
tokenFor(utcDate.plusDays(1), signatureKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
static GatewayResponse executeWithDateFallback(String signatureKey, Instant now,
|
||||||
|
Function<String, GatewayResponse> requestExecutor) {
|
||||||
|
GatewayResponse lastAuthenticationFailure = null;
|
||||||
|
for (String token : generateDailyTokens(signatureKey, now)) {
|
||||||
|
GatewayResponse response = requestExecutor.apply(token);
|
||||||
|
if (!isAuthenticationFailure(response.statusCode())) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
lastAuthenticationFailure = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer statusCode = lastAuthenticationFailure == null ? null : lastAuthenticationFailure.statusCode();
|
||||||
|
throw new GatewayRequestException(
|
||||||
|
"MQTT Gateway rejected all daily authorization tokens"
|
||||||
|
+ (statusCode == null ? "" : " (HTTP " + statusCode + ")"),
|
||||||
|
statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String tokenFor(LocalDate date, String signatureKey) {
|
||||||
|
return DigestUtil.sha256Hex(date + signatureKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAuthenticationFailure(int statusCode) {
|
||||||
|
return statusCode == HTTP_UNAUTHORIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isMissingSignatureKey(String signatureKey) {
|
||||||
|
return StringUtils.isBlank(signatureKey) || "null".equalsIgnoreCase(signatureKey.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GatewayResponse executeRequest(String url, String jsonBody, String token, int timeoutMillis) {
|
||||||
|
try (HttpResponse response = HttpRequest.post(url)
|
||||||
|
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
|
||||||
|
.header(Header.AUTHORIZATION, "Bearer " + token)
|
||||||
|
.body(jsonBody)
|
||||||
|
.timeout(timeoutMillis)
|
||||||
|
.execute()) {
|
||||||
|
return new GatewayResponse(response.getStatus(), response.body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record GatewayResponse(int statusCode, String body) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class GatewayRequestException extends RuntimeException {
|
||||||
|
private final Integer statusCode;
|
||||||
|
|
||||||
|
GatewayRequestException(String message, Integer statusCode) {
|
||||||
|
super(message);
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer statusCode() {
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -56,7 +56,7 @@ public class OtaServiceImpl extends BaseServiceImpl<OtaDao, OtaEntity> implement
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(String[] ids) {
|
public void delete(String[] ids) {
|
||||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
baseDao.deleteByIds(Arrays.asList(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -32,16 +32,19 @@ public class UserShowDeviceListVO {
|
|||||||
@Schema(description = "设备别名")
|
@Schema(description = "设备别名")
|
||||||
private String alias;
|
private String alias;
|
||||||
|
|
||||||
@Schema(description = "开启OTA")
|
@Schema(description = "自动更新开关(0关闭/1开启)")
|
||||||
private Integer otaUpgrade;
|
private Integer autoUpdate;
|
||||||
|
|
||||||
@Schema(description = "最近对话时间")
|
@Schema(description = "最近对话时间")
|
||||||
private String recentChatTime;
|
private String recentChatTime;
|
||||||
|
|
||||||
@Schema(description = "最后连接时间(UTC毫秒)")
|
@Schema(description = "最后连接时间戳(毫秒)", type = "string", example = "1783689702000")
|
||||||
private Long lastConnectedAtTimestamp;
|
private Long lastConnectedAtTimestamp;
|
||||||
|
|
||||||
@Schema(description = "绑定时间")
|
@Schema(description = "绑定时间戳(毫秒)", type = "string", example = "1783689702000")
|
||||||
|
private Long createDateTimestamp;
|
||||||
|
|
||||||
|
@Schema(description = "绑定时间(兼容字段,请使用 createDateTimestamp)", deprecated = true)
|
||||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
|
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
|
||||||
private Date createDate;
|
private Date createDate;
|
||||||
|
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package xiaozhi.modules.knowledge.dto;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文档 DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Schema(description = "知识库文档")
|
|
||||||
public class DocumentDTO implements Serializable {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
@Schema(description = "本地ID")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "知识库ID")
|
|
||||||
private String datasetId;
|
|
||||||
|
|
||||||
@Schema(description = "RAGFlow文档ID")
|
|
||||||
private String documentId;
|
|
||||||
|
|
||||||
@Schema(description = "文档名称")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Schema(description = "文件大小")
|
|
||||||
private Long size;
|
|
||||||
|
|
||||||
@Schema(description = "文件类型")
|
|
||||||
private String type;
|
|
||||||
|
|
||||||
@Schema(description = "分块方法")
|
|
||||||
private String chunkMethod;
|
|
||||||
|
|
||||||
@Schema(description = "解析配置")
|
|
||||||
private Map<String, Object> parserConfig;
|
|
||||||
|
|
||||||
@Schema(description = "处理状态 (1:解析中 3:成功 4:失败)")
|
|
||||||
private Integer status;
|
|
||||||
|
|
||||||
@Schema(description = "错误信息")
|
|
||||||
private String error;
|
|
||||||
|
|
||||||
@Schema(description = "分块数量")
|
|
||||||
private Integer chunkCount;
|
|
||||||
|
|
||||||
@Schema(description = "Token数量")
|
|
||||||
private Long tokenCount;
|
|
||||||
|
|
||||||
@Schema(description = "是否启用")
|
|
||||||
private Integer enabled;
|
|
||||||
|
|
||||||
@Schema(description = "创建时间")
|
|
||||||
private Date createdAt;
|
|
||||||
|
|
||||||
@Schema(description = "更新时间")
|
|
||||||
private Date updatedAt;
|
|
||||||
|
|
||||||
@Schema(description = "上传进度 (虚拟字段)")
|
|
||||||
private Double progress;
|
|
||||||
|
|
||||||
@Schema(description = "缩略图/预览图 (虚拟字段)")
|
|
||||||
private String thumbnail;
|
|
||||||
}
|
|
||||||
@@ -67,4 +67,7 @@ public class KnowledgeBaseDTO implements Serializable {
|
|||||||
|
|
||||||
@Schema(description = "文档数量")
|
@Schema(description = "文档数量")
|
||||||
private Integer documentCount;
|
private Integer documentCount;
|
||||||
|
|
||||||
|
@Schema(description = "异常提示")
|
||||||
|
private String errorMessage;
|
||||||
}
|
}
|
||||||
@@ -1,421 +0,0 @@
|
|||||||
package xiaozhi.modules.knowledge.dto.agent;
|
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.validation.constraints.*;
|
|
||||||
|
|
||||||
@Schema(description = "智能体 (Agent) 管理聚合 DTO")
|
|
||||||
public class AgentDTO {
|
|
||||||
|
|
||||||
// ========== 1. Agent 管理 (CRUD) - 对应 RAGFlow_Agent接口详解 ==========
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Agent 创建请求")
|
|
||||||
public static class CreateReq implements Serializable {
|
|
||||||
@Schema(description = "Agent 标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "My Agent")
|
|
||||||
@NotBlank(message = "Agent 标题不能为空")
|
|
||||||
@JsonProperty("title")
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Schema(description = "DSL 定义 (画布 JSON)", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@NotNull(message = "DSL 定义不能为空")
|
|
||||||
@JsonProperty("dsl")
|
|
||||||
private Map<String, Object> dsl;
|
|
||||||
|
|
||||||
@Schema(description = "描述", example = "这是一个测试 Agent")
|
|
||||||
@JsonProperty("description")
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
@Schema(description = "头像 URL", example = "http://example.com/avatar.png")
|
|
||||||
@JsonProperty("avatar")
|
|
||||||
private String avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Agent 更新请求")
|
|
||||||
public static class UpdateReq implements Serializable {
|
|
||||||
@Schema(description = "Agent 标题", example = "Updated Agent")
|
|
||||||
@JsonProperty("title")
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Schema(description = "DSL 定义 (画布 JSON)")
|
|
||||||
@JsonProperty("dsl")
|
|
||||||
private Map<String, Object> dsl;
|
|
||||||
|
|
||||||
@Schema(description = "描述")
|
|
||||||
@JsonProperty("description")
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
@Schema(description = "头像 URL")
|
|
||||||
@JsonProperty("avatar")
|
|
||||||
private String avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Agent 列表请求")
|
|
||||||
public static class ListReq implements Serializable {
|
|
||||||
@Schema(description = "页码", defaultValue = "1")
|
|
||||||
@JsonProperty("page")
|
|
||||||
@Builder.Default
|
|
||||||
private Integer page = 1;
|
|
||||||
|
|
||||||
@Schema(description = "每页大小", defaultValue = "10")
|
|
||||||
@JsonProperty("page_size")
|
|
||||||
@Builder.Default
|
|
||||||
private Integer pageSize = 10;
|
|
||||||
|
|
||||||
@Schema(description = "排序字段", defaultValue = "update_time")
|
|
||||||
@JsonProperty("orderby")
|
|
||||||
@Builder.Default
|
|
||||||
private String orderby = "update_time";
|
|
||||||
|
|
||||||
@Schema(description = "是否降序", defaultValue = "true")
|
|
||||||
@JsonProperty("desc")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean desc = true;
|
|
||||||
|
|
||||||
@Schema(description = "Agent ID 过滤")
|
|
||||||
@JsonProperty("id")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "标题模糊搜索")
|
|
||||||
@JsonProperty("title")
|
|
||||||
private String title;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Agent 响应对象")
|
|
||||||
public static class AgentVO implements Serializable {
|
|
||||||
@Schema(description = "Agent ID")
|
|
||||||
@JsonProperty("id")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "标题")
|
|
||||||
@JsonProperty("title")
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Schema(description = "描述")
|
|
||||||
@JsonProperty("description")
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
@Schema(description = "头像")
|
|
||||||
@JsonProperty("avatar")
|
|
||||||
private String avatar;
|
|
||||||
|
|
||||||
@Schema(description = "DSL 定义")
|
|
||||||
@JsonProperty("dsl")
|
|
||||||
private Map<String, Object> dsl;
|
|
||||||
|
|
||||||
@Schema(description = "创建者 ID")
|
|
||||||
@JsonProperty("user_id")
|
|
||||||
private String userId;
|
|
||||||
|
|
||||||
@Schema(description = "画布分类")
|
|
||||||
@JsonProperty("canvas_category")
|
|
||||||
private String canvasCategory;
|
|
||||||
|
|
||||||
@Schema(description = "创建时间 (时间戳)")
|
|
||||||
@JsonProperty("create_time")
|
|
||||||
private Long createTime;
|
|
||||||
|
|
||||||
@Schema(description = "更新时间 (时间戳)")
|
|
||||||
@JsonProperty("update_time")
|
|
||||||
private Long updateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 2. Webhook 调试与追踪 - 对应 RAGFlow_Agent接口详解 ==========
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Webhook 触发请求 (参数动态)")
|
|
||||||
public static class WebhookTriggerReq implements Serializable {
|
|
||||||
@Schema(description = "输入变量", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@NotNull(message = "输入变量不能为空")
|
|
||||||
@JsonProperty("inputs")
|
|
||||||
private Map<String, Object> inputs;
|
|
||||||
|
|
||||||
@Schema(description = "查询词", example = "Hello")
|
|
||||||
@JsonProperty("query")
|
|
||||||
private String query;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Webhook 追踪请求")
|
|
||||||
public static class WebhookTraceReq implements Serializable {
|
|
||||||
@Schema(description = "时间戳游标", example = "1700000000.0")
|
|
||||||
@JsonProperty("since_ts")
|
|
||||||
private Double sinceTs;
|
|
||||||
|
|
||||||
@Schema(description = "Webhook ID")
|
|
||||||
@JsonProperty("webhook_id")
|
|
||||||
private String webhookId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Webhook 追踪响应")
|
|
||||||
public static class WebhookTraceVO implements Serializable {
|
|
||||||
@Schema(description = "Webhook ID")
|
|
||||||
@JsonProperty("webhook_id")
|
|
||||||
private String webhookId;
|
|
||||||
|
|
||||||
@Schema(description = "是否结束")
|
|
||||||
@JsonProperty("finished")
|
|
||||||
private Boolean finished;
|
|
||||||
|
|
||||||
@Schema(description = "下一次查询的时间戳游标")
|
|
||||||
@JsonProperty("next_since_ts")
|
|
||||||
private Double nextSinceTs;
|
|
||||||
|
|
||||||
@Schema(description = "事件列表")
|
|
||||||
@JsonProperty("events")
|
|
||||||
private List<TraceEvent> events;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "追踪事件项")
|
|
||||||
public static class TraceEvent implements Serializable {
|
|
||||||
@Schema(description = "时间戳")
|
|
||||||
@JsonProperty("ts")
|
|
||||||
private Double ts;
|
|
||||||
|
|
||||||
@Schema(description = "事件类型")
|
|
||||||
@JsonProperty("event")
|
|
||||||
private String event;
|
|
||||||
|
|
||||||
@Schema(description = "事件数据")
|
|
||||||
@JsonProperty("data")
|
|
||||||
private Object data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 3. Agent 会话 (Session) - 对应 RAGFlow_Agent_Dify接口详解 ==========
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Session 创建请求")
|
|
||||||
public static class SessionCreateReq implements Serializable {
|
|
||||||
@Schema(description = "用户 ID")
|
|
||||||
@JsonProperty("user_id")
|
|
||||||
private String userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Session 列表请求")
|
|
||||||
public static class SessionListReq implements Serializable {
|
|
||||||
@Schema(description = "页码", defaultValue = "1")
|
|
||||||
@JsonProperty("page")
|
|
||||||
@Builder.Default
|
|
||||||
private Integer page = 1;
|
|
||||||
|
|
||||||
@Schema(description = "每页大小", defaultValue = "10")
|
|
||||||
@JsonProperty("page_size")
|
|
||||||
@Builder.Default
|
|
||||||
private Integer pageSize = 10;
|
|
||||||
|
|
||||||
@Schema(description = "排序字段", defaultValue = "create_time")
|
|
||||||
@JsonProperty("orderby")
|
|
||||||
@Builder.Default
|
|
||||||
private String orderby = "create_time";
|
|
||||||
|
|
||||||
@Schema(description = "是否降序", defaultValue = "true")
|
|
||||||
@JsonProperty("desc")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean desc = true;
|
|
||||||
|
|
||||||
@Schema(description = "Session ID")
|
|
||||||
@JsonProperty("id")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "用户 ID")
|
|
||||||
@JsonProperty("user_id")
|
|
||||||
private String userId;
|
|
||||||
|
|
||||||
@Schema(description = "是否返回 DSL")
|
|
||||||
@JsonProperty("dsl")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean dsl = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Session 批量删除请求")
|
|
||||||
public static class SessionBatchDeleteReq implements Serializable {
|
|
||||||
@Schema(description = "会话 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@JsonProperty("ids")
|
|
||||||
@NotEmpty(message = "ID列表不能为空")
|
|
||||||
private List<String> ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Session 响应对象")
|
|
||||||
public static class SessionVO implements Serializable {
|
|
||||||
@Schema(description = "Session ID")
|
|
||||||
@JsonProperty("id")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "Agent ID")
|
|
||||||
@JsonProperty("agent_id")
|
|
||||||
private String agentId;
|
|
||||||
|
|
||||||
@Schema(description = "用户 ID")
|
|
||||||
@JsonProperty("user_id")
|
|
||||||
private String userId;
|
|
||||||
|
|
||||||
@Schema(description = "来源")
|
|
||||||
@JsonProperty("source")
|
|
||||||
private String source;
|
|
||||||
|
|
||||||
@Schema(description = "DSL 定义")
|
|
||||||
@JsonProperty("dsl")
|
|
||||||
private Map<String, Object> dsl;
|
|
||||||
|
|
||||||
@Schema(description = "消息列表")
|
|
||||||
@JsonProperty("messages")
|
|
||||||
private List<Map<String, Object>> messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 4. Agent 对话 (Completion) - 对应 RAGFlow_Agent_Dify接口详解 ==========
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Completion 对话请求")
|
|
||||||
public static class CompletionReq implements Serializable {
|
|
||||||
@Schema(description = "会话 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@NotBlank(message = "会话 ID 不能为空")
|
|
||||||
@JsonProperty("session_id")
|
|
||||||
private String sessionId;
|
|
||||||
|
|
||||||
@Schema(description = "用户问题")
|
|
||||||
@JsonProperty("question")
|
|
||||||
private String question;
|
|
||||||
|
|
||||||
@Schema(description = "是否流式返回", defaultValue = "true")
|
|
||||||
@JsonProperty("stream")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean stream = true;
|
|
||||||
|
|
||||||
@Schema(description = "是否返回追踪信息", defaultValue = "false")
|
|
||||||
@JsonProperty("return_trace")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean returnTrace = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Completion 对话响应")
|
|
||||||
public static class CompletionVO implements Serializable {
|
|
||||||
@Schema(description = "会话 ID")
|
|
||||||
@JsonProperty("id")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "回复内容")
|
|
||||||
@JsonProperty("content")
|
|
||||||
private String content;
|
|
||||||
|
|
||||||
@Schema(description = "引用来源")
|
|
||||||
@JsonProperty("reference")
|
|
||||||
private Map<String, Object> reference;
|
|
||||||
|
|
||||||
@Schema(description = "追踪信息")
|
|
||||||
@JsonProperty("trace")
|
|
||||||
private List<Object> trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 5. Dify 兼容检索 - 对应 RAGFlow_Agent_Dify接口详解 ==========
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Dify 兼容检索请求")
|
|
||||||
public static class DifyRetrievalReq implements Serializable {
|
|
||||||
@Schema(description = "知识库 ID")
|
|
||||||
@JsonProperty("knowledge_id")
|
|
||||||
private String knowledgeId;
|
|
||||||
|
|
||||||
@Schema(description = "查询词")
|
|
||||||
@JsonProperty("query")
|
|
||||||
private String query;
|
|
||||||
|
|
||||||
@Schema(description = "检索设置")
|
|
||||||
@JsonProperty("retrieval_setting")
|
|
||||||
private Map<String, Object> retrievalSetting;
|
|
||||||
|
|
||||||
@Schema(description = "元数据过滤条件")
|
|
||||||
@JsonProperty("metadata_condition")
|
|
||||||
private Map<String, Object> metadataCondition;
|
|
||||||
|
|
||||||
@Schema(description = "是否使用知识图谱")
|
|
||||||
@JsonProperty("use_kg")
|
|
||||||
private Boolean useKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "Dify 兼容检索响应")
|
|
||||||
public static class DifyRetrievalVO implements Serializable {
|
|
||||||
@Schema(description = "检索结果列表")
|
|
||||||
@JsonProperty("records")
|
|
||||||
private List<Record> records;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "检索记录")
|
|
||||||
public static class Record implements Serializable {
|
|
||||||
@Schema(description = "内容")
|
|
||||||
@JsonProperty("content")
|
|
||||||
private String content;
|
|
||||||
|
|
||||||
@Schema(description = "相似度分数")
|
|
||||||
@JsonProperty("score")
|
|
||||||
private Double score;
|
|
||||||
|
|
||||||
@Schema(description = "标题")
|
|
||||||
@JsonProperty("title")
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Schema(description = "元数据")
|
|
||||||
@JsonProperty("metadata")
|
|
||||||
private Map<String, Object> metadata;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package xiaozhi.modules.knowledge.dto.bot;
|
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.validation.constraints.*;
|
|
||||||
|
|
||||||
@Schema(description = "外部机器人 (Bot) 聚合 DTO")
|
|
||||||
public class BotDTO {
|
|
||||||
|
|
||||||
// ========== 1. SearchBot (检索机器人) ==========
|
|
||||||
|
|
||||||
// 对应 /api/v1/searchbots/ask
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "SearchBot 提问请求")
|
|
||||||
public static class SearchAskReq implements Serializable {
|
|
||||||
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED, example = "What is RAG?")
|
|
||||||
@NotBlank(message = "问题不能为空")
|
|
||||||
@JsonProperty("question")
|
|
||||||
private String question;
|
|
||||||
|
|
||||||
@Schema(description = "是否返回引用", defaultValue = "false")
|
|
||||||
@JsonProperty("quote")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean quote = false;
|
|
||||||
|
|
||||||
@Schema(description = "是否流式返回", defaultValue = "true")
|
|
||||||
@JsonProperty("stream")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean stream = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "SearchBot 提问响应")
|
|
||||||
public static class SearchAskVO implements Serializable {
|
|
||||||
@Schema(description = "回答内容")
|
|
||||||
@JsonProperty("answer")
|
|
||||||
private String answer;
|
|
||||||
|
|
||||||
@Schema(description = "引用来源 (Value 结构通常对应 RetrievalDTO.HitVO)")
|
|
||||||
@JsonProperty("reference")
|
|
||||||
private Map<String, Object> reference;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对应 /api/v1/searchbots/related_questions
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "相关问题请求")
|
|
||||||
public static class RelatedQuestionReq implements Serializable {
|
|
||||||
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@NotBlank(message = "问题不能为空")
|
|
||||||
@JsonProperty("question")
|
|
||||||
private String question;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对应 /api/v1/searchbots/mindmap
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "思维导图请求")
|
|
||||||
public static class MindMapReq implements Serializable {
|
|
||||||
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@NotBlank(message = "问题不能为空")
|
|
||||||
@JsonProperty("question")
|
|
||||||
private String question;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 2. AgentBot (嵌入式 Agent) ==========
|
|
||||||
|
|
||||||
// 对应 /api/v1/agentbots/{id}/inputs
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "AgentBot 输入参数请求")
|
|
||||||
public static class AgentInputsReq implements Serializable {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "AgentBot 输入参数定义响应")
|
|
||||||
public static class AgentInputsVO implements Serializable {
|
|
||||||
@Schema(description = "表单变量定义列表")
|
|
||||||
@JsonProperty("variables")
|
|
||||||
private List<Map<String, Object>> variables;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对应 /api/v1/agentbots/{id}/completions
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "AgentBot 对话请求")
|
|
||||||
public static class AgentCompletionReq implements Serializable {
|
|
||||||
@Schema(description = "输入参数值")
|
|
||||||
@JsonProperty("inputs")
|
|
||||||
private Map<String, Object> inputs;
|
|
||||||
|
|
||||||
@Schema(description = "用户查询", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@NotBlank(message = "查询内容不能为空")
|
|
||||||
@JsonProperty("question")
|
|
||||||
private String question;
|
|
||||||
|
|
||||||
@Schema(description = "是否流式返回", defaultValue = "true")
|
|
||||||
@JsonProperty("stream")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean stream = true;
|
|
||||||
|
|
||||||
@Schema(description = "会话 ID")
|
|
||||||
@JsonProperty("session_id")
|
|
||||||
private String sessionId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
package xiaozhi.modules.knowledge.dto.chat;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 聊天对话请求 DTO (OpenAI 兼容格式)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Schema(description = "聊天对话请求")
|
|
||||||
public class ChatCompletionRequest implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "模型标识 (对应 agent_id 或 bot_id)", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@JsonProperty("model")
|
|
||||||
private String model;
|
|
||||||
|
|
||||||
@Schema(description = "对话消息列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@JsonProperty("messages")
|
|
||||||
private List<Message> messages;
|
|
||||||
|
|
||||||
@Schema(description = "是否流式返回", defaultValue = "false")
|
|
||||||
@JsonProperty("stream")
|
|
||||||
private Boolean stream = false;
|
|
||||||
|
|
||||||
@Schema(description = "温度系数 (0-1)", defaultValue = "0.7")
|
|
||||||
@JsonProperty("temperature")
|
|
||||||
private Double temperature;
|
|
||||||
|
|
||||||
@Schema(description = "Session ID (可选,用于延续会话)")
|
|
||||||
@JsonProperty("session_id")
|
|
||||||
private String sessionId;
|
|
||||||
|
|
||||||
@Schema(description = "其他RAGFlow特定参数 (可选)")
|
|
||||||
private Map<String, Object> extra;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class Message implements Serializable {
|
|
||||||
@Schema(description = "角色 (system, user, assistant)", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
private String role;
|
|
||||||
|
|
||||||
@Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
private String content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,523 +0,0 @@
|
|||||||
package xiaozhi.modules.knowledge.dto.chat;
|
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.validation.constraints.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对话管理聚合 DTO
|
|
||||||
* <p>
|
|
||||||
* 容器类,内含对话助手、会话和消息的所有请求/响应对象。
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
@Schema(description = "对话管理聚合 DTO")
|
|
||||||
public class ChatDTO {
|
|
||||||
|
|
||||||
// ========== 1. 对话助手 (Assistant/Bot) 相关 ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 提示词配置
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "提示词配置")
|
|
||||||
public static class PromptConfig implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "系统提示词", example = "你是一个专业的客服助手...")
|
|
||||||
@JsonProperty("prompt")
|
|
||||||
private String systemPrompt;
|
|
||||||
|
|
||||||
@Schema(description = "开场白", example = "您好,我是您的智能助手,请问有什么可以帮您?")
|
|
||||||
private String opener;
|
|
||||||
|
|
||||||
@Schema(description = "空结果回复", example = "抱歉,我没有找到相关信息。")
|
|
||||||
@JsonProperty("empty_response")
|
|
||||||
private String emptyResponse;
|
|
||||||
|
|
||||||
@Schema(description = "是否展示引用", example = "true")
|
|
||||||
@JsonProperty("show_quote")
|
|
||||||
private Boolean quote;
|
|
||||||
|
|
||||||
@Schema(description = "是否启用 TTS", example = "false")
|
|
||||||
private Boolean tts;
|
|
||||||
|
|
||||||
@Schema(description = "相似度阈值 (0.0 - 1.0)", example = "0.2")
|
|
||||||
@JsonProperty("similarity_threshold")
|
|
||||||
private Float similarityThreshold;
|
|
||||||
|
|
||||||
@Schema(description = "关键词相似度权重 (0.0 - 1.0)", example = "0.7")
|
|
||||||
@JsonProperty("keywords_similarity_weight")
|
|
||||||
private Float vectorSimilarityWeight;
|
|
||||||
|
|
||||||
@Schema(description = "检索 Top N", example = "6")
|
|
||||||
@JsonProperty("top_n")
|
|
||||||
private Integer topK;
|
|
||||||
|
|
||||||
@Schema(description = "Rerank 模型", example = "rerank_model_001")
|
|
||||||
@JsonProperty("rerank_model")
|
|
||||||
private String rerankId;
|
|
||||||
|
|
||||||
@Schema(description = "是否启用多轮对话优化", example = "false")
|
|
||||||
@JsonProperty("refine_multiturn")
|
|
||||||
private Boolean refineMultigraph;
|
|
||||||
|
|
||||||
@Schema(description = "变量列表")
|
|
||||||
private List<Map<String, Object>> variables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LLM 配置
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "LLM 模型配置")
|
|
||||||
public static class LLMConfig implements Serializable {
|
|
||||||
|
|
||||||
@NotBlank(message = "模型名称不能为空")
|
|
||||||
@Schema(description = "模型名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-4")
|
|
||||||
@JsonProperty("model_name")
|
|
||||||
private String modelName;
|
|
||||||
|
|
||||||
@Schema(description = "温度参数 (0.0 - 2.0)", example = "0.7")
|
|
||||||
private Float temperature;
|
|
||||||
|
|
||||||
@Schema(description = "Top P 采样", example = "0.9")
|
|
||||||
@JsonProperty("top_p")
|
|
||||||
private Float topP;
|
|
||||||
|
|
||||||
@Schema(description = "最大 Token 数", example = "4096")
|
|
||||||
@JsonProperty("max_tokens")
|
|
||||||
private Integer maxTokens;
|
|
||||||
|
|
||||||
@Schema(description = "存在惩罚", example = "0.0")
|
|
||||||
@JsonProperty("presence_penalty")
|
|
||||||
private Float presencePenalty;
|
|
||||||
|
|
||||||
@Schema(description = "频率惩罚", example = "0.0")
|
|
||||||
@JsonProperty("frequency_penalty")
|
|
||||||
private Float frequencyPenalty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建助手请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "创建助手请求")
|
|
||||||
public static class AssistantCreateReq implements Serializable {
|
|
||||||
|
|
||||||
@NotBlank(message = "助手名称不能为空")
|
|
||||||
@Schema(description = "助手名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能客服助手")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Schema(description = "助手头像 (Base64 编码)", example = "")
|
|
||||||
private String avatar;
|
|
||||||
|
|
||||||
@Schema(description = "关联的知识库 ID 列表", example = "[\"kb_001\", \"kb_002\"]")
|
|
||||||
@JsonProperty("dataset_ids")
|
|
||||||
private List<String> datasetIds;
|
|
||||||
|
|
||||||
@Schema(description = "助手描述", example = "这是一个智能客服助手")
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
@Schema(description = "LLM 模型配置")
|
|
||||||
@JsonProperty("llm")
|
|
||||||
private LLMConfig llm;
|
|
||||||
|
|
||||||
@Schema(description = "提示词配置")
|
|
||||||
@JsonProperty("prompt")
|
|
||||||
private PromptConfig promptConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新助手请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "更新助手请求")
|
|
||||||
public static class AssistantUpdateReq implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "助手名称", example = "智能客服助手 V2")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Schema(description = "助手头像 (Base64 编码)", example = "")
|
|
||||||
private String avatar;
|
|
||||||
|
|
||||||
@Schema(description = "关联的知识库 ID 列表", example = "[\"kb_001\", \"kb_002\"]")
|
|
||||||
@JsonProperty("dataset_ids")
|
|
||||||
private List<String> datasetIds;
|
|
||||||
|
|
||||||
@Schema(description = "助手描述", example = "这是一个智能客服助手")
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
@Schema(description = "LLM 模型配置")
|
|
||||||
@JsonProperty("llm")
|
|
||||||
private LLMConfig llm;
|
|
||||||
|
|
||||||
@Schema(description = "提示词配置")
|
|
||||||
@JsonProperty("prompt")
|
|
||||||
private PromptConfig promptConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询助手列表请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "查询助手列表请求")
|
|
||||||
public static class AssistantListReq implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "页码 (从 1 开始)", example = "1")
|
|
||||||
private Integer page;
|
|
||||||
|
|
||||||
@Schema(description = "每页数量", example = "30")
|
|
||||||
@JsonProperty("page_size")
|
|
||||||
private Integer pageSize;
|
|
||||||
|
|
||||||
@Schema(description = "按名称过滤 (模糊匹配)", example = "客服")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Schema(description = "排序字段: create_time / update_time", example = "create_time")
|
|
||||||
private String orderby;
|
|
||||||
|
|
||||||
@Schema(description = "是否降序", example = "true")
|
|
||||||
private Boolean desc;
|
|
||||||
|
|
||||||
@Schema(description = "按 ID 精确筛选", example = "assistant_001")
|
|
||||||
private String id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 助手详情 VO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "助手详情 VO")
|
|
||||||
public static class AssistantVO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "助手 ID", example = "assistant_001")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "租户 ID", example = "tenant_001")
|
|
||||||
@JsonProperty("tenant_id")
|
|
||||||
private String tenantId;
|
|
||||||
|
|
||||||
@Schema(description = "助手名称", example = "智能客服助手")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Schema(description = "助手头像", example = "")
|
|
||||||
private String avatar;
|
|
||||||
|
|
||||||
@Schema(description = "关联的知识库 ID 列表")
|
|
||||||
@JsonProperty("dataset_ids")
|
|
||||||
private List<String> datasetIds;
|
|
||||||
|
|
||||||
@Schema(description = "关联的知识库列表 (详情)")
|
|
||||||
private List<SimpleDatasetVO> datasets;
|
|
||||||
|
|
||||||
@Schema(description = "助手描述")
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
@Schema(description = "LLM 模型配置")
|
|
||||||
@JsonProperty("llm")
|
|
||||||
private LLMConfig llm;
|
|
||||||
|
|
||||||
@Schema(description = "提示词配置")
|
|
||||||
@JsonProperty("prompt")
|
|
||||||
private PromptConfig promptConfig;
|
|
||||||
|
|
||||||
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
|
|
||||||
@JsonProperty("create_time")
|
|
||||||
private Long createTime;
|
|
||||||
|
|
||||||
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
|
|
||||||
@JsonProperty("update_time")
|
|
||||||
private Long updateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除助手请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "删除助手请求")
|
|
||||||
public static class AssistantDeleteReq implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "要删除的助手 ID 列表", example = "[\"assistant_001\", \"assistant_002\"]")
|
|
||||||
private List<String> ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 2. 会话 (Session) 相关 ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建会话请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "创建会话请求")
|
|
||||||
public static class SessionCreateReq implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "会话名称", example = "技术咨询会话")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Schema(description = "用户 ID", example = "user_001")
|
|
||||||
@JsonProperty("user_id")
|
|
||||||
private String userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新会话请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "更新会话请求")
|
|
||||||
public static class SessionUpdateReq implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "会话名称", example = "技术咨询会话 - 更新")
|
|
||||||
private String name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询会话列表请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "查询会话列表请求")
|
|
||||||
public static class SessionListReq implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "助手 ID", example = "assistant_001")
|
|
||||||
@JsonProperty("assistant_id")
|
|
||||||
private String assistantId;
|
|
||||||
|
|
||||||
@Schema(description = "页码 (从 1 开始)", example = "1")
|
|
||||||
private Integer page;
|
|
||||||
|
|
||||||
@Schema(description = "每页数量", example = "30")
|
|
||||||
@JsonProperty("page_size")
|
|
||||||
private Integer pageSize;
|
|
||||||
|
|
||||||
@Schema(description = "按名称过滤", example = "技术")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Schema(description = "排序字段", example = "create_time")
|
|
||||||
private String orderby;
|
|
||||||
|
|
||||||
@Schema(description = "是否降序", example = "true")
|
|
||||||
private Boolean desc;
|
|
||||||
|
|
||||||
@Schema(description = "会话 ID 精确筛选", example = "session_001")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "用户标识筛选", example = "user_001")
|
|
||||||
@JsonProperty("user_id")
|
|
||||||
private String userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 会话详情 VO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "会话详情 VO")
|
|
||||||
public static class SessionVO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "会话 ID", example = "session_001")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "助手 ID", example = "assistant_001")
|
|
||||||
@JsonProperty("chat_id")
|
|
||||||
private String chatId;
|
|
||||||
|
|
||||||
@Schema(description = "助手 ID (兼容旧版)", example = "assistant_001")
|
|
||||||
@JsonProperty("assistant_id")
|
|
||||||
private String assistantId;
|
|
||||||
|
|
||||||
@Schema(description = "会话名称", example = "技术咨询会话")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
|
|
||||||
@JsonProperty("create_time")
|
|
||||||
private Long createTime;
|
|
||||||
|
|
||||||
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
|
|
||||||
@JsonProperty("update_time")
|
|
||||||
private Long updateTime;
|
|
||||||
|
|
||||||
@Schema(description = "创建日期", example = "2024-05-01 10:00:00")
|
|
||||||
@JsonProperty("create_date")
|
|
||||||
private String createDate;
|
|
||||||
|
|
||||||
@Schema(description = "更新日期", example = "2024-05-01 10:00:00")
|
|
||||||
@JsonProperty("update_date")
|
|
||||||
private String updateDate;
|
|
||||||
|
|
||||||
@Schema(description = "用户 ID", example = "user_001")
|
|
||||||
@JsonProperty("user_id")
|
|
||||||
private String userId;
|
|
||||||
|
|
||||||
@Schema(description = "对话历史消息列表")
|
|
||||||
private List<Map<String, Object>> messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除会话请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "删除会话请求")
|
|
||||||
public static class SessionDeleteReq implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "要删除的会话 ID 列表", example = "[\"session_001\", \"session_002\"]")
|
|
||||||
private List<String> ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 3. 消息/对话 (Completion) 相关 ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送消息请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "发送消息请求")
|
|
||||||
public static class CompletionReq implements Serializable {
|
|
||||||
|
|
||||||
@NotBlank(message = "问题内容不能为空")
|
|
||||||
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED, example = "请介绍一下你们的产品")
|
|
||||||
private String question;
|
|
||||||
|
|
||||||
@Schema(description = "是否使用流式响应 (SSE)", example = "true")
|
|
||||||
@Builder.Default
|
|
||||||
private Boolean stream = true;
|
|
||||||
|
|
||||||
@NotBlank(message = "会话 ID 不能为空")
|
|
||||||
@Schema(description = "会话 ID (可选,不传则创建新会话)", example = "session_001")
|
|
||||||
@JsonProperty("session_id")
|
|
||||||
private String sessionId;
|
|
||||||
|
|
||||||
@Schema(description = "是否展示引用", example = "true")
|
|
||||||
private Boolean quote;
|
|
||||||
|
|
||||||
@Schema(description = "指定检索的文档 ID 列表 (逗号分隔)", example = "doc_001,doc_002")
|
|
||||||
@JsonProperty("doc_ids")
|
|
||||||
private String docIds;
|
|
||||||
|
|
||||||
@Schema(description = "元数据过滤条件")
|
|
||||||
@JsonProperty("metadata_condition")
|
|
||||||
private Map<String, Object> metadataCondition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息响应 VO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "消息响应 VO")
|
|
||||||
public static class CompletionVO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "AI 回答内容")
|
|
||||||
private String answer;
|
|
||||||
|
|
||||||
@Schema(description = "引用信息")
|
|
||||||
private Reference reference;
|
|
||||||
|
|
||||||
@Schema(description = "会话 ID", example = "session_001")
|
|
||||||
@JsonProperty("session_id")
|
|
||||||
private String sessionId;
|
|
||||||
|
|
||||||
@Schema(description = "任务 ID (用于流式响应追踪)", example = "task_001")
|
|
||||||
@JsonProperty("task_id")
|
|
||||||
private String taskId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 引用信息 (检索命中结果)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "引用信息")
|
|
||||||
public static class Reference implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "命中的文档块列表")
|
|
||||||
private List<xiaozhi.modules.knowledge.dto.document.RetrievalDTO.HitVO> chunks;
|
|
||||||
|
|
||||||
@Schema(description = "文档聚合信息")
|
|
||||||
@JsonProperty("doc_aggs")
|
|
||||||
private List<DocAgg> docAggs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文档聚合信息
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "文档聚合信息")
|
|
||||||
public static class DocAgg implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "文档 ID", example = "doc_001")
|
|
||||||
@JsonProperty("doc_id")
|
|
||||||
private String docId;
|
|
||||||
|
|
||||||
@Schema(description = "文档名称", example = "产品手册.pdf")
|
|
||||||
@JsonProperty("doc_name")
|
|
||||||
private String docName;
|
|
||||||
|
|
||||||
@Schema(description = "命中次数", example = "3")
|
|
||||||
private Integer count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 简易知识库 VO (用于 Assistant 列表)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "简易知识库 VO")
|
|
||||||
public static class SimpleDatasetVO implements Serializable {
|
|
||||||
@Schema(description = "知识库 ID")
|
|
||||||
private String id;
|
|
||||||
@Schema(description = "知识库名称")
|
|
||||||
private String name;
|
|
||||||
@Schema(description = "头像")
|
|
||||||
private String avatar;
|
|
||||||
@Schema(description = "分块数量")
|
|
||||||
@JsonProperty("chunk_num")
|
|
||||||
private Integer chunkNum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package xiaozhi.modules.knowledge.dto.common;
|
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.validation.constraints.*;
|
|
||||||
|
|
||||||
@Schema(description = "通用扩展功能 DTO")
|
|
||||||
public class CommonDTO {
|
|
||||||
|
|
||||||
// ========== 1. 引用详情 (detail_share_embedded) ==========
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "引用详情请求")
|
|
||||||
public static class ReferenceDetailReq implements Serializable {
|
|
||||||
@Schema(description = "切片 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@NotBlank(message = "切片 ID 不能为空")
|
|
||||||
@JsonProperty("chunk_id")
|
|
||||||
private String chunkId;
|
|
||||||
|
|
||||||
@Schema(description = "知识库 ID")
|
|
||||||
@JsonProperty("knowledge_id")
|
|
||||||
private String knowledgeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "引用详情响应")
|
|
||||||
public static class ReferenceDetailVO implements Serializable {
|
|
||||||
@Schema(description = "切片 ID")
|
|
||||||
@JsonProperty("chunk_id")
|
|
||||||
private String chunkId;
|
|
||||||
|
|
||||||
@Schema(description = "完整内容")
|
|
||||||
@JsonProperty("content_with_weight")
|
|
||||||
private String contentWithWeight;
|
|
||||||
|
|
||||||
@Schema(description = "文档名称")
|
|
||||||
@JsonProperty("doc_name")
|
|
||||||
private String docName;
|
|
||||||
|
|
||||||
@Schema(description = "图片 ID 列表")
|
|
||||||
@JsonProperty("img_id")
|
|
||||||
private String imageId; // 注意:RAGFlow 有时返回 String 有时返回 List,需根据实际情况确认,暂定 String 用于 ID
|
|
||||||
|
|
||||||
@Schema(description = "文档 ID")
|
|
||||||
@JsonProperty("doc_id")
|
|
||||||
private String docId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 2. 通用问答 (ask_about) - 调试用 ==========
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "通用问答请求 (调试用)")
|
|
||||||
public static class AskAboutReq implements Serializable {
|
|
||||||
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED, example = "What is this dataset about?")
|
|
||||||
@NotBlank(message = "问题不能为空")
|
|
||||||
@JsonProperty("question")
|
|
||||||
private String question;
|
|
||||||
|
|
||||||
@Schema(description = "数据集 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
@NotEmpty(message = "数据集列表不能为空")
|
|
||||||
@JsonProperty("dataset_ids")
|
|
||||||
private List<String> datasetIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 响应通常复用 String 或者简单的 Map 结构,视具体实现而定,暂不定义专用 VO
|
|
||||||
}
|
|
||||||
@@ -1,363 +0,0 @@
|
|||||||
package xiaozhi.modules.knowledge.dto.file;
|
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.validation.constraints.*;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件管理聚合 DTO
|
|
||||||
* <p>
|
|
||||||
* 容器类,内含文件模块所有请求/响应对象的静态内部类定义。
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
@Schema(description = "文件管理聚合 DTO")
|
|
||||||
public class FileDTO {
|
|
||||||
|
|
||||||
// ========== 请求类 ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件上传请求 (对应接口 1: upload)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "文件上传请求")
|
|
||||||
public static class UploadReq implements Serializable {
|
|
||||||
|
|
||||||
@NotNull(message = "文件不能为空")
|
|
||||||
@Schema(description = "上传的文件", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
private MultipartFile file;
|
|
||||||
|
|
||||||
@Schema(description = "父文件夹 ID (为空则上传到根目录)", example = "folder_001")
|
|
||||||
@JsonProperty("parent_id")
|
|
||||||
private String parentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新建文件夹请求 (对应接口 2: create)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "新建文件夹请求")
|
|
||||||
public static class CreateReq implements Serializable {
|
|
||||||
|
|
||||||
@NotBlank(message = "文件夹名称不能为空")
|
|
||||||
@Schema(description = "文件夹名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "新建文件夹")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Schema(description = "父文件夹 ID (为空则创建在根目录)", example = "folder_001")
|
|
||||||
@JsonProperty("parent_id")
|
|
||||||
private String parentId;
|
|
||||||
|
|
||||||
@NotBlank(message = "类型不能为空")
|
|
||||||
@Schema(description = "类型: FOLDER", requiredMode = Schema.RequiredMode.REQUIRED, example = "FOLDER")
|
|
||||||
@Builder.Default
|
|
||||||
private String type = "FOLDER";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重命名请求 (对应接口 6: rename)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "重命名请求")
|
|
||||||
public static class RenameReq implements Serializable {
|
|
||||||
|
|
||||||
@NotBlank(message = "文件 ID 不能为空")
|
|
||||||
@Schema(description = "文件/文件夹 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "file_001")
|
|
||||||
@JsonProperty("file_id")
|
|
||||||
private String fileId;
|
|
||||||
|
|
||||||
@NotBlank(message = "新名称不能为空")
|
|
||||||
@Schema(description = "新名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "重命名后的文件")
|
|
||||||
private String name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动请求 (对应接口 7: move)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "移动请求")
|
|
||||||
public static class MoveReq implements Serializable {
|
|
||||||
|
|
||||||
@NotEmpty(message = "源文件 ID 列表不能为空")
|
|
||||||
@Schema(description = "源文件/文件夹 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"file_001\", \"file_002\"]")
|
|
||||||
@JsonProperty("src_file_ids")
|
|
||||||
private List<String> srcFileIds;
|
|
||||||
|
|
||||||
@NotBlank(message = "目标文件夹 ID 不能为空")
|
|
||||||
@Schema(description = "目标文件夹 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "folder_002")
|
|
||||||
@JsonProperty("dest_file_id")
|
|
||||||
private String destFileId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量删除请求 (对应接口 8: rm)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "批量删除请求")
|
|
||||||
public static class RemoveReq implements Serializable {
|
|
||||||
|
|
||||||
@NotEmpty(message = "文件 ID 列表不能为空")
|
|
||||||
@Schema(description = "文件/文件夹 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"file_001\", \"file_002\"]")
|
|
||||||
@JsonProperty("file_ids")
|
|
||||||
private List<String> fileIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导入知识库请求 (对应接口 9: convert)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "导入知识库请求")
|
|
||||||
public static class ConvertReq implements Serializable {
|
|
||||||
|
|
||||||
@NotEmpty(message = "文件 ID 列表不能为空")
|
|
||||||
@Schema(description = "文件 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"file_001\", \"file_002\"]")
|
|
||||||
@JsonProperty("file_ids")
|
|
||||||
private List<String> fileIds;
|
|
||||||
|
|
||||||
@NotEmpty(message = "知识库 ID 列表不能为空")
|
|
||||||
@Schema(description = "目标知识库 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"kb_001\"]")
|
|
||||||
@JsonProperty("kb_ids")
|
|
||||||
private List<String> kbIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 列表查询请求 (对应接口 3: list_files)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "列表查询请求")
|
|
||||||
public static class ListReq implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "父文件夹 ID (为空则查询根目录)", example = "folder_001")
|
|
||||||
@JsonProperty("parent_id")
|
|
||||||
private String parentId;
|
|
||||||
|
|
||||||
@Schema(description = "关键词搜索", example = "文档")
|
|
||||||
private String keywords;
|
|
||||||
|
|
||||||
@Schema(description = "页码 (从 1 开始)", example = "1")
|
|
||||||
private Integer page;
|
|
||||||
|
|
||||||
@Schema(description = "每页数量", example = "30")
|
|
||||||
@JsonProperty("page_size")
|
|
||||||
private Integer pageSize;
|
|
||||||
|
|
||||||
@Schema(description = "排序字段: create_time / update_time / name / size", example = "create_time")
|
|
||||||
private String orderby;
|
|
||||||
|
|
||||||
@Schema(description = "是否降序", example = "true")
|
|
||||||
private Boolean desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 响应类 ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件/文件夹基础信息 VO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "文件/文件夹基础信息")
|
|
||||||
public static class InfoVO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "文件/文件夹 ID", example = "file_001")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "父文件夹 ID", example = "folder_001")
|
|
||||||
@JsonProperty("parent_id")
|
|
||||||
private String parentId;
|
|
||||||
|
|
||||||
@Schema(description = "租户 ID", example = "tenant_001")
|
|
||||||
@JsonProperty("tenant_id")
|
|
||||||
private String tenantId;
|
|
||||||
|
|
||||||
@Schema(description = "创建者 ID", example = "user_001")
|
|
||||||
@JsonProperty("created_by")
|
|
||||||
private String createdBy;
|
|
||||||
|
|
||||||
@Schema(description = "类型: FOLDER / FILE", example = "FOLDER")
|
|
||||||
private String type;
|
|
||||||
|
|
||||||
@Schema(description = "名称", example = "我的文件夹")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Schema(description = "路径位置", example = "/root/folder")
|
|
||||||
private String location;
|
|
||||||
|
|
||||||
@Schema(description = "文件大小 (字节)", example = "1024")
|
|
||||||
private Long size;
|
|
||||||
|
|
||||||
@Schema(description = "来源类型", example = "local")
|
|
||||||
@JsonProperty("source_type")
|
|
||||||
private String sourceType;
|
|
||||||
|
|
||||||
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
|
|
||||||
@JsonProperty("create_time")
|
|
||||||
private Long createTime;
|
|
||||||
|
|
||||||
@Schema(description = "创建日期 (格式化)", example = "2024-01-15 10:30:00")
|
|
||||||
@JsonProperty("create_date")
|
|
||||||
private String createDate;
|
|
||||||
|
|
||||||
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
|
|
||||||
@JsonProperty("update_time")
|
|
||||||
private Long updateTime;
|
|
||||||
|
|
||||||
@Schema(description = "更新日期 (格式化)", example = "2024-01-15 11:00:00")
|
|
||||||
@JsonProperty("update_date")
|
|
||||||
private String updateDate;
|
|
||||||
|
|
||||||
@Schema(description = "文件扩展名", example = "pdf")
|
|
||||||
private String extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 列表响应 VO (对应接口 3: list_files)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "文件列表响应")
|
|
||||||
public static class ListVO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "总记录数", example = "100")
|
|
||||||
private Long total;
|
|
||||||
|
|
||||||
@Schema(description = "当前父文件夹信息")
|
|
||||||
@JsonProperty("parent_folder")
|
|
||||||
private InfoVO parentFolder;
|
|
||||||
|
|
||||||
@Schema(description = "文件/文件夹列表")
|
|
||||||
private List<InfoVO> files;
|
|
||||||
|
|
||||||
@Schema(description = "面包屑导航路径")
|
|
||||||
private List<InfoVO> breadcrumb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换结果项 VO (对应接口 9: convert)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "文件转换结果项")
|
|
||||||
public static class ConvertVO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "转换记录 ID", example = "convert_001")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Schema(description = "源文件 ID", example = "file_001")
|
|
||||||
@JsonProperty("file_id")
|
|
||||||
private String fileId;
|
|
||||||
|
|
||||||
@Schema(description = "目标文档 ID", example = "doc_001")
|
|
||||||
@JsonProperty("document_id")
|
|
||||||
private String documentId;
|
|
||||||
|
|
||||||
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
|
|
||||||
@JsonProperty("create_time")
|
|
||||||
private Long createTime;
|
|
||||||
|
|
||||||
@Schema(description = "创建日期 (格式化)", example = "2024-01-15 10:30:00")
|
|
||||||
@JsonProperty("create_date")
|
|
||||||
private String createDate;
|
|
||||||
|
|
||||||
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
|
|
||||||
@JsonProperty("update_time")
|
|
||||||
private Long updateTime;
|
|
||||||
|
|
||||||
@Schema(description = "更新日期 (格式化)", example = "2024-01-15 11:00:00")
|
|
||||||
@JsonProperty("update_date")
|
|
||||||
private String updateDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换状态 VO (对应接口 10: get_convert_status)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "文件转换状态")
|
|
||||||
public static class ConvertStatusVO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "转换状态: pending / processing / completed / failed", example = "completed")
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@Schema(description = "转换进度 (0.0 - 1.0)", example = "1.0")
|
|
||||||
private Float progress;
|
|
||||||
|
|
||||||
@Schema(description = "状态消息", example = "转换完成")
|
|
||||||
private String message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 面包屑 VO (对应接口 12: all_parent_folder)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "面包屑导航 (所有父文件夹)")
|
|
||||||
public static class BreadcrumbVO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "父文件夹列表 (从根到当前的路径)")
|
|
||||||
@JsonProperty("parent_folders")
|
|
||||||
private List<InfoVO> parentFolders;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根目录信息 VO (对应接口 10: get_root_folder)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "根目录信息")
|
|
||||||
public static class RootFolderVO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "根文件夹信息")
|
|
||||||
@JsonProperty("root_folder")
|
|
||||||
private InfoVO rootFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 父目录信息 VO (对应接口 11: get_parent_folder)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
@Schema(description = "父目录信息")
|
|
||||||
public static class ParentFolderVO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "父文件夹信息")
|
|
||||||
@JsonProperty("parent_folder")
|
|
||||||
private InfoVO parentFolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-15
@@ -149,8 +149,8 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
|||||||
log.info("=== [RAGFlow] 获取文档列表: datasetId={} ===", datasetId);
|
log.info("=== [RAGFlow] 获取文档列表: datasetId={} ===", datasetId);
|
||||||
|
|
||||||
// 使用 Jackson 将 DTO 转为 Map 作为查询参数
|
// 使用 Jackson 将 DTO 转为 Map 作为查询参数
|
||||||
@SuppressWarnings("unchecked")
|
Map<String, Object> params = objectMapper.convertValue(req, new TypeReference<Map<String, Object>>() {
|
||||||
Map<String, Object> params = objectMapper.convertValue(req, Map.class);
|
});
|
||||||
|
|
||||||
Map<String, Object> response = getClient().get("/api/v1/datasets/" + datasetId + "/documents", params);
|
Map<String, Object> response = getClient().get("/api/v1/datasets/" + datasetId + "/documents", params);
|
||||||
|
|
||||||
@@ -174,13 +174,12 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
|||||||
.pageSize(1)
|
.pageSize(1)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
Map<String, Object> params = objectMapper.convertValue(req, new TypeReference<Map<String, Object>>() {
|
||||||
Map<String, Object> params = objectMapper.convertValue(req, Map.class);
|
});
|
||||||
Map<String, Object> response = getClient().get("/api/v1/datasets/" + datasetId + "/documents", params);
|
Map<String, Object> response = getClient().get("/api/v1/datasets/" + datasetId + "/documents", params);
|
||||||
|
|
||||||
Object dataObj = response.get("data");
|
Object dataObj = response.get("data");
|
||||||
if (dataObj instanceof Map) {
|
if (dataObj instanceof Map<?, ?> dataMap) {
|
||||||
Map<String, Object> dataMap = (Map<String, Object>) dataObj;
|
|
||||||
List<?> documents = (List<?>) dataMap.get("docs");
|
List<?> documents = (List<?>) dataMap.get("docs");
|
||||||
if (documents != null && !documents.isEmpty()) {
|
if (documents != null && !documents.isEmpty()) {
|
||||||
return objectMapper.convertValue(documents.get(0), DocumentDTO.InfoVO.class);
|
return objectMapper.convertValue(documents.get(0), DocumentDTO.InfoVO.class);
|
||||||
@@ -485,16 +484,11 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getDocumentCount(String datasetId) {
|
public Integer getDocumentCount(String datasetId) {
|
||||||
try {
|
|
||||||
DatasetDTO.InfoVO info = getDatasetInfo(datasetId);
|
DatasetDTO.InfoVO info = getDatasetInfo(datasetId);
|
||||||
if (info != null && info.getDocumentCount() != null) {
|
if (info != null && info.getDocumentCount() != null) {
|
||||||
return info.getDocumentCount().intValue();
|
return info.getDocumentCount().intValue();
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("获取文档数量失败: {}", e.getMessage());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -518,7 +512,7 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
|||||||
return null;
|
return null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("获取数据集信息失败: datasetId={}, error={}", datasetId, e.getMessage());
|
log.warn("获取数据集信息失败: datasetId={}, error={}", datasetId, e.getMessage());
|
||||||
return null;
|
throw convertToRenException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,8 +566,8 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
|||||||
return new PageData<>(new ArrayList<>(), 0);
|
return new PageData<>(new ArrayList<>(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> dataMap = (Map<String, Object>) dataObj;
|
Map<?, ?> dataMap = Map.class.cast(dataObj);
|
||||||
List<Map<String, Object>> documents = (List<Map<String, Object>>) dataMap.get("docs");
|
List<?> documents = List.class.cast(dataMap.get("docs"));
|
||||||
if (documents == null || documents.isEmpty()) {
|
if (documents == null || documents.isEmpty()) {
|
||||||
// RAGFlow 明确返回了空文档列表,这是合法的"真空"
|
// RAGFlow 明确返回了空文档列表,这是合法的"真空"
|
||||||
return new PageData<>(new ArrayList<>(), 0);
|
return new PageData<>(new ArrayList<>(), 0);
|
||||||
@@ -684,7 +678,9 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
|||||||
dto.setChunkMethod(info.getChunkMethod().name().toLowerCase());
|
dto.setChunkMethod(info.getChunkMethod().name().toLowerCase());
|
||||||
}
|
}
|
||||||
if (info.getParserConfig() != null) {
|
if (info.getParserConfig() != null) {
|
||||||
dto.setParserConfig(objectMapper.convertValue(info.getParserConfig(), Map.class));
|
dto.setParserConfig(objectMapper.convertValue(info.getParserConfig(),
|
||||||
|
new TypeReference<Map<String, Object>>() {
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
|
|||||||
+4
-3
@@ -100,9 +100,9 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
|
|||||||
}
|
}
|
||||||
|
|
||||||
DatasetDTO.InfoVO datasetInfo = adapter.getDatasetInfo(dto.getDatasetId());
|
DatasetDTO.InfoVO datasetInfo = adapter.getDatasetInfo(dto.getDatasetId());
|
||||||
|
// getDatasetInfo 正常返回 null 表示远端确实不存在;异常时已抛出 RenException 由外层 catch 接管
|
||||||
if (datasetInfo == null) {
|
if (datasetInfo == null) {
|
||||||
// RAGFlow 端已删除 → 本地级联清理
|
// RAGFlow 端已确认删除 → 本地级联清理
|
||||||
log.info("数据集 {} 在 RAGFlow 端不存在,执行本地清理", dto.getDatasetId());
|
log.info("数据集 {} 在 RAGFlow 端不存在,执行本地清理", dto.getDatasetId());
|
||||||
cleanupLocalDataset(dto.getDatasetId(), dto.getId());
|
cleanupLocalDataset(dto.getDatasetId(), dto.getId());
|
||||||
// 标记为已删除,让上层从列表中移除
|
// 标记为已删除,让上层从列表中移除
|
||||||
@@ -145,8 +145,9 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("同步数据集信息失败 {}: {}", dto.getName(), e.getMessage());
|
log.error("同步数据集信息失败 {}: {}", dto.getName(), e.getMessage());
|
||||||
dto.setDocumentCount(0);
|
dto.setDocumentCount(0);
|
||||||
|
dto.setErrorMessage(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -529,7 +529,8 @@ public class KnowledgeFilesServiceImpl extends BaseServiceImpl<DocumentDao, Docu
|
|||||||
adapter.deleteDocument(datasetId, req);
|
adapter.deleteDocument(datasetId, req);
|
||||||
log.info("远程批量删除请求成功");
|
log.info("远程批量删除请求成功");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("远程删除请求部分或全部失败: {}", e.getMessage());
|
log.error("远程删除请求失败,中止本地清理以避免数据不一致: {}", e.getMessage());
|
||||||
|
throw new RenException(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 原子化清理本地影子记录并同步统计数据
|
// 4. 原子化清理本地影子记录并同步统计数据
|
||||||
|
|||||||
+3
-9
@@ -116,13 +116,11 @@ public class OpenAIStyleLLMServiceImpl implements LLMService {
|
|||||||
Map<String, Object> requestBody = new HashMap<>();
|
Map<String, Object> requestBody = new HashMap<>();
|
||||||
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
||||||
|
|
||||||
Map<String, Object>[] messages = new Map[1];
|
|
||||||
Map<String, Object> message = new HashMap<>();
|
Map<String, Object> message = new HashMap<>();
|
||||||
message.put("role", "user");
|
message.put("role", "user");
|
||||||
message.put("content", prompt);
|
message.put("content", prompt);
|
||||||
messages[0] = message;
|
|
||||||
|
|
||||||
requestBody.put("messages", messages);
|
requestBody.put("messages", List.of(message));
|
||||||
requestBody.put("temperature", temperature != null ? temperature : 0.7);
|
requestBody.put("temperature", temperature != null ? temperature : 0.7);
|
||||||
requestBody.put("max_tokens", maxTokens != null ? maxTokens : 2000);
|
requestBody.put("max_tokens", maxTokens != null ? maxTokens : 2000);
|
||||||
|
|
||||||
@@ -212,13 +210,11 @@ public class OpenAIStyleLLMServiceImpl implements LLMService {
|
|||||||
Map<String, Object> requestBody = new HashMap<>();
|
Map<String, Object> requestBody = new HashMap<>();
|
||||||
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
||||||
|
|
||||||
Map<String, Object>[] messages = new Map[1];
|
|
||||||
Map<String, Object> message = new HashMap<>();
|
Map<String, Object> message = new HashMap<>();
|
||||||
message.put("role", "user");
|
message.put("role", "user");
|
||||||
message.put("content", prompt);
|
message.put("content", prompt);
|
||||||
messages[0] = message;
|
|
||||||
|
|
||||||
requestBody.put("messages", messages);
|
requestBody.put("messages", List.of(message));
|
||||||
requestBody.put("temperature", 0.2);
|
requestBody.put("temperature", 0.2);
|
||||||
requestBody.put("max_tokens", 2000);
|
requestBody.put("max_tokens", 2000);
|
||||||
|
|
||||||
@@ -368,13 +364,11 @@ public class OpenAIStyleLLMServiceImpl implements LLMService {
|
|||||||
Map<String, Object> requestBody = new HashMap<>();
|
Map<String, Object> requestBody = new HashMap<>();
|
||||||
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
||||||
|
|
||||||
Map<String, Object>[] messages = new Map[1];
|
|
||||||
Map<String, Object> message = new HashMap<>();
|
Map<String, Object> message = new HashMap<>();
|
||||||
message.put("role", "user");
|
message.put("role", "user");
|
||||||
message.put("content", prompt);
|
message.put("content", prompt);
|
||||||
messages[0] = message;
|
|
||||||
|
|
||||||
requestBody.put("messages", messages);
|
requestBody.put("messages", List.of(message));
|
||||||
requestBody.put("temperature", 0.3);
|
requestBody.put("temperature", 0.3);
|
||||||
requestBody.put("max_tokens", 50);
|
requestBody.put("max_tokens", 50);
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -362,14 +362,14 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
if (SensitiveDataUtils.isSensitiveField(key)) {
|
if (SensitiveDataUtils.isSensitiveField(key)) {
|
||||||
|
|
||||||
if (value instanceof String && !SensitiveDataUtils.isMaskedValue((String) value)) {
|
if (value instanceof String && !SensitiveDataUtils.isMaskedValue((String) value)) {
|
||||||
updatedJson.put(key, value);
|
updatedJson.set(key, value);
|
||||||
}
|
}
|
||||||
} else if (value instanceof JSONObject) {
|
} else if (value instanceof JSONObject) {
|
||||||
// 递归处理嵌套JSON
|
// 递归处理嵌套JSON
|
||||||
mergeJson(updatedJson, key, (JSONObject) value);
|
mergeJson(updatedJson, key, (JSONObject) value);
|
||||||
} else {
|
} else {
|
||||||
// 非敏感字段直接更新
|
// 非敏感字段直接更新
|
||||||
updatedJson.put(key, value);
|
updatedJson.set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +405,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
|
|
||||||
// 如果 original 中不存在 key,创建一个新的 JSON 对象
|
// 如果 original 中不存在 key,创建一个新的 JSON 对象
|
||||||
if (!original.containsKey(key)) {
|
if (!original.containsKey(key)) {
|
||||||
original.put(key, new JSONObject());
|
original.set(key, new JSONObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 original 中的子对象
|
// 获取 original 中的子对象
|
||||||
@@ -420,7 +420,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
log.warn("mergeJson: key '{}' 的值不是 JSONObject 类型 (实际类型:{}),将创建新对象",
|
log.warn("mergeJson: key '{}' 的值不是 JSONObject 类型 (实际类型:{}),将创建新对象",
|
||||||
key, originalValue != null ? originalValue.getClass().getSimpleName() : "null");
|
key, originalValue != null ? originalValue.getClass().getSimpleName() : "null");
|
||||||
originalChild = new JSONObject();
|
originalChild = new JSONObject();
|
||||||
original.put(key, originalChild);
|
original.set(key, originalChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String childKey : updated.keySet()) {
|
for (String childKey : updated.keySet()) {
|
||||||
@@ -430,7 +430,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
} else {
|
} else {
|
||||||
if (!SensitiveDataUtils.isSensitiveField(childKey) ||
|
if (!SensitiveDataUtils.isSensitiveField(childKey) ||
|
||||||
(childValue instanceof String && !isMaskedValue((String) childValue))) {
|
(childValue instanceof String && !isMaskedValue((String) childValue))) {
|
||||||
originalChild.put(childKey, childValue);
|
originalChild.set(childKey, childValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -164,7 +164,7 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(List<String> ids) {
|
public void delete(List<String> ids) {
|
||||||
if (modelProviderDao.deleteBatchIds(ids) == 0) {
|
if (modelProviderDao.deleteByIds(ids) == 0) {
|
||||||
throw new RenException(ErrorCode.DELETE_DATA_FAILED);
|
throw new RenException(ErrorCode.DELETE_DATA_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,19 @@ import java.util.HashMap;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.shiro.mgt.SecurityManager;
|
|
||||||
import org.apache.shiro.session.mgt.SessionManager;
|
import org.apache.shiro.session.mgt.SessionManager;
|
||||||
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||||
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||||
import org.apache.shiro.web.config.ShiroFilterConfiguration;
|
import org.apache.shiro.web.config.ShiroFilterConfiguration;
|
||||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||||
|
import org.apache.shiro.web.mgt.WebSecurityManager;
|
||||||
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.context.annotation.Role;
|
||||||
|
|
||||||
import jakarta.servlet.Filter;
|
import jakarta.servlet.Filter;
|
||||||
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
||||||
@@ -39,7 +42,7 @@ public class ShiroConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean("securityManager")
|
@Bean("securityManager")
|
||||||
public SecurityManager securityManager(Oauth2Realm oAuth2Realm, SessionManager sessionManager) {
|
public WebSecurityManager securityManager(Oauth2Realm oAuth2Realm, SessionManager sessionManager) {
|
||||||
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||||
securityManager.setRealm(oAuth2Realm);
|
securityManager.setRealm(oAuth2Realm);
|
||||||
securityManager.setSessionManager(sessionManager);
|
securityManager.setSessionManager(sessionManager);
|
||||||
@@ -48,7 +51,8 @@ public class ShiroConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean("shiroFilter")
|
@Bean("shiroFilter")
|
||||||
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, SysParamsService sysParamsService) {
|
public static ShiroFilterFactoryBean shirFilter(@Lazy WebSecurityManager securityManager,
|
||||||
|
@Lazy SysParamsService sysParamsService) {
|
||||||
ShiroFilterConfiguration config = new ShiroFilterConfiguration();
|
ShiroFilterConfiguration config = new ShiroFilterConfiguration();
|
||||||
config.setFilterOncePerRequest(true);
|
config.setFilterOncePerRequest(true);
|
||||||
|
|
||||||
@@ -87,8 +91,7 @@ public class ShiroConfig {
|
|||||||
filterMap.put("/user/retrieve-password", "anon");
|
filterMap.put("/user/retrieve-password", "anon");
|
||||||
// 将config路径使用server服务过滤器
|
// 将config路径使用server服务过滤器
|
||||||
filterMap.put("/config/**", "server");
|
filterMap.put("/config/**", "server");
|
||||||
filterMap.put("/device/address-book/lookup", "server");
|
filterMap.put("/device/address-book/call", "server");
|
||||||
filterMap.put("/device/call/forward", "server");
|
|
||||||
filterMap.put("/agent/chat-history/report", "server");
|
filterMap.put("/agent/chat-history/report", "server");
|
||||||
filterMap.put("/agent/chat-history/download/**", "anon");
|
filterMap.put("/agent/chat-history/download/**", "anon");
|
||||||
filterMap.put("/agent/chat-summary/**", "server");
|
filterMap.put("/agent/chat-summary/**", "server");
|
||||||
@@ -102,12 +105,14 @@ public class ShiroConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean("lifecycleBeanPostProcessor")
|
@Bean("lifecycleBeanPostProcessor")
|
||||||
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
|
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
|
||||||
return new LifecycleBeanPostProcessor();
|
return new LifecycleBeanPostProcessor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||||
|
public static AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
|
||||||
|
@Lazy WebSecurityManager securityManager) {
|
||||||
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
|
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
|
||||||
advisor.setSecurityManager(securityManager);
|
advisor.setSecurityManager(securityManager);
|
||||||
return advisor;
|
return advisor;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class SysUserDTO implements Serializable {
|
|||||||
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
|
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "用户名", required = true)
|
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
@NotBlank(message = "{sysuser.username.require}", groups = DefaultGroup.class)
|
@NotBlank(message = "{sysuser.username.require}", groups = DefaultGroup.class)
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
@@ -40,14 +40,14 @@ public class SysUserDTO implements Serializable {
|
|||||||
@NotBlank(message = "{sysuser.password.require}", groups = AddGroup.class)
|
@NotBlank(message = "{sysuser.password.require}", groups = AddGroup.class)
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@Schema(description = "姓名", required = true)
|
@Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
@NotBlank(message = "{sysuser.realname.require}", groups = DefaultGroup.class)
|
@NotBlank(message = "{sysuser.realname.require}", groups = DefaultGroup.class)
|
||||||
private String realName;
|
private String realName;
|
||||||
|
|
||||||
@Schema(description = "头像")
|
@Schema(description = "头像")
|
||||||
private String headUrl;
|
private String headUrl;
|
||||||
|
|
||||||
@Schema(description = "性别 0:男 1:女 2:保密", required = true)
|
@Schema(description = "性别 0:男 1:女 2:保密", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
@Range(min = 0, max = 2, message = "{sysuser.gender.range}", groups = DefaultGroup.class)
|
@Range(min = 0, max = 2, message = "{sysuser.gender.range}", groups = DefaultGroup.class)
|
||||||
private Integer gender;
|
private Integer gender;
|
||||||
|
|
||||||
@@ -58,11 +58,11 @@ public class SysUserDTO implements Serializable {
|
|||||||
@Schema(description = "手机号")
|
@Schema(description = "手机号")
|
||||||
private String mobile;
|
private String mobile;
|
||||||
|
|
||||||
@Schema(description = "部门ID", required = true)
|
@Schema(description = "部门ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
@NotNull(message = "{sysuser.deptId.require}", groups = DefaultGroup.class)
|
@NotNull(message = "{sysuser.deptId.require}", groups = DefaultGroup.class)
|
||||||
private Long deptId;
|
private Long deptId;
|
||||||
|
|
||||||
@Schema(description = "状态 0:停用 1:正常", required = true)
|
@Schema(description = "状态 0:停用 1:正常", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
@Range(min = 0, max = 1, message = "{sysuser.status.range}", groups = DefaultGroup.class)
|
@Range(min = 0, max = 1, message = "{sysuser.status.range}", groups = DefaultGroup.class)
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -20,6 +20,7 @@ import xiaozhi.common.redis.RedisKeys;
|
|||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
import xiaozhi.common.utils.ToolUtil;
|
import xiaozhi.common.utils.ToolUtil;
|
||||||
import xiaozhi.modules.sys.dao.SysDictDataDao;
|
import xiaozhi.modules.sys.dao.SysDictDataDao;
|
||||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||||
@@ -138,7 +139,7 @@ public class SysDictDataServiceImpl extends BaseServiceImpl<SysDictDataDao, SysD
|
|||||||
|
|
||||||
// 设置更新者和创建者名称
|
// 设置更新者和创建者名称
|
||||||
if (!userIds.isEmpty()) {
|
if (!userIds.isEmpty()) {
|
||||||
List<SysUserEntity> sysUserEntities = sysUserDao.selectBatchIds(userIds);
|
List<SysUserEntity> sysUserEntities = sysUserDao.selectByIds(userIds);
|
||||||
// 把List转成Map,Map<Long, String>
|
// 把List转成Map,Map<Long, String>
|
||||||
Map<Long, String> userNameMap = sysUserEntities.stream().collect(Collectors.toMap(SysUserEntity::getId,
|
Map<Long, String> userNameMap = sysUserEntities.stream().collect(Collectors.toMap(SysUserEntity::getId,
|
||||||
SysUserEntity::getUsername, (existing, replacement) -> existing));
|
SysUserEntity::getUsername, (existing, replacement) -> existing));
|
||||||
@@ -170,7 +171,7 @@ public class SysDictDataServiceImpl extends BaseServiceImpl<SysDictDataDao, SysD
|
|||||||
|
|
||||||
// 先从Redis获取缓存
|
// 先从Redis获取缓存
|
||||||
String key = RedisKeys.getDictDataByTypeKey(dictType);
|
String key = RedisKeys.getDictDataByTypeKey(dictType);
|
||||||
List<SysDictDataItem> cachedData = (List<SysDictDataItem>) redisUtils.get(key);
|
List<SysDictDataItem> cachedData = JsonUtils.toList(redisUtils.get(key), SysDictDataItem.class);
|
||||||
if (cachedData != null) {
|
if (cachedData != null) {
|
||||||
return cachedData;
|
return cachedData;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -128,7 +128,7 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeDao, SysD
|
|||||||
|
|
||||||
// 设置更新者和创建者名称
|
// 设置更新者和创建者名称
|
||||||
if (!userIds.isEmpty()) {
|
if (!userIds.isEmpty()) {
|
||||||
List<SysUserEntity> sysUserEntities = sysUserDao.selectBatchIds(userIds);
|
List<SysUserEntity> sysUserEntities = sysUserDao.selectByIds(userIds);
|
||||||
// 把List转成Map,Map<Long, String>
|
// 把List转成Map,Map<Long, String>
|
||||||
Map<Long, String> userNameMap = sysUserEntities.stream().collect(Collectors.toMap(SysUserEntity::getId,
|
Map<Long, String> userNameMap = sysUserEntities.stream().collect(Collectors.toMap(SysUserEntity::getId,
|
||||||
SysUserEntity::getUsername, (existing, replacement) -> existing));
|
SysUserEntity::getUsername, (existing, replacement) -> existing));
|
||||||
|
|||||||
+10
-12
@@ -287,10 +287,10 @@ public class SysParamsServiceImpl extends BaseServiceImpl<SysParamsDao, SysParam
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (StringUtils.isNotBlank(currentConfig)) {
|
if (StringUtils.isNotBlank(currentConfig)) {
|
||||||
currentMap = JsonUtils.parseObject(currentConfig, Map.class);
|
currentMap = JsonUtils.parseMap(currentConfig);
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotBlank(configJson)) {
|
if (StringUtils.isNotBlank(configJson)) {
|
||||||
newMap = JsonUtils.parseObject(configJson, Map.class);
|
newMap = JsonUtils.parseMap(configJson);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RenException(ErrorCode.PARAM_JSON_INVALID);
|
throw new RenException(ErrorCode.PARAM_JSON_INVALID);
|
||||||
@@ -298,8 +298,8 @@ public class SysParamsServiceImpl extends BaseServiceImpl<SysParamsDao, SysParam
|
|||||||
|
|
||||||
// 检查addressBook功能是否被关闭
|
// 检查addressBook功能是否被关闭
|
||||||
if (currentMap != null && newMap != null) {
|
if (currentMap != null && newMap != null) {
|
||||||
Map<String, Object> currentFeatures = (Map<String, Object>) currentMap.get("features");
|
Map<?, ?> currentFeatures = Map.class.cast(currentMap.get("features"));
|
||||||
Map<String, Object> newFeatures = (Map<String, Object>) newMap.get("features");
|
Map<?, ?> newFeatures = Map.class.cast(newMap.get("features"));
|
||||||
|
|
||||||
if (currentFeatures != null && newFeatures != null) {
|
if (currentFeatures != null && newFeatures != null) {
|
||||||
Object currentAddressBookObj = currentFeatures.get("addressBook");
|
Object currentAddressBookObj = currentFeatures.get("addressBook");
|
||||||
@@ -308,16 +308,14 @@ public class SysParamsServiceImpl extends BaseServiceImpl<SysParamsDao, SysParam
|
|||||||
Boolean currentEnabled = false;
|
Boolean currentEnabled = false;
|
||||||
Boolean newEnabled = false;
|
Boolean newEnabled = false;
|
||||||
|
|
||||||
if (currentAddressBookObj instanceof Map) {
|
if (currentAddressBookObj instanceof Map<?, ?> currentAddressBook) {
|
||||||
Map<String, Object> currentAddressBook = (Map<String, Object>) currentAddressBookObj;
|
Object enabled = currentAddressBook.get("enabled");
|
||||||
currentEnabled = currentAddressBook.get("enabled") != null
|
currentEnabled = enabled != null ? Boolean.class.cast(enabled) : false;
|
||||||
? (Boolean) currentAddressBook.get("enabled") : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newAddressBookObj instanceof Map) {
|
if (newAddressBookObj instanceof Map<?, ?> newAddressBook) {
|
||||||
Map<String, Object> newAddressBook = (Map<String, Object>) newAddressBookObj;
|
Object enabled = newAddressBook.get("enabled");
|
||||||
newEnabled = newAddressBook.get("enabled") != null
|
newEnabled = enabled != null ? Boolean.class.cast(enabled) : false;
|
||||||
? (Boolean) newAddressBook.get("enabled") : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果之前是启用状态,现在被禁用,删除所有call_device插件
|
// 如果之前是启用状态,现在被禁用,删除所有call_device插件
|
||||||
|
|||||||
@@ -57,6 +57,14 @@ public interface TimbreService extends BaseService<TimbreEntity> {
|
|||||||
|
|
||||||
List<VoiceDTO> getVoiceNames(String ttsModelId, String voiceName);
|
List<VoiceDTO> getVoiceNames(String ttsModelId, String voiceName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取普通音色或克隆音色配置的首个有效语言。
|
||||||
|
*
|
||||||
|
* @param id 音色ID
|
||||||
|
* @return 默认语言;音色不存在或未配置有效语言时返回null
|
||||||
|
*/
|
||||||
|
String getDefaultLanguageById(String id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据ID获取音色名称
|
* 根据ID获取音色名称
|
||||||
*
|
*
|
||||||
|
|||||||
+30
-1
@@ -1,6 +1,7 @@
|
|||||||
package xiaozhi.modules.timbre.service.impl;
|
package xiaozhi.modules.timbre.service.impl;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -41,6 +42,8 @@ import xiaozhi.modules.voiceclone.entity.VoiceCloneEntity;
|
|||||||
@Service
|
@Service
|
||||||
public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity> implements TimbreService {
|
public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity> implements TimbreService {
|
||||||
|
|
||||||
|
private static final Pattern LANGUAGE_SEPARATOR = Pattern.compile("[、;;,,]");
|
||||||
|
|
||||||
private final TimbreDao timbreDao;
|
private final TimbreDao timbreDao;
|
||||||
private final VoiceCloneDao voiceCloneDao;
|
private final VoiceCloneDao voiceCloneDao;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
@@ -114,7 +117,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void delete(String[] ids) {
|
public void delete(String[] ids) {
|
||||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
baseDao.deleteByIds(Arrays.asList(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -158,6 +161,32 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
|||||||
return CollectionUtil.isEmpty(voiceDTOs) ? null : voiceDTOs;
|
return CollectionUtil.isEmpty(voiceDTOs) ? null : voiceDTOs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultLanguageById(String id) {
|
||||||
|
if (StringUtils.isBlank(id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimbreEntity timbre = timbreDao.selectById(id);
|
||||||
|
if (timbre != null) {
|
||||||
|
return firstNonBlankLanguage(timbre.getLanguages());
|
||||||
|
}
|
||||||
|
|
||||||
|
VoiceCloneEntity voiceClone = voiceCloneDao.selectById(id);
|
||||||
|
return voiceClone == null ? null : firstNonBlankLanguage(voiceClone.getLanguages());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlankLanguage(String languages) {
|
||||||
|
if (StringUtils.isBlank(languages)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return LANGUAGE_SEPARATOR.splitAsStream(languages)
|
||||||
|
.map(StringUtils::trimToNull)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理是不是tts模型的id
|
* 处理是不是tts模型的id
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1
-1
@@ -127,7 +127,7 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl<VoiceCloneDao, VoiceC
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(String[] ids) {
|
public void delete(String[] ids) {
|
||||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
baseDao.deleteByIds(Arrays.asList(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- 更新EdgeTTS供应器增加语速、音调、音量配置
|
||||||
|
UPDATE `ai_model_provider`
|
||||||
|
SET fields = '[{"key":"voice","label":"音色","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"rate","label":"语速(-100~100)","type":"number"},{"key":"volume","label":"音量(0~100)","type":"number"},{"key":"pitch","label":"音调(-100~100)","type":"number"}]'
|
||||||
|
WHERE id = 'SYSTEM_TTS_edge';
|
||||||
|
|
||||||
|
UPDATE `ai_model_config` SET
|
||||||
|
`remark` = 'EdgeTTS配置说明:
|
||||||
|
1. 使用微软Edge TTS服务
|
||||||
|
2. 支持多种语言和音色
|
||||||
|
3. 免费使用,无需注册
|
||||||
|
4. 需要网络连接
|
||||||
|
5. 输出文件保存在tmp/目录
|
||||||
|
6. 语速:-100~100,0为正常速度
|
||||||
|
7. 音量:0~100,50为正常音量
|
||||||
|
8. 音调:-100~100,0为正常音调' WHERE `id` = 'TTS_EdgeTTS';
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
-- 修正、添加Minimax流式TTS音色
|
||||||
|
delete from `ai_tts_voice` where tts_model_id = 'TTS_MinimaxStreamTTS';
|
||||||
|
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0001', 'TTS_MinimaxStreamTTS', '少女音', 'female-shaonv', '普通话', NULL, NULL, NULL, NULL, 1, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0002', 'TTS_MinimaxStreamTTS', '成熟女声', 'female-chengshu', '普通话', NULL, NULL, NULL, NULL, 2, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0003', 'TTS_MinimaxStreamTTS', '霸道少爷', 'badao_shaoye', '普通话', NULL, NULL, NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0004', 'TTS_MinimaxStreamTTS', '病娇弟弟', 'bingjiao_didi', '普通话', NULL, NULL, NULL, NULL, 4, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0005', 'TTS_MinimaxStreamTTS', '纯真学弟', 'chunzhen_xuedi', '普通话', NULL, NULL, NULL, NULL, 5, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0006', 'TTS_MinimaxStreamTTS', '冷淡学长', 'lengdan_xiongzhang', '普通话', NULL, NULL, NULL, NULL, 6, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0007', 'TTS_MinimaxStreamTTS', '甜美小玲', 'tianxin_xiaoling', '普通话', NULL, NULL, NULL, NULL, 7, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0008', 'TTS_MinimaxStreamTTS', '俏皮萌妹', 'qiaopi_mengmei', '普通话', NULL, NULL, NULL, NULL, 8, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0009', 'TTS_MinimaxStreamTTS', '妩媚御姐', 'wumei_yujie', '普通话', NULL, NULL, NULL, NULL, 9, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0010', 'TTS_MinimaxStreamTTS', '调教学妹', 'diadia_xuemei', '普通话', NULL, NULL, NULL, NULL, 10, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0011', 'TTS_MinimaxStreamTTS', '淡雅学姐', 'danya_xuejie', '普通话', NULL, NULL, NULL, NULL, 11, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0012', 'TTS_MinimaxStreamTTS', 'Santa Claus', 'Santa_Claus', '普通话', NULL, NULL, NULL, NULL, 12, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0013', 'TTS_MinimaxStreamTTS', 'Grinch', 'Grinch', '普通话', NULL, NULL, NULL, NULL, 13, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0014', 'TTS_MinimaxStreamTTS', '专业女主持', 'Cantonese_ProfessionalHost(F)', '粤语', "https://filecdn.minimax.chat/public/84589766-c651-477b-b320-36b89599f953.mp3", NULL, NULL, NULL, 14, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0015', 'TTS_MinimaxStreamTTS', '温柔女声', 'Cantonese_GentleLady', '粤语', "https://filecdn.minimax.chat/public/d391936d-d586-4dab-886c-a2a084a20920.mp3", NULL, NULL, NULL, 15, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0016', 'TTS_MinimaxStreamTTS', '专业男主持', 'Cantonese_ProfessionalHost(M)', '粤语', NULL, NULL, NULL, NULL, 16, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0017', 'TTS_MinimaxStreamTTS', '活泼男声', 'Cantonese_PlayfulMan', '粤语', NULL, NULL, NULL, NULL, 17, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0018', 'TTS_MinimaxStreamTTS', '可爱女孩', 'Cantonese_CuteGirl', '粤语', "https://filecdn.minimax.chat/public/ff96520a-ac12-491d-945e-d6337467eb7d.mp3", NULL, NULL, NULL, 18, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0019', 'TTS_MinimaxStreamTTS', '善良女声', 'Cantonese_KindWoman', '粤语', "https://filecdn.minimax.chat/public/78239a18-5e9c-4538-94f6-2f504c080374.mp3", NULL, NULL, NULL, 19, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0020', 'TTS_MinimaxStreamTTS', 'Crisp News Anchor', 'Cantonese_crisp_news_anchor_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/741a5a4b-f42a-4a9b-ae83-3954d097ab73.mp3', NULL, NULL, NULL, 20, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0021', 'TTS_MinimaxStreamTTS', 'Pro Reporter', 'Cantonese_professional_reporter_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/77afb86d-2cc4-4149-8c2e-e1174fa849d4.mp3', NULL, NULL, NULL, 21, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0022', 'TTS_MinimaxStreamTTS', 'Crisp Reporter', 'Cantonese_crisp_reporter_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/4ad39a61-628e-481c-9d60-bfb7c36bceb8.mp3', NULL, NULL, NULL, 22, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0023', 'TTS_MinimaxStreamTTS', 'Articulate', 'Cantonese_Articulate_commentator_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/9346601d-3035-4f9e-9b1c-0e03f30e5eef.mp3', NULL, NULL, NULL, 23, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0024', 'TTS_MinimaxStreamTTS', 'Warm Reporter', 'Cantonese_Warm_Reporter_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/c651050e-d6a5-4653-b09e-5177bdc8b04c.mp3', NULL, NULL, NULL, 24, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0025', 'TTS_MinimaxStreamTTS', 'Energetic', 'Cantonese_energetic_commentator_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/1fd1c461-71da-450d-91bb-45f819249b44.mp3', NULL, NULL, NULL, 25, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0026', 'TTS_MinimaxStreamTTS', 'Objective Narrator', 'Cantonese_objective_narrator_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/463c5a68-a7b5-4fe9-85b6-d494dca74abb.mp3', NULL, NULL, NULL, 26, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0027', 'TTS_MinimaxStreamTTS', '沙哑男声', 'Cantonese_Narrator', '粤语', 'https://filecdn.minimax.chat/public/22e29cb0-0f5a-443a-9fa9-a32dabd55cf9.mp3', NULL, NULL, NULL, 27, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0028', 'TTS_MinimaxStreamTTS', 'News Anchor', 'Cantonese_news_anchor_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/59b9ba49-47d4-48f1-b207-09c0e0769a1b.mp3', NULL, NULL, NULL, 28, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0029', 'TTS_MinimaxStreamTTS', '渊博学者', 'Cantonese_WiselProfessor', '粤语', 'https://filecdn.minimax.chat/public/8de234c4-64a7-4539-a316-c3b43ee1d0a9.mp3', NULL, NULL, NULL, 29, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0030', 'TTS_MinimaxStreamTTS', 'Resonant Host', 'Cantonese_resonant_host_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/ec8fc0ce-6657-4c49-9bf0-3953a15046ec.mp3', NULL, NULL, NULL, 30, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0031', 'TTS_MinimaxStreamTTS', '冷漠员工', 'Cantonese_IndifferentStaff', '粤语', 'https://filecdn.minimax.chat/public/9686682c-3fc8-45f0-bd88-2a4692e1f63e.mp3', NULL, NULL, NULL, 31, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0032', 'TTS_MinimaxStreamTTS', 'Male News Anchor', 'Cantonese_Male_news_anchor_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/2055ee8b-fb25-424c-b8cb-e2baf971e520.mp3', NULL, NULL, NULL, 32, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0033', 'TTS_MinimaxStreamTTS', 'History Narrator', 'Cantonese_casual_narrator_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/c170f9e4-145d-4331-9910-1c3a0e6bbc3a.mp3', NULL, NULL, NULL, 33, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0034', 'TTS_MinimaxStreamTTS', 'Steady Reporter', 'Cantonese_Steady_reporter_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/381cfee2-772d-4a73-b30c-7914aec05771.mp3', NULL, NULL, NULL, 34, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0035', 'TTS_MinimaxStreamTTS', 'Serious Commentator', 'Cantonese_serious_commentator_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/57335c8a-32ea-475f-a198-b0de7fa1b126.mp3', NULL, NULL, NULL, 35, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0036', 'TTS_MinimaxStreamTTS', 'Podcast Host', 'Cantonese_podcast_host_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/048b617f-e1f8-422c-89fb-fdbc6e303675.mp3', NULL, NULL, NULL, 36, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxStreamTTS_0037', 'TTS_MinimaxStreamTTS', 'Obj commentator', 'Cantonese_Objective_commentator_vv2', '粤语', 'https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/a8f55e44-b726-4ba5-815a-f1b515ba57ef.mp3', NULL, NULL, NULL, 37, NULL, NULL, NULL, NULL);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-- Add language configuration for local FunASR ASR.
|
||||||
|
UPDATE `ai_model_provider`
|
||||||
|
SET `fields` = '[{"key":"model_dir","label":"模型目录","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"language","label":"识别语言","type":"string","default":"auto"}]'
|
||||||
|
WHERE `id` = 'SYSTEM_ASR_FunASR';
|
||||||
|
|
||||||
|
UPDATE `ai_model_config`
|
||||||
|
SET `config_json` = JSON_SET(`config_json`, '$.language', 'auto')
|
||||||
|
WHERE `id` = 'ASR_FunASR'
|
||||||
|
AND JSON_EXTRACT(`config_json`, '$.language') IS NULL;
|
||||||
|
|
||||||
|
-- Update the FunASR local model configuration description to mention the language option.
|
||||||
|
UPDATE `ai_model_config`
|
||||||
|
SET `remark` = 'FunASR本地模型配置说明:
|
||||||
|
1. 需要下载模型文件到xiaozhi-server/models/SenseVoiceSmall目录
|
||||||
|
2. 支持中日韩粤语音识别
|
||||||
|
3. 本地推理,无需网络连接
|
||||||
|
4. 待识别文件保存在tmp/目录
|
||||||
|
5. “识别语言”字段控制识别语种:auto = 自动检测;如需限定只识别中文可设为 zh(en=英语、ja=日语、ko=韩语、yue=粤语)。'
|
||||||
|
WHERE `id` = 'ASR_FunASR';
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
-- liquibase formatted sql
|
||||||
|
|
||||||
|
-- changeset tykechen:202607071530
|
||||||
|
CREATE TABLE IF NOT EXISTS `ai_agent_snapshot` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '快照ID',
|
||||||
|
`agent_id` VARCHAR(32) NOT NULL COMMENT '智能体ID',
|
||||||
|
`user_id` BIGINT DEFAULT NULL COMMENT '所属用户ID',
|
||||||
|
`version_no` INT UNSIGNED NOT NULL COMMENT '版本号',
|
||||||
|
`snapshot_data` JSON NOT NULL DEFAULT (JSON_OBJECT()) COMMENT '快照数据',
|
||||||
|
`changed_fields` JSON DEFAULT NULL COMMENT '变更字段',
|
||||||
|
`source` VARCHAR(32) DEFAULT 'config' COMMENT '快照来源',
|
||||||
|
`restore_from_snapshot_id` VARCHAR(32) DEFAULT NULL COMMENT '恢复来源快照ID',
|
||||||
|
`restore_from_version_no` INT UNSIGNED DEFAULT NULL COMMENT '恢复来源版本号',
|
||||||
|
`creator` BIGINT DEFAULT NULL COMMENT '创建者',
|
||||||
|
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_agent_version` (`agent_id`, `version_no`),
|
||||||
|
INDEX `idx_agent_created_at` (`agent_id`, `created_at`),
|
||||||
|
INDEX `idx_snapshot_user_created_at` (`user_id`, `created_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能体配置快照表';
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- liquibase formatted sql
|
||||||
|
|
||||||
|
-- changeset tykechen:202607101200
|
||||||
|
ALTER TABLE `ai_agent_snapshot`
|
||||||
|
ADD COLUMN `redaction_version` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '快照脱敏规则版本' AFTER `created_at`,
|
||||||
|
ADD INDEX `idx_snapshot_redaction_version_id` (`redaction_version`, `id`);
|
||||||
|
|
||||||
|
-- rollback ALTER TABLE `ai_agent_snapshot` DROP INDEX `idx_snapshot_redaction_version_id`, DROP COLUMN `redaction_version`;
|
||||||
Executable → Regular
+35
@@ -676,3 +676,38 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202605251426.sql
|
path: classpath:db/changelog/202605251426.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202606231030
|
||||||
|
author: RanChen
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202606231030.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202606261131
|
||||||
|
author: RanChen
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202606261131.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202607011405
|
||||||
|
author: LauraGPT
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202607011405.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202607071530
|
||||||
|
author: tykechen
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202607071530.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202607101200
|
||||||
|
author: tykechen
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202607101200.sql
|
||||||
|
|||||||
@@ -1,20 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<!-- 启用JansiConsoleAppender以确保控制台输出有颜色 -->
|
|
||||||
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
|
|
||||||
|
|
||||||
<!-- 确保日志目录存在 -->
|
|
||||||
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
|
|
||||||
|
|
||||||
<!-- 定义日志文件存储位置 -->
|
<!-- 定义日志文件存储位置 -->
|
||||||
<property name="LOG_HOME" value="./logs" />
|
<property name="LOG_HOME" value="./logs" />
|
||||||
|
|
||||||
<!-- 使用自定义的初始化监听器确保日志目录存在 -->
|
|
||||||
<define name="LOGBACK_DIR_CHECK" class="ch.qos.logback.core.property.FileExistsPropertyDefiner">
|
|
||||||
<path>${LOG_HOME}</path>
|
|
||||||
<createIfMissing>true</createIfMissing>
|
|
||||||
</define>
|
|
||||||
|
|
||||||
<!-- 引入Spring Boot默认配置 -->
|
<!-- 引入Spring Boot默认配置 -->
|
||||||
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,6 @@
|
|||||||
<result column="memModelId" property="memModelId"/>
|
<result column="memModelId" property="memModelId"/>
|
||||||
<result column="intentModelId" property="intentModelId"/>
|
<result column="intentModelId" property="intentModelId"/>
|
||||||
|
|
||||||
<result column="functions" property="functions"
|
|
||||||
typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
|
|
||||||
|
|
||||||
<result column="chatHistoryConf" property="chatHistoryConf"/>
|
<result column="chatHistoryConf" property="chatHistoryConf"/>
|
||||||
<result column="systemPrompt" property="systemPrompt"/>
|
<result column="systemPrompt" property="systemPrompt"/>
|
||||||
<result column="summaryMemory" property="summaryMemory"/>
|
<result column="summaryMemory" property="summaryMemory"/>
|
||||||
@@ -38,6 +35,13 @@
|
|||||||
<result column="createdAt" property="createdAt"/>
|
<result column="createdAt" property="createdAt"/>
|
||||||
<result column="updater" property="updater"/>
|
<result column="updater" property="updater"/>
|
||||||
<result column="updatedAt" property="updatedAt"/>
|
<result column="updatedAt" property="updatedAt"/>
|
||||||
|
<collection property="functions"
|
||||||
|
ofType="xiaozhi.modules.agent.entity.AgentPluginMapping">
|
||||||
|
<id column="functionId" property="id"/>
|
||||||
|
<result column="functionAgentId" property="agentId"/>
|
||||||
|
<result column="pluginId" property="pluginId"/>
|
||||||
|
<result column="paramInfo" property="paramInfo"/>
|
||||||
|
</collection>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="selectAgentInfoById" resultMap="AgentInfoMap">
|
<select id="selectAgentInfoById" resultMap="AgentInfoMap">
|
||||||
@@ -58,19 +62,6 @@
|
|||||||
a.tts_pitch AS ttsPitch,
|
a.tts_pitch AS ttsPitch,
|
||||||
a.mem_model_id AS memModelId,
|
a.mem_model_id AS memModelId,
|
||||||
a.intent_model_id AS intentModelId,
|
a.intent_model_id AS intentModelId,
|
||||||
COALESCE(
|
|
||||||
(SELECT JSON_ARRAYAGG(
|
|
||||||
JSON_OBJECT(
|
|
||||||
'id', m.id,
|
|
||||||
'agentId', m.agent_id,
|
|
||||||
'pluginId', m.plugin_id,
|
|
||||||
'paramInfo', m.param_info
|
|
||||||
)
|
|
||||||
)
|
|
||||||
FROM ai_agent_plugin_mapping m
|
|
||||||
WHERE m.agent_id = a.id),
|
|
||||||
JSON_ARRAY()
|
|
||||||
) AS functions,
|
|
||||||
a.chat_history_conf AS chatHistoryConf,
|
a.chat_history_conf AS chatHistoryConf,
|
||||||
a.system_prompt AS systemPrompt,
|
a.system_prompt AS systemPrompt,
|
||||||
a.summary_memory AS summaryMemory,
|
a.summary_memory AS summaryMemory,
|
||||||
@@ -80,8 +71,50 @@
|
|||||||
a.creator,
|
a.creator,
|
||||||
a.created_at AS createdAt,
|
a.created_at AS createdAt,
|
||||||
a.updater,
|
a.updater,
|
||||||
a.updated_at AS updatedAt
|
a.updated_at AS updatedAt,
|
||||||
|
f.id AS functionId,
|
||||||
|
f.agent_id AS functionAgentId,
|
||||||
|
f.plugin_id AS pluginId,
|
||||||
|
f.param_info AS paramInfo
|
||||||
FROM ai_agent a
|
FROM ai_agent a
|
||||||
|
LEFT JOIN ai_agent_plugin_mapping f ON f.agent_id = a.id
|
||||||
WHERE a.id = #{agentId}
|
WHERE a.id = #{agentId}
|
||||||
|
ORDER BY f.id ASC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByIdForUpdate" resultType="xiaozhi.modules.agent.entity.AgentEntity">
|
||||||
|
SELECT *
|
||||||
|
FROM ai_agent
|
||||||
|
WHERE id = #{agentId}
|
||||||
|
FOR UPDATE
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="updateSnapshotFields">
|
||||||
|
UPDATE ai_agent
|
||||||
|
SET agent_code = #{agent.agentCode,jdbcType=VARCHAR},
|
||||||
|
agent_name = #{agent.agentName,jdbcType=VARCHAR},
|
||||||
|
asr_model_id = #{agent.asrModelId,jdbcType=VARCHAR},
|
||||||
|
vad_model_id = #{agent.vadModelId,jdbcType=VARCHAR},
|
||||||
|
llm_model_id = #{agent.llmModelId,jdbcType=VARCHAR},
|
||||||
|
slm_model_id = #{agent.slmModelId,jdbcType=VARCHAR},
|
||||||
|
vllm_model_id = #{agent.vllmModelId,jdbcType=VARCHAR},
|
||||||
|
tts_model_id = #{agent.ttsModelId,jdbcType=VARCHAR},
|
||||||
|
tts_voice_id = #{agent.ttsVoiceId,jdbcType=VARCHAR},
|
||||||
|
tts_language = #{agent.ttsLanguage,jdbcType=VARCHAR},
|
||||||
|
tts_volume = #{agent.ttsVolume,jdbcType=INTEGER},
|
||||||
|
tts_rate = #{agent.ttsRate,jdbcType=INTEGER},
|
||||||
|
tts_pitch = #{agent.ttsPitch,jdbcType=INTEGER},
|
||||||
|
mem_model_id = #{agent.memModelId,jdbcType=VARCHAR},
|
||||||
|
intent_model_id = #{agent.intentModelId,jdbcType=VARCHAR},
|
||||||
|
chat_history_conf = #{agent.chatHistoryConf,jdbcType=INTEGER},
|
||||||
|
system_prompt = #{agent.systemPrompt,jdbcType=LONGVARCHAR},
|
||||||
|
summary_memory = #{agent.summaryMemory,jdbcType=LONGVARCHAR},
|
||||||
|
lang_code = #{agent.langCode,jdbcType=VARCHAR},
|
||||||
|
language = #{agent.language,jdbcType=VARCHAR},
|
||||||
|
sort = #{agent.sort,jdbcType=INTEGER},
|
||||||
|
updater = #{agent.updater,jdbcType=BIGINT},
|
||||||
|
updated_at = #{agent.updatedAt,jdbcType=TIMESTAMP}
|
||||||
|
WHERE id = #{agent.id}
|
||||||
|
</update>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="xiaozhi.modules.agent.dao.AgentSnapshotDao">
|
||||||
|
|
||||||
|
<select id="selectMaxVersionNo" resultType="java.lang.Integer">
|
||||||
|
SELECT COALESCE(MAX(version_no), 0)
|
||||||
|
FROM ai_agent_snapshot
|
||||||
|
WHERE agent_id = #{agentId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectLatestSnapshot" resultType="xiaozhi.modules.agent.entity.AgentSnapshotEntity">
|
||||||
|
SELECT id,
|
||||||
|
agent_id AS agentId,
|
||||||
|
user_id AS userId,
|
||||||
|
version_no AS versionNo,
|
||||||
|
snapshot_data AS snapshotData,
|
||||||
|
changed_fields AS changedFields,
|
||||||
|
source,
|
||||||
|
restore_from_snapshot_id AS restoreFromSnapshotId,
|
||||||
|
restore_from_version_no AS restoreFromVersionNo,
|
||||||
|
creator,
|
||||||
|
created_at AS createdAt,
|
||||||
|
redaction_version AS redactionVersion
|
||||||
|
FROM ai_agent_snapshot
|
||||||
|
WHERE agent_id = #{agentId}
|
||||||
|
ORDER BY version_no DESC
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectNextSnapshot" resultType="xiaozhi.modules.agent.entity.AgentSnapshotEntity">
|
||||||
|
SELECT id,
|
||||||
|
agent_id AS agentId,
|
||||||
|
user_id AS userId,
|
||||||
|
version_no AS versionNo,
|
||||||
|
snapshot_data AS snapshotData,
|
||||||
|
changed_fields AS changedFields,
|
||||||
|
source,
|
||||||
|
restore_from_snapshot_id AS restoreFromSnapshotId,
|
||||||
|
restore_from_version_no AS restoreFromVersionNo,
|
||||||
|
creator,
|
||||||
|
created_at AS createdAt,
|
||||||
|
redaction_version AS redactionVersion
|
||||||
|
FROM ai_agent_snapshot
|
||||||
|
WHERE agent_id = #{agentId}
|
||||||
|
AND version_no > #{versionNo}
|
||||||
|
ORDER BY version_no ASC
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertWithNextVersion">
|
||||||
|
INSERT INTO ai_agent_snapshot (
|
||||||
|
id,
|
||||||
|
agent_id,
|
||||||
|
user_id,
|
||||||
|
version_no,
|
||||||
|
snapshot_data,
|
||||||
|
changed_fields,
|
||||||
|
source,
|
||||||
|
restore_from_snapshot_id,
|
||||||
|
restore_from_version_no,
|
||||||
|
creator,
|
||||||
|
created_at,
|
||||||
|
redaction_version
|
||||||
|
)
|
||||||
|
SELECT #{snapshot.id},
|
||||||
|
#{snapshot.agentId},
|
||||||
|
#{snapshot.userId},
|
||||||
|
COALESCE(MAX(version_no), 0) + 1,
|
||||||
|
#{snapshot.snapshotData},
|
||||||
|
#{snapshot.changedFields},
|
||||||
|
#{snapshot.source},
|
||||||
|
#{snapshot.restoreFromSnapshotId},
|
||||||
|
#{snapshot.restoreFromVersionNo},
|
||||||
|
#{snapshot.creator},
|
||||||
|
#{snapshot.createdAt},
|
||||||
|
#{snapshot.redactionVersion}
|
||||||
|
FROM ai_agent_snapshot
|
||||||
|
WHERE agent_id = #{snapshot.agentId}
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<delete id="deleteOlderThanKeepLimit">
|
||||||
|
DELETE FROM ai_agent_snapshot
|
||||||
|
WHERE agent_id = #{agentId}
|
||||||
|
AND id NOT IN (
|
||||||
|
SELECT id
|
||||||
|
FROM (
|
||||||
|
SELECT id
|
||||||
|
FROM ai_agent_snapshot
|
||||||
|
WHERE agent_id = #{agentId}
|
||||||
|
ORDER BY version_no DESC
|
||||||
|
LIMIT #{keepLimit}
|
||||||
|
) retained_snapshots
|
||||||
|
)
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<select id="selectLegacyRedactionBatch" resultType="xiaozhi.modules.agent.entity.AgentSnapshotEntity">
|
||||||
|
SELECT id,
|
||||||
|
snapshot_data AS snapshotData,
|
||||||
|
redaction_version AS redactionVersion
|
||||||
|
FROM ai_agent_snapshot
|
||||||
|
WHERE redaction_version < #{targetRedactionVersion}
|
||||||
|
AND (#{afterId} IS NULL OR id > #{afterId})
|
||||||
|
ORDER BY id ASC
|
||||||
|
LIMIT #{limit}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="updateRedactedSnapshots">
|
||||||
|
UPDATE ai_agent_snapshot
|
||||||
|
SET snapshot_data = CASE id
|
||||||
|
<foreach collection="snapshots" item="snapshot">
|
||||||
|
WHEN #{snapshot.id} THEN #{snapshot.snapshotData}
|
||||||
|
</foreach>
|
||||||
|
ELSE snapshot_data
|
||||||
|
END,
|
||||||
|
redaction_version = #{redactionVersion}
|
||||||
|
WHERE redaction_version < #{redactionVersion}
|
||||||
|
AND id IN
|
||||||
|
<foreach collection="snapshots" item="snapshot" open="(" separator="," close=")">
|
||||||
|
#{snapshot.id}
|
||||||
|
</foreach>
|
||||||
|
</update>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -21,8 +21,19 @@
|
|||||||
WHERE mac_address = #{macAddress} AND target_mac = #{targetMac}
|
WHERE mac_address = #{macAddress} AND target_mac = #{targetMac}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<insert id="insert">
|
<insert id="insertAddressBook">
|
||||||
INSERT INTO ai_device_address_book (mac_address, target_mac, alias, has_permission, creator, create_date, updater, update_date)
|
INSERT INTO ai_device_address_book (mac_address, target_mac, alias, has_permission, creator, create_date, updater, update_date)
|
||||||
VALUES (#{macAddress}, #{targetMac}, #{alias}, #{hasPermission}, #{creator}, NOW(), #{updater}, NOW())
|
VALUES (#{macAddress}, #{targetMac}, #{alias}, #{hasPermission}, #{creator}, NOW(), #{updater}, NOW())
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
<delete id="deleteByMacAddresses">
|
||||||
|
DELETE FROM ai_device_address_book WHERE mac_address IN
|
||||||
|
<foreach collection="macAddresses" item="mac" open="(" separator="," close=")">
|
||||||
|
#{mac}
|
||||||
|
</foreach>
|
||||||
|
OR target_mac IN
|
||||||
|
<foreach collection="macAddresses" item="mac" open="(" separator="," close=")">
|
||||||
|
#{mac}
|
||||||
|
</foreach>
|
||||||
|
</delete>
|
||||||
</mapper>
|
</mapper>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user