Compare commits

...
735 Commits
Author SHA1 Message Date
欣南科技andGitHub d59a769084 Merge pull request #1728 from xinnan-tech/redis-password
update:允许在docker-compose文件里设置redis密码
2025-07-01 16:35:20 +08:00
hrz 7a8fe1074c update:允许在docker-compose文件里设置redis密码 2025-07-01 16:34:37 +08:00
欣南科技andGitHub 3fc256ed9a Merge pull request #1727 from xinnan-tech/mcp-update
update:支持中文名称的mcp工具
2025-07-01 16:16:03 +08:00
hrz 18eed4417c update:支持中文名称的mcp工具 2025-07-01 16:07:35 +08:00
欣南科技andGitHub 20adbadc02 Merge pull request #1718 from xinnan-tech/newsnow
可配置新闻来源
2025-06-30 17:13:13 +08:00
hrz c68384be69 update:区分本地化部署和非本地化部署tts的配置字段 2025-06-30 16:56:56 +08:00
hrz 7619979046 update:简化新闻源配置 2025-06-30 14:24:46 +08:00
hrzandGitHub 54ccae8665 Merge pull request #1518 from shane04111/add_local_tts_change_voice
update: 添加本地tts支援面板的"角色音色"
2025-06-30 10:48:15 +08:00
hrzandGitHub 3015b01740 Merge branch 'newsnow' into add_local_tts_change_voice 2025-06-30 10:48:06 +08:00
hrzandGitHub 43d47a4f09 Merge pull request #1552 from liammazy/main
增加timeout配置项,单位为秒
2025-06-30 10:46:50 +08:00
hrzandGitHub 96abc34a92 Merge branch 'newsnow' into main 2025-06-30 10:44:39 +08:00
hrzandGitHub 26553b8875 Update openai.py 2025-06-30 10:41:15 +08:00
hrzandGitHub 5e3187e36b Merge pull request #1673 from goxofy/main
update: newsnow 插件支持前端配置新闻源  && fix issues/1658
2025-06-30 10:10:28 +08:00
hrzandGitHub eba8c3122f Merge branch 'newsnow' into main 2025-06-30 10:10:19 +08:00
hrzandGitHub f1a3f39782 Merge pull request #1712 from ifhaveif/main
🐞 fix: 只清空当前数据库,不清空整个数据库
2025-06-30 10:04:01 +08:00
xiongyonghui 0880f78002 🐞 fix: 只清空当前数据库,不清空整个数据库
-- emptyAll.lua
1、当redis被多个项目使用时,清空了其它项目的库(影响其它项目)
2025-06-30 00:17:39 +08:00
欣南科技andGitHub 1c16d497f9 Merge pull request #1706 from xinnan-tech/update-doc
update:增加MCP接入点的视频demo
2025-06-28 20:36:44 +08:00
hrz faaae95197 update:增加MCP接入点的视频demo 2025-06-28 20:36:05 +08:00
欣南科技andGitHub d5e0e8ab40 Merge pull request #1704 from xinnan-tech/py_websoket_link
update:优化超时资源优化
2025-06-27 23:50:25 +08:00
hrz 500b0ebde3 update:去除无关日志 2025-06-27 23:47:17 +08:00
hrz 6f6c39da23 update:优化超时资源优化 2025-06-27 23:38:05 +08:00
CGDandGitHub f8ccc7a92f Merge pull request #1703 from xinnan-tech/py_websoket_link
fix: 退出过快问题
2025-06-27 17:26:38 +08:00
Sakura-RanChen 95a678bb18 fix: 退出过快问题 2025-06-27 16:39:53 +08:00
欣南科技andGitHub e43ce6ba3c Merge pull request #1700 from xinnan-tech/manager-api-mcp
update:优化接入点连接跳转方式
2025-06-27 15:31:09 +08:00
hrz 7663b0b969 update:优化接入点连接跳转方式 2025-06-27 15:30:18 +08:00
欣南科技andGitHub ba898b0874 Merge pull request #1699 from xinnan-tech/manager-api-mcp
update:优化mcp地址的校验
2025-06-27 15:16:58 +08:00
hrz 04843010bc update:优化mcp地址的校验 2025-06-27 15:14:41 +08:00
欣南科技andGitHub 41c06efeee Merge pull request #1698 from xinnan-tech/manager-api-mcp
Manager api mcp
2025-06-27 14:59:27 +08:00
hrz d0425fa31a update:优化设备端读取mcp接入点工具 2025-06-27 14:57:49 +08:00
hrz 08753b97df update:页面显示接入点功能 2025-06-27 11:33:12 +08:00
hrz a86fef5f28 update:智能体获取mcp接入点工具列表 2025-06-27 10:06:40 +08:00
hrzandGitHub c5c2f59b31 Merge pull request #1688 from xinnan-tech/opt_tools
fix: 处理config_functions的类型转换,避免初始化错误
2025-06-26 18:32:00 +08:00
hrz 69ee8ab438 添加mcp工具列表接口 2025-06-26 18:29:07 +08:00
CGD 4a24bc8c21 fix: 处理config_functions的类型转换,避免初始化错误 2025-06-26 17:37:51 +08:00
hrz 1d293244d3 update:智控台增加mcp接入点的配置 2025-06-26 16:54:26 +08:00
hrz 6812c5ac40 update:更改版本号 2025-06-26 16:34:30 +08:00
TinKandGitHub eeb5ed920a Merge pull request #10 from goxofy/manual-add-device
fix
2025-06-26 16:29:29 +08:00
hrzandGitHub 90ad3e019f Merge pull request #1680 from xinnan-tech/opt_tools
MCP接入点完成
2025-06-26 16:27:42 +08:00
Tink 3af453c2df fix 2025-06-26 16:26:45 +08:00
hrz 3071d2bdfa update:完成mcp接入点对接 2025-06-26 16:25:39 +08:00
TinKandGitHub 90aad4f131 Merge pull request #9 from goxofy/manual-add-device
fix
2025-06-26 16:24:55 +08:00
Tink f69b1b34f1 fix 2025-06-26 16:22:21 +08:00
TinKandGitHub ece2c4b49f Merge pull request #8 from goxofy/manual-add-device
fix
2025-06-26 16:07:57 +08:00
Tink 033c8f9bee fix 2025-06-26 16:06:36 +08:00
TinKandGitHub 1b9ca95a0e Merge pull request #7 from goxofy/manual-add-device
UpdateWrapper
2025-06-26 16:00:31 +08:00
Tink 7e86a9f8a9 UpdateWrapper 2025-06-26 15:54:26 +08:00
TinKandGitHub 9dee744498 Merge pull request #6 from goxofy/manual-add-device
fix last_connected_at
2025-06-26 15:35:51 +08:00
Tink 32fa71e8f1 fix last_connected_at 2025-06-26 15:24:57 +08:00
hrz ec4694f859 update:优化服务端mcp 2025-06-26 14:35:12 +08:00
TinKandGitHub 717386f10a Merge pull request #5 from goxofy/manual-add-device
fix id in ai_device
2025-06-26 14:14:07 +08:00
Tink fe071fe41a fix id in ai_device 2025-06-26 14:13:29 +08:00
TinKandGitHub 560a7d083b Merge pull request #4 from goxofy/manual-add-device
manualAddDevice
2025-06-26 13:41:46 +08:00
Tink 4428c1a298 manualAddDevice 2025-06-26 13:40:01 +08:00
TinKandGitHub 760fd2fdc5 Merge pull request #3 from goxofy/manual-add-device
手动添加设备增加后端接口
2025-06-26 13:37:06 +08:00
Tink 84c11a281a 手动添加设备增加后端接口 2025-06-26 13:36:11 +08:00
TinKandGitHub ad682ca0c4 Merge pull request #2 from goxofy/manual-add-device
fix RequestService
2025-06-26 12:27:46 +08:00
Tink 2a349ca9ef fix RequestService 2025-06-26 12:25:19 +08:00
hrz da8435cfea update:视觉结果优化 2025-06-26 11:42:13 +08:00
hrz 0f36daf1fd update:优化工具回复 2025-06-26 11:27:23 +08:00
TinKandGitHub 1f3c90e9e5 Merge pull request #1 from goxofy/manual-add-device
manual-add-device
2025-06-26 11:16:23 +08:00
Tink 6ec7a1fe09 manual-add-device 2025-06-26 11:09:17 +08:00
hrz 2f78acaf4d update:优化iot操作 2025-06-26 09:34:00 +08:00
hrz 9412a26bfc update:优化工具目录结构 2025-06-26 09:28:29 +08:00
hrz 2348d9ceb3 update:统一工具注册及调用 2025-06-25 18:27:08 +08:00
JianYu Zheng 61b4b6617b 优化:提取共用方法,实现部分getAgentMcpToolsList内容
--AgentMcpAccessPointServiceImpl.java
1.获取URI对象,统一2个方法获取URI对象的异常处理
2.实现getAgentMcpToolsList部分内容
2025-06-25 17:39:01 +08:00
JianYu Zheng a8620f21a0 优化:方法分割提取
--AgentMcpAccessPointServiceImpl.java 方法分割提取共用
1.获取密钥方法
2.获取智能体mcp接入点url部分
3.获取对智能体id加密的token
2025-06-25 17:17:26 +08:00
Tink 78dc266eee change newsnow plugin doc 2025-06-25 16:49:54 +08:00
JianYu Zheng 0631fe37ea 添加新接口:获取智能体的Mcp接入点地址(/agent/mcp/address/{audioId})
--AgentMcpAccessPointServiceImpl.java 实现获取智能体的mcp接入点地址定义
--AgentMcpAccessPointController.java 添加获取智能体的Mcp接入点地址接口
2025-06-25 16:37:04 +08:00
Tink 8828f8401d newsnow 插件支持前端配置新闻源 2025-06-25 16:32:49 +08:00
JianYu Zheng 21339aba42 添加智能体mcp接入点接口方法定义
--AgentMcpAccessPointService.java
1.获取智能体的mcp接入点地址定义
2.获取智能体的mcp接入点已有的工具列表定义
2025-06-25 16:16:39 +08:00
JianYu Zheng 660231978f 添加一个哈希加密的工具类
--HashEncryptionUtil.java
1.指定哈希算法进行加密方法
2.使用md5进行加密
2025-06-25 16:15:06 +08:00
JianYu Zheng dde203b158 添加了一个新的系统参数,mcp接入点参数
--Constant.java mcp接入点参数的key常量
--SysParamsServiceImpl.java 添加了mcp参数验证方法,修改是进行验证
2025-06-25 11:21:41 +08:00
JianYu Zheng 0ee69fa2b2 添加了http发送工具类
--HttpSendUtils.java
添加了发送get请求,获取返回的body转换成字符串 方法
发送post请求,参数为json格式。取返回的body转换成字符串 方法
2025-06-25 11:06:36 +08:00
hrzandGitHub 8c1a8f7d55 Merge pull request #1663 from xinnan-tech/py_iot_fix
update:iot设备多指令适配
2025-06-24 16:52:10 +08:00
欣南科技andGitHub ef03b665c9 Merge pull request #1666 from xinnan-tech/aes_utils
update:新增AES加密方法,和python端AES一致
2025-06-24 16:48:56 +08:00
hrz b87906234d update:新增AES加密方法,和python端AES一致 2025-06-24 16:26:14 +08:00
CGD 896b318c49 update:iot设备多指令适配 2025-06-24 14:55:34 +08:00
CGDandGitHub b3d441c385 Merge pull request #1661 from xinnan-tech/web_fuction_MCP
update: mcp接入点页面实现
2025-06-24 14:41:12 +08:00
Sakura-RanChen 96de670bfe update: mcp接入点页面实现 2025-06-24 14:39:18 +08:00
欣南科技andGitHub bc2fc35cc9 Merge pull request #1642 from xinnan-tech/py_tts_listen
update:更新版本号
2025-06-20 17:42:53 +08:00
hrz 993d5395b2 update:更新版本号 2025-06-20 17:42:14 +08:00
欣南科技andGitHub 67f0b828ea Merge pull request #1641 from xinnan-tech/py_tts_listen
update:修复部分doubaoasr出现400错误的问题
2025-06-20 17:20:51 +08:00
hrz 879c1267b6 update:修复部分doubaoasr出现400错误的问题 2025-06-20 17:20:08 +08:00
hrzandGitHub 46c53e36a6 Merge pull request #1640 from xinnan-tech/py_tts_listen
fix: 反复打断任务没有被清除,vad四帧语音识别
2025-06-20 17:17:19 +08:00
hrz 09f6605cfe update:更新版本号 2025-06-20 17:17:09 +08:00
Sakura-RanChen 12cee4027a fix: 反复打断任务没有被清除,vad四帧语音识别 2025-06-20 16:25:08 +08:00
欣南科技andGitHub 979fea0d60 Merge pull request #1627 from xinnan-tech/update-remark
更新智控台两款意图识别的说明
2025-06-19 16:56:38 +08:00
hrz 94553c54bd 更新智控台两款意图识别的说明 2025-06-19 16:53:39 +08:00
hrzandGitHub 248db31c8b Merge pull request #1618 from xinnan-tech/py_bug_fix
update:添加MCP重连机制
2025-06-18 22:40:25 +08:00
hrzandGitHub 24bfa1ca15 Merge pull request #1615 from xinnan-tech/py_fix_type
fix: 豆包流式decode错误
2025-06-18 17:46:47 +08:00
欣南科技andGitHub 1e97a8febc Merge pull request #1620 from xinnan-tech/hot-fix
update:修复mcp返回json的bug
2025-06-18 17:28:51 +08:00
hrz 2742f2e1ff update:修复mcp返回json的bug 2025-06-18 17:28:21 +08:00
Sakura-RanChen 0a5ae70a7c 二次错误提醒 2025-06-18 16:32:58 +08:00
CGD 22d53bd36e update:添加MCP重连机制 2025-06-18 16:29:28 +08:00
Sakura-RanChen ebf68929ce update: 错误信息读取 2025-06-18 14:03:18 +08:00
Sakura-RanChen ef3b373211 fix: 豆包流式decode错误 2025-06-18 11:06:11 +08:00
hrzandGitHub 5012a51e1d Merge pull request #1609 from xinnan-tech/py_fix_type
Py fix type
2025-06-18 09:39:38 +08:00
Sakura-RanChen fb1f476a3c fix: 智控台切换默认视觉模型报错 2025-06-18 09:24:52 +08:00
Sakura-RanChen 1a31c8cd1d update: 视觉模块直接识别输出,不经过二次LLM 2025-06-17 16:13:24 +08:00
欣南科技andGitHub 3e491c7d79 Merge pull request #1602 from xinnan-tech/py_fix_bug
update:更新版本号
2025-06-17 00:11:52 +08:00
hrz 33345817d2 update:更新版本号 2025-06-17 00:10:55 +08:00
hrzandGitHub 82973a685a Merge pull request #1601 from xinnan-tech/py_fix_bug
fix:修复“设备使用iot协议,意图识别无法正确调用”的问题
2025-06-16 23:49:58 +08:00
hrz e1d245068c update:优化权限 2025-06-16 23:47:26 +08:00
hrzandGitHub b74517ffb0 Merge pull request #1591 from CaixyPromise/fix/comment-invalid
fix: 智控台备注功能无效
2025-06-16 23:34:24 +08:00
hrz 231ae8dfa6 update:修复iot不更新的bug 2025-06-16 23:16:40 +08:00
CGD 4ee9a47d41 fix:修复“设备使用iot协议,意图识别无法正确调用”的问题 2025-06-16 18:09:13 +08:00
Sakura-RanChen d8bf5cdedf fix: openAi schema不支持list 2025-06-16 16:26:49 +08:00
Sakura-RanChenandGitHub 755e0edd44 Merge pull request #1596 from xinnan-tech/py_fix_bug
fix:修复意图识别因缺少 home_assistant 配置导致部分功能错误将意图识别成“home_assistant ”的bug
2025-06-16 14:52:53 +08:00
CGD 178df82693 fix:修复意图识别因缺少 home_assistant 配置导致功能全部失效的 bug 2025-06-16 14:48:52 +08:00
Sakura-RanChenandGitHub a88c3b2032 Merge pull request #1594 from xinnan-tech/py_fix_bug
fix:修复数据库中两个字符串的排序规则(collation)不一致问题
2025-06-16 11:10:18 +08:00
CGD 513396b905 fix:修复数据库中两个字符串的排序规则(collation)不一致问题 2025-06-16 11:09:01 +08:00
caixypromise 9885d4758b fix: 修复设备管理无法提交备注信息的问题。
chore: 将设备自动更新状态接口重构为设备信息更新接口,以此实现备注、自动更新合并在一个接口内
2025-06-16 03:27:12 +08:00
欣南科技andGitHub f5583717d5 Merge pull request #1573 from xinnan-tech/update-doc
update:优化智控台配置HomeAssistant操作手册
2025-06-13 14:36:46 +08:00
hrz da3076e5f7 update:优化智控台配置HomeAssistant操作手册 2025-06-13 14:29:08 +08:00
欣南科技andGitHub 3b77e1ddf1 Merge pull request #1572 from xinnan-tech/hot-fix
update:优化报错信息
2025-06-13 13:48:45 +08:00
hrz 3763a615f0 update:优化报错信息 2025-06-13 13:22:17 +08:00
欣南科技andGitHub f90e07976e Merge pull request #1570 from xinnan-tech/hot-fix
update:优化视觉接口认证校验
2025-06-13 12:55:37 +08:00
hrz 45b7658634 update:优化视觉接口认证校验 2025-06-13 12:53:58 +08:00
欣南科技andGitHub 70323936e9 Merge pull request #1569 from xinnan-tech/update-doc
Update doc
2025-06-13 11:23:05 +08:00
hrz 44f64fa336 update:修改版本号 2025-06-13 11:21:53 +08:00
hrz 2bd8afcc89 update:优化文档内容 2025-06-13 11:21:00 +08:00
欣南科技andGitHub 5fdab2522c Merge pull request #1567 from xinnan-tech/agent-plugin
update:优化插件代码
2025-06-13 10:50:19 +08:00
hrzandGitHub ef7bd8bd19 Merge pull request #1559 from xinnan-tech/py_fix_mcp-type
修复MCP工具调用参数类型错误问题;"logger中vad和asr动态变化"的补充
2025-06-13 10:49:41 +08:00
hrzandGitHub 9aa76cad69 Merge pull request #1566 from xinnan-tech/py_bug_fix
fix:通过“当会话ID为None时,自动生成新的会话ID”,解决“首次从智控台连接后,无法播放音乐”的问题
2025-06-13 10:48:49 +08:00
hrz f51477bf18 update:优化插件代码 2025-06-13 10:47:47 +08:00
CGD 2a8ec27ba6 update:通过“当会话ID为None时,自动生成新的会话ID”,解决“首次从智控台连接后,无法播放音乐”的问题 2025-06-13 10:34:36 +08:00
欣南科技andGitHub 2ff7fbbe6f Merge pull request #1564 from xinnan-tech/agent-plugin
Agent plugin
2025-06-13 09:48:38 +08:00
hrz 8a2fe7c20e update:设备列表优化 2025-06-13 09:24:13 +08:00
hrz 0fc8f02c6e update:优化空密钥的提示提示方式 2025-06-12 23:11:42 +08:00
hrz 3e8246ac41 update:优化表单样式 2025-06-12 23:07:42 +08:00
hrz 1adb2fe0f6 清除无效变量 2025-06-12 22:00:23 +08:00
hrz 9c3e1a6ff6 update:优化ha工具获取密钥的方式 2025-06-12 21:43:30 +08:00
hrz e943b07344 update:创建智能体设置默认的插件 2025-06-12 16:52:56 +08:00
shane0411 0ca1a6286c Merge remote-tracking branch 'upstream/main' into add_local_tts_change_voice 2025-06-12 12:36:13 +08:00
hrz 535c088404 Merge branch 'agent-plugin' of https://github.com/xinnan-tech/xiaozhi-esp32-server into agent-plugin 2025-06-12 11:12:12 +08:00
hrz f615621ffe update:不限时默认配置 2025-06-12 11:11:43 +08:00
hrzandGitHub f3d0df6772 Merge pull request #1556 from jlau-ice/fix/prevent-dialog-close-on-outside-click
fix: 防止在单击外部未保存的更改时关闭对话框
2025-06-12 11:03:24 +08:00
CGD 3b34d687a6 "fix: 修复MCP工具调用参数类型错误问题,无参调用时提供默认空字典" 2025-06-12 10:52:07 +08:00
CGD d3ba8d4385 uodate:"logger中vad和asr动态变化"的补充 2025-06-12 10:41:14 +08:00
CGDandGitHub 25ccf7fed3 Merge pull request #1557 from xinnan-tech/py_fix_logger
fix:  logger中vad和asr动态变化
2025-06-12 10:16:06 +08:00
Sakura-RanChen 6139c51b60 fix: logger中vad和asr动态变化 2025-06-12 09:54:07 +08:00
ganbin 77ded65157 fix: 防止在单击外部未保存的更改时关闭对话框 2025-06-12 09:03:48 +08:00
hrz 1836284223 Merge branch 'main' into agent-plugin 2025-06-11 23:07:33 +08:00
hrz c43afc6b39 update:优化页面显示 2025-06-11 23:05:38 +08:00
hrzandGitHub 4b0902017b Merge pull request #1554 from xinnan-tech/py_fix_mcp
fix:MCP 工具的支持能力,并优化意图识
2025-06-11 22:42:34 +08:00
hrz b2d26b9481 update:优化插件显示 2025-06-11 22:09:21 +08:00
CGD 68dbc91d54 fix:MCP 工具的支持能力,并优化意图识 2025-06-11 22:02:57 +08:00
shane0411 a4aea694d4 Merge remote-tracking branch 'upstream/main' into add_local_tts_change_voice 2025-06-11 14:29:35 +08:00
Liam Mazy 0f31bfc78f 增加timeout配置项,单位为秒 2025-06-11 14:05:54 +08:00
hrz 80554ecff4 Merge branch 'main' into agent-plugin 2025-06-11 09:32:56 +08:00
欣南科技andGitHub 0e57aad12d Merge pull request #1546 from xinnan-tech/hot-fix
修复创建智能体的默认视觉模型
2025-06-10 23:03:22 +08:00
hrz 742db377f6 修复创建智能体的默认视觉模型 2025-06-10 23:01:31 +08:00
欣南科技andGitHub a0250c8191 Merge pull request #1543 from xinnan-tech/hot-fix
fix:#1538
2025-06-10 22:20:27 +08:00
hrz 65b2656112 fix: #1538 2025-06-10 22:15:46 +08:00
hrz 1c4e5f0eed 修复 #1538 2025-06-10 22:12:39 +08:00
欣南科技andGitHub 224a06803f Merge pull request #1540 from xinnan-tech/py_wakeup_Unicode
update:优化“更新唤醒词回复配置”中的过滤表情字符
2025-06-10 17:24:05 +08:00
欣南科技andGitHub 8534a6d509 Merge pull request #1537 from bigbencat/fix-vllm-update
Fix vllm update:修复更新智能体时无法保存视觉模型的问题
2025-06-10 17:20:32 +08:00
hrzandGitHub f3dbb1f930 Merge branch 'main' into fix-vllm-update 2025-06-10 17:19:55 +08:00
CGD fedb6f2da2 update:优化“更新唤醒词回复配置”中的过滤表情字符 2025-06-10 16:38:21 +08:00
brucelee aa7303c707 fix(agent): 修复更新智能体时无法保存视觉模型的问题 2025-06-10 16:09:27 +08:00
Sakura-RanChenandGitHub 4b99ca5c5a Merge pull request #1534 from xinnan-tech/py_wakeup_Unicode_fix
解决GBK编码保存问题;音频格式修改
2025-06-10 14:55:34 +08:00
CGD d49462152b update:音频格式修改 2025-06-10 14:36:31 +08:00
CGD 33faeee75d update:通过过滤“更新唤醒词响应配置”时文本中的表情符号,解决GBK编码保存问题 2025-06-10 14:14:02 +08:00
欣南科技andGitHub 8cb15dcc7e Merge pull request #1530 from xinnan-tech/update-doc
update:纠正最简化部署的端口号是8003
2025-06-10 11:20:10 +08:00
hrz cef5813318 update:纠正最简化部署的端口号是8003 2025-06-10 11:19:30 +08:00
CGDandGitHub c43ee8025e Merge pull request #1529 from xinnan-tech/py_wakeup_text
Py wakeup text
2025-06-10 11:16:18 +08:00
shane0411 052d8c8962 feat: 添加音频路径音频文本的提示 2025-06-09 19:36:57 +08:00
shane0411 2eb5539c9b feat: 新增音频路径音频文本欄位,提供給本地tts選取音色
https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/1503#issuecomment-2954952028
2025-06-09 19:18:58 +08:00
shane0411 be6fba5963 feat: 添加本地tts支援面板的"角色音色" 2025-06-09 15:20:27 +08:00
Sakura-RanChen b273181ba6 隐藏滑动条 2025-06-09 15:17:37 +08:00
欣南科技andGitHub f5c3c51fe2 Merge pull request #1515 from xinnan-tech/hot-fix
update:优化带有方括号的字符语音转换
2025-06-09 12:36:24 +08:00
hrz 3e7c200a0b update:优化带有方括号的字符语音转换 2025-06-09 12:35:25 +08:00
hrz 152dbe9dec update:字段管理添加VLLM和Plugin选项 2025-06-09 11:39:24 +08:00
hrz 24b744b27e Merge branch 'main' into agent-plugin
# Conflicts:
#	main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml
2025-06-09 10:33:46 +08:00
欣南科技andGitHub 250afc0a5c Merge pull request #1511 from xinnan-tech/update-doc
Update doc
2025-06-08 19:33:29 +08:00
hrz c22a3ed092 迁移技术文档到main目录下 2025-06-08 19:32:50 +08:00
hrz 7a23ae4b84 update:更新技术文档 2025-06-08 19:31:47 +08:00
hrzandGitHub 201462d743 Merge pull request #1510 from xinnan-tech/py_HuoshanDoubleStreamTTS_markdown_fix
fix:使用“clear_markdown”方法修复“使用火山流式TTS中markdown 语法被 TTS 读出”的问题
2025-06-08 17:06:13 +08:00
CGD 3d1768100f fix:使用“clear_markdown”方法修复“使用火山流式TTS中markdown 语法被 TTS 读出”的问题 2025-06-08 15:40:18 +08:00
欣南科技andGitHub 3fbfbeaf0f Merge pull request #1508 from xinnan-tech/hot-fix
update:修复windows平台无法使用fcntl模块的问题
2025-06-08 00:21:27 +08:00
hrz 7163abbbe3 update:修复windows平台无法使用fcntl模块的问题 2025-06-09 00:20:49 +08:00
欣南科技andGitHub 423626a6bf Merge pull request #1504 from xinnan-tech/fix-mcp-name
Fix mcp name
2025-06-07 16:00:49 +08:00
hrz 182acc0787 update:替换描述里涉及到的原始方法名称 2025-06-08 15:51:57 +08:00
hrzandGitHub 705aef462c Merge pull request #1502 from whats2000/main
update: 修正 MCP tool naming 問題 (OpenAI SDK 命名規範)
2025-06-07 14:40:08 +08:00
whats2000andGitHub 1f092dd80e update: 修正 MCP tool naming 問題 (OpenAI SDK 命名規範)
Fix MCP tool naming for OpenAI
2025-06-07 13:48:10 +08:00
whats2000 4d0ddd7ff3 fix: sanitize MCP tool names for OpenAI 2025-06-07 13:46:35 +08:00
欣南科技andGitHub 2a212ae759 Merge pull request #1500 from xinnan-tech/fix-stream-mem
Fix stream mem
2025-06-07 11:59:45 +08:00
hrz 1d57e75d28 update:单流式tts聊天记录上报优化 2025-06-08 11:59:06 +08:00
hrz ac1e254eed update:智控台开启唤醒词加速 2025-06-08 11:50:09 +08:00
欣南科技andGitHub 6074431364 Merge pull request #1499 from xinnan-tech/py_Text
update:优化流式前帧发送
2025-06-07 02:34:18 +08:00
hrz d5fc4d48b4 update:优化流失前帧发送 2025-06-08 02:33:07 +08:00
hrz 61232e7dda update:优化空asr语音 2025-06-08 01:09:32 +08:00
hrz 594b4f1d75 add:增加psutil依赖 2025-06-08 00:21:30 +08:00
hrz 98174bcc16 update:【流式tts】增加【非流式】方法,用于测试及生成文件的场景 2025-06-08 00:06:40 +08:00
hrz 29d90df9ea Merge branch 'main' into py_Text
# Conflicts:
#	main/xiaozhi-server/core/handle/helloHandle.py
#	main/xiaozhi-server/core/providers/tts/base.py
2025-06-07 22:48:23 +08:00
hrz 50e6e4817b update:优化唤醒词答复 2025-06-07 22:08:22 +08:00
欣南科技andGitHub c6a88e41e1 Merge pull request #1498 from xinnan-tech/py_Text
update:优化空asr语音
2025-06-07 01:10:13 +08:00
欣南科技andGitHub ae1c41ba82 Merge pull request #1497 from xinnan-tech/py_Text
add:增加psutil依赖
2025-06-07 00:22:11 +08:00
欣南科技andGitHub 837bb74576 Merge pull request #1496 from xinnan-tech/py_Text
优化唤醒词答复速度
2025-06-07 00:12:21 +08:00
Sakura-RanChen a8f559b22c update: tts_one_sentence单句文本分段 2025-06-06 16:29:57 +08:00
brucelee 992eb89d1a 新增依赖文件 requirements_bruce.txt,包含多个库的版本信息以支持项目运行。 2025-06-06 15:39:25 +08:00
hrzandGitHub 1284418c18 Merge pull request #1481 from xinnan-tech/py_AEC_test
funasr添加内存检测判断;修改日志模块,日志轮转(单个文件10MB限制)
2025-06-06 15:02:46 +08:00
hrzandGitHub 3eb3a4502f Update fun_local.py 2025-06-06 14:08:38 +08:00
欣南科技andGitHub 6023af2f21 Merge pull request #1488 from xinnan-tech/fix-wakeup-bug
update:修复被唤醒词打断bug
2025-06-05 20:55:28 +08:00
hrz 2cd90d8066 update:修复被唤醒词打断bug 2025-06-05 20:54:53 +08:00
hrzandGitHub c1dfb540a3 Merge pull request #1486 from pursue-wind/main
fix: 使用deepseek r1-0528模型时,tools_call为空list,tools_call[0]报错
2025-06-05 17:07:59 +08:00
hrzandGitHub bc44ea8757 Update connection.py 2025-06-05 17:07:29 +08:00
Pursue WindandGitHub 77743ef8ba Merge branch 'xinnan-tech:main' into main 2025-06-05 16:34:39 +08:00
chan f8de052d54 fix: 使用deepseek r1-0528模型时,tools_call为空list,tools_call[0]报错 2025-06-05 16:33:47 +08:00
欣南科技andGitHub cb4eb21551 Merge pull request #1485 from xinnan-tech/py_linkerai_single
update:添加免费流式tts(灵犀流式)
2025-06-05 16:26:23 +08:00
hrz 8357d7abc3 update:添加免费流式tts(灵犀流式) 2025-06-05 16:25:40 +08:00
欣南科技andGitHub 5a01bfcdbf Merge pull request #1484 from xinnan-tech/py_linkerai_single
Py linkerai single
2025-06-05 16:13:21 +08:00
hrz 01eb416a03 update:智控台添加免费流式TTS(linkerai) 2025-06-05 16:02:16 +08:00
Sakura-RanChen dca02c1f4b update: 唤醒音频兼容 2025-06-05 14:11:50 +08:00
hrz 2654802bb0 update:流式播放 2025-06-05 11:09:09 +08:00
hrz d7eecfdcea update:优化代码 2025-06-05 10:49:25 +08:00
CGD 8df9846ad7 update:改为采用日志轮转机制,同时添加自动清理 2025-06-05 09:46:54 +08:00
hrz e43c0135a7 Merge branch 'main' into py_linkerai_single 2025-06-05 09:26:00 +08:00
hrz adf1a47945 update:优化配置说明 2025-06-05 09:25:30 +08:00
hrz 492e0b952e update:删除无效引用 2025-06-05 09:03:17 +08:00
hrzandGitHub ecc9d154d8 Merge pull request #1404 from CaixyPromise/main
feat: 智控台智能体级插件/工具调用改造-#1358
2025-06-04 23:52:54 +08:00
hrzandGitHub e776966ef9 Merge branch 'agent-plugin' into main 2025-06-04 23:52:33 +08:00
hrzandGitHub 98df2e5486 Update AgentServiceImpl.java 2025-06-04 23:50:02 +08:00
hrzandGitHub ca1beb956a Merge pull request #1458 from nodexiaoyu/add-technical-overview-doc
It sounds like you're looking for a detailed technical overview of th…
2025-06-04 23:38:37 +08:00
欣南科技andGitHub c694d3d6f1 Merge pull request #1478 from xinnan-tech/hot-fix
update:优化文档
2025-06-04 22:04:07 +08:00
hrz a314d03870 update:优化文档 2025-06-04 22:03:34 +08:00
欣南科技andGitHub f2fd3a0b7e Merge pull request #1476 from xinnan-tech/fix-manager-agent-vllm
修复智控台保存时保存不了视觉模型的bug
2025-06-04 21:49:53 +08:00
hrz 7b693527d9 update:优化文档 2025-06-04 21:49:31 +08:00
hrz 93e0d57783 修复智控台保存时保存不了视觉模型的bug 2025-06-04 21:22:43 +08:00
欣南科技andGitHub 18027d5d5c Merge pull request #1474 from xinnan-tech/asr-stream
update:更新sherpa_onnx版本
2025-06-04 21:04:16 +08:00
hrz 0ef514a68f update:更新sherpa_onnx版本 2025-06-04 21:03:31 +08:00
欣南科技andGitHub 4780f5e972 Merge pull request #1471 from xinnan-tech/asr-stream
update:豆包tts流式ASR空字符串问题
2025-06-04 18:34:37 +08:00
hrz 5b8a567f26 update:豆包tts流式ASR空字符串问题 2025-06-04 18:33:29 +08:00
Sakura-RanChen 23cb7616d9 update: 更改to_tts保存临时文件判断 2025-06-04 16:46:49 +08:00
欣南科技andGitHub 488522de6e Merge pull request #1469 from xinnan-tech/asr-stream
update:优化豆包流式ASR接口
2025-06-04 16:45:21 +08:00
hrz 03bcf5d2c7 update:优化豆包流式ASR接口 2025-06-04 16:43:15 +08:00
CGD fba758ea36 update:恢复最初的日志模块 2025-06-04 15:42:52 +08:00
CGD 0268f90e9c update:修改日志模块,使其按日期和大小分割的日志文件名 2025-06-04 14:46:52 +08:00
CGD 4cc7247c37 update:funasr实例之前添加内存检测判断 2025-06-04 14:35:21 +08:00
欣南科技andGitHub 5d94ed853d Merge pull request #1464 from xinnan-tech/asr-stream
update:ASR加入队列
2025-06-04 11:42:08 +08:00
hrz d06e297c2d update:ASR加入队列 2025-06-04 11:41:04 +08:00
欣南科技andGitHub 6304467d3a Merge pull request #1461 from xinnan-tech/vllm-qwen
add:增加千问收费视觉模型,速度更稳定一点
2025-06-03 23:30:14 +08:00
hrz e52601a584 添加视觉模型响应测试工具 2025-06-03 23:29:39 +08:00
hrz 74226581b7 add:增加千问收费视觉模型,速度更稳定一点 2025-06-03 23:09:27 +08:00
欣南科技andGitHub bc28504b3e Merge pull request #1459 from xinnan-tech/fix-doubao-asr
update:区分豆包ASR按次收费和按时收费接口
2025-06-03 17:33:10 +08:00
hrz 109811199d 更新:智控台区分豆包ASR按次收费和按时收费接口 2025-06-03 17:32:18 +08:00
hrz 610fa4d101 update:区分豆包ASR按次收费和按时收费接口 2025-06-03 17:30:35 +08:00
google-labs-jules[bot] 0269620e66 Sure, here is the rewritten message:
Add detailed technical overview documentation (Chinese)

This document provides a comprehensive technical overview of the xiaozhi-esp32-server project in Chinese, including:
- Overall architecture
- In-depth analysis of xiaozhi-server, manager-api, and manager-web components, covering their purpose, core technologies, and key implementation aspects.
- Data flow and interaction mechanisms.
- Summary of key features.
- Deployment and configuration overview.
2025-06-03 08:50:30 +00:00
google-labs-jules[bot] 10ebe975cf It sounds like you're looking for a detailed technical overview of the xiaozhi-esp32-server project. I can help you put together documentation that includes:
-   The overall architecture.
-   Deep dives into the xiaozhi-server, manager-api, and manager-web components, covering their purpose, core technologies, and key implementation aspects.
-   How data flows and how different parts of the system interact.
-   A summary of the key features.
-   An overview of how to deploy and configure it.

Let me know if this is what you had in mind!
2025-06-03 08:45:40 +00:00
Sakura-RanChen 0bc609cac4 优化实时编码 2025-06-03 11:20:54 +08:00
欣南科技andGitHub 3657f6ce75 Merge pull request #1453 from xinnan-tech/update_doc
update:补充剩余文档
2025-06-03 10:49:32 +08:00
hrz e62f72810c update:补充剩余文档 2025-06-03 10:48:36 +08:00
欣南科技andGitHub 21553410f9 Merge pull request #1452 from xinnan-tech/update_doc
udpate:更新文档
2025-06-03 10:28:13 +08:00
hrz a6bc910b0f udpate:更新文档 2025-06-03 10:27:13 +08:00
欣南科技andGitHub 3e68667323 Merge pull request #1450 from xinnan-tech/tts-huoshan-fix
Tts huoshan fix
2025-06-02 21:26:05 +08:00
hrz c0d4bbcecf 修复分布式部署时jwt密钥同步问题 2025-06-02 21:24:26 +08:00
hrz 6bf6159e6c update:更新hamcp文档 2025-06-02 21:15:07 +08:00
hrz fd4193daab fix:local variable 'response' referenced before assignment 2025-06-02 21:06:51 +08:00
欣南科技andGitHub da2077f3dd Merge pull request #1446 from xinnan-tech/tts-huoshan-fix
update:优化火山引擎双流TTS连接方式
2025-06-02 17:05:07 +08:00
hrz ee65032f7d update:优化火山引擎双流TTS连接方式 2025-06-02 17:03:53 +08:00
欣南科技andGitHub 2d7d75c290 Merge pull request #1438 from xinnan-tech/vllm
udpate:添加拍照识物教程
2025-06-01 16:29:27 +08:00
hrz 237e88ba89 udpate:添加拍照识物教程 2025-06-01 16:18:00 +08:00
欣南科技andGitHub 48223e4f39 Merge pull request #1435 from xinnan-tech/vllm
update:智控台,完成mcp拍照识图
2025-06-01 13:35:33 +08:00
hrz ce776f210c update:智控台,完成mcp拍照识图 2025-06-01 13:34:32 +08:00
欣南科技andGitHub fb5e25ec16 Merge pull request #1433 from xinnan-tech/vllm
update:单模块部署xiaozhi-server,支持mcp调用摄像头识图
2025-06-01 02:31:13 +08:00
hrz c4f2411fee update:单模块部署xiaozhi-server,支持mcp调用摄像头识图 2025-06-01 02:26:19 +08:00
欣南科技andGitHub 3c3be950e9 Merge pull request #1428 from xinnan-tech/hot-fix
update:修复参数错误
2025-05-31 10:14:50 +08:00
hrz b24ee0b9a6 update:修复参数错误 2025-05-31 10:14:23 +08:00
hrzandGitHub e1d5caa0fd Merge pull request #1426 from bitailab/main
SSEClient支持认证
2025-05-30 23:45:26 +08:00
lihaolong 35ba9b0e6b SSEClient支持认证 2025-05-30 11:08:32 +00:00
欣南科技andGitHub 66de07823d Merge pull request #1424 from xinnan-tech/update-doc
update:更新文档
2025-05-30 18:16:14 +08:00
hrz fd96e71d04 update:更新文档 2025-05-30 18:15:25 +08:00
欣南科技andGitHub 3c85242efc Merge pull request #1421 from xinnan-tech/test-mcp
Test mcp
2025-05-30 17:14:44 +08:00
hrz 3c288e1e59 update:优化客户端MCP服务 2025-05-30 17:13:54 +08:00
hrzandGitHub 4bf2f927b0 Merge pull request #1420 from xinnan-tech/py_test_mcp
Py test mcp
2025-05-30 16:09:06 +08:00
hrzandGitHub ad30030a1f Merge branch 'test-mcp' into py_test_mcp 2025-05-30 16:08:14 +08:00
hrzandGitHub 02d66e8093 Merge pull request #1414 from myifeng/dev
修复 has_tool错误;优化检索复杂度
2025-05-30 16:03:36 +08:00
欣南科技andGitHub 49eab95be9 Merge pull request #1419 from xinnan-tech/fix-server
update:双麦实时打断
2025-05-30 15:48:27 +08:00
hrz b1bfaf5a5c update:双麦实时打断 2025-05-30 15:47:32 +08:00
Sakura-RanChen 92affd6e13 待优化 2025-05-30 15:22:41 +08:00
myifeng 843e605352 handle_speaker_volume_or_screen_brightness 属于IOT功能函数,不作为必要函数自动注册 2025-05-30 14:33:29 +08:00
myifeng 6a7fa6060b 增加MCP协议测试功能 2025-05-30 14:26:52 +08:00
myifeng 43ce1df0ed 修复 has_tool错误;优化检索复杂度 2025-05-30 11:35:12 +08:00
myifengandGitHub a6deb3af8b Merge branch 'xinnan-tech:main' into dev 2025-05-30 10:27:23 +08:00
CGDandGitHub 9c01f5f0b0 Merge pull request #1411 from myifeng/dev
修正错误提交
2025-05-30 09:47:50 +08:00
欣南科技andGitHub 80b89a85ee Merge pull request #1412 from xinnan-tech/update-doc
update:优化文档
2025-05-30 09:39:05 +08:00
hrz 2fd0bb4912 update:优化文档 2025-05-30 09:37:13 +08:00
欣南科技andGitHub c0329618fe Merge pull request #1410 from xinnan-tech/update-doc
update:添加配置说明
2025-05-30 09:33:08 +08:00
hrz c4a240cb04 update:添加配置说明 2025-05-30 09:32:11 +08:00
myifeng 33d5761194 FIX 错误提交 2025-05-30 09:30:15 +08:00
myifeng a58ddd5880 为空判断处理; 2025-05-30 09:20:40 +08:00
CGDandGitHub 3bbb5f9b83 Merge pull request #1396 from myifeng/dev
监听MCP类型消息,等待后续适配
2025-05-30 09:11:57 +08:00
欣南科技andGitHub 3d6bf800f3 Merge pull request #1409 from xinnan-tech/asr_stream
豆包流式ASR
2025-05-30 02:17:36 +08:00
hrz a0b0a0e1e8 update:优化日志打印 2025-05-30 02:16:39 +08:00
hrz 8f48e9ac0c update:调通流式ASR 2025-05-30 02:05:34 +08:00
hrz bb42095ca0 update:兼容豆包流式ASR 2025-05-29 23:56:34 +08:00
myifeng c487f69676 FIX 为空判断 2025-05-29 17:55:01 +08:00
myifengandGitHub 3db46a84b2 Merge branch 'xinnan-tech:main' into dev 2025-05-29 16:13:13 +08:00
myifeng 69520391f2 参数为JSON 2025-05-29 15:41:18 +08:00
myifeng 4b212753fb 对接小智MCP协议控制 2025-05-29 13:42:31 +08:00
hrz d86a2cf9de update:抽离公共方法save_audio_to_file到父类 2025-05-29 10:38:01 +08:00
Sakura-RanChen 3130044909 test 2025-05-29 09:05:08 +08:00
caixypromise fac1c4db7b chore: deleted debug logs. 2025-05-29 01:46:37 +08:00
caixypromise ae1b90dfd7 chore: deleted debug logs. 2025-05-29 01:28:07 +08:00
caixypromise ec55de429c fix: fix the changeSet.id to commit datetime 2025-05-29 01:05:38 +08:00
caixypromise d62b957189 chore: commit to resolve commit conflict 2025-05-29 01:03:26 +08:00
caixypromise 599ce19ace feat: 智控台智能体级插件/工具调用改造。
新增支持从控制台控制大模型插件工具与配置插件工具的管理能力。
关联issue: issue(#1358)
2025-05-29 00:58:20 +08:00
欣南科技andGitHub 625d079168 Merge pull request #1403 from xinnan-tech/hot-fix
update:修复退出卡壳问题
2025-05-28 21:35:33 +08:00
hrz 2586843654 update:修复退出卡壳问题 2025-05-28 21:34:21 +08:00
myifeng 8ea48f9875 独立处理MCP Message 2025-05-28 19:28:36 +08:00
myifeng d7f3b3caf5 接收并打印MCP数据 2025-05-28 18:32:15 +08:00
欣南科技andGitHub b37d8abf62 Merge pull request #1400 from xinnan-tech/hot-fix
update:优化资源释放判断
2025-05-28 18:23:15 +08:00
hrz cd930edd06 update:优化资源释放判断 2025-05-28 18:22:31 +08:00
欣南科技andGitHub 4956aa6d70 Merge pull request #1399 from xinnan-tech/hot-fix
update:增强TTS流会话关闭和开启逻辑
2025-05-28 18:14:19 +08:00
hrz fd17465d94 update:增强TTS流会话关闭和开启逻辑 2025-05-28 18:07:58 +08:00
欣南科技andGitHub 87a9425241 Merge pull request #1398 from xinnan-tech/huoshan_double_stream
update:优化第一句话
2025-05-28 16:20:47 +08:00
hrz b225e8afd5 update:优化第一句话 2025-05-28 16:19:21 +08:00
hrzandGitHub 297b6e69d5 Merge pull request #1397 from xinnan-tech/huoshan_double_stream
火山双向流式tts优化
2025-05-28 15:52:58 +08:00
王华侨 d7f0e88801 火山双向流式tts优化 2025-05-28 15:47:49 +08:00
myifeng d62b27814c 打印cmp类型消息,后期适配 2025-05-28 14:09:13 +08:00
myifeng 19526878b3 接收hello消息中的features信息 2025-05-28 11:49:18 +08:00
欣南科技andGitHub 14ecad720f update:修复web测试工具Maximum call stack size exceeded 的bug (#1394) 2025-05-28 11:35:32 +08:00
hrzandGitHub 81689c04a5 Merge pull request #1393 from wenbindu/main
修改注释
2025-05-28 11:30:06 +08:00
hrzandGitHub de6b9de341 Update silero.py 2025-05-28 11:29:48 +08:00
hrz 7f864eb84d update:修复web测试工具Maximum call stack size exceeded 的bug 2025-05-28 11:23:39 +08:00
wenbindu 4af0c1e2ce 修改注释 2025-05-28 10:59:59 +08:00
欣南科技andGitHub 6a4ed78812 Merge pull request #1386 from xinnan-tech/py_server_log
update:日志配置动态获取
2025-05-28 01:14:40 +08:00
hrzandGitHub 23b881f364 Merge branch 'main' into py_server_log 2025-05-28 01:13:25 +08:00
hrz 22054cf8bc update:更新日志标识位置 2025-05-28 01:00:48 +08:00
欣南科技andGitHub fc236b1d96 Merge pull request #1383 from xinnan-tech/tts-response
TTS流式框架改造:支持豆包语音双流式+支持传统非流式
2025-05-27 23:34:11 +08:00
hrz 4f81454f93 update:更新sql脚本 2025-05-27 23:30:07 +08:00
hrz b3d6f173f1 update:智控台支持火山双流式配置 2025-05-27 23:29:11 +08:00
hrz 8d2ba39ab8 优化双流式TTS时的声音 2025-05-27 18:51:08 +08:00
hrz 4260e5a1a7 update:优化音频播放 2025-05-27 18:06:44 +08:00
Sakura-RanChen 17585c8294 update:日志配置动态获取 2025-05-27 16:17:13 +08:00
hrzandGitHub d242a9e5c0 Merge branch 'main' into tts-response 2025-05-27 15:36:42 +08:00
欣南科技andGitHub 9ca98f6391 Merge pull request #1351 from xinnan-tech/py_memory_llm
update: 记忆模块使用独立LLM openai增加超参
2025-05-27 15:29:22 +08:00
hrz be7ef08f40 update:智控台添加独立记忆模型配置 2025-05-27 15:28:17 +08:00
hrz 626692df29 update:优化阿里云tts数字参数读取方式 2025-05-27 13:47:07 +08:00
hrz 631787a4f1 update:这几天tts流式改造合并后,self.executor的主要任务将是用来上报聊天记录,因此这里其实可以合并在self.executor 2025-05-27 13:41:53 +08:00
hrzandGitHub 09cf6cdc2d Merge pull request #1372 from tiamohummer/main
增加阿里云TTS private_voice 判断
2025-05-27 13:14:41 +08:00
hrzandGitHub baf174b059 Merge pull request #1223 from GOODDAYDAY/feature/muti_upload
feat: 增加上报线程池
2025-05-27 13:06:55 +08:00
tiamohummerandGitHub 49eb3178a9 Merge branch 'xinnan-tech:main' into main 2025-05-27 09:08:34 +08:00
hrzandGitHub 6a6aceff1d Tts respone update (#1382)
* update:优化

* update:添加编码解码

* update:优化流式tts

* update:优化

* update:优化

* update:旧非流失兼容改造

* update:优化线程

* update:优化

* update:优化公共方法

* update:优化火山双流式tts

* update:优化代码

* update:修改版本号

* update:合并双流式
2025-05-26 22:31:39 +08:00
hrz b8349af3da update:合并双流式 2025-05-26 22:30:45 +08:00
goodyhao 258d783f8e Merge remote-tracking branch 'origin/main' into feature/muti_upload 2025-05-26 20:28:41 +08:00
goodyhao 761fc05331 feat: 增加上报时间 2025-05-26 20:27:22 +08:00
hrz 0c8e943d1b update:修改版本号 2025-05-26 16:18:35 +08:00
hrz 9787ca60da update:优化代码 2025-05-26 16:12:38 +08:00
Sakura-RanChenandGitHub b8e57aeff4 Merge pull request #1377 from xinnan-tech/py_test_page.html_fix
Py test page.html fix
2025-05-26 15:43:12 +08:00
CGD 9a0240ef3e update:优化了页面样式 2025-05-26 15:34:21 +08:00
tiamohummerandGitHub cf5ccafe7e Merge branch 'xinnan-tech:main' into main 2025-05-26 13:42:37 +08:00
hrz ae64233986 update:优化火山双流式tts 2025-05-26 12:48:43 +08:00
hrz 6dda79ee10 update:优化公共方法 2025-05-26 11:57:55 +08:00
CGD 335a855968 Merge branch 'py_test_page.html_fix' of https://github.com/xinnan-tech/xiaozhi-esp32-server into py_test_page.html_fix 2025-05-26 11:26:44 +08:00
CGD 94c92c5f38 fix:修复测试页面音频重复播放问题 2025-05-26 11:23:42 +08:00
CGDandGitHub 35fa1493b3 Merge pull request #1373 from Minamiyama/ENH/test-page-localstorage
refactor(test_page.html): 优化UI布局和输入提示,增加本地存储功能
2025-05-26 11:13:36 +08:00
hrz f78f6fc529 update:优化 2025-05-26 11:04:13 +08:00
hrz 5be65216e2 update:优化线程 2025-05-26 10:44:35 +08:00
hrz 24526ad206 update:旧非流失兼容改造 2025-05-26 02:20:38 +08:00
Minamiyama d7564a65f7 refactor(test_page.html): 优化UI布局和输入提示,增加本地存储功能
- 在OTA和WebSocket服务器地址输入框中添加更详细的占位符提示
- 增加本地存储功能,保存并恢复OTA和WebSocket服务器地址
2025-05-25 15:24:06 +08:00
goodyhao 866d61cfaf Merge branch 'refs/heads/main' into feature/muti_upload
# Conflicts:
#	main/xiaozhi-server/core/connection.py
2025-05-25 13:22:51 +08:00
欣南科技andGitHub 3f3f3fdaa6 Merge pull request #1371 from xinnan-tech/hot-fix
修复测试页面音频重复播放
2025-05-25 09:10:47 +08:00
hrz e0da59096a 修复测试页面音频重复播放 2025-05-25 09:10:11 +08:00
hrz 40632019ac update:优化 2025-05-25 08:56:58 +08:00
hrzandGitHub 3c5563b62f Merge pull request #1369 from xinnan-tech/py_test
fix:修复“小智服务器测试页面播放语音问题”
2025-05-25 08:46:57 +08:00
CGD 562424b74d fix:修复“小智服务器测试页面播放语音问题” 2025-05-25 00:09:36 +08:00
hrz 7a598d5839 update:优化 2025-05-24 23:43:16 +08:00
hrz 574d34bc2c update:优化流式tts 2025-05-24 17:50:03 +08:00
hrz 16a4ccdb12 update:添加编码解码 2025-05-24 14:52:27 +08:00
hrz 920cf4f897 update:优化 2025-05-24 12:11:13 +08:00
欣南科技andGitHub 76ee2c5365 Merge pull request #1366 from xinnan-tech/update-fix
合并chat和chat_with_function_calling
2025-05-24 09:40:28 +08:00
hrz 472106390d update:优化音频播放方法 2025-05-24 09:39:10 +08:00
hrz d97f8b2e9a update:供应器管理更名为字段管理,防止混淆模型管理 2025-05-24 02:17:49 +08:00
hrz 98e2526f8a update:合并chat和chat_with_function_calling 2025-05-24 02:15:06 +08:00
tiamohummer c30c4649a4 增加阿里云TTS private_voice 判断 2025-05-23 17:41:22 +08:00
欣南科技andGitHub ede8676979 Merge pull request #1363 from xinnan-tech/hot-fix
update:修复iotbug
2025-05-23 16:10:21 +08:00
hrz 17fb60b7ae update:修复iotbug 2025-05-23 16:03:15 +08:00
hrz 8119897818 Merge branch 'main' into tts-response
# Conflicts:
#	main/xiaozhi-server/core/handle/intentHandler.py
2025-05-23 09:15:15 +08:00
欣南科技andGitHub 0a9e5d2cea Merge pull request #1357 from xinnan-tech/update-docs
update:更新视频教程
2025-05-22 22:23:21 +08:00
hrz a5f74d7767 update:更新视频教程 2025-05-22 22:22:43 +08:00
Sakura-RanChen 02cb9c35b3 update: 记忆模块使用独立LLM openai增加超参 2025-05-22 17:14:58 +08:00
欣南科技andGitHub d934e0a69d Merge pull request #1348 from xinnan-tech/custom_tts_api
Custom tts api
2025-05-22 11:26:26 +08:00
hrz 2ecce6485c update:增加自定义的TTS接口服务的使用说明 2025-05-22 11:26:00 +08:00
hrz 1ad8aeeaa7 add:CustomTTS提供一个示例 2025-05-22 11:22:16 +08:00
hrz 46a15082d6 update:优化按钮 2025-05-22 10:41:56 +08:00
hrzandGitHub 153ecf0b41 Merge pull request #1253 from hsoftxl/custom_tts_api
自定义tts接口增加POST请求支持
2025-05-22 09:58:47 +08:00
hrzandGitHub f0f7304ae2 Merge branch 'custom_tts_api' into custom_tts_api 2025-05-22 09:58:38 +08:00
欣南科技andGitHub 997e171118 Merge pull request #1327 from xinnan-tech/web-pages-modifiy
增加fastgpt字段处理,搜索图标下移
2025-05-22 09:44:11 +08:00
欣南科技andGitHub 04a8fe8af2 Merge pull request #1342 from xinnan-tech/fix-global-iot
fix:修复全局变量all_function_registry导致iot描述无法更新bug
2025-05-22 09:43:13 +08:00
hrzandGitHub a0822a356c Merge pull request #1345 from CaixyPromise/chore/server-manager
chore: 服务端管理接口代码健壮性调整,补充注释与删除署名注释。
2025-05-22 09:01:28 +08:00
caixypromise b78d337fd0 chore: 服务端管理接口代码健壮性优化,补充注释与删除署名配置。 2025-05-22 00:49:56 +08:00
hrzandGitHub 13c5cb3f1d Merge pull request #1340 from xinnan-tech/py_symbol_fix
解决符号误识别问题,添加与退出相关的提示词
2025-05-21 22:12:52 +08:00
hrzandGitHub 197c0532fa Merge pull request #1341 from xinnan-tech/py_Recognizing-Intent_fix
update:修复intent_llm识别意图不精准问题,handle_device改名
2025-05-21 22:09:56 +08:00
hrz 76342b09ff fix:修复全局变量all_function_registry导致iot描述无法更新bug 2025-05-21 18:35:34 +08:00
CGD 241d96f6ac update:修复intent_llm识别意图不精准问题,handle_device改名 2025-05-21 18:17:08 +08:00
hrz fdc6dcb26e update:去掉无用代码 2025-05-21 16:25:22 +08:00
hrz 9fd010e91e update:调试 2025-05-21 15:59:24 +08:00
hrz c900498ce8 update:合并main分支 2025-05-21 15:55:40 +08:00
hrz c4c84e44e1 update:合并main分支 2025-05-21 14:52:24 +08:00
hrz 191ac47353 合并main分支 2025-05-21 14:13:52 +08:00
hrz 851365fb58 update:合并非tts代码 2025-05-21 13:18:12 +08:00
hrz ede2bc6a4e update:更新manager-api模块 2025-05-21 11:55:56 +08:00
hrz 38780b6daa update:忽略本地文件 2025-05-21 11:46:26 +08:00
欣南科技andGitHub 0dc267c109 Merge pull request #1337 from xinnan-tech/connect-server-and-api
智控台控制xiaozhi-server重启和更新配置
2025-05-21 11:01:43 +08:00
hrz bc6c85c6c6 update:增加ws连接时长,避免ws时间过短到只conn对象销毁丢失无法使用 2025-05-21 10:43:48 +08:00
Sakura-RanChen 915f4cac5e 对通用dict类型的字段进行处理 2025-05-21 09:55:15 +08:00
hrz b8da0714c2 update:优化无用组件 2025-05-20 22:58:42 +08:00
hrzandGitHub f1105e6822 Merge pull request #1331 from CaixyPromise/feature/connect-server-and-api
feat: 新增智控台管理websocket服务器端【配置拉取】和【重启】
2025-05-20 22:09:29 +08:00
hrzandGitHub 20296148eb Merge pull request #1332 from CaixyPromise/fix/dockerfile-arm
fix: 修复docker镜像不兼容arm64架构。
2025-05-20 22:01:52 +08:00
caixypromise 2138c0fb4f fix: 修复docker镜像不兼容arm开发板架构。
更新第三阶段运行状态的运行jdk镜像,切换包管理器为apk。
2025-05-20 20:13:41 +08:00
caixypromise 34d9857089 feat: 新增智控台管理websocket服务器端【配置拉取】和【重启】 2025-05-20 20:02:16 +08:00
欣南科技andGitHub 888b71a196 Merge pull request #1330 from xinnan-tech/hot-fix
update:优化ai_model_provider的fields字段
2025-05-20 18:05:10 +08:00
hrz 5f16c871dc update:优化ai_model_provider的fields字段 2025-05-20 18:04:33 +08:00
Sakura-RanChen 595a338de7 增加fastgpt字段处理,搜索图标下移 2025-05-20 16:57:48 +08:00
欣南科技andGitHub fe47fd1910 Merge pull request #1323 from xinnan-tech/hot-fix
fix:参数名称变更
2025-05-20 11:31:28 +08:00
hrz ac9abf8ede fix:参数名称变更 2025-05-20 11:30:57 +08:00
欣南科技andGitHub 282706dbc8 Merge pull request #1320 from xinnan-tech/manager-icp-num
可自定义智控台底部的系统名称、ICP备案号、公安备案号
2025-05-20 11:15:38 +08:00
hrz 5360cbf19c update:优化asr测速源:从已有的几个文件中测速度 2025-05-20 11:14:20 +08:00
hrz 84ac1b6686 update:可自定义智控台底部系统名称 2025-05-20 10:59:22 +08:00
CGD ebc16207f4 update:intent_llm添加与退出相关的提示词 2025-05-20 10:54:48 +08:00
欣南科技andGitHub 7611e98156 Merge pull request #1312 from xinnan-tech/manager-api-aly-message
修复短信并发漏洞
2025-05-19 21:23:49 +08:00
hrz 848f73715f update:完成密码找回功能 2025-05-19 21:19:24 +08:00
hrzandGitHub 6f30dfb963 Merge pull request #1315 from qjh9527/add-asr-performance
fix: stt中 test_count 计算逻辑错误
2025-05-19 20:32:03 +08:00
QJH c8aa903e3e fix: stt中 test_count 计算逻辑错误 2025-05-19 18:43:34 +08:00
剑雨 f0e55037de 剔除已经删除的类的引用和使用 2025-05-19 18:33:02 +08:00
hrzandGitHub 1335b368d1 Merge pull request #1314 from xinnan-tech/web-forget-password
增加找回密码的功能
2025-05-19 17:57:55 +08:00
CGD a647f3c105 fix:解决符号误识别问题 2025-05-19 15:18:01 +08:00
剑雨 c936372105 优化发送短信失败的情况,回退发送次数
--ALiYunSmsService.java 使用redis加1原子操作
--CaptchaServiceImpl.java 使用redis减1原子操作
2025-05-19 14:41:53 +08:00
Sakura-RanChen f242d43f73 增加找回密码的功能 2025-05-19 14:15:28 +08:00
hrzandGitHub c216012304 Merge pull request #1296 from qjh9527/add-asr-performance
Add ASR performance
2025-05-19 14:04:45 +08:00
hrzandGitHub 87c2f0e38e Merge pull request #1304 from 2331892928/main
ICP备案号添加
2025-05-19 13:51:15 +08:00
欣南科技andGitHub d9cc75edc0 Merge pull request #1307 from xinnan-tech/ali-msm-doc
增加阿里云短信集成使用说明
2025-05-19 11:44:27 +08:00
hrz 1f91ec32da update:优化说明 2025-05-19 11:43:28 +08:00
hrz 92b60b7f25 update:优化阿里云短信管理文档 2025-05-19 11:36:59 +08:00
hrz 8104d02a3a add:阿里云短信集群指南 2025-05-19 11:31:53 +08:00
剑雨 83c4add375 优化清空redis的key方法。添加加1,减1方法
--RedisUtils.java 新的方法
--emptyAll.lua 清空redis的key的lua脚本
2025-05-19 11:20:57 +08:00
剑雨 c19084b7b4 修复并发漏洞,获取和设置短信发送间隔时间,不是原子性的,并发请求过来会导致验证有漏洞被通过
--CaptchaServiceImpl.java 设置获取和设置发现间隔的方法为原子性
2025-05-19 10:51:39 +08:00
剑雨 2761e97c6a 新增获取在redis指定key的值,如果值为空,着设置key的默认值的方法
--RedisUtils.java 添加新方法
--getKeyOrCreate.lua 此方法的lua脚本
2025-05-19 10:46:59 +08:00
剑雨 0cdfd1d114 新建资源读取工具类
--ResourcesUtils.java 读取资源获取到字符串的方法
2025-05-19 10:41:02 +08:00
AMEN 8566e048b6 Merge remote-tracking branch 'origin/main' 2025-05-18 23:34:53 +08:00
AMEN 31fc731af6 前端
修改参数查询的input提示
增加公安备案号和网站备案号显示
2025-05-18 23:33:28 +08:00
AMEN ff95764824 后端
参数管理新增参数:
server.beian_icp_num
server.beian_ga_num
修改:
参数查询支持备注查询
2025-05-18 23:00:01 +08:00
欣南科技andGitHub 737c4a6b5d Merge pull request #1303 from xinnan-tech/hot-fix
修复:测试用例参数错误
2025-05-18 22:25:46 +08:00
hrz db68871646 修复:测试用例参数错误 2025-05-18 22:25:09 +08:00
剑雨 748f17b777 开启手机注册,验证相关参数不可以为空
--SysParamsServiceImpl.java 开启手机注册参数添加验证
2025-05-18 18:44:11 +08:00
欣南科技andGitHub bbba24c7bc Merge pull request #1301 from xinnan-tech/hot-fix
fix:登录失败错误信息显示
2025-05-18 17:59:37 +08:00
hrz 5bba864faa fix:登录失败错误信息显示 2025-05-18 17:59:08 +08:00
欣南科技andGitHub 33b7fe6072 Merge pull request #1300 from xinnan-tech/hot-fix
update:补充缺失参数
2025-05-18 17:55:53 +08:00
hrz 4d0bef62ed update:补充缺失参数 2025-05-18 17:55:18 +08:00
欣南科技andGitHub e09ba81cfa Merge pull request #1290 from xinnan-tech/manager-api-aly-message
阿里云短信发送功能,手机注册和找回密码接口
2025-05-18 17:42:30 +08:00
hrzandGitHub baf15450a3 Merge branch 'main' into manager-api-aly-message 2025-05-18 17:42:09 +08:00
hrz d24419cfb7 update:发送手机验证码 2025-05-18 17:39:12 +08:00
hrz fe88db2094 add:登录页面和注册页面增加手机号码选项 2025-05-18 17:38:20 +08:00
hrz 91d2ab7d1d update:优化 2025-05-18 11:09:58 +08:00
QJH 9a405a756f fix: "ASR", "LLM", "TTS"为空时的情况 2025-05-18 05:56:16 +08:00
QJH 86f5be23fe add: ASR测试任务 2025-05-18 05:48:31 +08:00
欣南科技andGitHub 9f4ca1aa6b Merge pull request #1294 from xinnan-tech/model-provider-service
Model provider service
2025-05-17 23:56:47 +08:00
hrz 624b1cce74 update:优化已存在的Group注解 2025-05-17 23:54:25 +08:00
hrz 29216768e1 update:优化模型供应器查询 2025-05-17 23:18:19 +08:00
hrz 59e18dc1c1 Merge branch 'main' into model-provider-service 2025-05-17 22:41:25 +08:00
欣南科技andGitHub 858a85f952 Merge pull request #1293 from xinnan-tech/hit-fix
update:修复fishspeech seed参数类型转换
2025-05-17 17:08:01 +08:00
hrz 1cbd68f1e3 update:修复fishspeech seed参数类型转换 2025-05-17 17:07:19 +08:00
欣南科技andGitHub 324bf62bb8 Merge pull request #1291 from xinnan-tech/hot-fix
update:恢复旧文件,解决已部署用户sql出错问题
2025-05-17 16:00:35 +08:00
hrz dd1d7d5dc1 update:回复旧文件,解决已部署用户sql出错问题 2025-05-17 15:58:53 +08:00
hrzandGitHub e6ad9e23cc Merge pull request #1288 from ljwwd2/main
供应器管理
2025-05-17 15:47:42 +08:00
hrzandGitHub a557322dc6 Merge pull request #1277 from 2331892928/main
用户管理页面的搜索功能增加模糊搜索
2025-05-17 15:36:20 +08:00
剑雨 7d7324ddbc 测试短信发送
--loginControllerTest.java 短信测试的测试类
2025-05-17 11:00:02 +08:00
剑雨 680ed4596f 测试,修复业务流程bug,添加新的错误代码和说明
--ErrorCode.java 新的错误代码
--LoginController.java 修复业务流程bug
--messages.properties 添加新的业务说明
2025-05-17 10:59:00 +08:00
剑雨 e66ab647e1 优化短信发送的异常处理
--ALiYunSmsService.java 优化异常处理
2025-05-17 10:57:02 +08:00
ljwwd2andGitHub 253f4c59a3 Merge branch 'xinnan-tech:main' into main 2025-05-17 01:19:12 +08:00
ljwwd2 38a97d6b5a 供应器管理-前端页面 2025-05-17 01:18:19 +08:00
ljwwd2 ae8f11d5a8 供应器管理-增删改查api 2025-05-17 01:18:03 +08:00
CGDandGitHub dcfd82f84e Merge pull request #1283 from xinnan-tech/py_text_audio
样式修改
2025-05-16 17:48:00 +08:00
TOM88812andGitHub 25a531341a Merge pull request #1281 from jwhna1/main
fix:修复智控台fish-speech无法按声音播放bug
2025-05-16 17:23:22 +08:00
曾能混andGitHub f421f6dee8 Merge branch 'xinnan-tech:main' into main 2025-05-16 17:16:44 +08:00
曾能混andGitHub a5332ccc7d Update 202504112044.sql
fix:修复智控台fish-speech无法按声音播放bug
2025-05-16 17:10:55 +08:00
Sakura-RanChen 76c3f401ea 样式修改 2025-05-16 14:55:53 +08:00
Sakura-RanChenandGitHub 5173dded2a Merge pull request #1280 from xinnan-tech/py_test_fix
update:在识别到continue_chat意图时,清理对话历史中的工具调用相关消息
2025-05-16 14:47:04 +08:00
CGD bdd8597f69 update:在识别到continue_chat意图时,清理对话历史中的工具调用相关消息 2025-05-16 14:33:37 +08:00
lvjinfa 46bba64a94 用户管理页面的搜索功能增加模糊搜索 2025-05-16 12:28:32 +08:00
XL d0694a0e70 兼容yaml配置和管理端配置 2025-05-16 11:56:54 +08:00
hsoftxlandGitHub bd12b0e81f Merge branch 'xinnan-tech:main' into custom_tts_api 2025-05-16 10:51:49 +08:00
TOM88812andGitHub 2f4fb0fc52 Merge pull request #1250 from jwhna1/main
Update ollama.py 为本地ollama部署qwen3大语言模型添加适配!
2025-05-16 10:51:17 +08:00
ljwwd2 cbabf01902 供应器管理-增删改查api 2025-05-16 03:04:52 +08:00
ljwwd2 c51c8b4af0 供应器管理-增删改查api 2025-05-16 01:42:19 +08:00
剑雨 2741e11078 新增发送短信验证码接口,找回密码接口,修改注册接口,如果开启手机注册模式,开启短信验证模式
--LoginController.java 添加和修改方法
--RetrievePasswordDTO.java 添加找回密码dto
2025-05-16 00:16:20 +08:00
剑雨 aa251aa011 添加校验单参数是否是正确的手机号的方法
--ValidatorUtils.java 添加新方法
2025-05-15 22:47:57 +08:00
剑雨 0509ddb129 验证模块添加发送短信验证码和验证短信验证码
--CaptchaService.java 新增发送短信验证码和验证短信验证码方法定义
--CaptchaServiceImpl.java 实现新定义定义的方法
--RedisKeys.java 添加短信验证码key
2025-05-15 21:45:33 +08:00
欣南科技andGitHub 5a180836b7 Merge pull request #1273 from xinnan-tech/fix-firmware-default-url
修复:固件要求firmware的url不能为空
2025-05-15 21:42:23 +08:00
hrz 9c7c6b9ea7 修复:固件要求firmware的url不能为空 2025-05-15 21:41:40 +08:00
欣南科技andGitHub d64eb0b087 Merge pull request #1268 from xinnan-tech/openrz-patch-2
docker-compose设置redis开机自启动
2025-05-15 17:41:54 +08:00
hrzandGitHub 28e4318f8a Update docker-compose_all.yml 2025-05-15 17:41:16 +08:00
欣南科技andGitHub 36f0821ea0 Merge pull request #1267 from xinnan-tech/openrz-patch-1
Update requirements.txt
2025-05-15 17:22:11 +08:00
hrzandGitHub 293a690ccd Update requirements.txt 2025-05-15 17:21:46 +08:00
欣南科技andGitHub 1c96ff7d3b Merge pull request #1266 from xinnan-tech/web-headerbar-modify
update:优化文档
2025-05-15 17:07:17 +08:00
hrz db8a2bb872 update:优化文档 2025-05-15 17:06:06 +08:00
hrz dfbe4fc7ee update:增加mcp-proxy依赖 2025-05-15 16:30:57 +08:00
欣南科技andGitHub 3cccc4ec64 Merge pull request #1265 from xinnan-tech/web-headerbar-modify
update:优化文档
2025-05-15 16:23:26 +08:00
hrz e29684e672 update:优化文档 2025-05-15 16:22:41 +08:00
欣南科技andGitHub 30796c6f81 Merge pull request #1262 from xinnan-tech/web-headerbar-modify
update:修复init_memorybug
2025-05-15 16:14:27 +08:00
hrz 86b5eb1818 update:修复init_memorybug 2025-05-15 16:13:32 +08:00
hrzandGitHub 696405a8bf Merge pull request #1256 from xinnan-tech/web-headerbar-modify
优化headerbar遮挡问题
2025-05-15 15:47:55 +08:00
Sakura-RanChen a2c2eab6b3 no message 2025-05-15 15:46:53 +08:00
XL eadb69bdc0 修改使用说明 2025-05-15 15:46:41 +08:00
XL e73223ca8a Merge remote-tracking branch 'origin/custom_tts_api' into custom_tts_api
# Conflicts:
#	main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml
2025-05-15 15:39:47 +08:00
XL 6dd66dbae9 自定义tts接口参数转dict
支持post请求
2025-05-15 15:39:06 +08:00
Sakura-RanChen ead9ee81fb fix:summaryMemory key错误 2025-05-15 15:39:05 +08:00
XL 073b69f94a 自定义tts接口参数转dict 2025-05-15 15:38:34 +08:00
XL 1a0a0777a3 自定义tts接口参数转dict
支持post请求
2025-05-15 15:35:45 +08:00
XL a971ae2168 自定义tts接口参数转dict 2025-05-15 10:23:40 +08:00
Sakura-RanChen bd140af714 优化headerbar遮挡问题 2025-05-15 09:54:59 +08:00
曾能混andGitHub 6b8d2502b7 Update ollama.py
为本地ollama部署qwen3大语言模型添加适配!在识别到启用为qwen3大模型的时候自动添加no_think停用推理模式,并过滤<think></think>标签转入给TTS(因为qwen3虽然用no_think停用推理后还是会输出<think></think>转入到TTS)
2025-05-15 06:32:47 +08:00
欣南科技andGitHub 96008fa78f Merge pull request #1249 from xinnan-tech/manager-local-mem
fix:short_momery可能为空的bug
2025-05-14 23:01:03 +08:00
hrz da4d0f515c fix:short_momery可能为空的bug 2025-05-14 22:59:55 +08:00
欣南科技andGitHub 67e6a22da9 Merge pull request #1248 from xinnan-tech/manager-local-mem
增加智控台管理【本地记忆】功能
2025-05-14 22:29:59 +08:00
hrz b791fb1494 update:优化总结内容 2025-05-14 22:28:36 +08:00
hrzandGitHub 9e329bf19f Merge pull request #1245 from CaixyPromise/chore/setting-ota-firmware-in-old-version
chore: 支持在老版本激活设备时,OTA返回有效的硬件信息。
2025-05-14 20:32:16 +08:00
hrzandGitHub 3906e8a942 Delete main/manager-web/pnpm-lock.yaml 2025-05-14 20:31:55 +08:00
hrzandGitHub f74faea065 Merge pull request #1243 from xinnan-tech/py_weather_fix
Py weather fix
2025-05-14 20:30:56 +08:00
hrzandGitHub 7d3759acce 统一无意义域名 2025-05-14 20:28:40 +08:00
caixypromise 172962f711 chore: 支持在老版本激活设备时,OTA返回有效的硬件信息。
如果设备未激活,当前OTA会返回一个空的的firmware响应体。但老版本(如1.4.7)会因为缺少firmware响应体无法走通OTA版本检查流程。老版本相关代码如下
```CPP
// ota.cc - version 1.4.7 -line 131
cJSON *firmware = cJSON_GetObjectItem(root, "firmware");
    if (firmware == NULL) { // firmware为空
        ESP_LOGE(TAG, "Failed to get firmware object");
        cJSON_Delete(root);
        return false;
    }
```
因此,在老版本激活设备时,OTA将直接返回的当前激活设备上传硬件的信息,以便老版本设备能够走通OTA版本检查流程。后续用户可以再通过智控台更新OTA版本。
2025-05-14 19:55:20 +08:00
Sakura-RanChen 36109e3b88 增加获取默认值,配置文件提示 2025-05-14 17:17:03 +08:00
Sakura-RanChen 7c63b7c062 no message 2025-05-14 16:50:15 +08:00
Sakura-RanChen 1efaf048f5 优化供应器编辑 2025-05-14 16:48:35 +08:00
Sakura-RanChen ac52714498 weather-fix 2025-05-14 16:30:53 +08:00
Sakura-RanChenandGitHub c3de89f504 Merge pull request #1233 from xinnan-tech/py_test
修复“A客户端断开连接执行"保存记忆并关闭连接"过程中,B客户端创建连接会被阻塞”问题
2025-05-14 14:57:41 +08:00
CGDandGitHub 6f6a645d0a Merge pull request #1238 from xinnan-tech/web-Supplier-Page
增加供应器管理页面,优化页面样式和数据校验
2025-05-14 14:54:33 +08:00
CGDandGitHub 5605352144 Merge branch 'main' into py_test 2025-05-14 14:52:57 +08:00
Sakura-RanChen ef433f446d 增加供应器管理页面,优化页面样式和数据校验 2025-05-14 14:43:36 +08:00
hrz fe8f200364 update:连接ota接口时,异步更新最新连接时间 2025-05-14 14:42:59 +08:00
剑雨 e4194cdf4d 添加使用阿里云平台发送验证码短信的方法
--pom.xml 添加短信发送sdk
--Constant.java 添加发送短信的参数枚举
--ALiYunSmsService.java 阿里云实现短信方法
--SmsService.java 短信方法定义
2025-05-14 14:18:01 +08:00
剑雨 c8392c9b47 添加发送短信参数,添加到数据库里
--202505141129.sql
--db.changelog-master.yaml
2025-05-14 12:22:13 +08:00
hrz be3a030302 update:优化记忆体布局 2025-05-14 12:09:29 +08:00
CGD cd6d4488d1 update:"使用线程池异步保存记忆" 2025-05-14 11:22:09 +08:00
hrzandGitHub da3a5df5c3 Merge pull request #1230 from ljwwd2/main
增加智控台管理【本地记忆】功能
2025-05-14 10:52:57 +08:00
欣南科技andGitHub 4864407d61 Merge pull request #1232 from xinnan-tech/test-exit
fix:修正warn为warning
2025-05-14 10:41:31 +08:00
hrz f6f7879d6e fix:修正warn为warning 2025-05-14 10:40:40 +08:00
hrzandGitHub dab1f14a59 Merge pull request #1228 from CaixyPromise/feature/custom-end-prompt
feature: 新增结束词prompt自定义能力。
2025-05-14 10:05:31 +08:00
hrzandGitHub 51ca914206 end_prompt默认为开启 2025-05-14 10:04:30 +08:00
ljwwd2 933ffb8ca2 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml
2025-05-14 03:31:31 +08:00
ljwwd2 2f5f20a257 增加智控台管理【本地记忆】功能 2025-05-14 03:27:32 +08:00
caixypromise 92544f89f3 feature: 新增结束词prompt自定义能力。
新增在python服务端在config配置内自定义结束词提示词(end_prompt),仅在enable为True时触发结束聊天,否则自动默认清理资源退出。

close(#963)
2025-05-14 00:12:31 +08:00
欣南科技andGitHub f1cf1acbfa Merge pull request #1226 from xinnan-tech/fix-fun-server
fix:修复智控台fish-speech无法按声音播放bug
2025-05-13 18:29:16 +08:00
hrz f22008389c fix:修复智控台fish-speech无法按声音播放bug 2025-05-13 18:28:27 +08:00
goodyhao 885b7a0b05 feat: 增加上报线程池 2025-05-13 15:54:00 +08:00
欣南科技andGitHub 91de372cbc Merge pull request #1222 from xinnan-tech/fix-fun-server
update:优化ASR_FunASRServer文档
2025-05-13 15:19:02 +08:00
hrz 811bd94934 update:优化ASR_FunASRServer文档 2025-05-13 15:18:23 +08:00
hrzandGitHub 5987488b3d Merge pull request #1199 from whosmyqueen/feature/add-fun-asr-auth
[feature] 添加FunServerAsr认证
2025-05-13 14:21:16 +08:00
hrzandGitHub 966c9e972d Merge branch 'main' into feature/add-fun-asr-auth 2025-05-13 14:21:06 +08:00
欣南科技andGitHub 464261a1b7 Merge pull request #1218 from xinnan-tech/hot-fix
update:优化未开启记忆的聊天记录按钮样式
2025-05-13 11:21:13 +08:00
hrz 363dcef81a update:优化未开启记忆的聊天记录按钮样式 2025-05-13 11:18:03 +08:00
GoodyHaoandGitHub 4439617c50 Merge pull request #1204 from GOODDAYDAY/main
feat: 优化上报命名含义,增加不同上报开关
2025-05-13 10:58:33 +08:00
欣南科技andGitHub 5d2fa3d65d Merge pull request #1215 from xinnan-tech/hot-fix
update:修复fishspeech的normalize获取值转换bug
2025-05-13 10:14:51 +08:00
hrz 8930ab0af6 update:修复fishspeech的normalize获取值转换bug 2025-05-13 10:14:15 +08:00
欣南科技andGitHub 6af6fe22fc Merge pull request #1211 from xinnan-tech/enhance-gemini-with-proxy
Enhance gemini with proxy
2025-05-13 08:50:31 +08:00
hrzandGitHub dd577a7843 Merge pull request #1208 from CaixyPromise/update/enhance-gemini-with-proxy
Update: 提升Gemini代理配置能力,支持function_call调用与stream流式对话能力
2025-05-12 21:54:24 +08:00
caixypromise f772155674 chore: 删除无用的包引入。 2025-05-12 21:39:13 +08:00
caixypromise 3ce204338a chore: 删除无用的包引入。 2025-05-12 21:38:23 +08:00
caixypromise 168bc9ef45 update: 改进gemini连通代理能力,自动测试与装配合适的代理配置信息。 2025-05-12 21:33:43 +08:00
caixypromise bfc3d4888b Merge remote-tracking branch 'upstream/main' into update/enhance-gemini-with-proxy 2025-05-12 21:29:42 +08:00
goodyhao 0148b115db Merge branch 'feature/upload-optimise'
# Conflicts:
#	main/xiaozhi-server/core/connection.py
#	main/xiaozhi-server/core/handle/reportHandle.py
2025-05-12 19:16:29 +08:00
goodyhao 0b11953f6f feat: 优化上报命名含义,增加不同上报开关 2025-05-12 19:14:04 +08:00
欣南科技andGitHub 6e210faacf Merge pull request #1206 from xinnan-tech/manager-plugin-api
聊天记录支持配置
2025-05-12 18:30:12 +08:00
hrz 4ef755e10b update:优化插件显示 2025-05-12 18:28:52 +08:00
hrz 62b7cc5509 update:更新版本号 2025-05-12 18:10:34 +08:00
hrz 0e1ec05110 Merge branch 'manager-plugin-api' of https://github.com/xinnan-tech/xiaozhi-esp32-server into manager-plugin-api 2025-05-12 18:07:28 +08:00
hrz dd38fc74db update:右智控台下发聊天记录上报策略 2025-05-12 18:06:58 +08:00
GoodyHao d619d7ac76 feat: 优化上报命名含义,增加不同上报开关 2025-05-12 17:55:54 +08:00
Sakura-RanChen 37f7511ba0 Merge branch 'manager-plugin-api' of https://github.com/xinnan-tech/xiaozhi-esp32-server into manager-plugin-api 2025-05-12 16:49:46 +08:00
Sakura-RanChen afd03ba1b5 完善插件管理页面 2025-05-12 16:48:34 +08:00
hrz ac7b02d28a update:保存智能体配置时,删除对应的历史记录 2025-05-12 16:31:06 +08:00
hrz 4a0bdbf779 update:聊天记录上报模式和记忆模式绑定 2025-05-12 15:49:44 +08:00
whosmyqueen 164dada46a feat(core): 为 FunASR 服务添加 API 密钥支持并优化结果处理
- 在 FunASRWebSocket 类中添加 API密钥配置
- 在 WebSocket连接时添加认证头信息- 使用正则表达式处理返回结果,提取有用信息
2025-05-12 14:38:58 +08:00
hrz fd1c4ec792 Merge branch 'manager-plugin-api' of https://github.com/xinnan-tech/xiaozhi-esp32-server into manager-plugin-api 2025-05-12 14:16:35 +08:00
hrz add000d221 update:数据库增加聊天上报策略字段 2025-05-12 14:16:08 +08:00
hrzandGitHub def598f728 Merge pull request #1198 from xinnan-tech/web-Plug
增加插件管理的页面
2025-05-12 14:13:05 +08:00
hrzandGitHub 0294885fbf Merge branch 'manager-plugin-api' into web-Plug 2025-05-12 14:12:54 +08:00
Sakura-RanChen c0ec4338f8 增加插件管理的页面 2025-05-12 14:10:05 +08:00
whosmyqueen fb7f6f73bd Merge branch 'main' into feature/add-fun-asr-auth 2025-05-12 13:10:27 +08:00
caixypromise e04c38e557 Merge branch 'refs/heads/feature/gemini-llm' into update/enhance-gemini-with-proxy 2025-05-12 04:38:09 +08:00
hrz bfb5420733 update:修改记录类型为0、1、2 2025-05-11 19:23:01 +08:00
hrzandGitHub 9e6fc32903 Merge pull request #1175 from ljwwd2/main
聊天记录支持配置
2025-05-11 19:06:27 +08:00
hrz 217ceb1c7a update:优化新增固件框宽度显示 2025-05-11 19:04:39 +08:00
hrz 846f89664e update:优化非LLM使用functioncall意图识别的提示 2025-05-11 16:11:44 +08:00
hrz 0e30f69e96 update:优化小图显示 2025-05-11 16:10:37 +08:00
ljwwd2 af9eb538f4 Merge remote-tracking branch 'origin/main' 2025-05-11 03:25:23 +08:00
ljwwd2 66a0beea33 前端-根据agentId获取智能体配置 2025-05-11 03:25:04 +08:00
ljwwd2andGitHub 411e73bb35 Merge branch 'xinnan-tech:main' into main 2025-05-11 03:23:26 +08:00
ljwwd2 852e222f5d 前端-聊天记录支持配置 2025-05-11 03:19:40 +08:00
hrz 054ceab4f4 update:优化非LLM使用functioncall的提示 2025-05-10 22:22:27 +08:00
欣南科技andGitHub d9149ff572 Merge pull request #1180 from xinnan-tech/feature/ha-llm
恢复HomeAssistant作为LLM使用
2025-05-10 21:55:08 +08:00
hrz 7f331690a0 update:增加误用function_call的提示 2025-05-10 21:52:31 +08:00
ljwwd2 a5698dfff0 聊天记录支持配置 2025-05-10 02:24:55 +08:00
ljwwd2 1e5866da06 聊天记录支持配置 2025-05-10 02:18:28 +08:00
欣南科技andGitHub 3f7b7da184 Merge pull request #1173 from xinnan-tech/hot-fix
update:修复立创1.6.2固件一直聆听中bug
2025-05-10 01:15:38 +08:00
hrz c0020c8769 update:修复立创1.6.2固件一直聆听中bug 2025-05-10 01:14:51 +08:00
hrzandGitHub 1c5e678cef Merge pull request #1130 from xinnan-tech/py-test
Py test
2025-05-09 18:09:31 +08:00
hrzandGitHub 3ab1b8ebc9 Merge branch 'main' into py-test 2025-05-09 18:08:48 +08:00
hrz 7635c68ccd update:增加aioconsole依赖 2025-05-09 18:01:36 +08:00
hrzandGitHub a3b562b895 Merge pull request #1113 from hsoftxl/tts-bug
tts 失败重试bug修复
2025-05-09 17:32:47 +08:00
欣南科技andGitHub 94d1493195 Merge pull request #1165 from xinnan-tech/hot-fix
fix:intent_llm的functions也需要返回成数组
2025-05-09 16:39:32 +08:00
hrz 3a5bbe32b9 fix:intent_llm的functions也需要返回成数组 2025-05-09 16:15:26 +08:00
whosmyqueen 7e712c5003 build(db): 更新模型供应器和配置
- 更新 ai_model_provider表中的字段配置
- 更新 ai_model_config 表中的配置 JSON- 添加新的数据库变更脚本202505091552.sql
- 在 db.changelog-master.yaml 中引用新脚本
2025-05-09 16:01:09 +08:00
欣南科技andGitHub 31328e092e Merge pull request #1164 from xinnan-tech/add_news
整合新的新闻插件
2025-05-09 15:26:58 +08:00
hrzandGitHub 29671f48a8 Merge branch 'main' into add_news 2025-05-09 15:25:15 +08:00
hrz 61c2782491 update:智控台,intent_llmM供应器增加functions输入框 2025-05-09 15:23:12 +08:00
hrz 15263b5223 update:更新英文版说明 2025-05-09 14:26:49 +08:00
hrz 557d1ee5b0 update:取消设备一连接就强制升级,改成绑定后再根据升级策略升级 2025-05-09 14:07:46 +08:00
hrz 777265c730 fix:参数管理保存json类型出错bug 2025-05-09 14:00:42 +08:00
hrz e76a8d547b update:使用intent_llm中的LLM进行工具内容回复 2025-05-09 13:45:01 +08:00
hrz ff98f84f18 update:intent_llm加载动态插件 2025-05-09 11:39:32 +08:00
hrzandGitHub d0b4fa5b28 Merge branch 'main' into py-test 2025-05-09 09:52:26 +08:00
hrzandGitHub 91b80f7c49 Merge pull request #1155 from xinnan-tech/web-pages-modifiy
页面优化
2025-05-09 09:51:27 +08:00
hrz 47f409246d update:intent_llm暂时不引用新闻插件,下一个版本改造intent_llm,让它能动态获取functioncall 2025-05-08 18:25:02 +08:00
hrz be1ff83297 update:通过pip来安装markitdown依赖 2025-05-08 18:23:37 +08:00
hrz 297c9e0085 update:重命名新闻插件,以平台来源来命名 2025-05-08 18:23:06 +08:00
Sakura-RanChen ec95917b4d 页面优化 2025-05-08 17:57:11 +08:00
hrzandGitHub 98bfe863fe 修复test_page.html没有hello消息,导致缺失audio_format的bug (#1151) 2025-05-08 15:08:11 +08:00
hrzandGitHub af3d00662e Merge pull request #1147 from xinnan-tech/fix-web-doubaoasr
update:更新版本号
2025-05-08 12:10:39 +08:00
欣南科技andGitHub 39803fb0df Merge pull request #1146 from xinnan-tech/fix-web-doubaoasr
修复:智控台豆包ASR缺少热词引发的bug
2025-05-08 12:09:11 +08:00
hrz 4dad5ea6c1 update:智控台添加百度ASR
修复:智控台豆包ASR缺少热词引发的bug
2025-05-08 12:07:50 +08:00
hrzandGitHub ba8cb8c6f8 Merge pull request #1125 from xinnan-tech/pcm
客户端上传编码为PCM时,服务端下发PCM格式的音频
2025-05-08 11:31:49 +08:00
hrz 64f10b28e7 update:合并main分支 2025-05-08 11:31:12 +08:00
hrzandGitHub 7f34447ace Merge branch 'main' into pcm 2025-05-08 11:18:12 +08:00
hrz c2e000f937 add:asr赋值audio_format 2025-05-08 11:11:28 +08:00
hrzandGitHub 831ab6d99b Merge pull request #1005 from JavaZeroo/add_news
feat: 添加多个新闻源,并修复大模型意图识别没有处理ActionResponse的问题
2025-05-08 10:01:33 +08:00
hrzandGitHub b246d9e567 fix:selected_module可能为空的bug (#1144)
* update:优化时间显示

* update:优化时间显示

* fix:selected_module可能为空的bug
2025-05-08 09:35:46 +08:00
hrzandGitHub 919c2ffd46 update:优化时间显示 (#1143)
* update:优化时间显示

* update:优化时间显示
2025-05-07 23:37:38 +08:00
hrzandGitHub e6d63a811e update:优化时间显示 (#1139) 2025-05-07 23:21:47 +08:00
hrzandGitHub 92227098b7 Merge pull request #1066 from xinnan-tech/manager-api-lastConnectedAtIsNull-BUG
修复了"获取用户智能体列表"中lastConnectedAt为null的bug
2025-05-07 22:57:08 +08:00
hrz 571080c1d6 update:优化最近对话时间 2025-05-07 22:56:51 +08:00
hrzandGitHub d07feb837d Merge branch 'main' into manager-api-lastConnectedAtIsNull-BUG 2025-05-07 22:45:51 +08:00
JavaZeroo 1c823c4255 feat: add news retrieval functionality and register new function 2025-05-07 21:15:06 +08:00
欣南科技andGitHub 63e378524b Merge pull request #1133 from xinnan-tech/hot-fix
update:优化日志对象
2025-05-07 18:06:57 +08:00
hrz ea5f54e421 update:优化日志对象 2025-05-07 18:06:13 +08:00
CGD ee18fbebae fix:修复“服务器运行时未处理标准输入(stdin),导致输入被缓冲,直到程序终止后才释放”的问题 2025-05-07 16:20:43 +08:00
CGD fa56d06e0c update:优化重启服务器功能 2025-05-07 16:17:50 +08:00
CGDandGitHub 72f7514114 Merge pull request #1129 from xinnan-tech/py-timeout-fix
Py timeout fix
2025-05-07 15:26:24 +08:00
Sakura-RanChen 44e1f00ffc fix:增加日志,方便意图识别 2025-05-07 15:17:34 +08:00
CGD e7054ea13f update:重启服务器功能功能 2025-05-07 14:36:08 +08:00
Sakura-RanChen 4a3ac2cfcd no message 2025-05-07 14:35:41 +08:00
Sakura-RanChen bbc31e01c4 fix:tts队列统一元组 2025-05-07 14:31:59 +08:00
玄凤科技 ba86b34a8c pcm模式,同步其他asr 2025-05-07 11:34:29 +08:00
剑雨 770a198772 获取设备最大的最近连接时间。改为在数据库里排序好后返回给系统,缓存时间修改为2分钟
--DeviceDao.java 修改方法返回值
--DeviceDao.xml 修改sql,在数据库里排序返回
--DeviceServiceImpl.java 修改缓存时间为2分钟
2025-05-07 11:04:14 +08:00
欣南科技andGitHub a26bee3696 Py update config (#1120)
* update:优化获取默认配置

* update:优化未绑定用户的连接

* update:修复智控台模式下,所选模块的日志名称

* update:优化参数配置敏感密钥的显示方式

* update:更新服务器配置并重新初始化组件
2025-05-07 09:15:52 +08:00
hrzandGitHub abb8f4f963 Merge pull request #1115 from xinnan-tech/py_tts_timeout
update: tts超时导致的文本索引混乱
2025-05-07 09:12:35 +08:00
hrz 43d2adff70 update:更新服务器配置并重新初始化组件 2025-05-07 09:11:59 +08:00
Junsen HuangandGitHub 474eeb9e68 Merge pull request #1102 from CaixyPromise/feature/ha-llm
feature: 恢复HA的LLM能力。补充关于HA的LLM大模型和MCP集成方法文档
2025-05-07 01:18:24 +08:00
Junsen HuangandGitHub e2dffea423 Merge pull request #1104 from CaixyPromise/feature/wait-exit-fix
fix(app): 修复app.py内的wait_for_exit(),以此解决Windows环境下手动退出时,进程阻塞卡死的问题。
2025-05-07 00:48:27 +08:00
Junsen HuangandGitHub 3d5eaba46f Merge pull request #1105 from CaixyPromise/feature/mcp-exit-fix
fix(MCP): 重构MCPClient为后台协程 + AsyncExitStack管理,解决进程退出时“Attempted to exit cancel scope in a different task”错误
2025-05-07 00:47:37 +08:00
caixypromise 1bb8fbd56f resolve: merge upstream/main into feature/mcp-exit-fix and fix conflicts 2025-05-07 00:39:21 +08:00
hrz b699886953 update:优化参数配置敏感密钥的显示方式 2025-05-06 17:30:39 +08:00
hrz 12c957d48b update:修复智控台模式下,所选模块的日志名称 2025-05-06 17:14:58 +08:00
玄凤科技andGitHub 48e890c1b1 Merge pull request #1106 from kevin1sMe/feat-mcp
feat: MCP server支持使用sse模式
2025-05-06 15:38:11 +08:00
Sakura-RanChen 05331f001a update: tts超时导致的文本索引混乱 2025-05-06 15:15:27 +08:00
玄凤科技 bde260b330 PCM音频模式 2025-05-06 14:57:29 +08:00
XL 59ef51ea20 tts 失败重试bug修复 2025-05-06 14:19:05 +08:00
hrz aa77bfdfc4 update:优化未绑定用户的连接 2025-05-06 13:10:26 +08:00
hrz a2baef8911 update:优化获取默认配置 2025-05-06 13:09:43 +08:00
kevin1sMe 3fcfb65d45 update: example 2025-05-04 23:37:17 +08:00
kevin1sMe 8f266bea0d feat: MCP server支持使用sse模式 2025-05-04 23:33:07 +08:00
caixypromise 0a765f4aac fix(MCP): 重构MCPClient为后台协程 + AsyncExitStack管理,解决进程退出时的“Attempted to exit cancel scope in a different task”错误
# 变更
----
- 将所有stdio_client与ClientSession的创建/销毁都放到同一个后台 task 中
- 使用AsyncExitStack托管异步资源,cleanup时在同一task内执行exit_stack.aclose()
- 外部只通过事件通知后台task退出,避免跨协程调用cancel-scope异常
2025-05-04 23:27:47 +08:00
caixypromise ee3f0555d1 fix(app): 重写app.py内的wait_for_exit(),以此解决Windows环境下手动退出时,进程阻塞卡死的问题。
影响
----
- Ctrl‑C/kill退出时不再卡住,资源完全释放,
- Windows 与 Unix 行为一致,改动不影响正常业务逻辑。
2025-05-04 23:26:39 +08:00
caixypromise 0c0386f4ba feature: 新增Gemini模型支持function_call功能,支持在代理模式下使用流式stream对话模式。 2025-05-04 23:24:33 +08:00
caixypromise 6c2b1cb589 feature: 恢复HA的LLM支持能力。
docs: 更新Home Assistant-integration.md内容,补充提交关于HA集成的说法,介绍LLM大模型和MCP集成方法。

issue #616
2025-05-04 23:22:33 +08:00
hrzandGitHub 9fc1285c09 Merge pull request #1101 from xinnan-tech/hot-fix
update:删除智能体时删除聊天记录
2025-05-04 20:34:49 +08:00
欣南科技andGitHub e6e8ccee50 Merge pull request #1100 from xinnan-tech/hot-fix
Hot fix
2025-05-04 20:15:07 +08:00
hrz f9632af016 创建智能体返回智能体id 2025-05-04 20:13:32 +08:00
hrz 81359bf419 修复智控台音频播放权限bug 2025-05-04 20:03:13 +08:00
欣南科技andGitHub a144570ed7 Merge pull request #1097 from xinnan-tech/chat-history-ui
Chat history UI
2025-05-04 15:45:11 +08:00
hrz 0732b72e8f update:聊天记录音频播放 2025-05-04 15:43:28 +08:00
欣南科技andGitHub 8332445a52 Merge pull request #1096 from xinnan-tech/chat-history-ui
Chat history UI
2025-05-04 13:16:58 +08:00
hrz c9111d1bfc update:优化websocket地址验证 2025-05-04 13:15:49 +08:00
hrz e7d999278d update:优化显示 2025-05-04 13:00:56 +08:00
hrz 4043d20ea6 update:聊天记录展现 2025-05-04 12:53:36 +08:00
hrzandGitHub afe88ccfd6 Merge pull request #1091 from xinnan-tech/asr_code_clear
规范asr部分的代码,添加百度asr支持
2025-05-04 01:21:00 +08:00
hrz 56c3a04809 update:优化百度ASR文档链接 2025-05-04 01:19:54 +08:00
hrz 4530345a1f fix:设置有效的时区 2025-05-04 00:51:51 +08:00
hrzandGitHub 4a89ec8515 Merge pull request #1092 from kevin1sMe/fix-doubao-tts
fix: doubao tts token
2025-05-04 00:46:53 +08:00
欣南科技andGitHub cb5f1c6485 Merge pull request #1093 from xinnan-tech/hot-fix
add:获取聊天记录API
2025-05-03 23:49:02 +08:00
kevin1sMe 15c0677a4b fix: doubao tts token 2025-05-03 23:33:32 +08:00
hrz dc68b8148a add:获取聊天记录API 2025-05-03 23:30:39 +08:00
王华侨 1976034f12 添加百度asr支持 2025-05-03 22:21:22 +08:00
王华侨 727621fdac 规范asr部分的代码 2025-05-03 22:20:50 +08:00
欣南科技andGitHub 0f3806d358 Merge pull request #1086 from xinnan-tech/hot-fix
update:上报聊天音频
2025-05-02 16:29:48 +08:00
hrz dfb2bf3923 update:上报聊天音频 2025-05-02 16:17:18 +08:00
剑雨 b4944f23ef 添加了一个新的redis:key
--RedisKeys.java 获取设备最近最久时间的key
2025-04-30 11:08:59 +08:00
剑雨 09c17b2dc5 优化方法,把获取最近最后时间获取,放在获取设备数量前面前,可以减少一次sql查询,因为在获取时间的时候,已经顺便缓存的设备数量了
--AgentServiceImpl.java 优化:减少sql查询
2025-04-30 11:07:53 +08:00
剑雨 6f7ff9f858 优化获取这个智能体设备理的最近的最后连接时间方法,缓存时间和设备数量
--DeviceServiceImpl.java 优化方法
2025-04-30 11:05:58 +08:00
剑雨 7d74e853ae 修复智能体最近的最后连接时间为空的bug
--AgentServiceImpl.java 修复bug
2025-04-30 10:46:34 +08:00
剑雨 1d627d570d 添加获取这个智能体设备理的最近的最后连接时间定义和实现
--DeviceService.java 方法定义
--DeviceServiceImpl.java 方法实现
2025-04-30 10:45:49 +08:00
剑雨 9b0088f4f0 添加获取获取此智能体全部设备的最后连接时间,方法定义和sql,不使用mysql-plus的方法,是为了减少数据库数据传输,因为只需要最后连接时间字段
--DeviceDao.java 方法定义
--DeviceDao.xml sql
2025-04-30 10:44:54 +08:00
剑雨 f92c0313a6 添加保存用户测试方法和模拟设备连连接过来的方法,方便新开发者调试
--DeviceTest.java
2025-04-30 09:41:38 +08:00
剑雨 b9fc0da215 Merge remote-tracking branch 'origin/main' 2025-04-29 10:32:22 +08:00
剑雨 39fe057f9f Merge remote-tracking branch 'origin/main' 2025-04-17 15:56:40 +08:00
CoderMonkeyandGitHub 92e117c10a Merge pull request #737 from HiCoderMonkey/tts-response
Tts response 双向流tts,fix 播放音乐
2025-04-11 11:12:12 +08:00
lizhongxiang 71db92ef61 fix: 播放音乐 2025-04-11 11:08:22 +08:00
lizhongxiang 118bb728a9 fix: 播放咋音问题 2025-04-11 10:25:43 +08:00
hrz d7aac5e10f Merge branch 'main' into tts-response
# Conflicts:
#	main/manager-web/.env.development
2025-04-09 21:46:59 +08:00
CoderMonkeyandGitHub 4359f1d5b3 Merge pull request #719 from HiCoderMonkey/tts-response
Tts response ,fix 播放有咋音
2025-04-09 14:38:02 +08:00
lizhongxiang 8bb888c58e fix: 播放咋音问题 2025-04-09 14:32:32 +08:00
lizhongxiang 4c81585e32 merge oeigin/tts-response 2025-04-07 18:15:09 +08:00
hrz 0e6d1cd677 Merge branch 'main' into tts-response 2025-04-04 02:02:30 +08:00
hrz 98368cbe06 fix:文字回复卡住bug 2025-04-04 02:01:48 +08:00
hrz 0b4a4df1af update:合并最新代码 2025-04-04 00:27:04 +08:00
hrz c8a3d378b7 update:合并main分支 2025-04-03 23:06:59 +08:00
剑雨 f55d6c2e60 Merge remote-tracking branch 'origin/main' 2025-04-03 10:22:13 +08:00
CoderMonkeyandGitHub aa34473560 Merge pull request #640 from HiCoderMonkey/tts-response
fix:删除多余方法
2025-04-02 11:37:23 +08:00
lizhongxiang f479a789db fix:删除多余方法 2025-04-02 11:35:49 +08:00
CoderMonkeyandGitHub d4ce501c75 Merge pull request #639 from HiCoderMonkey/tts-response
fix:播放音乐不会停
2025-04-02 11:26:43 +08:00
lizhongxiang c2643d0b80 fix:merge main 2025-04-02 11:18:26 +08:00
lizhongxiang 078605c188 fix:播放音乐不会停 2025-04-02 11:11:16 +08:00
lizhongxiang cd3e9ed494 fix:播放音乐不会停 2025-04-02 11:07:04 +08:00
CoderMonkeyandGitHub 8a5a9cc0b3 Merge pull request #625 from HiCoderMonkey/tts-response
fix:火山声音复刻
2025-04-01 15:09:12 +08:00
lizhongxiang 347fc4ca44 fix:火上声音复刻 2025-04-01 15:07:16 +08:00
hrz 61db1192e2 update:格式化代码 2025-04-01 14:27:17 +08:00
CoderMonkeyandGitHub d9d973b6aa Merge pull request #612 from HiCoderMonkey/tts-response
fix:aliyun,cozecn,custom,doubao,gpt_sovits_v2,gpt_sovits_v3,minimax,o…
2025-03-31 16:32:08 +08:00
lizhongxiang cc4aed5a8e fix:aliyun,cozecn,custom,doubao,gpt_sovits_v2,gpt_sovits_v3,minimax,openai,siliconflow,ttson,这些tts兼容 2025-03-31 16:31:06 +08:00
CoderMonkeyandGitHub b44d52fb3e Merge pull request #610 from HiCoderMonkey/tts-response
fix:普通聊天调试
2025-03-31 15:35:33 +08:00
lizhongxiang 93265be974 fix:普通聊天调试 2025-03-31 15:33:20 +08:00
CoderMonkeyandGitHub 2028405af6 Merge pull request #606 from HiCoderMonkey/tts-response
tts 双向流改造
2025-03-31 15:21:38 +08:00
lizhongxiang bb9df3f54c add:双向流tts结构改造,火山双向tts语音流式输入输出 2025-03-31 15:15:59 +08:00
lizhongxiang 264487574b add:火山双向tts语音流式输入输出 2025-03-29 14:17:09 +08:00
CoderMonkeyandGitHub 6a00895af8 Merge pull request #443 from HiCoderMonkey/tts-response
Tts 改造
2025-03-20 10:16:47 +08:00
lizhongxiang 1136ca4b24 mergin main 2025-03-20 10:14:49 +08:00
lizhongxiang b153bc11f2 mergin main,速度提升一下 2025-03-20 09:39:25 +08:00
lizhongxiang a067a6ba87 fix: 流式tts,function聊天时候的支持 2025-03-13 17:50:29 +08:00
lizhongxiang c271b88c87 fix: 流式tts,function聊天时候的支持 2025-03-13 17:44:10 +08:00
lizhongxiang 1dfa619243 fix: 流式流畅度问题,英文符号切分,空格切分 2025-03-13 15:30:00 +08:00
lizhongxiang 91bdc05a38 merge main 2025-03-13 14:24:18 +08:00
lizhongxiang 0e8edfe002 merge main,fix gitignore 2025-03-10 11:12:17 +08:00
lizhongxiang c1084923f7 merge main 2025-03-10 10:51:25 +08:00
lizhongxiang b2381fc689 add: 语音流式输出 2025-03-05 17:29:26 +08:00
lizhongxiang fa75f56ffb add: 语音流式输出 2025-03-05 17:26:32 +08:00
330 changed files with 22151 additions and 4622 deletions
+10 -2
View File
@@ -107,6 +107,7 @@ celerybeat.pid
*.sage.py *.sage.py
# Environments # Environments
.env
.venv .venv
env/ env/
venv/ venv/
@@ -145,24 +146,31 @@ tmp
.history .history
.DS_Store .DS_Store
main/xiaozhi-server/data main/xiaozhi-server/data
main/xiaozhi-server/config/assets/wakeup_words.*
main/manager-web/node_modules main/manager-web/node_modules
.config.yaml .config.yaml
.secrets.yaml .secrets.yaml
.private_config.yaml .private_config.yaml
.env.development
# model files # model files
main/xiaozhi-server/models/SenseVoiceSmall/model.pt main/xiaozhi-server/models/SenseVoiceSmall/model.pt
main/xiaozhi-server/models/sherpa-onnx* main/xiaozhi-server/models/sherpa-onnx*
/main/xiaozhi-server/audio_ref/
/audio_ref/
/asr-models/iic/SenseVoiceSmall/
/main/xiaozhi-server/asr-models/iic/SenseVoiceSmall/
/models/SenseVoiceSmall/model.pt
my_wakeup_words.mp3 my_wakeup_words.mp3
!main/xiaozhi-server/config/assets/bind_code.wav !main/xiaozhi-server/config/assets/bind_code.wav
!main/xiaozhi-server/config/assets/wakeup_words.wav
!main/xiaozhi-server/config/assets/bind_not_found.wav !main/xiaozhi-server/config/assets/bind_not_found.wav
!main/xiaozhi-server/config/assets/bind_code/*.wav !main/xiaozhi-server/config/assets/bind_code/*.wav
!main/xiaozhi-server/config/assets/max_output_size.wav !main/xiaozhi-server/config/assets/max_output_size.wav
main/manager-api/.vscode main/manager-api/.vscode
# Ignore webpack cache directory # Ignore webpack cache directory
main/manager-web/.webpack_cache/ main/manager-web/.webpack_cache/
main/xiaozhi-server/mysql main/xiaozhi-server/mysql
uploadfile uploadfile
*.json
.vscode .vscode
+10 -7
View File
@@ -7,20 +7,23 @@ COPY main/manager-web .
RUN npm run build RUN npm run build
# 第二阶段:构建Java后端 # 第二阶段:构建Java后端
FROM maven:3-eclipse-temurin-21-alpine as api-builder FROM maven:3.9.4-eclipse-temurin-21 as api-builder
WORKDIR /app WORKDIR /app
COPY main/manager-api/pom.xml . COPY main/manager-api/pom.xml .
COPY main/manager-api/src ./src COPY main/manager-api/src ./src
RUN mvn clean package -Dmaven.test.skip=true RUN mvn clean package -Dmaven.test.skip=true
# 第三阶段:构建最终镜像 # 第三阶段:构建最终镜像
FROM eclipse-temurin:21-jdk-jammy FROM bellsoft/liberica-runtime-container:jre-21-glibc
# 安装Nginx并清理缓存 # 安装Nginx和字体库
RUN apt-get update && \ RUN apk update && \
apt-get install -y nginx && \ apk add --no-cache nginx bash && \
apt-get clean && \ apk add --no-cache fontconfig ttf-dejavu msttcorefonts-installer && \
rm -rf /var/lib/apt/lists/* rm -rf /var/cache/apk/*
# 更新字体缓存
RUN printf 'YES\n' | update-ms-fonts && fc-cache -f -v
# 配置Nginx # 配置Nginx
COPY docs/docker/nginx.conf /etc/nginx/nginx.conf COPY docs/docker/nginx.conf /etc/nginx/nginx.conf
+90 -17
View File
@@ -10,7 +10,7 @@
</p> </p>
<p align="center"> <p align="center">
<a href="./README.md">English</a> <a href="./README_en.md">English</a>
· <a href="./docs/FAQ.md">常见问题</a> · <a href="./docs/FAQ.md">常见问题</a>
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">反馈问题</a> · <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">反馈问题</a>
· <a href="./README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3">部署文档</a> · <a href="./README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3">部署文档</a>
@@ -121,6 +121,38 @@
</a> </a>
</td> </td>
</tr> </tr>
<tr>
<td>
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
<picture>
<img alt="实时打断" src="docs/images/demo10.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
<picture>
<img alt="拍照识物品" src="docs/images/demo12.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>
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
<picture>
<img alt="MCP接入点" src="docs/images/demo13.png" />
</picture>
</a>
</td>
<td>
</td>
</tr>
</table> </table>
--- ---
@@ -141,11 +173,11 @@
本项目提供两种部署方式,请根据您的具体需求选择: 本项目提供两种部署方式,请根据您的具体需求选择:
#### 🚀 部署方式选择 #### 🚀 部署方式选择
| 部署方式 | 特点 | 适用场景 | 部署文档 | 配置要求 | 视频教程 |
|---------|------|---------|---------|---------|---------|
| **最简化安装** | 智能对话、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) |
| 部署方式 | 特点 | 适用场景 | Docker部署文档 | 源码部署文档 |
|---------|------|---------|---------|---------|
| **最简化安装** | 智能对话、IOT功能,数据存储在配置文件 | 低配置环境,无需数据库 | [Docker只运行Server](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) | [本地源码只运行Server](./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)|
| **全模块安装** | 智能对话、IOT、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) |
> 💡 提示:以下是按最新代码部署后的测试平台,有需要可烧录测试,并发为6个,每天会清空数据 > 💡 提示:以下是按最新代码部署后的测试平台,有需要可烧录测试,并发为6个,每天会清空数据
@@ -157,22 +189,53 @@ OTA接口地址: https://2662r3426b.vicp.fun/xiaozhi/ota/
Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
``` ```
#### 🚩 配置说明和推荐
> [!Note]
> 本项目提供两种配置方案:
>
> 1. `入门全免费`配置:适合个人家庭使用,所有组件均采用免费方案,无需额外付费。
>
> 2. `流式配置`:适合演示、培训、超过2个并发等场景,采用流式处理技术,响应速度更快,体验更佳。
>
> 自`0.5.2`版本起,项目支持流式配置,相比早期版本,响应速度提升约`2.5秒`,显著改善用户体验。
| 模块名称 | 入门全免费设置 | 流式配置 |
|:---:|:---:|:---:|
| ASR(语音识别) | FunASR(本地) | 👍FunASRServer 或 👍DoubaoStreamASR |
| LLM(大模型) | ChatGLMLLM(智谱glm-4-flash) | 👍DoubaoLLM(火山doubao-1-5-pro-32k-250115) |
| VLLM(视觉大模型) | ChatGLMVLLM(智谱glm-4v-flash) | 👍QwenVLVLLM(千问qwen2.5-vl-3b-instructh) |
| TTS(语音合成) | ✅LinkeraiTTS(灵犀流式) | 👍HuoshanDoubleStreamTTS(火山双流式语音合成) |
| Intent(意图识别) | function_call(函数调用) | function_call(函数调用) |
| Memory(记忆功能) | mem_local_short(本地短期记忆) | mem_local_short(本地短期记忆) |
#### 🔧 测试工具
本项目提供以下测试工具,帮助您验证系统和选择合适的模型:
| 工具名称 | 位置 | 使用方法 | 功能说明 |
|:---:|:---|:---:|:---:|
| 音频交互测试工具 | main》xiaozhi-server》test》test_page.html | 使用谷歌浏览器直接打开 | 测试音频播放和接收功能,验证Python端音频处理是否正常 |
| 模型响应测试工具1 | main》xiaozhi-server》performance_tester.py | 执行 `python performance_tester.py` | 测试ASR(语音识别)、LLM(大模型)、TTS(语音合成)三个核心模块的响应速度 |
| 模型响应测试工具2 | main》xiaozhi-server》performance_tester_vllm.py | 执行 `python performance_tester_vllm.py` | 测试VLLM(视觉模型)的响应速度 |
> 💡 提示:测试模型速度时,只会测试配置了密钥的模型。
--- ---
## 功能清单 ✨ ## 功能清单 ✨
### 已实现 ✅ ### 已实现 ✅
| 功能模块 | 描述 | | 功能模块 | 描述 |
|---------|------| |:---:|:---|
| 通信协议 | 基于 `xiaozhi-esp32` 协议,通过 WebSocket 实现数据交互 | | 核心服务架构 | 基于WebSocket和HTTP服务器,提供完整的控制台管理和认证系统 |
| 对话交互 | 支持唤醒对话、手动对话及实时打断。长时间无对话时自动休眠 | | 语音交互系统 | 支持流式ASR(语音识别)、流式TTS(语音合成)、VAD(语音活动检测),支持多语言识别和语音处理 |
| 意图识别 | 支持使用LLM意图识别、function call函数调用,减少硬编码意图判断 | | 智能对话系统 | 支持多种LLM(大语言模型),实现智能对话 |
| 多语言识别 | 支持国语、粤语、英语、日语、韩语(默认使用 FunASR) | | 视觉感知系统 | 支持多种VLLM(视觉大模型),实现多模态交互 |
| LLM 模块 | 支持灵活切换 LLM 模块,默认使用 ChatGLMLLM,也可选用阿里百炼、DeepSeek、Ollama 等接口 | | 意图识别系统 | 支持LLM意图识别、Function Call函数调用,提供插件化意图处理机制 |
| TTS 模块 | 支持 EdgeTTS(默认)、火山引擎豆包 TTS 等多种 TTS 接口,满足语音合成需求 | | 记忆系统 | 支持本地短期记忆、mem0ai接口记忆,具备记忆总结功能 |
| 记忆功能 | 支持超长记忆、本地总结记忆、无记忆三种模式,满足不同场景需求 | | 工具调用 | 支持客户端IOT协议、客户MCP协议、服务端MCP协议、MCP接入点协议、自定义工具函数 |
| IOT功能 | 支持管理注册设备IOT功能,支持基于对话上下文语境下的智能物联网控制 | | 管理后台 | 提供Web管理界面,支持用户管理、系统配置和设备管理 |
| 智控台 | 提供Web管理界面,支持智能体管理、用户管理、系统配置等功能,方便管理员和用户进行管理 | | 测试工具 | 提供性能测试工具、视觉模型测试工具和音频交互测试工具 |
| 部署支持 | 支持Docker部署和本地部署,提供完整的配置文件管理 |
| 插件系统 | 支持功能插件扩展、自定义插件开发和插件热加载 |
### 正在开发 🚧 ### 正在开发 🚧
@@ -209,11 +272,21 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
--- ---
### VLLM 视觉模型
| 使用方式 | 支持平台 | 免费平台 |
|:---:|:---:|:---:|
| openai 接口调用 | 阿里百炼、智谱ChatGLMVLLM | 智谱ChatGLMVLLM |
实际上,任何支持 openai 接口调用的 VLLM 均可接入使用。
---
### TTS 语音合成 ### TTS 语音合成
| 使用方式 | 支持平台 | 免费平台 | | 使用方式 | 支持平台 | 免费平台 |
|:---:|:---:|:---:| |:---:|:---:|:---:|
| 接口调用 | EdgeTTS、火山引擎豆包TTS、腾讯云、阿里云TTS、CosyVoiceSiliconflow、TTS302AI、CozeCnTTS、GizwitsTTS、ACGNTTS、OpenAITTS | EdgeTTS、CosyVoiceSiliconflow(部分) | | 接口调用 | EdgeTTS、火山引擎豆包TTS、腾讯云、阿里云TTS、CosyVoiceSiliconflow、TTS302AI、CozeCnTTS、GizwitsTTS、ACGNTTS、OpenAITTS、灵犀流式TTS | 灵犀流式TTS、EdgeTTS、CosyVoiceSiliconflow(部分) |
| 本地服务 | FishSpeech、GPT_SOVITS_V2、GPT_SOVITS_V3、MinimaxTTS | FishSpeech、GPT_SOVITS_V2、GPT_SOVITS_V3、MinimaxTTS | | 本地服务 | FishSpeech、GPT_SOVITS_V2、GPT_SOVITS_V3、MinimaxTTS | FishSpeech、GPT_SOVITS_V2、GPT_SOVITS_V3、MinimaxTTS |
--- ---
+311
View File
@@ -0,0 +1,311 @@
[![Banners](docs/images/banner1.png)](https://github.com/xinnan-tech/xiaozhi-esp32-server)
<h1 align="center">Xiaozhi Backend Service xiaozhi-esp32-server</h1>
<p align="center">
This project provides backend services for the open-source smart hardware project
<a href="https://github.com/78/xiaozhi-esp32">xiaozhi-esp32</a><br/>
Implemented using Python, Java, and Vue according to the <a href="https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh">Xiaozhi Communication Protocol</a><br/>
Helps you quickly set up your Xiaozhi server
</p>
<p align="center">
<a href="./README.md">中文</a>
· <a href="./docs/FAQ.md">FAQ</a>
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">Report Issues</a>
· <a href="./README_en.md#deployment-documentation">Deployment Guide</a>
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">Release Notes</a>
</p>
<p align="center">
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
</a>
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/graphs/contributors">
<img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/xinnan-tech/xiaozhi-esp32-server?logo=github" />
</a>
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">
<img alt="Issues" src="https://img.shields.io/github/issues/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
</a>
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/pulls">
<img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
</a>
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/LICENSE">
<img alt="GitHub pull requests" src="https://img.shields.io/badge/license-MIT-white?labelColor=black" />
</a>
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server">
<img alt="stars" src="https://img.shields.io/github/stars/xinnan-tech/xiaozhi-esp32-server?color=ffcb47&labelColor=black" />
</a>
</p>
---
## Target Users 👥
This project requires ESP32 hardware devices. If you have purchased ESP32-related hardware, successfully connected to Brother Xia's backend service, and want to set up your own `xiaozhi-esp32` backend service, then this project is perfect for you.
Want to see it in action? Check out these videos 🎥
<table>
<tr>
<td>
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
<picture>
<img alt="Xiaozhi esp32 connecting to own backend model" src="docs/images/demo1.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
<picture>
<img alt="Custom voice" src="docs/images/demo2.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
<picture>
<img alt="Using Cantonese" src="docs/images/demo3.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
<picture>
<img alt="Control home appliances" src="docs/images/demo5.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV1kgA2eYEQ9" target="_blank">
<picture>
<img alt="Lowest cost configuration" src="docs/images/demo4.png" />
</picture>
</a>
</td>
</tr>
<tr>
<td>
<a href="https://www.bilibili.com/video/BV1Vy96YCE3R" target="_blank">
<picture>
<img alt="Custom voice" src="docs/images/demo6.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
<picture>
<img alt="Play music" src="docs/images/demo7.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV1Z8XuYZEAS" target="_blank">
<picture>
<img alt="Weather plugin" src="docs/images/demo8.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV178XuYfEpi" target="_blank">
<picture>
<img alt="IOT command control" src="docs/images/demo9.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
<picture>
<img alt="News broadcast" src="docs/images/demo0.png" />
</picture>
</a>
</td>
</tr>
<tr>
<td>
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
<picture>
<img alt="Real-time interruption" src="docs/images/demo10.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
<picture>
<img alt="Photo recognition" src="docs/images/demo12.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
<picture>
<img alt="Multi-command tasks" src="docs/images/demo11.png" />
</picture>
</a>
</td>
<td>
</td>
<td>
</td>
</tr>
</table>
---
## Warning ⚠️
1. This project is open-source software. This software has no commercial relationship with any third-party API service providers (including but not limited to speech recognition, large models, speech synthesis, and other platforms) and does not provide any form of guarantee for their service quality or financial security.
It is recommended that users prioritize service providers with relevant business licenses and carefully read their service agreements and privacy policies. This software does not host any account keys, does not participate in fund transfers, and does not bear the risk of recharge fund losses.
2. This project's functionality is not complete and has not passed network security testing. Please do not use it in production environments. If you deploy this project for learning in a public network environment, please ensure necessary protection measures are in place.
---
## Deployment Documentation
![Banners](docs/images/banner2.png)
This project provides two deployment methods. Please choose according to your specific needs:
#### 🚀 Deployment Method Selection
| Deployment Method | Features | Suitable Scenarios | Deployment Guide | Requirements | Video Tutorial |
|---------|------|---------|---------|---------|---------|
| **Simplified Installation** | Smart dialogue, IOT functionality, data stored in configuration files | Low-configuration environment, no database needed | [Docker Version](./docs/Deployment.md#method-1-docker-server-only) / [Source Code Deployment](./docs/Deployment.md#method-2-local-source-code-server-only) | 2 cores 4G if using `FunASR`, 2 cores 2G if using all APIs | - |
| **Full Module Installation** | Smart dialogue, IOT, OTA, Control Panel, data stored in database | Complete functionality experience | [Docker Version](./docs/Deployment_all.md#method-1-docker-full-modules) / [Source Code Deployment](./docs/Deployment_all.md#method-2-local-source-code-full-modules) | 4 cores 8G if using `FunASR`, 2 cores 4G if using all APIs | [Local Source Code Startup Video Tutorial](https://www.bilibili.com/video/BV1wBJhz4Ewe) / [Local Source Code Auto-Update Tutorial](./docs/dev-ops-integration.md) |
> 💡 Note: Below are the test platforms deployed with the latest code. You can flash and test if needed. Concurrent users: 6, data will be cleared daily
```
Control Panel Address: https://2662r3426b.vicp.fun
Service Test Tool: https://2662r3426b.vicp.fun/test/
OTA Interface Address: https://2662r3426b.vicp.fun/xiaozhi/ota/
Websocket Interface Address: wss://2662r3426b.vicp.fun/xiaozhi/v1/
```
#### 🚩 Configuration Description and Recommendations
> [!Note]
> The default configuration of this project is `Entry Level Free` settings. For better results, we recommend using `Full Streaming Configuration`.
>
> Since version `0.5.2`, this project supports full streaming throughout the entire lifecycle. Compared to versions before `0.5`, response speed has improved by approximately `2.5 seconds`
| Module Name | Entry Level Free Settings | Full Streaming Configuration |
|---------|---------|------|
| ASR(Speech Recognition) | FunASR(Local) | ✅DoubaoASR(Volcano Streaming Speech Recognition) |
| LLM(Large Language Model) | ChatGLMLLM(Zhipu glm-4-flash) | ✅DoubaoLLM(Volcano doubao-1-5-pro-32k-250115) |
| VLLM(Vision Large Model) | ChatGLMVLLM(Zhipu glm-4v-flash) | ✅ChatGLMVLLM(Zhipu glm-4v-flash) |
| TTS(Speech Synthesis) | EdgeTTS(Microsoft Speech) | ✅HuoshanDoubleStreamTTS(Volcano Double Streaming Speech Synthesis) |
| Intent(Intent Recognition) | function_call(Function Call) | ✅function_call(Function Call) |
| Memory(Memory Function) | mem_local_short(Local Short-term Memory) | ✅mem_local_short(Local Short-term Memory) |
---
## Feature List ✨
### Implemented ✅
| Feature Module | Description |
|---------|------|
| Communication Protocol | Based on `xiaozhi-esp32` protocol, implements data interaction through WebSocket |
| Dialogue Interaction | Supports wake-up dialogue, manual dialogue, and real-time interruption. Auto-sleep after long periods of no dialogue |
| Intent Recognition | Supports LLM intent recognition, function call, reducing hard-coded intent judgment |
| Multi-language Recognition | Supports Mandarin, Cantonese, English, Japanese, Korean (default using FunASR) |
| LLM Module | Supports flexible LLM module switching, default using ChatGLMLLM, can also use Ali Bailian, DeepSeek, Ollama, etc. |
| TTS Module | Supports EdgeTTS (default), Volcano Engine Doubao TTS, and other TTS interfaces |
| Memory Function | Supports ultra-long memory, local summary memory, and no memory modes |
| IOT Function | Supports managing registered device IOT functionality, supports smart IoT control based on dialogue context |
| Control Panel | Provides Web management interface, supports agent management, user management, system configuration, etc. |
### In Development 🚧
To learn about specific development progress, [click here](https://github.com/users/xinnan-tech/projects/3)
If you are a software developer, here is an [Open Letter to Developers](docs/contributor_open_letter.md). Welcome to join!
---
## Product Ecosystem 👬
Xiaozhi is an ecosystem. When using this product, you might also want to check out other excellent projects in this ecosystem
| Project Name | Project Address | Project Description |
|:---------------------|:--------|:--------|
| Xiaozhi Android Client | [xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) | A Flutter-based Android and iOS voice dialogue application supporting real-time voice interaction and text dialogue. |
| Xiaozhi PC Client | [py-xiaozhi](https://github.com/Huang-junsen/py-xiaozhi) | This project provides a Python-based Xiaozhi AI client, allowing you to experience Xiaozhi AI's functionality through code even without physical hardware. |
| Xiaozhi Java Server | [xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) | The Java version of Xiaozhi open-source backend service is a Java-based open-source project.<br/>It includes both frontend and backend services, aiming to provide users with a complete backend service solution. |
---
## Supported Platforms/Components List 📋
### LLM Language Models
| Usage Method | Supported Platforms | Free Platforms |
|:---:|:---:|:---:|
| openai interface call | Ali Bailian, Volcano Engine Doubao, DeepSeek, Zhipu ChatGLM, Gemini | Zhipu ChatGLM, Gemini |
| ollama interface call | Ollama | - |
| dify interface call | Dify | - |
| fastgpt interface call | Fastgpt | - |
| coze interface call | Coze | - |
In fact, any LLM that supports openai interface calls can be integrated and used.
### TTS Speech Synthesis
| Usage Method | Supported Platforms | Free Platforms |
|:---:|:---:|:---:|
| API Call | EdgeTTS, Volcano Engine Doubao TTS, Tencent Cloud, Alibaba Cloud TTS, CosyVoiceSiliconflow, TTS302AI, CozeCnTTS, GizwitsTTS, ACGNTTS, OpenAITTS | EdgeTTS, CosyVoiceSiliconflow(partial) |
| Local Service | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, MinimaxTTS | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, MinimaxTTS |
---
### VAD Voice Activity Detection
| Type | Platform Name | Usage Method | Pricing Model | Notes |
|:---:|:---------:|:----:|:----:|:--:|
| VAD | SileroVAD | Local Usage | Free | |
---
### ASR Speech Recognition
| Usage Method | Supported Platforms | Free Platforms |
|:---:|:---:|:---:|
| Local Usage | FunASR, SherpaASR | FunASR, SherpaASR |
| API Call | DoubaoASR, FunASRServer, TencentASR, AliyunASR | FunASRServer |
---
### Memory Storage
| Type | Platform Name | Usage Method | Pricing Model | Notes |
|:------:|:---------------:|:----:|:---------:|:--:|
| Memory | mem0ai | API Call | 1000 calls/month quota | |
| Memory | mem_local_short | Local Summary | Free | |
---
### Intent Recognition
| Type | Platform Name | Usage Method | Pricing Model | Notes |
|:------:|:-------------:|:----:|:-------:|:---------------------:|
| Intent | intent_llm | API Call | Based on LLM pricing | Uses large model for intent recognition, highly versatile |
| Intent | function_call | API Call | Based on LLM pricing | Uses large model function calls for intent, fast and effective |
---
## Acknowledgments 🙏
| Logo | Project/Company | Description |
|:---:|:---:|:---|
| <img src="./docs/images/logo_bailing.png" width="160"> | [Bailing Voice Dialogue Robot](https://github.com/wwbin2017/bailing) | This project was inspired by [Bailing Voice Dialogue Robot](https://github.com/wwbin2017/bailing) and implemented based on it |
| <img src="./docs/images/logo_tenclass.png" width="160"> | [Tenclass](https://www.tenclass.com/) | Thanks to [Tenclass](https://www.tenclass.com/) for establishing standard communication protocols, multi-device compatibility solutions, and high-concurrency scenario practices for the Xiaozhi ecosystem; providing full-chain technical documentation support for this project |
| <img src="./docs/images/logo_xuanfeng.png" width="160"> | [Xuanfeng Technology](https://github.com/Eric0308) | Thanks to [Xuanfeng Technology](https://github.com/Eric0308) for contributing the function call framework, MCP communication protocol, and plugin call mechanism implementation code, significantly improving front-end device (IoT) interaction efficiency and functional extensibility through standardized instruction scheduling system and dynamic expansion capabilities |
| <img src="./docs/images/logo_huiyuan.png" width="160"> | [Huiyuan Design](http://ui.kwd988.net/) | Thanks to [Huiyuan Design](http://ui.kwd988.net/) for providing professional visual solutions for this project, empowering the product user experience with their design experience serving over a thousand enterprises |
| <img src="./docs/images/logo_qinren.png" width="160"> | [Xi'an Qinren Information Technology](https://www.029app.com/) | Thanks to [Xi'an Qinren Information Technology](https://www.029app.com/) for deepening the visual system of this project, ensuring consistency and extensibility of the overall design style in multi-scenario applications |
<a href="https://star-history.com/#xinnan-tech/xiaozhi-esp32-server&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
</picture>
</a>
+2 -2
View File
@@ -269,7 +269,7 @@ LLM:
如果你能看到,类似以下日志,则是本项目服务启动成功的标志。 如果你能看到,类似以下日志,则是本项目服务启动成功的标志。
``` ```
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-OTA接口是 http://192.168.4.123:8002/xiaozhi/ota/ 250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-OTA接口是 http://192.168.4.123:8003/xiaozhi/ota/
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-Websocket地址是 ws://192.168.4.123:8000/xiaozhi/v1/ 250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-Websocket地址是 ws://192.168.4.123:8000/xiaozhi/v1/
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-=======上面的地址是websocket协议地址,请勿用浏览器访问======= 250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-=======上面的地址是websocket协议地址,请勿用浏览器访问=======
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-如想测试websocket请用谷歌浏览器打开test目录下的test_page.html 250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
@@ -280,7 +280,7 @@ LLM:
但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。 但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。
最正确的方法,是根据电脑的局域网IP来确定你的接口地址。 最正确的方法,是根据电脑的局域网IP来确定你的接口地址。
如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000/xiaozhi/v1/`,对应的OTA地址就是:`http://192.168.1.25:8002/xiaozhi/ota/` 如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000/xiaozhi/v1/`,对应的OTA地址就是:`http://192.168.1.25:8003/xiaozhi/ota/`
这个信息很有用的,后面`编译esp32固件`需要用到。 这个信息很有用的,后面`编译esp32固件`需要用到。
+1 -1
View File
@@ -213,7 +213,7 @@ CREATE DATABASE xiaozhi_esp32_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unico
如果还没有MySQL,你可以通过docker安装mysql 如果还没有MySQL,你可以通过docker安装mysql
``` ```
docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -d mysql:latest docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -e TZ=Asia/Shanghai -d mysql:latest
``` ```
## 2.安装redis ## 2.安装redis
+9 -1
View File
@@ -104,7 +104,15 @@ VAD:
参考教程[ESP32设备与HomeAssistant集成指南](./homeassistant-integration.md) 参考教程[ESP32设备与HomeAssistant集成指南](./homeassistant-integration.md)
### 8、更多问题,可联系我们反馈 💬 ### 8、如何开启手机注册智控台 📱
参考教程[阿里云短信集成指南](./ali-sms-integration.md)
### 9、如何开启视觉模型实现拍照识物 📷
参考教程[视觉模型使用指南](./mcp-vision-integration.md)
### 10、更多问题,可联系我们反馈 💬
可以在[issues](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues)提交您的问题。 可以在[issues](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues)提交您的问题。
+44
View File
@@ -0,0 +1,44 @@
# 阿里云短信集成指南
登录阿里云控制台,进入“短信服务”页面:https://dysms.console.aliyun.com/overview
## 第一步 添加签名
![步骤](images/alisms/sms-01.png)
![步骤](images/alisms/sms-02.png)
以上步骤,会得到签名,请把它写入到智控台参数,`aliyun.sms.sign_name`
## 第二步 添加模版
![步骤](images/alisms/sms-11.png)
以上步骤,会得到模版code,请把它写入到智控台参数,`aliyun.sms.sms_code_template_code`
注意,签名要等7个工作日,等运营商报备成功后才能发送成功。
注意,签名要等7个工作日,等运营商报备成功后才能发送成功。
注意,签名要等7个工作日,等运营商报备成功后才能发送成功。
可以等报备成功后,再继续往下操作。
## 第三步 创建短信账户和开通权限
登录阿里云控制台,进入“访问控制”页面:https://ram.console.aliyun.com/overview?activeTab=overview
![步骤](images/alisms/sms-21.png)
![步骤](images/alisms/sms-22.png)
![步骤](images/alisms/sms-23.png)
![步骤](images/alisms/sms-24.png)
![步骤](images/alisms/sms-25.png)
以上步骤,会得到access_key_id和access_key_secret,请把它写入到智控台参数,`aliyun.sms.access_key_id``aliyun.sms.access_key_secret`
## 第四步 启动手机注册功能
1、正常来说,以上信息都填完后,会有这个效果,如果没有,可能缺少了某个步骤
![步骤](images/alisms/sms-31.png)
2、开启允许非管理员用户可注册,将参数`server.allow_user_register`设置成`true`
3、开启手机注册功能,将参数`server.enable_mobile_register`设置成`true`
![步骤](images/alisms/sms-32.png)
+163
View File
@@ -0,0 +1,163 @@
# 全模块源码部署自动升级方法
本教程是方便全模块源码部署的爱好者,如何通过自动命令,自动拉取源码,自动编译,自动启动端口运行。实现最高效率的升级系统。
本项目的测试平台`https://2662r3426b.vicp.fun`,从开放以来就使用了该方法,效果良好。
# 开始条件
- 你的电脑/服务器是linux操作系统
- 你已经跑通了整个流程
- 你喜欢跟进最新功能,但是觉得每次手动部署有点麻烦,期待有一个自动更新的方法
第二个条件必须满足,因为本教程所涉及的某些文件,JDK、Node.js环境、Conda环境等,是需要你跑通整个流程才有的,如果你没有跑通,当我讲到某个文件的时候,你可能就不知道什么意思。
# 教程效果
- 解决国内不能拉取最新项目源码问题
- 自动拉取代码编译前端文件
- 自动拉取代码编译java文件,自动杀掉8002端口,自动启动8002端口
- 自动拉取python代码,自动杀掉8000端口,自动启动8000端口
# 第一步 选好你的项目目录
例如,我规划了我的项目目录是,这是一个新建的空白的目录,如果你不想出错,可以和我一样
```
/home/system/xiaozhi
```
# 第二步 克隆本项目
此刻,先要执行第一句话,拉取源码,这句命令适用于国内网络的服务器和电脑,无需翻墙
```
cd /home/system/xiaozhi
git clone https://ghproxy.net/https://github.com/xinnan-tech/xiaozhi-esp32-server.git
```
执行完后,你的项目目录会多了一个文件夹`xiaozhi-esp32-server`,这个就是项目的源码
# 第三步 复制基础的文件
如果你之前已经跑通了整个流程,对funasr的模型文件`xiaozhi-server/models/SenseVoiceSmall/model.pt`和你的私有配置文件`xiaozhi-server/data/.config.yaml`这两个文件不会陌生。
此刻你需要把`model.pt`文件复制到新的目录去,你可以这样
```
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
```
# 第四步 建立三个自动编译文件
## 4.1 自动编译mananger-web模块
`/home/system/xiaozhi/`目录下,创建名字为`update_8001.sh`的文件,内容如下
```
cd /home/system/xiaozhi/xiaozhi-esp32-server
git fetch --all
git reset --hard
git pull origin main
cd /home/system/xiaozhi/xiaozhi-esp32-server/main/manager-web
npm install
npm run build
rm -rf /home/system/xiaozhi/manager-web
mv /home/system/xiaozhi/xiaozhi-esp32-server/main/manager-web/dist /home/system/xiaozhi/manager-web
```
保存好后执行赋权命令
```
chmod 777 update_8001.sh
```
执行完后,继续往下
## 4.2 自动编译运行manager-api模块
`/home/system/xiaozhi/`目录下,创建名字为`update_8002.sh`的文件,内容如下
```
cd /home/system/xiaozhi/xiaozhi-esp32-server
git pull origin main
cd /home/system/xiaozhi/xiaozhi-esp32-server/main/manager-api
rm -rf target
mvn clean package -Dmaven.test.skip=true
cd /home/system/xiaozhi/
# 查找占用8002端口的进程号
PID=$(sudo netstat -tulnp | grep 8002 | awk '{print $7}' | cut -d'/' -f1)
rm -rf /home/system/xiaozhi/xiaozhi-esp32-api.jar
mv /home/system/xiaozhi/xiaozhi-esp32-server/main/manager-api/target/xiaozhi-esp32-api.jar /home/system/xiaozhi/xiaozhi-esp32-api.jar
# 检查是否找到进程号
if [ -z "$PID" ]; then
echo "没有找到占用8002端口的进程"
else
echo "找到占用8002端口的进程,进程号为: $PID"
# 杀掉进程
kill -9 $PID
kill -9 $PID
echo "已杀掉进程 $PID"
fi
nohup java -jar xiaozhi-esp32-api.jar --spring.profiles.active=dev &
```
保存好后执行赋权命令
```
chmod 777 update_8002.sh
```
执行完后,继续往下
## 4.3 自动编译运行Python项目
`/home/system/xiaozhi/`目录下,创建名字为`update_8000.sh`的文件,内容如下
```
cd /home/system/xiaozhi/xiaozhi-esp32-server
git pull origin main
# 查找占用8000端口的进程号
PID=$(sudo netstat -tulnp | grep 8000 | awk '{print $7}' | cut -d'/' -f1)
# 检查是否找到进程号
if [ -z "$PID" ]; then
echo "没有找到占用8000端口的进程"
else
echo "找到占用8000端口的进程,进程号为: $PID"
# 杀掉进程
kill -9 $PID
kill -9 $PID
echo "已杀掉进程 $PID"
fi
cd main/xiaozhi-server
pip install -r requirements.txt
nohup python app.py >/dev/null &
```
保存好后执行赋权命令
```
chmod 777 update_8000.sh
```
执行完后,继续往下
# 日常更新
以上的脚本都建立好后,日常更新,我们只要依次执行以下命令就可以做到自动更新和启动
```
# 进入pyhton环境
conda activate xiaozhi-esp32-server
cd /home/system/xiaozhi
# 更新并启动Java程序
./update_8001.sh
# 更新web程序
./update_8002.sh
# 更新并启动python程序
./update_8000.sh
# 查看Java日志
tail -f nohup.out
# 查看Python日志
tail -f /home/system/xiaozhi/xiaozhi-esp32-server/main/xiaozhi-server/tmp/server.log
```
# 注意事项
测试平台`https://2662r3426b.vicp.fun`,是使用nginx做了反向代理。nginx.conf详细配置可以[参考这里](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/791)
+3 -3
View File
@@ -42,9 +42,9 @@ http {
proxy_set_header Referer $http_referer; proxy_set_header Referer $http_referer;
proxy_set_header Cookie $http_cookie; proxy_set_header Cookie $http_cookie;
proxy_connect_timeout 10; proxy_connect_timeout 15;
proxy_send_timeout 10; proxy_send_timeout 15;
proxy_read_timeout 10; proxy_read_timeout 15;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+1
View File
@@ -6,6 +6,7 @@ java -jar /app/xiaozhi-esp32-api.jar \
--spring.datasource.druid.username=${SPRING_DATASOURCE_DRUID_USERNAME} \ --spring.datasource.druid.username=${SPRING_DATASOURCE_DRUID_USERNAME} \
--spring.datasource.druid.password=${SPRING_DATASOURCE_DRUID_PASSWORD} \ --spring.datasource.druid.password=${SPRING_DATASOURCE_DRUID_PASSWORD} \
--spring.data.redis.host=${SPRING_DATA_REDIS_HOST} \ --spring.data.redis.host=${SPRING_DATA_REDIS_HOST} \
--spring.data.redis.password=${SPRING_DATA_REDIS_PASSWORD} \
--spring.data.redis.port=${SPRING_DATA_REDIS_PORT} & --spring.data.redis.port=${SPRING_DATA_REDIS_PORT} &
# 启动Nginx(前台运行保持容器存活) # 启动Nginx(前台运行保持容器存活)
+1 -1
View File
@@ -9,7 +9,7 @@
### 如果你用的是简单Server部署 ### 如果你用的是简单Server部署
此刻,请你用浏览器打开你的ota地址,例如我的ota地址 此刻,请你用浏览器打开你的ota地址,例如我的ota地址
``` ```
http://192.168.1.25:8002/xiaozhi/ota/ http://192.168.1.25:8003/xiaozhi/ota/
``` ```
如果显示“OTA接口运行正常,向设备发送的websocket地址是:ws://xxx:8000/xiaozhi/v1/ 如果显示“OTA接口运行正常,向设备发送的websocket地址是:ws://xxx:8000/xiaozhi/v1/
+2 -1
View File
@@ -1,7 +1,7 @@
# 基于虾哥编译好的固件配置自定义服务器 # 基于虾哥编译好的固件配置自定义服务器
## 第1步 确认版本 ## 第1步 确认版本
烧录虾哥已经编译好的1.6.1版本固件 烧录虾哥已经编译好的[1.6.1版本以上固件](https://github.com/78/xiaozhi-esp32/releases)
## 第2步 准备你的ota地址 ## 第2步 准备你的ota地址
如果你按照教程使用的是全模块部署,就应该会有ota地址。 如果你按照教程使用的是全模块部署,就应该会有ota地址。
@@ -29,6 +29,7 @@ wss://2662r3426b.vicp.fun/xiaozhi/v1/
## 第3步 进入配网模式 ## 第3步 进入配网模式
进入机器的配网模式,在页面顶部,点击“高级选项”,在里面输入你服务器的`ota`地址,点击保存。重启设备 进入机器的配网模式,在页面顶部,点击“高级选项”,在里面输入你服务器的`ota`地址,点击保存。重启设备
![请参考-OTA地址设置](../docs/images/firmware-setting-ota.png)
## 第4步 唤醒小智,查看日志输出 ## 第4步 唤醒小智,查看日志输出
+72
View File
@@ -0,0 +1,72 @@
登录AutoDL,租赁镜像
选择镜像:
```
PyTorch / 2.1.0 / 3.10(ubuntu22.04) / cuda 12.1
```
机器开机后,设置学术加速
```
source /etc/network_turbo
```
进入工作目录
```
cd autodl-tmp/
```
拉取项目
```
git clone https://gitclone.com/github.com/fishaudio/fish-speech.git ; cd fish-speech
```
安装依赖
```
pip install -e.
```
如果报错,安装portaudio
```
apt-get install portaudio19-dev -y
```
安装后执行
```
pip install torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 --index-url https://download.pytorch.org/whl/cu121
```
下载模型
```
cd tools
python download_models.py
```
下载完模型后运行接口
```
python -m tools.api_server --listen 0.0.0.0:6006
```
然后用浏览器去到aotodl实例页面
```
https://autodl.com/console/instance/list
```
如下图点击你刚才机器的`自定义服务`按钮,开启端口转发服务
![自定义服务](images/fishspeech/autodl-01.png)
端口转发服务设置完成后,你本地电脑打开网址`http://localhost:6006/`,就可以访问fish-speech的接口了
![服务预览](images/fishspeech/autodl-02.png)
如果你是单模块部署,核心配置如下
```
selected_module:
TTS: FishSpeech
TTS:
FishSpeech:
reference_audio: ["config/assets/wakeup_words.wav",]
reference_text: ["哈啰啊,我是小智啦,声音好听的台湾女孩一枚,超开心认识你耶,最近在忙啥,别忘了给我来点有趣的料哦,我超爱听八卦的啦",]
api_key: "123"
api_url: "http://127.0.0.1:6006/v1/tts"
```
然后重启服务
+164 -49
View File
@@ -1,4 +1,8 @@
# ESP32设备与HomeAssistant集成指南 # 小智ESP32-开源服务端与HomeAssistant集成指南
[TOC]
-----
## 简介 ## 简介
@@ -7,26 +11,58 @@
## 前提条件 ## 前提条件
- 已安装并配置好`HomeAssistant` - 已安装并配置好`HomeAssistant`
- 已经在HomeAssistant中集成`Xiaomi Home`,并将米家的设备导入进`HomeAssistant`
- `xiaozhi-esp32-server智控台`能正常使用
- 本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用 - 本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用
## 网络环境 ## 开始前的操作(必要)
### 1. 获取HA的网络网络地址信息
请访问你Home Assistant的网络地址,例如,我的HA的地址是192.168.4.7,端口则是默认的8123,则在浏览器打开
1、我的`HomeAssistant`部署在下面,版本是`2025.3.4`
``` ```
http://192.168.4.7:8123 http://192.168.4.7:8123
``` ```
2、我的`xiaozhi-esp32-server智控台``HomeAssistant`部署在同一台机器的另一个端口,版本是`0.3.10` > 手动查询 HA 的 IP 地址方法**(仅限小智esp32-server和HA部署在同一个网络设备[例如同一个wifi]下)**:
>
> 1. 进入 Home Assistant(前端)。
>
> 2. 点击左下角 **设置(Settings** → **系统(System** → **网络(Network**。
>
> 3. 滑到最底部`Home Assistant 网址(Home Assistant website)`区域,在`本地网络(local network)`中,点击`眼睛`按钮,可以看到当前使用的 IP 地址(如 `192.168.1.10`)和网络接口。点击`复制连接(copy link)`可以直接复制。
>
> ![image-20250504051716417](images/image-ha-integration-01.png)
或,您已经设置了直接可以访问的Home Assistant的OAuth地址,您也可以在浏览器内直接访问
``` ```
http://192.168.4.7:8002 http://homeassistant.local:8123
``` ```
### 2. 登录`Home Assistant`拿到开发密钥
## 配置步骤 登录`HomeAssistant`,点击`左下角头像 -> 个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key,并复制保存,后续的方法都需要使用这个api key且仅出现一次(小tips: 您可以保存生成的二维码图像,后续可以扫描二维码再此提取api key)。
### 1. 登录`HomeAssistant`整理需要控制的设备清单 ## 方法1:小智社区共建的HA调用功能
### 功能描述
- 如您后续需要增加新的设备,该方法需要手动重启`xiaozhi-esp32-server服务端`以此更新设备信息**(重要**)。
- 需要您确保已经在HomeAssistant中集成`Xiaomi Home`,并将米家的设备导入进`HomeAssistant`
- 需要您确保`xiaozhi-esp32-server智控台`能正常使用。
- 我的`xiaozhi-esp32-server智控台``HomeAssistant`部署在同一台机器的另一个端口,版本是`0.3.10`
```
http://192.168.4.7:8002
```
### 配置步骤
#### 1. 登录`HomeAssistant`整理需要控制的设备清单
登录`HomeAssistant`,点击`左下角的设置`,然后进入`设备与服务`,再点击顶部的`实体`。 登录`HomeAssistant`,点击`左下角的设置`,然后进入`设备与服务`,再点击顶部的`实体`。
@@ -53,59 +89,138 @@ http://192.168.4.7:8002
公司,台灯,switch.iot_cn_831898993_socn1_on_p_2_1; 公司,台灯,switch.iot_cn_831898993_socn1_on_p_2_1;
``` ```
这段字符,我们为“设备清单字符”需要保存好,等一下有用。 这段字符,我们为“设备清单字符”需要保存好,等一下有用。
#### 2. 登录`智控台`
![image-20250504051716417](images/image-ha-integration-06.png)
使用管理员账号,登录`智控台`。在`智能体管理`,找到你的智能体,再点击`配置角色`。
将意图识别设置成`函数调用`或`LLM意图识别`。这时你会看到右侧有一个`编辑功能`。点击`编辑功能`按钮,会弹出`功能管理`的框。
在`功能管理`的框里,你需要勾选`HomeAssistant设备状态查询`和`HomeAssistant设备状态修改`。
勾选后,在`已选功能`点击`HomeAssistant设备状态查询`,然后在`参数配置`里配置你的`HomeAssistant`地址、密钥、设备清单字符。
编辑好后,点击`保存配置`,这时`功能管理`的框会隐藏,这时你再点击保存智能体配置。
保存成功后,即可唤醒设备操作。
#### 3. 唤醒设别进行控制
尝试和esp32说,“打开XXX灯”
## 方法2:小智将Home Assistant的语音助手作为LLM工具
### 功能描述
- 该方法有一个比较严重的缺点——**该方法无法使用小智开源生态的function_call插件功能的能力**,因为使用Home Assistant作为小智的LLM工具会将意图识别能力转让给Home Assistant。但是**这个方法是能体验到原生的Home Assistant操作功能,且小智的聊天能力不变**。如实在介意可以使用同样是Home Assistant支持的[方法3](##方法3:使用Home Assistant的MCP服务(推荐)),能够最大程度体验到Home Assistant的功能。
### 配置步骤:
#### 1. 配置Home Assistant的大模型语音助手。
**需要您提前配置好Home Assistant的语音助手或大模型工具。**
#### 2. 获取Home Assistant的语言助手的Agent ID.
1. 进入Home Assistant页面内。左侧点击`开发者助手`。
2. 在打开的`开发者助手`内,点击`动作`选项卡(如图示操作1),在页面内的选项栏`动作`中,找到或输入`conversation.process(对话-处理)`并选择`对话(conversation: 处理`(如图示操作2)。
![image-20250504043539343](images/image-ha-integration-02.png)
3. 在页面内勾选`代理(agent)`选项,在变成常亮的`对话代理(conversation agent)`内选择您步骤一配置好的语音助手名称,如图示,我这边配置好的是`ZhipuAi`并选择。
![image-20250504043854760](images/image-ha-integration-03.png)
4. 选中后,点击表单左下方的`进入YAML模式`。
![image-20250504043951126](images/image-ha-integration-04.png)
5. 复制其中的agent-id的值,例如图示中我的是`01JP2DYMBDF7F4ZA2DMCF2AGX2`(仅供参考)。
![image-20250504044046466](images/image-ha-integration-05.png)
6. 切换到小智开源服务端`xiaozhi-esp32-server`的`config.yaml`文件内,在LLM配置中,找到Home Assistant,设置您的Home Assistant的网络地址,Api key和刚刚查询到的agent_id。
7. 修改`config.yaml`文件内的`selected_module`属性的`LLM`为`HomeAssistant``Intent`为`nointent`。
8. 重启小智开源服务端`xiaozhi-esp32-server`即可正常使用。
## 方法3:使用Home Assistant的MCP服务(推荐)
### 功能描述
- 需要您提前在Home Assistant内集成并安装好HA集成——[Model Context Protocol Server](https://www.home-assistant.io/integrations/mcp_server/)。
- 这个方法与方法2都是HA官方提供的解决方法,与方法2不同的是,您可以正常使用小智开源服务端`xiaozhi-esp32-server`的开源共建的插件,同时允许您随意使用任何一个支持function_call功能的LLM大模型。
### 配置步骤
#### 1. 安装Home Assistant的MCP服务集成。
集成官方网址——[Model Context Protocol Server](https://www.home-assistant.io/integrations/mcp_server/)。。
或跟随以下手动操作。
> - 前往Home Assistant页面的**[设置 > 设备和服务(Settings > Devices & Services.](https://my.home-assistant.io/redirect/integrations)**。
>
> - 在右下角,选择 **[添加集成(Add Integration](https://my.home-assistant.io/redirect/config_flow_start?domain=mcp_server)**按钮。
>
> - 从列表中选择**模型上下文协议服务器(Model Context Protocol Server**。
>
> - 按照屏幕上的说明完成设置。
#### 2. 配置小智开源服务端MCP配置信息
### 2. 登录`HomeAssistant`拿到开发密钥 进入`data`目录,找到`.mcp_server_settings.json`文件。
登录`HomeAssistant`,点击`左下角个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key。 如果你的`data`目录下没有`.mcp_server_settings.json`文件,
- 请把在`xiaozhi-server`文件夹根目录的`mcp_server_settings.json`文件复制到`data`目录下,并重命名为`.mcp_server_settings.json`
- 或[下载这个文件](https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/main/xiaozhi-server/mcp_server_settings.json),下载到`data`目录下,并重命名为`.mcp_server_settings.json`
### 3. 登录`智控台` 修改`"mcpServers"`里的这部分的内容:
使用管理员账号,登录`智控台`。点击顶部菜单`参数管理`,搜索`plugins.home_assistant.`,会有三条结果出来 ```json
"Home Assistant": {
编辑`plugins.home_assistant.devices`,把刚才整理的设备清单字符粘贴进去。 "command": "mcp-proxy",
"args": [
"http://YOUR_HA_HOST/mcp_server/sse"
编辑`plugins.home_assistant.base_url`,把你部署的`HomeAssistant`接口地址粘贴进去,我粘贴进去的地址是这样的 ],
"env": {
``` "API_ACCESS_TOKEN": "YOUR_API_ACCESS_TOKEN"
http://192.168.4.7:8123 }
},
``` ```
编辑`plugins.home_assistant.api_key`,把你从`HomeAssistant`复制过来的密钥,粘贴进去 注意:
1. **替换配置:**
- 替换`args`内的`YOUR_HA_HOST`为您的HA服务地址,如果你的服务地址已经包含了https/http字样(例如`http://192.168.1.101:8123`),则只需要填入`192.168.1.101:8123`即可。
- 将`env`内`API_ACCESS_TOKEN`的`YOUR_API_ACCESS_TOKEN`替换成您之前获取到的开发密钥api key。
2. **如果你添加配置是在`"mcpServers"`的括号内后续没有新的`mcpServers`的配置时,需要把最后的逗号`,`移除**,否则可能会解析失败。
### 4. 设置`意图识别`函数 **最后效果参考以下(参考如下)**:
在智控台,点击顶部菜单“模型配置”,在左侧栏,找到“意图识别”,找到id为`Intent_function_call`的意图,点击编辑 ```json
"mcpServers": {
然后在弹框中,在原来的基础上追加两个函数:“hass_get_state”和“hass_set_state” "Home Assistant": {
"command": "mcp-proxy",
修改前 "args": [
``` "http://192.168.1.101:8123/mcp_server/sse"
change_role;get_weather;get_news;play_music ],
"env": {
"API_ACCESS_TOKEN": "abcd.efghi.jkl"
}
}
}
``` ```
修改后 #### 3. 配置小智开源服务端的系统配置
``` 1. **选择任意一款支持function_call的LLM大模型作为小智的LLM聊天助手(但不要选择Home Assistant作为LLM工具)**,本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用,但部分时候调用不太稳定,如果像追求稳定建议把LLM设置成:DoubaoLLM,使用的具体model_name是:doubao-1-5-pro-32k-250115。
change_role;get_weather;get_news;play_music;hass_get_state;hass_set_state
```
### 6. 手动重启xiaozhi-server 2. 切换到小智开源服务端`xiaozhi-esp32-server`的`config.yaml`文件内,设置您的LLM大模型配置,并且将`selected_module`配置的`Intent`调整为`function_call`。
重启xiaozhi-server程序 3. 重启小智开源服务端`xiaozhi-esp32-server`即可正常使用。
### 5. 确认角色配置是否设置了函数意图识别
在智控台,点击顶部菜单“智能体管理”,找到设备所在的智能体,点击“配置角色”
确认意图识别(Intent),是否选择“函数调用意图识别”
### 6. 唤醒设别进行控制
尝试和esp32说,“打开XXX灯”
Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

+134
View File
@@ -0,0 +1,134 @@
# MCP 接入点部署使用指南
本教程包含2个部分
- 1、如何开启mcp接入点
- 2、如何为智能体接入一个简单的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。
# 如何为智能体接入一个简单的mcp功能,如计算器功能
如果以上步骤顺利,你可以进入智能体管理,点击`配置角色`,在`意图识别`的右边,有一个`编辑功能`的按钮。
点击这个按钮。在弹出的页面里,位于底部,会有`MCP接入点`,正常来说,会显示这个智能体的`MCP接入点地址`,接下来,我们来给这个智能体扩展一个基于MCP技术的计算器的功能。
这个`MCP接入点地址`很重要,你等一下会用到。
## 第一步 下载虾哥MCP计算器项目代码
浏览器打开虾哥写的[计算器项目](https://github.com/78/mcp-calculator)
打开完,找到页面中一个绿色的按钮,写着`Code`的按钮,点开它,然后你就看到`Download ZIP`的按钮。
点击它,下载本项目源码压缩包。下载到你电脑后,解压它,此时它的名字可能叫`mcp-calculatorr-main`
你需要把它重命名成`mcp-calculator`。接下来,我们用命令行进入项目目录即安装依赖
```bash
# 进入项目目录
cd mcp-calculator
conda remove -n mcp-calculator --all -y
conda create -n mcp-calculator python=3.10 -y
conda activate mcp-calculator
pip install -r requirements.txt
```
## 第二步 启动
启动前,先从你的智控台的智能体里,复制到了MCP接入点的地址。
例如我的智能体的mcp地址是
```
ws://192.168.4.7:8004/mcp_endpoint/mcp/?token=abc
```
开始输入命令
```bash
export MCP_ENDPOINT=ws://192.168.4.7:8004/mcp_endpoint/mcp/?token=abc
```
输入完后,启动程序
```bash
python mcp_pipe.py calculator.py
```
启动完后,你再进入智控台,点击刷新MCP的接入状态,就会看到你扩展的功能列表了。
+171
View File
@@ -0,0 +1,171 @@
# 视觉模型使用指南
本教程分为两部分:
- 第一部分:单模块运行xiaozhi-server开启视觉模型
- 第二部分:全模块运行时,如何开启视觉模型
开启视觉模型前,你需要准备三件事:
- 你需要准备一台带摄像头的设备,而且这台设备已经在虾哥仓库里,实现了调用摄像头功能。例如`立创·实战派ESP32-S3开发板`
- 你设备固件的版本升级到1.6.6及以上
- 你已经成功跑通基础对话模块
## 单模块运行xiaozhi-server开启视觉模型
### 第一步确认网络
由于视觉模型会默认启动8003端口。
如果你是docker运行,请确认一下你的`docker-compose.yml`是否放了`8003`端口,如果没有就更新最新的`docker-compose.yml`文件
如果你是源码运行,确认防火墙是否放行`8003`端口
### 第二步选择你的视觉模型
打开你的`data/.config.yaml`文件,设置你的`selected_module.VLLM`设置为某个视觉模型。目前我们已经支持`openai`类型接口的视觉模型。`ChatGLMVLLM`就是其中一款兼容`openai`的模型。
```
selected_module:
VAD: ..
ASR: ..
LLM: ..
VLLM: ChatGLMVLLM
TTS: ..
Memory: ..
Intent: ..
```
假设我们使用`ChatGLMVLLM`作为视觉模型,那我们需要先登录[智谱AI](https://bigmodel.cn/usercenter/proj-mgmt/apikeys)网站,申请密钥。如果你之前已经申请过了密钥,可以复用这个密钥。
在你的配置文件中,增加这个配置,如果已经有了这个配置,就设置好你的api_key。
```
VLLM:
ChatGLMVLLM:
api_key: 你的api_key
```
### 第三步启动xiaozhi-server服务
如果你是源码,就输入命令启动
```
python app.py
```
如果你是docker运行,就重启容器
```
docker restart xiaozhi-esp32-server
```
启动后会输出以下内容的日志。
```
2025-06-01 **** - OTA接口是 http://192.168.4.7:8003/xiaozhi/ota/
2025-06-01 **** - 视觉分析接口是 http://192.168.4.7:8003/mcp/vision/explain
2025-06-01 **** - Websocket地址是 ws://192.168.4.7:8000/xiaozhi/v1/
2025-06-01 **** - =======上面的地址是websocket协议地址,请勿用浏览器访问=======
2025-06-01 **** - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
2025-06-01 **** - =============================================================
```
启动后,使用使用浏览器打开日志里`视觉分析接口`连接。看看输出了什么?如果你是linux,没有浏览器,你可以执行这个命令:
```
curl -i 你的视觉分析接口
```
正常来说会这样显示
```
MCP Vision 接口运行正常,视觉解释接口地址是:http://xxxx:8003/mcp/vision/explain
```
请注意,如果你是公网部署,或者docker部署,一定要改一下你的`data/.config.yaml`里这个配置
```
server:
vision_explain: http://你的ip或者域名:端口号/mcp/vision/explain
```
为什么呢?因为视觉解释接口需要下发到设备,如果你的地址是局域网地址,或者是docker内部地址,设备是无法访问的。
假设你的公网地址是`111.111.111.111`,那么`vision_explain`应该这么配
```
server:
vision_explain: http://111.111.111.111:8003/mcp/vision/explain
```
如果你的MCP Vision 接口运行正常,且你也试着用浏览器访问正常打开下发的`视觉解释接口地址`,请继续下一步
### 第四步 设备唤醒开启
对设备说“请打开摄像头,说你你看到了什么”
留意xiaozhi-server的日志输出,看看有没有报错。
## 全模块运行时,如何开启视觉模型
### 第一步 确认网络
由于视觉模型会默认启动8003端口。
如果你是docker运行,请确认一下你的`docker-compose_all.yml`是否映射了`8003`端口,如果没有就更新最新的`docker-compose_all.yml`文件
如果你是源码运行,确认防火墙是否放行`8003`端口
### 第二步 确认你配置文件
打开你的`data/.config.yaml`文件,确认一下你的配置文件的结构,是否和`data/config_from_api.yaml`一样。如果不一样,或缺少某项,请补齐。
### 第三步 配置视觉模型密钥
那我们需要先登录[智谱AI](https://bigmodel.cn/usercenter/proj-mgmt/apikeys)网站,申请密钥。如果你之前已经申请过了密钥,可以复用这个密钥。
登录`智控台`,顶部菜单点击`模型配置`,在左侧栏点击`视觉打语言模型`,找到`VLLM_ChatGLMVLLM`,点击修改按钮,在弹框中,在`API密钥`输入你密钥,点击保存。
保存成功后,去到你需要测试的智能体哪里,点击`配置角色`,在打开的内容里,查看`视觉大语言模型(VLLM)`是否选择了刚才的视觉模型。点击保存。
### 第三步 启动xiaozhi-server模块
如果你是源码,就输入命令启动
```
python app.py
```
如果你是docker运行,就重启容器
```
docker restart xiaozhi-esp32-server
```
启动后会输出以下内容的日志。
```
2025-06-01 **** - 视觉分析接口是 http://192.168.4.7:8003/mcp/vision/explain
2025-06-01 **** - Websocket地址是 ws://192.168.4.7:8000/xiaozhi/v1/
2025-06-01 **** - =======上面的地址是websocket协议地址,请勿用浏览器访问=======
2025-06-01 **** - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
2025-06-01 **** - =============================================================
```
启动后,使用使用浏览器打开日志里`视觉分析接口`连接。看看输出了什么?如果你是linux,没有浏览器,你可以执行这个命令:
```
curl -i 你的视觉分析接口
```
正常来说会这样显示
```
MCP Vision 接口运行正常,视觉解释接口地址是:http://xxxx:8003/mcp/vision/explain
```
请注意,如果你是公网部署,或者docker部署,一定要改一下你的`data/.config.yaml`里这个配置
```
server:
vision_explain: http://你的ip或者域名:端口号/mcp/vision/explain
```
为什么呢?因为视觉解释接口需要下发到设备,如果你的地址是局域网地址,或者是docker内部地址,设备是无法访问的。
假设你的公网地址是`111.111.111.111`,那么`vision_explain`应该这么配
```
server:
vision_explain: http://111.111.111.111:8003/mcp/vision/explain
```
如果你的MCP Vision 接口运行正常,且你也试着用浏览器访问正常打开下发的`视觉解释接口地址`,请继续下一步
### 第四步 设备唤醒开启
对设备说“请打开摄像头,说你你看到了什么”
留意xiaozhi-server的日志输出,看看有没有报错。
+105
View File
@@ -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. 多个新闻源之间使用英文分号(;)分隔,不要使用中文分号(;)
+457 -7
View File
@@ -1,7 +1,70 @@
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3) # 技术文档:`xiaozhi-esp32-server`
# 项目目录介绍 **目录:**
如果你是一名软件开发者,这里有一份[《致开发者的公开信》](../docs/contributor_open_letter.md),欢迎归队!
1. [引言](#1-引言)
2. [整体架构](#2-整体架构)
3. [核心组件深度剖析](#3-核心组件深度剖析)
* [3.1. `xiaozhi-server` (核心AI引擎 - Python实现)](#31-xiaozhi-server-核心ai引擎---python实现)
* [3.2. `manager-api` (管理后端 - Java Spring Boot实现)](#32-manager-api-管理后端---java-spring-boot实现)
* [3.3. `manager-web` (Web管理前端 - Vue.js实现)](#33-manager-web-web管理前端---vuejs实现)
4. [数据流与交互机制](#4-数据流与交互机制)
5. [核心功能概要](#5-核心功能概要)
6. [部署与配置概述](#6-部署与配置概述)
---
## 1. 引言
`xiaozhi-esp32-server` 项目是一个专为基于ESP32的智能硬件提供支持的**综合性后端系统**。其核心目标是使开发人员能够快速构建一个强大的服务器基础设施,该设施不仅能够理解自然语言指令,还能与多种AI服务(用于语音识别、自然语言理解及语音合成)进行高效交互、管理物联网(IoT)设备,并提供一个基于Web的用户界面以进行系统配置和管理。通过将多种尖端技术整合到一个高内聚且可扩展的平台中,本项目旨在简化和加速可定制化语音助手及智能控制系统的开发进程。它不仅仅是一个简单的服务器,更是一个连接硬件、AI能力与用户管理的桥梁。
---
## 2. 整体架构
`xiaozhi-esp32-server` 系统采用了一种**分布式、多组件协作**的架构设计,确保了系统的模块化、可维护性和可扩展性。各个核心组件各司其职,协同工作。主要组件包括:
1. **ESP32 硬件 (客户端设备):**
这是终端用户直接与之交互的物理智能硬件设备。其主要职责包括:
* 捕捉用户的语音指令。
* 将捕捉到的原始音频数据安全地发送至 `xiaozhi-server` 进行处理。
* 接收来自 `xiaozhi-server` 合成的语音回复,并通过扬声器播放给用户。
* 根据从 `xiaozhi-server` 收到的指令,控制与之连接的其他外围设备或IoT设备(例如智能灯泡、传感器等)。
2. **`xiaozhi-server` (核心AI引擎 - Python实现):**
这个基于Python的服务器是整个系统的“大脑”,负责处理所有语音相关的逻辑和AI交互。其关键职责细化如下:
* 通过WebSocket协议与ESP32设备建立**稳定、低延迟的实时双向通信链路**。
* 接收来自ESP32的音频流,并利用语音活动检测(VAD)技术精确切分有效的语音片段。
* 集成并调用自动语音识别(ASR)服务(可配置本地或云端),将语音片段转换为文本。
* 通过与大型语言模型(LLM)的交互来解析用户意图、生成智能回复,并支持复杂的自然语言理解任务。
* 管理多轮对话中的上下文信息和用户记忆,以提供连贯的交互体验。
* 调用文本转语音(TTS)服务,将LLM生成的文本回复合成为自然流畅的语音。
* 通过一个灵活的**插件系统**执行自定义命令,包括对IoT设备的控制逻辑。
*`manager-api` 服务获取其详细的运行时操作配置。
3. **`manager-api` (管理后端 - Java实现):**
这是一个基于Java Spring Boot框架构建的应用程序,它为整个系统的管理和配置提供了一套安全的RESTful API。它不仅是 `manager-web` 控制台的后端支撑,也是 `xiaozhi-server` 的配置数据来源。其核心功能包括:
* 为Web控制台提供用户认证(登录、权限验证)和用户账户管理功能。
* ESP32设备的注册、信息管理以及设备特定配置的维护。
* 在**MySQL数据库**中持久化存储系统配置,例如用户选择的AI服务提供商、API密钥、设备参数、插件设置等。
* 提供特定的API端点,供 `xiaozhi-server` 拉取其所需的最新配置。
* 管理TTS音色选项、处理OTAOver-The-Air)固件更新流程及相关元数据。
* 利用 **Redis** 作为高速缓存,存储热点数据(如会话信息、频繁访问的配置),以提升API响应速度和系统整体性能。
4. **`manager-web` (Web控制面板 - Vue.js实现):**
这是一个基于Vue.js构建的单页应用(SPA),为系统管理员提供了一个图形化、用户友好的操作界面。其主要能力包括:
* 便捷地配置 `xiaozhi-server` 所使用的各项AI服务(如ASR、LLM、TTS的提供商切换、参数调整)。
* 管理平台用户账户、角色分配及权限控制。
* 管理已注册的ESP32设备及其相关设置。
* (潜在功能)监控系统运行状态、查看日志、进行故障排查等。
*`manager-api` 提供的所有后端管理功能进行全面的交互。
**高层交互流程概述:**
* **语音交互主线:** **ESP32设备**捕捉到用户语音后,通过**WebSocket**将音频数据实时传输给**`xiaozhi-server`**。`xiaozhi-server`完成一系列AI处理(VAD、ASR、LLM交互、TTS)后,再通过WebSocket将合成的语音回复发送回ESP32设备进行播放。所有与语音直接相关的实时交互均在此链路完成。
* **管理配置主线:** 管理员通过浏览器访问**`manager-web`**控制台。`manager-web`通过调用**`manager-api`**提供的**RESTful HTTP接口**来执行各种管理操作(如修改配置、管理用户或设备)。数据以JSON格式在两者间传递。
* **配置同步:** **`xiaozhi-server`**在启动或特定更新机制触发时,会主动通过HTTP请求从**`manager-api`**拉取其最新的操作配置。这确保了管理员在Web界面上所做的配置更改能够及时有效地应用到核心AI引擎的运行中。
这种**前后端分离、核心服务与管理服务分离**的架构设计,使得 `xiaozhi-server`能够专注于高效的实时AI处理任务,而 `manager-api``manager-web` 则共同提供了一个功能强大且易于使用的管理和配置平台。各组件职责清晰,有利于独立开发、测试、部署和扩展。
``` ```
xiaozhi-esp32-server xiaozhi-esp32-server
@@ -10,10 +73,397 @@ xiaozhi-esp32-server
├─ manager-api 8002 端口 Java语言开发 负责提供控制台的api ├─ manager-api 8002 端口 Java语言开发 负责提供控制台的api
``` ```
# xiaozhi-server 和ESP32通讯协议 ---
https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh ## 3. 核心组件深度剖析
# manager-web 、manager-api接口协议 ### 3.1. `xiaozhi-server` (核心AI引擎 - Python实现)
https://2662r3426b.vicp.fun/xiaozhi/v1/doc.html `xiaozhi-server` 作为系统的智能核心,全权负责处理语音交互、对接各类AI服务以及管理与ESP32设备间的通信。其设计目标是实现高效、灵活且可扩展的语音AI处理能力。
* **核心目标:**
* 为ESP32设备提供实时的语音指令处理服务。
* 深度集成各类AI服务,包括:自动语音识别 (ASR)、大型语言模型 (LLM) 进行自然语言理解 (NLU)、文本转语音 (TTS)、语音活动检测 (VAD)、意图识别 (Intent Recognition) 及对话记忆 (Memory)。
* 精细管理用户与设备间的对话流程及上下文状态。
* 基于用户指令,通过插件化机制执行自定义函数及控制物联网 (IoT) 设备。
* 支持通过 `manager-api`进行动态配置加载与更新。
* **核心技术栈:**
* **Python 3:** 作为主要编程语言,Python以其丰富的AI/ML生态库和快速开发特性被选用。
* **Asyncio:** Python的异步编程框架,是`xiaozhi-server`高性能的关键。它被广泛用于高效处理来自大量ESP32设备的并发WebSocket连接,以及执行与外部AI服务API通信时的非阻塞I/O操作,确保服务器在高并发下的响应能力。
* **`websockets` 库:** 提供WebSocket服务器的具体实现,支持与ESP32客户端进行全双工实时通信。
* **HTTP客户端 (如 `aiohttp`, `httpx`):** 用于异步执行HTTP请求,主要目的是从`manager-api`获取配置信息,以及与云端AI服务的API进行交互。
* **YAML (通常通过 PyYAML 库):** 用于解析本地的 `config.yaml` 配置文件。
* **FFmpeg (外部依赖):**`app.py` 启动时会进行检查 (`check_ffmpeg_installed()`)。FFmpeg通常用于音频处理和格式转换,例如,确保音频数据符合特定AI服务的要求或进行内部处理。
* **关键实现细节:**
1. **AI服务提供者模式 (Provider Pattern - `core/providers/`):**
* **设计思想:** 这是`xiaozhi-server`集成不同AI服务的核心设计模式,极大地增强了系统的灵活性和可扩展性。针对每一种AI服务类型(ASR, TTS, LLM, VAD, Intent, Memory, VLLM),都在其对应子目录下定义了一个抽象基类 (ABC, Abstract Base Class),例如 `core/providers/asr/base.py`。这个基类规定了该类型服务必须实现的通用接口方法(如ASR的 `async def transcribe(self, audio_chunk: bytes) -> str: pass`)。
* **具体实现:** 各种具体的AI服务提供商或本地模型的实现,则以独立的Python类形式存在(例如 `core/providers/asr/fun_local.py` 实现了本地FunASR的逻辑,`core/providers/llm/openai.py` 实现了与OpenAI GPT模型的对接)。这些具体类继承自相应的抽象基类,并实现其定义的接口。部分提供者还使用DTOs (Data Transfer Objects, 存在于各自的 `dto/` 目录) 来结构化与外部服务交换的数据。
* **优势:** 使得核心业务逻辑能够以统一的方式调用不同的AI服务,而无需关心其底层具体实现。用户可以通过配置文件轻松切换AI服务后端。添加对新AI服务的支持也变得相对简单,只需实现对应的Provider接口。
* **动态加载与初始化:** `core/utils/modules_initialize.py` 脚本扮演了工厂的角色。它在服务器启动时,或在接收到配置更新指令时,会根据配置文件中 `selected_module` 及各项服务的具体provider设置,动态地导入并实例化相应的Provider类。
2. **WebSocket通信与连接处理 (`app.py`, `core/websocket_server.py`, `core/connection.py`):**
* **服务器启动与入口 (`app.py`):**
* `app.py` 作为主入口,负责初始化应用环境(如检查FFmpeg、加载配置、设置日志)。
* 它会生成或加载一个 `auth_key` (JWT密钥),用于保护特定的HTTP接口(如视觉分析接口 `/mcp/vision/explain`)。若配置中 `manager-api.secret` 为空,则会生成一个UUID作为 `auth_key`
* 使用 `asyncio.create_task()` 并发启动 `WebSocketServer` (监听如 `ws://0.0.0.0:8000/xiaozhi/v1/`) 和 `SimpleHttpServer` (监听如 `http://0.0.0.0:8003/xiaozhi/ota/`)。
* 包含一个 `monitor_stdin()` 协程,用于在某些环境下保持应用存活或处理终端输入。
* **WebSocket服务器核心 (`core/websocket_server.py`):**
* `WebSocketServer` 类使用 `websockets` 库监听来自ESP32设备的连接请求。
* 对于每一个成功的WebSocket连接,它都会创建一个**独立的 `ConnectionHandler` 实例** (推测定义于 `core/connection.py`)。这种每个连接一个处理程序实例的设计模式,是实现多设备状态隔离和并发处理的关键,确保每个设备的对话流程和上下文信息互不干扰。
* 该服务器还提供一个 `_http_response` 方法,允许在同一端口上对非WebSocket升级的HTTP GET请求做出简单响应(例如返回 "Server is running"),便于进行健康检查。
* **动态配置更新:** `WebSocketServer` 包含一个 `update_config()` 异步方法。此方法使用 `config_lock` (一个 `asyncio.Lock`) 保证配置更新的原子性。它调用 `get_config_from_api()` (可能在 `config_loader.py` 中实现,通过 `manage_api_client.py``manager-api` 通信) 来获取新的配置。通过 `check_vad_update()``check_asr_update()` 等辅助函数判断是否需要重新初始化特定的AI模块,避免不必要的开销。更新后的配置会用于重新调用 `initialize_modules()`,从而实现AI服务提供者的热切换。
3. **消息处理与对话流程控制 (`core/handle/` 和 `ConnectionHandler`):**
* `ConnectionHandler` (推测) 作为每个连接的控制中心,负责接收来自ESP32的消息,并根据消息类型或当前对话状态,将其分发给 `core/handle/` 目录下的相应处理模块。这种模块化的处理器设计使得 `ConnectionHandler` 逻辑更清晰,易于扩展。
* **主要处理模块及其职责:**
* `helloHandle.py`: 处理与ESP32初次连接时的握手协议、设备认证或初始化信息交换。
* `receiveAudioHandle.py`: 接收音频流数据,调用VAD Provider进行语音活动检测,并将有效的音频片段传递给ASR Provider进行识别。
* `textHandle.py` / `intentHandler.py`: 获取ASR识别出的文本后,与Intent Provider (可能利用LLM进行意图识别) 和LLM Provider交互,以理解用户意图并生成初步回复或决策。
* `functionHandler.py`: 当LLM的响应包含执行特定“函数调用”的指令时,此模块负责从插件注册表中查找并执行对应的插件函数。
* `sendAudioHandle.py`: 将LLM最终生成的文本回复交给TTS Provider合成语音,并将音频流通过WebSocket发送回ESP32。
* `abortHandle.py`: 处理来自ESP32的中断请求,例如停止当前的TTS播报。
* `iotHandle.py`, `mcpHandle.py`: 处理与IoT设备控制相关的特定指令或更复杂的模块通信协议 (MCP)。
4. **插件化功能扩展系统 (`plugins_func/`):**
* **设计目的:** 提供一种标准化的方式来扩展语音助手的功能和“技能”,而无需修改核心代码。
* **实现机制:**
* 各个具体功能以独立的Python脚本形式存在于 `plugins_func/functions/` 目录中(例如 `get_weather.py`, `hass_set_state.py` 用于Home Assistant集成)。
* `loadplugins.py` 在服务器启动时负责扫描并加载这些插件模块。
* `register.py` (或插件模块内部的特定装饰器/函数) 可能用于定义每个插件函数的元数据,包括:
* **函数名称 (Function Name):** LLM调用时使用的标识符。
* **功能描述 (Description):** 供LLM理解此函数的作用。
* **参数模式 (Parameters Schema):** 通常是一个JSON Schema,详细定义了函数所需的参数、类型、是否必需以及描述。这是LLM能够正确生成函数调用参数的关键。
* **执行流程:** 当LLM在其思考过程中决定需要调用某个外部工具或函数来获取信息或执行操作时,它会依据预先提供的函数模式生成一个结构化的“函数调用”请求。`xiaozhi-server`中的`functionHandler.py`捕获此请求,从插件注册表中找到对应的Python函数并执行,然后将执行结果返回给LLM,LLM再基于此结果生成最终给用户的自然语言回复。
5. **配置管理 (`config/`):**
* **加载机制:** `config_loader.py` (通过 `settings.py` 被调用) 负责从根目录的 `config.yaml` 文件加载基础配置。
* **远程配置与合并:** 通过 `manage_api_client.py` (使用如`aiohttp`的库与`manager-api`通信) 可以从`manager-api`服务拉取配置。远程配置通常会覆盖本地 `config.yaml` 中的同名设置,从而实现通过Web界面动态调整服务器行为。
* **日志系统:** `logger.py` 初始化应用日志系统(可能使用 `loguru` 或对标准 `logging` 模块进行封装,支持通过 `logger.bind(tag=TAG)` 添加标签,便于追踪和过滤)。
* **静态资源:** `config/assets/` 目录下存放了用于系统提示音的静态音频文件(如设备绑定提示音 `bind_code.wav`、错误提示音等)。
6. **辅助HTTP服务 (`core/http_server.py`):**
* 与WebSocket服务并行运行一个简单的HTTP服务器,用于处理特定的HTTP请求。最主要的功能是为ESP32设备提供OTA (Over-The-Air) 固件更新的下载服务 (通过 `/xiaozhi/ota/` 端点)。此外,也可能承载其他如 `/mcp/vision/explain` (视觉分析) 等工具性HTTP接口。
综上所述,`xiaozhi-server` 是一个采用现代Python异步编程模型构建的、高度模块化、配置驱动的AI应用服务器。其精心设计的Provider模式和插件架构赋予了它强大的适应性和扩展性,能够灵活接入不同的AI能力并支持日益增长的功能需求。
---
### 3.2. `manager-api` (管理后端 - Java Spring Boot实现)
`manager-api` 组件是使用Java和Spring Boot框架构建的强大后端服务,作为整个`xiaozhi-esp32-server`生态系统的中央行政管理和配置中枢。
* **核心目标:**
*`manager-web`(Vue.js前端)提供一套安全、稳定、符合RESTful规范的API接口,使得管理员能够便捷地管理用户、设备、系统配置及其他相关资源。
* 充当`xiaozhi-server`(Python核心AI引擎)的集中化配置数据提供者,允许`xiaozhi-server`实例在启动或运行时获取其最新的操作参数。
* 持久化存储关键数据,例如:用户账户信息、设备注册详情、AI服务提供商配置(包括API密钥、选定的服务模型等)、TTS音色参数,以及OTA固件版本信息等。
* **核心技术栈:**
* **Java 21:** 项目采用的JDK版本,确保了对现代Java特性的支持。
* **Spring Boot 3:** 作为核心开发框架,极大地简化了独立、生产级别的Spring应用的创建和部署。它提供了自动配置、内嵌Web服务器(默认为Tomcat)、依赖管理等关键功能。
* **Spring MVC:** Spring框架中用于构建Web应用和RESTful API的模块。
* **MyBatis-Plus:** 一个对MyBatis进行功能增强的ORM(对象关系映射)框架。它简化了数据库操作,提供了强大的CRUD(增删改查)功能、条件构造器、代码生成器等,并能很好地与Spring Boot集成。
* **MySQL:** 作为主要的后端关系型数据库,用于存储所有需要持久化的管理数据和配置信息。
* **Druid (Alibaba Druid):** 一个功能强大的JDBC连接池实现,提供了丰富的监控功能和优秀的性能,用于高效管理数据库连接。
* **Redis (通过 Spring Data Redis):** 一个高性能的内存数据结构存储,常用于实现数据缓存(例如缓存热点配置数据、用户会话信息),以显著提升API的响应速度。
* **Apache Shiro:** 一个成熟且易用的Java安全框架,负责处理应用的认证(用户身份验证)和授权(API访问权限控制)需求。
* **Liquibase:** 一个用于跟踪、管理和应用数据库 schéma(模式)变更的开源工具。它允许开发者以数据库无关的方式定义和版本化数据库结构变更。
* **Knife4j:** 一个集成了Swagger并增强了UI的API文档生成工具,专为Java MVC框架(尤其是Spring Boot)设计。它能生成美观且易于交互的API文档界面(通常通过 `/xiaozhi/doc.html` 访问)。
* **Maven:** 用于项目的构建自动化和依赖项管理。
* **Lombok:** 一个Java库,通过注解自动生成构造函数、getter/setter、equals/hashCode、toString等样板代码,减少冗余。
* **HuTool / Google Guava:** 提供大量实用工具类,简化常见编程任务。
* **Aliyun Dysmsapi:** 阿里云短信服务SDK,用于集成发送短信功能(如验证码、通知)。
* **关键实现细节:**
1. **模块化项目结构 (`modules/` 包):**
* `manager-api` 的核心业务逻辑被清晰地划分到 `src/main/java/xiaozhi/modules/` 目录下的不同模块中。这种按功能领域划分模块的方式(例如 `sys` 负责系统管理,`agent` 负责智能体配置,`device` 负责设备管理,`config` 负责为`xiaozhi-server`提供配置,`security` 负责安全,`timbre` 负责音色管理,`ota` 负责固件升级)极大地提高了代码的可维护性和可扩展性。
* **各模块内部结构:** 每个业务模块通常遵循经典的三层架构或其变体:
* **Controller (控制层):** 位于 `xiaozhi.modules.[模块名].controller`
* **Service (服务层):** 位于 `xiaozhi.modules.[模块名].service`
* **DAO/Mapper (数据访问层):** 位于 `xiaozhi.modules.[模块名].dao`
* **Entity (实体类):** 位于 `xiaozhi.modules.[模块名].entity`
* **DTO (数据传输对象):** 位于 `xiaozhi.modules.[模块名].dto`
2. **分层架构实现:**
* **Controller层 (`@RestController`):** 这些类使用Spring MVC注解(如 `@GetMapping`, `@PostMapping` 等)来定义API的端点(endpoints)。它们负责接收HTTP请求,将请求体中的JSON数据反序列化为DTO对象,调用相应的Service层方法处理业务逻辑,最后将Service层的返回结果序列化为JSON并作为HTTP响应返回给客户端。
* **Service层 (`@Service`):** 这些类(通常是接口及其实现类的组合)封装了核心的业务规则和操作流程。它们可能会调用一个或多个DAO/Mapper对象来与数据库交互,并常常使用 `@Transactional` 注解来管理数据库事务的原子性。
* **Data Access (DAO/Mapper) 层 (MyBatis-Plus Mappers):** 这些是Java接口,继承自MyBatis-Plus提供的 `BaseMapper<Entity>` 接口。MyBatis-Plus会为这些接口自动提供标准的CRUD方法。对于更复杂的数据库查询,开发者可以通过在Mapper接口中定义方法并使用注解(如 `@Select`, `@Update`)或编写对应的XML映射文件来实现。例如,`UserMapper.selectById(userId)` 会被MyBatis-Plus自动实现。
* **Entity层 (`@TableName`, `@TableId` 等MyBatis-Plus注解):** 这些POJOPlain Old Java Objects)类直接映射到数据库中的表结构。Lombok的 `@Data` 注解常用于自动生成getter/setter等。
* **DTO层:** 用于在各层之间,特别是Controller层与Service层之间,以及API的请求/响应体中传递数据。使用DTO有助于解耦API接口的数据结构与数据库实体的数据结构,使API更稳定。
3. **通用功能与配置 (`common/` 包):**
* `src/main/java/xiaozhi/common/` 包提供了一系列跨模块共享的通用组件和配置:
* **基类:**`BaseDao`, `BaseEntity`, `BaseService`, `CrudService`,为各模块的相应组件提供通用的属性或方法。
* **全局配置:** 包括 `MybatisPlusConfig` (MyBatis-Plus的配置,如分页插件、数据权限插件等)、`RedisConfig` (Redis连接及序列化配置)、`SwaggerConfig` (Knife4j的配置)、`AsyncConfig` (异步任务执行器配置)。
* **自定义注解:** 例如 `@LogOperation` 用于通过AOP记录操作日志,`@DataFilter` 可能用于实现数据范围过滤。
* **AOP切面:**`RedisAspect` 可能用于实现方法级别的缓存逻辑。
* **全局异常处理:** `RenExceptionHandler` (使用 `@ControllerAdvice` 注解) 捕获应用中抛出的特定或所有异常 (如自定义的 `RenException`),并返回统一格式的JSON错误响应给客户端。`ErrorCode` 定义了标准化的错误码。
* **工具类:** 提供了日期转换、JSON处理(Jackson)、IP地址获取、HTTP上下文操作、统一结果封装 (`Result` 类)等多种实用工具。
* **校验工具:** `ValidatorUtils``AssertUtils` 用于简化参数校验逻辑。
* **XSS防护:** `XssFilter` 等组件用于防止跨站脚本攻击。
* **MyBatis-Plus自动填充:** `FieldMetaObjectHandler` 用于在执行插入或更新数据库操作时,自动填充如 `createTime`, `updateTime` 等公共字段。
4. **安全机制 (Apache Shiro):**
* Shiro的配置(通常在 `modules/security/config/``common/config/` 下)定义了如何进行用户认证和授权。
* **Realms (域):** 自定义的Shiro Realm类负责从数据库中查询用户信息(用户名、密码、盐值)进行身份验证,以及获取用户的角色和权限信息用于授权决策。
* **Filters (过滤器):** Shiro过滤器链被应用于保护API端点,确保只有经过认证且拥有足够权限的用户才能访问特定资源。
* **Session/Token Management:** Shiro管理用户会话。对于RESTful API,可能结合OAuth2或JWT等令牌机制实现无状态认证。
5. **数据库版本控制 (Liquibase):**
* 数据库的表结构、索引、初始数据等变更,都通过Liquibase的 `changelog` 文件(通常是XML格式)进行定义和版本化管理。当应用启动时,Liquibase会自动检查并应用必要的数据库结构更新,确保开发、测试和生产环境数据库结构的一致性。
6. **API文档:**
* 完整的API接口文档可通过以下地址访问: https://2662r3426b.vicp.fun/xiaozhi/doc.html
* 该文档使用Knife4j生成,提供了所有RESTful API端点的详细说明、请求/响应示例以及在线测试功能。
`manager-api` 通过这些精心选择的技术和设计模式,构建了一个功能全面、结构清晰、安全可靠且易于维护和扩展的Java后端服务。其模块化的设计特别适合处理具有多种管理功能需求的复杂系统。
---
### 3.3. `manager-web` (Web管理前端 - Vue.js实现)
`manager-web` 组件是一个采用 Vue.js 2 框架构建的单页应用 (SPA - Single Page Application)。它为系统管理员提供了一个功能丰富、交互友好的图形用户界面,用于全面管理和配置 `xiaozhi-esp32-server` 生态系统。
* **核心目标:**
* 提供一个基于Web的集中式控制面板,供管理员进行系统操作与监控。
* 实现对 `xiaozhi-server` 中AI服务提供商(ASR、LLM、TTS等)及其相关API密钥或许可配置的便捷管理。
* 支持用户账户、角色及权限的精细化管理。
* 提供ESP32设备的注册、配置及状态查看功能。
* 允许管理员自定义TTS音色、管理OTA固件更新流程、调整系统级参数及字典数据等。
* 作为 `manager-api` 所暴露各项功能的图形化交互前端。
* **核心技术栈:**
* **Vue.js 2:** 一个渐进式的JavaScript框架,用于构建用户界面。其核心特性包括声明式渲染、组件化系统、数据绑定等,非常适合构建复杂的SPA。
* **Vue CLI (`@vue/cli-service`):** Vue.js的官方命令行工具,用于项目的快速搭建、开发服务器的运行(支持热模块替换HMR)、以及生产环境构建打包(内部集成并配置了Webpack)。
* **Vue Router (`vue-router`):** Vue.js官方的路由管理器。它负责在SPA内部实现不同“页面”或视图组件之间的导航切换,而无需重新加载整个HTML页面,提供了流畅的用户体验。
* **Vuex (`vuex`):** Vue.js官方的状态管理模式和库。它充当了应用中所有组件的“中央数据存储”,用于管理全局共享状态(例如当前登录用户信息、设备列表、应用配置等),特别适用于大型复杂应用。
* **Element UI (`element-ui`):** 一个广受欢迎的基于Vue 2.0的桌面端UI组件库。它提供了大量预先设计和实现的组件(如表单、表格、对话框、导航菜单、按钮、提示等),帮助开发者快速构建出专业且一致的用户界面。
* **JavaScript (ES6+):** 前端逻辑实现的主要编程语言,利用其现代特性进行开发。
* **SCSS (Sassy CSS):** 一种CSS预处理器,它为CSS增加了变量、嵌套规则、混合(Mixin)、继承等高级特性,使得CSS代码更易于组织、维护和复用。
* **HTTP客户端 (Flyio 或 Axios 通过 `vue-axios`):** 用于在浏览器端向 `manager-api` 后端发起异步HTTP(AJAX)请求,以获取数据或提交操作。
* **Webpack:** 一个强大的模块打包工具(由Vue CLI在底层管理和配置)。它将项目中的各种资源(JavaScript文件、CSS、图片、字体等)视为模块,并将它们打包成浏览器可识别的静态文件。
* **Workbox (通过 `workbox-webpack-plugin`):** Google开发的一个库,用于简化Service Worker的编写和PWAProgressive Web App - 渐进式Web应用)的实现。它可以帮助生成Service Worker脚本,实现资源缓存、离线访问等功能。
* **Opus库 (`opus-decoder`, `opus-recorder`):** 这些音频处理库表明前端可能具备一些直接在浏览器中处理Opus格式音频的能力,例如:用于测试麦克风输入、允许管理员录制自定义音频片段(可能用于TTS音色样本或语音指令测试),或播放在管理界面中预览的Opus编码音频。
* **关键实现细节:**
1. **单页应用 (SPA) 结构:**
* 整个前端应用加载一个主HTML文件 (`public/index.html`)。后续的所有页面切换和内容更新都在客户端由Vue Router动态完成,无需每次都从服务器请求新的HTML页面。这种模式能提供更快的页面加载速度和更流畅的交互体验。
2. **组件化架构 (Component-Based Architecture):**
* 用户界面由一系列可复用的Vue组件 (`.vue` 单文件组件) 构成,形成一个组件树。这种方式提高了代码的模块化程度、可维护性和复用性。
* **`src/main.js`:** 应用的入口JS文件。它负责创建和初始化根Vue实例,注册全局插件(如Vue Router, Vuex, Element UI),并把根Vue实例挂载到 `public/index.html` 中的某个DOM元素上(通常是 `#app`)。
* **`src/App.vue`:** 应用的根组件。它通常定义了应用的基础布局结构(如包含导航栏、侧边栏、主内容区),并通过 `<router-view></router-view>` 标签来显示当前路由匹配到的视图组件。
* **视图组件 (`src/views/`):** 这些组件代表了应用中的各个“页面”或主要功能区(例如 `Login.vue` 登录页, `DeviceManagement.vue` 设备管理页, `UserManagement.vue` 用户管理页, `ModelConfig.vue` 模型配置页)。它们通常由Vue Router直接映射。
* **可复用UI组件 (`src/components/`):** 包含了在不同视图之间共享的、更小粒度的UI组件(例如 `HeaderBar.vue` 顶部导航栏, `AddDeviceDialog.vue` 添加设备对话框, `AudioPlayer.vue` 音频播放器组件)。
3. **客户端路由 (`src/router/index.js`):**
* Vue Router在此文件中进行配置,定义了应用的路由表。每个路由规则将一个特定的URL路径映射到一个视图组件。
* 常常包含**导航守卫 (Navigation Guards)**,例如 `beforeEach` 守卫,用于在路由跳转前执行逻辑,如检查用户是否已登录,如果未登录则重定向到登录页面,从而保护需要认证才能访问的页面。
4. **状态管理 (`src/store/index.js`):**
* Vuex被用来构建一个集中的状态管理中心(Store)。这个Store包含了:
* **State:** 存储应用级别的共享数据(例如,当前登录用户的详细信息、从API获取的设备列表、系统配置等)。
* **Getters:** 类似于Vue组件中的计算属性,用于从State派生出一些状态值,方便组件使用。
* **Mutations:** **唯一**可以同步修改State中数据的方法。它们必须是同步函数。
* **Actions:** 用于处理异步操作(如API调用)或封装多个Mutation提交。Actions会调用API,获取数据后,通过 `commit` 一个或多个Mutation来更新State。
* 例如,用户登录时,一个名为 `login` 的Action可能会被调用,它会向后端API发送登录请求,成功后获取到用户信息和token,然后 `commit` 一个名为 `SET_USER_INFO` 的Mutation来更新State中的用户信息和token。
5. **API通信 (`src/apis/`):**
*`manager-api` 后端的所有HTTP通信逻辑被封装在 `src/apis/` 目录下,通常会按照后端API的模块进行组织(例如 `src/apis/module/agent.js`, `src/apis/module/device.js`)。
* 每个模块导出一系列函数,每个函数对应一个具体的API请求。这些函数内部使用配置好的HTTP客户端实例 (例如,在 `src/apis/api.js``src/apis/httpRequest.js` 中统一配置Axios或Flyio实例,可能包含设置请求基地址、请求/响应拦截器等)。
* **拦截器 (Interceptors):** HTTP客户端的请求拦截器常用于在每个请求发送前自动添加认证令牌(如JWT);响应拦截器则可用于全局处理API错误(如权限不足、服务器错误)或对响应数据进行预处理。
6. **样式与资源 (`src/styles/`, `src/assets/`):**
* `Element UI` 提供了基础的组件样式。
* `src/styles/global.scss` 文件用于定义全局共享的SCSS样式、变量、混合(Mixin)等。
* Vue单文件组件内部的 `<style scoped>` 标签允许编写只作用于当前组件的局部样式。
* `src/assets/` 目录存放图片、字体等静态资源。
7. **构建与PWA特性:**
* Vue CLI通过Webpack将所有代码和资源打包成优化的静态文件,用于生产部署。
* `workbox-webpack-plugin` 的使用(体现在 `service-worker.js``registerServiceWorker.js` 文件)表明项目集成了Service Worker技术。Service Worker可以拦截网络请求,实现前端资源的智能缓存(从而加快后续访问速度),甚至在网络断开时提供一定的离线访问能力,是PWA的核心技术之一。
8. **环境配置 (`.env`系列文件):**
* 项目根目录下的 `.env` (以及 `.env.development`, `.env.production` 等) 文件用于定义环境变量。这些变量(例如 `VUE_APP_API_BASE_URL` 来指定 `manager-api` 的基础URL)可以在应用代码中通过 `process.env.VUE_APP_XXX` 的形式访问,从而允许为不同构建环境(开发、测试、生产)配置不同的参数。
`manager-web` 通过这些技术的综合运用,构建了一个功能强大、易于维护且用户体验良好的管理界面,为 `xiaozhi-esp32-server` 系统的配置和监控提供了坚实的前端支持。
---
## 4. 数据流与交互机制
`xiaozhi-esp32-server` 系统通过各组件间定义清晰的数据流和交互协议来协同工作。主要的通信方式依赖于针对实时交互优化的WebSocket协议和适用于客户端-服务器请求的RESTful API。
**4.1.核心语音交互流程 (ESP32设备 <-> `xiaozhi-server`)**
此流程是实时的,主要通过WebSocket进行低延迟、双向的数据交换。
* **通讯协议文档:**
* 详细的通讯协议说明文档可通过以下地址访问: https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh
* 该文档详细描述了ESP32设备与`xiaozhi-server`之间的WebSocket通信协议,包括:
* 连接建立与握手流程
* 音频数据传输格式
* 控制命令格式
* 状态报告格式
* 错误处理机制
* **连接建立与握手:**
* ESP32设备作为客户端,主动向`xiaozhi-server`的指定端点(例如 `ws://<服务器IP>:<WebSocket端口>/xiaozhi/v1/`)发起WebSocket连接请求。
* `xiaozhi-server` (`core/websocket_server.py`) 接收连接,并为每个成功连接的ESP32设备实例化一个独立的`ConnectionHandler`对象来管理该会话的整个生命周期。
* 连接建立后,可能会执行一个初始握手流程(由`core/handle/helloHandle.py`处理),用于交换设备标识、认证信息、协议版本或基本状态。
* **音频上行传输 (ESP32 -> `xiaozhi-server`):**
* 用户对ESP32设备讲话后,设备上的麦克风捕捉原始音频数据(通常是PCM或经过压缩如Opus的格式)。
* ESP32将这些音频数据块(chunks)作为WebSocket的**二进制消息 (binary messages)** 实时推送到`xiaozhi-server`对应的`ConnectionHandler`
* 服务器端的`core/handle/receiveAudioHandle.py`模块负责接收、缓冲并处理这些音频数据。
* **AI核心处理 (在`xiaozhi-server`内部):**
* **VAD (语音活动检测):** `receiveAudioHandle.py`利用配置的VAD提供者(如SileroVAD)分析音频流,以准确识别语音的起始和结束点,滤除静默或噪声片段。
* **ASR (自动语音识别):** 检测到的有效语音片段被送往配置的ASR提供者(本地如FunASR,或云端服务)。ASR引擎将音频信号转换为文本字符串。
* **NLU/LLM (自然语言理解/大型语言模型):** ASR输出的文本,连同从Memory提供者获取的当前对话上下文历史,以及从`plugins_func/`加载的可用函数(工具)的描述模式,一同被传递给配置的LLM提供者。
* **函数调用执行 (若LLM决策需要):** 如果LLM分析后认为需要调用外部函数(例如查询天气、控制家电),它会生成一个结构化的函数调用请求。`core/handle/functionHandler.py`接收此请求,查找并执行在`plugins_func/`中定义的相应Python函数,并将函数的执行结果返回给LLM。LLM随后基于此结果生成最终的自然语言回复。
* **回复生成:** LLM综合所有信息(用户输入、上下文、函数调用结果等)生成最终的文本回复。
* **记忆更新:** 当前轮次的交互(用户问题、LLM回复、可能的功能调用)会被Memory提供者处理,以更新对话历史,供后续交互使用。
* **TTS (文本转语音):** LLM生成的最终文本回复被送往配置的TTS提供者,后者将文本合成为语音数据流(例如MP3或WAV格式)。
* **音频下行响应 (`xiaozhi-server` -> ESP32):**
* 由TTS提供者合成的语音数据流,通过`core/handle/sendAudioHandle.py`模块,作为WebSocket的**二进制消息**实时发送回ESP32设备。
* ESP32设备接收这些音频数据块并立即通过扬声器播放给用户。
* **控制与状态消息 (双向):**
* 除了音频流,ESP32与`xiaozhi-server`之间也通过WebSocket交换**文本消息 (text messages)**,这些消息通常采用JSON格式封装。
* **ESP32 -> Server:** 设备可能发送状态报告(如网络状况、麦克风状态)、错误代码、或特定的控制命令(例如用户按键触发的“停止TTS播报”)。
* **Server -> ESP32:** 服务器可能发送控制指令给设备(如“开始监听”、“停止监听”、调整灵敏度、下发特定配置参数)。
* `core/handle/abortHandle.py`(处理中断请求)、`core/handle/reportHandle.py`(处理设备报告)等模块负责解析和响应这些控制/状态消息。
**4.2.管理与配置流程 (`manager-web` <-> `manager-api` <-> `xiaozhi-server`)**
此流程主要依赖于基于HTTP/HTTPS的RESTful API进行请求-响应式的交互。
* **管理员UI后端交互 (`manager-web` -> `manager-api`):**
* 当管理员在`manager-web`界面执行操作时(例如保存一项配置、添加一个新用户、注册一台ESP32设备):
* Vue.js前端应用 (`manager-web`) 会通过其API封装模块(位于`src/apis/module/`)向`manager-api`的对应REST API端点发起异步HTTP请求(通常是GET, POST, PUT, DELETE)。
* 请求体和响应体通常使用JSON格式。
* `manager-api`中的`@RestController`类接收这些请求。**Apache Shiro**框架会首先对请求进行认证和授权检查。
* 通过验证后,Controller将请求分发给相应的Service层处理业务逻辑。Service层可能会与MySQL数据库(通过MyBatis-Plus)交互,并可能利用Redis进行数据缓存。
* 处理完成后,`manager-api``manager-web`返回一个JSON格式的HTTP响应。
* `manager-web`根据响应结果更新其Vuex状态存储和用户界面显示。
* **配置同步 (`manager-api` -> `xiaozhi-server`):**
* `xiaozhi-server`的运行依赖于从`manager-api`获取的动态配置(例如当前选用的AI服务提供商及其API密钥)。
* **拉取机制 (Pull Mechanism):** `xiaozhi-server`内部的`config/manage_api_client.py`模块,在服务器启动时或通过特定更新触发器(例如`WebSocketServer.update_config()`被调用),会向`manager-api`的一个指定端点(例如由`modules/config/controller/`中的某个Controller提供)发起HTTP GET请求。
* `manager-api`响应该请求,返回`xiaozhi-server`所需的配置数据(JSON格式)。
* `xiaozhi-server`接收到配置后,会更新其内部状态,并可能重新初始化相关的AI服务模块,以使新配置生效。
* **OTA固件更新流程 (概念性描述):**
* 管理员通过`manager-web`界面上传新的ESP32固件包到`manager-api`的特定端点。
* `manager-api`将固件文件存储起来,并记录相关元数据(版本号、适用设备型号等)。
* 当管理员触发对特定设备的OTA更新时:
* `manager-api`可能会通知`xiaozhi-server`(具体通知机制可能是一个轮询检查点,或`xiaozhi-server`暴露一个接收更新通知的API,或者更松耦合的如消息队列)。
* `xiaozhi-server`随后可以通过WebSocket向目标ESP32设备发送一条包含固件下载URL的指令消息。
* ESP32设备收到指令后,通过HTTP GET请求从该URL下载固件。此URL可能指向`xiaozhi-server`自身运行的`SimpleHttpServer`所服务的路径(如`/xiaozhi/ota/`),或者在某些架构中,也可能直接指向`manager-api`或专用的文件服务器。
**4.3. 主要协议总结:**
* **WebSocket:** 被选用于ESP32与`xiaozhi-server`之间的通信链路,因为它非常适合实时、低延迟、双向的数据流传输(尤其是音频),以及异步控制消息的传递。
* **RESTful APIs (基于HTTP/HTTPS,通常使用JSON作为数据交换格式):** 这是Web服务间通信的标准方式。用于`manager-web`(客户端)与`manager-api`(服务器)之间的请求-响应交互,也用于`xiaozhi-server`(作为客户端)从`manager-api`(作为服务器)拉取配置信息。其无状态特性、广泛的库支持和易于理解的语义使其成为此类交互的理想选择。
这种多协议并用的通信策略,确保了系统内不同类型的交互需求都能得到高效和恰当的处理,兼顾了实时性和标准化的请求-响应模式。
---
## 5. 核心功能概要
`xiaozhi-esp32-server` 系统提供了一系列丰富的功能,旨在支持开发者构建先进的语音控制应用:
1. **全面的语音交互后端:** 提供从语音捕获指导到响应生成和动作执行的端到端解决方案。
2. **模块化和可插拔的AI服务:**
* 支持广泛的ASR(自动语音识别)、LLM(大型语言模型)、TTS(文本转语音)、VAD(语音活动检测)、意图识别和记忆提供商。
* 允许动态选择和配置这些服务(包括基于云的API和本地模型),以平衡成本、性能、隐私和语言需求。
3. **高级对话管理:**
* 支持自然交互,具有唤醒词启动对话、手动(按键说话式)对话以及对系统响应的实时打断等功能。
* 包含上下文记忆,以在多轮对话中保持连贯性。
* 在一段时间不活动后具有自动休眠模式。
4. **多语言能力:**
* 支持多种语言的识别和合成,包括普通话、粤语、英语、日语和韩语(具体取决于所选的ASR/LLM/TTS提供商)。
5. **通过插件实现的可扩展功能:**
* 强大的插件系统允许开发人员添加自定义“技能”或函数(例如,获取天气、控制智能家居设备、访问新闻)。
* 这些函数可以由LLM使用其函数调用能力,根据提供的模式来触发。
* 内置对Home Assistant集成的支持。
6. **物联网设备控制:**
* 设计用于通过语音命令管理和控制智能家居设备及其他物联网硬件,并利用插件系统。
7. **基于Web的管理控制台 (`manager-web` & `manager-api`):**
* 提供全面的图形界面,用于:
* 系统配置(AI服务选择、API密钥、操作参数)。
* 基于角色的访问控制的用户管理。
* ESP32设备注册和管理。
* 语音音色/TTS语音定制。
* ESP32设备的OTA(空中下载)固件更新管理。
* 系统参数和字典的管理。
8. **灵活的部署选项:**
* 支持通过Docker容器(用于简化的仅服务器或全栈设置)和直接从源代码部署,以适应各种环境和用户专业知识。
9. **动态远程配置:**
* `xiaozhi-server`可以从`manager-api`获取其配置,允许实时更新AI提供商和设置,而无需重新启动服务器。
10. **开源和社区驱动:**
* 根据MIT许可证授权,鼓励透明、协作和社区贡献。
11. **经济高效的解决方案:**
* 提供“入门全免费设置”路径,利用AI服务的免费套餐或本地模型,使其易于进行实验和个人项目。
12. **渐进式Web应用 (PWA) 特性:**
* `manager-web`控制面板包含Service Worker集成,以增强缓存和潜在的离线访问能力。
13. **详细的API文档:**
* `manager-api`通过Knife4j提供OpenAPI (Swagger) 文档,以便清晰理解和测试其RESTful端点。
这些功能共同使`xiaozhi-esp32-server`成为一个强大、适应性强且用户友好的平台,用于构建复杂的语音交互应用程序。
---
## 6. 部署与配置概述
`xiaozhi-esp32-server`系统在设计上充分考虑了灵活性,提供了多种部署方法和全面的配置选项,以适应不同的使用场景和需求。
**部署选项:**
项目可以通过多种方式部署,主要包括使用Docker简化安装过程,或直接从源代码部署以获得更大的控制权和进行开发。
1. **基于Docker的部署:**
* **简化安装 (仅`xiaozhi-server`):** 此选项仅部署核心的基于Python的`xiaozhi-server`。它适用于主要需要语音AI处理能力和IoT控制,而不需要完整Web管理界面和数据库支持功能(如OTA)的用户。在此模式下,配置通常通过本地文件(`config.yaml`)管理,但如果需要,仍可将其指向一个已存在的`manager-api`实例。
* **全模块安装 (所有组件):** 此方案部署所有核心组件:`xiaozhi-server`、基于Java的`manager-api`、以及基于Vue.js的`manager-web`,同时还包括所需的数据库服务(MySQL和Redis)。这提供了完整的系统体验,包括用于全面配置和管理的Web控制面板。
* 项目为每个服务提供了`Dockerfile`定义,并使用`docker-compose.yml`文件(例如`docker-compose.yml`用于基础版,`docker-compose_all.yml`用于全功能版)来编排和管理多容器的部署。此外,还可能提供一个`docker-setup.sh`脚本来辅助自动化部分Docker环境的搭建工作。
2. **源代码部署:**
* 这种方法需要为每个组件手动设置相应的开发环境:Python环境用于`xiaozhi-server`Java/Maven环境用于`manager-api`Node.js/Vue CLI环境用于`manager-web`
* 对于全模块安装,还需要手动安装和配置MySQL及Redis数据库服务。
* 这种方式通常用于项目开发、深度定制、调试,或者在对环境有特殊要求的生产场景中。
**配置管理:**
配置是定制系统行为的关键,尤其是在选择AI服务提供商和管理API密钥方面。
1. **`xiaozhi-server` 配置:**
* **本地`config.yaml`:** 位于`xiaozhi-server`根目录下的一个主要的YAML格式配置文件。它定义了服务器端口、选定的AI服务提供商(ASR、LLM、TTS、VAD、意图识别、记忆模块等)、它们各自的API密钥或模型路径、插件配置以及日志级别等。
* **通过`manager-api`进行远程配置:** `xiaozhi-server`被设计为可以从`manager-api`获取其运行配置。从`manager-api`获取的设置通常会覆盖本地`config.yaml`中的同名设置。这带来了两大好处:
* **集中管理:** 所有配置都可以通过`manager-web`界面进行统一管理。
* **动态更新:** `xiaozhi-server`可以刷新其配置并重新初始化AI模块,而无需完全重启服务。
* `xiaozhi-server`中的`config/config_loader.py``config/manage_api_client.py`负责处理配置的加载、合并及从`manager-api`拉取的逻辑。
2. **`manager-api` 配置:**
* 作为一个Spring Boot应用,其配置主要通过位于`src/main/resources`目录下的`application.properties``application.yml`文件进行管理。
* 关键配置项包括:数据库连接信息(MySQL的URL、用户名、密码)、Redis服务器地址和端口、应用服务端口(默认为8002)、Apache Shiro安全相关的设置,以及任何集成的第三方服务(如阿里云短信)的配置参数。
3. **`manager-web` 配置:**
* Vue.js前端应用的环境特定设置通过项目根目录下的`.env`系列文件(例如`.env`, `.env.development`, `.env.production`)进行管理。
* 这里最关键的配置通常是`manager-api`后端的API基础URL地址 (例如 `VUE_APP_API_BASE_URL`),前端应用将向此地址发送所有API请求。
4. **预定义的配置方案:**
* 项目文档(通常是README)中会推荐一些常见的配置组合,例如:
* **“入门全免费设置”:** 该方案旨在利用云AI服务的免费套餐额度或完全免费的本地模型,以最大程度地降低用户的初始使用成本和运营费用。
* **“全流式配置”:** 该方案优先考虑系统的响应速度和交互的流畅性,通常会选用支持流式处理的(可能付费的)AI服务。
* 这些预定义方案为用户在`xiaozhi-server`中配置AI服务提供商(通过`manager-web`界面或直接修改`config.yaml`)提供了指导。
在全模块部署的情况下,推荐使用`manager-web`控制面板作为大多数配置任务的主要操作界面,因为它提供了一种用户友好的方式来管理由`manager-api`持久化并最终由`xiaozhi-server`使用的各项设置。
---
+369
View File
@@ -0,0 +1,369 @@
# Technical Documentation: `xiaozhi-esp32-server`
**Table of Contents:**
1. [Introduction](#1-introduction)
2. [Overall Architecture](#2-overall-architecture)
3. [Component Deep Dive](#3-component-deep-dive)
* [3.1. `xiaozhi-server` (Core AI Engine - Python Implementation)](#31-xiaozhi-server-core-ai-engine---python-implementation)
* [3.2. `manager-api` (Management Backend - Java Spring Boot Implementation)](#32-manager-api-management-backend---java-spring-boot-implementation)
* [3.3. `manager-web` (Web Management Frontend - Vue.js Implementation)](#33-manager-web-web-management-frontend---vuejs-implementation)
4. [Data Flow and Interaction Mechanisms](#4-data-flow-and-interaction-mechanisms)
5. [Key Features Summary](#5-key-features-summary)
6. [Deployment and Configuration Overview](#6-deployment-and-configuration-overview)
---
## 1. Introduction
The `xiaozhi-esp32-server` project is a **comprehensive backend system** designed to support intelligent hardware based on ESP32. Its core goal is to enable developers to quickly build a robust server infrastructure that can understand natural language commands, interact efficiently with various AI services (for speech recognition, natural language understanding, and speech synthesis), manage IoT devices, and provide a web-based user interface for system configuration and management. By integrating multiple cutting-edge technologies into a cohesive and extensible platform, this project aims to simplify and accelerate the development process of customizable voice assistants and intelligent control systems. It is not just a simple server, but a bridge connecting hardware, AI capabilities, and user management.
---
## 2. Overall Architecture
The `xiaozhi-esp32-server` system adopts a **distributed, multi-component collaborative** architectural design, ensuring modularity, maintainability, and scalability. Each core component has its specific role and works in coordination. The main components include:
1. **ESP32 Hardware (Client Device):**
This is the physical smart hardware device that end-users directly interact with. Its main responsibilities include:
* Capturing user voice commands.
* Securely sending captured raw audio data to `xiaozhi-server` for processing.
* Receiving synthesized voice responses from `xiaozhi-server` and playing them through speakers.
* Controlling other connected peripherals or IoT devices (such as smart bulbs, sensors, etc.) based on instructions received from `xiaozhi-server`.
2. **`xiaozhi-server` (Core AI Engine - Python Implementation):**
This Python-based server is the "brain" of the entire system, responsible for handling all voice-related logic and AI interactions. Its key responsibilities are detailed as follows:
* Establishing **stable, low-latency real-time bidirectional communication links** with ESP32 devices through the WebSocket protocol.
* Receiving audio streams from ESP32 and using Voice Activity Detection (VAD) technology to precisely segment valid speech segments.
* Integrating and calling Automatic Speech Recognition (ASR) services (configurable for local or cloud), converting speech segments to text.
* Interacting with Large Language Models (LLMs) to parse user intent, generate intelligent responses, and support complex natural language understanding tasks.
* Managing context information and user memory in multi-turn dialogues to provide coherent interaction experiences.
* Calling Text-to-Speech (TTS) services to synthesize natural and fluent speech from LLM-generated text responses.
* Executing custom commands through a flexible **plugin system**, including IoT device control logic.
* Obtaining its detailed runtime operation configuration from the `manager-api` service.
3. **`manager-api` (Management Backend - Java Spring Boot Implementation):**
This is an application built using the Java Spring Boot framework, providing a secure RESTful API for system management and configuration. It serves not only as the backend support for the `manager-web` console but also as the configuration data source for `xiaozhi-server`. Its core functions include:
* Providing user authentication (login, permission verification) and user account management functions for the Web console.
* Registration, information management of ESP32 devices, and maintenance of device-specific configurations.
* Persistently storing system configurations in the **MySQL database**, such as user-selected AI service providers, API keys, device parameters, plugin settings, etc.
* Providing specific API endpoints for `xiaozhi-server` to pull its required latest configuration.
* Managing TTS voice options, handling OTA (Over-The-Air) firmware update processes, and related metadata.
* Utilizing **Redis** as a high-speed cache to store hotspot data (such as session information, frequently accessed configurations) to improve API response speed and overall system performance.
4. **`manager-web` (Web Control Panel - Vue.js Implementation):**
This is a Single Page Application (SPA) built with Vue.js, providing system administrators with a graphical, user-friendly operation interface. Its main capabilities include:
* Conveniently configuring various AI services used by `xiaozhi-server` (such as ASR, LLM, TTS provider switching, parameter adjustment).
* Managing platform user accounts, role assignment, and permission control.
* Managing registered ESP32 devices and their related settings.
* (Potential functionality) Monitoring system operation status, viewing logs, troubleshooting, etc.
* Comprehensive interaction with all backend management functions provided by `manager-api`.
**High-Level Interaction Flow Overview:**
* **Voice Interaction Main Line:** After the **ESP32 device** captures user voice, it transmits audio data in real-time to **`xiaozhi-server`** through **WebSocket**. After `xiaozhi-server` completes a series of AI processing (VAD, ASR, LLM interaction, TTS), it sends the synthesized voice response back to the ESP32 device for playback through WebSocket. All real-time interactions directly related to voice are completed in this link.
* **Management Configuration Main Line:** Administrators access the **`manager-web`** console through a browser. `manager-web` executes various management operations (such as modifying configurations, managing users or devices) by calling **RESTful HTTP interfaces** provided by **`manager-api`**. Data is passed between them in JSON format.
* **Configuration Synchronization:** **`xiaozhi-server`** actively pulls its latest operation configuration from **`manager-api`** through HTTP requests when starting or when specific update mechanisms are triggered. This ensures that configuration changes made by administrators in the Web interface can be effectively applied to the operation of the core AI engine in a timely manner.
This **frontend-backend separation, core service and management service separation** architectural design allows `xiaozhi-server` to focus on efficient real-time AI processing tasks, while `manager-api` and `manager-web` together provide a powerful and easy-to-use management and configuration platform. Each component has clear responsibilities, facilitating independent development, testing, deployment, and expansion.
```
xiaozhi-esp32-server
├─ xiaozhi-server Port 8000 Python development Responsible for ESP32 communication
├─ manager-web Port 8001 Node.js+Vue development Responsible for providing web interface for console
├─ manager-api Port 8002 Java development Responsible for providing console API
```
---
## 3. Component Deep Dive
### 3.1. `xiaozhi-server` (Core AI Engine - Python Implementation)
The `xiaozhi-server` is the intelligent core of the system, responsible for processing voice interactions, interfacing with AI services, and managing communication with ESP32 devices.
* **Purpose:**
* To provide real-time processing of voice commands from ESP32 devices.
* To integrate with various AI services for Speech-to-Text (ASR), Natural Language Understanding (via Large Language Models - LLMs), Text-to-Speech (TTS), Voice Activity Detection (VAD), Intent Recognition, and Memory.
* To manage dialogue flow and context with users.
* To execute custom functions and control IoT devices based on user commands.
* To be dynamically configurable through the `manager-api`.
* **Core Technologies:**
* **Python 3:** The primary programming language.
* **Asyncio:** Python's asynchronous programming framework, crucial for handling concurrent WebSocket connections and non-blocking I/O for AI service API calls.
* **`websockets` Library:** For WebSocket server implementation.
* **HTTP Client (e.g., `aiohttp`, `httpx`):** For asynchronous HTTP requests to `manager-api` and external AI services.
* **YAML (PyYAML):** For local configuration file parsing.
* **Key Implementation Aspects:**
1. **AI Service Provider Pattern (`core/providers/`):**
* **Concept:** A flexible design for integrating AI services. Each service type (ASR, TTS, LLM, etc.) has an abstract base class defining a common interface. Concrete classes implement this interface for specific vendors or local models.
* **Benefit:** Allows easy switching of AI service backends via configuration and simplifies adding new service integrations.
* **Initialization:** `core/utils/modules_initialize.py` acts as a factory to load and instantiate configured providers.
2. **WebSocket Communication & Connection Handling (`core/websocket_server.py`, `core/connection.py`):**
* **Server Setup:** Manages WebSocket connections from ESP32 devices.
* **Connection Isolation:** Each ESP32 client gets a dedicated `ConnectionHandler` instance, isolating its session state and dialogue.
* **Dynamic Configuration Updates:** Can fetch updated configurations from `manager-api` and re-initialize AI service modules live, without a full server restart.
3. **Message Handling & Dialogue Flow (`core/handle/`):**
* Employs a modular handler pattern. The `ConnectionHandler` dispatches message processing to specialized modules based on message type or dialogue phase (e.g., `receiveAudioHandle.py` for audio input, `intentHandler.py` for NLU, `functionHandler.py` for plugin execution, `sendAudioHandle.py` for TTS output).
4. **Plugin System for Extensible Functions (`plugins_func/`):**
* **Purpose:** Allows adding custom "skills" (e.g., weather, news, Home Assistant control).
* **Mechanism:** Plugins define functions and schemas. The LLM can request execution of these functions (function calling). `loadplugins.py` and `register.py` manage plugin discovery and registration.
5. **Configuration Management (`config/`):**
* Loads settings from a local `config.yaml` and merges them with configurations fetched from `manager-api` (via `manage_api_client.py`), enabling remote dynamic configuration.
* `logger.py` sets up structured application logging.
* `config/assets/` stores predefined audio files for system notifications.
6. **Auxiliary HTTP Server (`core/http_server.py`):**
* Handles specific HTTP requests, notably for OTA firmware updates (`/xiaozhi/ota/`) and other utility endpoints.
### 3.2. `manager-api` (Management Backend - Java Spring Boot Implementation)
The `manager-api` component is a backend server built using Java and the Spring Boot framework, serving as the administrative hub.
* **Purpose:**
* Provide a secure RESTful API for the `manager-web` frontend.
* Act as a centralized configuration provider for `xiaozhi-server`.
* Manage persistent data (users, devices, AI configurations, voice timbres, OTA firmware).
* **Core Technologies:**
* **Java 21 & Spring Boot 3:** Core language and framework.
* **Spring MVC:** For building REST controllers.
* **MyBatis-Plus:** ORM for database interaction with MySQL.
* **MySQL:** Relational database.
* **Druid:** JDBC connection pool.
* **Redis (Spring Data Redis):** For caching.
* **Apache Shiro:** Security framework for authentication and authorization.
* **Liquibase:** Database schema migration.
* **Knife4j:** OpenAPI (Swagger) API documentation.
* **Maven:** Build and dependency management.
* **Key Implementation Aspects:**
1. **Modular Architecture (`modules/` package):**
* Business logic is organized into distinct modules (e.g., `sys` for users/roles, `agent` for assistant configs, `device` for ESP32s, `config` for `xiaozhi-server` settings, `security`, `timbre`, `ota`).
* Each module typically follows a layered pattern: Controller, Service, DAO (Mapper), Entity, DTO.
2. **Layered Architecture:**
* **Controller Layer (`@RestController`):** Defines API endpoints, handles HTTP request/response.
* **Service Layer (`@Service`):** Contains business logic, transaction management.
* **Data Access Layer (MyBatis-Plus Mappers):** Interacts with the MySQL database.
3. **Common Functionalities (`common/` package):**
* Provides shared code: base classes, global configurations (Spring, MyBatis, Redis, Knife4j), custom annotations (e.g., `@LogOperation`), AOP aspects, global exception handling, utility classes, and XSS protection.
4. **Security (Apache Shiro):**
* Manages user authentication and permissions for accessing API endpoints. Configured with Shiro Realms and security filters.
5. **Database Schema Management (Liquibase):**
* Ensures consistent database structure across environments through versioned schema changes.
### 3.3. `manager-web` (Web Control Panel - Vue.js Implementation)
The `manager-web` is a Single Page Application (SPA) providing the administrative user interface.
* **Purpose:**
* Offer a web-based control panel for system configuration and management.
* Enable administrators to configure `xiaozhi-server`'s AI services, manage users and devices, customize voice timbres, and handle OTA updates.
* **Core Technologies:**
* **Vue.js 2 & Vue CLI:** Core JavaScript framework and build tools.
* **Vue Router:** For client-side routing within the SPA.
* **Vuex:** For centralized state management.
* **Element UI:** UI component library for a consistent look and feel.
* **SCSS:** CSS preprocessor.
* **HTTP Client (Flyio or Axios):** For API calls to `manager-api`.
* **Workbox:** For PWA features (caching, service worker).
* **Opus Libraries:** For potential in-browser audio recording/playback.
* **Key Implementation Aspects:**
1. **SPA Structure:** Single HTML page with dynamic view updates.
2. **Component-Based Architecture:** UI built from reusable Vue components (`.vue` files in `src/views/` for pages and `src/components/` for smaller elements).
3. **Client-Side Routing (`src/router/index.js`):** Maps browser URLs to view components, with route guards for authentication.
4. **State Management (`src/store/index.js`):** Vuex manages global state (user info, device lists, etc.) via state, getters, mutations, and actions (often involving API calls).
5. **API Communication (`src/apis/`):** Modularized API service files make asynchronous calls to `manager-api`.
6. **Build Process & PWA Features:** Vue CLI (Webpack) bundles assets. Workbox enables PWA features like caching.
7. **Environment Configuration (`.env` files):** Manages settings like the `manager-api` base URL for different environments.
---
## 4. Data Flow and Interaction Mechanisms
The `xiaozhi-esp32-server` system coordinates work through well-defined data flows and interaction protocols between components. The main communication methods rely on WebSocket protocol optimized for real-time interaction and RESTful API suitable for client-server requests.
**4.1. Core Voice Interaction Flow (ESP32 Device <-> `xiaozhi-server`)**
This flow is real-time, primarily using WebSocket for low-latency, bidirectional data exchange.
* **Communication Protocol Documentation:**
* Detailed communication protocol documentation can be accessed at: https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh
* This document details the WebSocket communication protocol between ESP32 devices and `xiaozhi-server`, including:
* Connection establishment and handshake process
* Audio data transmission format
* Control command format
* Status report format
* Error handling mechanism
* **Connection Establishment and Handshake:**
* The ESP32 device, as a client, actively initiates a WebSocket connection request to the specified endpoint of `xiaozhi-server` (e.g., `ws://<server-IP>:<WebSocket-port>/xiaozhi/v1/`).
* `xiaozhi-server` (`core/websocket_server.py`) receives the connection and instantiates an independent `ConnectionHandler` object for each successfully connected ESP32 device to manage the entire lifecycle of that session.
* After the connection is established, an initial handshake process may be executed (handled by `core/handle/helloHandle.py`) to exchange device identification, authentication information, protocol version, or basic status.
* **Audio Uplink Transmission (ESP32 -> `xiaozhi-server`):**
* After a user speaks to the ESP32 device, the device's microphone captures raw audio data (usually in PCM or compressed formats like Opus).
* The ESP32 pushes these audio data chunks as WebSocket **binary messages** in real-time to the corresponding `ConnectionHandler` in `xiaozhi-server`.
* The server-side `core/handle/receiveAudioHandle.py` module is responsible for receiving, buffering, and processing these audio data.
* **AI Core Processing (within `xiaozhi-server`):**
* **VAD (Voice Activity Detection):** `receiveAudioHandle.py` uses the configured VAD provider (such as SileroVAD) to analyze the audio stream, accurately identifying the start and end points of speech, filtering out silent or noise segments.
* **ASR (Automatic Speech Recognition):** Detected valid speech segments are sent to the configured ASR provider (local such as FunASR, or cloud services). The ASR engine converts audio signals into text strings.
* **NLU/LLM (Natural Language Understanding/Large Language Model):** The ASR output text, along with the current dialogue context history obtained from the Memory provider, and the description schemas of available functions (tools) loaded from `plugins_func/`, are passed to the configured LLM provider.
* **Function Call Execution (if LLM decides needed):** If the LLM analysis determines that an external function needs to be called (e.g., querying weather, controlling home appliances), it generates a structured function call request. `core/handle/functionHandler.py` receives this request, finds and executes the corresponding Python function defined in `plugins_func/`, and returns the function's execution result to the LLM. The LLM then generates the final natural language response based on this result.
* **Response Generation:** The LLM synthesizes all information (user input, context, function call results, etc.) to generate the final text response.
* **Memory Update:** The current round of interaction (user question, LLM response, possible function calls) is processed by the Memory provider to update the dialogue history for subsequent interactions.
* **TTS (Text-to-Speech):** The final text response generated by the LLM is sent to the configured TTS provider, which synthesizes the text into a speech data stream (e.g., MP3 or WAV format).
* **Audio Downlink Response (`xiaozhi-server` -> ESP32):**
* The speech data stream synthesized by the TTS provider is sent in real-time as WebSocket **binary messages** back to the ESP32 device through the `core/handle/sendAudioHandle.py` module.
* The ESP32 device receives these audio data chunks and immediately plays them to the user through the speaker.
* **Control and Status Messages (Bidirectional):**
* In addition to audio streams, ESP32 and `xiaozhi-server` also exchange **text messages** through WebSocket, these messages are usually encapsulated in JSON format.
* **ESP32 -> Server:** The device may send status reports (such as network conditions, microphone status), error codes, or specific control commands (e.g., "stop TTS playback" triggered by user button press).
* **Server -> ESP32:** The server may send control instructions to the device (such as "start listening", "stop listening", adjust sensitivity, send specific configuration parameters).
* Modules like `core/handle/abortHandle.py` (handling interrupt requests), `core/handle/reportHandle.py` (handling device reports) are responsible for parsing and responding to these control/status messages.
**4.2. Management and Configuration Flow (`manager-web` <-> `manager-api` <-> `xiaozhi-server`)**
This flow primarily relies on HTTP/HTTPS-based RESTful API for request-response interactions.
* **Administrator UI Backend Interaction (`manager-web` -> `manager-api`):**
* When administrators perform operations in the `manager-web` interface (e.g., saving a configuration, adding a new user, registering an ESP32 device):
* The Vue.js frontend application (`manager-web`) will initiate asynchronous HTTP requests (usually GET, POST, PUT, DELETE) to the corresponding REST API endpoints of `manager-api` through its API encapsulation module (located in `src/apis/module/`).
* Request and response bodies typically use JSON format.
* The `@RestController` classes in `manager-api` receive these requests. The **Apache Shiro** framework will first perform authentication and authorization checks on the requests.
* After verification, the Controller distributes the request to the corresponding Service layer to handle business logic. The Service layer may interact with the MySQL database (through MyBatis-Plus) and may utilize Redis for data caching.
* After processing, `manager-api` returns an HTTP response in JSON format to `manager-web`.
* `manager-web` updates its Vuex state store and user interface display based on the response results.
* **Configuration Synchronization (`manager-api` -> `xiaozhi-server`):**
* The operation of `xiaozhi-server` depends on dynamic configurations obtained from `manager-api` (such as currently selected AI service providers and their API keys).
* **Pull Mechanism:** The `config/manage_api_client.py` module within `xiaozhi-server`, when the server starts or through specific update triggers (e.g., when `WebSocketServer.update_config()` is called), will initiate an HTTP GET request to a specified endpoint of `manager-api` (e.g., provided by a Controller in `modules/config/controller/`).
* `manager-api` responds to this request, returning the configuration data required by `xiaozhi-server` (in JSON format).
* After receiving the configuration, `xiaozhi-server` will update its internal state and may reinitialize relevant AI service modules to make the new configuration effective.
* **OTA Firmware Update Flow (Conceptual Description):**
* Administrators upload new ESP32 firmware packages to specific endpoints of `manager-api` through the `manager-web` interface.
* `manager-api` stores the firmware files and records related metadata (version number, applicable device models, etc.).
* When administrators trigger OTA updates for specific devices:
* `manager-api` may notify `xiaozhi-server` (the specific notification mechanism may be a polling checkpoint, or `xiaozhi-server` exposes an API to receive update notifications, or more loosely coupled like message queues).
* `xiaozhi-server` can then send an instruction message containing the firmware download URL to the target ESP32 device through WebSocket.
* After receiving the instruction, the ESP32 device downloads the firmware through an HTTP GET request from that URL. This URL may point to a path served by the `SimpleHttpServer` running on `xiaozhi-server` itself (such as `/xiaozhi/ota/`), or in some architectures, it may directly point to `manager-api` or a dedicated file server.
**4.3. Main Protocol Summary:**
* **WebSocket:** Selected for the communication link between ESP32 and `xiaozhi-server` because it is very suitable for real-time, low-latency, bidirectional data stream transmission (especially audio), as well as asynchronous control message delivery.
* **RESTful APIs (based on HTTP/HTTPS, usually using JSON as the data exchange format):** This is the standard way for web service communication. Used for request-response interactions between `manager-web` (client) and `manager-api` (server), and also for `xiaozhi-server` (as client) to pull configuration information from `manager-api` (as server). Its stateless nature, wide library support, and easy-to-understand semantics make it an ideal choice for such interactions.
This multi-protocol communication strategy ensures that different types of interaction requirements within the system can be handled efficiently and appropriately, balancing real-time performance and standardized request-response patterns.
---
## 5. Key Features Summary
The `xiaozhi-esp32-server` system provides a series of rich features aimed at supporting developers in building advanced voice control applications:
1. **Comprehensive Voice Interaction Backend:** Provides an end-to-end solution from voice capture guidance to response generation and action execution.
2. **Modular and Pluggable AI Services:**
* Supports a wide range of ASR (Automatic Speech Recognition), LLM (Large Language Model), TTS (Text-to-Speech), VAD (Voice Activity Detection), Intent Recognition, and Memory providers.
* Allows dynamic selection and configuration of these services (including cloud-based APIs and local models) to balance cost, performance, privacy, and language requirements.
3. **Advanced Dialogue Management:**
* Supports natural interaction, with wake word to start dialogue, manual (push-to-talk) dialogue, and real-time interruption of system responses.
* Includes contextual memory to maintain coherence in multi-turn dialogues.
* Has automatic sleep mode after a period of inactivity.
4. **Multi-language Capabilities:**
* Supports recognition and synthesis in multiple languages, including Mandarin, Cantonese, English, Japanese, and Korean (specific capabilities depend on the selected ASR/LLM/TTS providers).
5. **Extensible Functions through Plugins:**
* Powerful plugin system allows developers to add custom "skills" or functions (e.g., getting weather, controlling smart home devices, accessing news).
* These functions can be triggered by the LLM using its function calling capability, based on provided schemas.
* Built-in support for Home Assistant integration.
6. **IoT Device Control:**
* Designed to manage and control smart home devices and other IoT hardware through voice commands, utilizing the plugin system.
7. **Web-based Management Console (`manager-web` & `manager-api`):**
* Provides a comprehensive graphical interface for:
* System configuration (AI service selection, API keys, operation parameters).
* Role-based access control user management.
* ESP32 device registration and management.
* Voice timbre/TTS voice customization.
* OTA (Over-The-Air) firmware update management for ESP32 devices.
* System parameter and dictionary management.
8. **Flexible Deployment Options:**
* Supports deployment through Docker containers (for simplified server-only or full-stack setup) and directly from source code, adapting to various environments and user expertise.
9. **Dynamic Remote Configuration:**
* `xiaozhi-server` can obtain its configuration from `manager-api`, allowing real-time updates of AI providers and settings without restarting the server.
10. **Open Source and Community-Driven:**
* Licensed under MIT License, encouraging transparency, collaboration, and community contribution.
11. **Cost-Effective Solution:**
* Provides an "Entry Level Free Settings" path, utilizing free tiers of AI services or local models, making it easy to experiment and for personal projects.
12. **Progressive Web Application (PWA) Features:**
* The `manager-web` control panel includes Service Worker integration to enhance caching and potential offline access capabilities.
13. **Detailed API Documentation:**
* `manager-api` provides OpenAPI (Swagger) documentation through Knife4j for clear understanding and testing of its RESTful endpoints.
These features together make `xiaozhi-esp32-server` a powerful, adaptable, and user-friendly platform for building complex voice interaction applications.
---
## 6. Deployment and Configuration Overview
The `xiaozhi-esp32-server` system is designed with flexibility in mind, providing multiple deployment methods and comprehensive configuration options to adapt to different usage scenarios and requirements.
**Deployment Options:**
The project can be deployed in multiple ways, mainly including using Docker to simplify the installation process, or deploying directly from source code for greater control and development.
1. **Docker-based Deployment:**
* **Simplified Installation (Only `xiaozhi-server`):** This option only deploys the core Python-based `xiaozhi-server`. It is suitable for users who mainly need voice AI processing capabilities and IoT control, without requiring the complete Web management interface and database support functions (such as OTA). In this mode, configuration is typically managed through local files (`config.yaml`), but if needed, it can still point to an existing `manager-api` instance.
* **Full Module Installation (All Components):** This scheme deploys all core components: `xiaozhi-server`, Java-based `manager-api`, and Vue.js-based `manager-web`, along with required database services (MySQL and Redis). This provides a complete system experience, including a Web control panel for comprehensive configuration and management.
* The project provides `Dockerfile` definitions for each service and uses `docker-compose.yml` files (e.g., `docker-compose.yml` for basic version, `docker-compose_all.yml` for full-featured version) to orchestrate and manage multi-container deployment. Additionally, a `docker-setup.sh` script may be provided to assist in automating part of the Docker environment setup work.
2. **Source Code Deployment:**
* This method requires manual setup of the corresponding development environment for each component: Python environment for `xiaozhi-server`, Java/Maven environment for `manager-api`, Node.js/Vue CLI environment for `manager-web`.
* For full module installation, MySQL and Redis database services also need to be manually installed and configured.
* This approach is typically used for project development, deep customization, debugging, or in production scenarios with special environmental requirements.
**Configuration Management:**
Configuration is key to customizing system behavior, especially in selecting AI service providers and managing API keys.
1. **`xiaozhi-server` Configuration:**
* **Local `config.yaml`:** A main YAML format configuration file located in the `xiaozhi-server` root directory. It defines server ports, selected AI service providers (ASR, LLM, TTS, VAD, Intent Recognition, Memory modules, etc.), their respective API keys or model paths, plugin configurations, and log levels.
* **Remote Configuration through `manager-api`:** `xiaozhi-server` is designed to obtain its operation configuration from `manager-api`. Settings obtained from `manager-api` typically override settings with the same name in the local `config.yaml`. This brings two major benefits:
* **Centralized Management:** All configurations can be managed uniformly through the `manager-web` interface.
* **Dynamic Updates:** `xiaozhi-server` can refresh its configuration and reinitialize AI modules without completely restarting the service.
* `config/config_loader.py` and `config/manage_api_client.py` in `xiaozhi-server` are responsible for handling configuration loading, merging, and pulling logic from `manager-api`.
2. **`manager-api` Configuration:**
* As a Spring Boot application, its configuration is mainly managed through the `application.properties` or `application.yml` file located in the `src/main/resources` directory.
* Key configuration items include: database connection information (MySQL URL, username, password), Redis server address and port, application service port (default 8002), Apache Shiro security-related settings, and configuration parameters for any integrated third-party services (such as Aliyun SMS).
3. **`manager-web` Configuration:**
* Environment-specific settings for the Vue.js frontend application are managed through `.env` series files (e.g., `.env`, `.env.development`, `.env.production`) in the project root directory.
* The most critical configuration here is usually the API base URL address of the `manager-api` backend (e.g., `VUE_APP_API_BASE_URL`), to which the frontend application will send all API requests.
4. **Predefined Configuration Schemes:**
* The project documentation (usually README) will recommend some common configuration combinations, for example:
* **"Entry Level Free Settings":** This scheme aims to utilize free tier quotas of cloud AI services or completely free local models to minimize users' initial usage costs and operating expenses.
* **"Full Streaming Configuration":** This scheme prioritizes system response speed and interaction fluency, typically choosing AI services that support streaming processing (possibly paid).
* These predefined schemes provide guidance for users to configure AI service providers in `xiaozhi-server` (through the `manager-web` interface or directly modifying `config.yaml`).
In the case of full module deployment, it is recommended to use the `manager-web` control panel as the main operation interface for most configuration tasks, as it provides a user-friendly way to manage various settings that are persisted by `manager-api` and ultimately used by `xiaozhi-server`.
---
+20 -1
View File
@@ -28,6 +28,8 @@
<captcha.version>1.6.2</captcha.version> <captcha.version>1.6.2</captcha.version>
<guava.version>33.0.0-jre</guava.version> <guava.version>33.0.0-jre</guava.version>
<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>
<okio-version>3.4.0</okio-version>
</properties> </properties>
<dependencies> <dependencies>
@@ -37,6 +39,7 @@
<classifier>jakarta</classifier> <classifier>jakarta</classifier>
<version>${shiro.version}</version> <version>${shiro.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.shiro</groupId> <groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId> <artifactId>shiro-spring</artifactId>
@@ -197,6 +200,22 @@
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- 阿里云短信sdk -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>${aliyun-sms-version}</version>
</dependency>
<!-- 阿里云短信sdk使用的okio:1.15.0版本有安全漏洞:GHSA-w33c-445m-f8w7 现在升级成3.4.0安全版本-->
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>${okio-version}</version>
</dependency>
</dependencies> </dependencies>
<!-- 阿里云maven仓库 --> <!-- 阿里云maven仓库 -->
@@ -239,4 +258,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
@@ -0,0 +1,40 @@
package xiaozhi.common.config;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@EnableAsync
@EnableAspectJAutoProxy(exposeProxy = true)
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(1000);
executor.setThreadNamePrefix("AsyncThread-");
// 设置拒绝策略:由调用线程执行
executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// 如果线程池已满,则由调用线程执行
r.run();
} catch (Exception e) {
throw new RuntimeException("执行异步任务失败", e);
}
}
});
executor.initialize();
return executor;
}
}
@@ -1,5 +1,7 @@
package xiaozhi.common.constant; package xiaozhi.common.constant;
import lombok.Getter;
/** /**
* 常量 * 常量
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -109,31 +111,29 @@ public interface Constant {
*/ */
String FILE_EXTENSION_SEG = "."; String FILE_EXTENSION_SEG = ".";
/**
* mcp接入点路径
*/
String SERVER_MCP_ENDPOINT = "server.mcp_endpoint";
/**
* 无记忆
*/
String MEMORY_NO_MEM = "Memory_nomem";
enum SysBaseParam { enum SysBaseParam {
/** /**
* 系统全称 * ICP备案号
*/ */
SYS_NAME("SYS_NAME"), BEIAN_ICP_NUM("server.beian_icp_num"),
/** /**
* 系统简称 * GA备案号
*/ */
SYS_SHORT_NAME("SYS_SHORT_NAME"), BEIAN_GA_NUM("server.beian_ga_num"),
/** /**
* 系统描述 * 系统名称
*/ */
SYS_DES("SYS_DES"), SERVER_NAME("server.name");
/**
* 登录失败几次锁定
*/
LOGIN_LOCK_COUNT("LOGIN_LOCK_COUNT"),
/**
* 账号失败锁定分钟数
*/
LOGIN_LOCK_TIME("LOGIN_LOCK_TIME"),
/**
* TOKEN强验证
*/
SYS_TOKEN_SECURITY("SYS_TOKEN_SECURITY");
private String value; private String value;
@@ -146,6 +146,46 @@ public interface Constant {
} }
} }
/**
* 系统短信
*/
enum SysMSMParam {
/**
* 阿里云授权keyID
*/
ALIYUN_SMS_ACCESS_KEY_ID("aliyun.sms.access_key_id"),
/**
* 阿里云授权密钥
*/
ALIYUN_SMS_ACCESS_KEY_SECRET("aliyun.sms.access_key_secret"),
/**
* 阿里云短信签名
*/
ALIYUN_SMS_SIGN_NAME("aliyun.sms.sign_name"),
/**
* 阿里云短信模板
*/
ALIYUN_SMS_SMS_CODE_TEMPLATE_CODE("aliyun.sms.sms_code_template_code"),
/**
* 单号码最大短信发送条数
*/
SERVER_SMS_MAX_SEND_COUNT("server.sms_max_send_count"),
/**
* 是否开启手机注册
*/
SERVER_ENABLE_MOBILE_REGISTER("server.enable_mobile_register");
private String value;
SysMSMParam(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
/** /**
* 数据状态 * 数据状态
*/ */
@@ -174,8 +214,48 @@ public interface Constant {
} }
} }
@Getter
enum ChatHistoryConfEnum {
IGNORE(0, "不记录"),
RECORD_TEXT(1, "记录文本"),
RECORD_TEXT_AUDIO(2, "文本音频都记录");
private final int code;
private final String name;
ChatHistoryConfEnum(int code, String name) {
this.code = code;
this.name = name;
}
}
/** /**
* 版本号 * 版本号
*/ */
public static final String VERSION = "0.3.13"; public static final String VERSION = "0.6.1";
/**
* 无效固件URL
*/
String INVALID_FIRMWARE_URL = "http://xiaozhi.server.com:8002/xiaozhi/otaMag/download/NOT_ACTIVATED_FIRMWARE_THIS_IS_A_INVALID_URL";
/**
* 字典类型
*/
enum DictType {
/**
* 手机区号
*/
MOBILE_AREA("MOBILE_AREA");
private String value;
DictType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
} }
@@ -20,7 +20,7 @@ public interface ErrorCode {
int ACCOUNT_DISABLE = 10005; int ACCOUNT_DISABLE = 10005;
int IDENTIFIER_NOT_NULL = 10006; int IDENTIFIER_NOT_NULL = 10006;
int CAPTCHA_ERROR = 10007; int CAPTCHA_ERROR = 10007;
int SUB_MENU_EXIST = 10008; int PHONE_NOT_NULL = 10008;
int PASSWORD_ERROR = 10009; int PASSWORD_ERROR = 10009;
int SUPERIOR_DEPT_ERROR = 10011; int SUPERIOR_DEPT_ERROR = 10011;
@@ -1,7 +1,12 @@
package xiaozhi.common.exception; package xiaozhi.common.exception;
import java.util.List;
import java.util.Objects;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.DuplicateKeyException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException; import org.springframework.web.servlet.resource.NoResourceFoundException;
@@ -60,4 +65,20 @@ public class RenExceptionHandler {
return new Result<Void>().error(404, "资源不存在"); return new Result<Void>().error(404, "资源不存在");
} }
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
String errorMsg = allErrors.stream()
.filter(Objects::nonNull)
.map(err -> {
String msg = err.getDefaultMessage();
return (msg != null && !msg.trim().isEmpty()) ? msg : null;
})
.filter(Objects::nonNull)
.findFirst()
.orElse("请求参数错误!");
return new Result<Void>().error(ErrorCode.PARAM_VALUE_NULL, errorMsg);
}
} }
@@ -62,6 +62,13 @@ public class RedisKeys {
return "agent:device:count:" + id; return "agent:device:count:" + id;
} }
/**
* 获取智能体最后连接时间缓存key
*/
public static String getAgentDeviceLastConnectedAtById(String id) {
return "agent:device:lastConnected:" + id;
}
/** /**
* 获取系统配置缓存key * 获取系统配置缓存key
*/ */
@@ -103,4 +110,33 @@ public class RedisKeys {
public static String getDictDataByTypeKey(String dictType) { public static String getDictDataByTypeKey(String dictType) {
return "sys:dict:data:" + dictType; return "sys:dict:data:" + dictType;
} }
/**
* 获取智能体音频ID的缓存key
*/
public static String getAgentAudioIdKey(String uuid) {
return "agent:audio:id:" + uuid;
}
/**
* 获取短信验证码的缓存key
*/
public static String getSMSValidateCodeKey(String phone) {
return "sms:Validate:Code:" + phone;
}
/**
* 获取短信验证码最后发送时间的缓存key
*/
public static String getSMSLastSendTimeKey(String phone) {
return "sms:Validate:Code:" + phone + ":last_send_time";
}
/**
* 获取短信验证码今日发送次数的缓存key
*/
public static String getSMSTodayCountKey(String phone) {
return "sms:Validate:Code:" + phone + ":today_count";
}
} }
@@ -6,12 +6,14 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import xiaozhi.common.utils.ResourcesUtils;
/** /**
* Redis工具类 * Redis工具类
@@ -23,6 +25,9 @@ public class RedisUtils {
@Resource @Resource
private RedisTemplate<String, Object> redisTemplate; private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ResourcesUtils resourceUtils;
/** /**
* 默认过期时长为24小时,单位:秒 * 默认过期时长为24小时,单位:秒
*/ */
@@ -40,6 +45,24 @@ public class RedisUtils {
*/ */
public final static long NOT_EXPIRE = -1L; public final static long NOT_EXPIRE = -1L;
public Long increment(String key, long expire) {
Long increment = redisTemplate.opsForValue().increment(key, 1L);
if (expire != NOT_EXPIRE) {
expire(key, expire);
}
return increment;
}
public Long increment(String key) {
return redisTemplate.opsForValue().increment(key, 1L);
}
public Long decrement(String key) {
return redisTemplate.opsForValue().decrement(key, 1L);
}
public void set(String key, Object value, long expire) { public void set(String key, Object value, long expire) {
redisTemplate.opsForValue().set(key, value); redisTemplate.opsForValue().set(key, value);
if (expire != NOT_EXPIRE) { if (expire != NOT_EXPIRE) {
@@ -134,7 +157,7 @@ public class RedisUtils {
*/ */
public void emptyAll() { public void emptyAll() {
// Lua 脚本 FLUSHALL是redis清空所有库的命令 // Lua 脚本 FLUSHALL是redis清空所有库的命令
String luaScript ="redis.call('FLUSHALL')"; String luaScript =resourceUtils.loadString("lua/emptyAll.lua");
// 创建 DefaultRedisScript 对象 // 创建 DefaultRedisScript 对象
DefaultRedisScript<Void> redisScript = new DefaultRedisScript<>(); DefaultRedisScript<Void> redisScript = new DefaultRedisScript<>();
@@ -147,4 +170,26 @@ public class RedisUtils {
} }
/**
* 获取在redis指定key的值,如果值为空,着设置key的默认值
* @param key redis的key
* @param defaultValue 默认值
* @param expiresInSecond 过期时间
* @return 返回key的值
*/
public String getKeyOrCreate(String key, String defaultValue,Long expiresInSecond) {
// Lua 脚本
String luaScript = resourceUtils.loadString("lua/getKeyOrCreate.lua");
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(luaScript);
redisScript.setResultType(String.class);
// 执行 Lua 脚本
List<String> keys = Collections.singletonList(key);
return redisTemplate.execute(redisScript, keys, defaultValue,expiresInSecond);
}
} }
@@ -1,6 +1,7 @@
package xiaozhi.common.service.impl; package xiaozhi.common.service.impl;
import java.io.Serializable; import java.io.Serializable;
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;
@@ -18,6 +19,7 @@ import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils;
@@ -45,6 +47,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
* @param params 分页查询参数 * @param params 分页查询参数
* @param defaultOrderField 默认排序字段 * @param defaultOrderField 默认排序字段
* @param isAsc 排序方式 * @param isAsc 排序方式
* @see xiaozhi.common.constant.Constant
* params.put(Constant.PAGE, "1");
* params.put(Constant.LIMIT, "10");
* params.put(Constant.ORDER_FIELD, "field"); // 单个字段
* params.put(Constant.ORDER_FIELD, List.of("field1", "field2")); // 多个字段
* params.put(Constant.ORDER, "asc");
*/ */
protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) { protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
// 分页参数 // 分页参数
@@ -65,28 +73,34 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
params.put(Constant.PAGE, page); params.put(Constant.PAGE, page);
// 排序字段 // 排序字段
String orderField = (String) params.get(Constant.ORDER_FIELD); Object orderField = params.get(Constant.ORDER_FIELD);
String order = (String) params.get(Constant.ORDER); String order = (String) params.get(Constant.ORDER);
// 前端字段排序 List<String> orderFields = new ArrayList<>();
if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) {
if (Constant.ASC.equalsIgnoreCase(order)) { // 处理排序字段
return page.addOrder(OrderItem.asc(orderField)); if (orderField instanceof String) {
orderFields.add((String) orderField);
} else if (orderField instanceof List) {
orderFields.addAll((List<String>) orderField);
}
// 有排序字段则排序
if (CollectionUtils.isNotEmpty(orderFields)) {
if (StringUtils.isNotBlank(order) && Constant.ASC.equalsIgnoreCase(order)) {
return page.addOrder(OrderItem.ascs(orderFields.toArray(new String[0])));
} else { } else {
return page.addOrder(OrderItem.desc(orderField)); return page.addOrder(OrderItem.descs(orderFields.toArray(new String[0])));
} }
} }
// 没有排序字段,则不排序 // 没有排序字段,使用默认排序
if (StringUtils.isBlank(defaultOrderField)) { if (StringUtils.isNotBlank(defaultOrderField)) {
return page; if (isAsc) {
} page.addOrder(OrderItem.asc(defaultOrderField));
} else {
// 默认排序 page.addOrder(OrderItem.desc(defaultOrderField));
if (isAsc) { }
page.addOrder(OrderItem.asc(defaultOrderField));
} else {
page.addOrder(OrderItem.desc(defaultOrderField));
} }
return page; return page;
@@ -0,0 +1,78 @@
package xiaozhi.common.utils;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AESUtils {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
/**
* AES加密
*
* @param key 密钥(16位、24位或32位)
* @param plainText 待加密字符串
* @return 加密后的Base64字符串
*/
public static String encrypt(String key, String plainText) {
try {
// 确保密钥长度为16、24或32位
byte[] keyBytes = padKey(key.getBytes(StandardCharsets.UTF_8));
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
} catch (Exception e) {
throw new RuntimeException("AES加密失败", e);
}
}
/**
* AES解密
*
* @param key 密钥(16位、24位或32位)
* @param encryptedText 待解密的Base64字符串
* @return 解密后的字符串
*/
public static String decrypt(String key, String encryptedText) {
try {
// 确保密钥长度为16、24或32位
byte[] keyBytes = padKey(key.getBytes(StandardCharsets.UTF_8));
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("AES解密失败", e);
}
}
/**
* 填充密钥到指定长度(16、24或32位)
*
* @param keyBytes 原始密钥字节数组
* @return 填充后的密钥字节数组
*/
private static byte[] padKey(byte[] keyBytes) {
int keyLength = keyBytes.length;
if (keyLength == 16 || keyLength == 24 || keyLength == 32) {
return keyBytes;
}
// 如果密钥长度不足,用0填充;如果超过,截取前32位
byte[] paddedKey = new byte[32];
System.arraycopy(keyBytes, 0, paddedKey, 0, Math.min(keyLength, 32));
return paddedKey;
}
}
@@ -21,6 +21,8 @@ public class DateUtils {
* 时间格式(yyyy-MM-dd HH:mm:ss) * 时间格式(yyyy-MM-dd HH:mm:ss)
*/ */
public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public final static String DATE_TIME_MILLIS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS";
/** /**
* 日期格式化 日期格式为:yyyy-MM-dd * 日期格式化 日期格式为:yyyy-MM-dd
@@ -63,6 +65,19 @@ public class DateUtils {
return null; return null;
} }
public static String getDateTimeNow() {
return getDateTimeNow(DATE_TIME_PATTERN);
}
public static String getDateTimeNow(String pattern) {
return format(new Date(), pattern);
}
public static String millsToSecond(long mills) {
return String.format("%.3f", mills / 1000.0);
}
/** /**
* 获取简短的时间字符串:10秒前返回刚刚,多少秒前,几小时前,超过一周返回年月日时分秒 * 获取简短的时间字符串:10秒前返回刚刚,多少秒前,几小时前,超过一周返回年月日时分秒
* @param date * @param date
@@ -0,0 +1,52 @@
package xiaozhi.common.utils;
import lombok.extern.slf4j.Slf4j;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 哈希加密算法的工具类
* @author zjy
*/
@Slf4j
public class HashEncryptionUtil {
/**
* 使用md5进行加密
* @param context 被加密的内容
* @return 哈希值
*/
public static String Md5hexDigest(String context){
return hexDigest(context,"MD5");
}
/**
* 指定哈希算法进行加密
* @param context 被加密的内容
* @param algorithm 哈希算法
* @return 哈希值
*/
public static String hexDigest(String context,String algorithm ){
// 获取MD5算法实例
MessageDigest md = null;
try {
md = MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
log.error("加密失败的算法:{}",algorithm);
throw new RuntimeException("加密失败,"+ algorithm +"哈希算法系统不支持");
}
// 计算智能体id的MD5值
byte[] messageDigest = md.digest(context.getBytes());
// 将字节数组转换为十六进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}
@@ -0,0 +1,44 @@
package xiaozhi.common.utils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import xiaozhi.common.exception.RenException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 资源处理工具
*/
@AllArgsConstructor
@Slf4j
@Component
public class ResourcesUtils {
private ResourceLoader resourceLoader;
/**
* 读取资源,返回字符串
* @param fileName 资源路径:resources下开始
* @return 字符串
*/
public String loadString(String fileName) {
Resource resource = resourceLoader.getResource("classpath:" + fileName);
StringBuilder luaScriptBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(resource.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
luaScriptBuilder.append(line).append("\n");
}
} catch (IOException e){
log.error("方法:loadString()读取资源失败--{}",e.getMessage());
throw new RenException("读取资源失败");
}
return luaScriptBuilder.toString();
}
}
@@ -0,0 +1,31 @@
package xiaozhi.common.utils;
/**
* 返回响应体工具类
*/
public class ResultUtils
{
public static <T> Result<T> success(T data) {
return new Result<T>().ok(data);
}
public static <T> Result<T> error() {
return new Result<T>().error();
}
public static <T> Result<T> error(String msg) {
return new Result<T>().error(msg);
}
public static <T> Result<T> error(int errorCode, String msg) {
return new Result<T>().error(errorCode, msg);
}
public static <T> Result<T> error(int errorCode) {
return new Result<T>().error(errorCode);
}
public static <T> Result<T> empty() {
return new Result<T>();
}
}
@@ -2,6 +2,7 @@ package xiaozhi.common.validator;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
@@ -45,4 +46,32 @@ public class ValidatorUtils {
throw new RenException(constraint.getMessage()); throw new RenException(constraint.getMessage());
} }
} }
/**
* 国际手机号正则表达式
* 要求必须带国际区号,格式:+[国家代码][手机号]
* 例如:
* - +8613800138000
* - +12345678900
* - +447123456789
*/
private static final String INTERNATIONAL_PHONE_REGEX = "^\\+[1-9]\\d{0,3}[1-9]\\d{4,14}$";
/**
* 校验手机号是否有效
* 要求必须带国际区号,格式:+[国家代码][手机号]
* 例如:+8613800138000
*
* @param phone 手机号
* @return boolean
*/
public static boolean isValidPhone(String phone) {
if (phone == null || phone.isEmpty()) {
return false;
}
// 验证必须带国际区号的手机号格式
Pattern pattern = Pattern.compile(INTERNATIONAL_PHONE_REGEX);
return pattern.matcher(phone).matches();
}
} }
@@ -1,10 +1,14 @@
package xiaozhi.modules.agent.controller; package xiaozhi.modules.agent.controller;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
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;
@@ -25,16 +29,26 @@ import jakarta.validation.Valid;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant; import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.user.UserDetail; import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.Result; import xiaozhi.common.utils.Result;
import xiaozhi.common.utils.ResultUtils;
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
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.entity.AgentTemplateEntity; import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentChatAudioService;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
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.agent.vo.AgentInfoVO;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.security.user.SecurityUser; import xiaozhi.modules.security.user.SecurityUser;
@@ -46,6 +60,10 @@ public class AgentController {
private final AgentService agentService; private final AgentService agentService;
private final AgentTemplateService agentTemplateService; private final AgentTemplateService agentTemplateService;
private final DeviceService deviceService; private final DeviceService deviceService;
private final AgentChatHistoryService agentChatHistoryService;
private final AgentChatAudioService agentChatAudioService;
private final AgentPluginMappingService agentPluginMappingService;
private final RedisUtils redisUtils;
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "获取用户智能体列表") @Operation(summary = "获取用户智能体列表")
@@ -72,42 +90,29 @@ public class AgentController {
@GetMapping("/{id}") @GetMapping("/{id}")
@Operation(summary = "获取智能体详情") @Operation(summary = "获取智能体详情")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result<AgentEntity> getAgentById(@PathVariable("id") String id) { public Result<AgentInfoVO> getAgentById(@PathVariable("id") String id) {
AgentEntity agent = agentService.getAgentById(id); AgentInfoVO agent = agentService.getAgentById(id);
return new Result<AgentEntity>().ok(agent); return ResultUtils.success(agent);
} }
@PostMapping @PostMapping
@Operation(summary = "创建智能体") @Operation(summary = "创建智能体")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result<Void> save(@RequestBody @Valid AgentCreateDTO dto) { public Result<String> save(@RequestBody @Valid AgentCreateDTO dto) {
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class); String agentId = agentService.createAgent(dto);
return new Result<String>().ok(agentId);
}
// 获取默认模板 @PutMapping("/saveMemory/{macAddress}")
AgentTemplateEntity template = agentTemplateService.getDefaultTemplate(); @Operation(summary = "根据设备id更新智能体")
if (template != null) { public Result<Void> updateByDeviceId(@PathVariable String macAddress, @RequestBody @Valid AgentMemoryDTO dto) {
// 设置模板中的默认值 DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
entity.setAsrModelId(template.getAsrModelId()); if (device == null) {
entity.setVadModelId(template.getVadModelId()); return new Result<>();
entity.setLlmModelId(template.getLlmModelId());
entity.setTtsModelId(template.getTtsModelId());
entity.setTtsVoiceId(template.getTtsVoiceId());
entity.setMemModelId(template.getMemModelId());
entity.setIntentModelId(template.getIntentModelId());
entity.setSystemPrompt(template.getSystemPrompt());
entity.setLangCode(template.getLangCode());
entity.setLanguage(template.getLanguage());
} }
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
// 设置用户ID和创建者信息 agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
UserDetail user = SecurityUser.getUser(); agentService.updateAgentById(device.getAgentId(), agentUpdateDTO);
entity.setUserId(user.getId());
entity.setCreator(user.getId());
entity.setCreatedAt(new Date());
// ID、智能体编码和排序会在Service层自动生成
agentService.insert(entity);
return new Result<>(); return new Result<>();
} }
@@ -115,60 +120,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);
AgentEntity existingEntity = agentService.getAgentById(id);
if (existingEntity == null) {
return new Result<Void>().error("智能体不存在");
}
// 只更新提供的非空字段
if (dto.getAgentName() != null) {
existingEntity.setAgentName(dto.getAgentName());
}
if (dto.getAgentCode() != null) {
existingEntity.setAgentCode(dto.getAgentCode());
}
if (dto.getAsrModelId() != null) {
existingEntity.setAsrModelId(dto.getAsrModelId());
}
if (dto.getVadModelId() != null) {
existingEntity.setVadModelId(dto.getVadModelId());
}
if (dto.getLlmModelId() != null) {
existingEntity.setLlmModelId(dto.getLlmModelId());
}
if (dto.getTtsModelId() != null) {
existingEntity.setTtsModelId(dto.getTtsModelId());
}
if (dto.getTtsVoiceId() != null) {
existingEntity.setTtsVoiceId(dto.getTtsVoiceId());
}
if (dto.getMemModelId() != null) {
existingEntity.setMemModelId(dto.getMemModelId());
}
if (dto.getIntentModelId() != null) {
existingEntity.setIntentModelId(dto.getIntentModelId());
}
if (dto.getSystemPrompt() != null) {
existingEntity.setSystemPrompt(dto.getSystemPrompt());
}
if (dto.getLangCode() != null) {
existingEntity.setLangCode(dto.getLangCode());
}
if (dto.getLanguage() != null) {
existingEntity.setLanguage(dto.getLanguage());
}
if (dto.getSort() != null) {
existingEntity.setSort(dto.getSort());
}
// 设置更新者信息
UserDetail user = SecurityUser.getUser();
existingEntity.setUpdater(user.getId());
existingEntity.setUpdatedAt(new Date());
agentService.updateById(existingEntity);
return new Result<>(); return new Result<>();
} }
@@ -178,6 +130,10 @@ public class AgentController {
public Result<Void> delete(@PathVariable String id) { public Result<Void> delete(@PathVariable String id) {
// 先删除关联的设备 // 先删除关联的设备
deviceService.deleteByAgentId(id); deviceService.deleteByAgentId(id);
// 删除关联的聊天记录
agentChatHistoryService.deleteByAgentId(id, true, true);
// 删除关联的插件
agentPluginMappingService.deleteByAgentId(id);
// 再删除智能体 // 再删除智能体
agentService.deleteById(id); agentService.deleteById(id);
return new Result<>(); return new Result<>();
@@ -192,4 +148,71 @@ public class AgentController {
return new Result<List<AgentTemplateEntity>>().ok(list); return new Result<List<AgentTemplateEntity>>().ok(list);
} }
@GetMapping("/{id}/sessions")
@Operation(summary = "获取智能体会话列表")
@RequiresPermissions("sys:role:normal")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
})
public Result<PageData<AgentChatSessionDTO>> getAgentSessions(
@PathVariable("id") String id,
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
params.put("agentId", id);
PageData<AgentChatSessionDTO> page = agentChatHistoryService.getSessionListByAgentId(params);
return new Result<PageData<AgentChatSessionDTO>>().ok(page);
}
@GetMapping("/{id}/chat-history/{sessionId}")
@Operation(summary = "获取智能体聊天记录")
@RequiresPermissions("sys:role:normal")
public Result<List<AgentChatHistoryDTO>> getAgentChatHistory(
@PathVariable("id") String id,
@PathVariable("sessionId") String sessionId) {
// 获取当前用户
UserDetail user = SecurityUser.getUser();
// 检查权限
if (!agentService.checkAgentPermission(id, user.getId())) {
return new Result<List<AgentChatHistoryDTO>>().error("没有权限查看该智能体的聊天记录");
}
// 查询聊天记录
List<AgentChatHistoryDTO> result = agentChatHistoryService.getChatHistoryBySessionId(id, sessionId);
return new Result<List<AgentChatHistoryDTO>>().ok(result);
}
@PostMapping("/audio/{audioId}")
@Operation(summary = "获取音频下载ID")
@RequiresPermissions("sys:role:normal")
public Result<String> getAudioId(@PathVariable("audioId") String audioId) {
byte[] audioData = agentChatAudioService.getAudio(audioId);
if (audioData == null) {
return new Result<String>().error("音频不存在");
}
String uuid = UUID.randomUUID().toString();
redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId);
return new Result<String>().ok(uuid);
}
@GetMapping("/play/{uuid}")
@Operation(summary = "播放音频")
public ResponseEntity<byte[]> playAudio(@PathVariable("uuid") String uuid) {
String audioId = (String) redisUtils.get(RedisKeys.getAgentAudioIdKey(uuid));
if (StringUtils.isBlank(audioId)) {
return ResponseEntity.notFound().build();
}
byte[] audioData = agentChatAudioService.getAudio(audioId);
if (audioData == null) {
return ResponseEntity.notFound().build();
}
redisUtils.delete(RedisKeys.getAgentAudioIdKey(uuid));
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"play.wav\"")
.body(audioData);
}
} }
@@ -0,0 +1,66 @@
package xiaozhi.modules.agent.controller;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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 lombok.RequiredArgsConstructor;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.Result;
import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.security.user.SecurityUser;
@Tag(name = "智能体Mcp接入点管理")
@RequiredArgsConstructor
@RestController
@RequestMapping("/agent/mcp")
public class AgentMcpAccessPointController {
private final AgentMcpAccessPointService agentMcpAccessPointService;
private final AgentService agentService;
/**
* 获取智能体的Mcp接入点地址
*
* @param audioId 智能体id
* @return 返回错误提醒或者Mcp接入点地址
*/
@Operation(summary = "获取智能体的Mcp接入点地址")
@GetMapping("/address/{agentId}")
@RequiresPermissions("sys:role:normal")
public Result<String> getAgentMcpAccessAddress(@PathVariable("agentId") String agentId) {
// 获取当前用户
UserDetail user = SecurityUser.getUser();
// 检查权限
if (!agentService.checkAgentPermission(agentId, user.getId())) {
return new Result<String>().error("没有权限查看该智能体的MCP接入点地址");
}
String agentMcpAccessAddress = agentMcpAccessPointService.getAgentMcpAccessAddress(agentId);
if (agentMcpAccessAddress == null) {
return new Result<String>().ok("请联系管理员进入参数管理配置mcp接入点地址");
}
return new Result<String>().ok(agentMcpAccessAddress);
}
@Operation(summary = "获取智能体的Mcp工具列表")
@GetMapping("/tools/{agentId}")
@RequiresPermissions("sys:role:normal")
public Result<List<String>> getAgentMcpToolsList(@PathVariable("agentId") String agentId) {
// 获取当前用户
UserDetail user = SecurityUser.getUser();
// 检查权限
if (!agentService.checkAgentPermission(agentId, user.getId())) {
return new Result<List<String>>().error("没有权限查看该智能体的MCP工具列表");
}
List<String> agentMcpToolsList = agentMcpAccessPointService.getAgentMcpToolsList(agentId);
return new Result<List<String>>().ok(agentMcpToolsList);
}
}
@@ -6,6 +6,7 @@ import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import xiaozhi.common.dao.BaseDao; import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.vo.AgentInfoVO;
@Mapper @Mapper
public interface AgentDao extends BaseDao<AgentEntity> { public interface AgentDao extends BaseDao<AgentEntity> {
@@ -28,4 +29,11 @@ public interface AgentDao extends BaseDao<AgentEntity> {
" WHERE d.mac_address = #{macAddress} " + " WHERE d.mac_address = #{macAddress} " +
" ORDER BY d.id DESC LIMIT 1") " ORDER BY d.id DESC LIMIT 1")
AgentEntity getDefaultAgentByMacAddress(@Param("macAddress") String macAddress); AgentEntity getDefaultAgentByMacAddress(@Param("macAddress") String macAddress);
/**
* 根据id查询agent信息,包括插件信息
*
* @param agentId 智能体ID
*/
AgentInfoVO selectAgentInfoById(@Param("agentId") String agentId);
} }
@@ -0,0 +1,22 @@
package xiaozhi.modules.agent.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import xiaozhi.modules.agent.entity.AgentPluginMapping;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Mapper
* @createDate 2025-05-25 22:33:17
* @Entity xiaozhi.modules.agent.entity.AgentPluginMapping
*/
@Mapper
public interface AgentPluginMappingMapper extends BaseMapper<AgentPluginMapping> {
List<AgentPluginMapping> selectPluginsByAgentId(@Param("agentId") String agentId);
}
@@ -1,7 +1,9 @@
package xiaozhi.modules.agent.dao; package xiaozhi.modules.agent.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
/** /**
@@ -13,4 +15,24 @@ import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
*/ */
@Mapper @Mapper
public interface AiAgentChatHistoryDao extends BaseMapper<AgentChatHistoryEntity> { public interface AiAgentChatHistoryDao extends BaseMapper<AgentChatHistoryEntity> {
/**
* 根据智能体ID删除音频
*
* @param agentId 智能体ID
*/
void deleteAudioByAgentId(String agentId);
/**
* 根据智能体ID删除聊天历史记录
*
* @param agentId 智能体ID
*/
void deleteHistoryByAgentId(String agentId);
/**
* 根据智能体ID删除音频ID
*
* @param agentId 智能体ID
*/
void deleteAudioIdByAgentId(String agentId);
} }
@@ -0,0 +1,28 @@
package xiaozhi.modules.agent.dto;
import java.util.Date;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 智能体聊天记录DTO
*/
@Data
@Schema(description = "智能体聊天记录")
public class AgentChatHistoryDTO {
@Schema(description = "创建时间")
private Date createdAt;
@Schema(description = "消息类型: 1-用户, 2-智能体")
private Byte chatType;
@Schema(description = "聊天内容")
private String content;
@Schema(description = "音频ID")
private String audioId;
@Schema(description = "MAC地址")
private String macAddress;
}
@@ -26,6 +26,8 @@ public class AgentChatHistoryReportDTO {
@Schema(description = "聊天内容", example = "你好呀") @Schema(description = "聊天内容", example = "你好呀")
@NotBlank @NotBlank
private String content; private String content;
@Schema(description = "文件数据(opus编码)", example = "") @Schema(description = "base64编码的opus音频数据", example = "")
private String opusDataBase64; private String audioBase64;
@Schema(description = "上报时间,十位时间戳,空时默认使用当前时间", example = "1745657732")
private Long reportTime;
} }
@@ -0,0 +1,26 @@
package xiaozhi.modules.agent.dto;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 智能体会话列表DTO
*/
@Data
public class AgentChatSessionDTO {
/**
* 会话ID
*/
private String sessionId;
/**
* 会话时间
*/
private LocalDateTime createdAt;
/**
* 聊天条数
*/
private Integer chatCount;
}
@@ -27,9 +27,19 @@ public class AgentDTO {
@Schema(description = "大语言模型名称", example = "llm_model_01") @Schema(description = "大语言模型名称", example = "llm_model_01")
private String llmModelName; private String llmModelName;
@Schema(description = "视觉模型名称", example = "vllm_model_01")
private String vllmModelName;
@Schema(description = "记忆模型ID", example = "mem_model_01")
private String memModelId;
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助") @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助")
private String systemPrompt; private String systemPrompt;
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
private String summaryMemory;
@Schema(description = "最后连接时间", example = "2024-03-20 10:00:00") @Schema(description = "最后连接时间", example = "2024-03-20 10:00:00")
private Date lastConnectedAt; private Date lastConnectedAt;
@@ -0,0 +1,19 @@
package xiaozhi.modules.agent.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 智能体记忆更新DTO
*/
@Data
@Schema(description = "智能体记忆更新对象")
public class AgentMemoryDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
private String summaryMemory;
}
@@ -1,6 +1,8 @@
package xiaozhi.modules.agent.dto; package xiaozhi.modules.agent.dto;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@@ -15,42 +17,67 @@ import lombok.Data;
public class AgentUpdateDTO implements Serializable { public class AgentUpdateDTO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Schema(description = "智能体编码", example = "AGT_1234567890", required = false) @Schema(description = "智能体编码", example = "AGT_1234567890", nullable = true)
private String agentCode; private String agentCode;
@Schema(description = "智能体名称", example = "客服助手", required = false) @Schema(description = "智能体名称", example = "客服助手", nullable = true)
private String agentName; private String agentName;
@Schema(description = "语音识别模型标识", example = "asr_model_02", required = false) @Schema(description = "语音识别模型标识", example = "asr_model_02", nullable = true)
private String asrModelId; private String asrModelId;
@Schema(description = "语音活动检测标识", example = "vad_model_02", required = false) @Schema(description = "语音活动检测标识", example = "vad_model_02", nullable = true)
private String vadModelId; private String vadModelId;
@Schema(description = "大语言模型标识", example = "llm_model_02", required = false) @Schema(description = "大语言模型标识", example = "llm_model_02", nullable = true)
private String llmModelId; private String llmModelId;
@Schema(description = "VLLM模型标识", example = "vllm_model_02", required = false)
private String vllmModelId;
@Schema(description = "语音合成模型标识", example = "tts_model_02", required = false) @Schema(description = "语音合成模型标识", example = "tts_model_02", required = false)
private String ttsModelId; private String ttsModelId;
@Schema(description = "音色标识", example = "voice_02", required = false) @Schema(description = "音色标识", example = "voice_02", nullable = true)
private String ttsVoiceId; private String ttsVoiceId;
@Schema(description = "记忆模型标识", example = "mem_model_02", required = false) @Schema(description = "记忆模型标识", example = "mem_model_02", nullable = true)
private String memModelId; private String memModelId;
@Schema(description = "意图模型标识", example = "intent_model_02", required = false) @Schema(description = "意图模型标识", example = "intent_model_02", nullable = true)
private String intentModelId; private String intentModelId;
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false) @Schema(description = "插件函数信息", nullable = true)
private List<FunctionInfo> functions;
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", nullable = true)
private String systemPrompt; private String systemPrompt;
@Schema(description = "语言编码", example = "zh_CN", required = false) @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n"
+ "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", nullable = true)
private String summaryMemory;
@Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", nullable = true)
private Integer chatHistoryConf;
@Schema(description = "语言编码", example = "zh_CN", nullable = true)
private String langCode; private String langCode;
@Schema(description = "交互语种", example = "中文", required = false) @Schema(description = "交互语种", example = "中文", nullable = true)
private String language; private String language;
@Schema(description = "排序", example = "1", required = false) @Schema(description = "排序", example = "1", nullable = true)
private Integer sort; private Integer sort;
@Data
@Schema(description = "插件函数信息")
public static class FunctionInfo implements Serializable {
@Schema(description = "插件ID", example = "plugin_01")
private String pluginId;
@Schema(description = "函数参数信息", nullable = true)
private HashMap<String, Object> paramInfo;
private static final long serialVersionUID = 1L;
}
} }
@@ -0,0 +1,32 @@
package xiaozhi.modules.agent.dto;
import lombok.Data;
/**
* MCP JSON-RPC 请求 DTO
*/
@Data
public class McpJsonRpcRequest {
private String jsonrpc = "2.0";
private String method;
private Object params;
private Integer id;
public McpJsonRpcRequest() {
}
public McpJsonRpcRequest(String method) {
this.method = method;
}
public McpJsonRpcRequest(String method, Object params, Integer id) {
this.method = method;
this.params = params;
this.id = id;
}
public McpJsonRpcRequest(String method, Object params) {
this.method = method;
this.params = params;
}
}
@@ -0,0 +1,48 @@
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() {
}
}
}
@@ -67,12 +67,6 @@ public class AgentChatHistoryEntity {
@TableField(value = "audio_id") @TableField(value = "audio_id")
private String audioId; private String audioId;
/**
* 音频URL
*/
@TableField(value = "audio_url")
private String audioUrl;
/** /**
* 创建时间 * 创建时间
*/ */
@@ -36,6 +36,9 @@ public class AgentEntity {
@Schema(description = "大语言模型标识") @Schema(description = "大语言模型标识")
private String llmModelId; private String llmModelId;
@Schema(description = "VLLM模型标识")
private String vllmModelId;
@Schema(description = "语音合成模型标识") @Schema(description = "语音合成模型标识")
private String ttsModelId; private String ttsModelId;
@@ -48,9 +51,16 @@ public class AgentEntity {
@Schema(description = "意图模型标识") @Schema(description = "意图模型标识")
private String intentModelId; private String intentModelId;
@Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)")
private Integer chatHistoryConf;
@Schema(description = "角色设定参数") @Schema(description = "角色设定参数")
private String systemPrompt; private String systemPrompt;
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
private String summaryMemory;
@Schema(description = "语言编码") @Schema(description = "语言编码")
private String langCode; private String langCode;
@@ -0,0 +1,54 @@
package xiaozhi.modules.agent.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* Agent与插件的唯一映射表
*
* @TableName ai_agent_plugin_mapping
*/
@Data
@TableName(value = "ai_agent_plugin_mapping")
@Schema(description = "Agent与插件的唯一映射表")
public class AgentPluginMapping implements Serializable {
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "映射信息主键ID")
private Long id;
/**
* 智能体ID
*/
@Schema(description = "智能体ID")
private String agentId;
/**
* 插件ID
*/
@Schema(description = "插件ID")
private String pluginId;
/**
* 插件参数(Json)格式
*/
@Schema(description = "插件参数(Json)格式")
private String paramInfo;
// 冗余字段,用于方便在根据id查询插件时,对照查出插件的Provider_code,详见dao层xml文件
@TableField(exist = false)
@Schema(description = "插件provider_code, 对应表ai_model_provider")
private String providerCode;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
@@ -49,6 +49,11 @@ public class AgentTemplateEntity implements Serializable {
*/ */
private String llmModelId; private String llmModelId;
/**
* VLLM模型标识
*/
private String vllmModelId;
/** /**
* 语音合成模型标识 * 语音合成模型标识
*/ */
@@ -69,11 +74,20 @@ public class AgentTemplateEntity implements Serializable {
*/ */
private String intentModelId; private String intentModelId;
/**
* 聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)
*/
private Integer chatHistoryConf;
/** /**
* 角色设定参数 * 角色设定参数
*/ */
private String systemPrompt; private String systemPrompt;
/**
* 总结记忆
*/
private String summaryMemory;
/** /**
* 语言编码 * 语言编码
*/ */
@@ -19,4 +19,12 @@ public interface AgentChatAudioService extends IService<AgentChatAudioEntity> {
* @return 音频ID * @return 音频ID
*/ */
String saveAudio(byte[] audioData); String saveAudio(byte[] audioData);
}
/**
* 获取音频数据
*
* @param audioId 音频ID
* @return 音频数据
*/
byte[] getAudio(String audioId);
}
@@ -1,6 +1,13 @@
package xiaozhi.modules.agent.service; package xiaozhi.modules.agent.service;
import java.util.List;
import java.util.Map;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import xiaozhi.common.page.PageData;
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
/** /**
@@ -11,4 +18,30 @@ import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
* @since 1.0.0 * @since 1.0.0
*/ */
public interface AgentChatHistoryService extends IService<AgentChatHistoryEntity> { public interface AgentChatHistoryService extends IService<AgentChatHistoryEntity> {
/**
* 根据智能体ID获取会话列表
*
* @param params 查询参数,包含agentId、page、limit
* @return 分页的会话列表
*/
PageData<AgentChatSessionDTO> getSessionListByAgentId(Map<String, Object> params);
/**
* 根据会话ID获取聊天记录列表
*
* @param agentId 智能体ID
* @param sessionId 会话ID
* @return 聊天记录列表
*/
List<AgentChatHistoryDTO> getChatHistoryBySessionId(String agentId, String sessionId);
/**
* 根据智能体ID删除聊天记录
*
* @param agentId 智能体ID
* @param deleteAudio 是否删除音频
* @param deleteText 是否删除文本
*/
void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText);
} }
@@ -0,0 +1,25 @@
package xiaozhi.modules.agent.service;
import java.util.List;
/**
* 智能体Mcp接入点处理service
*
* @author zjy
*/
public interface AgentMcpAccessPointService {
/**
* 获取智能体的mcp接入点地址
* @param id 智能体id
* @return mcp接入点地址
*/
String getAgentMcpAccessAddress(String id);
/**
* 获取智能体的mcp接入点已有的工具列表
* @param id 智能体id
* @return 工具列表
*/
List<String> getAgentMcpToolsList(String id);
}
@@ -0,0 +1,29 @@
package xiaozhi.modules.agent.service;
import java.util.List;
import com.baomidou.mybatisplus.extension.service.IService;
import xiaozhi.modules.agent.entity.AgentPluginMapping;
/**
* @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Service
* @createDate 2025-05-25 22:33:17
*/
public interface AgentPluginMappingService extends IService<AgentPluginMapping> {
/**
* 根据智能体id获取插件参数
*
* @param agentId
* @return
*/
List<AgentPluginMapping> agentPluginParamsByAgentId(String agentId);
/**
* 根据智能体id删除插件参数
*
* @param agentId
*/
void deleteByAgentId(String agentId);
}
@@ -5,39 +5,62 @@ import java.util.Map;
import xiaozhi.common.page.PageData; 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.AgentDTO; import xiaozhi.modules.agent.dto.AgentDTO;
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.vo.AgentInfoVO;
/**
* 智能体表处理service
*
* @author Goody
* @version 1.0, 2025/4/30
* @since 1.0.0
*/
public interface AgentService extends BaseService<AgentEntity> { public interface AgentService extends BaseService<AgentEntity> {
/** /**
* 管理员获取所有智能体列表(分页) * 获取管理员智能体列表
*
* @param params 查询参数
* @return 分页数据
*/ */
PageData<AgentEntity> adminAgentList(Map<String, Object> params); PageData<AgentEntity> adminAgentList(Map<String, Object> params);
/** /**
* 获取智能体详情 * 根据ID获取智能体
*
* @param id 智能体ID
* @return 智能体实体
*/ */
AgentEntity getAgentById(String id); AgentInfoVO getAgentById(String id);
/** /**
* 删除这个用户的所有 * 插入智能体
* *
* @param userId * @param entity 智能体实体
* @return 是否成功
*/
boolean insert(AgentEntity entity);
/**
* 根据用户ID删除智能体
*
* @param userId 用户ID
*/ */
void deleteAgentByUserId(Long userId); void deleteAgentByUserId(Long userId);
/** /**
* 获取用户智能体列表 * 获取用户智能体列表
* *
* @param userId * @param userId 用户ID
* @return * @return 智能体列表
*/ */
List<AgentDTO> getUserAgents(Long userId); List<AgentDTO> getUserAgents(Long userId);
/** /**
* 获取智能体设备数量 * 根据智能体ID获取设备数量
* *
* @param agentId 智能体ID * @param agentId 智能体ID
* @return 设备数量 * @return 设备数量
*/ */
@@ -50,4 +73,29 @@ public interface AgentService extends BaseService<AgentEntity> {
* @return 默认智能体信息,不存在时返回null * @return 默认智能体信息,不存在时返回null
*/ */
AgentEntity getDefaultAgentByMacAddress(String macAddress); AgentEntity getDefaultAgentByMacAddress(String macAddress);
/**
* 检查用户是否有权限访问智能体
*
* @param agentId 智能体ID
* @param userId 用户ID
* @return 是否有权限
*/
boolean checkAgentPermission(String agentId, Long userId);
/**
* 更新智能体
*
* @param agentId 智能体ID
* @param dto 更新智能体所需的信息
*/
void updateAgentById(String agentId, AgentUpdateDTO dto);
/**
* 创建智能体
*
* @param dto 创建智能体所需的信息
* @return 创建的智能体ID
*/
String createAgent(AgentCreateDTO dto);
} }
@@ -1,10 +1,17 @@
package xiaozhi.modules.agent.service.biz.impl; package xiaozhi.modules.agent.service.biz.impl;
import java.util.Base64;
import java.util.Date;
import java.util.Objects;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO; import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO;
import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentEntity;
@@ -12,6 +19,8 @@ import xiaozhi.modules.agent.service.AgentChatAudioService;
import xiaozhi.modules.agent.service.AgentChatHistoryService; import xiaozhi.modules.agent.service.AgentChatHistoryService;
import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService; import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService;
/** /**
* {@link AgentChatHistoryBizService} impl * {@link AgentChatHistoryBizService} impl
@@ -27,6 +36,8 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
private final AgentService agentService; private final AgentService agentService;
private final AgentChatHistoryService agentChatHistoryService; private final AgentChatHistoryService agentChatHistoryService;
private final AgentChatAudioService agentChatAudioService; private final AgentChatAudioService agentChatAudioService;
private final RedisUtils redisUtils;
private final DeviceService deviceService;
/** /**
* 处理聊天记录上报,包括文件上传和相关信息记录 * 处理聊天记录上报,包括文件上传和相关信息记录
@@ -39,32 +50,63 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
public Boolean report(AgentChatHistoryReportDTO report) { public Boolean report(AgentChatHistoryReportDTO report) {
String macAddress = report.getMacAddress(); String macAddress = report.getMacAddress();
Byte chatType = report.getChatType(); Byte chatType = report.getChatType();
log.info("小智设备聊天上报请求: macAddress={}, type={}", macAddress, chatType); Long reportTimeMillis = null != report.getReportTime() ? report.getReportTime() * 1000 : System.currentTimeMillis();
log.info("小智设备聊天上报请求: macAddress={}, type={} reportTime={}", macAddress, chatType, reportTimeMillis);
// 1. base64解码report.getOpusDataBase64(),存入ai_agent_chat_audio表 // 根据设备MAC地址查询对应的默认智能体,判断是否需要上报
String audioId = null;
if (report.getOpusDataBase64() != null && !report.getOpusDataBase64().isEmpty()) {
try {
// TODO: 需要考虑保留什么格式的音频数据,比如是opus还是wave
// byte[] audioData = Base64.getDecoder().decode(report.getOpusDataBase64());
// audioId = agentChatAudioService.saveAudio(audioData);
// log.info("音频数据保存成功,audioId={}", audioId);
} catch (Exception e) {
log.error("音频数据保存失败", e);
return false;
}
}
// 2. 组装上报数据
// 2.1 根据设备MAC地址查询对应的默认智能体,判断是否需要上报
AgentEntity agentEntity = agentService.getDefaultAgentByMacAddress(macAddress); AgentEntity agentEntity = agentService.getDefaultAgentByMacAddress(macAddress);
if (agentEntity == null) { if (agentEntity == null) {
return false; return Boolean.FALSE;
} }
String agentId = agentEntity.getId();
log.info("设备 {} 对应智能体 {} 上报", macAddress, agentEntity.getId());
// 2.2 构建聊天记录实体 Integer chatHistoryConf = agentEntity.getChatHistoryConf();
String agentId = agentEntity.getId();
if (Objects.equals(chatHistoryConf, Constant.ChatHistoryConfEnum.RECORD_TEXT.getCode())) {
saveChatText(report, agentId, macAddress, null, reportTimeMillis);
} else if (Objects.equals(chatHistoryConf, Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode())) {
String audioId = saveChatAudio(report);
saveChatText(report, agentId, macAddress, audioId, reportTimeMillis);
}
// 更新设备最后对话时间
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;
}
/**
* base64解码report.getOpusDataBase64(),存入ai_agent_chat_audio表
*/
private String saveChatAudio(AgentChatHistoryReportDTO report) {
String audioId = null;
if (report.getAudioBase64() != null && !report.getAudioBase64().isEmpty()) {
try {
byte[] audioData = Base64.getDecoder().decode(report.getAudioBase64());
audioId = agentChatAudioService.saveAudio(audioData);
log.info("音频数据保存成功,audioId={}", audioId);
} catch (Exception e) {
log.error("音频数据保存失败", e);
return null;
}
}
return audioId;
}
/**
* 组装上报数据
*/
private void saveChatText(AgentChatHistoryReportDTO report, String agentId, String macAddress, String audioId, Long reportTime) {
// 构建聊天记录实体
AgentChatHistoryEntity entity = AgentChatHistoryEntity.builder() AgentChatHistoryEntity entity = AgentChatHistoryEntity.builder()
.macAddress(macAddress) .macAddress(macAddress)
.agentId(agentId) .agentId(agentId)
@@ -72,10 +114,13 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
.chatType(report.getChatType()) .chatType(report.getChatType())
.content(report.getContent()) .content(report.getContent())
.audioId(audioId) .audioId(audioId)
.createdAt(new Date(reportTime))
// NOTE(haotian): 2025/5/26 updateAt可以不设置,重点是createAt,而且这样可以看到上报延迟
.build(); .build();
// 3. 保存数据 // 保存数据
agentChatHistoryService.save(entity); agentChatHistoryService.save(entity);
return Boolean.TRUE;
log.info("设备 {} 对应智能体 {} 上报成功", macAddress, agentId);
} }
} }
@@ -25,4 +25,10 @@ public class AgentChatAudioServiceImpl extends ServiceImpl<AiAgentChatAudioDao,
save(entity); save(entity);
return entity.getId(); return entity.getId();
} }
}
@Override
public byte[] getAudio(String audioId) {
AgentChatAudioEntity entity = getById(audioId);
return entity != null ? entity.getAudio() : null;
}
}
@@ -1,19 +1,93 @@
package xiaozhi.modules.agent.service.impl; package xiaozhi.modules.agent.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import java.time.LocalDateTime;
import org.springframework.stereotype.Service; import java.util.List;
import xiaozhi.modules.agent.dao.AiAgentChatHistoryDao; import java.util.Map;
import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; import java.util.stream.Collectors;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
import org.springframework.stereotype.Service;
/** import org.springframework.transaction.annotation.Transactional;
* 智能体聊天记录表处理service {@link AgentChatHistoryService} impl
* import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
* @author Goody import com.baomidou.mybatisplus.core.metadata.IPage;
* @version 1.0, 2025/4/30 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
* @since 1.0.0 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
*/
@Service import xiaozhi.common.constant.Constant;
public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryDao, AgentChatHistoryEntity> implements AgentChatHistoryService { import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.ConvertUtils;
} import xiaozhi.modules.agent.dao.AiAgentChatHistoryDao;
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
/**
* 智能体聊天记录表处理service {@link AgentChatHistoryService} impl
*
* @author Goody
* @version 1.0, 2025/4/30
* @since 1.0.0
*/
@Service
public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryDao, AgentChatHistoryEntity>
implements AgentChatHistoryService {
@Override
public PageData<AgentChatSessionDTO> getSessionListByAgentId(Map<String, Object> params) {
String agentId = (String) params.get("agentId");
int page = Integer.parseInt(params.get(Constant.PAGE).toString());
int limit = Integer.parseInt(params.get(Constant.LIMIT).toString());
// 构建查询条件
QueryWrapper<AgentChatHistoryEntity> wrapper = new QueryWrapper<>();
wrapper.select("session_id", "MAX(created_at) as created_at", "COUNT(*) as chat_count")
.eq("agent_id", agentId)
.groupBy("session_id")
.orderByDesc("created_at");
// 执行分页查询
Page<Map<String, Object>> pageParam = new Page<>(page, limit);
IPage<Map<String, Object>> result = this.baseMapper.selectMapsPage(pageParam, wrapper);
List<AgentChatSessionDTO> records = result.getRecords().stream().map(map -> {
AgentChatSessionDTO dto = new AgentChatSessionDTO();
dto.setSessionId((String) map.get("session_id"));
dto.setCreatedAt((LocalDateTime) map.get("created_at"));
dto.setChatCount(((Number) map.get("chat_count")).intValue());
return dto;
}).collect(Collectors.toList());
return new PageData<>(records, result.getTotal());
}
@Override
public List<AgentChatHistoryDTO> getChatHistoryBySessionId(String agentId, String sessionId) {
// 构建查询条件
QueryWrapper<AgentChatHistoryEntity> wrapper = new QueryWrapper<>();
wrapper.eq("agent_id", agentId)
.eq("session_id", sessionId)
.orderByAsc("created_at");
// 查询聊天记录
List<AgentChatHistoryEntity> historyList = list(wrapper);
// 转换为DTO
return ConvertUtils.sourceToTarget(historyList, AgentChatHistoryDTO.class);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText) {
if (deleteAudio) {
baseMapper.deleteAudioByAgentId(agentId);
}
if (deleteAudio && !deleteText) {
baseMapper.deleteAudioIdByAgentId(agentId);
}
if (deleteText) {
baseMapper.deleteHistoryByAgentId(agentId);
}
}
}
@@ -0,0 +1,207 @@
package xiaozhi.modules.agent.service.impl;
import java.net.URI;
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;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.constant.Constant;
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.service.AgentMcpAccessPointService;
import xiaozhi.modules.sys.service.SysParamsService;
import xiaozhi.modules.sys.utils.WebSocketClientManager;
@AllArgsConstructor
@Service
@Slf4j
public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointService {
private SysParamsService sysParamsService;
@Override
public String getAgentMcpAccessAddress(String id) {
// 获取到mcp的地址
String url = sysParamsService.getValue(Constant.SERVER_MCP_ENDPOINT, true);
if (StringUtils.isBlank(url) || "null".equals(url)) {
return null;
}
URI uri = getURI(url);
// 获取智能体mcp的url前缀
String agentMcpUrl = getAgentMcpUrl(uri);
// 获取密钥
String key = getSecretKey(uri);
// 获取加密的token
String encryptToken = encryptToken(id, key);
// 对token进行URL编码
String encodedToken = URLEncoder.encode(encryptToken, StandardCharsets.UTF_8);
// 返回智能体Mcp路径的格式
agentMcpUrl = "%s/mcp/?token=%s".formatted(agentMcpUrl, encodedToken);
return agentMcpUrl;
}
@Override
public List<String> getAgentMcpToolsList(String id) {
String wsUrl = getAgentMcpAccessAddress(id);
if (StringUtils.isBlank(wsUrl)) {
return List.of();
}
// 将 /mcp 替换为 /call
wsUrl = wsUrl.replace("/mcp/", "/call/");
try {
// 创建 WebSocket 连接
try (WebSocketClientManager client = WebSocketClientManager.build(
new WebSocketClientManager.Builder()
.uri(wsUrl)
.connectTimeout(5, TimeUnit.SECONDS)
.maxSessionDuration(9, TimeUnit.SECONDS))) {
// 发送初始化消息
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);
// 等待初始化响应
Thread.sleep(200);
// 发送初始化完成通知
// 对于通知类型的消息,手动构建JSON以避免包含null字段
String notificationJson = "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}";
client.sendText(notificationJson);
// 等待 0.2 秒
Thread.sleep(200);
// 发送工具列表请求
McpJsonRpcRequest toolsRequest = new McpJsonRpcRequest("tools/list", null, 2);
client.sendJson(toolsRequest);
// 监听响应,直到收到包含 id=2 的响应(tools/list响应)
List<String> responses = client.listener(response -> {
try {
// 先尝试解析为通用JSON对象来获取id
Map<String, Object> 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 : responses) {
try {
// 先解析为通用JSON对象
Map<String, Object> 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<String, Object> resultMap = (Map<String, Object>) resultObj;
Object toolsObj = resultMap.get("tools");
if (toolsObj instanceof List) {
List<Map<String, Object>> toolsList = (List<Map<String, Object>>) toolsObj;
// 提取工具名称列表
return toolsList.stream()
.map(tool -> (String) tool.get("name"))
.filter(name -> name != null)
.collect(Collectors.toList());
}
}
}
} catch (Exception e) {
log.warn("处理工具列表响应失败: {}", response, e);
}
}
log.warn("未找到有效的工具列表响应");
return List.of();
}
} catch (Exception e) {
log.error("获取智能体 MCP 工具列表失败,智能体ID: {}", id, e);
return List.of();
}
}
/**
* 获取URI对象
*
* @param url 路径
* @return URI对象
*/
private static URI getURI(String url) {
try {
return new URI(url);
} catch (URISyntaxException e) {
log.error("路径格式不正确路径:{}\n错误信息:{}", url, e.getMessage());
throw new RuntimeException("mcp的地址存在错误,请进入参数管理修改mcp接入点地址");
}
}
/**
* 获取密钥
*
* @param uri mcp地址
* @return 密钥
*/
private static String getSecretKey(URI uri) {
// 获取参数
String query = uri.getQuery();
// 获取aes加密密钥
String str = "key=";
return query.substring(query.indexOf(str) + str.length());
}
/**
* 获取智能体mcp接入点url
*
* @param uri mcp地址
* @return 智能体mcp接入点url
*/
private String getAgentMcpUrl(URI uri) {
// 获取协议
String wsScheme = (uri.getScheme().equals("https")) ? "wss" : "ws";
// 获取主机,端口,路径
String path = uri.getSchemeSpecificPart();
// 获取到最后一个/前的path
path = path.substring(0, path.lastIndexOf("/"));
return wsScheme + ":" + path;
}
/**
* 获取对智能体id加密的token
*
* @param agentId 智能体id
* @param key 加密密钥
* @return 加密后token
*/
private static String encryptToken(String agentId, String key) {
// 使用md5对智能体id进行加密
String md5 = HashEncryptionUtil.Md5hexDigest(agentId);
// aes需要加密文本
String json = "{\"agentId\": \"%s\"}".formatted(md5);
// 加密后成token值
return AESUtils.encrypt(key, json);
}
}
@@ -0,0 +1,37 @@
package xiaozhi.modules.agent.service.impl;
import java.util.List;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import xiaozhi.modules.agent.dao.AgentPluginMappingMapper;
import xiaozhi.modules.agent.entity.AgentPluginMapping;
import xiaozhi.modules.agent.service.AgentPluginMappingService;
/**
* @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Service实现
* @createDate 2025-05-25 22:33:17
*/
@Service
@RequiredArgsConstructor
public class AgentPluginMappingServiceImpl extends ServiceImpl<AgentPluginMappingMapper, AgentPluginMapping>
implements AgentPluginMappingService {
private final AgentPluginMappingMapper agentPluginMappingMapper;
@Override
public List<AgentPluginMapping> agentPluginParamsByAgentId(String agentId) {
return agentPluginMappingMapper.selectPluginsByAgentId(agentId);
}
@Override
public void deleteByAgentId(String agentId) {
UpdateWrapper<AgentPluginMapping> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("agent_id", agentId);
agentPluginMappingMapper.delete(updateWrapper);
}
}
@@ -1,45 +1,64 @@
package xiaozhi.modules.agent.service.impl; package xiaozhi.modules.agent.service.impl;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
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.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
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.service.impl.BaseServiceImpl; import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.JsonUtils;
import xiaozhi.modules.agent.dao.AgentDao; import xiaozhi.modules.agent.dao.AgentDao;
import xiaozhi.modules.agent.dto.AgentCreateDTO;
import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.dto.AgentDTO;
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentPluginMapping;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
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.vo.AgentInfoVO;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.service.ModelConfigService; import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.model.service.ModelProviderService;
import xiaozhi.modules.security.user.SecurityUser;
import xiaozhi.modules.sys.enums.SuperAdminEnum;
import xiaozhi.modules.timbre.service.TimbreService; import xiaozhi.modules.timbre.service.TimbreService;
@Service @Service
@AllArgsConstructor
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService { public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
private final AgentDao agentDao; private final AgentDao agentDao;
private final TimbreService timbreModelService;
@Autowired private final ModelConfigService modelConfigService;
private TimbreService timbreModelService; private final RedisUtils redisUtils;
private final DeviceService deviceService;
@Autowired private final AgentPluginMappingService agentPluginMappingService;
private ModelConfigService modelConfigService; private final AgentChatHistoryService agentChatHistoryService;
private final AgentTemplateService agentTemplateService;
@Autowired private final ModelProviderService modelProviderService;
private RedisUtils redisUtils;
public AgentServiceImpl(AgentDao agentDao) {
this.agentDao = agentDao;
}
@Override @Override
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) { public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
@@ -50,8 +69,21 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
} }
@Override @Override
public AgentEntity getAgentById(String id) { public AgentInfoVO getAgentById(String id) {
return agentDao.selectById(id); AgentInfoVO agent = agentDao.selectAgentInfoById(id);
if (agent == null) {
throw new RenException("智能体不存在");
}
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
if (agent.getChatHistoryConf() == null) {
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
}
}
// 无需额外查询插件列表,已通过SQL查询出来
return agent;
} }
@Override @Override
@@ -98,12 +130,20 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
// 获取 LLM 模型名称 // 获取 LLM 模型名称
dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId())); dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId()));
// 获取 VLLM 模型名称
dto.setVllmModelName(modelConfigService.getModelNameById(agent.getVllmModelId()));
// 获取记忆模型名称
dto.setMemModelId(agent.getMemModelId());
// 获取 TTS 音色名称 // 获取 TTS 音色名称
dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId())); dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId()));
// 获取智能体最近的最后连接时长
dto.setLastConnectedAt(deviceService.getLatestLastConnectionTime(agent.getId()));
// 获取设备数量 // 获取设备数量
dto.setDeviceCount(getDeviceCountByAgentId(agent.getId())); dto.setDeviceCount(getDeviceCountByAgentId(agent.getId()));
return dto; return dto;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
@@ -138,4 +178,215 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
} }
return agentDao.getDefaultAgentByMacAddress(macAddress); return agentDao.getDefaultAgentByMacAddress(macAddress);
} }
@Override
public boolean checkAgentPermission(String agentId, Long 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更新智能体信息
@Override
@Transactional(rollbackFor = Exception.class)
public void updateAgentById(String agentId, AgentUpdateDTO dto) {
// 先查询现有实体
AgentEntity existingEntity = this.getAgentById(agentId);
if (existingEntity == null) {
throw new RuntimeException("智能体不存在");
}
// 只更新提供的非空字段
if (dto.getAgentName() != null) {
existingEntity.setAgentName(dto.getAgentName());
}
if (dto.getAgentCode() != null) {
existingEntity.setAgentCode(dto.getAgentCode());
}
if (dto.getAsrModelId() != null) {
existingEntity.setAsrModelId(dto.getAsrModelId());
}
if (dto.getVadModelId() != null) {
existingEntity.setVadModelId(dto.getVadModelId());
}
if (dto.getLlmModelId() != null) {
existingEntity.setLlmModelId(dto.getLlmModelId());
}
if (dto.getVllmModelId() != null) {
existingEntity.setVllmModelId(dto.getVllmModelId());
}
if (dto.getTtsModelId() != null) {
existingEntity.setTtsModelId(dto.getTtsModelId());
}
if (dto.getTtsVoiceId() != null) {
existingEntity.setTtsVoiceId(dto.getTtsVoiceId());
}
if (dto.getMemModelId() != null) {
existingEntity.setMemModelId(dto.getMemModelId());
}
if (dto.getIntentModelId() != null) {
existingEntity.setIntentModelId(dto.getIntentModelId());
}
if (dto.getSystemPrompt() != null) {
existingEntity.setSystemPrompt(dto.getSystemPrompt());
}
if (dto.getSummaryMemory() != null) {
existingEntity.setSummaryMemory(dto.getSummaryMemory());
}
if (dto.getChatHistoryConf() != null) {
existingEntity.setChatHistoryConf(dto.getChatHistoryConf());
}
if (dto.getLangCode() != null) {
existingEntity.setLangCode(dto.getLangCode());
}
if (dto.getLanguage() != null) {
existingEntity.setLanguage(dto.getLanguage());
}
if (dto.getSort() != null) {
existingEntity.setSort(dto.getSort());
}
// 更新函数插件信息
List<AgentUpdateDTO.FunctionInfo> functions = dto.getFunctions();
if (functions != null) {
// 1. 收集本次提交的 pluginId
List<String> newPluginIds = functions.stream()
.map(AgentUpdateDTO.FunctionInfo::getPluginId)
.toList();
// 2. 查询当前agent现有的所有映射
List<AgentPluginMapping> existing = agentPluginMappingService.list(
new QueryWrapper<AgentPluginMapping>()
.eq("agent_id", agentId));
Map<String, AgentPluginMapping> existMap = existing.stream()
.collect(Collectors.toMap(AgentPluginMapping::getPluginId, Function.identity()));
// 3. 构造所有要 保存或更新 的实体
List<AgentPluginMapping> allToPersist = functions.stream().map(info -> {
AgentPluginMapping m = new AgentPluginMapping();
m.setAgentId(agentId);
m.setPluginId(info.getPluginId());
m.setParamInfo(JsonUtils.toJsonString(info.getParamInfo()));
AgentPluginMapping old = existMap.get(info.getPluginId());
if (old != null) {
// 已存在,设置id表示更新
m.setId(old.getId());
}
return m;
}).toList();
// 4. 拆分:已有ID的走更新,无ID的走插入
List<AgentPluginMapping> toUpdate = allToPersist.stream()
.filter(m -> m.getId() != null)
.toList();
List<AgentPluginMapping> toInsert = allToPersist.stream()
.filter(m -> m.getId() == null)
.toList();
if (!toUpdate.isEmpty()) {
agentPluginMappingService.updateBatchById(toUpdate);
}
if (!toInsert.isEmpty()) {
agentPluginMappingService.saveBatch(toInsert);
}
// 5. 删除本次不在提交列表里的插件映射
List<Long> toDelete = existing.stream()
.filter(old -> !newPluginIds.contains(old.getPluginId()))
.map(AgentPluginMapping::getId)
.toList();
if (!toDelete.isEmpty()) {
agentPluginMappingService.removeBatchByIds(toDelete);
}
}
// 设置更新者信息
UserDetail user = SecurityUser.getUser();
existingEntity.setUpdater(user.getId());
existingEntity.setUpdatedAt(new Date());
// 更新记忆策略
if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
// 删除所有记录
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true);
existingEntity.setSummaryMemory("");
} else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) {
// 删除音频数据
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false);
}
this.updateById(existingEntity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String createAgent(AgentCreateDTO dto) {
// 转换为实体
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
// 获取默认模板
AgentTemplateEntity template = agentTemplateService.getDefaultTemplate();
if (template != null) {
// 设置模板中的默认值
entity.setAsrModelId(template.getAsrModelId());
entity.setVadModelId(template.getVadModelId());
entity.setLlmModelId(template.getLlmModelId());
entity.setVllmModelId(template.getVllmModelId());
entity.setTtsModelId(template.getTtsModelId());
entity.setTtsVoiceId(template.getTtsVoiceId());
entity.setMemModelId(template.getMemModelId());
entity.setIntentModelId(template.getIntentModelId());
entity.setSystemPrompt(template.getSystemPrompt());
entity.setSummaryMemory(template.getSummaryMemory());
entity.setChatHistoryConf(template.getChatHistoryConf());
entity.setLangCode(template.getLangCode());
entity.setLanguage(template.getLanguage());
}
// 设置用户ID和创建者信息
UserDetail user = SecurityUser.getUser();
entity.setUserId(user.getId());
entity.setCreator(user.getId());
entity.setCreatedAt(new Date());
// 保存智能体
insert(entity);
// 设置默认插件
List<AgentPluginMapping> toInsert = new ArrayList<>();
// 播放音乐、查天气、查新闻
String[] pluginIds = new String[] { "SYSTEM_PLUGIN_MUSIC", "SYSTEM_PLUGIN_WEATHER",
"SYSTEM_PLUGIN_NEWS_NEWSNOW" };
for (String pluginId : pluginIds) {
ModelProviderDTO provider = modelProviderService.getById(pluginId);
if (provider == null) {
continue;
}
AgentPluginMapping mapping = new AgentPluginMapping();
mapping.setPluginId(pluginId);
Map<String, Object> paramInfo = new HashMap<>();
List<Map<String, Object>> fields = JsonUtils.parseObject(provider.getFields(), List.class);
if (fields != null) {
for (Map<String, Object> field : fields) {
paramInfo.put((String) field.get("key"), field.get("default"));
}
}
mapping.setParamInfo(JsonUtils.toJsonString(paramInfo));
mapping.setAgentId(entity.getId());
toInsert.add(mapping);
}
// 保存默认插件
agentPluginMappingService.saveBatch(toInsert);
return entity.getId();
}
} }
@@ -56,6 +56,9 @@ public class AgentTemplateServiceImpl extends ServiceImpl<AgentTemplateDao, Agen
wrapper.set("tts_model_id", modelId); wrapper.set("tts_model_id", modelId);
wrapper.set("tts_voice_id", null); wrapper.set("tts_voice_id", null);
break; break;
case "VLLM":
wrapper.set("vllm_model_id", modelId);
break;
case "MEMORY": case "MEMORY":
wrapper.set("mem_model_id", modelId); wrapper.set("mem_model_id", modelId);
break; break;
@@ -0,0 +1,24 @@
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 lombok.Data;
import lombok.EqualsAndHashCode;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentPluginMapping;
import java.util.List;
/**
* Agent信息返回体VO
* 这里直接extend了Agent实体类AgentEntity,后续需要规范返回字段可以copy字段出来
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AgentInfoVO extends AgentEntity
{
@Schema(description = "插件列表Id")
@TableField(typeHandler = JacksonTypeHandler.class)
private List<AgentPluginMapping> functions;
}
@@ -27,7 +27,7 @@ public class ConfigController {
private final ConfigService configService; private final ConfigService configService;
@PostMapping("server-base") @PostMapping("server-base")
@Operation(summary = "获取配置") @Operation(summary = "服务端获取配置接口")
public Result<Object> getConfig() { public Result<Object> getConfig() {
Object config = configService.getConfig(true); Object config = configService.getConfig(true);
return new Result<Object>().ok(config); return new Result<Object>().ok(config);
@@ -4,18 +4,23 @@ import java.util.ArrayList;
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.Objects;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.utils.JsonUtils; import xiaozhi.common.utils.JsonUtils;
import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentPluginMapping;
import xiaozhi.modules.agent.entity.AgentTemplateEntity; import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
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.config.service.ConfigService; import xiaozhi.modules.config.service.ConfigService;
@@ -38,6 +43,8 @@ public class ConfigServiceImpl implements ConfigService {
private final AgentTemplateService agentTemplateService; private final AgentTemplateService agentTemplateService;
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
private final TimbreService timbreService; private final TimbreService timbreService;
private final AgentPluginMappingService agentPluginMappingService;
private final AgentMcpAccessPointService agentMcpAccessPointService;
@Override @Override
public Object getConfig(Boolean isCache) { public Object getConfig(Boolean isCache) {
@@ -61,15 +68,19 @@ public class ConfigServiceImpl implements ConfigService {
// 构建模块配置 // 构建模块配置
buildModuleConfig( buildModuleConfig(
agent.getAgentName(), null,
null,
null,
null,
null, null,
null, null,
agent.getVadModelId(), agent.getVadModelId(),
agent.getAsrModelId(), agent.getAsrModelId(),
agent.getLlmModelId(), null,
agent.getTtsModelId(), null,
agent.getMemModelId(), null,
agent.getIntentModelId(), null,
null,
result, result,
isCache); isCache);
@@ -99,15 +110,30 @@ public class ConfigServiceImpl implements ConfigService {
} }
// 获取音色信息 // 获取音色信息
String voice = null; String voice = null;
String referenceAudio = null;
String referenceText = null;
TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId()); TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId());
if (timbre != null) { if (timbre != null) {
voice = timbre.getTtsVoice(); voice = timbre.getTtsVoice();
referenceAudio = timbre.getReferenceAudio();
referenceText = timbre.getReferenceText();
} }
// 构建返回数据 // 构建返回数据
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
// 获取单台设备每天最多输出字数 // 获取单台设备每天最多输出字数
String deviceMaxOutputSize = sysParamsService.getValue("device_max_output_size", true); String deviceMaxOutputSize = sysParamsService.getValue("device_max_output_size", true);
result.put("device_max_output_size", deviceMaxOutputSize); result.put("device_max_output_size", deviceMaxOutputSize);
// 获取聊天记录配置
Integer chatHistoryConf = agent.getChatHistoryConf();
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
chatHistoryConf = Constant.ChatHistoryConfEnum.IGNORE.getCode();
} else if (agent.getMemModelId() != null
&& !agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)
&& agent.getChatHistoryConf() == null) {
chatHistoryConf = Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode();
}
result.put("chat_history_conf", chatHistoryConf);
// 如果客户端已实例化模型,则不返回 // 如果客户端已实例化模型,则不返回
String alreadySelectedVadModelId = (String) selectedModule.get("VAD"); String alreadySelectedVadModelId = (String) selectedModule.get("VAD");
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) { if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {
@@ -117,27 +143,38 @@ public class ConfigServiceImpl implements ConfigService {
if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) { if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) {
agent.setAsrModelId(null); agent.setAsrModelId(null);
} }
String alreadySelectedLlmModelId = (String) selectedModule.get("LLM");
if (alreadySelectedLlmModelId != null && alreadySelectedLlmModelId.equals(agent.getLlmModelId())) { // 添加函数调用参数信息
agent.setLlmModelId(null); if (!Objects.equals(agent.getIntentModelId(), "Intent_nointent")) {
String agentId = agent.getId();
List<AgentPluginMapping> pluginMappings = agentPluginMappingService.agentPluginParamsByAgentId(agentId);
if (pluginMappings != null && !pluginMappings.isEmpty()) {
Map<String, Object> pluginParams = new HashMap<>();
for (AgentPluginMapping pluginMapping : pluginMappings) {
pluginParams.put(pluginMapping.getProviderCode(), pluginMapping.getParamInfo());
}
result.put("plugins", pluginParams);
}
} }
String alreadySelectedMemModelId = (String) selectedModule.get("Memory"); // 获取mcp接入点地址
if (alreadySelectedMemModelId != null && alreadySelectedMemModelId.equals(agent.getMemModelId())) { String mcpEndpoint = agentMcpAccessPointService.getAgentMcpAccessAddress(agent.getId());
agent.setMemModelId(null); if (StringUtils.isNotBlank(mcpEndpoint) && mcpEndpoint.startsWith("ws")) {
} mcpEndpoint = mcpEndpoint.replace("/mcp/", "/call/");
String alreadySelectedIntentModelId = (String) selectedModule.get("Intent"); result.put("mcp_endpoint", mcpEndpoint);
if (alreadySelectedIntentModelId != null && alreadySelectedIntentModelId.equals(agent.getIntentModelId())) {
agent.setIntentModelId(null);
} }
// 构建模块配置 // 构建模块配置
buildModuleConfig( buildModuleConfig(
agent.getAgentName(), agent.getAgentName(),
agent.getSystemPrompt(), agent.getSystemPrompt(),
agent.getSummaryMemory(),
voice, voice,
referenceAudio,
referenceText,
agent.getVadModelId(), agent.getVadModelId(),
agent.getAsrModelId(), agent.getAsrModelId(),
agent.getLlmModelId(), agent.getLlmModelId(),
agent.getVllmModelId(),
agent.getTtsModelId(), agent.getTtsModelId(),
agent.getMemModelId(), agent.getMemModelId(),
agent.getIntentModelId(), agent.getIntentModelId(),
@@ -150,7 +187,7 @@ public class ConfigServiceImpl implements ConfigService {
/** /**
* 构建配置信息 * 构建配置信息
* *
* @param paramsList 系统参数列表 * @param config 系统参数列表
* @return 配置信息 * @return 配置信息
*/ */
private Object buildConfig(Map<String, Object> config) { private Object buildConfig(Map<String, Object> config) {
@@ -221,23 +258,29 @@ public class ConfigServiceImpl implements ConfigService {
/** /**
* 构建模块配置 * 构建模块配置
* *
* @param prompt 提示词 * @param prompt 提示词
* @param voice 音色 * @param voice 音色
* @param vadModelId VAD模型ID * @param referenceAudio 参考音频路径
* @param asrModelId ASR模型ID * @param referenceText 参考文本
* @param llmModelId LLM模型ID * @param vadModelId VAD模型ID
* @param ttsModelId TTS模型ID * @param asrModelId ASR模型ID
* @param memModelId 记忆模型ID * @param llmModelId LLM模型ID
* @param intentModelId 意图模型ID * @param ttsModelId TTS模型ID
* @param result 结果Map * @param memModelId 记忆模型ID
* @param intentModelId 意图模型ID
* @param result 结果Map
*/ */
private void buildModuleConfig( private void buildModuleConfig(
String assistantName, String assistantName,
String prompt, String prompt,
String summaryMemory,
String voice, String voice,
String referenceAudio,
String referenceText,
String vadModelId, String vadModelId,
String asrModelId, String asrModelId,
String llmModelId, String llmModelId,
String vllmModelId,
String ttsModelId, String ttsModelId,
String memModelId, String memModelId,
String intentModelId, String intentModelId,
@@ -245,9 +288,10 @@ public class ConfigServiceImpl implements ConfigService {
boolean isCache) { boolean isCache) {
Map<String, String> selectedModule = new HashMap<>(); Map<String, String> selectedModule = new HashMap<>();
String[] modelTypes = { "VAD", "ASR", "TTS", "Memory", "Intent", "LLM" }; String[] modelTypes = { "VAD", "ASR", "TTS", "Memory", "Intent", "LLM", "VLLM" };
String[] modelIds = { vadModelId, asrModelId, ttsModelId, memModelId, intentModelId, llmModelId }; String[] modelIds = { vadModelId, asrModelId, ttsModelId, memModelId, intentModelId, llmModelId, vllmModelId };
String intentLLMModelId = null; String intentLLMModelId = null;
String memLocalShortLLMModelId = null;
for (int i = 0; i < modelIds.length; i++) { for (int i = 0; i < modelIds.length; i++) {
if (modelIds[i] == null) { if (modelIds[i] == null) {
@@ -258,29 +302,54 @@ public class ConfigServiceImpl implements ConfigService {
if (model.getConfigJson() != null) { if (model.getConfigJson() != null) {
typeConfig.put(model.getId(), model.getConfigJson()); typeConfig.put(model.getId(), model.getConfigJson());
// 如果是TTS类型,添加private_voice属性 // 如果是TTS类型,添加private_voice属性
if ("TTS".equals(modelTypes[i]) && voice != null) { if ("TTS".equals(modelTypes[i])){
((Map<String, Object>) model.getConfigJson()).put("private_voice", voice); if (voice != null) ((Map<String, Object>) model.getConfigJson()).put("private_voice", voice);
if (referenceAudio != null) ((Map<String, Object>) model.getConfigJson()).put("ref_audio", referenceAudio);
if (referenceText != null) ((Map<String, Object>) model.getConfigJson()).put("ref_text", referenceText);
} }
// 如果是Intent类型,且type=intent_llm,则给他添加附加模型 // 如果是Intent类型,且type=intent_llm,则给他添加附加模型
if ("Intent".equals(modelTypes[i])) { if ("Intent".equals(modelTypes[i])) {
Map<String, Object> map = (Map<String, Object>) model.getConfigJson(); Map<String, Object> map = (Map<String, Object>) model.getConfigJson();
if ("intent_llm".equals(map.get("type"))) { if ("intent_llm".equals(map.get("type"))) {
intentLLMModelId = (String) map.get("llm"); intentLLMModelId = (String) map.get("llm");
if (intentLLMModelId != null && intentLLMModelId.equals(llmModelId)) { if (StringUtils.isNotBlank(intentLLMModelId) && intentLLMModelId.equals(llmModelId)) {
intentLLMModelId = null; intentLLMModelId = null;
} }
} else if ("function_call".equals(map.get("type"))) { }
if (map.get("functions") != null) {
String functionStr = (String) map.get("functions"); String functionStr = (String) map.get("functions");
if (StringUtils.isNotBlank(functionStr)) { if (StringUtils.isNotBlank(functionStr)) {
String[] functions = functionStr.split("\\;"); String[] functions = functionStr.split("\\;");
map.put("functions", functions); map.put("functions", functions);
} }
} }
System.out.println("map: " + map);
}
if ("Memory".equals(modelTypes[i])) {
Map<String, Object> map = (Map<String, Object>) model.getConfigJson();
if ("mem_local_short".equals(map.get("type"))) {
memLocalShortLLMModelId = (String) map.get("llm");
if (StringUtils.isNotBlank(memLocalShortLLMModelId)
&& memLocalShortLLMModelId.equals(llmModelId)) {
memLocalShortLLMModelId = null;
}
}
} }
// 如果是LLM类型,且intentLLMModelId不为空,则添加附加模型 // 如果是LLM类型,且intentLLMModelId不为空,则添加附加模型
if ("LLM".equals(modelTypes[i]) && intentLLMModelId != null) { if ("LLM".equals(modelTypes[i])) {
ModelConfigEntity intentLLM = modelConfigService.getModelById(intentLLMModelId, isCache); if (StringUtils.isNotBlank(intentLLMModelId)) {
typeConfig.put(intentLLM.getId(), intentLLM.getConfigJson()); if (!typeConfig.containsKey(intentLLMModelId)) {
ModelConfigEntity intentLLM = modelConfigService.getModelById(intentLLMModelId, isCache);
typeConfig.put(intentLLM.getId(), intentLLM.getConfigJson());
}
}
if (StringUtils.isNotBlank(memLocalShortLLMModelId)) {
if (!typeConfig.containsKey(memLocalShortLLMModelId)) {
ModelConfigEntity memLocalShortLLM = modelConfigService
.getModelById(memLocalShortLLMModelId, isCache);
typeConfig.put(memLocalShortLLM.getId(), memLocalShortLLM.getConfigJson());
}
}
} }
} }
result.put(modelTypes[i], typeConfig); result.put(modelTypes[i], typeConfig);
@@ -293,5 +362,6 @@ public class ConfigServiceImpl implements ConfigService {
prompt = prompt.replace("{{assistant_name}}", StringUtils.isBlank(assistantName) ? "小智" : assistantName); prompt = prompt.replace("{{assistant_name}}", StringUtils.isBlank(assistantName) ? "小智" : assistantName);
} }
result.put("prompt", prompt); result.put("prompt", prompt);
result.put("summaryMemory", summaryMemory);
} }
} }
@@ -4,6 +4,7 @@ import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
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.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisKeys;
@@ -22,6 +24,8 @@ import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.Result; import xiaozhi.common.utils.Result;
import xiaozhi.modules.device.dto.DeviceRegisterDTO; import xiaozhi.modules.device.dto.DeviceRegisterDTO;
import xiaozhi.modules.device.dto.DeviceUnBindDTO; 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.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.security.user.SecurityUser; import xiaozhi.modules.security.user.SecurityUser;
@@ -80,16 +84,29 @@ public class DeviceController {
return new Result<Void>(); return new Result<Void>();
} }
@PutMapping("/enableOta/{id}/{status}") @PutMapping("/update/{id}")
@Operation(summary = "启用/关闭OTA自动升级") @Operation(summary = "更新设备信息")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result<Void> enableOtaUpgrade(@PathVariable String id, @PathVariable Integer status) { public Result<Void> updateDeviceInfo(@PathVariable String id, @Valid @RequestBody DeviceUpdateDTO deviceUpdateDTO) {
DeviceEntity entity = deviceService.selectById(id); DeviceEntity entity = deviceService.selectById(id);
if (entity == null) { if (entity == null) {
return new Result<Void>().error("设备不存在"); return new Result<Void>().error("设备不存在");
} }
entity.setAutoUpdate(status); UserDetail user = SecurityUser.getUser();
if (!entity.getUserId().equals(user.getId())) {
return new Result<Void>().error("设备不存在");
}
BeanUtils.copyProperties(deviceUpdateDTO, entity);
deviceService.updateById(entity); deviceService.updateById(entity);
return new Result<Void>(); return new Result<Void>();
} }
@PostMapping("/manual-add")
@Operation(summary = "手动添加设备")
@RequiresPermissions("sys:role:normal")
public Result<Void> manualAddDevice(@RequestBody @Valid DeviceManualAddDTO dto) {
UserDetail user = SecurityUser.getUser();
deviceService.manualAddDevice(user.getId(), dto);
return new Result<>();
}
} }
@@ -1,5 +1,7 @@
package xiaozhi.modules.device.dao; package xiaozhi.modules.device.dao;
import java.util.Date;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -8,4 +10,12 @@ import xiaozhi.modules.device.entity.DeviceEntity;
@Mapper @Mapper
public interface DeviceDao extends BaseMapper<DeviceEntity> { public interface DeviceDao extends BaseMapper<DeviceEntity> {
/**
* 获取此智能体全部设备的最后连接时间
*
* @param agentId 智能体id
* @return
*/
Date getAllLastConnectedAtByAgentId(String agentId);
} }
@@ -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地址
}
@@ -0,0 +1,29 @@
package xiaozhi.modules.device.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
/**
* 设备更新DTO
*/
@Data
public class DeviceUpdateDTO implements Serializable {
/**
* 自动更新状态
*/
@Max(1)
@Min(0)
private Integer autoUpdate;
/**
* 设备别名
*/
@Size(max = 64)
private String alias;
private static final long serialVersionUID = 1L;
}
@@ -1,5 +1,6 @@
package xiaozhi.modules.device.service; package xiaozhi.modules.device.service;
import java.util.Date;
import java.util.List; import java.util.List;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
@@ -7,6 +8,7 @@ import xiaozhi.common.service.BaseService;
import xiaozhi.modules.device.dto.DevicePageUserDTO; import xiaozhi.modules.device.dto.DevicePageUserDTO;
import xiaozhi.modules.device.dto.DeviceReportReqDTO; import xiaozhi.modules.device.dto.DeviceReportReqDTO;
import xiaozhi.modules.device.dto.DeviceReportRespDTO; import xiaozhi.modules.device.dto.DeviceReportRespDTO;
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.vo.UserShowDeviceListVO; import xiaozhi.modules.device.vo.UserShowDeviceListVO;
@@ -78,4 +80,22 @@ public interface DeviceService extends BaseService<DeviceEntity> {
* @return 激活码 * @return 激活码
*/ */
String geCodeByDeviceId(String deviceId); String geCodeByDeviceId(String deviceId);
/**
* 获取这个智能体设备理的最近的最后连接时间
* @param agentId 智能体id
* @return 返回设备最近的最后连接时间
*/
Date getLatestLastConnectionTime(String agentId);
/**
* 手动添加设备
*/
void manualAddDevice(Long userId, DeviceManualAddDTO dto);
/**
* 更新设备连接信息
*/
void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion);
} }
@@ -9,6 +9,8 @@ import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
@@ -42,6 +44,7 @@ import xiaozhi.modules.device.vo.UserShowDeviceListVO;
import xiaozhi.modules.security.user.SecurityUser; import xiaozhi.modules.security.user.SecurityUser;
import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.service.SysParamsService;
import xiaozhi.modules.sys.service.SysUserUtilService; import xiaozhi.modules.sys.service.SysUserUtilService;
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
@Slf4j @Slf4j
@Service @Service
@@ -54,6 +57,24 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
private final OtaService otaService; private final OtaService otaService;
@Async
public void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion) {
try {
DeviceEntity device = new DeviceEntity();
device.setId(deviceId);
device.setLastConnectedAt(new Date());
if (StringUtils.isNotBlank(appVersion)) {
device.setAppVersion(appVersion);
}
deviceDao.updateById(device);
if (StringUtils.isNotBlank(agentId)) {
redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), new Date());
}
} catch (Exception e) {
log.error("异步更新设备连接信息失败", e);
}
}
@Override @Override
public Boolean deviceActivation(String agentId, String activationCode) { public Boolean deviceActivation(String agentId, String activationCode) {
if (StringUtils.isBlank(activationCode)) { if (StringUtils.isBlank(activationCode)) {
@@ -118,13 +139,20 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
DeviceEntity deviceById = getDeviceByMacAddress(macAddress); DeviceEntity deviceById = getDeviceByMacAddress(macAddress);
if (deviceById == null || deviceById.getAutoUpdate() != 0) { // 设备未绑定,则返回当前上传的固件信息(不更新)以此兼容旧固件版本
String type = deviceReport.getBoard() == null ? null : deviceReport.getBoard().getType(); if (deviceById == null) {
DeviceReportRespDTO.Firmware firmware = buildFirmwareInfo(type, DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware();
deviceReport.getApplication() == null ? null : deviceReport.getApplication().getVersion()); firmware.setVersion(deviceReport.getApplication().getVersion());
firmware.setUrl(Constant.INVALID_FIRMWARE_URL);
response.setFirmware(firmware); response.setFirmware(firmware);
} else { } else {
response.setFirmware(null); // 只有在设备已绑定且autoUpdate不为0的情况下才返回固件升级信息
if (deviceById.getAutoUpdate() != 0) {
String type = deviceReport.getBoard() == null ? null : deviceReport.getBoard().getType();
DeviceReportRespDTO.Firmware firmware = buildFirmwareInfo(type,
deviceReport.getApplication() == null ? null : deviceReport.getApplication().getVersion());
response.setFirmware(firmware);
}
} }
// 添加WebSocket配置 // 添加WebSocket配置
@@ -149,13 +177,12 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
response.setWebsocket(websocket); response.setWebsocket(websocket);
if (deviceById != null) { if (deviceById != null) {
// 如果设备存在,则更新上次连接时间 // 如果设备存在,则异步更新上次连接时间和版本信息
deviceById.setLastConnectedAt(new Date()); String appVersion = deviceReport.getApplication() != null ? deviceReport.getApplication().getVersion()
if (deviceReport.getApplication() != null : null;
&& StringUtils.isNotBlank(deviceReport.getApplication().getVersion())) { // 通过Spring代理调用异步方法
deviceById.setAppVersion(deviceReport.getApplication().getVersion()); ((DeviceServiceImpl) AopContext.currentProxy()).updateDeviceConnectionInfo(deviceById.getAgentId(),
} deviceById.getId(), appVersion);
deviceDao.updateById(deviceById);
} else { } else {
// 如果设备不存在,则生成激活码 // 如果设备不存在,则生成激活码
DeviceReportRespDTO.Activation code = buildActivation(macAddress, deviceReport); DeviceReportRespDTO.Activation code = buildActivation(macAddress, deviceReport);
@@ -258,6 +285,20 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
return null; return null;
} }
@Override
public Date getLatestLastConnectionTime(String agentId) {
// 查询是否有缓存时间,有则返回
Date cachedDate = (Date) redisUtils.get(RedisKeys.getAgentDeviceLastConnectedAtById(agentId));
if (cachedDate != null) {
return cachedDate;
}
Date maxDate = deviceDao.getAllLastConnectedAtByAgentId(agentId);
if (maxDate != null) {
redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), maxDate);
}
return maxDate;
}
private String getDeviceCacheKey(String deviceId) { private String getDeviceCacheKey(String deviceId) {
String safeDeviceId = deviceId.replace(":", "_").toLowerCase(); String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
String dataKey = String.format("ota:activation:data:%s", safeDeviceId); String dataKey = String.format("ota:activation:data:%s", safeDeviceId);
@@ -338,7 +379,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
} }
firmware.setVersion(ota == null ? currentVersion : ota.getVersion()); firmware.setVersion(ota == null ? currentVersion : ota.getVersion());
firmware.setUrl(downloadUrl == null ? "" : downloadUrl); firmware.setUrl(downloadUrl == null ? Constant.INVALID_FIRMWARE_URL : downloadUrl);
return firmware; return firmware;
} }
@@ -370,4 +411,30 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
} }
return 0; return 0;
} }
}
@Override
public void manualAddDevice(Long userId, DeviceManualAddDTO dto) {
// 检查mac是否已存在
QueryWrapper<DeviceEntity> 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);
}
}
@@ -0,0 +1,75 @@
package xiaozhi.modules.model.controller;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.Result;
import xiaozhi.common.utils.ResultUtils;
import xiaozhi.common.validator.group.UpdateGroup;
import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.service.ModelProviderService;
@AllArgsConstructor
@RestController
@RequestMapping("/models/provider")
@Tag(name = "模型供应器")
public class ModelProviderController {
private final ModelProviderService modelProviderService;
@GetMapping
@Operation(summary = "获取模型供应器列表")
@RequiresPermissions("sys:role:superAdmin")
public Result<PageData<ModelProviderDTO>> getListPage(ModelProviderDTO modelProviderDTO,
@RequestParam(required = true, defaultValue = "0") String page,
@RequestParam(required = true, defaultValue = "10") String limit) {
return new Result<PageData<ModelProviderDTO>>()
.ok(modelProviderService.getListPage(modelProviderDTO, page, limit));
}
@PostMapping
@Operation(summary = "新增模型供应器")
@RequiresPermissions("sys:role:superAdmin")
public Result<ModelProviderDTO> add(@RequestBody @Validated ModelProviderDTO modelProviderDTO) {
ModelProviderDTO resp = modelProviderService.add(modelProviderDTO);
return new Result<ModelProviderDTO>().ok(resp);
}
@PutMapping
@Operation(summary = "修改模型供应器")
@RequiresPermissions("sys:role:superAdmin")
public Result<ModelProviderDTO> edit(@RequestBody @Validated(UpdateGroup.class) ModelProviderDTO modelProviderDTO) {
ModelProviderDTO resp = modelProviderService.edit(modelProviderDTO);
return new Result<ModelProviderDTO>().ok(resp);
}
@PostMapping("/delete")
@Operation(summary = "删除模型供应器")
@RequiresPermissions("sys:role:superAdmin")
@Parameter(name = "ids", description = "ID数组", required = true)
public Result<Void> delete(@RequestBody List<String> ids) {
modelProviderService.delete(ids);
return new Result<>();
}
@GetMapping("/plugin/names")
@Tag(name = "获取插件名称列表")
public Result<List<ModelProviderDTO>> getPluginNameList() {
return ResultUtils.success(modelProviderService.getPluginList());
}
}
@@ -8,29 +8,37 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import xiaozhi.common.validator.group.UpdateGroup;
@Data @Data
@Schema(description = "模型供应器/商") @Schema(description = "模型供应器/商")
public class ModelProviderDTO implements Serializable { public class ModelProviderDTO implements Serializable {
// @Schema(description = "主键")
// @Schema(description = "主键") @NotBlank(message = "id不能为空", groups = UpdateGroup.class)
// private Long id; private String id;
@Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)") @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
@NotBlank(message = "modelType不能为空")
private String modelType; private String modelType;
@Schema(description = "供应器类型") @Schema(description = "供应器类型")
@NotBlank(message = "providerCode不能为空")
private String providerCode; private String providerCode;
@Schema(description = "供应器名称") @Schema(description = "供应器名称")
@NotBlank(message = "name不能为空")
private String name; private String name;
@Schema(description = "供应器字段列表(JSON格式)") @Schema(description = "供应器字段列表(JSON格式)")
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
@NotBlank(message = "fields(JSON格式)不能为空")
private String fields; private String fields;
@Schema(description = "排序") @Schema(description = "排序")
@NotNull(message = "sort不能为空")
private Integer sort; private Integer sort;
@Schema(description = "更新者") @Schema(description = "更新者")
@@ -3,10 +3,8 @@ package xiaozhi.modules.model.entity;
import java.util.Date; import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
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;
@@ -30,7 +28,6 @@ public class ModelProviderEntity {
private String name; private String name;
@Schema(description = "供应器字段列表(JSON格式)") @Schema(description = "供应器字段列表(JSON格式)")
@TableField(typeHandler = JacksonTypeHandler.class)
private String fields; private String fields;
@Schema(description = "排序") @Schema(description = "排序")
@@ -1,21 +1,30 @@
package xiaozhi.modules.model.service; package xiaozhi.modules.model.service;
import java.util.Collection;
import java.util.List; import java.util.List;
import xiaozhi.common.page.PageData;
import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.entity.ModelProviderEntity;
public interface ModelProviderService { public interface ModelProviderService {
// List<String> getModelNames(String modelType, String modelName); List<ModelProviderDTO> getPluginList();
ModelProviderDTO getById(String id);
List<ModelProviderDTO> getPluginListByIds(Collection<String> ids);
List<ModelProviderDTO> getListByModelType(String modelType); List<ModelProviderDTO> getListByModelType(String modelType);
ModelProviderDTO add(ModelProviderEntity modelProviderEntity); ModelProviderDTO add(ModelProviderDTO modelProviderDTO);
ModelProviderDTO edit(ModelProviderEntity modelProviderEntity); ModelProviderDTO edit(ModelProviderDTO modelProviderDTO);
void delete(); void delete(String id);
void delete(List<String> id);
PageData<ModelProviderDTO> getListPage(ModelProviderDTO modelProviderDTO, String page, String limit);
List<ModelProviderDTO> getList(String modelType, String provideCode); List<ModelProviderDTO> getList(String modelType, String provideCode);
} }
@@ -1,19 +1,31 @@
package xiaozhi.modules.model.service.impl; package xiaozhi.modules.model.service.impl;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
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 cn.hutool.json.JSONArray;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.modules.model.dao.ModelProviderDao; import xiaozhi.modules.model.dao.ModelProviderDao;
import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.entity.ModelProviderEntity; import xiaozhi.modules.model.entity.ModelProviderEntity;
import xiaozhi.modules.model.service.ModelProviderService; import xiaozhi.modules.model.service.ModelProviderService;
import xiaozhi.modules.security.user.SecurityUser;
@Service @Service
@AllArgsConstructor @AllArgsConstructor
@@ -22,6 +34,29 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
private final ModelProviderDao modelProviderDao; private final ModelProviderDao modelProviderDao;
@Override
public List<ModelProviderDTO> getPluginList() {
LambdaQueryWrapper<ModelProviderEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ModelProviderEntity::getModelType, "Plugin");
List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper);
return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class);
}
@Override
public ModelProviderDTO getById(String id) {
ModelProviderEntity entity = modelProviderDao.selectById(id);
return ConvertUtils.sourceToTarget(entity, ModelProviderDTO.class);
}
@Override
public List<ModelProviderDTO> getPluginListByIds(Collection<String> ids) {
LambdaQueryWrapper<ModelProviderEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(ModelProviderEntity::getId, ids);
queryWrapper.eq(ModelProviderEntity::getModelType, "Plugin");
List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper);
return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class);
}
@Override @Override
public List<ModelProviderDTO> getListByModelType(String modelType) { public List<ModelProviderDTO> getListByModelType(String modelType) {
@@ -32,18 +67,78 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
} }
@Override @Override
public ModelProviderDTO add(ModelProviderEntity modelProviderEntity) { public PageData<ModelProviderDTO> getListPage(ModelProviderDTO modelProviderDTO, String page, String limit) {
return null;
Map<String, Object> params = new HashMap<String, Object>();
params.put(Constant.PAGE, page);
params.put(Constant.LIMIT, limit);
params.put(Constant.ORDER_FIELD, List.of("model_type", "sort"));
params.put(Constant.ORDER, "asc");
IPage<ModelProviderEntity> pageParam = getPage(params, null, true);
QueryWrapper<ModelProviderEntity> wrapper = new QueryWrapper<ModelProviderEntity>();
if (StringUtils.isNotBlank(modelProviderDTO.getModelType())) {
wrapper.eq("model_type", modelProviderDTO.getModelType());
}
if (StringUtils.isNotBlank(modelProviderDTO.getName())) {
wrapper.and(w -> w.like("name", modelProviderDTO.getName())
.or()
.like("provider_code", modelProviderDTO.getName()));
}
return getPageData(modelProviderDao.selectPage(pageParam, wrapper), ModelProviderDTO.class);
}
public static void main(String[] args) {
String jsonString = "\"[]\"";
JSONArray jsonArray = new JSONArray(jsonString);
System.out.println("字符串转 JSONArray: " + jsonArray.toString());
} }
@Override @Override
public ModelProviderDTO edit(ModelProviderEntity modelProviderEntity) { public ModelProviderDTO add(ModelProviderDTO modelProviderDTO) {
return null; UserDetail user = SecurityUser.getUser();
modelProviderDTO.setCreator(user.getId());
modelProviderDTO.setUpdater(user.getId());
modelProviderDTO.setCreateDate(new Date());
modelProviderDTO.setUpdateDate(new Date());
// 去除Fields左右的双引号
modelProviderDTO.setFields(modelProviderDTO.getFields());
ModelProviderEntity entity = ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class);
if (modelProviderDao.insert(entity) == 0) {
throw new RenException("新增数据失败");
}
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
} }
@Override @Override
public void delete() { public ModelProviderDTO edit(ModelProviderDTO modelProviderDTO) {
UserDetail user = SecurityUser.getUser();
modelProviderDTO.setUpdater(user.getId());
modelProviderDTO.setUpdateDate(new Date());
if (modelProviderDao
.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
throw new RenException("修改数据失败");
}
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
}
@Override
public void delete(String id) {
if (modelProviderDao.deleteById(id) == 0) {
throw new RenException("删除数据失败");
}
}
@Override
public void delete(List<String> ids) {
if (modelProviderDao.deleteBatchIds(ids) == 0) {
throw new RenException("删除数据失败");
}
} }
@Override @Override

Some files were not shown because too many files have changed in this diff Show More