Compare commits

...
677 Commits
Author SHA1 Message Date
欣南科技andGitHub 2d5ef3011a Merge pull request #1028 from xinnan-tech/manager-api-model-delete
update:单Server部署时,docker-compose开放8002端口
2025-04-27 22:50:39 +08:00
hrz 63dab6d224 update:单Server部署时,docker-compose开放8002端口 2025-04-27 22:49:14 +08:00
CGDandGitHub 38dd76dfb3 Merge pull request #1026 from xinnan-tech/emote_switching
Emote switching
2025-04-27 22:20:04 +08:00
fe642de526 update:删除模型前检查是否有引用 (#1027)
* fix:DoubaoASR去掉转wav环节

* update:删除模型前检查是否有引用

* update:去除print

---------

Co-authored-by: MakerZorky <1053714527zhq@gmail.com>
2025-04-27 22:08:30 +08:00
hrz f4b7eefc38 update:去除print 2025-04-27 22:07:36 +08:00
hrz 5bbbad02b4 update:删除模型前检查是否有引用 2025-04-27 21:02:58 +08:00
Sakura-RanChen bd85dd5faf 增加“表情切换”英文适配,优化输出文本切割逻辑 2025-04-27 17:57:58 +08:00
hrzandGitHub d91ba3d219 Merge pull request #1020 from MakerZorky/main
fix:DoubaoASR去掉pcm转wav环节,优化响应速度
2025-04-27 16:49:38 +08:00
MakerZorky 7992c40815 fix:DoubaoASR去掉转wav环节 2025-04-27 15:11:50 +08:00
hrzandGitHub 574b01db97 Merge pull request #1011 from xinnan-tech/simple_ota_server
为最简化安装环境提供OTA基础信息接口
2025-04-27 13:55:51 +08:00
hrz 2d6049b7bd update:优化说明文档 2025-04-27 13:55:04 +08:00
hrz 96d0f2e6f9 update:更新教程 2025-04-27 13:34:17 +08:00
hrz b46bd4d0dd update:兼容全模块部署,不启动自带的OTA 服务 2025-04-27 13:07:14 +08:00
hrzandGitHub 411fc46189 Merge pull request #1006 from weinaike/main
fix:修复tts多次尝试逻辑
2025-04-27 11:16:10 +08:00
Sakura-RanChen 48427e330f 根据文本情绪权重进行表情切换处理,积极情绪优先 2025-04-27 11:15:58 +08:00
王华侨 5ddc2451c1 更新部署信息 2025-04-26 22:08:52 +08:00
王华侨 e357cad2bb 为最简化安装环境提供OTA基础信息接口 2025-04-26 21:56:41 +08:00
hrzandGitHub 80be8b2eaa Hot fix (#1007)
* update:优化代码

* fix:补回server.websocket配置

* update:修正版本号
2025-04-26 09:12:23 +08:00
wnk 9a5aaea8ca fix:
tts未及时响应,多次尝试逻辑修复。(删除空文件,达成While的条件)
2025-04-26 07:04:07 +08:00
hrzandGitHub 7d585615a9 update:细化版本对比 (#1004) 2025-04-25 23:58:43 +08:00
hrzandGitHub 3f3375dcac Auto ota update (#1001)
* update:修改ai_ota表编码

* update:更新教程

* add:增加OTA地址的配置

* update:将设备默认设置成自动升级

* update:设备管理可以设置某个设备是否自动升级

* update:判断客户端是否启动了自动升级

* update:校验OTA地址
2025-04-25 18:23:43 +08:00
hrzandGitHub f555eae653 Merge pull request #999 from xinnan-tech/auto-ota-update
Auto ota update
2025-04-25 17:56:47 +08:00
CGDandGitHub e805a6e037 Merge pull request #981 from xinnan-tech/aboutHandle-modify
打断时增加tts和音频任务清理
2025-04-25 09:58:15 +08:00
欣南科技andGitHub 3ed82ed7bb Merge pull request #982 from xinnan-tech/auto-ota-update
update:设置同类型OTA固件,只保留一个
2025-04-25 09:51:00 +08:00
hrz d638e512c9 update:设置同类型OTA固件,只保留一个 2025-04-25 09:48:25 +08:00
Sakura-RanChen 204b966935 打断时增加tts和音频任务清理 2025-04-25 09:34:22 +08:00
欣南科技andGitHub 8318ba5e55 Merge pull request #978 from xinnan-tech/auto-ota-update
update:OTA自动升级
2025-04-24 18:37:37 +08:00
hrz d39c595828 update:优化获取新固件方法 2025-04-24 18:36:04 +08:00
hrz 04154162c6 update:OTA自动升级 2025-04-24 18:27:38 +08:00
hrz 47f8e0f04c update:检验LLM密钥 2025-04-24 15:32:28 +08:00
hrz 7e4ada5f4b update:检验LLM密钥 2025-04-24 15:22:28 +08:00
hrz b0b4f87848 update:天气插件优化 2025-04-24 14:58:35 +08:00
欣南科技andGitHub d9085b82bf Merge pull request #967 from xinnan-tech/model-config-ui
update:优化模型配置左侧栏显示
2025-04-24 12:17:01 +08:00
hrz 8e5770ff59 修复tts名字过长 2025-04-24 12:13:42 +08:00
Sakura-RanChenandGitHub beb420c69d Merge pull request #968 from xinnan-tech/web-vue-ota-modify
Web vue ota modify
2025-04-24 11:54:08 +08:00
Junsen HuangandGitHub 1d8b898924 fix: index.js
修复router没有设置base
2025-04-24 11:46:29 +08:00
Junsen HuangandGitHub b5b4c5ced7 fix: vue.config.js
修复publicPath空串问题、默认是"/"
2025-04-24 11:45:24 +08:00
CGD b4e9d77895 update:修改了表格样式,增加了滑动条等,使其与其他页面基本对齐一致 2025-04-24 11:21:09 +08:00
hrzandGitHub 21a08b3c7a Merge pull request #933 from laride/feat/volces-ai-gateway
添加火山引擎边缘大模型网关 LLM/TTS
2025-04-24 11:19:28 +08:00
hrzandGitHub 6977d6bf73 Merge branch 'model-config-ui' into feat/volces-ai-gateway 2025-04-24 11:19:19 +08:00
hrzandGitHub 39f81dd0c0 Merge pull request #944 from laride/feat/add-public-path-env
添加前端部署路径环境变量
2025-04-24 11:16:20 +08:00
hrz b3191f5e0b update:优化tts异常捕获 2025-04-24 10:58:42 +08:00
hrzandGitHub 78f2f21142 Merge pull request #934 from taiping520/main
fix: 修复tts失败不重试问题
2025-04-24 10:43:32 +08:00
hrz 497fc1d1ae update:优化模型配置左侧栏显示 2025-04-24 10:41:14 +08:00
taiping520andGitHub d83e1c6fb2 Merge branch 'xinnan-tech:main' into main 2025-04-24 09:50:24 +08:00
CGD c5218d485a update:添加"加载中"样式,修改分页器样式 2025-04-24 09:33:46 +08:00
Sakura-RanChenandGitHub 19cbd3b8da Merge pull request #965 from xinnan-tech/py_test
Py test
2025-04-24 09:20:16 +08:00
CGD c0c5630bfb update:修改"获取天气"的逻辑顺序 2025-04-24 09:12:56 +08:00
CGD 969b5fcfbb update:"默认提示词"取消用户位置 2025-04-24 09:11:58 +08:00
欣南科技andGitHub 0739fa2195 Merge pull request #964 from xinnan-tech/update-fix
fix:取消docker版上传ota固件时默认的文件大小限制
2025-04-23 23:24:21 +08:00
hrz 3c62adab3c fix:取消docker版上传ota固件时默认的文件大小限制 2025-04-23 23:23:25 +08:00
欣南科技andGitHub edc601c35e Merge pull request #962 from xinnan-tech/update-fix
update:让alibl、等非原生llm使用functional时不卡顿
2025-04-23 21:30:40 +08:00
hrz 951370eee7 update:让alibl、等非原生llm使用function时不卡顿 2025-04-23 21:29:42 +08:00
hrzandGitHub fa81f4a150 Merge pull request #959 from TinsFox/main
update: 修改智控台登录地址为8002
2025-04-23 21:10:21 +08:00
TinsFox e6a90c40fb update: 修改智控台登录地址为8002 2025-04-23 20:25:04 +08:00
欣南科技andGitHub 2e0a1c00fa Merge pull request #955 from xinnan-tech/update-readme
update:增加HomeAssistant教程
2025-04-23 18:18:45 +08:00
hrz 109754fac9 update:增加HomeAssistant教程 2025-04-23 18:17:56 +08:00
taiping520andGitHub c3fbff3d7a Merge branch 'xinnan-tech:main' into main 2025-04-23 16:44:27 +08:00
欣南科技andGitHub 6dd967fe24 udpate:更新编译文档 (#948) 2025-04-23 11:49:24 +08:00
hrz f0bfdba944 udpate:更新编译文档 2025-04-23 11:48:20 +08:00
Liu Ruichao 7f76b5f4ee add PUBLIC_PATH env 2025-04-23 10:49:50 +08:00
taiping520andGitHub 35f4d8f15a Merge branch 'xinnan-tech:main' into main 2025-04-23 09:45:05 +08:00
hrzandGitHub 783f17e9e8 update:补充修改 OTA 固件信息接口 (#940)
* update:修复server.websocket配置占用bug

* update:补充修改 OTA 固件信息接口
2025-04-23 00:24:19 +08:00
hrzandGitHub 9e305c2840 update:修复server.websocket配置占用bug (#939) 2025-04-23 00:04:21 +08:00
78fa5d3110 add:ota管理与固件上传与下载 (#931)
* 新增ota管理与固件上传于下载

* feat: 支持通过OTA更新websocket地址

* update:合并最新代码

* update:优化OTA增删改查

* update:一个临时下载链接最多只能下载3次,防止盗链和流量攻击

* update:OTA固件增删改查

---------

Co-authored-by: kevin1sMe <linjiang1205@gmail.com>
Co-authored-by: hrz <1710360675@qq.com>
2025-04-22 23:36:23 +08:00
Liu Ruichao d57482d966 feat: add Volces AI Gateway LLM/TTS 2025-04-22 17:04:29 +08:00
sczx-win10 3d7c2aff4b Merge remote-tracking branch 'origin/main' 2025-04-22 16:58:29 +08:00
sczx-win10 3757c5dde7 fix: 修复tts失败不重试问题 2025-04-22 16:44:45 +08:00
Sakura-RanChenandGitHub 13c0a966ec Merge pull request #928 from xinnan-tech/web-vue-device-fixed
update:设备管理页面样式调整
2025-04-22 11:34:52 +08:00
CGD d987619946 update:设备管理页面样式调整 2025-04-22 11:33:17 +08:00
Sakura-RanChenandGitHub 8fb9498fe6 Merge pull request #927 from xinnan-tech/web-vue-height-fixed
Web vue height fixed
2025-04-22 10:18:19 +08:00
CGD b8f64a6693 update:更换设备管理页面的表格“选择”样式 2025-04-22 10:05:06 +08:00
CGD e31bb047bf update:将参数管理页面和用户管理页面的高度调整为固定 2025-04-22 09:27:14 +08:00
Sakura-RanChenandGitHub 6897b813a6 Merge pull request #919 from xinnan-tech/web-vue-loading
Web vue loading
2025-04-22 09:02:24 +08:00
hrzandGitHub 83f86406b5 update:更新固件下载源码路径 (#923) 2025-04-21 21:08:15 +08:00
dde237ff0d update:增加dashscope依赖 (#920)
* update:增加腾讯TTS、腾讯ASR供应器

* update:增加dashscope依赖

---------

Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com>
2025-04-21 17:55:27 +08:00
hrzandGitHub 6a4887e252 Merge pull request #916 from laride/fix/empty-chunk
处理 OpenAI 格式响应中 `choices` 为空的 chunk
2025-04-21 17:41:04 +08:00
d945b94910 update:增加腾讯TTS、腾讯ASR供应器 (#918)
Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com>
2025-04-21 17:35:10 +08:00
CGD bc7dcf791d update:获取"设备管理"数据时添加loading状态,修改“添加智能体”的错误字眼 2025-04-21 16:24:53 +08:00
CGD 66c71fe3b7 update:音色管理页面加载音色配置时添加loading状态 2025-04-21 15:23:27 +08:00
larideandGitHub 16a7aeec3b fix: handle empty choices in some chunk of OpenAI streaming response 2025-04-21 14:24:47 +08:00
Sakura-RanChenandGitHub 94605345e5 Merge pull request #914 from xinnan-tech/web-table-loading
Web table loading
2025-04-21 11:40:47 +08:00
CGD ec9fa32c81 update:添加多个组件"保存"时的loading状态 2025-04-21 11:36:10 +08:00
CGDandGitHub 264aa15281 Merge pull request #913 from xinnan-tech/web-role-exit
增加“角色配置”页面返回按钮,智能体管理骨架屏加载显示
2025-04-21 11:26:39 +08:00
Ran_Chen 180a6ff824 增加“角色配置”页面返回按钮,智能体管理骨架屏加载显示 2025-04-21 10:24:04 +08:00
CGD 5904ee1571 update:添加“模型配置”、“用户管理”、“参数管理”页面“获取列表”时的加载状态 2025-04-21 10:02:50 +08:00
hrzandGitHub bc99341f85 Merge pull request #909 from xinnan-tech/ota-activate
update:输入验证码后设备端快速刷新结果
2025-04-20 21:15:00 +08:00
hrzandGitHub 8b9174b67e update:绑定设备时,板子类型优先获取品牌商版名称 (#903)
* update:Server端兼容老设备缺失"client-id"请求头

* update:优化iot日志

* update:绑定设备时,板子类型优先获取品牌商版名称

* update:更新版本号
2025-04-20 01:40:21 +08:00
hrzandGitHub 6c04a6cf69 update:Server端兼容老设备缺失"client-id"请求头 (#902)
* update:Server端兼容老设备缺失"client-id"请求头

* update:优化iot日志

* update:绑定设备时,板子类型优先获取品牌商版名称
2025-04-20 01:28:01 +08:00
hrzandGitHub 14a63c4b97 update:补齐ddos攻击提示音 (#896) 2025-04-19 13:24:03 +08:00
hrzandGitHub 098d13a34b update:更新0.3.7 (#892) 2025-04-19 00:11:12 +08:00
488f247744 update:增加单台设备每天最多聊天字数,防止被ddos
* 添加清空redis所有库的接口
--AdminController.java 添加了清除所有的接口
--RedisUtils.java 添加了清除redis所有key的方法,redisTemplate提供的清空方法已经被标记为弃用了,所有选择用执行lua脚本方式

* fix:修复使用本地配置时忘记附带提示词

* fix:修复意图识别插件名称格式bug

* update:版本升级后强制刷新redis

* update:增加单台设备每天最多聊天字数,防止被ddos

---------

Co-authored-by: 剑雨 <2375294554@qq.com>
2025-04-18 23:39:56 +08:00
hrzandGitHub c74dbf03cd fix:修复意图识别插件名称格式bug (#885)
* fix:修复使用本地配置时忘记附带提示词

* fix:修复意图识别插件名称格式bug
2025-04-18 16:39:18 +08:00
hrzandGitHub 1f836c3235 fix:修复使用本地配置时忘记附带提示词 (#880) 2025-04-18 11:38:31 +08:00
hrzandGitHub bd2e2e77d5 update:更新版本号 (#874) 2025-04-18 00:51:09 +08:00
6282ef14e8 fix:修复第一句话是默认配置的bug (#873)
* update: 增加对httpClient的统一管理

* update: 增加retry机制

* update: 增加retry机制

* fix:websocket连接后不说话的备用关闭方法

* fix:修复第一句话是默认配置的bug

---------

Co-authored-by: haotian <haotian@codemao.cn>
Co-authored-by: GoodyHao <865700600@qq.com>
2025-04-18 00:37:40 +08:00
558f23688f fix:websocket连接后不说话的备用关闭方法 (#872)
* update: 增加对httpClient的统一管理

* update: 增加retry机制

* update: 增加retry机制

* fix:websocket连接后不说话的备用关闭方法

---------

Co-authored-by: haotian <haotian@codemao.cn>
Co-authored-by: GoodyHao <865700600@qq.com>
2025-04-17 23:57:04 +08:00
CGDandGitHub 541f2de599 Merge pull request #856 from xinnan-tech/web-page-check
解决切换页面样式变化的问题
2025-04-17 13:26:26 +08:00
hrzandGitHub 68116254cd dify、coze对话模式支持functioncall
Function call v2
2025-04-17 11:54:04 +08:00
hrzandGitHub 77ff4599ea Merge branch 'main' into function-call-v2 2025-04-17 11:53:25 +08:00
玄凤科技 652f5a3247 Merge branch 'function-call-v2' of https://github.com/xinnan-tech/xiaozhi-esp32-server into function-call-v2 2025-04-17 10:00:23 +08:00
玄凤科技 9b6e57b143 修改提示词,增强tool调用约束 2025-04-17 10:00:13 +08:00
Ran_Chen bdc19256bf 解决切换页面样式变化的问题 2025-04-17 09:34:42 +08:00
hrzandGitHub de5eaf4467 更新版本号0.3.5 (#852)
* update:测试页面增加OTA地址

* update:兼容旧设备,无Client-Id的情况

* update:修复智控台下发配置布尔类型转换出错bug

* update:修复智控台下发配置字符类型转换出错bug

* udpate:增加iot消息properties和methods可能为空的情况

* update:发布0.3.5版本
2025-04-16 23:47:37 +08:00
hrzandGitHub ca884833d1 udpate:增加iot消息properties和methods可能为空的情况 (#851)
* update:测试页面增加OTA地址

* update:兼容旧设备,无Client-Id的情况

* update:修复智控台下发配置布尔类型转换出错bug

* update:修复智控台下发配置字符类型转换出错bug

* udpate:增加iot消息properties和methods可能为空的情况
2025-04-16 23:34:23 +08:00
hrz 9fc164a69d udpate:增加iot消息properties和methods可能为空的情况 2025-04-16 23:30:46 +08:00
hrzandGitHub 0da2da83a5 update:修复智控台下发配置布尔类型转换出错bug (#850)
* update:测试页面增加OTA地址

* update:兼容旧设备,无Client-Id的情况

* update:修复智控台下发配置布尔类型转换出错bug

* update:修复智控台下发配置字符类型转换出错bug
2025-04-16 22:55:13 +08:00
hrzandGitHub bfdfa44edd Merge pull request #844 from xinnan-tech/web-page-modify
“角色配置”行内样式调整
2025-04-16 18:43:07 +08:00
hrzandGitHub ed1cfe5eb6 update:兼容旧设备,无Client-Id的情况 (#847)
* update:测试页面增加OTA地址

* update:兼容旧设备,无Client-Id的情况
2025-04-16 18:03:30 +08:00
Ran_Chen b696f3f5db “角色配置”行内样式调整 2025-04-16 17:18:11 +08:00
hrzandGitHub 7a0cf5ef9a update:测试页面增加OTA地址 (#842) 2025-04-16 16:44:44 +08:00
CGDandGitHub d1b7cb6a36 Merge pull request #837 from xinnan-tech/web-modify
“角色配置”页面间距调整
2025-04-16 14:47:58 +08:00
Ran_Chen b21d699472 “角色配置”页面间距调整 2025-04-16 14:25:46 +08:00
CGDandGitHub 029fadd778 Merge pull request #836 from xinnan-tech/web-modify
Web modify
2025-04-16 14:04:19 +08:00
Sakura-RanChenandGitHub 215d50235b Merge pull request #835 from xinnan-tech/web-vue-agent-modify
修改了部分components的样式
2025-04-16 14:03:24 +08:00
CGD 000e676397 修改了部分components的样式 2025-04-16 12:55:07 +08:00
玄凤科技 208c045d3d 触发function call后,清空回复队列 2025-04-16 11:48:40 +08:00
Ran_Chen ee20f59a8e 增加“回车”事件 2025-04-16 11:42:05 +08:00
Ran_Chen 925cf3dabb 新增智能体按钮绑定问题 2025-04-16 11:34:30 +08:00
CGDandGitHub 0049d0e926 Merge pull request #831 from xinnan-tech/web-pages-modify
Web pages modify
2025-04-16 11:23:42 +08:00
Ran_Chen 56959808a9 解决新增设备按钮绑定问题,优化“角色配置”页面 2025-04-16 11:17:02 +08:00
Ran_Chen 8667a9680e 增加时间戳转换,修改各页面的布局 2025-04-16 10:15:58 +08:00
玄凤科技 8c7d129089 同步coze llm 2025-04-16 08:58:55 +08:00
玄凤科技 a3a9b98a1d 增加系统提示词,支持dify使用function call 2025-04-16 08:56:23 +08:00
欣南科技andGitHub f2f66579cb 合并网友提交优化
合并网友提交优化
2025-04-15 22:48:03 +08:00
hrz 1354cf2a87 合并网友提交优化 2025-04-15 22:46:54 +08:00
hrzandGitHub 15f5592429 Merge pull request #814 from funshine/bug_fix_funshine
Bug fix test_page.html, 以及TTS_CosyVoiceSiliconflow
2025-04-15 22:02:19 +08:00
hrzandGitHub dc69094f00 Merge pull request #817 from GOODDAYDAY/main
update: 增加prompt个性化配置 & 增加vad,asr个性初始化 & 优化日志若干
2025-04-15 22:01:51 +08:00
hrzandGitHub 8ad3783a36 Merge pull request #812 from bohell/main
Update Deployment_all.md
2025-04-15 21:52:09 +08:00
hrzandGitHub 15d5dd8e92 Merge pull request #823 from xinnan-tech/web-vue-select-modify
修改了各个页面的“选择”表头样式
2025-04-15 21:50:46 +08:00
CGD b07edb977b 修改了各个页面的“选择”表头样式 2025-04-15 20:33:09 +08:00
hrzandGitHub 1509fc2039 update:播报音频文件
* update:websocket播报6位验证码

* update:播报音频文件
2025-04-15 18:55:23 +08:00
欣南科技andGitHub 2877bb8faa Web device modify (#818)
* “设备管理”分页响应调整

* 样式调整
2025-04-15 18:30:41 +08:00
hrzandGitHub a83410cb49 update:websocket播报6位验证码 (#821) 2025-04-15 18:30:12 +08:00
Ran_Chen 4ff9f2235b 样式调整 2025-04-15 18:02:04 +08:00
Ran_Chen 1bc611f097 “设备管理”分页响应调整 2025-04-15 17:59:29 +08:00
haotian 4e0a3941ff update: 增加prompt个性化配置 & 增加vad,asr个性初始化 & 优化日志若干 2025-04-15 17:21:44 +08:00
CGDandGitHub 4e885dac3e Merge pull request #815 from xinnan-tech/web-page-modify
Web page modify
2025-04-15 16:27:51 +08:00
Ran_Chen eafd131a22 “模型配置”响应适配,修改下拉图标样式,调整bottom高度差 2025-04-15 16:25:56 +08:00
Guijie HongandGitHub 289ce3716b Merge branch 'xinnan-tech:main' into bug_fix_funshine 2025-04-15 16:11:18 +08:00
Sakura-RanChenandGitHub 2242070bdc Merge pull request #813 from xinnan-tech/fix/manager-web-cdn
Fix/manager web cdn
2025-04-15 16:06:02 +08:00
funshine ee83cea11c 修正CosyVoiceSiliconflow音色 2025-04-15 15:51:36 +08:00
ZhilongandGitHub 6e571a43d8 Update Deployment_all.md
增加管理api的端口号
2025-04-15 15:44:21 +08:00
funshine 466a0245a9 add default value for siliconflow tts speed 2025-04-15 15:28:02 +08:00
funshine d9ce5e4ce7 fix audio test page websocket disconn 2025-04-15 15:22:47 +08:00
Sakura-RanChenandGitHub 6b0dd291c2 Merge pull request #811 from xinnan-tech/web-vue-tts
修复了“音色管理”样式
2025-04-15 14:09:50 +08:00
hrzandGitHub 4ff77d0d7e update:更新烧录时ota地址 (#810)
* update:修复默认模型没有修改到模版配置的bug

* update:智控台可设置允许用户自由注册

* update:更新烧录时ota地址
2025-04-15 13:18:23 +08:00
hrzandGitHub e00da5d8d2 update:智控台可设置允许用户自由注册 (#809)
* update:修复默认模型没有修改到模版配置的bug

* update:智控台可设置允许用户自由注册
2025-04-15 13:03:29 +08:00
CGD 1a525a59ab 修复了“音色管理”样式 2025-04-15 12:20:33 +08:00
Ran_Chen d5581c4236 “用户管理”页面,响应式调整 2025-04-15 11:27:35 +08:00
Sakura-RanChenandGitHub 4bf950574c Merge pull request #803 from xinnan-tech/web-vue-modify
修改了分页器样式
2025-04-15 10:33:34 +08:00
CGDandGitHub 0b9e434c4b Merge pull request #802 from xinnan-tech/web-pages-modify
参数页面响应调整
2025-04-15 10:33:26 +08:00
CGD c4b1466643 修改了分页器样式 2025-04-15 10:30:52 +08:00
Ran_Chen 488842029b 参数页面响应调整 2025-04-15 10:28:37 +08:00
Junsen HuangandGitHub 1b0ce59371 Merge pull request #800 from huangjunsen0406/fix/manager-web-cdn
Fix/manager web cdn
2025-04-15 10:00:36 +08:00
huangjunsen0406 97bea89204 Merge branch 'main' into fix/manager-web-cdn 2025-04-15 09:56:22 +08:00
huangjunsen0406 0892dfc601 Merge remote-tracking branch 'upstream/main' 2025-04-15 09:54:05 +08:00
huangjunsen0406 20d66ff401 fix(service_worker): 根据标志位在app.vue开启缓存检测工具 2025-04-15 09:53:06 +08:00
Sakura-RanChenandGitHub e2fda532d1 Merge pull request #796 from xinnan-tech/web-vue-modify-device
Web vue modify device
2025-04-15 09:06:01 +08:00
Junsen HuangandGitHub 68035af7b3 Merge pull request #794 from huangjunsen0406/fix/manager-web-cdn
Fix/manager web cdn
2025-04-15 09:01:35 +08:00
CGDandGitHub ee25555f0e Merge branch 'main' into web-vue-modify-device 2025-04-15 08:31:28 +08:00
CGD e528510f5c 修改了参数管理的页面高度 2025-04-15 08:24:06 +08:00
huangjunsen0406 afa0527b4c Merge remote-tracking branch 'upstream/main' 2025-04-15 04:07:27 +08:00
huangjunsen0406 dd7d23498f build(cdn): 📦 更换cdn为unpkg、新增service_worker缓存资源文件
closed #772
2025-04-15 04:03:28 +08:00
hrzandGitHub eaaea2b24c update:智控台添加版本号 (#793) 2025-04-15 01:46:36 +08:00
58d0aefae1 fix:修复manager下发配置的bug (#792)
* update: 优化系统prompt

* Update Deployment_all.md

更新部分的安装配置文档,后续需要修改 docker-compose.yaml

* Update docker-compose_all.yml

- 直接提供全量的部署配置,包括 mysql 与 Redis 实现真正意义上的一键配置。
- 将 MySQL 的数据内容本地挂载存储,需要备份迁移的时候,直接打包复制文件夹即可。MySQL 容器删除的时候也不会出现数据丢失的问题。

* Update Deployment_all.md

- 使用 docker compose 部署的时候,不应该每次更新的时候都把原来的 docker-compose 文件删除,再重新配置。
- 如果没有自己部署数据库需求的话,目前可以直接开箱即用,同步更新了 server 中的 Redis 与 MySQL 的镜像服务配置。

* Update Deployment_all.md

- MySQL 与 Redis 的安装是可选的

* 现在新版本的 docker 直接集成了 compose 命令,不需要手动安装与执行 docker-compose 命令了,提高兼容性

* fix:manager下发配置字符转数字bug

* update: 优化三方模块打印日志报错问题

* update: 修复Dify对接未过滤全量数据问题

* update:通过代理转发接口api

* fix:number熟知去掉小数点后的00

* update:优化docker启动

* update:优化参数读取

---------

Co-authored-by: haotian <haotian@codemao.cn>
Co-authored-by: expoli <31023767+expoli@users.noreply.github.com>
Co-authored-by: GoodyHao <865700600@qq.com>
2025-04-15 00:46:37 +08:00
hrz 11c9915a70 update:优化参数读取 2025-04-15 00:44:34 +08:00
hrz 6cf6b0d645 update:优化docker启动 2025-04-15 00:22:53 +08:00
hrzandGitHub 1980a39b73 Merge pull request #763 from GOODDAYDAY/main
update: 优化系统prompt & 优化三方模块打印日志报错问题 & 修复Dify未过滤全量数据问题
2025-04-14 23:09:43 +08:00
hrzandGitHub 8d3fbeeb22 Merge pull request #782 from expoli/main
[docker] 更新了 docker-compose
2025-04-14 22:50:50 +08:00
hrzandGitHub 2678644c2c Merge branch 'hot-fix' into main 2025-04-14 22:50:42 +08:00
hrz 25da9fe119 fix:number熟知去掉小数点后的00 2025-04-14 22:09:18 +08:00
hrz 03d89d22ec update:通过代理转发接口api 2025-04-14 21:53:52 +08:00
haotian 100cd25ffc Merge remote-tracking branch 'origin/main' 2025-04-14 21:44:06 +08:00
haotian 6284a74a6e update: 修复Dify对接未过滤全量数据问题 2025-04-14 21:43:55 +08:00
GoodyHaoandGitHub 978cd8ba8e Merge branch 'xinnan-tech:main' into main 2025-04-14 18:51:15 +08:00
haotian c76f28824c update: 优化三方模块打印日志报错问题 2025-04-14 18:49:24 +08:00
hrz 703ecfae40 fix:manager下发配置字符转数字bug 2025-04-14 18:30:58 +08:00
CGD b15c59aa48 fix:修复了分页器颜色不一致的问题 2025-04-14 17:23:07 +08:00
CGD 98a4ce701c fix:修复"设备管理"页面解绑设备时出现的异常,添加批量解绑功能,完善搜索功能 2025-04-14 16:10:46 +08:00
CGD 2fc0de8f4e fix:部分页面删去底部的©2025 xiaozhi-esp32-server 2025-04-14 14:38:04 +08:00
CGD 66fd551d68 完成“参数管理”的设置每页大小 2025-04-14 14:20:33 +08:00
expoliandGitHub 6e4e9a002b 现在新版本的 docker 直接集成了 compose 命令,不需要手动安装与执行 docker-compose 命令了,提高兼容性 2025-04-14 14:18:07 +08:00
expoliandGitHub 91278b0b3d Update Deployment_all.md
- MySQL 与 Redis 的安装是可选的
2025-04-14 14:16:25 +08:00
expoliandGitHub ff9b3ed1a2 Update Deployment_all.md
- 使用 docker compose 部署的时候,不应该每次更新的时候都把原来的 docker-compose 文件删除,再重新配置。
- 如果没有自己部署数据库需求的话,目前可以直接开箱即用,同步更新了 server 中的 Redis 与 MySQL 的镜像服务配置。
2025-04-14 14:15:37 +08:00
expoliandGitHub 0c855d4ba8 Update docker-compose_all.yml
- 直接提供全量的部署配置,包括 mysql 与 Redis 实现真正意义上的一键配置。
- 将 MySQL 的数据内容本地挂载存储,需要备份迁移的时候,直接打包复制文件夹即可。MySQL 容器删除的时候也不会出现数据丢失的问题。
2025-04-14 14:06:30 +08:00
expoliandGitHub 9176e9d255 Update Deployment_all.md
更新部分的安装配置文档,后续需要修改 docker-compose.yaml
2025-04-14 14:00:31 +08:00
hrzandGitHub 2bab917b3e update:优化文档 (#775)
* update:添加设计公司致谢

* update:添加设计公司致谢

* update:优化文档

* update:优化文档

* update:优化文档

* update:优化文档
2025-04-14 11:31:52 +08:00
CGD b80086a349 fix:移除“添加智能体”子元素触发弹框,将点击事件仅绑定在"添加智能体"文字部分 2025-04-14 10:48:43 +08:00
hrzandGitHub 8a4fca9bd5 update:优化文档 (#774)
* update:添加设计公司致谢

* update:添加设计公司致谢

* update:优化文档

* update:优化文档
2025-04-14 10:35:14 +08:00
hrzandGitHub 13288a6b3a update:优化文档
* update:添加设计公司致谢

* update:添加设计公司致谢

* update:优化文档
2025-04-14 10:26:34 +08:00
CGD 57ed552f43 完成“设备管理”样式的修改 2025-04-14 09:34:51 +08:00
CGD 5a4d65986d 优化了界面样式 2025-04-14 07:52:00 +08:00
CGD 427b3bb774 修改“设备管理”背景颜色,添加搜索框,完成设备管理的前端搜索功能 2025-04-13 22:32:34 +08:00
hrzandGitHub 4912f385c6 fix:智能体音色未随manager生效bug
* fix:连接manager后无法使用functioncallbug

* fix:意图识别使用llm无法播放音乐bug

* fix:manager第一图识别使用独立llm无法初始化llm的bug

* fix:智能体音色未随manager生效bug

* update:添加edgeTTS音色
2025-04-13 19:01:19 +08:00
haotian dcb6d0feab update: 优化系统prompt 2025-04-13 18:46:04 +08:00
hrzandGitHub de8c762d79 fix:manager意图识别bug (#762)
* fix:连接manager后无法使用functioncallbug

* fix:意图识别使用llm无法播放音乐bug

* fix:manager第一图识别使用独立llm无法初始化llm的bug
2025-04-13 18:10:17 +08:00
hrzandGitHub 056659304a Merge pull request #752 from xinnan-tech/web-api-model-fix
fix:修复了"模型配置"问题
2025-04-13 10:27:09 +08:00
hrzandGitHub 525bf18a03 Merge pull request #748 from xinnan-tech/web--vue-device-modify
Web  vue device modify
2025-04-13 10:26:40 +08:00
hrzandGitHub 7714c3f611 update:修复自动编译bug (#755) 2025-04-13 00:55:36 +08:00
hrzandGitHub 768b9fb7c1 Update readme (#754)
* update:更新文档

* update:优化文档路径

* update:优化文档
2025-04-13 00:32:32 +08:00
hrzandGitHub db4f028955 更新全模块部署文档 (#753)
* update:更新文档

* update:优化文档路径
2025-04-13 00:21:13 +08:00
CGD e3928ffbcc fix:修复了"模型配置"问题 2025-04-12 22:53:41 +08:00
hrzandGitHub db5159bd5e Hot fix (#750)
* fix:智能体名称

* update:修改版本号
2025-04-12 19:02:26 +08:00
hrzandGitHub 2626e1ebf0 fix:智能体名称 (#749) 2025-04-12 18:58:10 +08:00
hrzandGitHub 5d69ba0796 update:server连接api (#747)
* update:server连接manager-api

* update:读取智能体模型配置

* update:添加默认模型的按钮

* update:优化配置读取方式

* update:server兼容manager接口改造

* update:优化私有配置加载

* update:加载私有模型配置
2025-04-12 17:36:04 +08:00
hrzandGitHub c39ad97b8e Merge pull request #742 from xinnan-tech/web-vue-modify
fix:修复“模型配置编辑”功能
2025-04-12 17:33:28 +08:00
CGD 7c73f060ed update:添加了按钮样式 2025-04-12 09:28:19 +08:00
CGD f0aa7df3b0 update:完成“分页器”样式的替换 2025-04-12 08:27:03 +08:00
CGD 5882dec23a fix:修复“模型配置编辑”功能 2025-04-12 00:35:06 +08:00
hrzandGitHub 835c9deb28 Merge pull request #741 from xinnan-tech/web-api-timbre
Web api timbre
2025-04-11 21:25:34 +08:00
CGD a4ecc7ac7f update:优化了“音色配置”页面的样式 2025-04-11 17:44:05 +08:00
CGD 526a195e4d update:优化了"参数配置"页面的样式 2025-04-11 16:13:26 +08:00
CGD 5e77560313 update:优化了”用户管理“和”模型配置“页面样式 2025-04-11 16:01:57 +08:00
CGD 1abacfe90b update:优化了”模型配置“页面的样式 2025-04-11 15:43:45 +08:00
CGD d042c589df 修复了”删除“和”批量删除“的bug 2025-04-11 14:08:31 +08:00
hrzandGitHub 9f0f2870d0 Merge pull request #736 from xinnan-tech/web-vue-parameterization
Web vue parameterization
2025-04-11 10:27:17 +08:00
CGD 9dd445b0ce “值类型“添加json形式的选项 2025-04-11 10:19:38 +08:00
CGD 8e1cc6e75b 完善“搜索”功能 2025-04-11 10:03:05 +08:00
CGD a36887d885 修复“删除”和“批量删除”功能,优化“新增/编辑”页面 2025-04-11 09:29:34 +08:00
hrzandGitHub e300d04502 update:调整参数和音色删除接口,适应前端请求 (#732) 2025-04-10 18:26:28 +08:00
CGD 3d8afac9e1 update:优化了"新增/编辑参数"页面样式 2025-04-10 11:59:46 +08:00
hrzandGitHub 81d2571f8f update:参数配置,值类型强校验 (#725) 2025-04-10 10:33:55 +08:00
hrzandGitHub e4a0d2c56c update:初始化配置数据 (#724) 2025-04-10 00:18:59 +08:00
CGDandGitHub 55be189772 Merge pull request #723 from xinnan-tech/web_Timbre_api
Web timbre api
2025-04-09 17:54:01 +08:00
Ran_Chen 56352478fe 完成音色“修改”,“保存”接口的调用,删除报故障,需要调整一下 2025-04-09 17:52:41 +08:00
CGD f16d0406ea update:将“新增参数”和“编辑参数”以组件的形式分离出父组件 2025-04-09 17:14:19 +08:00
Sakura-RanChenandGitHub 9d1871dc80 Merge pull request #718 from xinnan-tech/web-vue-model-modify
Web vue model modify
2025-04-09 14:26:04 +08:00
CGD 137f29e9e5 update:优化了“模型配置”页面和“用户管理”页面的样式,完善了搜索功能 2025-04-09 14:23:23 +08:00
CGD 0e81a165e4 update:优化了“添加配置”页面和“修改配置”页面,修复了“添加配置”页面非正常关闭添加对话框时,没有正确重置对话框的状态 2025-04-09 14:08:05 +08:00
hrzandGitHub a73bcc05bc fix:用户id大值转换精度丢失的问题
* add:配置管理接口

* add:参数增删改查

* fix:用户id大值转换精度丢失的问题
2025-04-09 13:56:22 +08:00
hrzandGitHub cd97018c4d add:参数增删改查
* add:配置管理接口

* add:参数增删改查
2025-04-09 13:36:59 +08:00
Ran_Chen a42ed5c388 获取音色数据的接口实现,优化AudioPlayer警告 2025-04-09 11:00:10 +08:00
c1pherandGitHub f4bd80b5f9 [function]homeassistant操作能力增强 (#714)
* Update hass_get_state.py

支持查询色温、颜色

* Update hass_set_state.py

设置灯光颜色,色温

* 支持灯光色温颜色调整、查询

* 设置状态返回内容修正
2025-04-09 10:39:42 +08:00
hrzandGitHub 498542f8be add:配置管理接口 (#715) 2025-04-09 09:11:25 +08:00
hrzandGitHub 1aaa31ceb0 update:更新供应器sql语句
* update:修复vad,asr没有供应器bug

* update:更新供应器sql语句
2025-04-08 22:19:02 +08:00
hrzandGitHub 6fd63870f0 update:修复vad,asr没有供应器bug (#708) 2025-04-08 18:29:58 +08:00
CGD 0be5350a1c 优化了“修改模型”页面,使其能够动态获取到调用信息 2025-04-08 18:01:47 +08:00
Sakura-RanChenandGitHub 48a5489c52 Merge pull request #707 from xinnan-tech/web-api-model-modify
Web api model modify
2025-04-08 17:03:16 +08:00
CGDandGitHub fe0fb9a884 Merge pull request #706 from xinnan-tech/web_Sound_Management
完成“音色管理”页面
2025-04-08 16:28:09 +08:00
Ran_Chen 91a2846794 完成“音色管理”页面 2025-04-08 16:24:18 +08:00
CGD 625c9c2e40 优化了“添加模型”页面,使其能根据供应器动态获取到调用信息 2025-04-08 16:17:56 +08:00
CGD 710b338e6d 完成“编辑模型配置”功能开发 2025-04-08 10:53:59 +08:00
CGD 024468adbd 优化了“添加模型”页面,优化了“新增模型配置”功能 2025-04-08 09:38:31 +08:00
hrzandGitHub e4d173ad28 udate:优化异常显示 (#701) 2025-04-08 09:17:58 +08:00
CGD f4218b032a 优化了“修改模型”页面,优化了“获取模型配置”功能 2025-04-08 09:09:05 +08:00
hrzandGitHub b87ebc6e1c update:设定iot操作默认值 (#700) 2025-04-07 22:10:07 +08:00
CGD d901b0ed34 优化了“获取模型配置”功能 2025-04-07 20:52:11 +08:00
hrzandGitHub 519340dbf8 update:删除指定智能体关联的所有设备 (#699) 2025-04-07 18:24:43 +08:00
CGD a7f6a6cc16 完善“查询”功能 2025-04-07 16:57:31 +08:00
Sakura-RanChenandGitHub a0e56757d8 Merge pull request #696 from xinnan-tech/web-api
Web api
2025-04-07 15:54:14 +08:00
CGD a618c2a2c6 完成“启动-关闭模型配置”功能 2025-04-07 15:15:45 +08:00
hrzandGitHub 07d8f783e0 update:完成用户启用/禁用功能 (#694)
* update:优化文档

* update:完成用户启用/禁用功能
2025-04-07 15:14:20 +08:00
CGD 623bfe32f7 完成“获取模型配置”功能 2025-04-07 14:50:20 +08:00
hrzandGitHub 6862735aa0 update:优化文档 (#693) 2025-04-07 13:52:20 +08:00
hrzandGitHub 5461794a1e update:优化文档
* fix:修复不启动意图识别,functioncall依旧加载的bug
update:intent_llm意图识别获取新闻和天气过长,tts容易出错,暂时只支持简单简单工具

* update:优化文档
2025-04-07 13:31:33 +08:00
hrzandGitHub d9cd9acd27 fix:修复不启动意图识别,functioncall依旧加载的bug (#691)
update:intent_llm意图识别获取新闻和天气过长,tts容易出错,暂时只支持简单简单工具
2025-04-07 13:23:22 +08:00
CGD b2788ac7a0 完成“删除模型配置”以及批量删除功能 2025-04-07 10:04:16 +08:00
CGD d4f6e25f92 优化了“新增模型配置” 2025-04-07 09:44:48 +08:00
hrzandGitHub 17bcaba026 update:增加注册页面左图 (#683) 2025-04-06 20:55:11 +08:00
hrzandGitHub aed4e6b1e4 update:首页智能体面板显示优化
* update:更新版本号

* update:允许actions编译手动触发

* fix:如果获取不到版本号,actions编译失败

* add:增加docker部署redis步骤

* update:增加启用/关闭模型配置api

* update:完成角色配色功能

* update:编译文档优化

* update:优化编译文档

* update:首页智能体面板显示优化
2025-04-06 18:28:39 +08:00
hrzandGitHub 50897e97af update:完成角色配置功能 (#679)
* update:更新版本号

* update:允许actions编译手动触发

* fix:如果获取不到版本号,actions编译失败

* add:增加docker部署redis步骤

* update:增加启用/关闭模型配置api

* update:完成角色配色功能
2025-04-06 15:45:18 +08:00
hrzandGitHub e57137b101 fix:如果获取不到版本号,actions编译失败
* update:更新版本号

* update:允许actions编译手动触发

* fix:如果获取不到版本号,actions编译失败
2025-04-06 11:50:27 +08:00
hrzandGitHub 3370ecb2ef update:允许actions编译手动触发
* update:更新版本号

* update:允许actions编译手动触发
2025-04-06 11:38:43 +08:00
hrzandGitHub 8225a5a12f update:更新版本号 (#676) 2025-04-06 11:00:42 +08:00
hrzandGitHub 5655362a00 update:ota接口验证通过 (#675) 2025-04-06 02:09:49 +08:00
欣南科技andGitHub 2f2884e991 Merge pull request #672 from xinnan-tech/hot-fix
合并多个提交
2025-04-05 22:14:30 +08:00
hrz 6061a1d2bf update:优化样式 2025-04-05 22:12:54 +08:00
hrz a28d316ca4 update:删除首页背景,加快加载速度 2025-04-05 21:55:50 +08:00
hrz d53230245f update:模型配置删除接口,只需要id参数 2025-04-05 21:55:05 +08:00
hrz d2e8d1c261 fix:删除用户时调错agent接口bug 2025-04-05 21:22:54 +08:00
hrz fe86e1fb0f update:统一接口读取方式 2025-04-05 21:03:46 +08:00
hrz 03441c8582 update:格式化代码 2025-04-05 20:19:28 +08:00
hrz d255edfd5f update:设置接口读取方式 2025-04-05 20:16:02 +08:00
hrz 52187daafa Merge branch 'hot-fix' of https://github.com/xinnan-tech/xiaozhi-esp32-server into hot-fix 2025-04-05 19:37:51 +08:00
hrz 6f46776484 update:编辑模型配置(/models/{modelType}/{provideCode}/{id})保存configJson达不到预期 2025-04-05 19:37:18 +08:00
hrzandGitHub 50b2b37bc1 Merge pull request #671 from xinnan-tech/web-api-model
Web api model
2025-04-05 19:01:59 +08:00
欣南科技andGitHub 35eed6dca0 合并多个提交
合并多个提交
2025-04-05 17:19:54 +08:00
hrz e337dd920d update:统一使用PageData返回分页 2025-04-05 17:16:06 +08:00
hrzandGitHub 671c992f3a Merge pull request #627 from funsugar/patch-1
Update util.py
2025-04-05 14:57:01 +08:00
hrzandGitHub b5ff4d329b Merge pull request #654 from xinnan-tech/manager-api-admin
完善了管理员的接口,删除,用户分页,全部设备
2025-04-05 14:56:37 +08:00
hrzandGitHub 941eb063fb Merge pull request #663 from xinnan-tech/Web_Optimize
Web optimize
2025-04-05 14:56:16 +08:00
CGD 148e71985f 完成修改模型页面的”获取模型供应器列表“功能;”删除模型配置“出现”服务器内部异常“错误。 2025-04-04 23:52:36 +08:00
欣南科技andGitHub efd22b36ef Merge pull request #665 from xinnan-tech/test-server-pr
优化test_page.html播放卡顿问题
2025-04-04 19:11:06 +08:00
hrzandGitHub 70ea24ac83 Merge pull request #629 from joey-zhou/vad_optimize
优化vad,采用bytearray替换byte,提高字符串拼接效率
2025-04-04 18:39:16 +08:00
hrzandGitHub 3c9b7ad60b Merge pull request #664 from huangjunsen0406/main
fix: 🐛 优化test_page.html
2025-04-04 18:38:28 +08:00
hrzandGitHub 18cc1273f6 Merge pull request #651 from devspadeboy/add_PySocks_package
为xiaozhi-server的requirments.txt文件添加PySocks包
2025-04-04 18:37:49 +08:00
huangjunsen0406 a6f9f0d88c feat(test): 新增简化版网页调试工具 2025-04-04 16:43:14 +08:00
huangjunsen0406 d36210ad6b feat(test): 新增opus本地录制播放测试 2025-04-04 16:42:45 +08:00
huangjunsen0406 b2262d062c fix: 🐛 修复test_page.html 播放异常问题 2025-04-04 16:42:07 +08:00
hrzandGitHub 27be7fd217 Merge branch 'main' into Web_Optimize 2025-04-03 22:13:15 +08:00
hrzandGitHub 97f210c9de Merge branch 'main' into manager-api-admin 2025-04-03 22:11:09 +08:00
Ran_Chen a230fc6aaa ota接口文件编写 2025-04-03 18:00:23 +08:00
hrzandGitHub b042a34726 优化登录页大背景图片文件,加快页面加载 (#660)
* update:优化说明文档

* update:优化登录页大背景图片文件,加快页面加载
2025-04-03 17:40:53 +08:00
Ran_Chen 1673ad7e70 优化智能体列表布局,修复HeaderBar的搜索功能,增加确认的回车按键绑定 2025-04-03 17:07:13 +08:00
CGD f7eab31b57 完成”新增模型配置“API 2025-04-03 16:23:15 +08:00
CGDandGitHub 4553188739 Merge pull request #659 from xinnan-tech/web_Pop_Change
解决提示框遮挡问题,优化UserManagement和HeaderBar布局,导航栏重复点击警告,退出跳转页面的优化
2025-04-03 15:51:50 +08:00
Ran_Chen 7550ac327e 解决提示框遮挡问题,优化UserManagement和HeaderBar布局,导航栏重复点击警告,退出跳转页面的优化 2025-04-03 15:44:41 +08:00
CGD 5b28cf6c66 完成“获取模型供应器列表”的API 2025-04-03 14:31:57 +08:00
CGD 4ea27cbdc2 优化了“修改模型”弹框的样式 2025-04-03 10:47:31 +08:00
剑雨 e474a2114c 添加了参数验证
--AdminController.java
--AdminPageUserDTO.java
--DevicePageUserDTO.java
定义新的验证说明
--validation.properties
--validation_en_US.properties
--validation_zh_CN.properties
--validation_zh_TW.properties
2025-04-03 10:16:47 +08:00
剑雨 06e4834e27 优化获取用户的名称的方法
--RedisKeys.java 设一个新key
--SysUserUtilServiceImpl.java 优化赋值用户名的方法,把用户名暂存在redis。获取相同的不需要频繁去数据库查找
2025-04-03 09:49:39 +08:00
剑雨 083e7071f0 用户模块和设备模块存在循环引用,把用户模块常用方法抽出一个工具类,以后设备模块等和用户相关的模块,直接引用工具类,避免循环引用
--DeviceServiceImpl.java 修改设备模块获取用户名的方式,使用用户工具类
--SysUserService.java 新建用户工具类,定义了赋值用户名方法
--SysUserServiceImpl.java 实现工具类,重写了赋值用户名的方法
2025-04-03 09:41:14 +08:00
CGD 14be24a515 完成“获取模型配置列表”功能 2025-04-03 08:24:47 +08:00
CGD fbdfd8df69 完成"获取模型配置列表" 2025-04-02 23:52:13 +08:00
devspadeboy 17b5308515 为xiaozhi-server的requirments.txt文件添加PySocks包
原因: requests
经过socks代理访问网络的时候需要依赖第三方package,
对应PySocks包。否则会出现"Missing dependencies for SOCKS
support."的错误。
参考连接: https://requests.readthedocs.io/en/latest/user/advanced/#socks
2025-04-02 22:51:35 +08:00
8f4c0901f0 Hot fix (#650)
* update:添加设备注册验证码接口

* update:更新说明文档

* update:优化说明文档

* update:更新说明文档

* update:优化说明文档

---------

Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com>
2025-04-02 19:41:02 +08:00
hrzandGitHub e18065a1bf update:更新说明文档 (#649)
* update:添加设备注册验证码接口

* update:更新说明文档

* update:优化说明文档

* update:更新说明文档
2025-04-02 18:34:22 +08:00
Sakura-RanChenandGitHub ad0493f8a0 Merge pull request #648 from xinnan-tech/web-model-api
Web model api
2025-04-02 17:59:03 +08:00
CGDandGitHub b85368f8d4 Merge pull request #647 from xinnan-tech/web_HeaderBar_Classify
调整设备管理接口与页面,运用本地缓存增加HeaderBar管理员和用户分类
2025-04-02 17:55:41 +08:00
CGD 18ea1e7acf 样式调整 2025-04-02 17:54:25 +08:00
Ran_Chen 50d7627b81 调整设备管理接口于页面,增加HeaderBar管理员和用户分类,运用本地缓存 2025-04-02 17:39:49 +08:00
CGD 5934cf718d 优化了”用户管理“页面样式,修改了”模型配置“页面的分页器 2025-04-02 17:19:46 +08:00
CGD c57ab0e703 优化了”用户管理“和”模型配置“的样式 2025-04-02 16:47:44 +08:00
剑雨 2fb7281d07 管理员控制层分页获取用户接口改回使用ExtendPageData。符合设计接口定义
--AdminController.java
--SysUserService.java
--SysUserServiceImpl.java
2025-04-02 16:03:46 +08:00
剑雨 4c0d55d2e6 管理员控制层完善的查找全部设备的接口
--AdminController.java 完善查找全部设备接口
--DateUtils.java 编写了一个返回简短时间描述的方法,如刚刚,几分钟前
--DevicePageUserDTO.java 定义了需要什么查找参数
--DeviceService.java 定义了分页方法
--DeviceServiceImpl.java 实现了分页获取全部设备的方法
--ExtendPageData.java 扩展的分页对象,多返回一个页数
--UserShowDeviceListVO.java 定义了返回全部设备每个对象的字段
2025-04-02 15:59:18 +08:00
hrzandGitHub a1904d4a80 update:添加设备注册验证码接口 (#646) 2025-04-02 15:56:15 +08:00
CGDandGitHub 8856f7efa6 Merge pull request #645 from xinnan-tech/Web_Device_Api
Web device api
2025-04-02 15:41:08 +08:00
Ran_Chen bdedc48aca 完成device接口调整,调整弹窗设置,优化Headerbar 2025-04-02 15:38:04 +08:00
Sakura-RanChenandGitHub 8a6b4e5499 Merge pull request #644 from xinnan-tech/web-admin-modify
Web admin modify
2025-04-02 15:16:49 +08:00
CGD f7a6de912c 完成批量启用,批量删除,批量禁止功能 2025-04-02 15:14:41 +08:00
CGD 48fa48c159 优化了”全选“等四个按钮的样式,完成”全选“功能 2025-04-02 14:21:33 +08:00
Ran_Chen c811beadb4 修复警告 2025-04-02 12:05:09 +08:00
CGDandGitHub 4c76ad2ba7 Merge pull request #641 from xinnan-tech/web_voice
单独语音项处理,优化style结构
2025-04-02 11:47:41 +08:00
Ran_Chen 0a63b00199 单独语音项处理,优化style结构 2025-04-02 11:36:12 +08:00
CGD 0761d5a8c2 优化了“重置密码弹框” 2025-04-02 11:31:06 +08:00
hrzandGitHub f6ad17729f update:添加提示词模版sql (#638) 2025-04-02 11:07:19 +08:00
Sakura-RanChenandGitHub 1f9f564def Merge pull request #637 from xinnan-tech/web-API-admin
Web api admin
2025-04-02 11:02:43 +08:00
CGD 198755c3f5 完成“分页查找用户”、“重置密码”功能 2025-04-02 10:57:26 +08:00
CGDandGitHub d2b6d5f083 Merge pull request #635 from xinnan-tech/Web_Agent_Api_Test
Web agent api test
2025-04-02 10:18:28 +08:00
Ran_Chen 444e3192a2 完善并调试智能体接口配置 2025-04-02 10:16:12 +08:00
欣南科技andGitHub c12d3df073 Merge pull request #634 from xinnan-tech/fix-hot
Fix hot
2025-04-02 09:28:25 +08:00
hrz 82b1548ae0 update:优化异常登录 2025-04-02 09:23:36 +08:00
hrzandGitHub afd1d2ae6a Merge pull request #632 from joey-zhou/config
config文件添加缺失的api key申请地址
2025-04-02 09:19:10 +08:00
CGDandGitHub 90aafe1b5f Merge pull request #633 from flashily/web-flashily
重新划分agent-api
2025-04-02 09:14:45 +08:00
CGD 143d33e285 “分页查找用户”有问题,“用户删除”已完成 2025-04-02 08:05:03 +08:00
flashily 09938e0aa6 重新划分agent-api,修复登录后home页面报错。 2025-04-02 00:32:24 +08:00
joeyandGitHub fd59aff4d0 Merge branch 'main' into config 2025-04-01 23:58:54 +08:00
JoeyZhou 59133b9d8d config文件添加缺失的api key申请地址 2025-04-01 23:56:10 +08:00
欣南科技andGitHub cca42309c4 Merge pull request #630 from xinnan-tech/test-manager-api
合并多个修改
2025-04-01 23:21:20 +08:00
hrz 510ee1b282 update:manager-api已接近完成,现在进入正式接口联调阶段 2025-04-01 23:14:15 +08:00
hrz c4d3020a1d update:增加tencentasr领取信息 2025-04-01 23:10:10 +08:00
JoeyZhou 132174568f 优化vad,采用bytearray替换byte,提高字符串拼接效率 2025-04-01 22:47:15 +08:00
hrzandGitHub f3af30ae53 Merge pull request #607 from joey-zhou/tencent_asr
feat:新增腾讯云ASR语音识别服务
2025-04-01 22:09:59 +08:00
JoeyZhou 6d3f270a8f 添加tencent asr配置文件 2025-04-01 22:07:22 +08:00
hrz 86621661df update:日志配置在logback配置文件即可 2025-04-01 22:04:04 +08:00
hrz d668917694 fix:用户分页页码参数错误bug 2025-04-01 21:49:26 +08:00
hrz 8bc1d09c4a fix:hass_set_state字符判断错误 2025-04-01 21:31:50 +08:00
hrzandGitHub 76b90c179b Merge pull request #614 from joey-zhou/manager_build
优化后端Java部署文档方案
2025-04-01 21:26:47 +08:00
funsugarandGitHub ee17d9416a Update util.py
修复根据IP地址获取城市地址出错的问题
2025-04-01 17:27:58 +08:00
CGDandGitHub 7c389a0641 Merge pull request #624 from xinnan-tech/web_api_user_test
user接口修改并测试
2025-04-01 11:37:24 +08:00
CGDandGitHub 4241120853 Merge branch 'main' into web_api_user_test 2025-04-01 11:35:55 +08:00
Ran_Chen 5998fc3edd user接口修改并测试 2025-04-01 10:59:35 +08:00
joeyandGitHub 392f8a1ce0 Merge branch 'main' into manager_build 2025-04-01 09:50:40 +08:00
欣南科技andGitHub 04203c4103 update:更新版本号 (#623) 2025-04-01 09:37:22 +08:00
hrz 188cd7f023 update:更新版本号 2025-04-01 09:36:16 +08:00
欣南科技andGitHub 8d17f5bd3c merge:web端底层优化
update:web端底层优化
2025-04-01 09:22:27 +08:00
hrz 9d39880e9d update:让VUE_APP_TITLE变量控制title 2025-04-01 09:20:50 +08:00
hrzandGitHub f27e213d13 Merge pull request #620 from huangjunsen0406/main
feat: 同步上游 main 分支并优化构建配置
2025-04-01 09:04:37 +08:00
huangjunsen0406 69ecb2c503 Merge remote-tracking branch 'upstream/main' 2025-04-01 00:45:43 +08:00
huangjunsen0406 0585a0732f perf(图片): 压缩图片大小、img标签启用原生lazy 2025-04-01 00:42:36 +08:00
huangjunsen fab4adb07e perf(构建): 优化构建时间:首次构建提升 50%,二次构建提升 30%
优化构建配置,提升构建效率和文件体积优化

1. 禁用生产环境的 source map:为了减少生产环境构建的文件体积,提高构建速度,生产环境下不再生成 source map。

2. 配置开发服务器代理:在 `devServer` 中配置了代理,解决了跨域问题,确保本地开发时可以顺利访问后端 API。

3. 动态插入 CDN 链接:在生产环境下,通过 `chainWebpack` 动态修改 HTML 插件配置,插入 CDN 链接,避免打包常用库(如 `vue`, `axios`,
`element-ui` 等),从而减少打包体积。

4. 代码分割优化:优化了 `splitChunks` 配置,减少了小模块的分割,提升了构建效率和缓存利用率。增加了 `minSize` 和 `maxSize` 配置,避免过多的小模块被拆分。

5. 启用多线程压缩:在生产环境中,配置了 `TerserPlugin` 以启用并行压缩,减少了 JS 文件的压缩时间。

6. 启用 Gzip 压缩:通过 `CompressionPlugin` 配置了 Gzip 压缩,只压缩大于 20KB 的文件,减少了构建后的文件体积。

7. 配置 `externals`:通过 `externals` 排除了 `vue`, `vue-router`, `vuex`, `element-ui`, `axios`,
`opus-decoder` 等库的打包,避免重复打包并通过 CDN 加载这些库。

8. 配置文件系统缓存:启用了 Webpack 文件系统缓存,加速了增量构建,缓存目录设置为 `.webpack_cache`,缓存有效期为一个月。

9. 配置 Webpack 别名:为项目中的路径配置了别名,简化了模块导入,减少了代码中的路径冗余。

10. 提供可选的打包分析:通过 `BundleAnalyzerPlugin` 插件,在 `ANALYZE=true` 环境变量下启用打包分析功能,帮助分析并优化构建后的文件。
2025-04-01 00:38:32 +08:00
joeyandGitHub 5c3ae278be Update README.md 2025-04-01 00:05:32 +08:00
joeyandGitHub 19a067f4e8 Update README.md 2025-04-01 00:04:16 +08:00
hrzandGitHub 9430094072 update:修复退出保存记忆bug (#619) 2025-04-01 00:03:05 +08:00
joeyandGitHub a7b5e0df6c Delete main/manager-api/start.sh 2025-04-01 00:02:40 +08:00
joeyandGitHub 83e9725d02 Merge branch 'main' into manager_build 2025-04-01 00:00:48 +08:00
JoeyZhou 086dea8594 fix:修复日志无法正常生成bug,采用logback控制日志 2025-03-31 23:58:12 +08:00
欣南科技andGitHub 50490b70b7 Merge pull request #618 from xinnan-tech/test-server-pr
合并若干提交
2025-03-31 23:28:57 +08:00
欣南科技andGitHub fac3a0acd7 Merge branch 'main' into test-server-pr 2025-03-31 23:28:37 +08:00
hrz 73c4d6a604 update:优化提示词 2025-03-31 23:26:23 +08:00
hrz 6ba63db47f 1、将耗时最大的iot处理改成异步
2、iot循环5次等待func_handler加载完成
2025-03-31 22:54:37 +08:00
JoeyZhou d3c8e4badb feat:后端服务器新增启动脚本,自动建立log日志 2025-03-31 22:21:46 +08:00
hrz fdd16ad3c7 update:
1、await self.close(ws)已经在finally有关闭逻辑了,无需在except里关闭。
2、func_handler和mcp_manager的加载必须用异步实现,减少唤醒连接好使
3、关于iot等待func_handler问题,要在iot消息那里做等待
2025-03-31 21:40:48 +08:00
hrz 9178faef6d update:取消从音频文件转成文字,提高响应速度 2025-03-31 21:24:48 +08:00
joeyandGitHub 665318164e Update requirements.txt 2025-03-31 21:23:40 +08:00
joeyandGitHub 44f8254b43 Delete main/xiaozhi-server/core/providers/asr/vosk_local.py 2025-03-31 21:23:15 +08:00
joeyandGitHub e085fb1b10 Update config.yaml 2025-03-31 21:22:13 +08:00
joeyandGitHub 48ed5d5c9b Update .gitignore 2025-03-31 21:18:03 +08:00
hrz e2da300e09 update:优化腾讯tts生成指南 2025-03-31 21:13:50 +08:00
hrz e11a37ffec update:优化测试web工具 2025-03-31 21:04:11 +08:00
huangjunsen 20c1f0576b build(环境变量): 📦 去除.env忽略、修改请求地址、控制台名称通过env获取
修改了环境变量的读取方式

调整了请求地址配置

控制台名称通过 .env 获取
2025-03-31 19:52:01 +08:00
huangjunsen fef8cece90 chore: 🔨 去除.env.development忽略 2025-03-31 19:40:57 +08:00
huangjunsen f549f26541 feat(style): 新增全局css覆写element ui 自动填充色 2025-03-31 17:35:46 +08:00
hrzandGitHub 9ba836b814 修复唤醒缓存文件过小bug (#613)
* fix:音色表id为String

* update:修复唤醒缓存文件过小bug
2025-03-31 16:48:56 +08:00
剑雨 26e471616c 完善管理员分页查询用户功能
--AdminPageUserDTO.java 修改的dto的说明
--AdminPageUserVO.java 添加了状态的字段
--DeviceService.java 添加了查询用户设备数量定义
--DeviceServiceImpl.java 实现查询用户设备方法
--SysUserServiceImpl.java 完善分页查询,查询出每个用户的设备数量和状态字段
2025-03-31 16:30:56 +08:00
JoeyZhou f20c17f27d 优化部署文档:改进打包和服务启动命令
1. 修改打包命令为 mvn clean install,确保构建前清理旧文件
2. 改进服务启动命令,添加日志输出和进程ID保存
3. 规范化操作流程,提高部署可靠性
2025-03-31 16:13:32 +08:00
joeyandGitHub fd188f6f65 Merge branch 'main' into vosk_asr 2025-03-31 15:50:32 +08:00
剑雨 d10ba456d7 完善管理员删除用户功能,缺少对话删除功能
--AdminController.java 修改调用的方法名字
--AgentService.java 添加了指定用户id智能体批量删除定义
--AgentServiceImpl.java 实现了删除方法
--DeviceService.java添加了指定用户id设备批量删除定义
--DeviceServiceImpl.java 实现了删除方法
--SysUserService.java 修改删除定义的方法名字
--SysUserServiceImpl.java 完成用户删除实现
2025-03-31 15:47:42 +08:00
JoeyZhou ee3808dd2f feat:新增vosk本地语音识别服务 2025-03-31 15:41:45 +08:00
CGDandGitHub a345d41e9e Merge pull request #609 from xinnan-tech/web_HeaderBar_SignOut
清除本地token缓存,完成前端退出
2025-03-31 15:36:51 +08:00
Sakura-RanChenandGitHub d36ea8a906 Merge pull request #608 from xinnan-tech/web-api-admin-get
Web api admin get
2025-03-31 15:35:31 +08:00
Ran_Chen 28f8533d0a 清除本地token缓存,完成前端退出 2025-03-31 15:34:26 +08:00
CGD 587b9e8800 完成用户管理模块的搜索功能 2025-03-31 15:25:57 +08:00
JoeyZhou d2d97149fc feat:新增腾讯云ASR语音识别服务 2025-03-31 15:19:29 +08:00
CGD c3a1b0f609 完成”获取用户列表“ 2025-03-31 14:44:23 +08:00
CGDandGitHub 1b25ed5903 Merge pull request #604 from xinnan-tech/web_ModelConf_optimize
优化了“模型配置的页面”
2025-03-31 14:32:46 +08:00
Ran_Chen e87c96300b 优化了“模型配置的页面” 2025-03-31 14:28:29 +08:00
hrzandGitHub ba5bf33b5a fix:音色表id为String (#603) 2025-03-31 11:02:58 +08:00
hrzandGitHub 94e1c09647 fix:音色表id为String (#602) 2025-03-31 10:57:27 +08:00
hrzandGitHub 6bd7ad9413 Merge pull request #600 from AdJIa/feat-test-page
feat: 添加了方便用于测试xiaozhi-server的web页面,支持文本、语音(基于libopus库的编解码)对话
2025-03-31 10:52:54 +08:00
hrzandGitHub d75032e853 Merge pull request #601 from gmm932/xiaozhi-test-pr
add:添加 Server 测试网页
2025-03-31 10:50:44 +08:00
CGD 261ecfea4d 优化了用户管理的样式 2025-03-31 10:24:37 +08:00
lujia 6f9cd94e66 feat: 添加了方便用于测试xiaozhi-server的web页面,支持文本、语音(基于libopus库的编解码)对话 2025-03-31 09:38:26 +08:00
安稳 ee2e1ed5fc add:添加 Server 测试网页 2025-03-31 09:35:35 +08:00
hrzandGitHub 92c1317d4c Merge pull request #576 from joey-zhou/tencent_tts
feat:增加腾讯云TTS服务
2025-03-30 18:36:33 +08:00
hrzandGitHub cfb97a087b Merge pull request #587 from sxd-mike/main
添加路由守护
2025-03-30 18:36:06 +08:00
hrzandGitHub 76ba99aa6c Merge pull request #591 from journey-ad/patch-2
一些功能优化和bug修复
2025-03-30 18:35:27 +08:00
hrzandGitHub 330d7ed95c Merge pull request #596 from xhit-ux/patch-1
Update: 修改错别字
2025-03-30 18:34:10 +08:00
拾尘andGitHub 878e73524b Update: 修改错别字 2025-03-30 18:14:10 +08:00
hrzandGitHub 13b0aa1a7b update:初始化音色表、模型配置表数据 (#592) 2025-03-30 15:10:15 +08:00
Jad 326342b834 chore(server): 移除高频触发的debug日志输出 2025-03-30 13:11:52 +08:00
Jad 8bbd610f12 fix(server): 适配1.5.2版本客户端的设备名
已经在 4c91bff 修复了自动注册的iot方法,和此插件功能重复了,也许可以删掉此插件?
另外观察到function_call准确率不够高,可能是llm的问题
2025-03-30 13:09:18 +08:00
Jad 549c2af988 feat(server): asr保留更多的音频前导帧
语速较快或者声音不清晰时,保留更多前导帧可以提升asr识别效果
2025-03-30 13:06:15 +08:00
Jad d02c56a0e0 feat(server): 使用主动退出指令时显示到设备上 2025-03-30 13:03:01 +08:00
Jad 1f3654cc01 feat(server): 开启唤醒词加速时返回唤醒词文本 2025-03-30 13:01:04 +08:00
Jad b6b6b0310f fix(server): 退出时等待保存记忆导致立即重连时阻塞 2025-03-30 12:58:27 +08:00
Jad 4c91bffcc7 fix(server): 加载插件改为初始化时同步执行 #559 2025-03-30 12:46:54 +08:00
hrzandGitHub d730a6cd06 update:初始化模型供应器数据 (#589) 2025-03-30 01:31:26 +08:00
林zp 67050f972e 添加一个路由守护 2025-03-30 00:20:09 +08:00
欣南科技andGitHub ffafd32693 Merge pull request #579 from xinnan-tech/test-manager-api
manage-api集成测试
2025-03-29 21:56:04 +08:00
hrz ab4ec5e468 update:完成user、agent、device三个模块接口测试 2025-03-29 21:52:06 +08:00
hrzandGitHub a07277feb8 Merge pull request #580 from xinnan-tech/manager-api-newdevice
修改了设备模块的绑定设备接口的逻辑
2025-03-29 18:10:00 +08:00
hrzandGitHub a3cd4b6e66 Merge branch 'test-manager-api' into manager-api-newdevice 2025-03-29 18:09:51 +08:00
hrz 25b7e76407 update:格式化代码 2025-03-29 18:00:06 +08:00
hrz 630cb13066 update:优化泛型代码 2025-03-29 17:49:39 +08:00
hrz 81188a0129 update:删除无用文件 2025-03-29 17:15:19 +08:00
hrzandGitHub cf1abd90a0 Merge pull request #577 from CaixyPromise/main
实现OTA接口
2025-03-29 17:12:08 +08:00
hrzandGitHub 6865a8be45 Merge branch 'test-manager-api' into main 2025-03-29 17:11:58 +08:00
hrz b63fcc80a1 update:整合现有版本 2025-03-29 17:06:53 +08:00
caixypromise a19ba6d48b chore: 因为在OTA激活设备时要求绑定用户信息,如果不把这里的调试功能注释则拿不到用户信息,会数据不完整。 2025-03-29 16:04:57 +08:00
caixypromise 70b6efe922 feature: 新增实现OTA功能——实现设备激活、前端输入激活码验证并保存设备。 2025-03-29 16:02:30 +08:00
caixypromise 0b07269346 chore: DeviceEntity设备实体类的Id字段类型错误,数据库内为varchar,映射类型为Long; 2025-03-29 15:59:48 +08:00
JoeyZhou 336954eeeb feat:增加腾讯云TTS服务 2025-03-29 15:18:15 +08:00
hrz 2988e5eef2 merge:main分支的manager-web和xiaozhi-server 2025-03-29 10:49:55 +08:00
hrzandGitHub fe87889ccd Merge pull request #572 from dreamchen/feature_manager-web&api_agent&device&roleConfig&ota&syncConfig
Feature manager web&api agent&device&role config&ota&sync config
2025-03-29 10:27:55 +08:00
hrzandGitHub 0598ab4400 Merge pull request #574 from LinZeb906791229/main
get_ip_info接口优化
2025-03-29 09:06:05 +08:00
hrzandGitHub 7d090f8ecc Merge pull request #573 from mkanako/fix_function_call_type
修复function call的参数类型定义错误
2025-03-29 09:02:44 +08:00
kanako 088ee47b3c 修复function call的参数类型定义错误 2025-03-28 23:44:07 +08:00
Erlei Chen a320b4c001 update:调整方法鉴权 2025-03-28 23:17:53 +08:00
Erlei Chen 3adce95340 Merge branch 'main' into MVP 2025-03-28 23:13:24 +08:00
abin 2a6bfd792a get_ip_info接口优化
将失效的https://freeipapi.com/api/json替换为https://whois.pconline.com.cn/ipJson.jsp?json=true
2025-03-28 22:52:21 +08:00
Erlei Chen 0816f1f589 feature:为合并mian做准备 2025-03-28 22:22:23 +08:00
欣南科技andGitHub 497def8055 Merge pull request #569 from xinnan-tech/add-tag
update:0.1.16版本
2025-03-28 18:48:05 +08:00
hrz 62be26ff47 update:0.1.16版本
- 增加mcp服务
- 支持Dify、Coze时使用独立的意图识别
2025-03-28 18:47:20 +08:00
hrzandGitHub 6a81d80778 Merge pull request #492 from xinnan-tech/mcp-client
增加mcp的tool功能,可自行配置mcp服务,实现命令控制
2025-03-28 18:42:24 +08:00
剑雨 8914beae26 修改了设备模块的绑定设备接口的逻辑
--DeviceBindDTO.java 把原来设备请求头dto改为绑定dto
--DeviceController.java 修改设备绑定接口,改为需要提供智能体和设备验证码2个参数,通过设备验证码获取到mac地址,把用户id,mac地址,智能体id保存到数据库
--DeviceEntity.java 继承基础实体类
--DeviceService.java 修改绑定设备方法定义
--DeviceServiceImpl.java 重写绑定设备方法
2025-03-28 18:04:01 +08:00
hrzandGitHub 60c38a8dea Merge pull request #562 from mkanako/fix_handle_device
fix:修复handle_device.py 参数类型定义错误导致DoubaoLLM调用报 400 错误
2025-03-28 17:51:04 +08:00
欣南科技andGitHub 4e0934debf Merge pull request #567 from xinnan-tech/fix_intent_llm
fix:Dify、Coze可使用独立意图识别
2025-03-28 17:49:27 +08:00
hrz 17126303fa fix:已经使用了intent_llm,不应该再回到functioncall的chat中 2025-03-28 17:17:14 +08:00
CGDandGitHub 98fa95190c Merge pull request #563 from xinnan-tech/web_UserManagement_optimize
优化了“用户管理”,分页还未调整
2025-03-28 15:42:03 +08:00
Ran_Chen 3a2261107c 优化了“用户管理”,分页还未调整 2025-03-28 15:24:53 +08:00
kanakoandGitHub b1b64abed9 fix:修复handle_device.py 参数类型定义错误导致DoubaoLLM调用报 400 错误 2025-03-28 14:20:11 +08:00
hrz 05356b6652 update:简化intent_llm代码 2025-03-28 14:13:54 +08:00
CGDandGitHub cbd0c6615d Merge pull request #558 from xinnan-tech/web-HeaderBar-optimize
优化“导航栏”响应调整
2025-03-28 11:35:52 +08:00
Sakura-RanChenandGitHub 90e389efa2 Merge pull request #560 from xinnan-tech/web-api-AgentTemplate-get
完成“获取智能体模板”
2025-03-28 11:34:36 +08:00
CGD 00a0b26797 完成“获取智能体模板” 2025-03-28 11:20:08 +08:00
Ran_Chen 69b5ffe3bf 优化“导航栏”响应调整 2025-03-28 11:08:12 +08:00
玄凤科技 31539294eb 调整mcp等待时间为15秒 2025-03-28 09:52:27 +08:00
玄凤科技 d1a2307134 Merge branch 'mcp-client' of https://github.com/xinnan-tech/xiaozhi-esp32-server into mcp-client
# Conflicts:
#	main/xiaozhi-server/core/connection.py
#	main/xiaozhi-server/requirements.txt
2025-03-28 09:11:48 +08:00
hrzandGitHub 97b777f1b2 Merge pull request #550 from Ike-li/fix_Dockerfile-server
修复 Dockerfile-server 注释
2025-03-27 22:30:26 +08:00
Erlei Chen c565bfc6bc feature:实现角色配置 2025-03-27 22:14:52 +08:00
Sakura-RanChenandGitHub a63137abc9 Merge pull request #551 from xinnan-tech/web-API-device
Web api device
2025-03-27 17:59:54 +08:00
CGD 3ca06f8d50 完成”绑定设备“功能 2025-03-27 17:54:05 +08:00
Ike-li e5b0e202be 修复 Dockerfile-server 注释 2025-03-27 15:39:11 +08:00
Erlei Chen 41db6aafd9 feature:角色配置能力部分实现 2025-03-27 14:06:40 +08:00
CGDandGitHub c66385d1f7 Merge pull request #547 from xinnan-tech/web-AddModel-optimize
优化“添加模型”页面布局
2025-03-27 11:57:01 +08:00
Ran_Chen b0304fd875 优化“添加模型”页面布局 2025-03-27 11:54:05 +08:00
CGD 5bfc3efc9c 完成“解绑设备” 2025-03-27 11:24:49 +08:00
hrzandGitHub 62845cfcf1 Merge pull request #515 from koalalgx/main
意图识别intent_llm单独配置独立的LLM,意图识别intent_llm增加天气和新闻查询function
2025-03-27 10:43:49 +08:00
Sakura-RanChenandGitHub 5c536e72df Merge pull request #546 from xinnan-tech/web-ModelConfig-modify
优化了”模型配置“页面
2025-03-27 09:13:41 +08:00
欣南科技andGitHub 7cfece1592 Merge pull request #544 from xinnan-tech/hot-fix
fix:函数调用时有时参数为空的bug
2025-03-27 01:08:50 +08:00
hrzandGitHub f393b2c9ba Merge pull request #542 from flashily/manger-api-flashily
修复Long id写入string的bug
2025-03-27 01:07:02 +08:00
hrzandGitHub 164f2e5472 Merge branch 'main' into manger-api-flashily 2025-03-27 01:06:42 +08:00
hrz 1434ecbc2e fix:函数调用时有时参数为空的bug 2025-03-27 01:03:38 +08:00
flashily c2e443d4ac 修复Long id写入string的bug 2025-03-27 00:26:37 +08:00
hrzandGitHub c079125458 Merge pull request #536 from xinnan-tech/manage-server-aliTTSToken
增加token过期刷新,修复过期bug
2025-03-27 00:21:01 +08:00
欣南科技andGitHub 4c1d7b34b9 Merge pull request #541 from xinnan-tech/hot-fix
merge:manag-api
2025-03-27 00:11:37 +08:00
hrzandGitHub 53772b3fe3 Merge pull request #540 from xinnan-tech/manager-api-agent-jsonL
agent api
2025-03-27 00:06:03 +08:00
hrz 28d3c40835 fixed:modelConfigEntity.setId是String类型 2025-03-27 00:02:26 +08:00
hrzandGitHub a01faecd0c Delete main/manager-api/.vscode/settings.json 2025-03-26 23:59:48 +08:00
hrzandGitHub a1b071e38e Merge pull request #518 from xinnan-tech/manager-api-admin
管理员功能重置密码,删除用户接口完成,分页和查询设备需等设备管理完成后调用实现
2025-03-26 23:12:56 +08:00
hrzandGitHub 571622a0f4 Merge pull request #483 from xinnan-tech/manager-api-model
manager-api 模块新增 /api/v1/models/ 接口
2025-03-26 23:10:36 +08:00
CGD 4ab84d7209 优化了”模型配置“页面 2025-03-26 21:43:07 +08:00
Erlei Chen 4b90e70aad Merge branch 'main' into MVP 2025-03-26 19:44:02 +08:00
hrzandGitHub 30a938e348 update:更换iot演示demo图 (#538) 2025-03-26 18:31:23 +08:00
Sakura-RanChenandGitHub f6f4384e42 Merge pull request #537 from xinnan-tech/web-vue-modelconfig
优化了”模型配置“页面
2025-03-26 17:15:15 +08:00
CGD 911b11211e 优化了”模型配置“页面 2025-03-26 17:10:06 +08:00
Erlei Chen ee0da9fc3a feature:OTA能力完善 2025-03-26 17:00:54 +08:00
Ken 8bb596c74f 增加token过期刷新,修复 2025-03-26 16:51:26 +08:00
CGDandGitHub d0fdeb33e0 Merge pull request #533 from xinnan-tech/web-model-AddModelDialog
完成添加模型组件
2025-03-26 16:43:19 +08:00
Ran_Chen ae7f41032e 完成添加模型组件 2025-03-26 15:53:47 +08:00
剑雨 f30cb6badb 完善删除,删除用户时,删除其他关联数据(等待其他功能方法调用)
--SysUserServiceImpl.java
2025-03-26 14:49:22 +08:00
欣南科技andGitHub 97a9c1e08a Merge pull request #532 from xinnan-tech/test-server-pr
merge:农历插件优化、异步消费线程不销毁的BUG
2025-03-26 13:14:43 +08:00
hrzandGitHub 1e699c027d Merge pull request #504 from suofang/fix_not_clear_threads
fix:解决客户端websocket连接断开后异步消费线程不销毁的BUG
2025-03-26 11:40:06 +08:00
hrzandGitHub 9bd731729d Merge pull request #528 from journey-ad/refactor-plugins
优化获取日期和音量亮度控制插件
2025-03-26 11:39:40 +08:00
hrzandGitHub dc17eccf76 add:在日志里添加版本信息 (#529) 2025-03-26 10:15:00 +08:00
Jad 3649a33afb feat(plugin_func): 重构音量控制插件,支持亮度调整
支持立创实战派等设备亮度调整
2025-03-26 04:24:47 +08:00
Jad d76bcc1366 feat(plugin_func): 拆分get_time和get_lunar插件 2025-03-26 01:32:43 +08:00
欣南科技andGitHub 2987f1c680 Merge pull request #519 from xinnan-tech/manage-server-edgetts-stream
edge-tts 流式写
2025-03-26 00:40:15 +08:00
hrz ab93ee0406 update:调整加载顺序 2025-03-26 00:39:34 +08:00
hrzandGitHub 69dd933a7d Merge pull request #526 from xinnan-tech/fix-wakeup_words
Fix wakeup words
2025-03-25 23:48:53 +08:00
欣南科技andGitHub 5440de0453 Merge pull request #525 from xinnan-tech/flashily-web
Flashily web
2025-03-25 22:44:42 +08:00
hrzandGitHub f2e130c70e Merge pull request #517 from flashily/flashily-web
与mock连接
2025-03-25 22:41:38 +08:00
Sakura-RanChenandGitHub 4c845c26f7 Merge pull request #521 from xinnan-tech/web-device-bind
Web device bind
2025-03-25 19:52:23 +08:00
CGD a43acca375 优化了user.js文件 2025-03-25 18:59:05 +08:00
Ken 635520aa85 edge-tts 流式写入,避免一次性加载完整语音到内存,无需等待完整音频生成再保存,音频数据块(chunk)生成后立即写入文件,相比等待完整音频生成后再保存,能更早得到部分结果 2025-03-25 18:05:25 +08:00
剑雨 fce9759e93 修复bug
--AdminController.java 重置密码添加返回新密码
--AdminPageUserDTO.java 取消参数验证
--SysUserServiceImpl.java 修改分页查询条件
2025-03-25 18:00:50 +08:00
flashily 18243cd4b0 与mock连接 2025-03-25 16:52:40 +08:00
剑雨 75a6e51e68 管理员控制层,添加接口,删除用户,重置密码,查找所有设备(未完成)
--AdminController.java
2025-03-25 16:50:47 +08:00
剑雨 312edd4b15 用户业务层为添加新方法,重置密码,直接修改密码
--SysUserService.java 定义2个方法
--SysUserServiceImpl.java 实现重置密码,直接修改密码
2025-03-25 16:39:38 +08:00
CGD be3e67b2c1 完成“已绑设备”功能 2025-03-25 16:23:28 +08:00
剑雨 a5765f81fe 控制层添加了新接口
--AdminController.java 添加管理员分页接口
2025-03-25 15:22:56 +08:00
剑雨 4bee92abb0 用户业务层添加了新功能:分页查找
--AdminPageUserDTO.java 管理员分页用户参数DTO
--AdminPageUserVO.java 管理员分页用户展示VO
--SysUserService.java 添加分页方法定义
--SysUserServiceImpl.java 实现了分页方法
2025-03-25 15:04:48 +08:00
CGDandGitHub d9dcb90745 Merge pull request #512 from xinnan-tech/web-change-password
完成修改密码
2025-03-25 14:33:03 +08:00
koalalgxandGitHub fbd25353d5 Update get_weather.py 2025-03-25 14:29:28 +08:00
koalalgxandGitHub 44c3e73577 Update intentHandler.py 2025-03-25 14:28:59 +08:00
koalalgxandGitHub 9cbad1a448 Update get_news.py 2025-03-25 14:28:32 +08:00
koalalgxandGitHub 3a3ddbc2bc Update connection.py 2025-03-25 14:27:48 +08:00
koalalgxandGitHub 63a3c33df9 Update base.py 2025-03-25 14:27:10 +08:00
koalalgxandGitHub 32d7400f7d Update intent_llm.py 2025-03-25 14:26:46 +08:00
koalalgxandGitHub 38191f976b Create xinference.py 2025-03-25 14:25:31 +08:00
CGDandGitHub c380e0e110 Merge branch 'main' into web-change-password 2025-03-25 14:24:58 +08:00
koalalgxandGitHub 2199b8eca7 Update config.yaml 2025-03-25 14:24:05 +08:00
koalalgxandGitHub 88455604a2 Update config.yaml 2025-03-25 14:22:34 +08:00
Sakura-RanChenandGitHub 63eb69091f Merge pull request #505 from xinnan-tech/web-agent-put
Web agent put
2025-03-25 14:19:33 +08:00
欣南科技andGitHub fc668810bd Merge pull request #510 from xinnan-tech/web-flashily
Web flashily
2025-03-25 11:32:57 +08:00
hrzandGitHub 2773e3a48a Merge pull request #506 from flashily/web-flashily
优化界面与细节
2025-03-25 11:28:31 +08:00
flashily 18a03f6ccc 优化界面与细节 2025-03-25 03:15:04 +08:00
CGD e9348f5512 优化了”配置智能体“ 2025-03-25 00:01:13 +08:00
suofang ce65226ea4 fix:解决客户端websocket连接断开后异步消费线程不销毁的BUG 2025-03-24 23:53:56 +08:00
CGD a3b3079b63 完成“配置智能体“功能 2025-03-24 22:41:32 +08:00
hrzandGitHub 22de4bb13b update:修复音乐播放卡顿和断连的bug
修复音乐播放卡顿和断连的bug
2025-03-24 21:55:02 +08:00
zhishengandGitHub eef7bb0cad Delete main/manager-api/src/main/resources/db/changelog/data.sql~ 2025-03-24 20:13:14 +08:00
pengzhisheng 8f1fbbdeef 去除多余文件,优化接口权限,优化 model 实体类主键生成策略 2025-03-24 20:08:33 +08:00
pengzhisheng f3562e091f 去除多余文件,优化接口权限,优化 model 实体类主键生成策略 2025-03-24 20:03:56 +08:00
Ran_Chen 2001258ef4 完成修改密码 2025-03-24 18:08:05 +08:00
CGD 52a66ea304 完成获取智能体配置 2025-03-24 17:27:08 +08:00
欣南科技andGitHub 21ddb1541f update:优化天气、农历插件
update:优化天气、农历插件
2025-03-24 17:16:34 +08:00
hrzandGitHub a3ec6d79b0 feat: 优化查询天气插件
feat: 优化查询天气插件
2025-03-24 16:32:11 +08:00
hrzandGitHub bb8c315ffc Merge pull request #495 from journey-ad/improve-func-time
feat: 支持查询农历日期和黄历信息
2025-03-24 16:30:55 +08:00
hrzandGitHub f2ae398d3f Merge pull request #493 from xinnan-tech/web-agent-configuration
完成删除智能体
2025-03-24 15:31:01 +08:00
Jad 02e33f68b4 feat: 支持查询农历日期和黄历信息 2025-03-24 13:12:46 +08:00
玄凤科技 8c4c9d88da 增加网站浏览mcp 2025-03-24 10:28:48 +08:00
李健豪 49cb98a606 修复音乐播放卡顿和断连的bug 2025-03-24 09:28:01 +08:00
Erlei Chen bdd18b384b feature:
1.OTA基本能力(固定配置),待优化
2.智能体管理与设备管理对接api,实现流程闭环(已测试)
2025-03-24 09:26:45 +08:00
玄凤科技 32ab716f0d Merge branch 'main' into mcp-client
# Conflicts:
#	main/xiaozhi-server/core/connection.py
#	main/xiaozhi-server/requirements.txt
2025-03-24 09:26:14 +08:00
LFNL-scholarandGitHub fe24529fce Merge branch 'xinnan-tech:main' into main 2025-03-24 09:24:30 +08:00
CGD b59c097bca 完成删除智能体 2025-03-24 09:16:48 +08:00
玄凤科技 6d6414eb62 Merge branch 'main' of https://github.com/xinnan-tech/xiaozhi-esp32-server 2025-03-24 08:44:29 +08:00
Jad 5b1c20d625 feat: 优化查询天气插件
7天预报包含每日天气
2025-03-24 02:09:01 +08:00
hrzandGitHub af6c8ab935 update:优化唤醒词缓存文件
* update:增加私有唤醒答复文件

* update:优化唤醒词缓存文件
2025-03-23 22:59:09 +08:00
hrzandGitHub 4acb5f924a update:增加私有唤醒答复文件 (#488) 2025-03-23 22:40:31 +08:00
b1b99f1066 update:忽略提示词缓存文件
* update:开启唤醒词加速

* update:忽略提示词缓存文件

---------

Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com>
2025-03-23 22:12:11 +08:00
hrzandGitHub 004ee6f143 update:开启唤醒词加速 (#486) 2025-03-23 22:02:13 +08:00
欣南科技andGitHub 19455f89d3 update:优化唤醒词答复
update:优化唤醒词答复
2025-03-23 17:49:04 +08:00
hrz 18d98e3bd0 update:优化对唤醒词的识别 2025-03-23 17:47:39 +08:00
pengzhisheng af038b7fc8 manager-api 模块新增 /api/v1/models/ 接口:
1、新增模型配置对应接口;
2、优化 mapper 目录文件结构;
3、优化启动时自动执行数据库初始化脚本,新增默认账号 admin
2025-03-23 17:32:59 +08:00
hrz 4a9297dc65 update:优化提示音配置,这只成可配置项 2025-03-23 17:20:36 +08:00
HuangandGitHub 49fefb41a6 update:优化唤醒词响应时间,修复初始化函数异步bug (#482) 2025-03-23 16:20:20 +08:00
HuangandGitHub 2fa64dfdd0 Merge pull request #476 from journey-ad/optional-greeting
优化打招呼和进入聆听模式的反馈
2025-03-23 12:09:55 +08:00
HuangandGitHub e0f777b9b4 Merge pull request #472 from xinnan-tech/web-vue-add
Web vue add
2025-03-23 10:52:58 +08:00
HuangandGitHub dfe3de14d8 Merge branch 'main' into web-vue-add 2025-03-23 10:52:36 +08:00
HuangandGitHub abe234c7ce update:补充音乐插件的配置 (#479) 2025-03-23 10:46:15 +08:00
HuangandGitHub d7bf67fd0e Merge pull request #478 from xinnan-tech/XuSenfeng-patch-1
Update play_music.py 原有提示词在模型在未明确指定音乐的时候不能正确识别
2025-03-23 10:45:21 +08:00
Jiao HaoyangandGitHub 8218996f04 Update play_music.py 原有提示词在模型在未指定音乐的时候不能正确识别 2025-03-23 09:35:57 +08:00
Jad ed91e5d607 feat: 开场是否回复唤醒词支持配置 2025-03-23 01:26:31 +08:00
Jad 0bf0b0d381 feat: TTS播放结束提示音 2025-03-23 01:26:29 +08:00
欣南科技andGitHub d88559f5de update:homeassistant插件化改造
update:homeassistant插件化改造
2025-03-22 21:40:17 +08:00
hrz f398115df3 update:更新Home Assistant接入方式 2025-03-22 21:38:40 +08:00
hrz 82413fecab update:优化home_assistant提示词加载 2025-03-22 20:39:52 +08:00
Erlei Chen 66352de1eb Merge branch 'main' into feature_manage-web&api_agent&device 2025-03-22 08:37:38 +08:00
Erlei Chen a7730f6e93 update:修改样式、调整页面命名 2025-03-22 08:33:24 +08:00
HuangandGitHub d5a5b24223 Merge pull request #418 from c1pher-cn/main
服务端支持function的形式调用homeassistant控制智能设备
2025-03-22 01:40:27 +08:00
HuangandGitHub 472dba2665 Merge branch 'test-pr-homeassistant' into main 2025-03-22 01:40:15 +08:00
CGD a5444b11b3 优化了各个界面 2025-03-22 00:22:12 +08:00
HuangandGitHub 5a05a68524 add:AliAppLLM
add:AliAppLLM
2025-03-21 22:04:24 +08:00
欣南科技andGitHub 36be61bbe1 merge:音色管理功能
音色管理功能
2025-03-21 21:46:11 +08:00
hrz ec6ab983f7 udate:规范包名,小写包名 2025-03-21 21:44:40 +08:00
HuangandGitHub 69630c2be0 update:配置字段统一小写 2025-03-21 21:09:54 +08:00
HuangandGitHub 8e605dd1d4 update:配置字段统一小写 2025-03-21 21:09:32 +08:00
HuangandGitHub 8bb0cf02ad update:配置字段统一小写 2025-03-21 21:08:50 +08:00
HuangandGitHub 60c054f457 update:将manger-web启动设置成8001端口
Update vue.config.js
2025-03-21 21:01:06 +08:00
CGD 5648ee974a 完成三个组件 2025-03-21 18:08:47 +08:00
玄凤科技 dc4968f961 添加命令行服务 2025-03-21 17:31:29 +08:00
剑雨 36d1370720 添加了请求参数验证
--TimbreController.java 验证参数
--TimbreDataDTO.java
--TimbreDataDTO.java 定义参数验证
--validation.properties
--validation_en_US.properties
--validation_zh_CN.properties
--validation_zh_TW.properties 定义参数验证失败后的错误说明
2025-03-21 17:26:16 +08:00
玄凤科技 7ead5537f6 提交requirements.txt中mcp版本依赖 2025-03-21 17:15:54 +08:00
sunweigongandGitHub 7fe797d44d Update vue.config.js
Bug 修复:在启动manger-web连接服务端时,默认为8080端口。此处应按main/README.md文档说明,指定端口为 8001。
2025-03-21 16:42:00 +08:00
剑雨 a1e959420d 为接口添加权限控制
--TimbreController.java 添加权限
2025-03-21 16:07:17 +08:00
剑雨 4c99b40257 修复错误
--TimbreController.java 修复分页获取不到请求参数的错误
--TimbreDetailsVO.java 统一分页参数为string
--TimbrePageDTO.java 序列化
2025-03-21 15:58:39 +08:00
剑雨 ead0958d18 Merge branch 'refs/heads/main' into manager-api-modelAndTimbre-api 2025-03-21 15:04:11 +08:00
李健豪 220af8068e 修复音乐播放断开bug
- 修复音乐播放速度太快导致设备内存溢出断开连接的问题
2025-03-21 14:59:01 +08:00
剑雨 a1730b2d52 添加音色管理的功能
--TimbreController.java 音色控制层,添加了分页,删除,保存,修改接口
--TimbreDetailsVO.java 音色详情展示对象
--TimbrePageDTO.java 音色分页参数
--TimbreDataDTO.java 音色保存数据参数
--TimbreDao.java 音色持久定义
--TimbreService.java 音色业务层定义,添加了分页,删除,保存,修改,获取详情
--TimbreServiceImpl.java
--TimbreEntity.java 音色表对应的实体
2025-03-21 14:57:22 +08:00
jsonL 1a1d6c0835 agent api 2025-03-21 13:00:17 +08:00
Miyokiss 06cbffdea4 Add:AliAppLLM 2025-03-21 03:12:18 +08:00
Erlei Chen 4409f37000 合并样式及功能 2025-03-21 01:39:23 +08:00
HuangandGitHub c45e1faf42 update:默认开启意图识别 (#460) 2025-03-20 23:19:31 +08:00
欣南科技andGitHub 732d42f174 Merge pull request #459 from xinnan-tech/test-server-pr
@CaixyPromise
fix(tts.base): 修复 AudioSegment.from_file 读取音频时阻塞的问题 
@CaixyPromise
feature(#377): 实现清理大模型AI的Markdown语法功能,优化TTS自然度。关联Issue #377 
@yanyige
feat: 新闻插件开发, 使用function_call支持播放当天新闻 #403
2025-03-20 22:31:30 +08:00
HuangandGitHub 524fde99f8 Merge pull request #457 from yanyige/feat-support-news
feat: 新闻插件开发, 使用function_call支持播放当天新闻 #403
2025-03-20 22:14:19 +08:00
yanyige 649246dc6a feat: 新闻插件开发, 使用function_call支持播放当天新闻 #403 2025-03-20 21:55:07 +08:00
HuangandGitHub 6c7ac25388 Merge pull request #449 from BoBoloverrrr/bobo-xiaozhi
代码复用:将输入验证逻辑封装成一个独立的函数validateInput ,提高代码的复用性。
2025-03-20 21:46:36 +08:00
HuangandGitHub cb1d6ddeec Merge pull request #455 from CaixyPromise/feature/clean-markdown-for-tts
feature/clean markdown for tts 实现清理大模型AI的Markdown语法功能,
2025-03-20 21:45:19 +08:00
HuangandGitHub 58313889ee Merge pull request #454 from CaixyPromise/fix/tts.base-blocking-bug
fix(tts.base): 修复 AudioSegment.from_file 读取音频时阻塞的问题
2025-03-20 21:44:53 +08:00
caixypromise 5ebc62a084 feature(#377): 实现清理大模型AI的Markdown语法功能,优化TTS自然度。关联Issue #377
close #377
2025-03-20 21:29:53 +08:00
caixypromise fcba384a15 fix(tts.base): 修复 AudioSegment.from_file 读取音频时阻塞的问题
添加 `-nostdin` 参数,防止 FFmpeg 在 Windows 环境下等待标准输入,导致阻塞执行。
2025-03-20 21:10:04 +08:00
玄凤科技 3ea95ffa48 mcp模板文件 2025-03-20 18:20:50 +08:00
玄凤科技 898fd36eca 增加mcp调用超时检测逻辑 2025-03-20 18:20:09 +08:00
CGD 6cd88fb98f 完成模型配置-页面1 2025-03-20 18:11:37 +08:00
欣南科技andGitHub 396de72c5d Merge pull request #451 from xinnan-tech/hot-fix
fix:修复输出日志logger不存在bug
2025-03-20 16:59:33 +08:00
hrz 9932d60dee fix:修复输出日志logger不存在bug 2025-03-20 16:58:12 +08:00
bobo 1ad93ce1fd 右上角用户名显示,新增下拉菜单样式
接收username字段并显示在右上角。
右上角添加了下拉菜单样式,包含个人中心,修改密码以及退出登录。
2025-03-20 16:57:38 +08:00
bobo 2041513676 代码复用:将输入验证逻辑封装成一个独立的函数validateInput ,提高代码的复用性。
validateInput 函数:该函数用于验证输入是否为空,若为空则显示错误提示并返回 false,否则返回 true。
2025-03-20 16:05:06 +08:00
CGD 3ea1a2bf2f 完成用户管理页面 2025-03-20 14:08:38 +08:00
玄凤科技 2b81ebca8e 调通mcp tool 2025-03-20 11:52:37 +08:00
欣南科技andGitHub 27df97d710 Merge pull request #441 from xinnan-tech/fixbug-gettime
修复gettime插件导致chatglm卡住问题
2025-03-20 09:12:33 +08:00
玄凤科技 a25b7b9b3f 修复gettime插件导致chatglm卡住问题 2025-03-20 09:09:47 +08:00
玄凤科技 de9809ca5a 修复时间插件导致chatglm卡住问题 2025-03-20 09:06:36 +08:00
玄凤科技 8504c181c0 mcp初步调试 2025-03-20 08:59:45 +08:00
欣南科技andGitHub 8a005ffb00 Merge pull request #440 from xinnan-tech/test-server-pr
Test server pr
2025-03-20 08:58:24 +08:00
hrz 80ffa7a577 updtge:优化超参 2025-03-20 08:57:39 +08:00
HuangandGitHub ca8f9a7e4a Merge pull request #406 from diaoling6665/main
允许修改openai接口的超参
2025-03-19 23:27:57 +08:00
HuangandGitHub 8d19d78297 Merge pull request #419 from aileenfun/main
gemini独立代理模式
2025-03-19 23:27:14 +08:00
欣南科技andGitHub 483e5eb85a 合并:ASR增加sherpa-onnx模型
feat: ASR增加sherpa-onnx模型 #315 (#379)
2025-03-19 23:22:17 +08:00
HuangandGitHub a4b4f491d2 Merge branch 'main' into test-pr-sherpa-asr 2025-03-19 23:21:23 +08:00
HuangandGitHub 08093f509a update:优化配置文件缩进 2025-03-19 22:39:23 +08:00
HuangandGitHub 9a96223d12 fix:部分支持openai的LLM的url配置名不同 2025-03-19 22:21:11 +08:00
欣南科技andGitHub 0c2b194444 add:manage-web智能体功能
添加智能体功能
2025-03-19 21:48:04 +08:00
HuangandGitHub a06055168f Merge branch 'main' into web-agent-add 2025-03-19 21:40:52 +08:00
欣南科技andGitHub e04546a9ae 合并:机智云湾湾小何
合并:机智云湾湾小何
2025-03-19 20:28:23 +08:00
hrz f6da1a045d update:湾湾小何音色新渠道 2025-03-19 20:27:15 +08:00
CGD acd386bb09 添加智能体功能 2025-03-19 18:13:03 +08:00
HuangandGitHub 45225c0ce1 Merge pull request #427 from insrat/support-gizwitsapi
add:支持Gizwits API渠道接口
2025-03-19 17:44:21 +08:00
HuangandGitHub 0bd884d309 update:manage-web对接用户信息、获取智能体列表,搜索接口
完成前端获取用户信息、获取智能体列表,搜索功能开发
2025-03-19 17:38:38 +08:00
欣南科技andGitHub aea2ae8d79 合并提交,修复dify不支持工作流的bug 2025-03-19 17:25:10 +08:00
hrz 99ec5ab161 update:修正dify saas平台地址 2025-03-19 17:23:25 +08:00
CGD 4431e7470f 完成前端获取用户信息、获取智能体列表,搜索功能开发 2025-03-19 16:16:29 +08:00
jzhuang ed775405c6 add:支持Gizwits API渠道接口 2025-03-19 13:57:42 +08:00
欣南科技andGitHub 4aefbe5aa8 add:获取用户信息、修改密码 (#394)
Manager api user
2025-03-19 01:45:56 +08:00
hrz 786c467464 update:格式化代码 2025-03-19 01:44:25 +08:00
hrz 50182ef940 update:优化获取用户信息的代码 2025-03-19 01:42:36 +08:00
HuangandGitHub bf3aa2df09 dify默认应该是chat模式 2025-03-19 00:20:47 +08:00
HuangandGitHub 0b744a2599 update:dify默认应是chat模式 2025-03-19 00:20:08 +08:00
HuangandGitHub a75b8b40e2 Merge pull request #423 from xinnan-tech/XuSenfeng-patch-2
iot状态更新
2025-03-18 23:57:17 +08:00
HuangandGitHub 2e23c08608 Merge pull request #421 from xinnan-tech/web-vue-modify
Web vue modify
2025-03-18 23:55:54 +08:00
Jiao HaoyangandGitHub 178f45ee26 iot状态更新 2025-03-18 23:55:47 +08:00
CGD 7b16edc99c 优化了前端代码 2025-03-18 23:08:13 +08:00
CGD f3738a1630 修改了前端页面 2025-03-18 21:49:57 +08:00
aileenfun 34fa65132d gemini独立代理模式
在代理模式下手动组成request,无论 Steam 是否为 True,返回的均是非steam值。
不设置代理时自动切换为google的官方api,走 stream模式。

全局代理容易让系统中其他模块也使用代理,造成无法访问的问题。
测试最新的 gemini flash 2.0可以使用。
2025-03-18 21:04:21 +08:00
c1pher-cn 19fe2d2f03 服务端支持function的形式调用homeassistant控制智能设备 2025-03-18 20:42:09 +08:00
HuangandGitHub c796af7d07 Hot fix (#415)
* fix:找不到function卡住bug

* fix:客户端拾音模式日志bug
2025-03-18 18:42:17 +08:00
Jiao HaoyangandGitHub b7251e2be2 Merge pull request #413 from XuSenfeng/DifyBug
Dify bug添加工作流和文本生成模式的处理
2025-03-18 17:18:51 +08:00
Jiao HaoyangandGitHub 6efede79c7 Merge branch 'xinnan-tech:main' into DifyBug 2025-03-18 17:09:51 +08:00
Xvsenfeng e58da80f8e 添加工作流和文本生成模式 2025-03-18 15:58:31 +08:00
bd556c33a0 fix:找不到function卡住bug (#408)
Co-authored-by: hrz <1710360675@qq.com>
2025-03-18 14:26:27 +08:00
878809ecb4 update:修复iot的bug (#407)
* fix: 修复manual模式无法识别 (#404)

* feat(docs): 新增Issues模板

* fix: 补全core依赖

* fix: 修复manual模式无法识别

* 去除重复依赖.txt

已经有torch和torchaudio

---------

Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com>

* 修复iot功能中表达式问题 (#400)

* Custom paths asr tts (#388)

* #164 自定义asr、tts缓存目录,项目启动自动创建目录

* #164 自定义asr、tts缓存目录,项目启动自动创建目录

* fix:修复语音无法找到新配置项output_file的bug

* fix:电脑不支持iot音量控制bug

---------

Co-authored-by: Junsen <66542771+Huang-junsen@users.noreply.github.com>
Co-authored-by: tang <tangyiyong@gmail.com>
Co-authored-by: shudongW <178200623@qq.com>
Co-authored-by: hrz <1710360675@qq.com>
2025-03-18 13:25:34 +08:00
HaoDuoYuandGitHub ea4966f494 Merge branch 'xinnan-tech:main' into main 2025-03-18 12:08:16 +08:00
HaoDuoYuandGitHub f5d9b478d5 Update config.yaml 2025-03-18 12:07:53 +08:00
HaoDuoYuandGitHub 3d97fab16d Add files via upload 2025-03-18 12:05:33 +08:00
aileenfun 2c1fc30bfb gemini可以单独走代理,避免全局代理。
全局代理容易让系统中其他模块也使用代理,造成无法访问的问题。
测试最新的 gemini flash 2.0可以使用。
2025-03-18 00:53:03 +08:00
pengzhisheng c7dec8045e 后端新增 /api/v1/user/change-password 接口 2025-03-17 23:25:24 +08:00
pengzhisheng 5d7d12415c Merge branch 'main' into dev 2025-03-17 19:01:16 +08:00
JadandGitHub 08e57936fc feat: ASR增加sherpa-onnx模型 #315 (#379) 2025-03-17 13:45:49 +08:00
pengzhisheng 7d474e17fa Merge branch 'main' into dev 2025-03-17 09:59:11 +08:00
pengzhisheng d3571942d7 Merge branch 'main' into dev 2025-03-16 17:01:25 +08:00
pengzhisheng 66f5f8ab12 merge main 2025-03-15 10:00:23 +08:00
pengzhisheng 74ce9f7a86 Merge branch 'main' into dev 2025-03-14 10:15:29 +08:00
pengzhisheng 5b2957e7eb 优化登录 token 校验:
1、后端新增 /api/v1/user/info 接口,优化 Oauth2Filter.getRequestToken 逻辑;
2、前端 /api/v1/user/login 登录成功后,保存 token 至浏览器本地;
3、前端请求添加本地 token。
2025-03-14 09:54:54 +08:00
pengzhisheng 40eb4ea4ec merge main 2025-03-14 05:47:08 +08:00
pengzhisheng 0f41039b99 update package 2025-03-12 17:14:48 +08:00
pengzhisheng 8f76db9e41 优化登录注册流程,跑通注册登录、接口 2025-03-12 16:56:51 +08:00
pengzhisheng fc922e996c Merge branch 'main' into dev-manager-api-local 2025-03-12 15:31:20 +08:00
pengzhisheng 6142d325c8 Merge branch 'main' into dev-manager-api-local 2025-03-10 13:46:34 +08:00
pengzhisheng aa21aa686d Merge branch 'main' into dev-manager-api-local 2025-03-09 02:24:58 +08:00
pengzhisheng ec2719a572 Merge branch 'main' into dev 2025-03-08 02:54:17 +08:00
pengzhisheng a466126d63 Merge branch 'main' into dev 2025-03-07 13:25:59 +08:00
pengzhisheng 568f322a25 Merge branch 'main' into dev 2025-03-06 21:20:46 +08:00
pengzhisheng 1b64959e2c fix manager-api bug 2025-03-06 21:16:31 +08:00
397 changed files with 33940 additions and 5707 deletions
+10 -5
View File
@@ -4,6 +4,7 @@ on:
push: push:
tags: tags:
- 'v*.*.*' # 只在以 v 开头的标签推送时触发,例如 v1.0.0 - 'v*.*.*' # 只在以 v 开头的标签推送时触发,例如 v1.0.0
workflow_dispatch:
jobs: jobs:
release: release:
@@ -41,7 +42,13 @@ jobs:
- name: Extract version from tag - name: Extract version from tag
id: get_version id: get_version
run: | run: |
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV if [[ "$GITHUB_REF" =~ ^refs/tags/v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
echo "VERSION=${BASH_REMATCH[1]}" >> $GITHUB_ENV
echo "IS_VERSION=true" >> $GITHUB_ENV
else
echo "VERSION=latest" >> $GITHUB_ENV
echo "IS_VERSION=false" >> $GITHUB_ENV
fi
# 构建 xiaozhi-server 镜像 # 构建 xiaozhi-server 镜像
- name: Build and push xiaozhi-server - name: Build and push xiaozhi-server
@@ -51,8 +58,7 @@ jobs:
file: Dockerfile-server file: Dockerfile-server
push: true push: true
tags: | tags: |
ghcr.io/${{ github.repository }}:server_${{ env.VERSION }} ${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:server_{1},ghcr.io/{0}:server_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:server_latest', github.repository) }}
ghcr.io/${{ github.repository }}:server_latest
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
# 构建 manager-api 镜像 # 构建 manager-api 镜像
@@ -63,6 +69,5 @@ jobs:
file: Dockerfile-web file: Dockerfile-web
push: true push: true
tags: | tags: |
ghcr.io/${{ github.repository }}:web_${{ env.VERSION }} ${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:web_{1},ghcr.io/{0}:web_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:web_latest', github.repository) }}
ghcr.io/${{ github.repository }}:web_latest
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
+17 -3
View File
@@ -75,6 +75,7 @@ docs/_build/
# PyBuilder # PyBuilder
.pybuilder/ .pybuilder/
target/ target/
*.pid
# Jupyter Notebook # Jupyter Notebook
.ipynb_checkpoints .ipynb_checkpoints
@@ -106,7 +107,6 @@ celerybeat.pid
*.sage.py *.sage.py
# Environments # Environments
.env
.venv .venv
env/ env/
venv/ venv/
@@ -141,13 +141,27 @@ music/
# Cython debug symbols # Cython debug symbols
cython_debug/ cython_debug/
*.iml *.iml
model.pt
tmp tmp
.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
main/xiaozhi-server/models/SenseVoiceSmall/model.pt
main/xiaozhi-server/models/sherpa-onnx*
my_wakeup_words.mp3
!main/xiaozhi-server/config/assets/bind_code.wav
!main/xiaozhi-server/config/assets/bind_not_found.wav
!main/xiaozhi-server/config/assets/bind_code/*.wav
!main/xiaozhi-server/config/assets/max_output_size.wav
main/manager-api/.vscode
# Ignore webpack cache directory
main/manager-web/.webpack_cache/
main/xiaozhi-server/mysql
uploadfile
+3 -3
View File
@@ -5,15 +5,15 @@ WORKDIR /app
COPY main/xiaozhi-server/requirements.txt . COPY main/xiaozhi-server/requirements.txt .
# 优化apt安装 # 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
# 第阶段:生产镜像 # 第阶段:生产镜像
FROM python:3.10-slim FROM python:3.10-slim
WORKDIR /opt/xiaozhi-esp32-server WORKDIR /opt/xiaozhi-esp32-server
# 优化apt安装 # 安装系统依赖
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends libopus0 ffmpeg && \ apt-get install -y --no-install-recommends libopus0 ffmpeg && \
apt-get clean && \ apt-get clean && \
+1 -1
View File
@@ -23,7 +23,7 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 配置Nginx # 配置Nginx
COPY docs/docker/nginx.conf /etc/nginx/conf.d/default.conf COPY docs/docker/nginx.conf /etc/nginx/nginx.conf
# 复制前端构建产物 # 复制前端构建产物
COPY --from=web-builder /app/dist /usr/share/nginx/html COPY --from=web-builder /app/dist /usr/share/nginx/html
+111 -217
View File
@@ -1,7 +1,29 @@
[![SVG Banners](https://svg-banners.vercel.app/api?type=origin&text1=你好😃,小智📟&text2=开源小智ESP-32后端服务&width=830&height=210)](https://github.com/xinnan-tech/xiaozhi-esp32-server) [![Banners](docs/images/banner1.png)](https://github.com/xinnan-tech/xiaozhi-esp32-server)
<h1 align="center">小智后端服务xiaozhi-esp32-server</h1>
<p align="center"> <p align="center">
本项目为开源智能硬件项目
<a href="https://github.com/78/xiaozhi-esp32">xiaozhi-esp32</a>提供后端服务<br/>
根据<a href="https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh">小智通信协议</a>使用Python、Java、Vue实现<br/>
帮助您快速搭建小智服务器
</p>
<p align="center">
<a href="./README.md">English</a>
· 简体中文
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">更新日志</a>
· <a href="./README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3">部署文档</a>
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">反馈问题</a>
· <a href="./docs/FAQ.md">常见问题</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"> <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" /> <img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/xinnan-tech/xiaozhi-esp32-server?logo=github" />
</a> </a>
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues"> <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" /> <img alt="Issues" src="https://img.shields.io/github/issues/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
@@ -9,15 +31,14 @@
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/pulls"> <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" /> <img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
</a> </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> </p>
# 小智 ESP-32 后端服务(xiaozhi-esp32-server)
(中文 | [English](README_en.md)
本项目为开源智能硬件项目 [xiaozhi-esp32](https://github.com/78/xiaozhi-esp32)
提供后端服务。根据 [小智通信协议](https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh) 使用 `Python` 实现。
--- ---
## 适用人群 👥 ## 适用人群 👥
@@ -51,7 +72,7 @@
</a> </a>
</td> </td>
<td> <td>
<a href="https://www.bilibili.com/video/av114036381327149" target="_blank"> <a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
<picture> <picture>
<img alt="控制家电开关" src="docs/images/demo5.png" /> <img alt="控制家电开关" src="docs/images/demo5.png" />
</picture> </picture>
@@ -95,31 +116,17 @@
</a> </a>
</td> </td>
<td> <td>
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
<picture>
<img alt="播报新闻" src="docs/images/demo0.png" />
</picture>
</a>
</td> </td>
</tr> </tr>
</table> </table>
--- ---
## 系统要求与部署前提 🖥️
- **硬件**:一套兼容 `xiaozhi-esp32`
的硬件设备(具体型号请参考 [此处](https://rcnv1t9vps13.feishu.cn/wiki/DdgIw4BUgivWDPkhMj1cGIYCnRf))。
- **电脑或服务器**:建议 4 核 CPU、8G 内存的电脑。如果开启ASR也使用API,可运行在2核CPU、2G内存的服务器中。
- **固件编译**:请将本后端服务的接口地址更新至 `xiaozhi-esp32` 项目中,再重新编译`xiaozhi-esp32`固件并烧录到设备上。
![图片](docs/images/deploy.png)
如果你没有esp32相关的硬件设备,但是非常想体验该项目,可以使用以下的项目让你的电脑、手机模拟成esp32设备。
- [小智安卓端](https://github.com/TOM88812/xiaozhi-android-client)
- [小智电脑端](https://github.com/Huang-junsen/py-xiaozhi)
如果你是一名软件开发者,这里有一份[《致开发者的公开信》](docs/contributor_open_letter.md),欢迎加入!
---
## 警告 ⚠️ ## 警告 ⚠️
1、本项目为开源软件,本软件与对接的任何第三方API服务商(包括但不限于语音识别、大模型、语音合成等平台)均不存在商业合作关系,不为其服务质量及资金安全提供任何形式的担保。 1、本项目为开源软件,本软件与对接的任何第三方API服务商(包括但不限于语音识别、大模型、语音合成等平台)均不存在商业合作关系,不为其服务质量及资金安全提供任何形式的担保。
@@ -139,53 +146,79 @@ server:
--- ---
## 部署文档
![Banners](docs/images/banner2.png)
本项目提供两种部署方式,请根据您的具体需求选择:
#### 🚀 部署方式选择
| 部署方式 | 特点 | 适用场景 | 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个,每天会清空数据
```
智控台地址: https://2662r3426b.vicp.fun
OTA接口地址: https://2662r3426b.vicp.fun/xiaozhi/ota/
Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
```
---
## 常见问题 ❓
如遇到问题或产品建议反馈[点这里](docs/FAQ.md)。
---
## 功能清单 ✨ ## 功能清单 ✨
### 已实现 ✅ ### 已实现 ✅
- **通信协议** | 功能模块 | 描述 |
基于 `xiaozhi-esp32` 协议,通过 WebSocket 实现数据交互。 |---------|------|
- **对话交互** | 通信协议 | 基于 `xiaozhi-esp32` 协议,通过 WebSocket 实现数据交互 |
支持唤醒对话、手动对话及实时打断。长时间无对话时自动休眠 | 对话交互 | 支持唤醒对话、手动对话及实时打断。长时间无对话时自动休眠 |
- **意图识别** | 意图识别 | 支持使用LLM意图识别、function call函数调用,减少硬编码意图判断 |
支持使用LLM意图识别、function call函数调用,减少硬编码意图判断 | 多语言识别 | 支持国语、粤语、英语、日语、韩语(默认使用 FunASR) |
- **多语言识别** | LLM 模块 | 支持灵活切换 LLM 模块,默认使用 ChatGLMLLM,也可选用阿里百炼、DeepSeek、Ollama 等接口 |
支持国语、粤语、英语、日语、韩语(默认使用 FunASR)。 | TTS 模块 | 支持 EdgeTTS(默认)、火山引擎豆包 TTS 等多种 TTS 接口,满足语音合成需求 |
- **LLM 模块** | 记忆功能 | 支持超长记忆、本地总结记忆、无记忆三种模式,满足不同场景需求 |
支持灵活切换 LLM 模块,默认使用 ChatGLMLLM,也可选用阿里百炼、DeepSeek、Ollama 等接口。 | IOT功能 | 支持管理注册设备IOT功能,支持基于对话上下文语境下的智能物联网控制 |
- **TTS 模块** | 智控台 | 提供Web管理界面,支持智能体管理、用户管理、系统配置等功能,方便管理员和用户进行管理 |
支持 EdgeTTS(默认)、火山引擎豆包 TTS 等多种 TTS 接口,满足语音合成需求。
- **记忆功能**
支持超长记忆、本地总结记忆、无记忆三种模式,满足不同场景需求。
- **IOT功能**
支持管理注册设备IOT功能,支持基于对话上下文语境下的智能物联网控制。
### 正在开发 🚧 ### 正在开发 🚧
- 多种心情模式 想了解具体开发计划进度,[请点击这里](https://github.com/users/xinnan-tech/projects/3)
- 智控台webui
想了解具体开发进度,[请点击这里](https://github.com/users/xinnan-tech/projects/3) 如果你是一名软件开发者,这里有一份[《致开发者的公开信》](docs/contributor_open_letter.md),欢迎加入!
---
## 产品生态 👬
小智是一个生态,当你使用这个产品时,也可以看看其他在这个生态圈的优秀项目
| 项目名称 | 项目地址 | 项目描述 |
|:---------------------|:--------|:--------|
| 小智安卓客户端 | [xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) | 一个基于xiaozhi-server的Android、IOS语音对话应用,支持实时语音交互和文字对话。<br/>现在是flutter版本,打通IOS、Android端。 |
| 小智电脑客户端 | [py-xiaozhi](https://github.com/Huang-junsen/py-xiaozhi) | 该项目提供了一个基于 Python 实现的小白 AI 客户端,使得在不具备实体硬件条件的情况下,<br/>依然能够体过代码体验小智 AI 的功能。 |
| 小智Java服务端 | [xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) | 小智开源后端服务 Java 版本是一个基于 Java 的开源项目。<br/>它包括前后端的服务,旨在为用户提供一个完整的后端服务解决方案。 |
![图片](docs/images/webui.png)
--- ---
## 本项目支持的平台/组件列表 📋 ## 本项目支持的平台/组件列表 📋
### LLM 语言模型 ### LLM 语言模型
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 | | 使用方式 | 支持平台 | 免费平台 |
|:---:|:------------------:|:---------------------:|:-----------:|:-----------------------------------------------------------------------------------------------------------------------:| |:---:|:---:|:---:|
| LLM | 阿里百炼 (AliLLM) | openai 接口调用 | 消耗 token | [点击申请密钥](https://bailian.console.aliyun.com/?apiKey=1#/api-key) | | openai 接口调用 | 阿里百炼、火山引擎豆包、深度求索、智谱ChatGLM、Gemini | 智谱ChatGLM、Gemini |
| LLM | DoubaoLLM | openai 接口调用 | 消耗 token | [点击申请密钥](https://console.volcengine.com/ark/region:ark+cn-beijing/model/detail?Id=doubao-pro-32k&projectName=undefined) | | ollama 接口调用 | Ollama | - |
| LLM | 深度求索 (DeepSeekLLM) | openai 接口调用 | 消耗 token | [点击申请密钥](https://platform.deepseek.com/) | | dify 接口调用 | Dify | - |
| LLM | 智谱(ChatGLMLLM | openai 接口调用 | 免费 | 虽然免费,仍需[点击申请密钥](https://bigmodel.cn/usercenter/proj-mgmt/apikeys) | | fastgpt 接口调用 | Fastgpt | - |
| LLM | OllamaLLM | ollama 接口调用 | 免费/消耗 token | 需预先下载模型(`ollama pull`),服务地址:`http://localhost:11434` | | coze 接口调用 | Coze | - |
| LLM | DifyLLM | dify 接口调用 | 免费/消耗 token | 本地化部署,注意配置提示词需在 Dify 控制台设置 |
| LLM | FastgptLLM | fastgpt 接口调用 | 免费/消耗 token | 本地化部署,注意配置提示词需在 Fastgpt 控制台设置 |
| LLM | GeminiLLM | gemini 接口调用 | 免费 | [点击申请密钥](https://aistudio.google.com/apikey) |
| LLM | CozeLLM | coze 接口调用 | 消耗 token | 需提供 bot_id、user_id 及个人令牌 |
| LLM | Home Assistant | homeassistant语音助手接口调用 | 免费 | 需提供home assistant令牌 |
实际上,任何支持 openai 接口调用的 LLM 均可接入使用。 实际上,任何支持 openai 接口调用的 LLM 均可接入使用。
@@ -193,20 +226,10 @@ server:
### TTS 语音合成 ### TTS 语音合成
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 | | 使用方式 | 支持平台 | 免费平台 |
|:---:|:----------------------:|:----:|:--------:|:-------------------------------------------------------------------------:| |:---:|:---:|:---:|
| TTS | EdgeTTS | 接口调用 | 免费 | 默认 TTS,基于微软语音合成技术 | | 接口调用 | EdgeTTS、火山引擎豆包TTS、腾讯云、阿里云TTS、CosyVoiceSiliconflow、TTS302AI、CozeCnTTS、GizwitsTTS、ACGNTTS、OpenAITTS | EdgeTTS、CosyVoiceSiliconflow(部分) |
| TTS | 火山引擎豆包 TTS (DoubaoTTS) | 接口调用 | 消耗 token | [点击创建密钥](https://console.volcengine.com/speech/service/8);建议使用付费版本以获得更高并发 | | 本地服务 | FishSpeech、GPT_SOVITS_V2、GPT_SOVITS_V3、MinimaxTTS | FishSpeech、GPT_SOVITS_V2、GPT_SOVITS_V3、MinimaxTTS |
| TTS | AliyunTTS | 接口调用 | 消耗 token | [点击创建密钥](https://nls-portal.console.aliyun.com/applist) |
| TTS | CosyVoiceSiliconflow | 接口调用 | 消耗 token | 需申请硅基流动 API 密钥;输出格式为 wav |
| TTS | TTS302AI | 接口调用 | 消耗 token | [点击创建密钥](https://dash.302.ai/apis/list) |
| TTS | CozeCnTTS | 接口调用 | 消耗 token | 需提供 Coze API key;输出格式为 wav |
| TTS | ACGNTTS | 接口调用 | 消耗 token | [联系网站管理员购买密钥](www.ttson.cn) |
| TTS | OpenAITTS | 接口调用 | 消耗 token | 境外使用,境外购买 |
| TTS | FishSpeech | 接口调用 | 免费/自定义 | 本地启动 TTS 服务;启动方法见配置文件内说明 |
| TTS | GPT_SOVITS_V2 | 接口调用 | 免费/自定义 | 本地启动 TTS 服务,适用于个性化语音合成场景 |
| TTS | GPT_SOVITS_V3 | 接口调用 | 免费/自定义 | 本地启动 TTS 服务,适用于个性化语音合成场景 |
| TTS | MinimaxTTS | 接口调用 | 免费/自定义 | 本地启动 TTS 服务,适用于个性化语音合成场景 |
--- ---
@@ -220,10 +243,10 @@ server:
### ASR 语音识别 ### ASR 语音识别
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 | | 使用方式 | 支持平台 | 免费平台 |
|:---:|:---------:|:----:|:----:|:--:| |:---:|:---:|:---:|
| ASR | FunASR | 本地使用 | 免费 | | | 本地使用 | FunASR、SherpaASR | FunASR、SherpaASR |
| ASR | DoubaoASR | 接口调用 | 收费 | | | 接口调用 | DoubaoASR | - |
--- ---
@@ -245,148 +268,19 @@ server:
--- ---
## 使用方式 🚀
### 一、[部署文档](./docs/Deployment.md)
本项目支持以下三种部署方式,您可根据实际需求选择。
1. [Docker 快速部署](./docs/Deployment.md)
适合快速体验的普通用户,不需过多环境配置。缺点是,拉取镜像有点慢。视频教程可参考[美女大佬教你Docker部署](https://www.bilibili.com/video/BV1RNQnYDE5t)
2. [借助 Docker 环境运行部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E5%80%9F%E5%8A%A9docker%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2)
适用于已安装 Docker 且希望对代码进行自定义修改的软件工程师。
3. [本地源码运行](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%89%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C)
适合熟悉`Conda` 环境或希望从零搭建运行环境的用户。
对于对响应速度要求较高的场景,推荐使用本地源码运行方式以降低额外开销。视频教程可参考[帅哥大佬教你源码部署](https://www.bilibili.com/video/BV1GvQWYZEd2)
### 二、[固件编译](./docs/firmware-build.md)
点这里查看[固件编译](./docs/firmware-build.md)的详细过程。
烧录成功且联网成功后,通过唤醒词唤醒小智,留意server端输出的控制台信息。
---
## 常见问题 ❓
### 1、为什么我说的话,小智识别出来很多韩文、日文、英文?🇰🇷
建议:检查一下`models/SenseVoiceSmall`是否已经有`model.pt`
文件,如果没有就要下载,查看这里[下载语音识别模型文件](docs/Deployment.md#模型文件)
### 2、为什么会出现“TTS 任务出错 文件不存在”?📁
建议:检查一下是否正确使用`conda` 安装了`libopus``ffmpeg`库。
如果没有安装,就安装
```
conda install conda-forge::libopus
conda install conda-forge::ffmpeg
```
### 3、TTS 经常失败,经常超时 ⏰
建议:如果 `EdgeTTS` 经常失败,请先检查是否使用了代理(梯子)。如果使用了,请尝试关闭代理后再试;
如果用的是火山引擎的豆包 TTS,经常失败时建议使用付费版本,因为测试版本仅支持 2 个并发。
### 4、如何提高小智对话响应速度? ⚡
本项目默认配置为低成本方案,建议初学者先使用默认免费模型,解决“跑得动”的问题,再优化“跑得快”。
如需提升响应速度,可尝试更换各组件。以下为各组件的响应速度测试数据(仅供参考,不构成承诺):
| 影响因素 | 因素值 |
|:-----:|:----------------:|
| 测试地点 | 广东省广州市海珠区 |
| 测试时间 | 2025年2月19日 12:52 |
| 宽带运营商 | 中国联通 |
测试方法:
1、把各组件的密钥配置上去,只有配置了密钥的组件才参与测试。
2、配置完密钥后,执行以下方法
```
# 进入项目根目录,执行以下命令:
conda activate xiaozhi-esp32-server
python performance_tester.py
```
生成报告如下
LLM 性能排行:
| 模块名称 | 平均首Token时间 | 平均总响应时间 |
|:-----------|:-----------|:--------|
| AliLLM | 0.547s | 1.485s |
| ChatGLMLLM | 0.677s | 3.057s |
TTS 性能排行:
| 模块名称 | 平均合成时间 |
|----------------------|--------|
| EdgeTTS | 1.019s |
| DoubaoTTS | 0.503s |
| CosyVoiceSiliconflow | 3.732s |
推荐配置组合 (综合响应速度):
| 组合方案 | 综合得分 | LLM首Token | TTS合成 |
|-------------------------------|-------|-----------|--------|
| AliLLM + DoubaoTTS | 0.539 | 0.547s | 0.503s |
| AliLLM + EdgeTTS | 0.642 | 0.547s | 1.019s |
| ChatGLMLLM + DoubaoTTS | 0.642 | 0.677s | 0.503s |
| ChatGLMLLM + EdgeTTS | 0.745 | 0.677s | 1.019s |
| AliLLM + CosyVoiceSiliconflow | 1.184 | 0.547s | 3.732s |
### 结论 🔍
`2025年2月19日`,如果我的电脑在`广东省广州市海珠区`,且使用的是`中国联通`网络,我会优先使用:
- LLM`AliLLM`
- TTS`DoubaoTTS`
### 5、我说话很慢,停顿时小智老是抢话 🗣️
建议:在配置文件中找到如下部分,将 `min_silence_duration_ms` 的值调大(例如改为 `1000`):
```yaml
VAD:
SileroVAD:
threshold: 0.5
model_dir: models/snakers4_silero-vad
min_silence_duration_ms: 700 # 如果说话停顿较长,可将此值调大
```
### 6、我想通过小智控制电灯、空调、远程开关机等操作 💡
建议:在配置文件中将 `LLM` 设置为 `HomeAssistant`,通过 调用`HomeAssistant`接口实现相关控制。
### 7、更多问题,可联系我们反馈 💬
我们的联系方式放在[百度网盘中,点击前往](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),提取码是`223y`
网盘里有“硬件烧录QQ群”、“开源服务端交流群”、“产品建议联系人” 三张图片,请根据需要选择加入。
- 硬件烧录QQ群:适用于硬件烧录问题
- 开源服务端交流群:适用于服务端问题
- 产品建议联系人:适用于产品功能、产品设计等建议
---
## 鸣谢 🙏 ## 鸣谢 🙏
- 本项目受 [百聆语音对话机器人](https://github.com/wwbin2017/bailing) 启发,并在其基础上实现。 | Logo | 项目/公司 | 说明 |
- 感谢 [十方融海](https://www.tenclass.com/) 对小智通讯协议提供的详尽文档支持。 |:---:|:---:|:---|
| <img src="./docs/images/logo_bailing.png" width="160"> | [百聆语音对话机器人](https://github.com/wwbin2017/bailing) | 本项目受[百聆语音对话机器人](https://github.com/wwbin2017/bailing)启发,并在其基础上实现 |
| <img src="./docs/images/logo_tenclass.png" width="160"> | [十方融海](https://www.tenclass.com/) | 感谢[十方融海](https://www.tenclass.com/)为小智生态制定了标准的通讯协议、多设备兼容性方案及高并发场景实践示范;为本项目提供了全链路技术文档支持 |
| <img src="./docs/images/logo_xuanfeng.png" width="160"> | [玄凤科技](https://github.com/Eric0308) | 感谢[玄凤科技](https://github.com/Eric0308)贡献函数调用框架、MCP通信协议及插件化调用机制的实现代码,通过标准化的指令调度体系与动态扩展能力,显著提升了前端设备(IoT)的交互效率和功能延展性 |
| <img src="./docs/images/logo_huiyuan.png" width="160"> | [汇远设计](http://ui.kwd988.net/) | 感谢[汇远设计](http://ui.kwd988.net/)为本项目提供专业视觉解决方案,用其服务超千家企业的设计实战经验,赋能本项目产品用户体验 |
| <img src="./docs/images/logo_qinren.png" width="160"> | [西安勤人信息科技](https://www.029app.com/) | 感谢[西安勤人信息科技](https://www.029app.com/)深化本项目视觉体系,确保整体设计风格在多场景应用中的一致性和扩展性 |
<a href="https://star-history.com/#xinnan-tech/xiaozhi-esp32-server&Date"> <a href="https://star-history.com/#xinnan-tech/xiaozhi-esp32-server&Date">
<picture> <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: 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" /> <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
-296
View File
@@ -1,296 +0,0 @@
[![SVG Banners](https://svg-banners.vercel.app/api?type=origin&text1=Hello😃,%20XiaoZhi📟&text2=Open%20Source%20XiaoZhi%20ESP-32%20Backend%20Service&width=830&height=210)](https://github.com/xinnan-tech/xiaozhi-esp32-server)
<p align="center">
<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" />
</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>
</p>
# XiaoZhi ESP-32 Backend Service (xiaozhi-esp32-server)
([中文](README.md) | English)
This project provides the backend service for the open source smart hardware project [xiaozhi-esp32](https://github.com/78/xiaozhi-esp32). It is implemented in `Python` based on the [XiaoZhi Communication Protocol](https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh).
---
## Target Audience 👥
This project is designed to be used in conjunction with ESP32 hardware devices. If you have already purchased an ESP32 device, successfully connected to the backend service deployed by XieGe, and now wish to set up your own `xiaozhi-esp32` backend service, then this project is perfect for you.
Want to see it in action? Check out the videos 🎥
<table>
<tr>
<td>
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
<picture>
<img alt="XiaoZhi ESP32 connecting to a custom 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="Conversing in Cantonese" src="docs/images/demo3.png" />
</picture>
</a>
</td>
<td>
<a href="https://www.bilibili.com/video/av114036381327149" 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>
<td>
</td>
</tr>
</table>
---
## System Requirements and Deployment Prerequisites 🖥️
- **Hardware**: A set of devices compatible with `xiaozhi-esp32` (for specific models, please refer to [this link](https://rcnv1t9vps13.feishu.cn/wiki/DdgIw4BUgivWDPkhMj1cGIYCnRf)).
- **Server**: A computer with at least a 4-core CPU and 8GB of memory.
- **Firmware Compilation**: Please update the backend service API endpoint in the `xiaozhi-esp32` project, then recompile the firmware and flash it to your device.
---
## Warning ⚠️
This project is relatively new and has not yet undergone network security evaluations. **Do not use it in a production environment.**
If you deploy this project on a public network for learning purposes, be sure to enable protection in the configuration file `config.yaml`:
```yaml
server:
auth:
# Enable protection
enabled: true
```
Once protection is enabled, you will need to validate the machine's token or MAC address based on your actual situation. Please refer to the configuration documentation for details.
---
## Feature List ✨
### Implemented ✅
- **Communication Protocol**
Based on the `xiaozhi-esp32` protocol, data exchange is implemented via WebSocket.
- **Dialogue Interaction**
Supports wake-up dialogues, manual conversations, and real-time interruptions. Automatically enters sleep mode after long periods of inactivity.
- **Multilingual Recognition**
Supports Mandarin, Cantonese, English, Japanese, and Korean (default using FunASR).
- **LLM Module**
Allows flexible switching of LLM modules. The default is ChatGLMLLM, with options to use AliLLM, DeepSeek, Ollama, and others.
- **TTS Module**
Supports multiple TTS interfaces including EdgeTTS (default) and Volcano Engine Doubao TTS to meet speech synthesis requirements.
### In Development 🚧
- Conversation Memory Feature
- Multiple Mood Modes
- Smart Control Panel Web UI
![Screenshot](docs/images/webui.png)
---
## Supported Platforms/Components 📋
### LLM
| Type | Platform Name | Usage Method | Pricing Model | Remarks |
|:----:|:-----------------------------:|:-----------------------------:|:-----------------:|:-------------------------------------------------------------------------:|
| LLM | AliLLM (阿里百炼) | OpenAI API call | Token consumption | [Click to apply for API key](https://bailian.console.aliyun.com/?apiKey=1#/api-key) |
| LLM | DeepSeekLLM (深度求索) | OpenAI API call | Token consumption | [Click to apply for API key](https://platform.deepseek.com/) |
| LLM | ChatGLMLLM (智谱) | OpenAI API call | Free | Although free, you still need to [click to apply for an API key](https://bigmodel.cn/usercenter/proj-mgmt/apikeys) |
| LLM | OllamaLLM | Ollama API call | Free/Custom | Requires pre-downloading the model (`ollama pull`); service URL: `http://localhost:11434` |
| LLM | DifyLLM | Dify API call | Token consumption | For local deployment. Note that prompt configuration must be set in the Dify console. |
| LLM | GeminiLLM | Gemini API call | Free | [Click to apply for API key](https://aistudio.google.com/apikey) |
| LLM | CozeLLM | Coze API call | Token consumption | Requires providing bot_id, user_id, and personal token. |
| LLM | Home Assistant | Home Assistant voice assistant API call | Free | Requires providing a Home Assistant token. |
In fact, any LLM that supports OpenAI API calls can be integrated.
---
### TTS
| Type | Platform Name | Usage Method | Pricing Model | Remarks |
|:----:|:--------------------------------------:|:------------:|:-----------------:|:--------------------------------------------------------------------------------------:|
| TTS | EdgeTTS | API call | Free | Default TTS based on Microsoft's speech synthesis technology. |
| TTS | DoubaoTTS (火山引擎豆包 TTS) | API call | Token consumption | [Click to create an API key](https://console.volcengine.com/speech/service/8); it is recommended to use the paid version for higher concurrency. |
| TTS | CosyVoiceSiliconflow | API call | Token consumption | Requires application for the Siliconflow API key; output format is WAV. |
| TTS | CozeCnTTS | API call | Token consumption | Requires providing a Coze API key; output format is WAV. |
| TTS | FishSpeech | API call | Free/Custom | Starts a local TTS service; see the configuration file for startup instructions. |
| TTS | GPT_SOVITS_V2 | API call | Free/Custom | Starts a local TTS service, suitable for personalized speech synthesis scenarios. |
---
### VAD
| Type | Platform Name | Usage Method | Pricing Model | Remarks |
|:----:|:-------------------:|:------------:|:-------------:|:-------:|
| VAD | SileroVAD | Local | Free | |
---
### ASR
| Type | Platform Name | Usage Method | Pricing Model | Remarks |
|:----:|:-------------------:|:------------:|:-------------:|:-------:|
| ASR | FunASR | Local | Free | |
| ASR | DoubaoASR | API call | Paid | |
---
## Usage 🚀
### 1. [Deployment Documentation](./docs/Deployment.md)
This project supports three deployment methods. Choose the one that best fits your needs.
The documentation provided here is a **written tutorial**. If you prefer a **video tutorial**, you can refer to [this expert's hands-on guide](https://www.bilibili.com/video/BV1gePuejEvT).
Combining both the written and video tutorials can help you get started more quickly.
1. [Docker Quick Deployment](./docs/Deployment.md)
Suitable for general users who want a quick experience without extensive environment configuration. The only downside is that pulling the image can be a bit slow.
2. [Deployment Using Docker Environment](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E5%80%9F%E5%8A%A9docker%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2)
Ideal for software engineers who already have Docker installed and wish to customize the code.
3. [Running from Local Source Code](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%89%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C)
Suitable for users familiar with the `Conda` environment or those who wish to build the runtime environment from scratch.
For scenarios requiring higher response speeds, running from the local source code is recommended to reduce additional overhead.
### 2. [Firmware Compilation](./docs/firmware-build.md)
Click [here](./docs/firmware-build.md) for a detailed guide on firmware compilation.
After successful compilation and network connection, wake up XiaoZhi using the wake-up word and monitor the server console for output.
---
## Frequently Asked Questions ❓
### 1. TTS often fails and times out ⏰
**Suggestion:**
If `EdgeTTS` frequently fails, please first check whether you are using a proxy (VPN). If so, try disabling the proxy and try again. If you are using Volcano Engine Doubao TTS and it often fails, it is recommended to use the paid version since the trial only supports 2 concurrent requests.
### 2. I want to control lights, air conditioners, remote power on/off, etc. with XiaoZhi 💡
**Suggestion:**
Set the `LLM` to `HomeAssistant` in the configuration file and use the `HomeAssistant` API to perform the relevant controls.
### 3. I speak slowly, and XiaoZhi always interrupts during pauses 🗣️
**Suggestion:**
Locate the following section in the configuration file and increase the value of `min_silence_duration_ms` (for example, change it to `1000`):
```yaml
VAD:
SileroVAD:
threshold: 0.5
model_dir: models/snakers4_silero-vad
min_silence_duration_ms: 700 # If your pauses are longer, increase this value
```
### 4. Why does XiaoZhi recognize a lot of Korean, Japanese, and English in what I say? 🇰🇷
**Suggestion:**
Check whether the `model.pt` file exists in the `models/SenseVoiceSmall` directory. If it does not, please download it. See [Download ASR Model Files](docs/Deployment.md#模型文件) for details.
### 5. Why does the error “TTS task error: file does not exist” occur? 📁
**Suggestion:**
Verify that you have correctly installed the `libopus` and `ffmpeg` libraries using `conda`. If not, install them using:
```
conda install conda-forge::libopus
conda install conda-forge::ffmpeg
```
### 6. How can I improve XiaoZhi's dialogue response speed? ⚡
The default configuration of this project is designed to be cost-effective. It is recommended that beginners first use the default free models to ensure that the system runs smoothly, then optimize for faster response times.
To improve response speed, you can try replacing individual components. Below are the response time test results for each component (for reference only, not a guarantee):
**LLM Performance Ranking:**
| Module Name | Average First Token Time | Average Total Response Time |
|--------------|--------------------------|-----------------------------|
| AliLLM | 0.547s | 1.485s |
| ChatGLMLLM | 0.677s | 3.057s |
| OllamaLLM | 0.003s | 0.003s |
**TTS Performance Ranking:**
| Module Name | Average Synthesis Time |
|----------------------------|------------------------|
| EdgeTTS | 1.019s |
| DoubaoTTS | 0.503s |
| CosyVoiceSiliconflow | 3.732s |
**Recommended Configuration Combination (Overall Response Speed):**
| Combination Scheme | Overall Score | LLM First Token | TTS Synthesis |
|-----------------------------------|---------------|-----------------|---------------|
| AliLLM + DoubaoTTS | 0.539 | 0.547s | 0.503s |
| AliLLM + EdgeTTS | 0.642 | 0.547s | 1.019s |
| ChatGLMLLM + DoubaoTTS | 0.642 | 0.677s | 0.503s |
| ChatGLMLLM + EdgeTTS | 0.745 | 0.677s | 1.019s |
| AliLLM + CosyVoiceSiliconflow | 1.184 | 0.547s | 3.732s |
**Conclusion 🔍**
_As of February 19, 2025, if my computer were located in Haizhu District, Guangzhou, Guangdong Province, and connected via China Unicom, I would prioritize using:_
- **LLM:** `AliLLM`
- **TTS:** `DoubaoTTS`
### 7. For more questions, feel free to contact us for feedback 💬
Our contact information is in [Baidu Netdisk](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q)The extraction code is`223y`
---
## Acknowledgements 🙏
- This project was inspired by the [Bailing Voice Dialogue Robot](https://github.com/wwbin2017/bailing) and implemented based on it.
- Many thanks to [Tenclass](https://www.tenclass.com/) for providing detailed documentation support for the XiaoZhi communication protocol.
<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>
+31 -57
View File
@@ -1,6 +1,6 @@
# 部署方案参考 # 部署架构图
![图片](images/deploy.png) ![请参考-最简化架构图](../docs/images/deploy1.png)
# 方式一:docker快速部署 # 方式一:Docker只运行Server
docker镜像已支持x86架构、arm64架构的CPU,支持在国产操作系统上运行。 docker镜像已支持x86架构、arm64架构的CPU,支持在国产操作系统上运行。
@@ -40,7 +40,7 @@ chmod +x docker-setup.sh
> >
> 执行完成后,请按照提示配置 API 密钥。 > 执行完成后,请按照提示配置 API 密钥。
当你一切顺利完成以上操作后,继续操作[配置项目文件](#3-配置项目文件) 当你一切顺利完成以上操作后,继续操作[配置项目文件](#2-配置项目文件)
### 1.2 手动部署 ### 1.2 手动部署
@@ -102,14 +102,14 @@ xiaozhi-server
如果你的文件目录结构也是上面的,就继续往下。如果不是,你就再仔细看看是不是漏操作了什么。 如果你的文件目录结构也是上面的,就继续往下。如果不是,你就再仔细看看是不是漏操作了什么。
## 3. 配置项目文件 ## 2. 配置项目文件
接下里,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程: 接下里,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程:
[跳转到配置项目文件](#配置项目) [跳转到配置项目文件](#配置项目)
配置完项目文件后,回到本教程继续往下。 配置完项目文件后,回到本教程继续往下。
## 4. 执行docker命令 ## 3. 执行docker命令
打开命令行工具,使用`终端``命令行`工具 进入到你的`xiaozhi-server`,执行以下命令 打开命令行工具,使用`终端``命令行`工具 进入到你的`xiaozhi-server`,执行以下命令
@@ -137,50 +137,15 @@ docker logs -f xiaozhi-esp32-server
``` ```
docker stop xiaozhi-esp32-server docker stop xiaozhi-esp32-server
docker rm xiaozhi-esp32-server docker rm xiaozhi-esp32-server
docker stop xiaozhi-esp32-server-web
docker rm xiaozhi-esp32-server-web
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
``` ```
5.3、重新按docker方式部署 5.3、重新按docker方式部署
# 方式二:借助Docker环境运行部署 # 方式二:本地源码只运行Server
开发人员如果不想安装`conda`环境,可以使用这种方法管理好依赖。
## 1.克隆项目
## 2.[跳转到下载语音识别模型文件](#模型文件)
## 3.[跳转到配置项目文件](#配置项目)
## 4.运行docker
修改完配置后,打开命令行工具,`cd`进入到你的`main/xiaozhi-server`下,执行以下命令
```sh
docker run -it --name xiaozhi-env --restart always --security-opt seccomp:unconfined \
-p 8000:8000 \
-p 8002:8002 \
-v ./:/app \
kalicyh/python:xiaozhi
```
然后就和正常开发一样了
## 5.安装依赖
在刚刚的打开的终端运行
```sh
pip install -r requirements.txt
```
## 6.运行项目
```sh
python app.py
```
# 方式三:本地源码运行
## 1.安装基础环境 ## 1.安装基础环境
@@ -241,7 +206,7 @@ pip install -r requirements.txt
## 4.配置项目文件 ## 4.配置项目文件
接下,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程: 接下,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程:
[跳转到配置项目文件](#配置项目) [跳转到配置项目文件](#配置项目)
## 5.运行项目 ## 5.运行项目
@@ -259,7 +224,7 @@ python app.py
## 配置项目 ## 配置项目
如果你的`xiaozhi-server`目录没有`data`,你需要创建`data`目录。 如果你的`xiaozhi-server`目录没有`data`,你需要创建`data`目录。
如果你的`data`下面没有`.config.yaml`文件,你可以把源码目录下的`config.yaml`文件复制一份,重命名为`.config.yaml` 如果你的`data`下面没有`.config.yaml`文件,你可以把`xiaozhi-server`目录下的`config.yaml`文件复制`data`,并重命名为`.config.yaml`
修改`xiaozhi-server``data`目录下的`.config.yaml`文件,配置本项目必须的一个配置。 修改`xiaozhi-server``data`目录下的`.config.yaml`文件,配置本项目必须的一个配置。
@@ -311,31 +276,40 @@ LLM:
如果你能看到,类似以下日志,则是本项目服务启动成功的标志。 如果你能看到,类似以下日志,则是本项目服务启动成功的标志。
``` ```
25-02-23 12:01:09[core.websocket_server] - INFO - Server is running at ws://xxx.xx.xx.xx:8000 250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-OTA接口是 http://192.168.4.123:8002/xiaozhi/ota/
25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问======= 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请用谷歌浏览器打开test目录下的test_page.html
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-=======================================================
``` ```
正常来说,如果您是通过源码运行本项目,日志会有你的接口地址信息。 正常来说,如果您是通过源码运行本项目,日志会有你的接口地址信息。
但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。 但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。
最正确的方法,是根据电脑的局域网IP来确定你的接口地址。 最正确的方法,是根据电脑的局域网IP来确定你的接口地址。
如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000` 如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000/xiaozhi/v1/`,对应的OTA地址就是:`http://192.168.1.25:8002/xiaozhi/ota/`
这个信息很有用的,后面`编译esp32固件`需要用到。 这个信息很有用的,后面`编译esp32固件`需要用到。
接下来,你就可以开始 [编译esp32固件](firmware-build.md)了。 接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个
1、 [编译自己的esp32固件](firmware-build.md)了。
2、 [基于虾哥编译好的固件配置自定义服务器](firmware-setting.md)了。
以下是一些常见问题,供参考: 以下是一些常见问题,供参考:
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) [1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md)
[2、为什么会出现“TTS 任务出错 文件不存在”?](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) [2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md)
[3、TTS 经常失败,经常超时](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) [3、TTS 经常失败,经常超时](./FAQ.md)
[4、如何提高小智对话响应速度?](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) [4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md)
[5、我说话很慢,停顿时小智老是抢话](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) [5、如何提高小智对话响应速度?](./FAQ.md)
[6、我想通过小智控制电灯、空调、远程开关机等操作](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) [6、我说话很慢,停顿时小智老是抢话](./FAQ.md)
[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md)
+449
View File
@@ -0,0 +1,449 @@
# 部署架构图
![请参考-全模块安装架构图](../docs/images/deploy2.png)
# 方式一:Docker运行全模块
docker镜像已支持x86架构、arm64架构的CPU,支持在国产操作系统上运行。
## 1. 安装docker
如果您的电脑还没安装docker,可以按照这里的教程安装:[docker安装](https://www.runoob.com/docker/ubuntu-docker-install.html)
#### 1.1 创建目录
安装完后,你需要为这个项目找一个安放配置文件的目录,例如我们可以新建一个文件夹叫`xiaozhi-server`
创建好目录后,你需要在`xiaozhi-server`下面创建`data`文件夹和`models`文件夹,`models`下面还要再创建`SenseVoiceSmall`文件夹。
最终目录结构如下所示:
```
xiaozhi-server
├─ data
├─ models
├─ SenseVoiceSmall
```
#### 1.2 下载语音识别模型文件
本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt`
文件放在`models/SenseVoiceSmall`
目录下。下面两个下载路线任选一个。
- 线路一:阿里魔塔下载[SenseVoiceSmall](https://modelscope.cn/models/iic/SenseVoiceSmall/resolve/master/model.pt)
- 线路二:百度网盘下载[SenseVoiceSmall](https://pan.baidu.com/share/init?surl=QlgM58FHhYv1tFnUT_A8Sg&pwd=qvna) 提取码:
`qvna`
#### 1.3 下载配置文件
你需要下载两个配置文件:`docker-compose_all.yaml``config_from_api.yaml`。需要从项目仓库下载这两个文件。
##### 1.3.1 下载 docker-compose_all.yaml
用浏览器打开[这个链接](../main/xiaozhi-server/docker-compose_all.yml)。
在页面的右侧找到名称为`RAW`按钮,在`RAW`按钮的旁边,找到下载的图标,点击下载按钮,下载`docker-compose_all.yml`文件。 把文件下载到你的
`xiaozhi-server`中。
或者直接执行 `wget https://raw.githubusercontent.com/xinnan-tech/xiaozhi-esp32-server/refs/heads/main/main/xiaozhi-server/docker-compose_all.yml` 下载。
下载完后,回到本教程继续往下。
##### 1.3.2 下载 config_from_api.yaml
用浏览器打开[这个链接](../main/xiaozhi-server/config_from_api.yaml)。
在页面的右侧找到名称为`RAW`按钮,在`RAW`按钮的旁边,找到下载的图标,点击下载按钮,下载`config_from_api.yaml`文件。 把文件下载到你的
`xiaozhi-server`下面的`data`文件夹中,然后把`config_from_api.yaml`文件重命名为`.config.yaml`
或者直接执行 `wget https://raw.githubusercontent.com/xinnan-tech/xiaozhi-esp32-server/refs/heads/main/main/xiaozhi-server/config_from_api.yaml` 下载保存。
下载完配置文件后,我们确认一下整个`xiaozhi-server`里面的文件如下所示:
```
xiaozhi-server
├─ docker-compose_all.yml
├─ data
├─ .config.yaml
├─ models
├─ SenseVoiceSmall
├─ model.pt
```
如果你的文件目录结构也是上面的,就继续往下。如果不是,你就再仔细看看是不是漏操作了什么。
## 2. 备份数据
如果你之前已经成功运行智控台,如果上面保存有你的密钥信息,请先从智控台上拷贝重要数据下来。因为升级过程中,有可能会覆盖原来的数据。
## 3. 清除历史版本镜像和容器
接下来打开命令行工具,使用`终端``命令行`工具 进入到你的`xiaozhi-server`,执行以下命令
```
docker compose -f docker-compose_all.yml down
docker stop xiaozhi-esp32-server
docker rm xiaozhi-esp32-server
docker stop xiaozhi-esp32-server-web
docker rm xiaozhi-esp32-server-web
docker stop xiaozhi-esp32-server-db
docker rm xiaozhi-esp32-server-db
docker stop xiaozhi-esp32-server-redis
docker rm xiaozhi-esp32-server-redis
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
```
## 4. 运行程序
执行以下命令启动新版本容器
```
docker compose -f docker-compose_all.yml up -d
```
执行完后,再执行以下命令,查看日志信息。
```
docker logs -f xiaozhi-esp32-server-web
```
当你看到输出日志时,说明你的`智控台`启动成功了。
```
2025-xx-xx 22:11:12.445 [main] INFO c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource
2025-xx-xx 21:28:53.873 [main] INFO xiaozhi.AdminApplication - Started AdminApplication in 16.057 seconds (process running for 17.941)
http://localhost:8002/xiaozhi/doc.html
```
请注意此刻仅是`智控台`能运行,如果8000端口`xiaozhi-esp32-server`报错,先不要理会。
这时,你需要使用浏览器,打开`智控台`,链接:http://127.0.0.1:8002 ,注册第一个用户。第一个用户即是超级管理员,以后的用户都是普通用户。普通用户只能绑定设备和配置智能体;超级管理员可以进行模型管理、用户管理、参数配置等功能。
接下来要做三件重要的事情:
### 第一件重要的事情
使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到列表中第一条数据,参数编码是`server.secret`,复制它到`参数值`
`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api``server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。
复制`参数值`后,打开`xiaozhi-server`下的`data`目录的`.config.yaml`文件。此刻你的配置文件内容应该是这样的:
```
manager-api:
url: http://127.0.0.1:8002/xiaozhi
secret: 你的server.secret值
```
1、把你刚才从`智控台`复制过来的`server.secret``参数值`复制到`.config.yaml`文件里的`secret`里。
2、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi`
3、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi`
4、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi`
类似这样的效果
```
manager-api:
url: http://xiaozhi-esp32-server-web:8002/xiaozhi
secret: 12345678-xxxx-xxxx-xxxx-123456789000
```
保存好后,继续往下做第二件重要的事情
### 第二件重要的事情
使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮,
弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。
## 5.重启xiaozhi-esp32-server
接下来打开命令行工具,使用`终端``命令行`工具 输入
```
docker restart xiaozhi-esp32-server
docker logs -f xiaozhi-esp32-server
```
如果你能看到,类似以下日志,则是Server启动成功的标志。
```
25-02-23 12:01:09[core.websocket_server] - INFO - Websocket地址是 ws://xxx.xx.xx.xx:8000/xiaozhi/v1/
25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问=======
25-02-23 12:01:09[core.websocket_server] - INFO - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
25-02-23 12:01:09[core.websocket_server] - INFO - =======================================================
```
由于你是全模块部署,因此你有两个重要的接口需要写入到esp32中。
OTA接口:
```
http://你电脑局域网的ip:8002/xiaozhi/ota/
```
Websocket接口:
```
ws://你电脑局域网的ip:8000/xiaozhi/v1/
```
### 第三件重要的事情
使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到参数编码是`server.websocket`,输入你的`Websocket接口`
使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到数编码是`server.ota`,输入你的`OTA接口`
接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个
1、 [编译自己的esp32固件](firmware-build.md)了。
2、 [基于虾哥编译好的固件配置自定义服务器](firmware-setting.md)了。
# 方式二:本地源码运行全模块
## 1.安装MySQL数据库
如果本机已经安装了MySQL,可以直接在数据库中创建名为`xiaozhi_esp32_server`的数据库。
```sql
CREATE DATABASE xiaozhi_esp32_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
如果还没有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
```
## 2.安装redis
如果还没有Redis,你可以通过docker安装redis
```
docker run --name xiaozhi-esp32-server-redis -d -p 6379:6379 redis
```
## 3.运行manager-api程序
3.1 安装JDK21,设置JDK环境变量
3.2 安装Maven,设置Maven环境变量
3.3 使用Vscode编程工具,安装好Java环境相关插件
3.4 使用Vscode编程工具加载manager-api模块
`src/main/resources/application-dev.yml`中配置数据库连接信息
```
spring:
datasource:
username: root
password: 123456
```
`src/main/resources/application-dev.yml`中配置Redis连接信息
```
spring:
data:
redis:
host: localhost
port: 6379
password:
database: 0
```
3.5 运行主程序
本项目为SpringBoot项目,启动方式为:
打开`Application.java`运行`Main`方法启动
```
路径地址:
src/main/java/xiaozhi/AdminApplication.java
```
当你看到输出日志时,说明你的`manager-api`启动成功了。
```
2025-xx-xx 22:11:12.445 [main] INFO c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource
2025-xx-xx 21:28:53.873 [main] INFO xiaozhi.AdminApplication - Started AdminApplication in 16.057 seconds (process running for 17.941)
http://localhost:8002/xiaozhi/doc.html
```
## 4.运行manager-web程序
4.1 安装nodejs
4.2 使用Vscode编程工具加载manager-web模块
终端命令进入manager-web目录下
```
npm install
```
然后启动
```
npm run serve
```
请注意,如果你的manager-api的接口不在`http://localhost:8002`,请在开发时,修改
`main/manager-web/.env.development`中的路径
运行成功后,你需要使用浏览器,打开`智控台`,链接:http://127.0.0.1:8001 ,注册第一个用户。第一个用户即是超级管理员,以后的用户都是普通用户。普通用户只能绑定设备和配置智能体;超级管理员可以进行模型管理、用户管理、参数配置等功能。
重要:注册成功后,使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮,
弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。
重要:注册成功后,使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮,
弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。
重要:注册成功后,使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮,
弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。
## 5.安装Python环境
本项目使用`conda`管理依赖环境。如果不方便安装`conda`,需要根据实际的操作系统安装好`libopus``ffmpeg`
如果确定使用`conda`,则安装好后,开始执行以下命令。
重要提示!windows 用户,可以通过安装`Anaconda`来管理环境。安装好`Anaconda`后,在`开始`那里搜索`anaconda`相关的关键词,
找到`Anaconda Prpmpt`,使用管理员身份运行它。如下图。
![conda_prompt](./images/conda_env_1.png)
运行之后,如果你能看到命令行窗口前面有一个(base)字样,说明你成功进入了`conda`环境。那么你就可以执行以下命令了。
![conda_env](./images/conda_env_2.png)
```
conda remove -n xiaozhi-esp32-server --all -y
conda create -n xiaozhi-esp32-server python=3.10 -y
conda activate xiaozhi-esp32-server
# 添加清华源通道
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge
conda install libopus -y
conda install ffmpeg -y
```
请注意,以上命令,不是一股脑执行就成功的,你需要一步步执行,每一步执行完后,都检查一下输出的日志,查看是否成功。
## 6.安装本项目依赖
你先要下载本项目源码,源码可以通过`git clone`命令下载,如果你不熟悉`git clone`命令。
你可以用浏览器打开这个地址`https://github.com/xinnan-tech/xiaozhi-esp32-server.git`
打开完,找到页面中一个绿色的按钮,写着`Code`的按钮,点开它,然后你就看到`Download ZIP`的按钮。
点击它,下载本项目源码压缩包。下载到你电脑后,解压它,此时它的名字可能叫`xiaozhi-esp32-server-main`
你需要把它重命名成`xiaozhi-esp32-server`,在这个文件里,进入到`main`文件夹,再进入到`xiaozhi-server`,好了请记住这个目录`xiaozhi-server`
```
# 继续使用conda环境
conda activate xiaozhi-esp32-server
# 进入到你的项目根目录,再进入main/xiaozhi-server
cd main/xiaozhi-server
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
pip install -r requirements.txt
```
### 7.下载语音识别模型文件
本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt`
文件放在`models/SenseVoiceSmall`
目录下。下面两个下载路线任选一个。
- 线路一:阿里魔塔下载[SenseVoiceSmall](https://modelscope.cn/models/iic/SenseVoiceSmall/resolve/master/model.pt)
- 线路二:百度网盘下载[SenseVoiceSmall](https://pan.baidu.com/share/init?surl=QlgM58FHhYv1tFnUT_A8Sg&pwd=qvna) 提取码:
`qvna`
## 8.配置项目文件
使用超级管理员账号,登录智控台 ,在顶部菜单找到`参数管理`,找到列表中第一条数据,参数编码是`server.secret`,复制它到`参数值`
`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api``server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。
如果你的`xiaozhi-server`目录没有`data`,你需要创建`data`目录。
如果你的`data`下面没有`.config.yaml`文件,你可以把`xiaozhi-server`目录下的`config_from_api.yaml`文件复制到`data`,并重命名为`.config.yaml`
复制`参数值`后,打开`xiaozhi-server`下的`data`目录的`.config.yaml`文件。此刻你的配置文件内容应该是这样的:
```
manager-api:
url: http://127.0.0.1:8002/xiaozhi
secret: 你的server.secret值
```
把你刚才从`智控台`复制过来的`server.secret``参数值`复制到`.config.yaml`文件里的`secret`里。
类似这样的效果
```
manager-api:
url: http://127.0.0.1:8002/xiaozhi
secret: 12345678-xxxx-xxxx-xxxx-123456789000
```
## 5.运行项目
```
# 确保在xiaozhi-server目录下执行
conda activate xiaozhi-esp32-server
python app.py
```
如果你能看到,类似以下日志,则是本项目服务启动成功的标志。
```
25-02-23 12:01:09[core.websocket_server] - INFO - Server is running at ws://xxx.xx.xx.xx:8000/xiaozhi/v1/
25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问=======
25-02-23 12:01:09[core.websocket_server] - INFO - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
25-02-23 12:01:09[core.websocket_server] - INFO - =======================================================
```
由于你是全模块部署,因此你有两个重要的接口。
OTA接口:
```
http://你电脑局域网的ip:8002/xiaozhi/ota/
```
Websocket接口:
```
ws://你电脑局域网的ip:8000/xiaozhi/v1/
```
请你务必把以上两个接口地址写入到智控台中:他们将会影响websocket地址发放和自动升级功能。
1、使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到参数编码是`server.websocket`,输入你的`Websocket接口`
2、使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到数编码是`server.ota`,输入你的`OTA接口`
接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个
1、 [编译自己的esp32固件](firmware-build.md)了。
2、 [基于虾哥编译好的固件配置自定义服务器](firmware-setting.md)了。
# 常见问题
以下是一些常见问题,供参考:
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md)
[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md)
[3、TTS 经常失败,经常超时](./FAQ.md)
[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md)
[5、如何提高小智对话响应速度?](./FAQ.md)
[6、我说话很慢,停顿时小智老是抢话](./FAQ.md)
[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md)
+115
View File
@@ -0,0 +1,115 @@
# 常见问题 ❓
### 1、为什么我说的话,小智识别出来很多韩文、日文、英文?🇰🇷
建议:检查一下`models/SenseVoiceSmall`是否已经有`model.pt`
文件,如果没有就要下载,查看这里[下载语音识别模型文件](Deployment.md#模型文件)
### 2、为什么会出现"TTS 任务出错 文件不存在"?📁
建议:检查一下是否正确使用`conda` 安装了`libopus``ffmpeg`库。
如果没有安装,就安装
```
conda install conda-forge::libopus
conda install conda-forge::ffmpeg
```
### 3、TTS 经常失败,经常超时 ⏰
建议:如果 `EdgeTTS` 经常失败,请先检查是否使用了代理(梯子)。如果使用了,请尝试关闭代理后再试;
如果用的是火山引擎的豆包 TTS,经常失败时建议使用付费版本,因为测试版本仅支持 2 个并发。
### 4、使用Wifi能连接自建服务器,但是4G模式却接不上 🔐
原因:虾哥的固件,4G模式需要使用安全连接。
解决方法:目前有两种方法可以解决。任选一种:
1、改代码。参考这个视频解决 https://www.bilibili.com/video/BV18MfTYoE85
2、使用nginx配置ssl证书。参考教程 https://icnt94i5ctj4.feishu.cn/docx/GnYOdMNJOoRCljx1ctecsj9cnRe
### 5、如何提高小智对话响应速度? ⚡
本项目默认配置为低成本方案,建议初学者先使用默认免费模型,解决"跑得动"的问题,再优化"跑得快"。
如需提升响应速度,可尝试更换各组件。以下为各组件的响应速度测试数据(仅供参考,不构成承诺):
| 影响因素 | 因素值 |
|:-----:|:----------------:|
| 测试地点 | 广东省广州市海珠区 |
| 测试时间 | 2025年2月19日 12:52 |
| 宽带运营商 | 中国联通 |
测试方法:
1、把各组件的密钥配置上去,只有配置了密钥的组件才参与测试。
2、配置完密钥后,执行以下方法
```
# 进入项目根目录,执行以下命令:
conda activate xiaozhi-esp32-server
python performance_tester.py
```
生成报告如下
LLM 性能排行:
| 模块名称 | 平均首Token时间 | 平均总响应时间 |
|:-----------|:-----------|:--------|
| AliLLM | 0.547s | 1.485s |
| ChatGLMLLM | 0.677s | 3.057s |
TTS 性能排行:
| 模块名称 | 平均合成时间 |
|----------------------|--------|
| EdgeTTS | 1.019s |
| DoubaoTTS | 0.503s |
| CosyVoiceSiliconflow | 3.732s |
推荐配置组合 (综合响应速度):
| 组合方案 | 综合得分 | LLM首Token | TTS合成 |
|-------------------------------|-------|-----------|--------|
| AliLLM + DoubaoTTS | 0.539 | 0.547s | 0.503s |
| AliLLM + EdgeTTS | 0.642 | 0.547s | 1.019s |
| ChatGLMLLM + DoubaoTTS | 0.642 | 0.677s | 0.503s |
| ChatGLMLLM + EdgeTTS | 0.745 | 0.677s | 1.019s |
| AliLLM + CosyVoiceSiliconflow | 1.184 | 0.547s | 3.732s |
### 结论 🔍
`2025年2月19日`,如果我的电脑在`广东省广州市海珠区`,且使用的是`中国联通`网络,我会优先使用:
- LLM`AliLLM`
- TTS`DoubaoTTS`
### 6、我说话很慢,停顿时小智老是抢话 🗣️
建议:在配置文件中找到如下部分,将 `min_silence_duration_ms` 的值调大(例如改为 `1000`):
```yaml
VAD:
SileroVAD:
threshold: 0.5
model_dir: models/snakers4_silero-vad
min_silence_duration_ms: 700 # 如果说话停顿较长,可将此值调大
```
### 7、我想通过小智控制电灯、空调、远程开关机等操作 💡
参考教程[ESP32设备与HomeAssistant集成指南](./homeassistant-integration.md)
### 8、更多问题,可联系我们反馈 💬
我们的联系方式放在[百度网盘中,点击前往](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),提取码是`223y`
网盘里有"硬件烧录QQ群"、"开源服务端交流群"、"产品建议联系人" 三张图片,请根据需要选择加入。
- 硬件烧录QQ群:适用于硬件烧录问题
- 开源服务端交流群:适用于服务端问题
- 产品建议联系人:适用于产品功能、产品设计等建议
+47 -20
View File
@@ -1,26 +1,53 @@
server { user root;
listen 8002; worker_processes 4;
server_name localhost;
# 静态资源服务(Vue项目) events {
location / { worker_connections 1024;
root /usr/share/nginx/html; }
try_files $uri $uri/ /index.html;
}
# API反向代理(Java项目) http {
location /xiaozhi-esp32-api/ { include mime.types;
proxy_pass http://127.0.0.1:8003; default_type application/octet-stream;
proxy_set_header Host $host; sendfile on;
proxy_cookie_path /api/ /; keepalive_timeout 300;
proxy_set_header Referer $http_referer; client_header_timeout 180s;
proxy_set_header Cookie $http_cookie; client_body_timeout 180s;
client_max_body_size 1024M;
proxy_connect_timeout 10; gzip on;
proxy_send_timeout 10; gzip_buffers 32 4K;
proxy_read_timeout 10; gzip_comp_level 6;
gzip_min_length 100;
gzip_types application/javascript text/css text/xml image/jpeg image/gif image/png;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
proxy_set_header X-Real-IP $remote_addr; server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 无域名访问,就用localhost
server_name localhost;
# 80端口
listen 8002;
# 转发到编译后到web目录
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
# 转发到manager-api
location /xiaozhi/ {
proxy_pass http://127.0.0.1:8003;
proxy_set_header Host $host;
proxy_cookie_path /manager/ /;
proxy_set_header Referer $http_referer;
proxy_set_header Cookie $http_cookie;
proxy_connect_timeout 10;
proxy_send_timeout 10;
proxy_read_timeout 10;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
} }
} }
+79 -41
View File
@@ -1,40 +1,81 @@
# 编译esp32固件 # esp32固件编译
1. 下载`xiaozhi-esp32` ## 第1步 准备你的ota地址
项目,按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf)
2. 打开`xiaozhi-esp32/main/Kconfig.projbuild`文件,找到`WEBSOCKET_URL``default`的内容,把`wss://api.tenclass.net` 如果你,使用的是本项目0.3.12版本,不管是简单Server部署还是全模块部署,都会有ota地址。
改成你自己的地址,例如,我的接口地址是`ws://192.168.1.25:8000`,就把内容改成这个。
由于简单Server部署和全模块部署的OTA地址设置方式不一样,请你选择下面的具体方式:
### 如果你用的是简单Server部署
此刻,请你用浏览器打开你的ota地址,例如我的ota地址
```
http://192.168.1.25:8002/xiaozhi/ota/
```
如果显示“OTA接口运行正常,向设备发送的websocket地址是:ws://xxx:8000/xiaozhi/v1/
你可以使用项目自带的`test_page.html`测试一下,是否能连上ota页面输出的websocket地址。
如果访问不到,你需要到配置文件`.config.yaml`里修改`server.websocket`的地址,重启后再重新测试,直到`test_page.html`能正常访问。
成功后,请往下进行第2步
### 如果你用的是全模块部署
此刻,请你用浏览器打开你的ota地址,例如我的ota地址
```
http://192.168.1.25:8002/xiaozhi/ota/
```
如果显示“OTA接口运行正常,websocket集群数量:X”。那就往下进行2步。
如果显示“OTA接口运行不正常”,大概是你还没在`智控台`配置`Websocket`地址。那就:
- 1、使用超级管理员登录智控台
- 2、顶部菜单点击`参数管理`
- 3、在列表中找到`server.websocket`项目,输入你的`Websocket`地址。例如我的就是
```
ws://192.168.1.25:8000/xiaozhi/v1/
```
配置完后,再使用浏览器刷新你的ota接口地址,看看是不是正常了。如果还不正常就,就再次确认一下Websocket是否正常启动,是否配置了Websocket地址。
## 第2步 配置环境
先按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf)
## 第3步 打开配置文件
配置好编译环境后,下载虾哥iaozhi-esp32项目源码,
从这里下载虾哥[xiaozhi-esp32项目源码](https://github.com/78/xiaozhi-esp32)。
下载后,打开`xiaozhi-esp32/main/Kconfig.projbuild`文件。
## 第4步 修改OTA地址
找到`OTA_URL``default`的内容,把`https://api.tenclass.net/xiaozhi/ota/`
改成你自己的地址,例如,我的接口地址是`http://192.168.1.25:8002/xiaozhi/ota/`,就把内容改成这个。
修改前: 修改前:
``` ```
config WEBSOCKET_URL config OTA_URL
depends on CONNECTION_TYPE_WEBSOCKET string "Default OTA URL"
string "Websocket URL" default "https://api.tenclass.net/xiaozhi/ota/"
default "wss://api.tenclass.net/xiaozhi/v1/"
help help
Communication with the server through websocket after wake up. The application will access this URL to check for new firmwares and server address.
``` ```
修改后:
修改后(示例)
``` ```
config WEBSOCKET_URL config OTA_URL
depends on CONNECTION_TYPE_WEBSOCKET string "Default OTA URL"
string "Websocket URL" default "http://192.168.1.25:8002/xiaozhi/ota/"
default "ws://192.168.1.25:8000/xiaozhi/v1/"
help help
Communication with the server through websocket after wake up. The application will access this URL to check for new firmwares and server address.
``` ```
注意:你的地址是`ws://`开头,不是`wss://`开头,一定不要写错了。 ## 第4步 设置编译参数
注意:你的地址是`ws://`开头,不是`wss://`开头,一定不要写错了。 设置编译参数
注意:你的地址是`ws://`开头,不是`wss://`开头,一定不要写错了。
3. 设置编译参数
``` ```
# 终端命令行进入xiaozhi-esp32的根目录 # 终端命令行进入xiaozhi-esp32的根目录
@@ -53,32 +94,26 @@ idf.py menuconfig
![图片](images/build_setting02.png) ![图片](images/build_setting02.png)
4. 编译固件 ## 第5步 编译固件
``` ```
idf.py build idf.py build
``` ```
如果是vscode安装的idf可以使用`F1`或者`ctrl+shift+p`,输入idf然后直接选择进行编译 ## 第6步 打包bin固件
还可以直接进行烧录不用接下来的操作
<img src="./images/vscode_idf.png" width="500px"/>
5. 打包bin固件
``` ```
cd scripts cd scripts
python release.py python release.py
``` ```
编译成功后,会在项目根目录下的`build`目录下生成固件文件`merged-binary.bin` 上面的打包命令执行完成后,会在项目根目录下的`build`目录下生成固件文件`merged-binary.bin`
这个`merged-binary.bin`就是要烧录到硬件上的固件文件。 这个`merged-binary.bin`就是要烧录到硬件上的固件文件。
注意:如果执行到第二命令后,报了“zip”相关的错误,请忽略这个错误,只要`build`目录下生成固件文件`merged-binary.bin` 注意:如果执行到第二命令后,报了“zip”相关的错误,请忽略这个错误,只要`build`目录下生成固件文件`merged-binary.bin`
,对你没有太大影响,请继续。 ,对你没有太大影响,请继续。
6. 烧录固件 ## 第7步 烧录固件
将esp32设备连接电脑,使用chrome浏览器,打开以下网址 将esp32设备连接电脑,使用chrome浏览器,打开以下网址
``` ```
@@ -90,16 +125,19 @@ https://espressif.github.io/esp-launchpad/
烧录成功且联网成功后,通过唤醒词唤醒小智,留意server端输出的控制台信息。 烧录成功且联网成功后,通过唤醒词唤醒小智,留意server端输出的控制台信息。
## 常见问题
以下是一些常见问题,供参考: 以下是一些常见问题,供参考:
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md)
[2、为什么会出现“TTS 任务出错 文件不存在”?](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md)
[3、TTS 经常失败,经常超时](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [3、TTS 经常失败,经常超时](./FAQ.md)
[4、如何提高小智对话响应速度?](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md)
[5、我说话很慢,停顿时小智老是抢话](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [5、如何提高小智对话响应速度?](./FAQ.md)
[6、我想通过小智控制电灯、空调、远程开关机等操作](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [6、我说话很慢,停顿时小智老是抢话](./FAQ.md)
[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md)
+53
View File
@@ -0,0 +1,53 @@
# 基于虾哥编译好的固件配置自定义服务器
## 第1步 确认版本
烧录虾哥已经编译好的1.6.1版本固件
## 第2步 准备你的ota地址
如果你按照教程使用的是全模块部署,就应该会有ota地址。
此刻,请你用浏览器打开你的ota地址,例如我的ota地址
```
https://2662r3426b.vicp.fun/xiaozhi/ota/
```
如果显示“OTA接口运行正常,websocket集群数量:X”。那就往下。
如果显示“OTA接口运行不正常”,大概是你还没在`智控台`配置`Websocket`地址。那就:
- 1、使用超级管理员登录智控台
- 2、顶部菜单点击`参数管理`
- 3、在列表中找到`server.websocket`项目,输入你的`Websocket`地址。例如我的就是
```
wss://2662r3426b.vicp.fun/xiaozhi/v1/
```
配置完后,再使用浏览器刷新你的ota接口地址,看看是不是正常了。如果还不正常就,就再次确认一下Websocket是否正常启动,是否配置了Websocket地址。
## 第3步 进入配网模式
进入机器的配网模式,在页面顶部,点击“高级选项”,在里面输入你服务器的`ota`地址,点击保存。重启设备
## 第4步 唤醒小智,查看日志输出
唤醒小智,看看日志是不是正常输出。
## 常见问题
以下是一些常见问题,供参考:
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md)
[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md)
[3、TTS 经常失败,经常超时](./FAQ.md)
[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md)
[5、如何提高小智对话响应速度?](./FAQ.md)
[6、我说话很慢,停顿时小智老是抢话](./FAQ.md)
[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md)
+111
View File
@@ -0,0 +1,111 @@
# ESP32设备与HomeAssistant集成指南
## 简介
本文档将指导您如何将ESP32设备与HomeAssistant进行集成。
## 前提条件
- 已安装并配置好`HomeAssistant`
- 已经在HomeAssistant中集成`Xiaomi Home`,并将米家的设备导入进`HomeAssistant`
- `xiaozhi-esp32-server智控台`能正常使用
- 本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用
## 网络环境
1、我的`HomeAssistant`部署在下面,版本是`2025.3.4`
```
http://192.168.4.7:8123
```
2、我的`xiaozhi-esp32-server智控台``HomeAssistant`部署在同一台机器的另一个端口,版本是`0.3.10`
```
http://192.168.4.7:8002
```
## 配置步骤
### 1. 登录`HomeAssistant`整理需要控制的设备清单
登录`HomeAssistant`,点击`左下角的设置`,然后进入`设备与服务`,再点击顶部的`实体`
然后在实体中搜索你相关控制的开关,结果出来后,在列表中,点击其中一个结果,这是会出现一个开关的界面。
在开关的界面,我们尝试点击开关,看看是开发会随着我们的点击开/关。如果能操作,说明是正常联网的。
接着在开关面板找到设置按钮,点击后,可以查看这个开关的`实体标识符`
我们打开一个记事本,按照这样格式整理一条数据:
位置+英文逗号+设备名称+英文逗号+`实体标识符`+英文分号
例如,我在公司,我有一个玩具灯,他的标识符是switch.cuco_cn_460494544_cp1_on_p_2_1,那么就这个写这一条数据
```
公司,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1;
```
当然最后我可能要操作两个灯,我的最终的结果是:
```
公司,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1;
公司,台灯,switch.iot_cn_831898993_socn1_on_p_2_1;
```
这段字符,我们成为“设备清单字符”需要保存好,等一下有用。
### 2. 登录`HomeAssistant`拿到开发密钥
登录`HomeAssistant`,点击`左下角个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key。
### 3. 登录`智控台`
使用管理员账号,登录`智控台`。点击顶部菜单`参数管理`,搜索`plugins.home_assistant.`,会有三条结果出来
编辑`plugins.home_assistant.devices`,把刚才整理的设备清单字符粘贴进去。
编辑`plugins.home_assistant.base_url`,把你部署的`HomeAssistant`接口地址粘贴进去,我粘贴进去的地址是这样的
```
http://192.168.4.7:8123
```
编辑`plugins.home_assistant.api_key`,把你从`HomeAssistant`复制过来的密钥,粘贴进去
### 4. 设置`意图识别`函数
在智控台,点击顶部菜单“模型配置”,在左侧栏,找到“意图识别”,找到id为`Intent_function_call`的意图,点击编辑
然后在弹框中,在原来的基础上追加两个函数:“hass_get_state”和“hass_set_state”
修改前
```
change_role;get_weather;get_news;play_music
```
修改后
```
change_role;get_weather;get_news;play_music;hass_get_state;hass_set_state
```
### 6. 手动重启xiaozhi-server
重启xiaozhi-server程序
### 5. 确认角色配置是否设置了函数意图识别
在智控台,点击顶部菜单“智能体管理”,找到设备所在的智能体,点击“配置角色”
确认意图识别(Intent),是否选择“函数调用意图识别”
### 6. 唤醒设别进行控制
尝试和esp32说,“打开XXX灯”
Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

+4 -8
View File
@@ -1,8 +1,6 @@
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../README.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-) 本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3)
# 项目目录介绍 # 项目目录介绍
当你看到这份文件的时候,这个这个项目还没完善好。我们还有很多东西要做。
如果你是一名软件开发者,这里有一份[《致开发者的公开信》](../docs/contributor_open_letter.md),欢迎归队! 如果你是一名软件开发者,这里有一份[《致开发者的公开信》](../docs/contributor_open_letter.md),欢迎归队!
``` ```
@@ -12,12 +10,10 @@ xiaozhi-esp32-server
├─ manager-api 8002 端口 Java语言开发 负责提供控制台的api ├─ manager-api 8002 端口 Java语言开发 负责提供控制台的api
``` ```
# xiaozhi-server 接口协议 # xiaozhi-server 和ESP32通讯协议
[虾哥团队通信协议:Websocket 连接](https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh) https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh
# manager-web 、manager-api接口协议 # manager-web 、manager-api接口协议
[manager前后端接口协议](https://app.apifox.com/invite/project?token=eXg2_tUv85q-gc3ZRowmn) https://2662r3426b.vicp.fun/xiaozhi/v1/doc.html
[前端页面设计图](https://codesign.qq.com/app/s/526108506410828)
+4 -58
View File
@@ -1,72 +1,18 @@
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../README.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-) 本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3)
# 项目介绍 # 项目介绍
manager-api 该项目基于SpringBoot框架开发。 manager-api 该项目基于SpringBoot框架开发。
开发使用代码编辑器,导入项目时,选择`manager-api`文件夹作为项目目录 开发使用代码编辑器,导入项目时,选择`manager-api`文件夹作为项目目录
参照[manager前后端接口协议](https://app.apifox.com/invite/project?token=H_8qhgfjUeaAL0wybghgU)开发
# 开发环境 # 开发环境
JDK 21 JDK 21
Maven 3.8+ Maven 3.8+
MySQL 8.0+ MySQL 8.0+
Redis 5.0+
Vue 3.x Vue 3.x
# 创建数据库
如果本机已经安装了MySQL,可以直接在数据库中创建名为`xiaozhi_esp32_server`的数据库。
```sql
CREATE DATABASE xiaozhi_esp32_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
如果还没有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
```
# 确认项目数据库连接信息
`src/main/resources/application-dev.yml`中配置数据库连接信息
```
spring:
datasource:
username: root
password: 123456
```
# 测试启动
本项目为SpringBoot项目,启动方式为:
打开`Application.java`运行`Main`方法启动
```
路径地址:
src/main/java/xiaozhi/AdminApplication.java
```
# 打包编译
执行以下命令生产jar包
```
mvn install
```
把jar包放在服务器上,执行
```
nohup java -jar xiaozhi-esp32-api.jar --spring.profiles.active=dev >/dev/null &
```
# 接口文档 # 接口文档
启动后打开:http://localhost:8002/xiaozhi-esp32-api/doc.html 启动后打开:http://localhost:8002/xiaozhi/doc.html
+31 -2
View File
@@ -18,7 +18,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>21</java.version> <java.version>21</java.version>
<junit.version>5.12.0</junit.version> <junit.version>5.10.1</junit.version>
<druid.version>1.2.20</druid.version> <druid.version>1.2.20</druid.version>
<mybatisplus.version>3.5.5</mybatisplus.version> <mybatisplus.version>3.5.5</mybatisplus.version>
<hutool.version>5.8.24</hutool.version> <hutool.version>5.8.24</hutool.version>
@@ -27,7 +27,6 @@
<shiro.version>2.0.2</shiro.version> <shiro.version>2.0.2</shiro.version>
<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>
<easyexcel.version>3.3.2</easyexcel.version>
<liquibase-core.version>4.20.0</liquibase-core.version> <liquibase-core.version>4.20.0</liquibase-core.version>
</properties> </properties>
@@ -88,6 +87,36 @@
<version>${junit.version}</version> <version>${junit.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-commons</artifactId>
<version>1.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-engine</artifactId>
<version>1.10.1</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
@@ -8,6 +8,6 @@ public class AdminApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args); SpringApplication.run(AdminApplication.class, args);
System.out.println("http://localhost:8002/xiaozhi-esp32-api/api/v1/doc.html"); System.out.println("http://localhost:8002/xiaozhi/doc.html");
} }
} }
@@ -1,6 +1,10 @@
package xiaozhi.common.annotation; package xiaozhi.common.annotation;
import java.lang.annotation.*; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** /**
* 数据过滤注解 * 数据过滤注解
@@ -1,6 +1,10 @@
package xiaozhi.common.annotation; package xiaozhi.common.annotation;
import java.lang.annotation.*; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** /**
* 操作日志注解 * 操作日志注解
@@ -1,99 +0,0 @@
package xiaozhi.common.aspect;
import cn.hutool.core.collection.CollUtil;
import xiaozhi.common.annotation.DataFilter;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.interceptor.DataScope;
import xiaozhi.common.user.UserDetail;
import xiaozhi.modules.security.user.SecurityUser;
import xiaozhi.modules.sys.enums.SuperAdminEnum;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
/**
* 数据过滤,切面处理类
* Copyright (c) 人人开源 All rights reserved.
* Website: https://www.renren.io
*/
@Aspect
@Component
public class DataFilterAspect {
@Pointcut("@annotation(xiaozhi.common.annotation.DataFilter)")
public void dataFilterCut() {
}
@Before("dataFilterCut()")
public void dataFilter(JoinPoint point) {
Object params = point.getArgs()[0];
if (params != null && params instanceof Map) {
UserDetail user = SecurityUser.getUser();
//如果是超级管理员,则不进行数据过滤
if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
return;
}
try {
//否则进行数据过滤
Map map = (Map) params;
String sqlFilter = getSqlFilter(user, point);
map.put(Constant.SQL_FILTER, new DataScope(sqlFilter));
} catch (Exception e) {
}
return;
}
throw new RenException(ErrorCode.DATA_SCOPE_PARAMS_ERROR);
}
/**
* 获取数据过滤的SQL
*/
private String getSqlFilter(UserDetail user, JoinPoint point) throws Exception {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = point.getTarget().getClass().getDeclaredMethod(signature.getName(), signature.getParameterTypes());
DataFilter dataFilter = method.getAnnotation(DataFilter.class);
//获取表的别名
String tableAlias = dataFilter.tableAlias();
if (StringUtils.isNotBlank(tableAlias)) {
tableAlias += ".";
}
StringBuilder sqlFilter = new StringBuilder();
sqlFilter.append(" (");
//部门ID列表
List<Long> deptIdList = user.getDeptIdList();
if (CollUtil.isNotEmpty(deptIdList)) {
sqlFilter.append(tableAlias).append(dataFilter.deptId());
sqlFilter.append(" in(").append(StringUtils.join(deptIdList, ",")).append(")");
}
//查询本人数据
if (CollUtil.isNotEmpty(deptIdList)) {
sqlFilter.append(" or ");
}
sqlFilter.append(tableAlias).append(dataFilter.userId()).append("=").append(user.getId());
sqlFilter.append(")");
return sqlFilter.toString();
}
}
@@ -1,14 +1,15 @@
package xiaozhi.common.aspect; package xiaozhi.common.aspect;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
/** /**
* Redis切面处理类 * Redis切面处理类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -19,7 +20,7 @@ import org.springframework.stereotype.Component;
@Component @Component
public class RedisAspect { public class RedisAspect {
/** /**
* 是否开启redis缓存 true开启 false关闭 * 是否开启redis缓存 true开启 false关闭
*/ */
@Value("${renren.redis.open}") @Value("${renren.redis.open}")
private boolean open; private boolean open;
@@ -1,12 +1,14 @@
package xiaozhi.common.config; package xiaozhi.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import xiaozhi.common.interceptor.DataFilterInterceptor; import xiaozhi.common.interceptor.DataFilterInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** /**
* mybatis-plus配置 * mybatis-plus配置
@@ -0,0 +1,17 @@
package xiaozhi.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate配置
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@@ -1,11 +1,12 @@
package xiaozhi.common.config; package xiaozhi.common.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springdoc.core.models.GroupedOpenApi; import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
/** /**
* Swagger配置 * Swagger配置
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -14,11 +15,68 @@ import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class SwaggerConfig { public class SwaggerConfig {
@Bean
public GroupedOpenApi deviceApi() {
return GroupedOpenApi.builder()
.group("device")
.pathsToMatch("/device/**")
.build();
}
@Bean
public GroupedOpenApi agentApi() {
return GroupedOpenApi.builder()
.group("agent")
.pathsToMatch("/agent/**")
.build();
}
@Bean
public GroupedOpenApi modelApi() {
return GroupedOpenApi.builder()
.group("models")
.pathsToMatch("/models/**")
.build();
}
@Bean
public GroupedOpenApi oatApi() {
return GroupedOpenApi.builder()
.group("ota")
.pathsToMatch("/ota/**")
.build();
}
@Bean
public GroupedOpenApi timbreApi() {
return GroupedOpenApi.builder()
.group("timbre")
.pathsToMatch("/ttsVoice/**")
.build();
}
@Bean
public GroupedOpenApi sysApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/admin/**")
.build();
}
@Bean @Bean
public GroupedOpenApi userApi() { public GroupedOpenApi userApi() {
String[] paths = {"/**"}; return GroupedOpenApi.builder()
return GroupedOpenApi.builder().group("xiaozhi") .group("user")
.pathsToMatch(paths).build(); .pathsToMatch("/user/**")
.build();
}
@Bean
public GroupedOpenApi configApi() {
return GroupedOpenApi.builder()
.group("config")
.pathsToMatch("/config/**")
.build();
} }
@Bean @Bean
@@ -73,13 +73,37 @@ public interface Constant {
* 排序方式 * 排序方式
*/ */
String ORDER = "order"; String ORDER = "order";
/**
* token header
*/
String TOKEN_HEADER = "token";
/**
* 请求头授权标识
*/
String AUTHORIZATION = "Authorization"; String AUTHORIZATION = "Authorization";
/**
* 服务器密钥
*/
String SERVER_SECRET = "server.secret";
/**
* websocket地址
*/
String SERVER_WEBSOCKET = "server.websocket";
/**
* ota地址
*/
String SERVER_OTA = "server.ota";
/**
* 是否允许用户注册
*/
String SERVER_ALLOW_USER_REGISTER = "server.allow_user_register";
/**
* 下发六位验证码时显示的控制面板地址
*/
String SERVER_FRONTED_URL = "server.fronted_url";
/** /**
* 路径分割符 * 路径分割符
*/ */
@@ -149,4 +173,9 @@ public interface Constant {
return value; return value;
} }
} }
/**
* 版本号
*/
public static final String VERSION = "0.3.12";
} }
@@ -1,17 +1,17 @@
package xiaozhi.common.convert; package xiaozhi.common.convert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
/** /**
* 日期转换 * 日期转换
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,12 +1,13 @@
package xiaozhi.common.entity; package xiaozhi.common.entity;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable; import lombok.Data;
import java.util.Date;
/** /**
* 基础实体类,所有实体都需要继承 * 基础实体类,所有实体都需要继承
@@ -43,4 +43,16 @@ public interface ErrorCode {
int PASSWORD_WEAK_ERROR = 10031; int PASSWORD_WEAK_ERROR = 10031;
int DEL_MYSELF_ERROR = 10032; int DEL_MYSELF_ERROR = 10032;
int DEVICE_CAPTCHA_ERROR = 10033; int DEVICE_CAPTCHA_ERROR = 10033;
// 参数校验相关错误码
int PARAM_VALUE_NULL = 10034;
int PARAM_TYPE_NULL = 10035;
int PARAM_TYPE_INVALID = 10036;
int PARAM_NUMBER_INVALID = 10037;
int PARAM_BOOLEAN_INVALID = 10038;
int PARAM_ARRAY_INVALID = 10039;
int PARAM_JSON_INVALID = 10040;
int OTA_DEVICE_NOT_FOUND = 10041;
int OTA_DEVICE_NEED_BIND = 10042;
} }
@@ -1,6 +1,5 @@
package xiaozhi.common.exception; package xiaozhi.common.exception;
import xiaozhi.common.utils.MessageUtils; import xiaozhi.common.utils.MessageUtils;
/** /**
@@ -10,7 +9,6 @@ import xiaozhi.common.utils.MessageUtils;
*/ */
public class RenException extends RuntimeException { public class RenException extends RuntimeException {
private int code; private int code;
private String msg; private String msg;
@@ -1,13 +1,14 @@
package xiaozhi.common.exception; package xiaozhi.common.exception;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.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 xiaozhi.common.utils.Result; import org.springframework.web.servlet.resource.NoResourceFoundException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.utils.Result;
/** /**
* 异常处理器 * 异常处理器
@@ -23,35 +24,40 @@ public class RenExceptionHandler {
* 处理自定义异常 * 处理自定义异常
*/ */
@ExceptionHandler(RenException.class) @ExceptionHandler(RenException.class)
public Result handleRenException(RenException ex) { public Result<Void> handleRenException(RenException ex) {
Result result = new Result(); Result<Void> result = new Result<>();
result.error(ex.getCode(), ex.getMsg()); result.error(ex.getCode(), ex.getMsg());
return result; return result;
} }
@ExceptionHandler(DuplicateKeyException.class) @ExceptionHandler(DuplicateKeyException.class)
public Result handleDuplicateKeyException(DuplicateKeyException ex) { public Result<Void> handleDuplicateKeyException(DuplicateKeyException ex) {
Result result = new Result(); Result<Void> result = new Result<>();
result.error(ErrorCode.DB_RECORD_EXISTS); result.error(ErrorCode.DB_RECORD_EXISTS);
return result; return result;
} }
@ExceptionHandler(UnauthorizedException.class) @ExceptionHandler(UnauthorizedException.class)
public Result handleUnauthorizedException(UnauthorizedException ex) { public Result<Void> handleUnauthorizedException(UnauthorizedException ex) {
Result result = new Result(); Result<Void> result = new Result<>();
result.error(ErrorCode.FORBIDDEN); result.error(ErrorCode.FORBIDDEN);
return result; return result;
} }
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
public Result handleException(Exception ex) { public Result<Void> handleException(Exception ex) {
log.error(ex.getMessage(), ex); log.error(ex.getMessage(), ex);
return new Result().error(); return new Result<Void>().error();
}
@ExceptionHandler(NoResourceFoundException.class)
public Result<Void> handleNoResourceFoundException(NoResourceFoundException ex) {
log.warn("Resource not found: {}", ex.getMessage());
return new Result<Void>().error(404, "资源不存在");
} }
} }
@@ -1,13 +1,15 @@
package xiaozhi.common.handler; package xiaozhi.common.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import java.util.Date;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.user.UserDetail;
import xiaozhi.modules.security.user.SecurityUser;
import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Date; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.user.UserDetail;
import xiaozhi.modules.security.user.SecurityUser;
/** /**
* 公共字段,自动填充值 * 公共字段,自动填充值
@@ -22,38 +24,34 @@ public class FieldMetaObjectHandler implements MetaObjectHandler {
private final static String UPDATER = "updater"; private final static String UPDATER = "updater";
private final static String DATA_OPERATION = "dataOperation"; private final static String DATA_OPERATION = "dataOperation";
private final static String DEPT_ID = "deptId";
@Override @Override
public void insertFill(MetaObject metaObject) { public void insertFill(MetaObject metaObject) {
UserDetail user = SecurityUser.getUser(); UserDetail user = SecurityUser.getUser();
Date date = new Date(); Date date = new Date();
//创建者 // 创建者
strictInsertFill(metaObject, CREATOR, Long.class, user.getId()); strictInsertFill(metaObject, CREATOR, Long.class, user.getId());
//创建时间 // 创建时间
strictInsertFill(metaObject, CREATE_DATE, Date.class, date); strictInsertFill(metaObject, CREATE_DATE, Date.class, date);
//创建者所属部门 // 更新者
strictInsertFill(metaObject, DEPT_ID, Long.class, user.getDeptId());
//更新者
strictInsertFill(metaObject, UPDATER, Long.class, user.getId()); strictInsertFill(metaObject, UPDATER, Long.class, user.getId());
//更新时间 // 更新时间
strictInsertFill(metaObject, UPDATE_DATE, Date.class, date); strictInsertFill(metaObject, UPDATE_DATE, Date.class, date);
//数据标识 // 数据标识
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.INSERT.getValue()); strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.INSERT.getValue());
} }
@Override @Override
public void updateFill(MetaObject metaObject) { public void updateFill(MetaObject metaObject) {
//更新者 // 更新者
strictUpdateFill(metaObject, UPDATER, Long.class, SecurityUser.getUserId()); strictUpdateFill(metaObject, UPDATER, Long.class, SecurityUser.getUserId());
//更新时间 // 更新时间
strictUpdateFill(metaObject, UPDATE_DATE, Date.class, new Date()); strictUpdateFill(metaObject, UPDATE_DATE, Date.class, new Date());
//数据标识 // 数据标识
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.UPDATE.getValue()); strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.UPDATE.getValue());
} }
} }
@@ -1,8 +1,17 @@
package xiaozhi.common.interceptor; package xiaozhi.common.interceptor;
import cn.hutool.core.util.StrUtil; import java.util.Map;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils; import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import cn.hutool.core.util.StrUtil;
import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.StringValue;
@@ -10,13 +19,6 @@ import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.Select;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Map;
/** /**
* 数据过滤 * 数据过滤
@@ -25,8 +27,10 @@ import java.util.Map;
*/ */
public class DataFilterInterceptor implements InnerInterceptor { public class DataFilterInterceptor implements InnerInterceptor {
@SuppressWarnings("rawtypes")
@Override @Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
DataScope scope = getDataScope(parameter); DataScope scope = getDataScope(parameter);
// 不进行数据过滤 // 不进行数据过滤
if (scope == null || StrUtil.isBlank(scope.getSqlFilter())) { if (scope == null || StrUtil.isBlank(scope.getSqlFilter())) {
@@ -48,7 +52,7 @@ public class DataFilterInterceptor implements InnerInterceptor {
// 判断参数里是否有DataScope对象 // 判断参数里是否有DataScope对象
if (parameter instanceof Map) { if (parameter instanceof Map) {
Map<?, ?> parameterMap = (Map<?, ?>) parameter; Map<?, ?> parameterMap = (Map<?, ?>) parameter;
for (Map.Entry entry : parameterMap.entrySet()) { for (Map.Entry<?, ?> entry : parameterMap.entrySet()) {
if (entry.getValue() != null && entry.getValue() instanceof DataScope) { if (entry.getValue() != null && entry.getValue() instanceof DataScope) {
return (DataScope) entry.getValue(); return (DataScope) entry.getValue();
} }
@@ -1,11 +1,11 @@
package xiaozhi.common.page; package xiaozhi.common.page;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/** /**
* 分页工具类 * 分页工具类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,10 +1,10 @@
package xiaozhi.common.page; package xiaozhi.common.page;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
/** /**
* 令牌信息 * 令牌信息
* *
@@ -14,7 +14,6 @@ import java.io.Serializable;
@Schema(description = "令牌信息") @Schema(description = "令牌信息")
public class TokenDTO implements Serializable { public class TokenDTO implements Serializable {
@Schema(description = "密码") @Schema(description = "密码")
private String token; private String token;
@@ -1,6 +1,5 @@
package xiaozhi.common.redis; package xiaozhi.common.redis;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -8,6 +7,8 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import jakarta.annotation.Resource;
/** /**
* Redis配置 * Redis配置
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -26,4 +26,74 @@ public class RedisKeys {
public static String getDeviceCaptchaKey(String captcha) { public static String getDeviceCaptchaKey(String captcha) {
return "sys:device:captcha:" + captcha; return "sys:device:captcha:" + captcha;
} }
/**
* 用户id的Key
*/
public static String getUserIdKey(Long userid) {
return "sys:username:id:" + userid;
}
/**
* 模型名称的Key
*/
public static String getModelNameById(String id) {
return "model:name:" + id;
}
/**
* 模型配置的Key
*/
public static String getModelConfigById(String id) {
return "model:data:" + id;
}
/**
* 获取音色名称缓存key
*/
public static String getTimbreNameById(String id) {
return "timbre:name:" + id;
}
/**
* 获取设备数量缓存key
*/
public static String getAgentDeviceCountById(String id) {
return "agent:device:count:" + id;
}
/**
* 获取系统配置缓存key
*/
public static String getServerConfigKey() {
return "server:config";
}
/**
* 获取音色详情缓存key
*/
public static String getTimbreDetailsKey(String id) {
return "timbre:details:" + id;
}
/**
* 获取版本号Key
*/
public static String getVersionKey() {
return "sys:version";
}
/**
* OTA固件ID的Key
*/
public static String getOtaIdKey(String uuid) {
return "ota:id:" + uuid;
}
/**
* OTA固件下载次数的Key
*/
public static String getOtaDownloadCountKey(String uuid) {
return "ota:download:count:" + uuid;
}
} }
@@ -1,14 +1,18 @@
package xiaozhi.common.redis; package xiaozhi.common.redis;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
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.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
/** /**
* Redis工具类 * Redis工具类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -26,7 +30,7 @@ public class RedisUtils {
/** /**
* 过期时长为1小时,单位:秒 * 过期时长为1小时,单位:秒
*/ */
public final static long HOUR_ONE_EXPIRE = 60 * 60 * 1L; public final static long HOUR_ONE_EXPIRE = (long) 60 * 60;
/** /**
* 过期时长为6小时,单位:秒 * 过期时长为6小时,单位:秒
*/ */
@@ -123,4 +127,24 @@ public class RedisUtils {
public Object rightPop(String key) { public Object rightPop(String key) {
return redisTemplate.opsForList().rightPop(key); return redisTemplate.opsForList().rightPop(key);
} }
/**
* 清空所有 Redis 数据库中的所有键
*/
public void emptyAll() {
// Lua 脚本 FLUSHALL是redis清空所有库的命令
String luaScript ="redis.call('FLUSHALL')";
// 创建 DefaultRedisScript 对象
DefaultRedisScript<Void> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(luaScript); // 设置 Lua 脚本内容
redisScript.setResultType(Void.class); // 设置返回值类型
// 执行 Lua 脚本
List<String> keys = Collections.emptyList(); // 如果脚本不依赖 key,可以传入空列表
redisTemplate.execute(redisScript, keys);
}
} }
@@ -1,10 +1,10 @@
package xiaozhi.common.service; package xiaozhi.common.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
/** /**
* 基础服务接口,所有Service接口都要继承 * 基础服务接口,所有Service接口都要继承
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -56,7 +56,8 @@ public interface BaseService<T> {
* </p> * </p>
* *
* @param entity 实体对象 * @param entity 实体对象
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper} * @param updateWrapper 实体对象封装操作类
* {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/ */
boolean update(T entity, Wrapper<T> updateWrapper); boolean update(T entity, Wrapper<T> updateWrapper);
@@ -1,11 +1,11 @@
package xiaozhi.common.service; package xiaozhi.common.service;
import xiaozhi.common.page.PageData;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import xiaozhi.common.page.PageData;
/** /**
* CRUD基础服务接口 * CRUD基础服务接口
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,5 +1,18 @@
package xiaozhi.common.service.impl; package xiaozhi.common.service.impl;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -10,22 +23,11 @@ import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper; import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import xiaozhi.common.constant.Constant; import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService; import xiaozhi.common.service.BaseService;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
/** /**
* 基础服务类,所有Service都要继承 * 基础服务类,所有Service都要继承
@@ -45,7 +47,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
* @param isAsc 排序方式 * @param isAsc 排序方式
*/ */
protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) { protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
//分页参数 // 分页参数
long curPage = 1; long curPage = 1;
long limit = 10; long limit = 10;
@@ -56,17 +58,17 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
limit = Long.parseLong((String) params.get(Constant.LIMIT)); limit = Long.parseLong((String) params.get(Constant.LIMIT));
} }
//分页对象 // 分页对象
Page<T> page = new Page<>(curPage, limit); Page<T> page = new Page<>(curPage, limit);
//分页参数 // 分页参数
params.put(Constant.PAGE, page); params.put(Constant.PAGE, page);
//排序字段 // 排序字段
String orderField = (String) params.get(Constant.ORDER_FIELD); String orderField = (String) params.get(Constant.ORDER_FIELD);
String order = (String) params.get(Constant.ORDER); String order = (String) params.get(Constant.ORDER);
//前端字段排序 // 前端字段排序
if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) { if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) {
if (Constant.ASC.equalsIgnoreCase(order)) { if (Constant.ASC.equalsIgnoreCase(order)) {
return page.addOrder(OrderItem.asc(orderField)); return page.addOrder(OrderItem.asc(orderField));
@@ -75,12 +77,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
} }
} }
//没有排序字段,则不排序 // 没有排序字段,则不排序
if (StringUtils.isBlank(defaultOrderField)) { if (StringUtils.isBlank(defaultOrderField)) {
return page; return page;
} }
//默认排序 // 默认排序
if (isAsc) { if (isAsc) {
page.addOrder(OrderItem.asc(defaultOrderField)); page.addOrder(OrderItem.asc(defaultOrderField));
} else { } else {
@@ -90,13 +92,13 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
return page; return page;
} }
protected <T> PageData<T> getPageData(List<?> list, long total, Class<T> target) { protected <D> PageData<D> getPageData(List<?> list, long total, Class<D> target) {
List<T> targetList = ConvertUtils.sourceToTarget(list, target); List<D> targetList = ConvertUtils.sourceToTarget(list, target);
return new PageData<>(targetList, total); return new PageData<>(targetList, total);
} }
protected <T> PageData<T> getPageData(IPage page, Class<T> target) { protected <D> PageData<D> getPageData(IPage<?> page, Class<D> target) {
return getPageData(page.getRecords(), page.getTotal(), target); return getPageData(page.getRecords(), page.getTotal(), target);
} }
@@ -126,10 +128,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
return SqlHelper.retBool(result); return SqlHelper.retBool(result);
} }
@SuppressWarnings("unchecked")
protected Class<M> currentMapperClass() { protected Class<M> currentMapperClass() {
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0); return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
} }
@SuppressWarnings("unchecked")
@Override @Override
public Class<T> currentModelClass() { public Class<T> currentModelClass() {
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1); return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1);
@@ -164,11 +168,11 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
/** /**
* 执行批量操作 * 执行批量操作
*/ */
@SuppressWarnings("deprecation")
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) { protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer); return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer);
} }
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean updateById(T entity) { public boolean updateById(T entity) {
@@ -1,26 +1,30 @@
package xiaozhi.common.service.impl; package xiaozhi.common.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.CrudService;
import xiaozhi.common.utils.ConvertUtils;
import org.springframework.beans.BeanUtils;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.beans.BeanUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.CrudService;
import xiaozhi.common.utils.ConvertUtils;
/** /**
* CRUD基础服务类 * CRUD基础服务类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
* Website: https://www.renren.io * Website: https://www.renren.io
*/ */
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T> implements CrudService<T, D> { public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T>
implements CrudService<T, D> {
@SuppressWarnings("unchecked")
protected Class<D> currentDtoClass() { protected Class<D> currentDtoClass() {
return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2); return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
} }
@@ -29,8 +33,7 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
public PageData<D> page(Map<String, Object> params) { public PageData<D> page(Map<String, Object> params) {
IPage<T> page = baseDao.selectPage( IPage<T> page = baseDao.selectPage(
getPage(params, null, false), getPage(params, null, false),
getWrapper(params) getWrapper(params));
);
return getPageData(page, currentDtoClass()); return getPageData(page, currentDtoClass());
} }
@@ -56,7 +59,7 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
T entity = ConvertUtils.sourceToTarget(dto, currentModelClass()); T entity = ConvertUtils.sourceToTarget(dto, currentModelClass());
insert(entity); insert(entity);
//copy主键值到dto // copy主键值到dto
BeanUtils.copyProperties(entity, dto); BeanUtils.copyProperties(entity, dto);
} }
@@ -1,9 +1,8 @@
package xiaozhi.common.user; package xiaozhi.common.user;
import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
import lombok.Data;
/** /**
* 登录用户信息 * 登录用户信息
@@ -14,19 +13,7 @@ import java.util.List;
public class UserDetail implements Serializable { public class UserDetail implements Serializable {
private Long id; private Long id;
private String username; private String username;
private String realName;
private String headUrl;
private Integer gender;
private String email;
private String mobile;
private Long deptId;
private String password;
private Integer status;
private Integer superAdmin; private Integer superAdmin;
private String token; private String token;
/** private Integer status;
* 部门数据权限
*/
private List<Long> deptIdList;
} }
@@ -1,12 +1,13 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.springframework.beans.BeanUtils;
import lombok.extern.slf4j.Slf4j;
/** /**
* 转换工具类 * 转换工具类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -20,7 +21,7 @@ public class ConvertUtils {
} }
T targetObject = null; T targetObject = null;
try { try {
targetObject = target.newInstance(); targetObject = target.getDeclaredConstructor().newInstance();
BeanUtils.copyProperties(source, targetObject); BeanUtils.copyProperties(source, targetObject);
} catch (Exception e) { } catch (Exception e) {
log.error("convert error ", e); log.error("convert error ", e);
@@ -34,10 +35,10 @@ public class ConvertUtils {
return null; return null;
} }
List targetList = new ArrayList<>(sourceList.size()); List<T> targetList = new ArrayList<>(sourceList.size());
try { try {
for (Object source : sourceList) { for (Object source : sourceList) {
T targetObject = target.newInstance(); T targetObject = target.getDeclaredConstructor().newInstance();
BeanUtils.copyProperties(source, targetObject); BeanUtils.copyProperties(source, targetObject);
targetList.add(targetObject); targetList.add(targetObject);
} }
@@ -2,6 +2,9 @@ package xiaozhi.common.utils;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date; import java.util.Date;
/** /**
@@ -60,4 +63,39 @@ public class DateUtils {
return null; return null;
} }
/**
* 获取简短的时间字符串:10秒前返回刚刚,多少秒前,几小时前,超过一周返回年月日时分秒
* @param date
* @return
*/
public static String getShortTime(Date date) {
if (date == null) {
return null;
}
// 将 Date 转换为 Instant
LocalDateTime localDateTime = date.toInstant()
// 获取系统默认时区
.atZone(ZoneId.systemDefault())
// 转换为 LocalDateTime
.toLocalDateTime();
// 当前时间
LocalDateTime now = LocalDateTime.now();
// 时间差,单位为秒
long secondsBetween = ChronoUnit.SECONDS.between(localDateTime, now);
if (secondsBetween <= 10) {
return "刚刚";
} else if (secondsBetween < 60) {
return secondsBetween + "秒前";
} else if (secondsBetween < 60 * 60) {
return secondsBetween / 60 + "分钟前";
} else if (secondsBetween < 86400) {
return secondsBetween / 3600 + "小时前";
} else if (secondsBetween < 604800) {
return secondsBetween / 86400 + "天前";
} else {
// 超过一周,显示完整日期时间
return format(date,DATE_TIME_PATTERN);
}
}
} }
@@ -1,7 +1,10 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import xiaozhi.common.constant.Constant; import java.util.Date;
import jakarta.servlet.http.HttpServletRequest; import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.util.DigestUtils; import org.springframework.util.DigestUtils;
@@ -9,10 +12,9 @@ import org.springframework.web.context.request.RequestAttributes;
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;
import java.util.Date; import jakarta.servlet.http.HttpServletRequest;
import java.util.Enumeration; import xiaozhi.common.exception.ErrorCode;
import java.util.HashMap; import xiaozhi.common.exception.RenException;
import java.util.Map;
/** /**
* Http * Http
@@ -30,13 +32,14 @@ public class HttpContextUtils {
return ((ServletRequestAttributes) requestAttributes).getRequest(); return ((ServletRequestAttributes) requestAttributes).getRequest();
} }
public static String getToken() { public static String getToken(String authorization) {
HttpServletRequest httpRequest = getHttpServletRequest(); String token;
String token = httpRequest.getHeader(Constant.TOKEN_HEADER); if (StringUtils.isBlank(authorization) && authorization.contains("Bearer ")) {
throw new RenException(ErrorCode.UNAUTHORIZED);
//如果header中不存在token,则从参数中获取token }
token = authorization.replace("Bearer ", "");
if (StringUtils.isBlank(token)) { if (StringUtils.isBlank(token)) {
token = httpRequest.getParameter(Constant.TOKEN_HEADER); throw new RenException(ErrorCode.TOKEN_NOT_EMPTY);
} }
return token; return token;
} }
@@ -68,15 +71,15 @@ public class HttpContextUtils {
} }
public static String getLanguage() { public static String getLanguage() {
//默认语言 // 默认语言
String defaultLanguage = "zh-CN"; String defaultLanguage = "zh-CN";
//request // request
HttpServletRequest request = getHttpServletRequest(); HttpServletRequest request = getHttpServletRequest();
if (request == null) { if (request == null) {
return defaultLanguage; return defaultLanguage;
} }
//请求语言 // 请求语言
defaultLanguage = request.getHeader(HttpHeaders.ACCEPT_LANGUAGE); defaultLanguage = request.getHeader(HttpHeaders.ACCEPT_LANGUAGE);
return defaultLanguage; return defaultLanguage;
@@ -1,8 +1,9 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import org.apache.commons.lang3.StringUtils;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/** /**
* IP地址 * IP地址
@@ -1,13 +1,14 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
/** /**
* JSON 工具类 * JSON 工具类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -59,7 +60,8 @@ public class JsonUtils {
return new ArrayList<>(); return new ArrayList<>();
} }
try { try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz)); return objectMapper.readValue(text,
objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -1,16 +1,10 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import xiaozhi.common.exception.ErrorCode; import java.io.Serializable;
import xiaozhi.common.page.PageData;
import xiaozhi.modules.security.user.SecurityUser;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.StringUtils; import xiaozhi.common.exception.ErrorCode;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* 响应数据 * 响应数据
@@ -42,10 +36,6 @@ public class Result<T> implements Serializable {
return this; return this;
} }
public boolean success() {
return code == 0;
}
public Result<T> error() { public Result<T> error() {
this.code = ErrorCode.INTERNAL_SERVER_ERROR; this.code = ErrorCode.INTERNAL_SERVER_ERROR;
this.msg = MessageUtils.getMessage(this.code); this.msg = MessageUtils.getMessage(this.code);
@@ -1,11 +1,11 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.Data;
/** /**
* 树节点,所有需要实现树节点的,都需要继承该类 * 树节点,所有需要实现树节点的,都需要继承该类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,12 +1,12 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import xiaozhi.common.validator.AssertUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import xiaozhi.common.validator.AssertUtils;
/** /**
* 树形结构工具类,如:菜单、部门等 * 树形结构工具类,如:菜单、部门等
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -17,8 +17,8 @@ public class TreeUtils {
/** /**
* 根据pid,构建树节点 * 根据pid,构建树节点
*/ */
public static <T extends TreeNode> List<T> build(List<T> treeNodes, Long pid) { public static <T extends TreeNode<T>> List<T> build(List<T> treeNodes, Long pid) {
//pid不能为空 // pid不能为空
AssertUtils.isNull(pid, "pid"); AssertUtils.isNull(pid, "pid");
List<T> treeList = new ArrayList<>(); List<T> treeList = new ArrayList<>();
@@ -34,7 +34,7 @@ public class TreeUtils {
/** /**
* 查找子节点 * 查找子节点
*/ */
private static <T extends TreeNode> T findChildren(List<T> treeNodes, T rootNode) { private static <T extends TreeNode<T>> T findChildren(List<T> treeNodes, T rootNode) {
for (T treeNode : treeNodes) { for (T treeNode : treeNodes) {
if (rootNode.getId().equals(treeNode.getPid())) { if (rootNode.getId().equals(treeNode.getPid())) {
rootNode.getChildren().add(findChildren(treeNodes, treeNode)); rootNode.getChildren().add(findChildren(treeNodes, treeNode));
@@ -46,10 +46,10 @@ public class TreeUtils {
/** /**
* 构建树节点 * 构建树节点
*/ */
public static <T extends TreeNode> List<T> build(List<T> treeNodes) { public static <T extends TreeNode<T>> List<T> build(List<T> treeNodes) {
List<T> result = new ArrayList<>(); List<T> result = new ArrayList<>();
//list转map // list转map
Map<Long, T> nodeMap = new LinkedHashMap<>(treeNodes.size()); Map<Long, T> nodeMap = new LinkedHashMap<>(treeNodes.size());
for (T treeNode : treeNodes) { for (T treeNode : treeNodes) {
nodeMap.put(treeNode.getId(), treeNode); nodeMap.put(treeNode.getId(), treeNode);
@@ -1,14 +1,15 @@
package xiaozhi.common.validator; package xiaozhi.common.validator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
/** /**
* 校验工具类 * 校验工具类
@@ -73,11 +74,11 @@ public class AssertUtils {
} }
} }
public static void isMapEmpty(Map map, String... params) { public static void isMapEmpty(Map<?, ?> map, String... params) {
isMapEmpty(map, ErrorCode.NOT_NULL, params); isMapEmpty(map, ErrorCode.NOT_NULL, params);
} }
public static void isMapEmpty(Map map, Integer code, String... params) { public static void isMapEmpty(Map<?, ?> map, Integer code, String... params) {
if (code == null) { if (code == null) {
throw new RenException(ErrorCode.NOT_NULL, "code"); throw new RenException(ErrorCode.NOT_NULL, "code");
} }
@@ -1,16 +1,17 @@
package xiaozhi.common.validator; package xiaozhi.common.validator;
import xiaozhi.common.exception.RenException; import java.util.Locale;
import jakarta.validation.ConstraintViolation; import java.util.Set;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator; import org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator;
import java.util.Locale; import jakarta.validation.ConstraintViolation;
import java.util.Set; import jakarta.validation.Validation;
import jakarta.validation.Validator;
import xiaozhi.common.exception.RenException;
/** /**
* hibernate-validator校验工具类 * hibernate-validator校验工具类
@@ -35,7 +36,7 @@ public class ValidatorUtils {
throws RenException { throws RenException {
Locale.setDefault(LocaleContextHolder.getLocale()); Locale.setDefault(LocaleContextHolder.getLocale());
Validator validator = Validation.byDefaultProvider().configure().messageInterpolator( Validator validator = Validation.byDefaultProvider().configure().messageInterpolator(
new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource()))) new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource())))
.buildValidatorFactory().getValidator(); .buildValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups); Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
@@ -1,8 +1,9 @@
package xiaozhi.common.xss; package xiaozhi.common.xss;
import org.apache.commons.lang3.StringUtils;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import org.apache.commons.lang3.StringUtils;
/** /**
* SQL过滤 * SQL过滤
@@ -20,19 +21,20 @@ public class SqlFilter {
if (StringUtils.isBlank(str)) { if (StringUtils.isBlank(str)) {
return null; return null;
} }
//去掉'|"|;|\字符 // 去掉'|"|;|\字符
str = StringUtils.replace(str, "'", ""); str = StringUtils.replace(str, "'", "");
str = StringUtils.replace(str, "\"", ""); str = StringUtils.replace(str, "\"", "");
str = StringUtils.replace(str, ";", ""); str = StringUtils.replace(str, ";", "");
str = StringUtils.replace(str, "\\", ""); str = StringUtils.replace(str, "\\", "");
//转换成小写 // 转换成小写
str = str.toLowerCase(); str = str.toLowerCase();
//非法字符 // 非法字符
String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"}; String[] keywords = { "master", "truncate", "insert", "select", "delete", "update", "declare", "alter",
"drop" };
//判断是否包含非法字符 // 判断是否包含非法字符
for (String keyword : keywords) { for (String keyword : keywords) {
if (str.contains(keyword)) { if (str.contains(keyword)) {
throw new RenException(ErrorCode.INVALID_SYMBOL); throw new RenException(ErrorCode.INVALID_SYMBOL);
@@ -1,6 +1,5 @@
package xiaozhi.common.xss; package xiaozhi.common.xss;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -8,6 +7,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import jakarta.servlet.DispatcherType;
/** /**
* XSS 配置文件 * XSS 配置文件
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,11 +1,17 @@
package xiaozhi.common.xss; package xiaozhi.common.xss;
import jakarta.servlet.*; import java.io.IOException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import java.io.IOException; import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
/** /**
* XSS过滤 * XSS过滤
@@ -38,7 +44,8 @@ public class XssFilter implements Filter {
private boolean shouldNotFilter(HttpServletRequest request) { private boolean shouldNotFilter(HttpServletRequest request) {
// 放行不过滤的URL // 放行不过滤的URL
return properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getServletPath())); return properties.getExcludeUrls().stream()
.anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getServletPath()));
} }
@Override @Override
@@ -1,20 +1,20 @@
package xiaozhi.common.xss; package xiaozhi.common.xss;
import cn.hutool.core.io.IoUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import cn.hutool.core.io.IoUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
/** /**
* XSS过滤处理 * XSS过滤处理
@@ -29,18 +29,18 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
@Override @Override
public ServletInputStream getInputStream() throws IOException { public ServletInputStream getInputStream() throws IOException {
//非json类型,直接返回 // 非json类型,直接返回
if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))) { if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))) {
return super.getInputStream(); return super.getInputStream();
} }
//为空,直接返回 // 为空,直接返回
String json = IoUtil.readUtf8(super.getInputStream()); String json = IoUtil.readUtf8(super.getInputStream());
if (StringUtils.isBlank(json)) { if (StringUtils.isBlank(json)) {
return super.getInputStream(); return super.getInputStream();
} }
//xss过滤 // xss过滤
json = xssEncode(json); json = xssEncode(json);
final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
return new ServletInputStream() { return new ServletInputStream() {
@@ -1,11 +1,12 @@
package xiaozhi.common.xss; package xiaozhi.common.xss;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
/** /**
* XSS 配置项 * XSS 配置项
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -22,13 +22,15 @@ public class XssUtils extends Safelist {
*/ */
private static Safelist xssWhitelist() { private static Safelist xssWhitelist() {
return new Safelist() return new Safelist()
//支持的标签 // 支持的标签
.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", .addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl",
"dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q", "small", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q",
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul", "small",
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u",
"ul",
"embed", "object", "param", "span") "embed", "object", "param", "span")
//支持的标签属性 // 支持的标签属性
.addAttributes("a", "href", "class", "style", "target", "rel", "nofollow") .addAttributes("a", "href", "class", "style", "target", "rel", "nofollow")
.addAttributes("blockquote", "cite") .addAttributes("blockquote", "cite")
.addAttributes("code", "class", "style") .addAttributes("code", "class", "style")
@@ -44,13 +46,16 @@ public class XssUtils extends Safelist {
.addAttributes("ul", "type", "style") .addAttributes("ul", "type", "style")
.addAttributes("pre", "class", "style") .addAttributes("pre", "class", "style")
.addAttributes("div", "class", "id", "style") .addAttributes("div", "class", "id", "style")
.addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen", "allowfullscreen", .addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen",
"quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess", "allownetworking", "type") "allowfullscreen",
.addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid", "codebase") "quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess",
"allownetworking", "type")
.addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid",
"codebase")
.addAttributes("param", "name", "value") .addAttributes("param", "name", "value")
.addAttributes("span", "class", "style") .addAttributes("span", "class", "style")
//标签属性对应的协议 // 标签属性对应的协议
.addProtocols("a", "href", "ftp", "http", "https", "mailto") .addProtocols("a", "href", "ftp", "http", "https", "mailto")
.addProtocols("img", "src", "http", "https") .addProtocols("img", "src", "http", "https")
.addProtocols("blockquote", "cite", "http", "https") .addProtocols("blockquote", "cite", "http", "https")
@@ -0,0 +1,195 @@
package xiaozhi.modules.agent.controller;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.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 com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.Result;
import xiaozhi.modules.agent.dto.AgentCreateDTO;
import xiaozhi.modules.agent.dto.AgentDTO;
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentTemplateService;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.security.user.SecurityUser;
@Tag(name = "智能体管理")
@AllArgsConstructor
@RestController
@RequestMapping("/agent")
public class AgentController {
private final AgentService agentService;
private final AgentTemplateService agentTemplateService;
private final DeviceService deviceService;
@GetMapping("/list")
@Operation(summary = "获取用户智能体列表")
@RequiresPermissions("sys:role:normal")
public Result<List<AgentDTO>> getUserAgents() {
UserDetail user = SecurityUser.getUser();
List<AgentDTO> agents = agentService.getUserAgents(user.getId());
return new Result<List<AgentDTO>>().ok(agents);
}
@GetMapping("/all")
@Operation(summary = "智能体列表(管理员)")
@RequiresPermissions("sys:role:superAdmin")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
})
public Result<PageData<AgentEntity>> adminAgentList(
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<AgentEntity> page = agentService.adminAgentList(params);
return new Result<PageData<AgentEntity>>().ok(page);
}
@GetMapping("/{id}")
@Operation(summary = "获取智能体详情")
@RequiresPermissions("sys:role:normal")
public Result<AgentEntity> getAgentById(@PathVariable("id") String id) {
AgentEntity agent = agentService.getAgentById(id);
return new Result<AgentEntity>().ok(agent);
}
@PostMapping
@Operation(summary = "创建智能体")
@RequiresPermissions("sys:role:normal")
public Result<Void> save(@RequestBody @Valid 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.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());
}
// 设置用户ID和创建者信息
UserDetail user = SecurityUser.getUser();
entity.setUserId(user.getId());
entity.setCreator(user.getId());
entity.setCreatedAt(new Date());
// ID、智能体编码和排序会在Service层自动生成
agentService.insert(entity);
return new Result<>();
}
@PutMapping("/{id}")
@Operation(summary = "更新智能体")
@RequiresPermissions("sys:role:normal")
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO 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<>();
}
@DeleteMapping("/{id}")
@Operation(summary = "删除智能体")
@RequiresPermissions("sys:role:normal")
public Result<Void> delete(@PathVariable String id) {
// 先删除关联的设备
deviceService.deleteByAgentId(id);
// 再删除智能体
agentService.deleteById(id);
return new Result<>();
}
@GetMapping("/template")
@Operation(summary = "智能体模板模板列表")
@RequiresPermissions("sys:role:normal")
public Result<List<AgentTemplateEntity>> templateList() {
List<AgentTemplateEntity> list = agentTemplateService
.list(new QueryWrapper<AgentTemplateEntity>().orderByAsc("sort"));
return new Result<List<AgentTemplateEntity>>().ok(list);
}
}
@@ -0,0 +1,18 @@
package xiaozhi.modules.agent.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.agent.entity.AgentEntity;
@Mapper
public interface AgentDao extends BaseDao<AgentEntity> {
/**
* 获取智能体的设备数量
*
* @param agentId 智能体ID
* @return 设备数量
*/
Integer getDeviceCountByAgentId(@Param("agentId") String agentId);
}
@@ -0,0 +1,17 @@
package xiaozhi.modules.agent.dao;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
/**
* @author chenerlei
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Mapper
* @createDate 2025-03-22 11:48:18
*/
@Mapper
public interface AgentTemplateDao extends BaseMapper<AgentTemplateEntity> {
}
@@ -0,0 +1,21 @@
package xiaozhi.modules.agent.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 智能体创建DTO
* 专用于新增智能体,不包含id、agentCode和sort字段,这些字段由系统自动生成/设置默认值
*/
@Data
@Schema(description = "智能体创建对象")
public class AgentCreateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "智能体名称", example = "客服助手")
@NotBlank(message = "智能体名称不能为空")
private String agentName;
}
@@ -0,0 +1,38 @@
package xiaozhi.modules.agent.dto;
import java.util.Date;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 智能体数据传输对象
* 用于在服务层和控制器层之间传递智能体相关的数据
*/
@Data
@Schema(description = "智能体对象")
public class AgentDTO {
@Schema(description = "智能体编码", example = "AGT_1234567890")
private String id;
@Schema(description = "智能体名称", example = "客服助手")
private String agentName;
@Schema(description = "语音合成模型名称", example = "tts_model_01")
private String ttsModelName;
@Schema(description = "音色名称", example = "voice_01")
private String ttsVoiceName;
@Schema(description = "大语言模型名称", example = "llm_model_01")
private String llmModelName;
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助")
private String systemPrompt;
@Schema(description = "最后连接时间", example = "2024-03-20 10:00:00")
private Date lastConnectedAt;
@Schema(description = "设备数量", example = "10")
private Integer deviceCount;
}
@@ -0,0 +1,56 @@
package xiaozhi.modules.agent.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 智能体更新DTO
* 专用于更新智能体,id字段是必需的,用于标识要更新的智能体
* 其他字段均为非必填,只更新提供的字段
*/
@Data
@Schema(description = "智能体更新对象")
public class AgentUpdateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "智能体编码", example = "AGT_1234567890", required = false)
private String agentCode;
@Schema(description = "智能体名称", example = "客服助手", required = false)
private String agentName;
@Schema(description = "语音识别模型标识", example = "asr_model_02", required = false)
private String asrModelId;
@Schema(description = "语音活动检测标识", example = "vad_model_02", required = false)
private String vadModelId;
@Schema(description = "大语言模型标识", example = "llm_model_02", required = false)
private String llmModelId;
@Schema(description = "语音合成模型标识", example = "tts_model_02", required = false)
private String ttsModelId;
@Schema(description = "音色标识", example = "voice_02", required = false)
private String ttsVoiceId;
@Schema(description = "记忆模型标识", example = "mem_model_02", required = false)
private String memModelId;
@Schema(description = "意图模型标识", example = "intent_model_02", required = false)
private String intentModelId;
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false)
private String systemPrompt;
@Schema(description = "语言编码", example = "zh_CN", required = false)
private String langCode;
@Schema(description = "交互语种", example = "中文", required = false)
private String language;
@Schema(description = "排序", example = "1", required = false)
private Integer sort;
}
@@ -0,0 +1,74 @@
package xiaozhi.modules.agent.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@TableName("ai_agent")
@Schema(description = "智能体信息")
public class AgentEntity {
@TableId(type = IdType.ASSIGN_UUID)
@Schema(description = "智能体唯一标识")
private String id;
@Schema(description = "所属用户ID")
private Long userId;
@Schema(description = "智能体编码")
private String agentCode;
@Schema(description = "智能体名称")
private String agentName;
@Schema(description = "语音识别模型标识")
private String asrModelId;
@Schema(description = "语音活动检测标识")
private String vadModelId;
@Schema(description = "大语言模型标识")
private String llmModelId;
@Schema(description = "语音合成模型标识")
private String ttsModelId;
@Schema(description = "音色标识")
private String ttsVoiceId;
@Schema(description = "记忆模型标识")
private String memModelId;
@Schema(description = "意图模型标识")
private String intentModelId;
@Schema(description = "角色设定参数")
private String systemPrompt;
@Schema(description = "语言编码")
private String langCode;
@Schema(description = "交互语种")
private String language;
@Schema(description = "排序")
private Integer sort;
@Schema(description = "创建者")
private Long creator;
@Schema(description = "创建时间")
private Date createdAt;
@Schema(description = "更新者")
private Long updater;
@Schema(description = "更新时间")
private Date updatedAt;
}
@@ -0,0 +1,114 @@
package xiaozhi.modules.agent.entity;
import java.io.Serializable;
import java.util.Date;
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 lombok.Data;
/**
* 智能体配置模板表
*
* @TableName ai_agent_template
*/
@TableName(value = "ai_agent_template")
@Data
public class AgentTemplateEntity implements Serializable {
/**
* 智能体唯一标识
*/
@TableId(type = IdType.ASSIGN_UUID)
private String id;
/**
* 智能体编码
*/
private String agentCode;
/**
* 智能体名称
*/
private String agentName;
/**
* 语音识别模型标识
*/
private String asrModelId;
/**
* 语音活动检测标识
*/
private String vadModelId;
/**
* 大语言模型标识
*/
private String llmModelId;
/**
* 语音合成模型标识
*/
private String ttsModelId;
/**
* 音色标识
*/
private String ttsVoiceId;
/**
* 记忆模型标识
*/
private String memModelId;
/**
* 意图模型标识
*/
private String intentModelId;
/**
* 角色设定参数
*/
private String systemPrompt;
/**
* 语言编码
*/
private String langCode;
/**
* 交互语种
*/
private String language;
/**
* 排序权重
*/
private Integer sort;
/**
* 创建者 ID
*/
private Long creator;
/**
* 创建时间
*/
private Date createdAt;
/**
* 更新者 ID
*/
private Long updater;
/**
* 更新时间
*/
private Date updatedAt;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
@@ -0,0 +1,45 @@
package xiaozhi.modules.agent.service;
import java.util.List;
import java.util.Map;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
import xiaozhi.modules.agent.dto.AgentDTO;
import xiaozhi.modules.agent.entity.AgentEntity;
public interface AgentService extends BaseService<AgentEntity> {
/**
* 管理员获取所有智能体列表(分页)
*/
PageData<AgentEntity> adminAgentList(Map<String, Object> params);
/**
* 获取智能体详情
*/
AgentEntity getAgentById(String id);
/**
* 删除这个用户的所有
*
* @param userId
*/
void deleteAgentByUserId(Long userId);
/**
* 获取用户智能体列表
*
* @param userId
* @return
*/
List<AgentDTO> getUserAgents(Long userId);
/**
* 获取智能体的设备数量
*
* @param agentId 智能体ID
* @return 设备数量
*/
Integer getDeviceCountByAgentId(String agentId);
}
@@ -0,0 +1,28 @@
package xiaozhi.modules.agent.service;
import com.baomidou.mybatisplus.extension.service.IService;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
/**
* @author chenerlei
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service
* @createDate 2025-03-22 11:48:18
*/
public interface AgentTemplateService extends IService<AgentTemplateEntity> {
/**
* 获取默认模板
*
* @return 默认模板实体
*/
AgentTemplateEntity getDefaultTemplate();
/**
* 更新默认模板中的模型ID
*
* @param modelType 模型类型
* @param modelId 模型ID
*/
void updateDefaultTemplateModelId(String modelType, String modelId);
}
@@ -0,0 +1,133 @@
package xiaozhi.modules.agent.service.impl;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import xiaozhi.common.page.PageData;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.modules.agent.dao.AgentDao;
import xiaozhi.modules.agent.dto.AgentDTO;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.timbre.service.TimbreService;
@Service
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
private final AgentDao agentDao;
@Autowired
private TimbreService timbreModelService;
@Autowired
private ModelConfigService modelConfigService;
@Autowired
private RedisUtils redisUtils;
public AgentServiceImpl(AgentDao agentDao) {
this.agentDao = agentDao;
}
@Override
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
IPage<AgentEntity> page = agentDao.selectPage(
getPage(params, "agent_name", true),
new QueryWrapper<>());
return new PageData<>(page.getRecords(), page.getTotal());
}
@Override
public AgentEntity getAgentById(String id) {
return agentDao.selectById(id);
}
@Override
public boolean insert(AgentEntity entity) {
// 如果ID为空,自动生成一个UUID作为ID
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
entity.setId(UUID.randomUUID().toString().replace("-", ""));
}
// 如果智能体编码为空,自动生成一个带前缀的编码
if (entity.getAgentCode() == null || entity.getAgentCode().trim().isEmpty()) {
entity.setAgentCode("AGT_" + System.currentTimeMillis());
}
// 如果排序字段为空,设置默认值0
if (entity.getSort() == null) {
entity.setSort(0);
}
return super.insert(entity);
}
@Override
public void deleteAgentByUserId(Long userId) {
UpdateWrapper<AgentEntity> wrapper = new UpdateWrapper<>();
wrapper.eq("user_id", userId);
baseDao.delete(wrapper);
}
@Override
public List<AgentDTO> getUserAgents(Long userId) {
QueryWrapper<AgentEntity> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", userId);
List<AgentEntity> agents = agentDao.selectList(wrapper);
return agents.stream().map(agent -> {
AgentDTO dto = new AgentDTO();
dto.setId(agent.getId());
dto.setAgentName(agent.getAgentName());
dto.setSystemPrompt(agent.getSystemPrompt());
// 获取 TTS 模型名称
dto.setTtsModelName(modelConfigService.getModelNameById(agent.getTtsModelId()));
// 获取 LLM 模型名称
dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId()));
// 获取 TTS 音色名称
dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId()));
// 获取设备数量
dto.setDeviceCount(getDeviceCountByAgentId(agent.getId()));
return dto;
}).collect(Collectors.toList());
}
@Override
public Integer getDeviceCountByAgentId(String agentId) {
if (StringUtils.isBlank(agentId)) {
return 0;
}
// 先从Redis中获取
Integer cachedCount = (Integer) redisUtils.get(RedisKeys.getAgentDeviceCountById(agentId));
if (cachedCount != null) {
return cachedCount;
}
// 如果Redis中没有,则从数据库查询
Integer deviceCount = agentDao.getDeviceCountByAgentId(agentId);
// 将结果存入Redis
if (deviceCount != null) {
redisUtils.set(RedisKeys.getAgentDeviceCountById(agentId), deviceCount, 60);
}
return deviceCount != null ? deviceCount : 0;
}
}
@@ -0,0 +1,69 @@
package xiaozhi.modules.agent.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import xiaozhi.modules.agent.dao.AgentTemplateDao;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentTemplateService;
/**
* @author chenerlei
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service实现
* @createDate 2025-03-22 11:48:18
*/
@Service
public class AgentTemplateServiceImpl extends ServiceImpl<AgentTemplateDao, AgentTemplateEntity>
implements AgentTemplateService {
/**
* 获取默认模板
*
* @return 默认模板实体
*/
public AgentTemplateEntity getDefaultTemplate() {
LambdaQueryWrapper<AgentTemplateEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByAsc(AgentTemplateEntity::getSort)
.last("LIMIT 1");
return this.getOne(wrapper);
}
/**
* 更新默认模板中的模型ID
*
* @param modelType 模型类型
* @param modelId 模型ID
*/
@Override
public void updateDefaultTemplateModelId(String modelType, String modelId) {
modelType = modelType.toUpperCase();
UpdateWrapper<AgentTemplateEntity> wrapper = new UpdateWrapper<>();
switch (modelType) {
case "ASR":
wrapper.set("asr_model_id", modelId);
break;
case "VAD":
wrapper.set("vad_model_id", modelId);
break;
case "LLM":
wrapper.set("llm_model_id", modelId);
break;
case "TTS":
wrapper.set("tts_model_id", modelId);
wrapper.set("tts_voice_id", null);
break;
case "MEMORY":
wrapper.set("mem_model_id", modelId);
break;
case "INTENT":
wrapper.set("intent_model_id", modelId);
break;
}
wrapper.ge("sort", 0);
update(wrapper);
}
}
@@ -0,0 +1,15 @@
package xiaozhi.modules.agent.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
@Data
@EqualsAndHashCode(callSuper = true)
public class AgentTemplateVO extends AgentTemplateEntity {
// 角色音色
private String ttsModelName;
// 角色模型
private String llmModelName;
}
@@ -0,0 +1,61 @@
package xiaozhi.modules.config.controller;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.utils.Result;
import xiaozhi.common.validator.ValidatorUtils;
import xiaozhi.modules.config.dto.AgentModelsDTO;
import xiaozhi.modules.config.service.ConfigService;
import xiaozhi.modules.sys.dto.ConfigSecretDTO;
import xiaozhi.modules.sys.service.SysParamsService;
/**
* xiaozhi-server 配置获取
*
* @since 1.0.0
*/
@RestController
@RequestMapping("config")
@Tag(name = "参数管理")
@AllArgsConstructor
public class ConfigController {
private final ConfigService configService;
private final SysParamsService sysParamsService;
@PostMapping("server-base")
@Operation(summary = "获取配置")
public Result<Object> getConfig(@RequestBody ConfigSecretDTO dto) {
// 效验数据
ValidatorUtils.validateEntity(dto);
checkSecret(dto.getSecret());
Object config = configService.getConfig(true);
return new Result<Object>().ok(config);
}
@PostMapping("agent-models")
@Operation(summary = "获取智能体模型")
public Result<Object> getAgentModels(@RequestBody AgentModelsDTO dto) {
// 效验数据
ValidatorUtils.validateEntity(dto);
checkSecret(dto.getSecret());
Object models = configService.getAgentModels(dto.getMacAddress(), dto.getSelectedModule());
return new Result<Object>().ok(models);
}
private void checkSecret(String secret) {
String secretParam = sysParamsService.getValue(Constant.SERVER_SECRET, true);
// 验证密钥
if (StringUtils.isBlank(secret) || !secret.equals(secretParam)) {
throw new RenException("密钥错误");
}
}
}
@@ -0,0 +1,28 @@
package xiaozhi.modules.config.dto;
import java.util.Map;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
@Schema(description = "获取智能体模型配置DTO")
public class AgentModelsDTO {
@NotBlank(message = "密钥不能为空")
@Schema(description = "密钥")
private String secret;
@NotBlank(message = "设备MAC地址不能为空")
@Schema(description = "设备MAC地址")
private String macAddress;
@NotBlank(message = "客户端ID不能为空")
@Schema(description = "客户端ID")
private String clientId;
@NotNull(message = "客户端已实例化的模型不能为空")
@Schema(description = "客户端已实例化的模型")
private Map<String, String> selectedModule;
}
@@ -0,0 +1,41 @@
package xiaozhi.modules.config.init;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import jakarta.annotation.PostConstruct;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.modules.config.service.ConfigService;
import xiaozhi.modules.sys.service.SysParamsService;
@Configuration
@DependsOn("liquibase")
public class SystemInitConfig {
@Autowired
private SysParamsService sysParamsService;
@Autowired
private ConfigService configService;
@Autowired
private RedisUtils redisUtils;
@PostConstruct
public void init() {
// 检查版本号
String redisVersion = (String) redisUtils.get(RedisKeys.getVersionKey());
if (!Constant.VERSION.equals(redisVersion)) {
// 如果版本不一致,清空Redis
redisUtils.emptyAll();
// 存储新版本号
redisUtils.set(RedisKeys.getVersionKey(), Constant.VERSION);
}
sysParamsService.initServerSecret();
configService.getConfig(false);
}
}
@@ -0,0 +1,22 @@
package xiaozhi.modules.config.service;
import java.util.Map;
public interface ConfigService {
/**
* 获取服务器配置
*
* @param isCache 是否缓存
* @return 配置信息
*/
Object getConfig(Boolean isCache);
/**
* 获取智能体模型配置
*
* @param macAddress MAC地址
* @param selectedModule 客户端已实例化的模型
* @return 模型配置信息
*/
Map<String, Object> getAgentModels(String macAddress, Map<String, String> selectedModule);
}
@@ -0,0 +1,298 @@
package xiaozhi.modules.config.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import lombok.AllArgsConstructor;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.utils.JsonUtils;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentTemplateService;
import xiaozhi.modules.config.service.ConfigService;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.model.entity.ModelConfigEntity;
import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.sys.dto.SysParamsDTO;
import xiaozhi.modules.sys.service.SysParamsService;
import xiaozhi.modules.timbre.service.TimbreService;
import xiaozhi.modules.timbre.vo.TimbreDetailsVO;
@Service
@AllArgsConstructor
public class ConfigServiceImpl implements ConfigService {
private final SysParamsService sysParamsService;
private final DeviceService deviceService;
private final ModelConfigService modelConfigService;
private final AgentService agentService;
private final AgentTemplateService agentTemplateService;
private final RedisUtils redisUtils;
private final TimbreService timbreService;
@Override
public Object getConfig(Boolean isCache) {
if (isCache) {
// 先从Redis获取配置
Object cachedConfig = redisUtils.get(RedisKeys.getServerConfigKey());
if (cachedConfig != null) {
return cachedConfig;
}
}
// 构建配置信息
Map<String, Object> result = new HashMap<>();
buildConfig(result);
// 查询默认智能体
AgentTemplateEntity agent = agentTemplateService.getDefaultTemplate();
if (agent == null) {
throw new RenException("默认智能体未找到");
}
// 构建模块配置
buildModuleConfig(
agent.getAgentName(),
agent.getSystemPrompt(),
null,
agent.getVadModelId(),
agent.getAsrModelId(),
agent.getLlmModelId(),
agent.getTtsModelId(),
agent.getMemModelId(),
agent.getIntentModelId(),
result,
isCache);
// 将配置存入Redis
redisUtils.set(RedisKeys.getServerConfigKey(), result);
return result;
}
@Override
public Map<String, Object> getAgentModels(String macAddress, Map<String, String> selectedModule) {
// 根据MAC地址查找设备
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
if (device == null) {
// 如果设备,去redis里看看有没有需要连接的设备
String cachedCode = deviceService.geCodeByDeviceId(macAddress);
if (StringUtils.isNotBlank(cachedCode)) {
throw new RenException(ErrorCode.OTA_DEVICE_NEED_BIND, cachedCode);
}
throw new RenException(ErrorCode.OTA_DEVICE_NOT_FOUND, "not found device");
}
// 获取智能体信息
AgentEntity agent = agentService.getAgentById(device.getAgentId());
if (agent == null) {
throw new RenException("智能体未找到");
}
// 获取音色信息
String voice = null;
TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId());
if (timbre != null) {
voice = timbre.getTtsVoice();
}
// 构建返回数据
Map<String, Object> result = new HashMap<>();
// 获取单台设备每天最多输出字数
String deviceMaxOutputSize = sysParamsService.getValue("device_max_output_size", true);
result.put("device_max_output_size", deviceMaxOutputSize);
// 如果客户端已实例化模型,则不返回
String alreadySelectedVadModelId = (String) selectedModule.get("VAD");
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {
agent.setVadModelId(null);
}
String alreadySelectedAsrModelId = (String) selectedModule.get("ASR");
if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) {
agent.setAsrModelId(null);
}
String alreadySelectedLlmModelId = (String) selectedModule.get("LLM");
if (alreadySelectedLlmModelId != null && alreadySelectedLlmModelId.equals(agent.getLlmModelId())) {
agent.setLlmModelId(null);
}
String alreadySelectedMemModelId = (String) selectedModule.get("Memory");
if (alreadySelectedMemModelId != null && alreadySelectedMemModelId.equals(agent.getMemModelId())) {
agent.setMemModelId(null);
}
String alreadySelectedIntentModelId = (String) selectedModule.get("Intent");
if (alreadySelectedIntentModelId != null && alreadySelectedIntentModelId.equals(agent.getIntentModelId())) {
agent.setIntentModelId(null);
}
// 构建模块配置
buildModuleConfig(
agent.getAgentName(),
agent.getSystemPrompt(),
voice,
agent.getVadModelId(),
agent.getAsrModelId(),
agent.getLlmModelId(),
agent.getTtsModelId(),
agent.getMemModelId(),
agent.getIntentModelId(),
result,
true);
return result;
}
/**
* 构建配置信息
*
* @param paramsList 系统参数列表
* @return 配置信息
*/
@SuppressWarnings("unchecked")
private Object buildConfig(Map<String, Object> config) {
// 查询所有系统参数
List<SysParamsDTO> paramsList = sysParamsService.list(new HashMap<>());
for (SysParamsDTO param : paramsList) {
String[] keys = param.getParamCode().split("\\.");
Map<String, Object> current = config;
// 遍历除最后一个key之外的所有key
for (int i = 0; i < keys.length - 1; i++) {
String key = keys[i];
if (!current.containsKey(key)) {
current.put(key, new HashMap<String, Object>());
}
current = (Map<String, Object>) current.get(key);
}
// 处理最后一个key
String lastKey = keys[keys.length - 1];
String value = param.getParamValue();
// 根据valueType转换值
switch (param.getValueType().toLowerCase()) {
case "number":
try {
double doubleValue = Double.parseDouble(value);
// 如果数值是整数形式,则转换为Integer
if (doubleValue == (int) doubleValue) {
current.put(lastKey, (int) doubleValue);
} else {
current.put(lastKey, doubleValue);
}
} catch (NumberFormatException e) {
current.put(lastKey, value);
}
break;
case "boolean":
current.put(lastKey, Boolean.parseBoolean(value));
break;
case "array":
// 将分号分隔的字符串转换为数字数组
List<String> list = new ArrayList<>();
for (String num : value.split(";")) {
if (StringUtils.isNotBlank(num)) {
list.add(num.trim());
}
}
current.put(lastKey, list);
break;
case "json":
try {
current.put(lastKey, JsonUtils.parseObject(value, Object.class));
} catch (Exception e) {
current.put(lastKey, value);
}
break;
default:
current.put(lastKey, value);
}
}
return config;
}
/**
* 构建模块配置
*
* @param prompt 提示词
* @param voice 音色
* @param vadModelId VAD模型ID
* @param asrModelId ASR模型ID
* @param llmModelId LLM模型ID
* @param ttsModelId TTS模型ID
* @param memModelId 记忆模型ID
* @param intentModelId 意图模型ID
* @param result 结果Map
*/
private void buildModuleConfig(
String assistantName,
String prompt,
String voice,
String vadModelId,
String asrModelId,
String llmModelId,
String ttsModelId,
String memModelId,
String intentModelId,
Map<String, Object> result,
boolean isCache) {
Map<String, String> selectedModule = new HashMap<>();
String[] modelTypes = { "VAD", "ASR", "TTS", "Memory", "Intent", "LLM" };
String[] modelIds = { vadModelId, asrModelId, ttsModelId, memModelId, intentModelId, llmModelId };
String intentLLMModelId = null;
for (int i = 0; i < modelIds.length; i++) {
if (modelIds[i] == null) {
continue;
}
ModelConfigEntity model = modelConfigService.getModelById(modelIds[i], isCache);
Map<String, Object> typeConfig = new HashMap<>();
if (model.getConfigJson() != null) {
typeConfig.put(model.getId(), model.getConfigJson());
// 如果是TTS类型,添加private_voice属性
if ("TTS".equals(modelTypes[i]) && voice != null) {
((Map<String, Object>) model.getConfigJson()).put("private_voice", voice);
}
// 如果是Intent类型,且type=intent_llm,则给他添加附加模型
if ("Intent".equals(modelTypes[i])) {
Map<String, Object> map = (Map<String, Object>) model.getConfigJson();
if ("intent_llm".equals(map.get("type"))) {
intentLLMModelId = (String) map.get("llm");
if (intentLLMModelId != null && intentLLMModelId.equals(llmModelId)) {
intentLLMModelId = null;
}
} else if ("function_call".equals(map.get("type"))) {
String functionStr = (String) map.get("functions");
if (StringUtils.isNotBlank(functionStr)) {
String[] functions = functionStr.split("\\;");
map.put("functions", functions);
}
}
}
// 如果是LLM类型,且intentLLMModelId不为空,则添加附加模型
if ("LLM".equals(modelTypes[i]) && intentLLMModelId != null) {
ModelConfigEntity intentLLM = modelConfigService.getModelById(intentLLMModelId, isCache);
typeConfig.put(intentLLM.getId(), intentLLM.getConfigJson());
}
}
result.put(modelTypes[i], typeConfig);
selectedModule.put(modelTypes[i], model.getId());
}
result.put("selected_module", selectedModule);
if (StringUtils.isNotBlank(prompt)) {
prompt = prompt.replace("{{assistant_name}}", StringUtils.isBlank(assistantName) ? "小智" : assistantName);
}
result.put("prompt", prompt);
}
}
@@ -1,83 +1,95 @@
package xiaozhi.modules.device.controller; package xiaozhi.modules.device.controller;
import io.swagger.v3.oas.annotations.Operation; import java.util.List;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
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.web.bind.annotation.*; import org.springframework.web.bind.annotation.GetMapping;
import xiaozhi.common.constant.Constant; import org.springframework.web.bind.annotation.PathVariable;
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.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.page.PageData;
import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.user.UserDetail; import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.JsonUtils;
import xiaozhi.common.utils.Result; import xiaozhi.common.utils.Result;
import xiaozhi.modules.device.dto.DeviceHeaderDTO; import xiaozhi.modules.device.dto.DeviceRegisterDTO;
import xiaozhi.modules.device.dto.DeviceUnBindDTO; import xiaozhi.modules.device.dto.DeviceUnBindDTO;
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;
import java.util.List; @Tag(name = "设备管理")
import java.util.Map;
@AllArgsConstructor @AllArgsConstructor
@RestController @RestController
@RequestMapping("/device") @RequestMapping("/device")
@Tag(name = "设备管理")
public class DeviceController { public class DeviceController {
private final DeviceService deviceService; private final DeviceService deviceService;
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
@PostMapping("/bind/{agentId}/{deviceCode}")
@PostMapping("/bind/{deviceCode}")
@Operation(summary = "绑定设备") @Operation(summary = "绑定设备")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result<DeviceEntity> bindDevice(@PathVariable String deviceCode) { public Result<Void> bindDevice(@PathVariable String agentId, @PathVariable String deviceCode) {
UserDetail user = SecurityUser.getUser(); deviceService.deviceActivation(agentId, deviceCode);
return new Result<>();
String deviceHeaders = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode));
if (StringUtils.isBlank(deviceHeaders)) {
return new Result<DeviceEntity>().error(ErrorCode.DEVICE_CAPTCHA_ERROR);
}
DeviceHeaderDTO deviceHeader = JsonUtils.parseObject(deviceHeaders.getBytes(), DeviceHeaderDTO.class);
DeviceEntity device = deviceService.bindDevice(user.getId(), deviceHeader);
return new Result<DeviceEntity>().ok(device);
} }
@GetMapping("/bind") @PostMapping("/register")
@Operation(summary = "注册设备")
public Result<String> registerDevice(@RequestBody DeviceRegisterDTO deviceRegisterDTO) {
String macAddress = deviceRegisterDTO.getMacAddress();
if (StringUtils.isBlank(macAddress)) {
return new Result<String>().error(ErrorCode.NOT_NULL, "mac地址不能为空");
}
// 生成六位验证码
String code = String.valueOf(Math.random()).substring(2, 8);
String key = RedisKeys.getDeviceCaptchaKey(code);
String existsMac = null;
do {
existsMac = (String) redisUtils.get(key);
} while (StringUtils.isNotBlank(existsMac));
redisUtils.set(key, macAddress);
return new Result<String>().ok(code);
}
@GetMapping("/bind/{agentId}")
@Operation(summary = "获取已绑定设备") @Operation(summary = "获取已绑定设备")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result<List<DeviceEntity>> getUserDevices() { public Result<List<DeviceEntity>> getUserDevices(@PathVariable String agentId) {
UserDetail user = SecurityUser.getUser(); UserDetail user = SecurityUser.getUser();
List<DeviceEntity> devices = deviceService.getUserDevices(user.getId()); List<DeviceEntity> devices = deviceService.getUserDevices(user.getId(), agentId);
return new Result<List<DeviceEntity>>().ok(devices); return new Result<List<DeviceEntity>>().ok(devices);
} }
@PostMapping("/unbind") @PostMapping("/unbind")
@Operation(summary = "解绑设备") @Operation(summary = "解绑设备")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) { public Result<Void> unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) {
UserDetail user = SecurityUser.getUser(); UserDetail user = SecurityUser.getUser();
deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId()); deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId());
return new Result(); return new Result<Void>();
} }
@GetMapping("/all") @PutMapping("/enableOta/{id}/{status}")
@Operation(summary = "设备列表(管理员)") @Operation(summary = "启用/关闭OTA自动升级")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:normal")
@Parameters({ public Result<Void> enableOtaUpgrade(@PathVariable String id, @PathVariable Integer status) {
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), DeviceEntity entity = deviceService.selectById(id);
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true), if (entity == null) {
}) return new Result<Void>().error("设备不存在");
public Result<PageData<DeviceEntity>> adminDeviceList( }
@Parameter(hidden = true) @RequestParam Map<String, Object> params) { entity.setAutoUpdate(status);
PageData<DeviceEntity> page = deviceService.adminDeviceList(params); deviceService.updateById(entity);
return new Result<PageData<DeviceEntity>>().ok(page); return new Result<Void>();
} }
} }
@@ -0,0 +1,105 @@
package xiaozhi.modules.device.controller;
import java.nio.charset.StandardCharsets;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.constant.Constant;
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.device.utils.NetworkUtil;
import xiaozhi.modules.sys.service.SysParamsService;
@Tag(name = "设备管理", description = "OTA 相关接口")
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/ota/")
public class OTAController {
private final DeviceService deviceService;
private final SysParamsService sysParamsService;
@Operation(summary = "OTA版本和设备激活状态检查")
@PostMapping
public ResponseEntity<String> checkOTAVersion(
@RequestBody DeviceReportReqDTO deviceReportReqDTO,
@Parameter(name = "Device-Id", description = "设备唯一标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Device-Id") String deviceId,
@Parameter(name = "Client-Id", description = "客户端标识", required = false, in = ParameterIn.HEADER) @RequestHeader(value = "Client-Id", required = false) String clientId) {
if (StringUtils.isBlank(deviceId)) {
return createResponse(DeviceReportRespDTO.createError("Device ID is required"));
}
if (StringUtils.isBlank(clientId)) {
clientId = deviceId;
}
String macAddress = deviceReportReqDTO.getMacAddress();
boolean macAddressValid = NetworkUtil.isMacAddressValid(macAddress);
// 设备Id和Mac地址应是一致的, 并且必须需要application字段
if (!deviceId.equals(macAddress) || !macAddressValid || deviceReportReqDTO.getApplication() == null) {
return createResponse(DeviceReportRespDTO.createError("Invalid OTA request"));
}
return createResponse(deviceService.checkDeviceActive(macAddress, clientId, deviceReportReqDTO));
}
@Operation(summary = "设备快速检查激活状态")
@PostMapping("activate")
public ResponseEntity<String> activateDevice(
@Parameter(name = "Device-Id", description = "设备唯一标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Device-Id") String deviceId,
@Parameter(name = "Client-Id", description = "客户端标识", required = false, in = ParameterIn.HEADER) @RequestHeader(value = "Client-Id", required = false) String clientId) {
if (StringUtils.isBlank(deviceId)) {
return ResponseEntity.status(202).build();
}
DeviceEntity device = deviceService.getDeviceByMacAddress(deviceId);
if (device == null) {
return ResponseEntity.status(202).build();
}
return ResponseEntity.ok("success");
}
@GetMapping
@Hidden
public ResponseEntity<String> getOTA() {
String wsUrl = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true);
if (StringUtils.isBlank(wsUrl) || wsUrl.equals("null")) {
return ResponseEntity.ok("OTA接口不正常,缺少websocket地址,请登录智控台,在参数管理找到【server.websocket】配置");
}
String otaUrl = sysParamsService.getValue(Constant.SERVER_OTA, true);
if (StringUtils.isBlank(otaUrl) || otaUrl.equals("null")) {
return ResponseEntity.ok("OTA接口不正常,缺少ota地址,请登录智控台,在参数管理找到【server.ota】配置");
}
return ResponseEntity.ok("OTA接口运行正常,websocket集群数量:" + wsUrl.split(";").length);
}
@SneakyThrows
private ResponseEntity<String> createResponse(DeviceReportRespDTO deviceReportRespDTO) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String json = objectMapper.writeValueAsString(deviceReportRespDTO);
byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.contentLength(jsonBytes.length)
.body(json);
}
}
@@ -0,0 +1,291 @@
package xiaozhi.modules.device.controller;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.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 org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.utils.Result;
import xiaozhi.common.validator.ValidatorUtils;
import xiaozhi.modules.device.entity.OtaEntity;
import xiaozhi.modules.device.service.OtaService;
@Tag(name = "设备管理", description = "OTA 相关接口")
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/otaMag")
public class OTAMagController {
private static final Logger logger = LoggerFactory.getLogger(OTAController.class);
private final OtaService otaService;
private final RedisUtils redisUtils;
@GetMapping
@Operation(summary = "分页查询 OTA 固件信息")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true)
})
@RequiresPermissions("sys:role:superAdmin")
public Result<PageData<OtaEntity>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
ValidatorUtils.validateEntity(params);
PageData<OtaEntity> page = otaService.page(params);
return new Result<PageData<OtaEntity>>().ok(page);
}
@GetMapping("{id}")
@Operation(summary = "信息 OTA 固件信息")
@RequiresPermissions("sys:role:superAdmin")
public Result<OtaEntity> get(@PathVariable("id") String id) {
OtaEntity data = otaService.selectById(id);
return new Result<OtaEntity>().ok(data);
}
@PostMapping
@Operation(summary = "保存 OTA 固件信息")
@RequiresPermissions("sys:role:superAdmin")
public Result<Void> save(@RequestBody OtaEntity entity) {
if (entity == null) {
return new Result<Void>().error("固件信息不能为空");
}
if (StringUtils.isBlank(entity.getFirmwareName())) {
return new Result<Void>().error("固件名称不能为空");
}
if (StringUtils.isBlank(entity.getType())) {
return new Result<Void>().error("固件类型不能为空");
}
if (StringUtils.isBlank(entity.getVersion())) {
return new Result<Void>().error("版本号不能为空");
}
try {
otaService.save(entity);
return new Result<Void>();
} catch (RuntimeException e) {
return new Result<Void>().error(e.getMessage());
}
}
@DeleteMapping("/{id}")
@Operation(summary = "OTA 删除")
@RequiresPermissions("sys:role:superAdmin")
public Result<Void> delete(@PathVariable("id") String[] ids) {
if (ids == null || ids.length == 0) {
return new Result<Void>().error("删除的固件ID不能为空");
}
otaService.delete(ids);
return new Result<Void>();
}
@PutMapping("/{id}")
@Operation(summary = "修改 OTA 固件信息")
@RequiresPermissions("sys:role:superAdmin")
public Result<?> update(@PathVariable("id") String id, @RequestBody OtaEntity entity) {
if (entity == null) {
return new Result<>().error("固件信息不能为空");
}
entity.setId(id);
try {
otaService.update(entity);
return new Result<>();
} catch (RuntimeException e) {
return new Result<>().error(e.getMessage());
}
}
@GetMapping("/getDownloadUrl/{id}")
@Operation(summary = "获取 OTA 固件下载链接")
@RequiresPermissions("sys:role:superAdmin")
public Result<String> getDownloadUrl(@PathVariable("id") String id) {
String uuid = UUID.randomUUID().toString();
redisUtils.set(RedisKeys.getOtaIdKey(uuid), id);
return new Result<String>().ok(uuid);
}
@GetMapping("/download/{uuid}")
@Operation(summary = "下载固件文件")
public ResponseEntity<byte[]> downloadFirmware(@PathVariable("uuid") String uuid) {
String id = (String) redisUtils.get(RedisKeys.getOtaIdKey(uuid));
if (StringUtils.isBlank(id)) {
return ResponseEntity.notFound().build();
}
// 检查下载次数
String downloadCountKey = RedisKeys.getOtaDownloadCountKey(uuid);
Integer downloadCount = (Integer) redisUtils.get(downloadCountKey);
if (downloadCount == null) {
downloadCount = 0;
}
// 如果下载次数超过3次,返回404
if (downloadCount >= 3) {
redisUtils.delete(downloadCountKey);
redisUtils.delete(RedisKeys.getOtaIdKey(uuid));
logger.warn("Download limit exceeded for UUID: {}", uuid);
return ResponseEntity.notFound().build();
}
redisUtils.set(downloadCountKey, downloadCount + 1);
try {
// 获取固件信息
OtaEntity otaEntity = otaService.selectById(id);
if (otaEntity == null || StringUtils.isBlank(otaEntity.getFirmwarePath())) {
logger.warn("Firmware not found or path is empty for ID: {}", id);
return ResponseEntity.notFound().build();
}
// 获取文件路径 - 确保路径是绝对路径或正确的相对路径
String firmwarePath = otaEntity.getFirmwarePath();
Path path;
// 检查是否是绝对路径
if (Paths.get(firmwarePath).isAbsolute()) {
path = Paths.get(firmwarePath);
} else {
// 如果是相对路径,则从当前工作目录解析
path = Paths.get(System.getProperty("user.dir"), firmwarePath);
}
logger.info("Attempting to download firmware for ID: {}, DB path: {}, resolved path: {}",
id, firmwarePath, path.toAbsolutePath());
if (!Files.exists(path) || !Files.isRegularFile(path)) {
// 尝试直接从firmware目录下查找文件名
String fileName = new File(firmwarePath).getName();
Path altPath = Paths.get(System.getProperty("user.dir"), "firmware", fileName);
logger.info("File not found at primary path, trying alternative path: {}", altPath.toAbsolutePath());
if (Files.exists(altPath) && Files.isRegularFile(altPath)) {
path = altPath;
} else {
logger.error("Firmware file not found at either path: {} or {}",
path.toAbsolutePath(), altPath.toAbsolutePath());
return ResponseEntity.notFound().build();
}
}
// 读取文件内容
byte[] fileContent = Files.readAllBytes(path);
// 设置响应头
String originalFilename = otaEntity.getType() + "_" + otaEntity.getVersion();
if (firmwarePath.contains(".")) {
String extension = firmwarePath.substring(firmwarePath.lastIndexOf("."));
originalFilename += extension;
}
// 清理文件名,移除不安全字符
String safeFilename = originalFilename.replaceAll("[^a-zA-Z0-9._-]", "_");
logger.info("Providing download for firmware ID: {}, filename: {}, size: {} bytes",
id, safeFilename, fileContent.length);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + safeFilename + "\"")
.body(fileContent);
} catch (IOException e) {
logger.error("Error reading firmware file for ID: {}", id, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
} catch (Exception e) {
logger.error("Unexpected error during firmware download for ID: {}", id, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/upload")
@Operation(summary = "上传固件文件")
@RequiresPermissions("sys:role:superAdmin")
public Result<String> uploadFirmware(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return new Result<String>().error("上传文件不能为空");
}
// 检查文件扩展名
String originalFilename = file.getOriginalFilename();
if (originalFilename == null) {
return new Result<String>().error("文件名不能为空");
}
String extension = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
if (!extension.equals(".bin") && !extension.equals(".apk")) {
return new Result<String>().error("只允许上传.bin和.apk格式的文件");
}
try {
// 计算文件的MD5值
String md5 = calculateMD5(file);
// 设置存储路径
String uploadDir = "uploadfile";
Path uploadPath = Paths.get(uploadDir);
// 如果目录不存在,创建目录
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 使用MD5作为文件名,固定使用.bin扩展名
String uniqueFileName = md5 + extension;
Path filePath = uploadPath.resolve(uniqueFileName);
// 检查文件是否已存在
if (Files.exists(filePath)) {
return new Result<String>().ok(filePath.toString());
}
// 保存文件
Files.copy(file.getInputStream(), filePath);
// 返回文件路径
return new Result<String>().ok(filePath.toString());
} catch (IOException | NoSuchAlgorithmException e) {
return new Result<String>().error("文件上传失败:" + e.getMessage());
}
}
private String calculateMD5(MultipartFile file) throws IOException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(file.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
@@ -1,7 +1,9 @@
package xiaozhi.modules.device.dao; package xiaozhi.modules.device.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.device.entity.DeviceEntity; import xiaozhi.modules.device.entity.DeviceEntity;
@Mapper @Mapper
@@ -0,0 +1,15 @@
package xiaozhi.modules.device.dao;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import xiaozhi.modules.device.entity.OtaEntity;
/**
* OTA固件管理
*/
@Mapper
public interface OtaDao extends BaseMapper<OtaEntity> {
}
@@ -0,0 +1,27 @@
package xiaozhi.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 设备绑定的DTO
*
* @author zjy
* @since 2025-3-28
*/
@Data
@AllArgsConstructor
@Schema(description = "设备连接头信息")
public class DeviceBindDTO {
@Schema(description = "mac地址")
private String macAddress;
@Schema(description = "所属用户id")
private Long userId;
@Schema(description = "智能体id")
private String agentId;
}
@@ -1,19 +0,0 @@
package xiaozhi.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "设备连接头信息")
public class DeviceHeaderDTO {
@Schema(description = "设备ID")
private String deviceId;
@Schema(description = "协议版本号")
private Long protocolVersion;
@Schema(description = "认证信息")
private String authorization;
}
@@ -0,0 +1,27 @@
package xiaozhi.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import lombok.Data;
/**
* 查询所有设备的DTO
*
* @author zjy
* @since 2025-3-21
*/
@Data
@Schema(description = "查询所有设备的DTO")
public class DevicePageUserDTO {
@Schema(description = "设备关键词")
private String keywords;
@Schema(description = "页数")
@Min(value = 0, message = "{page.number}")
private String page;
@Schema(description = "显示列数")
@Min(value = 0, message = "{limit.number}")
private String limit;
}
@@ -0,0 +1,23 @@
package xiaozhi.modules.device.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* 设备注册头信息
*
* @author zjy
* @since 2025-3-28
*/
@Setter
@Getter
@Schema(description = "设备注册头信息")
public class DeviceRegisterDTO implements Serializable {
@Schema(description = "mac地址")
private String macAddress;
}
@@ -0,0 +1,148 @@
package xiaozhi.modules.device.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
@Setter
@Getter
@Schema(description = "设备固件信息上报求请求体")
public class DeviceReportReqDTO implements Serializable {
private static final long serialVersionUID = 1L;
// region 实体属性
@Schema(description = "板子固件版本号")
private Integer version;
@Schema(description = "闪存大小(单位:字节)")
@JsonProperty("flash_size")
private Integer flashSize;
@Schema(description = "最小空闲堆内存(字节)")
@JsonProperty("minimum_free_heap_size")
private Integer minimumFreeHeapSize;
@Schema(description = "设备 MAC 地址")
@JsonProperty("mac_address")
private String macAddress;
@Schema(description = "设备唯一标识 UUID")
private String uuid;
@Schema(description = "芯片型号名称")
@JsonProperty("chip_model_name")
private String chipModelName;
@Schema(description = "芯片详细信息")
@JsonProperty("chip_info")
private ChipInfo chipInfo;
@Schema(description = "应用程序信息")
private Application application;
@Schema(description = "分区表列表")
@JsonProperty("partition_table")
private List<Partition> partitionTable;
@Schema(description = "当前运行的 OTA 分区信息")
private OtaInfo ota;
@Schema(description = "板子配置信息")
private BoardInfo board;
// endregion
@Getter
@Setter
@Schema(description = "芯片信息")
public static class ChipInfo {
@Schema(description = "芯片模型代码")
private Integer model;
@Schema(description = "核心数")
private Integer cores;
@Schema(description = "硬件修订版本")
private Integer revision;
@Schema(description = "芯片功能标志位")
private Integer features;
}
@Getter
@Setter
@Schema(description = "板子编译信息")
public static class Application {
@Schema(description = "名称")
private String name;
@Schema(description = "应用版本号")
private String version;
@Schema(description = "编译时间(UTC ISO格式)")
@JsonProperty("compile_time")
private String compileTime;
@Schema(description = "ESP-IDF 版本号")
@JsonProperty("idf_version")
private String idfVersion;
@Schema(description = "ELF 文件 SHA256 校验")
@JsonProperty("elf_sha256")
private String elfSha256;
}
@Getter
@Setter
@Schema(description = "分区信息")
public static class Partition {
@Schema(description = "分区标签名")
private String label;
@Schema(description = "分区类型")
private Integer type;
@Schema(description = "子类型")
private Integer subtype;
@Schema(description = "起始地址")
private Integer address;
@Schema(description = "分区大小")
private Integer size;
}
@Getter
@Setter
@Schema(description = "OTA信息")
public static class OtaInfo {
@Schema(description = "当前OTA标签")
private String label;
}
@Getter
@Setter
@Schema(description = "板子连接和网络信息")
public static class BoardInfo {
@Schema(description = "板子类型")
private String type;
@Schema(description = "连接的 Wi-Fi SSID")
private String ssid;
@Schema(description = "Wi-Fi 信号强度(RSSI")
private Integer rssi;
@Schema(description = "Wi-Fi 信道")
private Integer channel;
@Schema(description = "IP 地址")
private String ip;
@Schema(description = "MAC 地址")
private String mac;
}
}

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