diff --git a/.gitignore b/.gitignore index 9469baac..b43a8de3 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,4 @@ main/xiaozhi-server/mysql uploadfile *.json .vscode +.cursor diff --git a/README.md b/README.md index 7a150e91..b018d5b6 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,11 @@ + + + MCP接入点 + + @@ -170,8 +175,8 @@ #### 🚀 部署方式选择 | 部署方式 | 特点 | 适用场景 | 部署文档 | 配置要求 | 视频教程 | |---------|------|---------|---------|---------|---------| -| **最简化安装** | 智能对话、IOT、MCP、视觉感知,数据存储在配置文件 | 低配置环境,无需数据库 | [①Docker版](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②源码部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)| 如果使用`FunASR`要2核4G,如果全API,要2核2G | - | -| **全模块安装** | 智能对话、IOT、MCP、视觉感知、OTA、智控台,数据存储在数据库 | 完整功能体验 |[①Docker版](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [②源码部署](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [③源码部署自动更新教程](./docs/dev-ops-integration.md) | 如果使用`FunASR`要4核8G,如果全API,要2核4G| [本地源码启动视频教程](https://www.bilibili.com/video/BV1wBJhz4Ewe) | +| **最简化安装** | 智能对话、IOT、MCP、视觉感知 | 低配置环境,数据存储在配置文件,无需数据库 | [①Docker版](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②源码部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)| 如果使用`FunASR`要2核4G,如果全API,要2核2G | - | +| **全模块安装** | 智能对话、IOT、MCP接入点、视觉感知、OTA、智控台 | 完整功能体验,数据存储在数据库 |[①Docker版](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [②源码部署](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [③源码部署自动更新教程](./docs/dev-ops-integration.md) | 如果使用`FunASR`要4核8G,如果全API,要2核4G| [本地源码启动视频教程](https://www.bilibili.com/video/BV1wBJhz4Ewe) | > 💡 提示:以下是按最新代码部署后的测试平台,有需要可烧录测试,并发为6个,每天会清空数据 @@ -226,7 +231,7 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/ | 视觉感知系统 | 支持多种VLLM(视觉大模型),实现多模态交互 | | 意图识别系统 | 支持LLM意图识别、Function Call函数调用,提供插件化意图处理机制 | | 记忆系统 | 支持本地短期记忆、mem0ai接口记忆,具备记忆总结功能 | -| IOT/MCP控制协议 | 支持设备注册管理、智能控制接口,同时支持IOT、MCP控制协议 | +| 工具调用 | 支持客户端IOT协议、客户MCP协议、服务端MCP协议、MCP接入点协议、自定义工具函数 | | 管理后台 | 提供Web管理界面,支持用户管理、系统配置和设备管理 | | 测试工具 | 提供性能测试工具、视觉模型测试工具和音频交互测试工具 | | 部署支持 | 支持Docker部署和本地部署,提供完整的配置文件管理 | diff --git a/docs/dev-ops-integration.md b/docs/dev-ops-integration.md index bb9640b1..3c547f4d 100644 --- a/docs/dev-ops-integration.md +++ b/docs/dev-ops-integration.md @@ -4,6 +4,8 @@ 本项目的测试平台`https://2662r3426b.vicp.fun`,从开放以来就使用了该方法,效果良好。 +教程可参考B站博主`毕乐labs`发布的视频教程:[《开源小智服务器xiaozhi-server自动更新以及最新版本MCP接入点配置保姆教程》](https://www.bilibili.com/video/BV15H37zHE7Q) + # 开始条件 - 你的电脑/服务器是linux操作系统 - 你已经跑通了整个流程 @@ -40,6 +42,9 @@ git clone https://ghproxy.net/https://github.com/xinnan-tech/xiaozhi-esp32-serve 此刻你需要把`model.pt`文件复制到新的目录去,你可以这样 ``` +# 创建需要的目录 +mkdir -p /home/system/xiaozhi/xiaozhi-esp32-server/main/xiaozhi-server/data/ + cp 你原来的.config.yaml完整路径 /home/system/xiaozhi/xiaozhi-esp32-server/main/xiaozhi-server/data/.config.yaml cp 你原来的model.pt完整路径 /home/system/xiaozhi/xiaozhi-esp32-server/main/xiaozhi-server/models/SenseVoiceSmall/model.pt ``` @@ -161,3 +166,11 @@ tail -f /home/system/xiaozhi/xiaozhi-esp32-server/main/xiaozhi-server/tmp/server # 注意事项 测试平台`https://2662r3426b.vicp.fun`,是使用nginx做了反向代理。nginx.conf详细配置可以[参考这里](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/791) + +## 常见问题 + +### 1、为什么没有见到8001端口? +回答:8001是开发环境使用的,用于运行前端的端口。如果你是服务器部署,不建议使用`npm run serve`启动8001端口运行前端,而是像本教程一样编译成html文件,然后使用nginx来管理访问。 + +### 2、每次更新需要更新手动SQL语句吗? +回答:不需要,因为项目使用**Liquibase**管理数据库版本,会自动执行新的sql脚本。 \ No newline at end of file diff --git a/docs/docker/nginx.conf b/docs/docker/nginx.conf index 9b9c3889..ca3bac3a 100644 --- a/docs/docker/nginx.conf +++ b/docs/docker/nginx.conf @@ -42,9 +42,9 @@ http { proxy_set_header Referer $http_referer; proxy_set_header Cookie $http_cookie; - proxy_connect_timeout 10; - proxy_send_timeout 10; - proxy_read_timeout 10; + proxy_connect_timeout 15; + proxy_send_timeout 15; + proxy_read_timeout 15; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/docs/docker/start.sh b/docs/docker/start.sh index bdaa1e1a..a64f46ba 100644 --- a/docs/docker/start.sh +++ b/docs/docker/start.sh @@ -6,6 +6,7 @@ java -jar /app/xiaozhi-esp32-api.jar \ --spring.datasource.druid.username=${SPRING_DATASOURCE_DRUID_USERNAME} \ --spring.datasource.druid.password=${SPRING_DATASOURCE_DRUID_PASSWORD} \ --spring.data.redis.host=${SPRING_DATA_REDIS_HOST} \ + --spring.data.redis.password=${SPRING_DATA_REDIS_PASSWORD} \ --spring.data.redis.port=${SPRING_DATA_REDIS_PORT} & # 启动Nginx(前台运行保持容器存活) diff --git a/docs/images/demo13.png b/docs/images/demo13.png new file mode 100644 index 00000000..1ad79fa3 Binary files /dev/null and b/docs/images/demo13.png differ diff --git a/docs/mcp-endpoint-enable.md b/docs/mcp-endpoint-enable.md new file mode 100644 index 00000000..7bc72484 --- /dev/null +++ b/docs/mcp-endpoint-enable.md @@ -0,0 +1,125 @@ +# MCP 接入点部署使用指南 + +本教程包含2个部分 +- 1、如何部署MCP接入点这个服务 +- 1、全模块部署时,怎么配置MCP接入点 +- 2、单模块部署时,怎么配置MCP接入点 + +# 1、如何部署MCP接入点这个服务 + +## 第一步,下载mcp接入点项目源码 + +浏览器打开[mcp接入点项目地址](https://github.com/xinnan-tech/mcp-endpoint-server) + +打开完,找到页面中一个绿色的按钮,写着`Code`的按钮,点开它,然后你就看到`Download ZIP`的按钮。 + +点击它,下载本项目源码压缩包。下载到你电脑后,解压它,此时它的名字可能叫`mcp-endpoint-server-main` +你需要把它重命名成`mcp-endpoint-server`。 + +## 第二步,启动程序 +这个项目是一个很简单的项目,建议使用docker运行。不过如果你不想使用docker运行,你可以参考[这个页面](https://github.com/xinnan-tech/mcp-endpoint-server/blob/main/README_dev.md)使用源码运行。以下是docker运行的方法 + +``` +# 进入本项目源码根目录 +cd mcp-endpoint-server + +# 清除缓存 +docker compose -f docker-compose.yml down +docker stop mcp-endpoint-server +docker rm mcp-endpoint-server +docker rmi ghcr.nju.edu.cn/xinnan-tech/mcp-endpoint-server:latest + +# 启动docker容器 +docker compose -f docker-compose.yml up -d +# 查看日志 +docker logs -f mcp-endpoint-server +``` + +此时,日志里会输出类似以下的日志 +``` +250705 INFO-=====下面的地址分别是智控台/单模块MCP接入点地址==== +250705 INFO-智控台MCP参数配置: http://172.22.0.2:8004/mcp_endpoint/health?key=abc +250705 INFO-单模块部署MCP接入点: ws://172.22.0.2:8004/mcp_endpoint/mcp/?token=def +250705 INFO-=====请根据具体部署选择使用,请勿泄露给任何人====== +``` + +请你把两个接口地址复制出来: + +由于你是docker部署,切不可直接使用上面的地址! + +由于你是docker部署,切不可直接使用上面的地址! + +由于你是docker部署,切不可直接使用上面的地址! + +你先把地址复制出来,放在一个草稿里,你要知道你的电脑的局域网ip是什么,例如我的电脑局域网ip是`192.168.1.25`,那么 +原来我的接口地址 +``` +智控台MCP参数配置: http://172.22.0.2:8004/mcp_endpoint/health?key=abc +单模块部署MCP接入点: ws://172.22.0.2:8004/mcp_endpoint/mcp/?token=def +``` +就要改成 +``` +智控台MCP参数配置: http://192.168.1.25:8004/mcp_endpoint/health?key=abc +单模块部署MCP接入点: ws://192.168.1.25:8004/mcp_endpoint/mcp/?token=def +``` + +改好后,请使用浏览器直接访问`智控台MCP参数配置`。当浏览器出现类似这样的代码,说明是成功了。 +``` +{"result":{"status":"success","connections":{"tool_connections":0,"robot_connections":0,"total_connections":0}},"error":null,"id":null,"jsonrpc":"2.0"} +``` + +请你保留好上面两个`接口地址`,下一步要用到。 + +# 2、全模块部署时,怎么配置MCP接入点 + +如果你是全模块部署,使用管理员账号,登录智控台,点击顶部`参数字典`,选择`参数管理`功能。 + +然后搜索参数`server.mcp_endpoint`,此时,它的值应该是`null`值。 +点击修改按钮,把上一步得来的`智控台MCP参数配置`粘贴到`参数值`里。然后保存。 + +如果能保存成功,说明一切顺利,你可以去智能体查看效果了。如果不成功,说明智控台无法访问mcp接入点,很大概率是网络防火墙,或者没有填写正确的局域网ip。 + +# 3、单模块部署时,怎么配置MCP接入点 + +如果你是单模块部署,找到你的配置文件`data/.config.yaml`。 +在配置文件搜索`mcp_endpoint`,如果没有找到,你就增加`mcp_endpoint`配置。类似我是就是这样 +``` +server: + websocket: ws://你的ip或者域名:端口号/xiaozhi/v1/ + http_port: 8002 +log: + log_level: INFO + +# 此处可能还更多配置.. + +mcp_endpoint: 你的接入点websocket地址 +``` +这时,请你把`如何部署MCP接入点这个服务`中得到的`单模块部署MCP接入点` 粘贴到 `mcp_endpoint`中。类似这样 + +``` +server: + websocket: ws://你的ip或者域名:端口号/xiaozhi/v1/ + http_port: 8002 +log: + log_level: INFO + +# 此处可能还更多配置 + +mcp_endpoint: ws://192.168.1.25:8004/mcp_endpoint/mcp/?token=def +``` + +配置好后,启动单模块会输出如下的日志。 +``` +250705[__main__]-INFO-初始化组件: vad成功 SileroVAD +250705[__main__]-INFO-初始化组件: asr成功 FunASRServer +250705[__main__]-INFO-OTA接口是 http://192.168.1.25:8002/xiaozhi/ota/ +250705[__main__]-INFO-视觉分析接口是 http://192.168.1.25:8002/mcp/vision/explain +250705[__main__]-INFO-mcp接入点是 ws://192.168.1.25:8004/mcp_endpoint/mcp/?token=abc +250705[__main__]-INFO-Websocket地址是 ws://192.168.1.25:8000/xiaozhi/v1/ +250705[__main__]-INFO-=======上面的地址是websocket协议地址,请勿用浏览器访问======= +250705[__main__]-INFO-如想测试websocket请用谷歌浏览器打开test目录下的test_page.html +250705[__main__]-INFO-============================================================= +``` + +如上,如果能输出类似的`mcp接入点是`中`ws://192.168.1.25:8004/mcp_endpoint/mcp/?token=abc`说明配置成功了。 + diff --git a/docs/mcp-endpoint-integration.md b/docs/mcp-endpoint-integration.md index ca72aef1..91d62278 100644 --- a/docs/mcp-endpoint-integration.md +++ b/docs/mcp-endpoint-integration.md @@ -1,92 +1,36 @@ -# MCP 接入点部署使用指南 +# MCP 接入点使用指南 -本教程包含2个部分 -- 1、如何开启mcp接入点 -- 2、如何为智能体接入一个简单的mcp功能,如计算器功能 +本教程以虾哥开源的mcp计算器功能为示例,介绍如何将自己自定义的mcp服务接入到自己的接入点里。 -部署的前提条件: -- 1、你已经部署了全模块,因为mcp接入点需要全模块中的智控台功能 -- 2、你想在不修改xiaozhi-server项目的前提下,扩展小智的功能 - -# 如何开启mcp接入点 - -## 第一步,下载mcp接入点项目源码 - -浏览器打开[mcp接入点项目地址](https://github.com/xinnan-tech/mcp-endpoint-server) - -打开完,找到页面中一个绿色的按钮,写着`Code`的按钮,点开它,然后你就看到`Download ZIP`的按钮。 - -点击它,下载本项目源码压缩包。下载到你电脑后,解压它,此时它的名字可能叫`mcp-endpoint-server-main` -你需要把它重命名成`mcp-endpoint-server`。 - -## 第二步,启动程序 -这个项目是一个很简单的项目,建议使用docker运行。不过如果你不想使用docker运行,你可以参考[这个页面](https://github.com/xinnan-tech/mcp-endpoint-server/blob/main/README_dev.md)使用源码运行。以下是docker运行的方法 - -``` -# 进入本项目源码根目录 -cd mcp-endpoint-server - -# 清除缓存 -docker compose -f docker-compose.yml down -docker stop mcp-endpoint-server -docker rm mcp-endpoint-server -docker rmi ghcr.nju.edu.cn/xinnan-tech/mcp-endpoint-server:latest - -# 启动docker容器 -docker compose -f docker-compose.yml up -d -# 查看日志 -docker logs -f mcp-endpoint-server -``` - -此时,日志里会输出类似以下的日志 -``` -====================================================== -接口地址: http://172.1.1.1:8004/mcp_endpoint/health?key=xxxx -=======上面的地址是MCP接入点地址,请勿泄露给任何人============ -``` - -请你把接口地址复制出来: - -由于你是docker部署,切不可直接使用上面的地址! - -由于你是docker部署,切不可直接使用上面的地址! - -由于你是docker部署,切不可直接使用上面的地址! - -你先把地址复制出来,放在一个草稿里,你要知道你的电脑的局域网ip是什么,例如我的电脑局域网ip是`192.168.1.25`,那么 -原来我的接口地址 -``` -http://172.1.1.1:8004/mcp_endpoint/health?key=xxxx -``` -就要改成 -``` -http://192.168.1.25:8004/mcp_endpoint/health?key=xxxx -``` - -改好后,请使用浏览器直接访问这个接口。当浏览器出现类似这样的代码,说明是成功了。 -``` -{"result":{"status":"success","connections":{"tool_connections":0,"robot_connections":0,"total_connections":0}},"error":null,"id":null,"jsonrpc":"2.0"} -``` - -请你保留好这个`接口地址`,下一步要用到。 - -## 第三步,配置智控台 - -使用管理员账号,登录智控台,点击顶部`参数字典`,选择`参数管理`功能。 - -然后搜索参数`server.mcp_endpoint`,此时,它的值应该是`null`值。 -点击修改按钮,把上一步得来的`接口地址`粘贴到`参数值`里。然后保存。 - -如果能保存成功,说明一切顺利,你可以去智能体查看效果了。如果不成功,说明智控台无法访问mcp接入点,很大概率是网络防火墙,或者没有填写正确的局域网ip。 +本教程的前提是,你的`xiaozhi-server`已经启用了mcp接入点功能,如果你还没启用,可以先根据[这个教程](./mcp-endpoint-enable.md)启用。 # 如何为智能体接入一个简单的mcp功能,如计算器功能 -如果以上步骤顺利,你可以进入智能体管理,点击`配置角色`,在`意图识别`的右边,有一个`编辑功能`的按钮。 +### 如果你是全模块部署 +如果你是全模块部署,你可以进入智控台,智能体管理,点击`配置角色`,在`意图识别`的右边,有一个`编辑功能`的按钮。 点击这个按钮。在弹出的页面里,位于底部,会有`MCP接入点`,正常来说,会显示这个智能体的`MCP接入点地址`,接下来,我们来给这个智能体扩展一个基于MCP技术的计算器的功能。 这个`MCP接入点地址`很重要,你等一下会用到。 +### 如果你是单模块部署 +如果你是单模块部署,且你已经在配置文件里配置了MCP接入点地址,那么正常来说,单模块部署启动的时候,会输出如下的日志。 +``` +250705[__main__]-INFO-初始化组件: vad成功 SileroVAD +250705[__main__]-INFO-初始化组件: asr成功 FunASRServer +250705[__main__]-INFO-OTA接口是 http://192.168.1.25:8002/xiaozhi/ota/ +250705[__main__]-INFO-视觉分析接口是 http://192.168.1.25:8002/mcp/vision/explain +250705[__main__]-INFO-mcp接入点是 ws://192.168.1.25:8004/mcp_endpoint/mcp/?token=abc +250705[__main__]-INFO-Websocket地址是 ws://192.168.1.25:8000/xiaozhi/v1/ +250705[__main__]-INFO-=======上面的地址是websocket协议地址,请勿用浏览器访问======= +250705[__main__]-INFO-如想测试websocket请用谷歌浏览器打开test目录下的test_page.html +250705[__main__]-INFO-============================================================= +``` + +如上,输出`mcp接入点是`中`ws://192.168.1.25:8004/mcp_endpoint/mcp/?token=abc`就是你的`MCP接入点地址`。 + +这个`MCP接入点地址`很重要,你等一下会用到。 + ## 第一步 下载虾哥MCP计算器项目代码 浏览器打开虾哥写的[计算器项目](https://github.com/78/mcp-calculator), @@ -114,13 +58,13 @@ pip install -r requirements.txt 例如我的智能体的mcp地址是 ``` -ws://192.168.4.7:8004/mcp_endpoint/mcp/?token=abc +ws://192.168.1.25:8004/mcp_endpoint/mcp/?token=abc ``` 开始输入命令 ```bash -export MCP_ENDPOINT=ws://192.168.4.7:8004/mcp_endpoint/mcp/?token=abc +export MCP_ENDPOINT=ws://192.168.1.25:8004/mcp_endpoint/mcp/?token=abc ``` 输入完后,启动程序 @@ -129,6 +73,22 @@ export MCP_ENDPOINT=ws://192.168.4.7:8004/mcp_endpoint/mcp/?token=abc python mcp_pipe.py calculator.py ``` +### 如果你是智控台部署 +如果你是智控台部署,启动完后,你再进入智控台,点击刷新MCP的接入状态,就会看到你扩展的功能列表了。 -启动完后,你再进入智控台,点击刷新MCP的接入状态,就会看到你扩展的功能列表了。 +### 如果你是单模块部署 +如果你是单模块部署,当设备连接后,会输出类似的日志,说明成功了 +``` +250705 -INFO-正在初始化MCP接入点: wss://2662r3426b.vicp.fun/mcp_e +250705 -INFO-发送MCP接入点初始化消息 +250705 -INFO-MCP接入点连接成功 +250705 -INFO-MCP接入点初始化成功 +250705 -INFO-统一工具处理器初始化完成 +250705 -INFO-MCP接入点服务器信息: name=Calculator, version=1.9.4 +250705 -INFO-MCP接入点支持的工具数量: 1 +250705 -INFO-所有MCP接入点工具已获取,客户端准备就绪 +250705 -INFO-工具缓存已刷新 +250705 -INFO-当前支持的函数列表: [ 'get_time', 'get_lunar', 'play_music', 'get_weather', 'handle_exit_intent', 'calculator'] +``` +如果包含了 `'calculator'`,说明设备将可以根据意图识别,调用计算器这个工具。 \ No newline at end of file diff --git a/docs/newsnow_plugin_config.md b/docs/newsnow_plugin_config.md new file mode 100644 index 00000000..8be887c2 --- /dev/null +++ b/docs/newsnow_plugin_config.md @@ -0,0 +1,105 @@ +# get_news_from_newsnow 插件新闻源配置指南 + +## 概述 + +`get_news_from_newsnow` 插件现在支持通过Web管理界面动态配置新闻源,不再需要修改代码。用户可以在智控台中为每个智能体配置不同的新闻源。 + +## 配置方式 + +### 1. 通过Web管理界面配置(推荐) + +1. 登录智控台 +2. 进入"角色配置"页面 +3. 选择要配置的智能体 +4. 点击"编辑功能"按钮 +5. 在右侧参数配置区域找到"newsnow新闻聚合"插件 +6. 在"新闻源配置"字段中输入分号分隔的中文名称 + +### 2. 配置文件方式 + +在 `config.yaml` 中配置: + +```yaml +plugins: + get_news_from_newsnow: + url: "https://newsnow.busiyi.world/api/s?id=" + news_sources: "澎湃新闻;百度热搜;财联社;微博;抖音" +``` + +## 新闻源配置格式 + +新闻源配置使用分号分隔的中文名称,格式为: + +``` +中文名称1;中文名称2;中文名称3 +``` + +### 配置示例 + +``` +澎湃新闻;百度热搜;财联社;微博;抖音;知乎;36氪 +``` + +## 支持的新闻源 + +插件支持以下新闻源的中文名称: + +- 澎湃新闻 +- 百度热搜 +- 财联社 +- 微博 +- 抖音 +- 知乎 +- 36氪 +- 华尔街见闻 +- IT之家 +- 今日头条 +- 虎扑 +- 哔哩哔哩 +- 快手 +- 雪球 +- 格隆汇 +- 法布财经 +- 金十数据 +- 牛客 +- 少数派 +- 稀土掘金 +- 凤凰网 +- 虫部落 +- 联合早报 +- 酷安 +- 远景论坛 +- 参考消息 +- 卫星通讯社 +- 百度贴吧 +- 靠谱新闻 +- 以及更多... + +## 默认配置 + +如果未配置新闻源,插件将使用以下默认配置: + +``` +澎湃新闻;百度热搜;财联社 +``` + +## 使用说明 + +1. **配置新闻源**:在Web界面或配置文件中设置新闻源的中文名称,用分号分隔 +2. **调用插件**:用户可以说"播报新闻"或"获取新闻" +3. **指定新闻源**:用户可以说"播报澎湃新闻"或"获取百度热搜" +4. **获取详情**:用户可以说"详细介绍这条新闻" + +## 工作原理 + +1. 插件接受中文名称作为参数(如"澎湃新闻") +2. 根据配置的新闻源列表,将中文名称转换为对应的英文ID(如"thepaper") +3. 使用英文ID调用API获取新闻数据 +4. 返回新闻内容给用户 + +## 注意事项 + +1. 配置的中文名称必须与 CHANNEL_MAP 中定义的名称完全一致 +2. 配置更改后需要重启服务或重新加载配置 +3. 如果配置的新闻源无效,插件会自动使用默认新闻源 +4. 多个新闻源之间使用英文分号(;)分隔,不要使用中文分号(;) \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java index 32a121fa..5be7b639 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java +++ b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java @@ -232,7 +232,7 @@ public interface Constant { /** * 版本号 */ - public static final String VERSION = "0.6.1"; + public static final String VERSION = "0.6.3"; /** * 无效固件URL diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java index 087c1b9d..36b59993 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java @@ -19,6 +19,8 @@ import xiaozhi.modules.agent.service.AgentChatAudioService; import xiaozhi.modules.agent.service.AgentChatHistoryService; import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService; +import xiaozhi.modules.device.entity.DeviceEntity; +import xiaozhi.modules.device.service.DeviceService; /** * {@link AgentChatHistoryBizService} impl @@ -35,6 +37,7 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic private final AgentChatHistoryService agentChatHistoryService; private final AgentChatAudioService agentChatAudioService; private final RedisUtils redisUtils; + private final DeviceService deviceService; /** * 处理聊天记录上报,包括文件上传和相关信息记录 @@ -68,6 +71,15 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic // 更新设备最后对话时间 redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), new Date()); + + // 更新设备最后连接时间 + DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress); + if (device != null) { + deviceService.updateDeviceConnectionInfo(agentId, device.getId(), null); + } else { + log.warn("聊天记录上报时,未找到mac地址为 {} 的设备", macAddress); + } + return Boolean.TRUE; } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java index e45d2201..839f99ff 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java @@ -5,6 +5,7 @@ import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -18,7 +19,6 @@ import xiaozhi.common.utils.AESUtils; import xiaozhi.common.utils.HashEncryptionUtil; import xiaozhi.common.utils.JsonUtils; import xiaozhi.modules.agent.dto.McpJsonRpcRequest; -import xiaozhi.modules.agent.dto.McpJsonRpcResponse; import xiaozhi.modules.agent.service.AgentMcpAccessPointService; import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.utils.WebSocketClientManager; @@ -61,53 +61,119 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic wsUrl = wsUrl.replace("/mcp/", "/call/"); try { - // 创建 WebSocket 连接 + // 创建 WebSocket 连接,增加超时时间到15秒 try (WebSocketClientManager client = WebSocketClientManager.build( new WebSocketClientManager.Builder() .uri(wsUrl) - .connectTimeout(5, TimeUnit.SECONDS) - .maxSessionDuration(20, TimeUnit.SECONDS))) { + .connectTimeout(8, TimeUnit.SECONDS) + .maxSessionDuration(10, TimeUnit.SECONDS))) { - // 发送初始化通知 - McpJsonRpcRequest initRequest = new McpJsonRpcRequest("notifications/initialized"); - client.sendJson(initRequest); + // 步骤1: 发送初始化消息并等待响应 + log.info("发送MCP初始化消息,智能体ID: {}", id); + McpJsonRpcRequest initializeRequest = new McpJsonRpcRequest("initialize", + Map.of( + "protocolVersion", "2024-11-05", + "capabilities", Map.of( + "roots", Map.of("listChanged", false), + "sampling", Map.of()), + "clientInfo", Map.of( + "name", "xz-mcp-broker", + "version", "0.0.1")), + 1); + client.sendJson(initializeRequest); - // 等待 0.2 秒 - Thread.sleep(200); - - // 发送工具列表请求 - McpJsonRpcRequest toolsRequest = new McpJsonRpcRequest("tools/list", null, 1); - client.sendJson(toolsRequest); - - // 监听响应,直到收到包含 id=1 的响应 - List responses = client.listener(response -> { + // 等待初始化响应 (id=1) - 移除固定延迟,改为响应驱动 + List initResponses = client.listenerWithoutClose(response -> { try { - McpJsonRpcResponse jsonResponse = JsonUtils.parseObject(response, McpJsonRpcResponse.class); - return jsonResponse != null && Integer.valueOf(1).equals(jsonResponse.getId()); + Map jsonMap = JsonUtils.parseObject(response, Map.class); + if (jsonMap != null && Integer.valueOf(1).equals(jsonMap.get("id"))) { + // 检查是否有result字段,表示初始化成功 + return jsonMap.containsKey("result") && !jsonMap.containsKey("error"); + } + return false; } catch (Exception e) { - log.warn("解析响应失败: {}", response, e); + log.warn("解析初始化响应失败: {}", response, e); return false; } }); - // 处理响应 - for (String response : responses) { + // 验证初始化响应 + boolean initSucceeded = false; + for (String response : initResponses) { try { - McpJsonRpcResponse jsonResponse = JsonUtils.parseObject(response, McpJsonRpcResponse.class); - if (jsonResponse != null && Integer.valueOf(1).equals(jsonResponse.getId()) - && jsonResponse.getResult() != null && jsonResponse.getResult().getTools() != null) { + Map jsonMap = JsonUtils.parseObject(response, Map.class); + if (jsonMap != null && Integer.valueOf(1).equals(jsonMap.get("id"))) { + if (jsonMap.containsKey("result")) { + log.info("MCP初始化成功,智能体ID: {}", id); + initSucceeded = true; + break; + } else if (jsonMap.containsKey("error")) { + log.error("MCP初始化失败,智能体ID: {}, 错误: {}", id, jsonMap.get("error")); + return List.of(); + } + } + } catch (Exception e) { + log.warn("处理初始化响应失败: {}", response, e); + } + } - // 提取工具名称列表 - return java.util.Arrays.stream(jsonResponse.getResult().getTools()) - .map(McpJsonRpcResponse.McpTool::getName) - .collect(Collectors.toList()); + if (!initSucceeded) { + log.error("未收到有效的MCP初始化响应,智能体ID: {}", id); + return List.of(); + } + + // 步骤2: 发送初始化完成通知 - 只有在收到initialize响应后才发送 + log.info("发送MCP初始化完成通知,智能体ID: {}", id); + String notificationJson = "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}"; + client.sendText(notificationJson); + + // 步骤3: 发送工具列表请求 - 立即发送,无需额外延迟 + log.info("发送MCP工具列表请求,智能体ID: {}", id); + McpJsonRpcRequest toolsRequest = new McpJsonRpcRequest("tools/list", null, 2); + client.sendJson(toolsRequest); + + // 等待工具列表响应 (id=2) + List toolsResponses = client.listener(response -> { + try { + Map jsonMap = JsonUtils.parseObject(response, Map.class); + return jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id")); + } catch (Exception e) { + log.warn("解析工具列表响应失败: {}", response, e); + return false; + } + }); + + // 处理工具列表响应 + for (String response : toolsResponses) { + try { + Map jsonMap = JsonUtils.parseObject(response, Map.class); + if (jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id"))) { + // 检查是否有result字段 + Object resultObj = jsonMap.get("result"); + if (resultObj instanceof Map) { + Map resultMap = (Map) resultObj; + Object toolsObj = resultMap.get("tools"); + if (toolsObj instanceof List) { + List> toolsList = (List>) toolsObj; + // 提取工具名称列表 + List result = toolsList.stream() + .map(tool -> (String) tool.get("name")) + .filter(name -> name != null) + .collect(Collectors.toList()); + log.info("成功获取MCP工具列表,智能体ID: {}, 工具数量: {}", id, result.size()); + return result; + } + } else if (jsonMap.containsKey("error")) { + log.error("获取工具列表失败,智能体ID: {}, 错误: {}", id, jsonMap.get("error")); + return List.of(); + } } } catch (Exception e) { log.warn("处理工具列表响应失败: {}", response, e); } } - log.warn("未找到有效的工具列表响应"); + log.warn("未找到有效的工具列表响应,智能体ID: {}", id); return List.of(); } @@ -177,4 +243,4 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic // 加密后成token值 return AESUtils.encrypt(key, json); } -} +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java index 34192dd8..9c591c35 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java @@ -72,6 +72,8 @@ public class ConfigServiceImpl implements ConfigService { null, null, null, + null, + null, agent.getVadModelId(), agent.getAsrModelId(), null, @@ -108,9 +110,13 @@ public class ConfigServiceImpl implements ConfigService { } // 获取音色信息 String voice = null; + String referenceAudio = null; + String referenceText = null; TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId()); if (timbre != null) { voice = timbre.getTtsVoice(); + referenceAudio = timbre.getReferenceAudio(); + referenceText = timbre.getReferenceText(); } // 构建返回数据 Map result = new HashMap<>(); @@ -163,6 +169,8 @@ public class ConfigServiceImpl implements ConfigService { agent.getSystemPrompt(), agent.getSummaryMemory(), voice, + referenceAudio, + referenceText, agent.getVadModelId(), agent.getAsrModelId(), agent.getLlmModelId(), @@ -179,7 +187,7 @@ public class ConfigServiceImpl implements ConfigService { /** * 构建配置信息 * - * @param paramsList 系统参数列表 + * @param config 系统参数列表 * @return 配置信息 */ private Object buildConfig(Map config) { @@ -250,21 +258,25 @@ public class ConfigServiceImpl implements ConfigService { /** * 构建模块配置 * - * @param prompt 提示词 - * @param voice 音色 - * @param vadModelId VAD模型ID - * @param asrModelId ASR模型ID - * @param llmModelId LLM模型ID - * @param ttsModelId TTS模型ID - * @param memModelId 记忆模型ID - * @param intentModelId 意图模型ID - * @param result 结果Map + * @param prompt 提示词 + * @param voice 音色 + * @param referenceAudio 参考音频路径 + * @param referenceText 参考文本 + * @param vadModelId VAD模型ID + * @param asrModelId ASR模型ID + * @param llmModelId LLM模型ID + * @param ttsModelId TTS模型ID + * @param memModelId 记忆模型ID + * @param intentModelId 意图模型ID + * @param result 结果Map */ private void buildModuleConfig( String assistantName, String prompt, String summaryMemory, String voice, + String referenceAudio, + String referenceText, String vadModelId, String asrModelId, String llmModelId, @@ -286,12 +298,20 @@ public class ConfigServiceImpl implements ConfigService { continue; } ModelConfigEntity model = modelConfigService.getModelById(modelIds[i], isCache); + if (model == null) { + continue; + } Map typeConfig = new HashMap<>(); if (model.getConfigJson() != null) { typeConfig.put(model.getId(), model.getConfigJson()); // 如果是TTS类型,添加private_voice属性 - if ("TTS".equals(modelTypes[i]) && voice != null) { - ((Map) model.getConfigJson()).put("private_voice", voice); + if ("TTS".equals(modelTypes[i])) { + if (voice != null) + ((Map) model.getConfigJson()).put("private_voice", voice); + if (referenceAudio != null) + ((Map) model.getConfigJson()).put("ref_audio", referenceAudio); + if (referenceText != null) + ((Map) model.getConfigJson()).put("ref_text", referenceText); } // 如果是Intent类型,且type=intent_llm,则给他添加附加模型 if ("Intent".equals(modelTypes[i])) { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java index eaf3636f..b0320e3c 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java @@ -25,6 +25,7 @@ import xiaozhi.common.utils.Result; import xiaozhi.modules.device.dto.DeviceRegisterDTO; import xiaozhi.modules.device.dto.DeviceUnBindDTO; import xiaozhi.modules.device.dto.DeviceUpdateDTO; +import xiaozhi.modules.device.dto.DeviceManualAddDTO; import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.security.user.SecurityUser; @@ -99,4 +100,13 @@ public class DeviceController { deviceService.updateById(entity); return new Result(); } + + @PostMapping("/manual-add") + @Operation(summary = "手动添加设备") + @RequiresPermissions("sys:role:normal") + public Result manualAddDevice(@RequestBody @Valid DeviceManualAddDTO dto) { + UserDetail user = SecurityUser.getUser(); + deviceService.manualAddDevice(user.getId(), dto); + return new Result<>(); + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java index 05d36e9a..023510cd 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java @@ -51,13 +51,12 @@ public class OTAController { if (StringUtils.isBlank(clientId)) { clientId = deviceId; } - String macAddress = deviceReportReqDTO.getMacAddress(); - boolean macAddressValid = isMacAddressValid(macAddress); + boolean macAddressValid = isMacAddressValid(deviceId); // 设备Id和Mac地址应是一致的, 并且必须需要application字段 - if (!deviceId.equals(macAddress) || !macAddressValid || deviceReportReqDTO.getApplication() == null) { - return createResponse(DeviceReportRespDTO.createError("Invalid OTA request")); + if (!macAddressValid) { + return createResponse(DeviceReportRespDTO.createError("Invalid device ID")); } - return createResponse(deviceService.checkDeviceActive(macAddress, clientId, deviceReportReqDTO)); + return createResponse(deviceService.checkDeviceActive(deviceId, clientId, deviceReportReqDTO)); } @Operation(summary = "设备快速检查激活状态") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceManualAddDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceManualAddDTO.java new file mode 100644 index 00000000..369726b0 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceManualAddDTO.java @@ -0,0 +1,11 @@ +package xiaozhi.modules.device.dto; + +import lombok.Data; + +@Data +public class DeviceManualAddDTO { + private String agentId; + private String board; // 设备型号 + private String appVersion; // 固件版本 + private String macAddress; // Mac地址 +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java index fb91ff89..3392c96f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java @@ -8,6 +8,7 @@ import xiaozhi.common.service.BaseService; import xiaozhi.modules.device.dto.DevicePageUserDTO; import xiaozhi.modules.device.dto.DeviceReportReqDTO; import xiaozhi.modules.device.dto.DeviceReportRespDTO; +import xiaozhi.modules.device.dto.DeviceManualAddDTO; import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.vo.UserShowDeviceListVO; @@ -87,5 +88,14 @@ public interface DeviceService extends BaseService { */ Date getLatestLastConnectionTime(String agentId); + /** + * 手动添加设备 + */ + void manualAddDevice(Long userId, DeviceManualAddDTO dto); + + /** + * 更新设备连接信息 + */ + void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion); } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java index ee31c41f..3cff111d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java @@ -44,6 +44,7 @@ import xiaozhi.modules.device.vo.UserShowDeviceListVO; import xiaozhi.modules.security.user.SecurityUser; import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.service.SysUserUtilService; +import xiaozhi.modules.device.dto.DeviceManualAddDTO; @Slf4j @Service @@ -410,4 +411,30 @@ public class DeviceServiceImpl extends BaseServiceImpl } return 0; } + + @Override + public void manualAddDevice(Long userId, DeviceManualAddDTO dto) { + // 检查mac是否已存在 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("mac_address", dto.getMacAddress()); + DeviceEntity exist = baseDao.selectOne(wrapper); + if (exist != null) { + throw new RenException("该Mac地址已存在"); + } + Date now = new Date(); + DeviceEntity entity = new DeviceEntity(); + entity.setId(dto.getMacAddress()); + entity.setUserId(userId); + entity.setAgentId(dto.getAgentId()); + entity.setBoard(dto.getBoard()); + entity.setAppVersion(dto.getAppVersion()); + entity.setMacAddress(dto.getMacAddress()); + entity.setCreateDate(now); + entity.setUpdateDate(now); + entity.setLastConnectedAt(now); + entity.setCreator(userId); + entity.setUpdater(userId); + entity.setAutoUpdate(1); + baseDao.insert(entity); + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java index edcc9328..9560e1a6 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java @@ -218,7 +218,7 @@ public class SysParamsController { if (response.getStatusCode() != HttpStatus.OK) { throw new RenException("MCP接口访问失败,状态码:" + response.getStatusCode()); } - // 检查响应内容是否包含OTA相关信息 + // 检查响应内容是否包含mcp相关信息 String body = response.getBody(); if (body == null || !body.contains("success")) { throw new RenException("MCP接口返回内容格式不正确,可能不是一个真实的MCP接口"); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java index 45ac488e..d7171ae7 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java @@ -137,6 +137,37 @@ public class WebSocketClientManager implements Closeable { return collected; } + private List listenerCustomWithoutClose( + BlockingQueue queue, + Predicate predicate) + throws InterruptedException, TimeoutException, ExecutionException { + List collected = new ArrayList<>(); + long deadline = System.currentTimeMillis() + maxSessionDurationUnit.toMillis(maxSessionDuration); + + while (true) { + if (errorFuture.isDone()) { + errorFuture.get(); + } + + long remaining = deadline - System.currentTimeMillis(); + if (remaining <= 0) { + throw new TimeoutException("等待批量消息超时"); + } + + T msg = queue.poll(remaining, TimeUnit.MILLISECONDS); + if (msg == null) { + throw new TimeoutException("等待批量消息超时"); + } + + collected.add(msg); + if (predicate.test(msg)) { + break; + } + } + // 不调用 close(),保持连接开放 + return collected; + } + /** * 同步接收多条消息,直到 predicate 为 true 或超时抛异常; * @@ -147,6 +178,17 @@ public class WebSocketClientManager implements Closeable { return listenerCustom(textMessageQueue, predicate); } + /** + * 同步接收多条消息,直到 predicate 为 true 或超时抛异常; + * 不自动关闭连接,适用于需要在同一连接上发送多个消息的场景 + * + * @return 返回监听期间的所有消息列表 + */ + public List listenerWithoutClose(Predicate predicate) + throws InterruptedException, TimeoutException, ExecutionException { + return listenerCustomWithoutClose(textMessageQueue, predicate); + } + public List listenerBinary(Predicate predicate) throws InterruptedException, TimeoutException, ExecutionException { return listenerCustom(binaryMessageQueue, predicate); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java index 030e2b4a..202c4eaf 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java @@ -26,6 +26,12 @@ public class TimbreDataDTO { @Schema(description = "备注") private String remark; + @Schema(description = "参考音频路径") + private String referenceAudio; + + @Schema(description = "參考文本") + private String referenceText; + @Schema(description = "排序") @Min(value = 0, message = "{sort.number}") private long sort; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java index 3d6e5ba1..508c3f32 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java @@ -34,6 +34,12 @@ public class TimbreEntity { @Schema(description = "备注") private String remark; + @Schema(description = "参考音频路径") + private String referenceAudio; + + @Schema(description = "參考文本") + private String referenceText; + @Schema(description = "排序") private long sort; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java index 3f0ace40..2cae1a53 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java @@ -25,6 +25,12 @@ public class TimbreDetailsVO implements Serializable { @Schema(description = "备注") private String remark; + @Schema(description = "参考音频路径") + private String referenceAudio; + + @Schema(description = "參考文本") + private String referenceText; + @Schema(description = "排序") private long sort; diff --git a/main/manager-api/src/main/resources/db/changelog/202506091720.sql b/main/manager-api/src/main/resources/db/changelog/202506091720.sql new file mode 100644 index 00000000..97b457ee --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202506091720.sql @@ -0,0 +1,3 @@ +ALTER TABLE `ai_tts_voice` +ADD COLUMN `reference_audio` VARCHAR(500) DEFAULT NULL COMMENT '参考音频路径' AFTER `remark`, +ADD COLUMN `reference_text` VARCHAR(500) DEFAULT NULL COMMENT '参考文本' AFTER `reference_audio`; diff --git a/main/manager-api/src/main/resources/db/changelog/202506251620.sql b/main/manager-api/src/main/resources/db/changelog/202506251620.sql new file mode 100644 index 00000000..9af9acd7 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202506251620.sql @@ -0,0 +1,18 @@ +-- 更新现有的 get_news_from_newsnow 插件配置 +UPDATE ai_model_provider +SET fields = JSON_ARRAY( + JSON_OBJECT( + 'key', 'url', + 'type', 'string', + 'label', '接口地址', + 'default', 'https://newsnow.busiyi.world/api/s?id=' + ), + JSON_OBJECT( + 'key', 'news_sources', + 'type', 'string', + 'label', '新闻源配置', + 'default', '澎湃新闻;百度热搜;财联社' + ) +) +WHERE provider_code = 'get_news_from_newsnow' +AND model_type = 'Plugin'; \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index 3852c545..ced8f47d 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -205,6 +205,13 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202506080955.sql + - changeSet: + id: 202506091720 + author: shane0411 + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202506091720.sql - changeSet: id: 202506161101 author: hrz @@ -219,6 +226,13 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202506191643.sql + - changeSet: + id: 202506251620 + author: Tink + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202506251620.sql - changeSet: id: 202506261637 author: hrz diff --git a/main/manager-api/src/main/resources/lua/emptyAll.lua b/main/manager-api/src/main/resources/lua/emptyAll.lua index 1d984cb7..2626a894 100644 --- a/main/manager-api/src/main/resources/lua/emptyAll.lua +++ b/main/manager-api/src/main/resources/lua/emptyAll.lua @@ -1 +1 @@ -redis.call('FLUSHALL') \ No newline at end of file +redis.call('FLUSHDB') \ No newline at end of file diff --git a/main/manager-web/src/apis/api.js b/main/manager-web/src/apis/api.js index 0a3e8d18..4d1d7d7d 100755 --- a/main/manager-web/src/apis/api.js +++ b/main/manager-web/src/apis/api.js @@ -7,6 +7,7 @@ import model from './module/model.js' import ota from './module/ota.js' import timbre from "./module/timbre.js" import user from './module/user.js' + /** * 接口地址 * 开发时自动读取使用.env.development文件 @@ -22,7 +23,6 @@ export function getServiceUrl() { return DEV_API_SERVICE } - /** request服务封装 */ export default { getServiceUrl, diff --git a/main/manager-web/src/apis/module/device.js b/main/manager-web/src/apis/module/device.js index e6d61353..233c764b 100644 --- a/main/manager-web/src/apis/module/device.js +++ b/main/manager-web/src/apis/module/device.js @@ -68,4 +68,21 @@ export default { }) }).send() }, + // 手动添加设备 + manualAddDevice(params, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/device/manual-add`) + .method('POST') + .data(params) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('手动添加设备失败:', err); + RequestService.reAjaxFun(() => { + this.manualAddDevice(params, callback); + }); + }).send(); + }, } \ No newline at end of file diff --git a/main/manager-web/src/apis/module/timbre.js b/main/manager-web/src/apis/module/timbre.js index 7aa6a711..3a149b1d 100644 --- a/main/manager-web/src/apis/module/timbre.js +++ b/main/manager-web/src/apis/module/timbre.js @@ -34,6 +34,8 @@ export default { languages: params.languageType, name: params.voiceName, remark: params.remark, + referenceAudio: params.referenceAudio, + referenceText: params.referenceText, sort: params.sort, ttsModelId: params.ttsModelId, ttsVoice: params.voiceCode, @@ -75,6 +77,8 @@ export default { languages: params.languageType, name: params.voiceName, remark: params.remark, + referenceAudio: params.referenceAudio, + referenceText: params.referenceText, ttsModelId: params.ttsModelId, ttsVoice: params.voiceCode, voiceDemo: params.voiceDemo || '' diff --git a/main/manager-web/src/components/FunctionDialog.vue b/main/manager-web/src/components/FunctionDialog.vue index 76d47005..76c69c8e 100644 --- a/main/manager-web/src/components/FunctionDialog.vue +++ b/main/manager-web/src/components/FunctionDialog.vue @@ -110,8 +110,10 @@
以下是智能体的MCP接入点地址。 + 如何部署MCP接入点   |   查看接入点使用文档 + target="_blank" class="doc-link">如何接入MCP功能  
@@ -660,6 +662,7 @@ export default { .mcp-left, .mcp-right { flex: 1; + padding-bottom: 50px; } .url-header { diff --git a/main/manager-web/src/components/ManualAddDeviceDialog.vue b/main/manager-web/src/components/ManualAddDeviceDialog.vue new file mode 100644 index 00000000..b6060ff5 --- /dev/null +++ b/main/manager-web/src/components/ManualAddDeviceDialog.vue @@ -0,0 +1,158 @@ + + + + + \ No newline at end of file diff --git a/main/manager-web/src/components/TtsModel.vue b/main/manager-web/src/components/TtsModel.vue index 87356523..84d2c883 100644 --- a/main/manager-web/src/components/TtsModel.vue +++ b/main/manager-web/src/components/TtsModel.vue @@ -1,79 +1,66 @@ \ No newline at end of file diff --git a/main/manager-web/src/views/DeviceManagement.vue b/main/manager-web/src/views/DeviceManagement.vue index 25cd5a68..35238371 100644 --- a/main/manager-web/src/views/DeviceManagement.vue +++ b/main/manager-web/src/views/DeviceManagement.vue @@ -77,7 +77,10 @@ {{ isAllSelected ? '取消全选' : '全选' }} - 新增 + 验证码绑定 + + + 手动添加 解绑 @@ -103,6 +106,8 @@ + @@ -110,13 +115,19 @@