Compare commits

...
222 Commits
Author SHA1 Message Date
欣南科技andGitHub d721270bed Merge pull request #2915 from xinnan-tech/test-arm64-docker
update:兼容arm架构字体
2026-01-28 11:40:26 +08:00
hrz fe2e178b19 update:兼容arm架构字体 2026-01-28 11:39:50 +08:00
欣南科技andGitHub 21337e5046 Merge pull request #2914 from xinnan-tech/test-arm64-docker
update:兼容arm架构字体
2026-01-28 11:34:54 +08:00
hrz 1c8b271e20 update:兼容arm架构字体 2026-01-28 11:34:16 +08:00
欣南科技andGitHub 3d1160b6bb Merge pull request #2913 from xinnan-tech/test-arm64-docker
update:调试编译arm镜像
2026-01-28 11:13:40 +08:00
hrz 086c4a55e6 update:调试编译arm镜像 2026-01-28 11:12:58 +08:00
欣南科技andGitHub 889f2f9a75 Merge pull request #2912 from xinnan-tech/test-arm64-docker
update:兼容arm架构字体
2026-01-28 11:03:04 +08:00
hrz 07b686be14 update:兼容arm架构字体 2026-01-28 11:02:17 +08:00
欣南科技andGitHub 4d6b453485 Merge pull request #2910 from xinnan-tech/test-arm64-docker
add:增加linux/arm64docker镜像编译
2026-01-28 09:16:25 +08:00
hrz 9909045f9d add:增加linux/arm64docker镜像编译 2026-01-28 09:15:40 +08:00
hrzandGitHub b0dc4d88db Merge pull request #2908 from xinnan-tech/py_fix_text
fix: 保存音频数据文件时文本不记录
2026-01-27 17:53:51 +08:00
Sakura-RanChen bbf3858735 fix: 保存音频数据文件时文本不记录 2026-01-27 16:22:35 +08:00
欣南科技andGitHub 73573e2f12 Merge pull request #2907 from xinnan-tech/live2d-actions
update:增加版本号,解决html引用文件缓存问题
2026-01-27 16:13:31 +08:00
hrz 99e136786e update:增加版本号,解决html引用文件缓存问题 2026-01-27 16:12:55 +08:00
欣南科技andGitHub 205be0e4ec Merge pull request #2906 from xinnan-tech/live2d-actions
add:麦克风权限的判断
2026-01-27 16:03:49 +08:00
hrz 2e64f45a31 update:优化代码排版 2026-01-27 15:59:46 +08:00
hrz 818b4f2f6d update:增加版本号,解决html引用文件缓存问题 2026-01-27 15:52:34 +08:00
hrz 5d72f9f79d update:由于现在的模型本身动作有限,使用起来体验不佳,暂时移除动作控制的代码 2026-01-27 15:45:53 +08:00
hrzandGitHub 0ee349df2d Merge pull request #2905 from xinnan-tech/py_fix_minimaxLog
补充minimax接口错误信息
2026-01-27 11:28:17 +08:00
Sakura-RanChen 4dde66339b 补充minimax接口错误信息 2026-01-27 11:22:32 +08:00
hrzandGitHub 0064f975c2 Merge pull request #2903 from spider-yamet/feature/issue-2896-clean
feat: implement Issue 2896 - Live2D Actions and Microphone Detection
2026-01-27 11:17:31 +08:00
rainv123andGitHub 43234f4798 Merge pull request #2836 from xinnan-tech/py_audio_rate
update: 自定义配置输出音频采样率
2026-01-27 10:58:32 +08:00
spider-yamet 48034fc000 Translate log messages to Chinese in recorder.js 2026-01-26 15:03:23 -08:00
spider-yamet 3fc33e9095 Optimize code density: simplify changed files while keeping comments (reduce 364 lines) 2026-01-26 14:48:08 -08:00
spider-yamet da71dce860 Optimize code : minify test files 2026-01-26 14:37:07 -08:00
spider-yamet b7e4408a0f Add browser-based unit tests for xiaozhi test modules
- Add browser-compatible test files (no npm required)

  - recorder.test.browser.js: 8 tests for microphone and HTTP detection

  - tools.test.browser.js: 5 tests for Live2D actions and error handling

- Add test runner (test-runner.html) with built-in test framework

- Add null safety checks in tools.js for DOM element access

- Add documentation (English and Chinese versions)

  - README_TESTS.md / README_TESTS_CN.md: Complete test guide

  - QUICK_START_TEST.md / QUICK_START_TEST_CN.md: Quick start guides

- Total: 13 unit tests covering microphone detection, HTTP detection, Live2D actions, and error handling
2026-01-26 10:18:40 -08:00
spider-yamet 4f4f8ca54e docs: add testing guide and fix Chinese text in controller.js 2026-01-26 08:30:39 -08:00
spider-yamet 99c12a0fed fix: translate corrupted Chinese text to proper Chinese in controller.js 2026-01-26 08:05:02 -08:00
spider-yamet dcd0ef3f40 fix: translate corrupted Chinese text to proper Chinese in controller.js
- Fix corrupted Chinese characters in addMCPTool function
- Translate placeholder and button text to proper Chinese
2026-01-26 07:57:13 -08:00
spider-yamet 2f53b921fe feat: implement Issue 2896 - Live2D Actions and Microphone Detection
- Add Live2D action tools (smile, wave, generic actions) as MCP tools
- Implement microphone availability detection
- Handle HTTP non-localhost access scenarios
- Update UI to reflect microphone availability state
- Translate all comments and messages to proper Chinese/English
- Add test files for new functionality
2026-01-26 07:44:33 -08:00
Sakura-RanChen 3051dc8327 更改默认输出采样率与设备端适配 2026-01-26 17:35:45 +08:00
Sakura-RanChen c313997a61 去除调试输出 2026-01-26 16:33:09 +08:00
欣南科技andGitHub 07c0c764d3 Merge pull request #2898 from xinnan-tech/fix-ChatHistoryConf-value
set ChatHistoryConf default value
2026-01-26 15:11:46 +08:00
hrz e339a501aa set ChatHistoryConf default value 2026-01-26 15:11:11 +08:00
hrzandGitHub f2c541b4ab Merge pull request #2897 from xinnan-tech/fix-path
fix: 修复项目访问地址
2026-01-26 15:07:47 +08:00
zhuoqinglian 206af1c074 fix: 修复项目访问地址 2026-01-26 15:06:05 +08:00
欣南科技andGitHub 344729c9a6 Merge pull request #2895 from xinnan-tech/fix-digital-human-initialize
add:响应mcp初始化请求
2026-01-26 12:51:09 +08:00
hrz 3b1c2d3b1b add:响应mcp初始化请求 2026-01-26 12:42:11 +08:00
wengzhandGitHub 3660af9619 Merge pull request #2881 from xinnan-tech/generator
feat: 添加语音盒子在线烧录功能
2026-01-26 09:41:42 +08:00
zhuoqinglian 4905ba836c fix: 修复语音盒子自定义主题表情无法跟随语气变化问题 2026-01-26 09:33:58 +08:00
Sakura-RanChen ce00c862fe HuoshanDoubleStreamTTS增加自定义配置 2026-01-23 17:16:46 +08:00
zhuoqinglian a50229431b fix: 修复https协议导致无法上传表情的问题 2026-01-23 17:07:23 +08:00
wengzhandGitHub 275102f5b7 Merge pull request #2882 from xinnan-tech/fix-devices-loading
fix: 修复获取设备状态接口报错时, 没有执行失败回调导致loading一直存在
2026-01-23 15:36:46 +08:00
lww155 ff6286ffcc fix: 修复获取设备状态接口报错时, 没有执行失败回调导致loading一直存在 2026-01-23 15:33:44 +08:00
zhuoqinglian 974fe6c3b7 feat: 添加语音盒子在线烧录功能 2026-01-23 15:29:43 +08:00
wengzhandGitHub b78292b270 Merge pull request #2880 from xinnan-tech/perf-manager-api-business
pred:优化MCP工具名称列表顺序问题
2026-01-23 14:13:23 +08:00
DaGou12138 3559a7018c 优化MCP工具名称列表顺序问题 2026-01-23 14:03:00 +08:00
wengzhandGitHub 3222d6175d Merge pull request #2879 from xinnan-tech/fix-digital-human
update: 拨号成功后自动开启录音
2026-01-23 09:22:03 +08:00
lww155 28f9f23449 update: 拨号成功后自动开启录音 2026-01-23 09:13:19 +08:00
wengzhandGitHub 5e7d192ebc Merge pull request #2877 from xinnan-tech/fix-digital-human
update: 调整配置获取
2026-01-22 17:55:46 +08:00
lww155 d6a32416d7 update: 调整配置获取 2026-01-22 17:48:40 +08:00
wengzhandGitHub 6a2c01763e Merge pull request #2876 from xinnan-tech/fix-digital-human
Fix digital human
2026-01-22 17:27:36 +08:00
lww155 e1dd272708 update: 关闭设置框时保存配置, 阻止按钮事件冒泡, 调整配置获取方式 2026-01-22 17:23:27 +08:00
lww155 3e7bc0606a update: 优化背景加载代码 2026-01-22 16:16:09 +08:00
lww155 9d9ca1475d fix: 修复重复请求ota与重复建立连接问题 2026-01-22 15:54:26 +08:00
lww155 43ddc0135c update: 去除未使用代码 2026-01-22 15:39:40 +08:00
欣南科技andGitHub 98f907a97b Merge pull request #2874 from xinnan-tech/manager-mcp-api
update:增加mcp调用后数据类型的判断
2026-01-22 14:46:23 +08:00
hrz b2ddf060c9 update:增加mcp调用后数据类型的判断 2026-01-22 14:42:43 +08:00
欣南科技andGitHub 5e612276db Merge pull request #2873 from xinnan-tech/manager-mcp-api
自定义表情固件接口
2026-01-22 14:02:48 +08:00
hrz cbb293e7fa add:上传自定义表情固件接口 2026-01-22 13:27:10 +08:00
hrz fdb050f6ed add:获取设备的mcp工具列表 2026-01-21 22:58:58 +08:00
wengzhandGitHub f83ddf79fa Merge pull request #2868 from xinnan-tech/fix-digital-human
fix: 修复未绑定设备时状态处理
2026-01-20 17:48:35 +08:00
lww155 95b340a7a3 fix: 修复未绑定设备时状态处理 2026-01-20 17:39:06 +08:00
milieyoandGitHub d46ac89e58 Merge pull request #2867 from xinnan-tech/update-style
style: 优化部分样式,保持一致性
2026-01-20 15:45:00 +08:00
zhuoqinglian 80b0912f84 Merge branch 'main' into update-style 2026-01-20 15:36:22 +08:00
zhuoqinglian e0b4431064 style: 优化部分样式,保持一致性 2026-01-20 15:35:41 +08:00
milieyoandGitHub e3f16f91cf Merge pull request #2863 from xinnan-tech/fix-digital-human
update: 新增聊天输入框
2026-01-20 09:32:23 +08:00
milieyoandGitHub 5f39e5c308 Merge pull request #2860 from xinnan-tech/perf-manager-api-business
perf:优化字典接口、模型接口、音色资源部分业务逻辑
2026-01-20 09:17:24 +08:00
lww155 83304c9ff4 update: 新增聊天输入框 2026-01-20 09:16:45 +08:00
DaGou12138 863cfb575c 优化音色资源相关业务逻辑 2026-01-19 17:55:38 +08:00
DaGou12138 2944040648 优化音色资源相关业务逻辑 2026-01-19 17:19:45 +08:00
DaGou12138 5f19b9b0d0 优化字典接口、模型接口部分业务逻辑 2026-01-19 15:19:48 +08:00
milieyoandGitHub 2948ed091c Merge pull request #2858 from xinnan-tech/perf-manager-api-device
perf:优化知识库相关业务逻辑
2026-01-19 14:24:27 +08:00
DaGou12138 97ee397b54 Merge branch 'refs/heads/main' into perf-manager-api-device 2026-01-19 12:00:07 +08:00
DaGou12138 f7fbe7efbe 优化修改知识库接口部分业务逻辑 2026-01-19 11:59:25 +08:00
hrzandGitHub ce6b0bd099 Merge pull request #2857 from xinnan-tech/build-generator
fix: 修复项目部署二级目录自定义语音盒子主题文件资源异常问题
2026-01-19 11:45:08 +08:00
zhuoqinglian 8643960104 Merge branch 'main' into build-generator 2026-01-19 11:33:11 +08:00
zhuoqinglian b4549ebc59 fix: 修复项目部署二级目录自定义语音盒子主题文件资源异常问题 2026-01-19 11:30:28 +08:00
hrzandGitHub a602cddd05 Merge pull request #2850 from xinnan-tech/build-generator
feat: 添加语音盒子自定义主题功能
2026-01-17 14:04:12 +08:00
hrzandGitHub c4278864d1 Merge pull request #2852 from xinnan-tech/fix_device-mcp
fix: 修复开机立刻唤醒导致客户端 MCP 工具列表为空的问题
2026-01-16 19:02:58 +08:00
3030332422 7094548ecf fix: 修复开机立刻唤醒导致客户端 MCP 工具列表为空的问题 2026-01-16 18:04:04 +08:00
milieyoandGitHub 363fffada8 Merge pull request #2849 from xinnan-tech/fix-multilingual-bug
fix: 语音合成-音色管理,克隆音频路径、克隆音频文本多语言异常处理
2026-01-16 17:14:28 +08:00
milieyoandGitHub c2c3eb7106 Merge pull request #2848 from xinnan-tech/perf-manager-api-device
perf:优化设备在线状态接口相关逻辑
2026-01-16 17:13:30 +08:00
zhuoqinglian 8e19a64f15 fix: 语音合成-音色管理,克隆音频路径、克隆音频文本多语言异常处理 2026-01-16 17:05:46 +08:00
linjiaqin 9042b7e6d7 优化智能体模块相关逻辑 2026-01-16 16:43:04 +08:00
linjiaqin ad1855cfb5 优化设备在线接口相关逻辑 2026-01-16 15:45:42 +08:00
zhuoqinglian efa0d1095d feat: 添加语音盒子自定义主题功能 2026-01-16 14:52:40 +08:00
hrzandGitHub a8c13d740b Merge pull request #2842 from hlleng/fix/model-remark
fix: 修复智控台模型配置中备注不能保存的问题
2026-01-15 20:49:50 +08:00
Honglin Leng 7475e9829f fix: persist model remark on update 2026-01-15 20:15:25 +08:00
milieyoandGitHub b7dde1dae2 Merge pull request #2839 from xinnan-tech/fix-digital-human
修复背景404、通话接通后自动录音、模型加载Loading
2026-01-15 14:26:50 +08:00
lww155 778c4560fd update: 新增模型加载 2026-01-15 09:20:22 +08:00
lww155 fa31096cd8 update: 拨号成功后开启自动录音 2026-01-15 09:19:25 +08:00
Sakura-RanChen fd1dd39463 update: 自定义配置输出音频采样率 2026-01-14 17:54:50 +08:00
lww9029 ecd3e12a41 fix: 切换背景404问题 2026-01-14 11:18:41 +08:00
欣南科技andGitHub 0a8121e849 Merge pull request #2831 from xinnan-tech/test_page_fix
update:修复live2d模型加在路径bug
2026-01-12 18:06:41 +08:00
hrz e93f9d7111 update:修复live2d模型加在路径bug 2026-01-12 18:05:39 +08:00
hrzandGitHub fdf98985a1 Merge pull request #2826 from xinnan-tech/test_page_fix
update:增加live2d模型,修改测试页面样式
2026-01-12 17:44:30 +08:00
hrz 6c3609e79e update:优化图片大小 2026-01-12 17:43:30 +08:00
rainv123 a9aa709502 update:增加live2d模型,修改测试页面样式 2026-01-12 11:00:50 +08:00
milieyoandGitHub 1f6a89ada5 Merge pull request #2823 from xinnan-tech/20260109
调整智能体管理list接口排序,根据创建时间倒序返回
2026-01-09 14:32:22 +08:00
LJH-rgsze 31a1180b79 还原 2026-01-09 14:30:27 +08:00
LJH-rgsze 77b4a416a9 调整智能体管理list接口排序,根据创建时间倒序返回 2026-01-09 14:28:01 +08:00
milieyoandGitHub da7a6933f5 Merge pull request #2818 from xinnan-tech/fix_wakeup_audioRate
修复设备播放音频时打断后使用唤醒词时流控器未重置造成的死锁
2026-01-08 17:27:53 +08:00
Sakura-RanChen 54cac0d2fd fix: 修复当手动设备上个会话正常结束后,使用唤醒词唤醒音频播放不完整 2026-01-08 16:17:48 +08:00
hrzandGitHub 7fe718c887 Merge pull request #2817 from xinnan-tech/py_TTS_text
TTS文本过滤
2026-01-08 14:21:17 +08:00
hrzandGitHub 36eaefb8b0 Merge pull request #2816 from xinnan-tech/20260105
修复音色播放按钮显示错误
2026-01-08 12:39:33 +08:00
hrzandGitHub 3a61ec84b3 移除console打印 2026-01-08 12:39:07 +08:00
Sakura-RanChen c1bf92d243 修复设备播放音频时打断后使用唤醒词时流控器未重置造成的死锁 2026-01-08 11:39:04 +08:00
LJH-rgsze c1959e0795 优化代码逻辑 2026-01-08 10:10:39 +08:00
LJH-rgsze 40a6b7434b 调整判断逻辑 2026-01-07 18:08:43 +08:00
Sakura-RanChen 24519a2732 TTS文本过滤 2026-01-07 17:38:38 +08:00
LJH-rgsze c190abe4b1 用国际化重写硬编码提示 2026-01-07 17:03:56 +08:00
LJH-rgsze 07d5b3cd2a 补充-上个修改上传少了 2026-01-07 16:54:08 +08:00
LJH-rgsze 81e923b4f7 补充音色管理页面缺失的国际化 2026-01-07 16:52:36 +08:00
LJH-rgsze bcb51729a5 修复播放按钮的显示错误 2026-01-07 16:00:35 +08:00
hrzandGitHub a731c1d01f Merge pull request #2811 from xinnan-tech/py_fix_llm_text
修复独立LLM调用时文本未记录
2026-01-06 17:34:07 +08:00
Sakura-RanChen 0bb5a37ace 修复独立LLM调用时文本未记录 2026-01-06 10:29:56 +08:00
hrzandGitHub a711827996 Merge pull request #2810 from xinnan-tech/fix_report_connection
fix: 禁用httpx连接池并显式关闭响应防止连接泄漏,进程文件描述符耗尽
2026-01-05 18:00:41 +08:00
hrzandGitHub 8f854e6f74 Merge pull request #2809 from xinnan-tech/20260105
补充国际化
2026-01-05 17:59:12 +08:00
Sakura-RanChen f1b5afbf5c fix: 禁用httpx连接池并显式关闭响应防止连接泄漏 2026-01-05 17:50:23 +08:00
LJH-rgsze 84f6930389 补充国际化 2026-01-05 17:46:35 +08:00
hrzandGitHub f415dd4482 Merge pull request #2808 from xinnan-tech/20260105
修复音色克隆,ota管理,语音资源管理的分页错误
2026-01-05 17:30:46 +08:00
LJH-rgsze 927f65e106 修复参数名不匹配导致的翻页错误 2026-01-05 17:21:38 +08:00
hrzandGitHub 4b6b3082c7 Merge pull request #2794 from xinnan-tech/20260104SSL
拓展首页的搜索栏-支持同时搜索设备mac和智能体名字
2026-01-05 16:54:20 +08:00
LJH-rgsze 4c550245db 调整判断逻辑 2026-01-05 16:51:09 +08:00
LJH-rgsze 341ed22370 简化代码 2026-01-05 15:27:47 +08:00
LJH-rgsze 6851ccacf3 提取重复代码合并 2026-01-05 15:05:29 +08:00
hrzandGitHub 815af687ea Merge pull request #2798 from xinnan-tech/py_asr_emotion_language
Py asr emotion language
2026-01-05 15:03:50 +08:00
LJH-rgsze e9373b59c8 优化代码 2026-01-05 14:50:23 +08:00
Sakura-RanChen 3f3f3fcd57 更改Funserver使用模型 2026-01-05 14:46:34 +08:00
LJH-rgsze d003e66310 还原部分代码 2026-01-05 11:31:28 +08:00
Sakura-RanChen e6108ccbbd 增加ASR情绪和语种返回 2026-01-05 11:17:04 +08:00
LJH-rgsze a01f01198c 补全参数的返回 2026-01-05 11:13:01 +08:00
LJH-rgsze cc96eaeb8f 修复设备列表显示问题 2026-01-05 10:13:11 +08:00
LJH-rgsze e25ca3d1d3 调整搜索实现-从前端过滤改为后端查询 2026-01-05 10:10:17 +08:00
LJH-rgsze 9c111b2d5e 更新国际化提示 2026-01-04 18:04:26 +08:00
LJH-rgsze 436a01fb6e 删除依赖更新部分(还原) 2026-01-04 17:14:25 +08:00
LJH-rgsze 9b6620dd4a 搜索拓展 2026-01-04 16:53:38 +08:00
Sakura-RanChenandGitHub 85919e800e Merge pull request #2552 from Packeting1/multi_lang_tts
feat: 为阿里百炼流式TTS支持多语言音色
2026-01-04 15:02:24 +08:00
欣南科技andGitHub a2023a65c0 Merge pull request #2789 from xinnan-tech/test_page_token
Bump to 0.8.11
2026-01-02 22:46:55 +08:00
hrz 619370f36c Bump to 0.8.11 2026-01-02 22:45:53 +08:00
欣南科技andGitHub ea6c0e691f Merge pull request #2788 from xinnan-tech/test_page_token
update:删除认证令牌输入框,从ota接口获取令牌
2026-01-02 22:42:48 +08:00
hrz de0d82f1e8 update:删除认证令牌输入框,从ota接口获取令牌 2026-01-02 22:41:37 +08:00
hrzandGitHub c8a26d9c5e Merge pull request #2623 from xinnan-tech/dependabot/pip/main/xiaozhi-server/psutil-7.1.3
build(deps): bump psutil from 7.0.0 to 7.1.3 in /main/xiaozhi-server
2026-01-02 21:50:21 +08:00
hrz d89b842586 update:保留适配本项目python3.10的版本 2026-01-02 21:48:34 +08:00
hrzandGitHub 6dd3ceea7e Merge pull request #2627 from xinnan-tech/dependabot/pip/main/xiaozhi-server/mcp-1.22.0
build(deps): bump mcp from 1.20.0 to 1.22.0 in /main/xiaozhi-server
2026-01-02 21:39:03 +08:00
hrzandGitHub 5fe68030b0 Merge pull request #2626 from xinnan-tech/dependabot/pip/main/xiaozhi-server/torchaudio-2.9.1
build(deps): bump torchaudio from 2.2.2 to 2.9.1 in /main/xiaozhi-server
2026-01-02 21:38:45 +08:00
hrzandGitHub 5215105440 Merge pull request #2625 from xinnan-tech/dependabot/pip/main/xiaozhi-server/websockets-15.0.1
build(deps): bump websockets from 14.2 to 15.0.1 in /main/xiaozhi-server
2026-01-02 21:38:17 +08:00
hrzandGitHub fb69eb751a Merge pull request #2624 from xinnan-tech/dependabot/pip/main/xiaozhi-server/modelscope-1.32.0
build(deps): bump modelscope from 1.23.2 to 1.32.0 in /main/xiaozhi-server
2026-01-02 21:37:33 +08:00
hrzandGitHub b903d51592 Merge pull request #2777 from xinnan-tech/update_blasr_paraformer
Update blasr paraformer
2026-01-02 20:39:43 +08:00
Sakura-RanChen d1f6774e58 update: 增加火山流式情感字段支持 2025-12-30 16:46:04 +08:00
CGDandGitHub 9e55df4141 Merge pull request #2778 from xinnan-tech/fix_mcp_rag
fix:serch_from_ragflow调用失败返回详细错误信息
2025-12-30 11:06:50 +08:00
rainv123 b169e9413f fix:serch_from_ragflow调用失败返回详细错误信息 2025-12-30 11:02:56 +08:00
Sakura-RanChen c511766e9d update: 增加豆包流式多语种识别 2025-12-29 16:38:23 +08:00
hrzandGitHub e6f9ceb7fa Merge pull request #2767 from xinnan-tech/web_Hidden_state
update:仅在 MQTT 服务可用时显示设备在线状态列
2025-12-27 14:24:01 +08:00
3030332422 eab8b48c9c update:仅在 MQTT 服务可用时显示设备在线状态列 2025-12-26 17:39:08 +08:00
FAN-yeB 46abb1009a update:初始化阿里云百炼asr供应器 手动模式待更改 2025-12-26 16:13:14 +08:00
CGDandGitHub 2a42d86db0 Merge pull request #2581 from xinnan-tech/py_tts_huoshan
自主选择链接复用功能
2025-12-26 14:47:01 +08:00
CGDandGitHub 68b0d9654c Merge branch 'main' into py_tts_huoshan 2025-12-26 14:46:51 +08:00
hrzandGitHub 8f96cf13ac Merge pull request #2764 from xinnan-tech/fix_ServerSideManage_emit-action
fix:修复服务器管理页面WebSocket连接失败和消息丢弃问题
2025-12-26 09:04:23 +08:00
3030332422 dbd179e507 fix:修复服务器管理页面WebSocket连接失败和消息丢弃问题 2025-12-25 18:11:03 +08:00
rainv123andGitHub 9541e85600 Merge pull request #2743 from xinnan-tech/py_async_server-mcp
update:将服务端MCP的初始化MCP服务从串行改为并发执行,并添加超时机制
2025-12-23 16:50:26 +08:00
rainv123andGitHub 9ecd829bfa Merge pull request #2747 from xinnan-tech/py_asr_doubao_stream
豆包ASR流式自定义语句停止时长
2025-12-23 16:49:56 +08:00
hrzandGitHub fd4cff73ae Merge pull request #2742 from xinnan-tech/manager-web-logo-i18n
add:登录、注册、首页、忘记密码页的多语言logo显示判断
2025-12-23 15:35:20 +08:00
hrz 1d6c4751de update:股东记忆模型的token大小 2025-12-23 14:42:33 +08:00
3030332422 e066c1d6a1 update:超时时间更改为10秒,并且删去无用的模块导入 2025-12-22 16:38:28 +08:00
3030332422 85f5404b3b update:添加锁保护避免MCP并发初始化时的竞态条件 2025-12-22 15:56:40 +08:00
Sakura-RanChen 33c5893d32 豆包ASR流式自定义语句停止时长 2025-12-22 15:23:55 +08:00
hrz b597cb0686 update:移除无用代码 2025-12-20 00:30:20 +08:00
hrz c5e288ca9c Merge branch 'main' into manager-web-logo-i18n 2025-12-19 23:46:43 +08:00
欣南科技andGitHub 38d984f225 Merge pull request #2744 from xinnan-tech/fix-delete-audio-data
分批次删除音频数据,避免数据库超时
2025-12-19 23:29:40 +08:00
hrz 628c642040 分批次删除音频数据,避免数据库超时 2025-12-19 23:27:35 +08:00
hrz d217cc4dd0 update:移除无用代码 2025-12-19 22:31:20 +08:00
rainv123 4f0e54b390 调整 2025-12-19 14:51:32 +08:00
rainv123 d7f89be31c add:登录、注册、首页、忘记密码页的多语言logo显示判断 2025-12-19 14:33:35 +08:00
rainv123 53bcee7032 Merge branch 'manager-web-logo-i18n' of https://github.com/xinnan-tech/xiaozhi-esp32-server into new 2025-12-19 14:06:19 +08:00
hrz 96991ae5ef add:多语言logo 2025-12-19 13:29:06 +08:00
欣南科技andGitHub 068bcde451 Merge pull request #2741 from xinnan-tech/openrz-patch-1
Update huoshan-streamTTS-voice-cloning.md
2025-12-19 12:12:44 +08:00
hrzandGitHub 0dda4f5646 Update huoshan-streamTTS-voice-cloning.md 2025-12-19 12:12:10 +08:00
3030332422 6c57ce9dd2 update:将服务端MCP的初始化MCP服务从串行改为并发执行,并添加超时机制 2025-12-19 11:49:42 +08:00
rainv123 6ac67a7e41 fix:调整 2025-12-19 09:55:18 +08:00
hrzandGitHub a23a585ad8 Merge pull request #2737 from xinnan-tech/py_prompt_update
update:提示词上下文按模板按需获取(位置/天气/动态上下文)
2025-12-18 18:20:01 +08:00
欣南科技andGitHub eba9562e81 Merge pull request #2738 from xinnan-tech/xiaozhi-server_ota_bin
Xiaozhi server ota bin
2025-12-18 18:12:44 +08:00
hrz ce49b409ac update:优化说明 2025-12-18 18:11:53 +08:00
hrz 7222f68d4d update:添加重要说明 2025-12-18 17:57:05 +08:00
hrz f3f0d62f12 add:添加单模块部署时,使用ota接口自动升级固件的教程 2025-12-18 17:38:58 +08:00
hrz 6e7c86e159 update:从vision_url配置里读取域名和端口号 2025-12-18 17:37:20 +08:00
hrzandGitHub f5565f6700 Merge pull request #2730 from rxchen/main
add basic "real" OTA support for single server deployment.
2025-12-18 17:29:31 +08:00
3030332422 e8d0bb0c54 update:提示词上下文按模板按需获取(位置/天气/动态上下文) 2025-12-18 16:34:37 +08:00
rui chen 33d70ccc96 get OTA address from websocket address config, if failed find ota_addr, if failed again, use local address. 2025-12-18 15:48:25 +08:00
rxchenandGitHub 53313a26af Merge branch 'xinnan-tech:main' into main 2025-12-18 15:26:51 +08:00
欣南科技andGitHub 7ba180752a Merge pull request #2735 from xinnan-tech/fix-nointent-weather
fix:无意图识别时,无"plugins"配置报错的bug
2025-12-18 11:23:36 +08:00
hrz 19736e66ad fix:无意图识别时,无"plugins"配置报错的bug 2025-12-18 11:18:49 +08:00
1cccuxandGitHub a00f8e4b76 Merge pull request #2734 from xinnan-tech/py_fix_listenmessage
fix:修复唤醒状态
2025-12-18 10:12:02 +08:00
FAN-yeB 7d9895cf5b 修复唤醒状态 2025-12-18 10:11:13 +08:00
rui chen d5f804bbb3 add basic OTA support for single server deployment, remove debug 2025-12-17 16:44:54 +08:00
rui chen 33a385cfa8 add basic OTA support for single server deployment
Committer: rxchen <rchentl@hotmail.com>
2025-12-17 16:26:35 +08:00
欣南科技andGitHub 109d924591 Merge pull request #2728 from xinnan-tech/update_provider_name
Update provider name
2025-12-17 12:00:13 +08:00
hrz a825eb3d4c Merge branch 'py_websocket_activate' into update_provider_name 2025-12-17 11:59:22 +08:00
hrz 3fb40677a4 update:纠正显示名称 2025-12-17 11:58:40 +08:00
hrzandGitHub 833971cd28 Merge pull request #2722 from xinnan-tech/py_websocket_activate
update: 增加websocket心跳机制
2025-12-17 11:51:14 +08:00
hrzandGitHub d14b14bce9 Merge pull request #2709 from xinnan-tech/py_audio_change
优化
2025-12-16 22:04:02 +08:00
hrz 53e26821ad update:补回前5个包提前发送的时间,因为发送完不等于播放完 2025-12-16 22:03:12 +08:00
Sakura-RanChen 1a7c06eb81 update: 增加websocket心跳机制 2025-12-16 16:39:13 +08:00
Packeting1 6cdbe45313 feat: 为阿里百炼流式TTS添加多语言音色支持、相关 音色配置; 2025-12-16 11:35:58 +08:00
hrzandGitHub 42a5b4892d Merge pull request #2712 from xinnan-tech/fix_bind_event
fix: 等待初始化成功设置事件
2025-12-15 22:46:31 +08:00
欣南科技andGitHub 9f4508b0c7 Merge pull request #2714 from xinnan-tech/update-doc
update:优化文档说明
2025-12-15 18:16:23 +08:00
hrz b2123ff01a update:优化文档说明 2025-12-15 18:15:35 +08:00
Sakura-RanChen 43ead841a4 fix: 等待初始化成功设置事件 2025-12-15 17:31:13 +08:00
Sakura-RanChen 33b4794e83 优化 2025-12-15 16:34:50 +08:00
欣南科技andGitHub fb91e6b1dd Merge pull request #2703 from xinnan-tech/test_page_pre_buffer
update:mqtt部署更新
2025-12-14 15:29:59 +08:00
hrz 4a4dbf123e update:mqtt部署更新 2025-12-14 15:29:13 +08:00
欣南科技andGitHub 0decedd6a1 Merge pull request #2701 from xinnan-tech/test_page_pre_buffer
update:优化测试页面缓冲音频播放
2025-12-14 14:25:25 +08:00
hrz d2e3a63418 update:优化测试页面缓冲音频播放 2025-12-14 14:24:39 +08:00
rainv123 5eaaf9f01d Merge branch 'main' of https://github.com/xinnan-tech/xiaozhi-esp32-server into test 2025-12-12 12:55:02 +08:00
rainv123 d6697948c2 fix:使用java端做聊天记录总结 2025-12-12 12:53:59 +08:00
dependabot[bot]andGitHub 80d10cf4b5 build(deps): bump mcp from 1.20.0 to 1.22.0 in /main/xiaozhi-server
Bumps [mcp](https://github.com/modelcontextprotocol/python-sdk) from 1.20.0 to 1.22.0.
- [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases)
- [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md)
- [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.20.0...v1.22.0)

---
updated-dependencies:
- dependency-name: mcp
  dependency-version: 1.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 10:28:38 +00:00
dependabot[bot]andGitHub ba1fb16f2d build(deps): bump torchaudio from 2.2.2 to 2.9.1 in /main/xiaozhi-server
Bumps [torchaudio](https://github.com/pytorch/audio) from 2.2.2 to 2.9.1.
- [Release notes](https://github.com/pytorch/audio/releases)
- [Commits](https://github.com/pytorch/audio/compare/v2.2.2...v2.9.1)

---
updated-dependencies:
- dependency-name: torchaudio
  dependency-version: 2.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 10:28:33 +00:00
dependabot[bot]andGitHub 5f574667d3 build(deps): bump websockets from 14.2 to 15.0.1 in /main/xiaozhi-server
Bumps [websockets](https://github.com/python-websockets/websockets) from 14.2 to 15.0.1.
- [Release notes](https://github.com/python-websockets/websockets/releases)
- [Commits](https://github.com/python-websockets/websockets/compare/14.2...15.0.1)

---
updated-dependencies:
- dependency-name: websockets
  dependency-version: 15.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 10:28:26 +00:00
dependabot[bot]andGitHub c470bb1db1 build(deps): bump modelscope in /main/xiaozhi-server
Bumps [modelscope](https://github.com/modelscope/modelscope) from 1.23.2 to 1.32.0.
- [Release notes](https://github.com/modelscope/modelscope/releases)
- [Commits](https://github.com/modelscope/modelscope/compare/v1.23.2...v1.32.0)

---
updated-dependencies:
- dependency-name: modelscope
  dependency-version: 1.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 10:28:21 +00:00
dependabot[bot]andGitHub 26ed8ca33f build(deps): bump psutil from 7.0.0 to 7.1.3 in /main/xiaozhi-server
Bumps [psutil](https://github.com/giampaolo/psutil) from 7.0.0 to 7.1.3.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-7.0.0...release-7.1.3)

---
updated-dependencies:
- dependency-name: psutil
  dependency-version: 7.1.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 10:28:16 +00:00
Sakura-RanChen 3c46e16494 自主选择链接复用功能 2025-11-22 16:19:13 +08:00
463 changed files with 74896 additions and 2770 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
file: Dockerfile-server-base file: Dockerfile-server-base
push: true push: true
tags: ghcr.io/${{ github.repository }}:server-base tags: ghcr.io/${{ github.repository }}:server-base
platforms: linux/amd64 platforms: linux/amd64,linux/arm64
cache-from: type=gha,scope=server-base cache-from: type=gha,scope=server-base
cache-to: type=gha,mode=max,scope=server-base cache-to: type=gha,mode=max,scope=server-base
build-args: | build-args: |
+2 -2
View File
@@ -66,7 +66,7 @@ jobs:
push: true push: true
tags: | tags: |
${{ 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) }} ${{ 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) }}
platforms: linux/amd64 platforms: linux/amd64,linux/arm64
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
build-args: | build-args: |
@@ -81,7 +81,7 @@ jobs:
push: true push: true
tags: | tags: |
${{ 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) }} ${{ 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) }}
platforms: linux/amd64 platforms: linux/amd64,linux/arm64
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
build-args: | build-args: |
+7 -7
View File
@@ -1,5 +1,5 @@
# 第一阶段:构建Vue前端 # 第一阶段:构建Vue前端
FROM node:18 as web-builder FROM node:18 AS web-builder
WORKDIR /app WORKDIR /app
COPY main/manager-web/package*.json ./ COPY main/manager-web/package*.json ./
RUN npm install RUN npm install
@@ -7,7 +7,7 @@ COPY main/manager-web .
RUN npm run build RUN npm run build
# 第二阶段:构建Java后端 # 第二阶段:构建Java后端
FROM maven:3.9.4-eclipse-temurin-21 as api-builder FROM maven:3.9.4-eclipse-temurin-21 AS api-builder
WORKDIR /app WORKDIR /app
COPY main/manager-api/pom.xml . COPY main/manager-api/pom.xml .
COPY main/manager-api/src ./src COPY main/manager-api/src ./src
@@ -18,18 +18,18 @@ FROM bellsoft/liberica-runtime-container:jre-21-glibc
# 安装Nginx和字体库 # 安装Nginx和字体库
RUN apk update && \ RUN apk update && \
apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \ apk add --no-cache --no-scripts \
nginx \ nginx \
bash \ bash \
fontconfig \ fontconfig \
ttf-dejavu \ ttf-dejavu \
msttcorefonts-installer \
&& ACCEPT_EULA=Y apk add --no-cache msttcorefonts-installer \
&& fc-cache -f -v \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
# 复制项目自带的中文字体
COPY main/manager-web/public/generator/static/fonts/*.ttf /usr/share/fonts/
# 更新字体缓存 # 更新字体缓存
RUN (printf 'YES\n' | update-ms-fonts || true) && fc-cache -f -v RUN fc-cache -f -v
# 配置Nginx # 配置Nginx
COPY docs/docker/nginx.conf /etc/nginx/nginx.conf COPY docs/docker/nginx.conf /etc/nginx/nginx.conf
+7 -7
View File
@@ -183,8 +183,8 @@ Spearheaded by Professor Siyuan Liu's Team (South China University of Technology
#### 🚀 部署方式选择 #### 🚀 部署方式选择
| 部署方式 | 特点 | 适用场景 | 部署文档 | 配置要求 | 视频教程 | | 部署方式 | 特点 | 适用场景 | 部署文档 | 配置要求 | 视频教程 |
|---------|------|---------|---------|---------|---------| |---------|------|---------|---------|---------|---------|
| **最简化安装** | 智能对话、IOT、MCP、视觉感知 | 低配置环境,数据存储在配置文件,无需数据库 | [①Docker版](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②源码部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)| 如果使用`FunASR`要2核4G,如果全API,要2核2G | - | | **最简化安装** | 智能对话、单智能体管理 | 低配置环境,数据存储在配置文件,无需数据库 | [①Docker版](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②源码部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)| 如果使用`FunASR`要2核4G,如果全API,要2核2G | - |
| **全模块安装** | 智能对话、IOT、MCP接入点、声纹识别、视觉感知、OTA、智控台 | 完整功能体验,数据存储在数据库 |[①Docker版](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [②源码部署](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [③源码部署自动更新教程](./docs/dev-ops-integration.md) | 如果使用`FunASR`要4核8G,如果全API,要2核4G| [本地源码启动视频教程](https://www.bilibili.com/video/BV1wBJhz4Ewe) | | **全模块安装** | 智能对话、多用户管理、多智能体管理、智控台界面操作 | 完整功能体验,数据存储在数据库 |[①Docker版](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [②源码部署](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [③源码部署自动更新教程](./docs/dev-ops-integration.md) | 如果使用`FunASR`要4核8G,如果全API,要2核4G| [本地源码启动视频教程](https://www.bilibili.com/video/BV1wBJhz4Ewe) |
常见问题及相关教程,可参考[这个链接](./docs/FAQ.md) 常见问题及相关教程,可参考[这个链接](./docs/FAQ.md)
@@ -211,10 +211,10 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
| 模块名称 | 入门全免费设置 | 流式配置 | | 模块名称 | 入门全免费设置 | 流式配置 |
|:---:|:---:|:---:| |:---:|:---:|:---:|
| ASR(语音识别) | FunASR(本地) | 👍FunASR(本地GPU模式) | | ASR(语音识别) | FunASR(本地) | 👍XunfeiStreamASR(讯飞流式) |
| LLM(大模型) | ChatGLMLLM(智谱glm-4-flash) | 👍AliLLM(qwen3-235b-a22b-instruct-2507) 或 👍DoubaoLLM(doubao-1-5-pro-32k-250115) | | LLM(大模型) | glm-4-flash(智谱) | 👍qwen-flash(阿里百炼) |
| VLLM(视觉大模型) | ChatGLMVLLM(智谱glm-4v-flash) | 👍QwenVLVLLM(千问qwen2.5-vl-3b-instructh) | | VLLM(视觉大模型) | glm-4v-flash(智谱) | 👍qwen2.5-vl-3b-instructh(阿里百炼) |
| TTS(语音合成) | ✅LinkeraiTTS(灵犀流式) | 👍HuoshanDoubleStreamTTS(火山流式语音合成) 或 👍AliyunStreamTTS(阿里云流式语音合成) | | TTS(语音合成) | ✅LinkeraiTTS(灵犀流式) | 👍HuoshanDoubleStreamTTS(火山流式) |
| Intent(意图识别) | function_call(函数调用) | function_call(函数调用) | | Intent(意图识别) | function_call(函数调用) | function_call(函数调用) |
| Memory(记忆功能) | mem_local_short(本地短期记忆) | mem_local_short(本地短期记忆) | | Memory(记忆功能) | mem_local_short(本地短期记忆) | mem_local_short(本地短期记忆) |
@@ -260,7 +260,7 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
--- ---
## 产品生态 👬 ## 产品生态 👬
小智是一个生态,当你使用这个产品时,也可以看看其他在这个生态圈的[优秀项目](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE) 小智是一个生态,当你使用这个产品时,也可以看看其他在这个生态圈的[优秀项目](https://github.com/78/xiaozhi-esp32/blob/main/README_zh.md#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE)
--- ---
+7 -7
View File
@@ -181,8 +181,8 @@ Dieses Projekt bietet zwei Bereitstellungsmethoden. Bitte wählen Sie basierend
#### 🚀 Auswahl der Bereitstellungsmethode #### 🚀 Auswahl der Bereitstellungsmethode
| Bereitstellungsmethode | Funktionen | Anwendungsszenarien | Deployment-Dokumente | Konfigurationsanforderungen | Video-Tutorials | | Bereitstellungsmethode | Funktionen | Anwendungsszenarien | Deployment-Dokumente | Konfigurationsanforderungen | Video-Tutorials |
|---------|------|---------|---------|---------|---------| |---------|------|---------|---------|---------|---------|
| **Vereinfachte Installation** | Intelligenter Dialog, IOT, MCP, visuelle Wahrnehmung | Umgebungen mit geringer Konfiguration, Daten in Konfigurationsdateien gespeichert, keine Datenbank erforderlich | [①Docker-Version](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②Quellcode-Deployment](./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)| 2 Kerne 4GB bei Verwendung von `FunASR`, 2 Kerne 2GB bei allen APIs | - | | **Vereinfachte Installation** | Intelligenter Dialog, Einzel-Agenten-Verwaltung | Umgebungen mit geringer Konfiguration, Daten in Konfigurationsdateien gespeichert, keine Datenbank erforderlich | [①Docker-Version](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②Quellcode-Deployment](./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)| 2 Kerne 4GB bei Verwendung von `FunASR`, 2 Kerne 2GB bei allen APIs | - |
| **Vollständige Modulinstallation** | Intelligenter Dialog, IOT, MCP-Endpunkte, Stimmabdruckerkennung, visuelle Wahrnehmung, OTA, intelligente Steuerkonsole | Vollständige Funktionserfahrung, Daten in Datenbank gespeichert |[①Docker-Version](./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) / [②Quellcode-Deployment](./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) / [③Quellcode-Deployment Auto-Update-Tutorial](./docs/dev-ops-integration.md) | 4 Kerne 8GB bei Verwendung von `FunASR`, 2 Kerne 4GB bei allen APIs| [Video-Tutorial für lokalen Quellcode-Start](https://www.bilibili.com/video/BV1wBJhz4Ewe) | | **Vollständige Modulinstallation** | Intelligenter Dialog, Mehrbenutzerverwaltung, Mehr-Agenten-Verwaltung, Intelligente Steuerkonsole-Bedienung | Vollständige Funktionserfahrung, Daten in Datenbank gespeichert |[①Docker-Version](./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) / [②Quellcode-Deployment](./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) / [③Quellcode-Deployment Auto-Update-Tutorial](./docs/dev-ops-integration.md) | 4 Kerne 8GB bei Verwendung von `FunASR`, 2 Kerne 4GB bei allen APIs| [Video-Tutorial für lokalen Quellcode-Start](https://www.bilibili.com/video/BV1wBJhz4Ewe) |
Häufige Fragen und entsprechende Tutorials finden Sie unter [diesem Link](./docs/FAQ.md) Häufige Fragen und entsprechende Tutorials finden Sie unter [diesem Link](./docs/FAQ.md)
@@ -209,10 +209,10 @@ Websocket-Schnittstellenadresse: wss://2662r3426b.vicp.fun/xiaozhi/v1/
| Modulname | Einstiegslevel Kostenlose Einstellungen | Streaming-Konfiguration | | Modulname | Einstiegslevel Kostenlose Einstellungen | Streaming-Konfiguration |
|:---:|:---:|:---:| |:---:|:---:|:---:|
| ASR (Spracherkennung) | FunASR (Lokal) | 👍FunASR (Lokaler GPU-Modus) | | ASR (Spracherkennung) | FunASR (Lokal) | 👍XunfeiStreamASR (Xunfei-Streaming) |
| LLM (Großes Modell) | ChatGLMLLM (Zhipu glm-4-flash) | 👍AliLLM (qwen3-235b-a22b-instruct-2507) oder 👍DoubaoLLM (doubao-1-5-pro-32k-250115) | | LLM (Großes Modell) | glm-4-flash (Zhipu) | 👍qwen-flash (Alibaba Bailian) |
| VLLM (Vision Large Model) | ChatGLMVLLM (Zhipu glm-4v-flash) | 👍QwenVLVLLM (Qwen qwen2.5-vl-3b-instructh) | | VLLM (Vision Large Model) | glm-4v-flash (Zhipu) | 👍qwen2.5-vl-3b-instructh (Alibaba Bailian) |
| TTS (Sprachsynthese) | ✅LinkeraiTTS (Lingxi-Streaming) | 👍HuoshanDoubleStreamTTS (Volcano Dual-Stream-Sprachsynthese) oder 👍AliyunStreamTTS (Alibaba Cloud Streaming-Sprachsynthese) | | TTS (Sprachsynthese) | ✅LinkeraiTTS (Lingxi-Streaming) | 👍HuoshanDoubleStreamTTS (Volcano-Streaming) |
| Intent (Absichtserkennung) | function_call (Funktionsaufruf) | function_call (Funktionsaufruf) | | Intent (Absichtserkennung) | function_call (Funktionsaufruf) | function_call (Funktionsaufruf) |
| Memory (Gedächtnisfunktion) | mem_local_short (Lokales Kurzzeitgedächtnis) | mem_local_short (Lokales Kurzzeitgedächtnis) | | Memory (Gedächtnisfunktion) | mem_local_short (Lokales Kurzzeitgedächtnis) | mem_local_short (Lokales Kurzzeitgedächtnis) |
@@ -258,7 +258,7 @@ Wenn Sie ein Softwareentwickler sind, finden Sie hier einen [Offenen Brief an En
--- ---
## Produktökosystem 👬 ## Produktökosystem 👬
Xiaozhi ist ein Ökosystem. Wenn Sie dieses Produkt verwenden, können Sie sich auch andere [hervorragende Projekte](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE) in diesem Ökosystem ansehen Xiaozhi ist ein Ökosystem. Wenn Sie dieses Produkt verwenden, können Sie sich auch andere [hervorragende Projekte](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#related-open-source-projects) in diesem Ökosystem ansehen
--- ---
+7 -7
View File
@@ -181,8 +181,8 @@ This project provides two deployment methods. Please choose based on your specif
#### 🚀 Deployment Method Selection #### 🚀 Deployment Method Selection
| Deployment Method | Features | Applicable Scenarios | Deployment Docs | Configuration Requirements | Video Tutorials | | Deployment Method | Features | Applicable Scenarios | Deployment Docs | Configuration Requirements | Video Tutorials |
|---------|------|---------|---------|---------|---------| |---------|------|---------|---------|---------|---------|
| **Simplified Installation** | Intelligent dialogue, IOT, MCP, visual perception | Low-configuration environments, data stored in config files, no database required | [①Docker Version](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②Source Code Deployment](./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)| 2 cores 4GB if using `FunASR`, 2 cores 2GB if all APIs | - | | **Simplified Installation** | Intelligent dialogue, single agent management | Low-configuration environments, data stored in config files, no database required | [①Docker Version](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②Source Code Deployment](./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)| 2 cores 4GB if using `FunASR`, 2 cores 2GB if all APIs | - |
| **Full Module Installation** | Intelligent dialogue, IOT, MCP endpoints, voiceprint recognition, visual perception, OTA, intelligent control console | Complete functionality experience, data stored in database |[①Docker Version](./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) / [②Source Code Deployment](./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) / [③Source Code Deployment Auto-Update Tutorial](./docs/dev-ops-integration.md) | 4 cores 8GB if using `FunASR`, 2 cores 4GB if all APIs| [Local Source Code Startup Video Tutorial](https://www.bilibili.com/video/BV1wBJhz4Ewe) | | **Full Module Installation** | Intelligent dialogue, multi-user management, multi-agent management, intelligent console interface operation | Complete functionality experience, data stored in database |[①Docker Version](./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) / [②Source Code Deployment](./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) / [③Source Code Deployment Auto-Update Tutorial](./docs/dev-ops-integration.md) | 4 cores 8GB if using `FunASR`, 2 cores 4GB if all APIs| [Local Source Code Startup Video Tutorial](https://www.bilibili.com/video/BV1wBJhz4Ewe) |
> 💡 Note: Below is a test platform deployed with the latest code. You can burn and test if needed. Concurrent users: 6, data will be cleared daily. > 💡 Note: Below is a test platform deployed with the latest code. You can burn and test if needed. Concurrent users: 6, data will be cleared daily.
@@ -208,10 +208,10 @@ Websocket Interface Address: wss://2662r3426b.vicp.fun/xiaozhi/v1/
| Module Name | Entry Level Free Settings | Streaming Configuration | | Module Name | Entry Level Free Settings | Streaming Configuration |
|:---:|:---:|:---:| |:---:|:---:|:---:|
| ASR(Speech Recognition) | FunASR(Local) | 👍FunASRServer or 👍DoubaoStreamASR | | ASR(Speech Recognition) | FunASR(Local) | 👍XunfeiStreamASR(Xunfei Streaming) |
| LLM(Large Model) | ChatGLMLLM(Zhipu glm-4-flash) | 👍DoubaoLLM(Volcano doubao-1-5-pro-32k-250115) | | LLM(Large Model) | glm-4-flash(Zhipu) | 👍qwen-flash(Alibaba Bailian) |
| VLLM(Vision Large Model) | ChatGLMVLLM(Zhipu glm-4v-flash) | 👍QwenVLVLLM(Qwen qwen2.5-vl-3b-instructh) | | VLLM(Vision Large Model) | glm-4v-flash(Zhipu) | 👍qwen2.5-vl-3b-instructh(Alibaba Bailian) |
| TTS(Speech Synthesis) | ✅LinkeraiTTS(Lingxi streaming) | 👍HuoshanDoubleStreamTTS(Volcano dual-stream speech synthesis) | | TTS(Speech Synthesis) | ✅LinkeraiTTS(Lingxi streaming) | 👍HuoshanDoubleStreamTTS(Volcano Streaming) |
| Intent(Intent Recognition) | function_call(Function calling) | function_call(Function calling) | | Intent(Intent Recognition) | function_call(Function calling) | function_call(Function calling) |
| Memory(Memory function) | mem_local_short(Local short-term memory) | mem_local_short(Local short-term memory) | | Memory(Memory function) | mem_local_short(Local short-term memory) | mem_local_short(Local short-term memory) |
@@ -256,7 +256,7 @@ If you are a software developer, here is an [Open Letter to Developers](docs/con
--- ---
## Product Ecosystem 👬 ## Product Ecosystem 👬
Xiaozhi is an ecosystem. When using this product, you can also check out other [excellent projects](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE) in this ecosystem Xiaozhi is an ecosystem. When using this product, you can also check out other [excellent projects](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#related-open-source-projects) in this ecosystem
| Project Name | Project Address | Project Description | | Project Name | Project Address | Project Description |
|:---------------------|:--------|:--------| |:---------------------|:--------|:--------|
+7 -7
View File
@@ -182,8 +182,8 @@ Dự án này cung cấp hai phương pháp triển khai, vui lòng chọn theo
#### 🚀 Lựa chọn phương pháp triển khai #### 🚀 Lựa chọn phương pháp triển khai
| Phương pháp triển khai | Đặc điểm | Tình huống áp dụng | Tài liệu triển khai | Yêu cầu cấu hình | Video hướng dẫn | | Phương pháp triển khai | Đặc điểm | Tình huống áp dụng | Tài liệu triển khai | Yêu cầu cấu hình | Video hướng dẫn |
|---------|------|---------|---------|---------|---------| |---------|------|---------|---------|---------|---------|
| **Cài đặt tối giản** | Đối thoại thông minh, IOT, MCP, cảm nhận thị giác | Môi trường cấu hình thấp, dữ liệu lưu trong tệp cấu hình, không cần cơ sở dữ liệu | [①Phiên bản Docker](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②Triển khai mã nguồn](./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)| 2 nhân 4GB nếu dùng `FunASR`, 2 nhân 2GB nếu toàn API | - | | **Cài đặt tối giản** | Đối thoại thông minh, quản lý đơn tác nhân | Môi trường cấu hình thấp, dữ liệu lưu trong tệp cấu hình, không cần cơ sở dữ liệu | [①Phiên bản Docker](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②Triển khai mã nguồn](./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)| 2 nhân 4GB nếu dùng `FunASR`, 2 nhân 2GB nếu toàn API | - |
| **Cài đặt toàn bộ module** | Đối thoại thông minh, IOT, điểm truy cập MCP, nhận dng giọng nói, cảm nhận thị giác, OTA, bảng điều khiển thông minh | Trải nghiệm đầy đủ tính năng, dữ liệu lưu trong cơ sở dữ liệu |[①Phiên bản 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) / [②Triển khai mã nguồn](./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) / [③Hướng dẫn tự động cập nhật triển khai mã nguồn](./docs/dev-ops-integration.md) | 4 nhân 8GB nếu dùng `FunASR`, 2 nhân 4GB nếu toàn API| [Video hướng dẫn khởi động mã nguồn cục bộ](https://www.bilibili.com/video/BV1wBJhz4Ewe) | | **Cài đặt toàn bộ module** | Đối thoại thông minh, quản lý đa người dùng, quản lý đa tác nhân, bảng điều khiển thông minh | Trải nghiệm đầy đủ tính năng, dữ liệu lưu trong cơ sở dữ liệu |[①Phiên bản 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) / [②Triển khai mã nguồn](./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) / [③Hướng dẫn tự động cập nhật triển khai mã nguồn](./docs/dev-ops-integration.md) | 4 nhân 8GB nếu dùng `FunASR`, 2 nhân 4GB nếu toàn API| [Video hướng dẫn khởi động mã nguồn cục bộ](https://www.bilibili.com/video/BV1wBJhz4Ewe) |
Câu hỏi thường gặp và hướng dẫn liên quan, vui lòng tham khảo [liên kết này](./docs/FAQ.md) Câu hỏi thường gặp và hướng dẫn liên quan, vui lòng tham khảo [liên kết này](./docs/FAQ.md)
@@ -210,10 +210,10 @@ Công cụ kiểm tra dịch vụ: https://2662r3426b.vicp.fun/test/
| Tên module | Cài đặt miễn phí cho người mới | Cấu hình streaming | | Tên module | Cài đặt miễn phí cho người mới | Cấu hình streaming |
|:---:|:---:|:---:| |:---:|:---:|:---:|
| ASR(Nhận dạng giọng nói) | FunASR(Local) | 👍FunASR(Chế độ GPU cục bộ) | | ASR(Nhận dạng giọng nói) | FunASR(Local) | 👍XunfeiStreamASR(Xunfei Streaming) |
| LLM(Mô hình lớn) | ChatGLMLLM(Zhipu glm-4-flash) | 👍AliLLM(qwen3-235b-a22b-instruct-2507) hoặc 👍DoubaoLLM(doubao-1-5-pro-32k-250115) | | LLM(Mô hình lớn) | glm-4-flash(Zhipu) | 👍qwen-flash(Alibaba Bailian) |
| VLLM(Mô hình lớn thị giác) | ChatGLMVLLM(Zhipu glm-4v-flash) | 👍QwenVLVLLM(Qwen qwen2.5-vl-3b-instructh) | | VLLM(Mô hình lớn thị giác) | glm-4v-flash(Zhipu) | 👍qwen2.5-vl-3b-instructh(Alibaba Bailian) |
| TTS(Tổng hợp giọng nói) | ✅LinkeraiTTS(Lingxi streaming) | 👍HuoshanDoubleStreamTTS(Tổng hợp giọng nói streaming kép Volcano) hoặc 👍AliyunStreamTTS(Tổng hợp giọng nói streaming Alibaba Cloud) | | TTS(Tổng hợp giọng nói) | ✅LinkeraiTTS(Lingxi streaming) | 👍HuoshanDoubleStreamTTS(Volcano Streaming) |
| Intent(Nhận dạng ý định) | function_call(Gọi hàm) | function_call(Gọi hàm) | | Intent(Nhận dạng ý định) | function_call(Gọi hàm) | function_call(Gọi hàm) |
| Memory(Chức năng bộ nhớ) | mem_local_short(Bộ nhớ ngắn hạn cục bộ) | mem_local_short(Bộ nhớ ngắn hạn cục bộ) | | Memory(Chức năng bộ nhớ) | mem_local_short(Bộ nhớ ngắn hạn cục bộ) | mem_local_short(Bộ nhớ ngắn hạn cục bộ) |
@@ -259,7 +259,7 @@ Nếu bạn là một nhà phát triển phần mềm, đây có một [Lá thư
--- ---
## Hệ sinh thái sản phẩm 👬 ## Hệ sinh thái sản phẩm 👬
Xiaozhi là một hệ sinh thái, khi bạn sử dụng sản phẩm này, bạn cũng có thể xem các [dự án xuất sắc](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE) khác trong hệ sinh thái này Xiaozhi là một hệ sinh thái, khi bạn sử dụng sản phẩm này, bạn cũng có thể xem các [dự án xuất sắc](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#related-open-source-projects) khác trong hệ sinh thái này
--- ---
+5 -4
View File
@@ -38,10 +38,10 @@ conda install conda-forge::ffmpeg
| 模块名称 | 入门全免费设置 | 流式配置 | | 模块名称 | 入门全免费设置 | 流式配置 |
|:---:|:---:|:---:| |:---:|:---:|:---:|
| ASR(语音识别) | FunASR(本地) | 👍FunASR(本地GPU模式) | | ASR(语音识别) | FunASR(本地) | 👍XunfeiStreamASR(讯飞流式) |
| LLM(大模型) | ChatGLMLLM(智谱glm-4-flash) | 👍AliLLM(qwen3-235b-a22b-instruct-2507) 或 👍DoubaoLLM(doubao-1-5-pro-32k-250115) | | LLM(大模型) | glm-4-flash(智谱) | 👍qwen-flash(阿里百炼) |
| VLLM(视觉大模型) | ChatGLMVLLM(智谱glm-4v-flash) | 👍QwenVLVLLM(千问qwen2.5-vl-3b-instructh) | | VLLM(视觉大模型) | glm-4v-flash(智谱) | 👍qwen2.5-vl-3b-instructh(阿里百炼) |
| TTS(语音合成) | ✅LinkeraiTTS(灵犀流式) | 👍HuoshanDoubleStreamTTS(火山流式语音合成) 或 👍AliyunStreamTTS(阿里云流式语音合成) | | TTS(语音合成) | ✅LinkeraiTTS(灵犀流式) | 👍HuoshanDoubleStreamTTS(火山流式) |
| Intent(意图识别) | function_call(函数调用) | function_call(函数调用) | | Intent(意图识别) | function_call(函数调用) | function_call(函数调用) |
| Memory(记忆功能) | mem_local_short(本地短期记忆) | mem_local_short(本地短期记忆) | | Memory(记忆功能) | mem_local_short(本地短期记忆) | mem_local_short(本地短期记忆) |
@@ -69,6 +69,7 @@ VAD:
### 9、编译固件相关教程 ### 9、编译固件相关教程
1、[如何自己编译小智固件](./firmware-build.md)<br/> 1、[如何自己编译小智固件](./firmware-build.md)<br/>
2、[如何基于虾哥编译好的固件修改OTA地址](./firmware-setting.md)<br/> 2、[如何基于虾哥编译好的固件修改OTA地址](./firmware-setting.md)<br/>
3、[单模块部署如何配置固件OTA自动升级](./ota-upgrade-guide.md)<br/>
### 10、拓展相关教程 ### 10、拓展相关教程
1、[如何开启手机号码注册智控台](./ali-sms-integration.md)<br/> 1、[如何开启手机号码注册智控台](./ali-sms-integration.md)<br/>
+2
View File
@@ -23,6 +23,8 @@
### 2.将音色资源ID分配给系统账号 ### 2.将音色资源ID分配给系统账号
使用超级管理员账号登录智控台,点击顶部`参数字典`,在下拉菜单中,点击`系统功能配置`页面。在页面上勾选`音色克隆`,点击保存配置。即可在顶部菜单看到`音色克隆`按钮。
使用超级管理员账号登录智控台,点击顶部【音色克隆】、【音色资源】。 使用超级管理员账号登录智控台,点击顶部【音色克隆】、【音色资源】。
点击新增按钮,在【平台名称】选择“火山双流式语音合成”; 点击新增按钮,在【平台名称】选择“火山双流式语音合成”;
+8
View File
@@ -76,6 +76,7 @@ MQTT_PORT=1883 # MQTT服务器端口
UDP_PORT=8884 # UDP服务器端口 UDP_PORT=8884 # UDP服务器端口
API_PORT=8007 # 管理API端口 API_PORT=8007 # 管理API端口
MQTT_SIGNATURE_KEY=test # MQTT签名密钥 MQTT_SIGNATURE_KEY=test # MQTT签名密钥
SERVER_SECRET=Te1st12134 # 服务器密钥,请保持和智控台(server.secret)一致或者和xiaozhi-server里(server.auth_key)保持一致
``` ```
请注意`PUBLIC_IP`配置,确保其与实际公网IP一致,如果有域名就填域名。 请注意`PUBLIC_IP`配置,确保其与实际公网IP一致,如果有域名就填域名。
@@ -85,6 +86,13 @@ MQTT_SIGNATURE_KEY=test # MQTT签名密钥
- 注意不要用简单的密码,比如`123456`、`test`等。 - 注意不要用简单的密码,比如`123456`、`test`等。
- 注意不要用简单的密码,比如`123456`、`test`等。 - 注意不要用简单的密码,比如`123456`、`test`等。
`SERVER_SECRET` 是用生成websocket连接的认证信息。
1、如果你是全模块部署,且你的智控台的参数管理里`server.auth.enabled`设置成了`true`,那么,`SERVER_SECRET`需要和智控台(`server.secret`)保持一致。
2、如果你是单模块部署,且你在配置文件里把`server.auth.enabled`设置成了`true`,那么,`SERVER_SECRET`需要和配置文件里(`server.auth_key`)保持一致。
6. 启动MQTT网关 6. 启动MQTT网关
``` ```
# 启动服务 # 启动服务
+142
View File
@@ -0,0 +1,142 @@
# 单模块部署固件OTA自动升级配置指南
本教程将指导你如何在**单模块部署**场景下配置固件OTA自动升级功能,实现设备固件的自动更新。
如果你已经使用**全模块部署**,请忽略本教程。
## 功能介绍
在单模块部署中,xiaozhi-server内置了OTA固件管理功能,可以自动检测设备版本并下发升级固件。系统会根据设备型号和当前版本,自动匹配并推送最新的固件版本。
## 前提条件
- 你已经成功进行**单模块部署**并运行xiaozhi-server
- 设备能够正常连接到服务器
## 第一步 准备固件文件
### 1. 创建固件存放目录
固件文件需要放在`data/bin/`目录下。如果该目录不存在,请手动创建:
```bash
mkdir -p data/bin
```
### 2. 固件文件命名规则
固件文件必须遵循以下命名格式:
```
{设备型号}_{版本号}.bin
```
**命名规则说明:**
- `设备型号`:设备的型号名称,例如 `lichuang-dev``bread-compact-wifi`
- `版本号`:固件版本号,必须以数字开头,支持数字、字母、点号、下划线和短横线,例如 `1.6.6``2.0.0`
- 文件扩展名必须是 `.bin`
**命名示例:**
```
bread-compact-wifi_1.6.6.bin
lichuang-dev_2.0.0.bin
```
### 3. 放置固件文件
将准备好的固件文件(.bin文件)复制到`data/bin/`目录下:
重要的事情说三遍:升级的bin文件是`xiaozhi.bin`,不是全量固件文件`merged-binary.bin`!
重要的事情说三遍:升级的bin文件是`xiaozhi.bin`,不是全量固件文件`merged-binary.bin`!
重要的事情说三遍:升级的bin文件是`xiaozhi.bin`,不是全量固件文件`merged-binary.bin`!
```bash
cp xiaozhi.bin data/bin/设备型号_版本号.bin
```
例如:
```bash
cp xiaozhi.bin data/bin/bread-compact-wifi_1.6.6.bin
```
## 第二步 配置公网访问地址(仅公网部署需要)
**注意:此步骤仅适用于单模块公网部署的场景。**
如果你的xiaozhi-server是公网部署(使用公网IP或域名),**必须**配置`server.vision_explain`参数,因为OTA固件下载地址会使用该配置的域名和端口。
如果你是局域网部署,可以跳过此步骤。
### 为什么要配置这个参数?
在单模块部署中,系统生成固件下载地址时,会使用`vision_explain`配置的域名和端口作为基础地址。如果不配置或配置错误,设备将无法访问固件下载地址。
### 配置方法
打开`data/.config.yaml`文件,找到`server`配置段,设置`vision_explain`参数:
```yaml
server:
vision_explain: http://你的域名或IP:端口号/mcp/vision/explain
```
**配置示例:**
局域网部署(默认):
```yaml
server:
vision_explain: http://192.168.1.100:8003/mcp/vision/explain
```
公网域名部署:
```yaml
server:
vision_explain: http://yourdomain.com:8003/mcp/vision/explain
```
### 注意事项
- 域名或IP必须是设备能够访问的地址
- 如果使用Docker部署,不能使用Docker内部地址(如127.0.0.1或localhost
- 如果你使用了nginx反向代理,请填写对外的地址和端口号,不是本项目运行的端口号
## 常见问题
### 1. 设备收不到固件更新
**可能原因和解决方法:**
- 检查固件文件命名是否符合规则:`{型号}_{版本号}.bin`
- 检查固件文件是否正确放置在`data/bin/`目录
- 检查设备型号是否与固件文件名中的型号匹配
- 检查固件版本号是否高于设备当前版本
- 查看服务器日志,确认OTA请求是否正常处理
### 2. 设备报告下载地址无法访问
**可能原因和解决方法:**
- 检查`server.vision_explain`配置的域名或IP是否正确
- 确认端口号配置正确(默认8003
- 如果是公网部署,确保设备能够访问该公网地址
- 如果是Docker部署,确保不是使用了内部地址(127.0.0.1)
- 检查防火墙是否开放了对应端口
- 如果你使用了nginx反向代理,请填写对外的地址和端口号,不是本项目运行的端口号
### 3. 如何确认设备当前版本
查看OTA请求日志,日志中会显示设备上报的版本号:
```
[ota_handler] - 设备 AA:BB:CC:DD:EE:FF 固件已是最新: 1.6.6
```
### 4. 固件文件放置后没有生效
系统有30秒的缓存时间(默认),可以:
- 等待30秒后再让设备发起OTA请求
- 重启xiaozhi-server服务
- 调整`firmware_cache_ttl`配置为更短的时间
@@ -43,7 +43,7 @@ public class SwaggerConfig {
public GroupedOpenApi oatApi() { public GroupedOpenApi oatApi() {
return GroupedOpenApi.builder() return GroupedOpenApi.builder()
.group("ota") .group("ota")
.pathsToMatch("/ota/**") .pathsToMatch("/ota/**", "/otaMag/**")
.build(); .build();
} }
@@ -304,7 +304,7 @@ public interface Constant {
/** /**
* 版本号 * 版本号
*/ */
public static final String VERSION = "0.8.10"; public static final String VERSION = "0.9.1";
/** /**
* 无效固件URL * 无效固件URL
@@ -240,4 +240,9 @@ public interface ErrorCode {
int RAG_ADAPTER_CACHE_ERROR = 10191; // 适配器缓存错误 int RAG_ADAPTER_CACHE_ERROR = 10191; // 适配器缓存错误
int RAG_ADAPTER_TYPE_NOT_FOUND = 10192; // 适配器类型未找到 int RAG_ADAPTER_TYPE_NOT_FOUND = 10192; // 适配器类型未找到
// 设备工具相关错误码
int DEVICE_ID_NOT_NULL = 10193; // 设备ID不能为空
int DEVICE_NOT_EXIST = 10194; // 设备不存在
int OTA_UPLOAD_COUNT_EXCEED = 10195; // OTA上传次数超过限制
} }
@@ -159,4 +159,32 @@ public class RedisKeys {
public static String getKnowledgeBaseCacheKey(String datasetId) { public static String getKnowledgeBaseCacheKey(String datasetId) {
return "knowledge:base:" + datasetId; return "knowledge:base:" + datasetId;
} }
/**
* 获取临时注册设备标记key
*/
public static String getTmpRegisterMacKey(String deviceId) {
return "tmp_register_mac:" + deviceId;
}
/**
* OTA绑定设备
*/
public static String getOtaActivationCode(String activationCode) {
return "ota:activation:code:" + activationCode;
}
/**
* OTA获取设备mac相关信息
*/
public static String getOtaDeviceActivationInfo(String deviceId) {
return "ota:activation:data:" + deviceId;
}
/**
* OTA上传次数
*/
public static String getOtaUploadCountKey(Long username) {
return "ota:upload:count:" + username;
}
} }
@@ -0,0 +1,89 @@
package xiaozhi.common.utils;
import cn.hutool.core.util.ReUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 通用工具类
*/
public class ToolUtil {
private static final Logger logger = LoggerFactory.getLogger(ToolUtil.class);
/**
* 对象是否不为空(新增)
*/
public static boolean isNotEmpty(Object o) {
return !isEmpty(o);
}
/**
* 对象是否为空
*/
public static boolean isEmpty(Object o) {
if (o == null) {
return true;
}
if (o instanceof String) {
if (o.toString().trim().equals("")) {
return true;
}
} else if (o instanceof List) {
if (((List) o).size() == 0) {
return true;
}
} else if (o instanceof Map) {
if (((Map) o).size() == 0) {
return true;
}
} else if (o instanceof Set) {
if (((Set) o).size() == 0) {
return true;
}
} else if (o instanceof Object[]) {
if (((Object[]) o).length == 0) {
return true;
}
} else if (o instanceof int[]) {
if (((int[]) o).length == 0) {
return true;
}
} else if (o instanceof long[]) {
if (((long[]) o).length == 0) {
return true;
}
}
return false;
}
/**
* 对象组中是否存在空对象
*/
public static boolean isOneEmpty(Object... os) {
for (Object o : os) {
if (isEmpty(o)) {
return true;
}
}
return false;
}
/**
* 对象组中是否全是空对象
*/
public static boolean isAllEmpty(Object... os) {
for (Object o : os) {
if (!isEmpty(o)) {
return false;
}
}
return true;
}
}
@@ -44,6 +44,7 @@ import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentTemplateEntity; import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentChatAudioService; import xiaozhi.modules.agent.service.AgentChatAudioService;
import xiaozhi.modules.agent.service.AgentChatHistoryService; import xiaozhi.modules.agent.service.AgentChatHistoryService;
import xiaozhi.modules.agent.service.AgentChatSummaryService;
import xiaozhi.modules.agent.service.AgentContextProviderService; import xiaozhi.modules.agent.service.AgentContextProviderService;
import xiaozhi.modules.agent.service.AgentPluginMappingService; import xiaozhi.modules.agent.service.AgentPluginMappingService;
import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentService;
@@ -66,14 +67,19 @@ public class AgentController {
private final AgentChatAudioService agentChatAudioService; private final AgentChatAudioService agentChatAudioService;
private final AgentPluginMappingService agentPluginMappingService; private final AgentPluginMappingService agentPluginMappingService;
private final AgentContextProviderService agentContextProviderService; private final AgentContextProviderService agentContextProviderService;
private final AgentChatSummaryService agentChatSummaryService;
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "获取用户智能体列表") @Operation(summary = "获取用户智能体列表")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result<List<AgentDTO>> getUserAgents() { public Result<List<AgentDTO>> getUserAgents(
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "searchType", defaultValue = "name") String searchType) {
UserDetail user = SecurityUser.getUser(); UserDetail user = SecurityUser.getUser();
List<AgentDTO> agents = agentService.getUserAgents(user.getId());
// 直接调用整合后的getUserAgents方法,无需再区分搜索和普通查询
List<AgentDTO> agents = agentService.getUserAgents(user.getId(), keyword, searchType);
return new Result<List<AgentDTO>>().ok(agents); return new Result<List<AgentDTO>>().ok(agents);
} }
@@ -119,6 +125,27 @@ public class AgentController {
return new Result<>(); return new Result<>();
} }
@PostMapping("/chat-summary/{sessionId}/save")
@Operation(summary = "根据会话ID生成聊天记录总结并保存(异步执行)")
public Result<Void> generateAndSaveChatSummary(@PathVariable String sessionId) {
try {
// 异步执行总结生成任务,立即返回成功响应
new Thread(() -> {
try {
agentChatSummaryService.generateAndSaveChatSummary(sessionId);
System.out.println("异步执行会话 " + sessionId + " 的聊天记录总结完成");
} catch (Exception e) {
System.err.println("异步执行会话 " + sessionId + " 的聊天记录总结失败: " + e.getMessage());
}
}).start();
// 立即返回成功响应,不等待总结生成完成
return new Result<Void>().ok(null);
} catch (Exception e) {
return new Result<Void>().error("启动异步总结生成任务失败: " + e.getMessage());
}
}
@PutMapping("/{id}") @PutMapping("/{id}")
@Operation(summary = "更新智能体") @Operation(summary = "更新智能体")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
@@ -186,6 +213,7 @@ public class AgentController {
List<AgentChatHistoryDTO> result = agentChatHistoryService.getChatHistoryBySessionId(id, sessionId); List<AgentChatHistoryDTO> result = agentChatHistoryService.getChatHistoryBySessionId(id, sessionId);
return new Result<List<AgentChatHistoryDTO>>().ok(result); return new Result<List<AgentChatHistoryDTO>>().ok(result);
} }
@GetMapping("/{id}/chat-history/user") @GetMapping("/{id}/chat-history/user")
@Operation(summary = "获取智能体聊天记录(用户)") @Operation(summary = "获取智能体聊天记录(用户)")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
@@ -247,4 +275,6 @@ public class AgentController {
.body(audioData); .body(audioData);
} }
} }
@@ -28,7 +28,7 @@ public class AgentMcpAccessPointController {
/** /**
* 获取智能体的Mcp接入点地址 * 获取智能体的Mcp接入点地址
* *
* @param audioId 智能体id * @param agentId 智能体id
* @return 返回错误提醒或者Mcp接入点地址 * @return 返回错误提醒或者Mcp接入点地址
*/ */
@Operation(summary = "获取智能体的Mcp接入点地址") @Operation(summary = "获取智能体的Mcp接入点地址")
@@ -1,6 +1,9 @@
package xiaozhi.modules.agent.dao; package xiaozhi.modules.agent.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -15,12 +18,6 @@ import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
*/ */
@Mapper @Mapper
public interface AiAgentChatHistoryDao extends BaseMapper<AgentChatHistoryEntity> { public interface AiAgentChatHistoryDao extends BaseMapper<AgentChatHistoryEntity> {
/**
* 根据智能体ID删除音频
*
* @param agentId 智能体ID
*/
void deleteAudioByAgentId(String agentId);
/** /**
* 根据智能体ID删除聊天历史记录 * 根据智能体ID删除聊天历史记录
@@ -35,4 +32,19 @@ public interface AiAgentChatHistoryDao extends BaseMapper<AgentChatHistoryEntity
* @param agentId 智能体ID * @param agentId 智能体ID
*/ */
void deleteAudioIdByAgentId(String agentId); void deleteAudioIdByAgentId(String agentId);
/**
* 根据智能体ID获取所有音频ID列表
*
* @param agentId 智能体ID
* @return 音频ID列表
*/
List<String> getAudioIdsByAgentId(String agentId);
/**
* 批量删除音频
*
* @param audioIds 音频ID列表
*/
void deleteAudioByIds(@Param("audioIds") List<String> audioIds);
} }
@@ -0,0 +1,45 @@
package xiaozhi.modules.agent.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 智能体聊天记录总结DTO
*/
@Data
@Schema(description = "智能体聊天记录总结对象")
public class AgentChatSummaryDTO {
@Schema(description = "会话ID")
private String sessionId;
@Schema(description = "智能体ID")
private String agentId;
@Schema(description = "总结内容")
private String summary;
@Schema(description = "总结状态")
private boolean success;
@Schema(description = "错误信息")
private String errorMessage;
public AgentChatSummaryDTO() {
this.success = true;
}
public AgentChatSummaryDTO(String sessionId, String agentId, String summary) {
this.sessionId = sessionId;
this.agentId = agentId;
this.summary = summary;
this.success = true;
}
public AgentChatSummaryDTO(String sessionId, String errorMessage) {
this.sessionId = sessionId;
this.errorMessage = errorMessage;
this.success = false;
}
}
@@ -1,6 +1,7 @@
package xiaozhi.modules.agent.dto; package xiaozhi.modules.agent.dto;
import java.util.Date; import java.util.Date;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@@ -0,0 +1,15 @@
package xiaozhi.modules.agent.service;
/**
* 智能体聊天记录总结服务接口
*/
public interface AgentChatSummaryService {
/**
* 根据会话ID生成聊天记录总结并保存到智能体记忆
*
* @param sessionId 会话ID
* @return 保存结果
*/
boolean generateAndSaveChatSummary(String sessionId);
}
@@ -54,9 +54,11 @@ public interface AgentService extends BaseService<AgentEntity> {
* 获取用户智能体列表 * 获取用户智能体列表
* *
* @param userId 用户ID * @param userId 用户ID
* @param keyword 搜索关键词
* @param searchType 搜索类型(name - 按名称搜索,mac - 按MAC地址搜索)
* @return 智能体列表 * @return 智能体列表
*/ */
List<AgentDTO> getUserAgents(Long userId); List<AgentDTO> getUserAgents(Long userId, String keyword, String searchType);
/** /**
* 根据智能体ID获取设备数量 * 根据智能体ID获取设备数量
@@ -98,4 +100,6 @@ public interface AgentService extends BaseService<AgentEntity> {
* @return 创建的智能体ID * @return 创建的智能体ID
*/ */
String createAgent(AgentCreateDTO dto); String createAgent(AgentCreateDTO dto);
} }
@@ -17,6 +17,7 @@ import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.service.AgentChatAudioService; import xiaozhi.modules.agent.service.AgentChatAudioService;
import xiaozhi.modules.agent.service.AgentChatHistoryService; import xiaozhi.modules.agent.service.AgentChatHistoryService;
import xiaozhi.modules.agent.service.AgentChatSummaryService;
import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService; import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService;
import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.entity.DeviceEntity;
@@ -36,6 +37,7 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
private final AgentService agentService; private final AgentService agentService;
private final AgentChatHistoryService agentChatHistoryService; private final AgentChatHistoryService agentChatHistoryService;
private final AgentChatAudioService agentChatAudioService; private final AgentChatAudioService agentChatAudioService;
private final AgentChatSummaryService agentChatSummaryService;
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
private final DeviceService deviceService; private final DeviceService deviceService;
@@ -50,7 +52,8 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
public Boolean report(AgentChatHistoryReportDTO report) { public Boolean report(AgentChatHistoryReportDTO report) {
String macAddress = report.getMacAddress(); String macAddress = report.getMacAddress();
Byte chatType = report.getChatType(); Byte chatType = report.getChatType();
Long reportTimeMillis = null != report.getReportTime() ? report.getReportTime() * 1000 : System.currentTimeMillis(); Long reportTimeMillis = null != report.getReportTime() ? report.getReportTime() * 1000
: System.currentTimeMillis();
log.info("小智设备聊天上报请求: macAddress={}, type={} reportTime={}", macAddress, chatType, reportTimeMillis); log.info("小智设备聊天上报请求: macAddress={}, type={} reportTime={}", macAddress, chatType, reportTimeMillis);
// 根据设备MAC地址查询对应的默认智能体,判断是否需要上报 // 根据设备MAC地址查询对应的默认智能体,判断是否需要上报
@@ -105,7 +108,8 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
/** /**
* 组装上报数据 * 组装上报数据
*/ */
private void saveChatText(AgentChatHistoryReportDTO report, String agentId, String macAddress, String audioId, Long reportTime) { private void saveChatText(AgentChatHistoryReportDTO report, String agentId, String macAddress, String audioId,
Long reportTime) {
// 构建聊天记录实体 // 构建聊天记录实体
AgentChatHistoryEntity entity = AgentChatHistoryEntity.builder() AgentChatHistoryEntity entity = AgentChatHistoryEntity.builder()
.macAddress(macAddress) .macAddress(macAddress)
@@ -5,6 +5,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import cn.hutool.core.collection.ListUtil;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -18,6 +19,7 @@ import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.JsonUtils; import xiaozhi.common.utils.JsonUtils;
import xiaozhi.common.utils.ToolUtil;
import xiaozhi.modules.agent.Enums.AgentChatHistoryType; import xiaozhi.modules.agent.Enums.AgentChatHistoryType;
import xiaozhi.modules.agent.dao.AiAgentChatHistoryDao; import xiaozhi.modules.agent.dao.AiAgentChatHistoryDao;
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
@@ -84,7 +86,15 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText) { public void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText) {
if (deleteAudio) { if (deleteAudio) {
baseMapper.deleteAudioByAgentId(agentId); // 分批删除音频,避免超时
List<String> audioIds = baseMapper.getAudioIdsByAgentId(agentId);
if (ToolUtil.isNotEmpty(audioIds)) {
// 每批删除1000条
List<List<String>> batch = ListUtil.split(audioIds, 1000);
batch.forEach(dataList->{
baseMapper.deleteAudioByIds(dataList);
});
}
} }
if (deleteAudio && !deleteText) { if (deleteAudio && !deleteText) {
baseMapper.deleteAudioIdByAgentId(agentId); baseMapper.deleteAudioIdByAgentId(agentId);
@@ -107,7 +117,7 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
// 添加此行,确保查询结果按照创建时间降序排列 // 添加此行,确保查询结果按照创建时间降序排列
// 使用id的原因:数据形式,id越大的创建时间就越晚,所以使用id的结果和创建时间降序排列结果一样 // 使用id的原因:数据形式,id越大的创建时间就越晚,所以使用id的结果和创建时间降序排列结果一样
// id作为降序排列的优势,性能高,有主键索引,不用在排序的时候重新进行排除扫描比较 // id作为降序排列的优势,性能高,有主键索引,不用在排序的时候重新进行排除扫描比较
.orderByDesc(AgentChatHistoryEntity::getId); .orderByDesc(AgentChatHistoryEntity::getId);
// 构建分页查询,查询前50页数据 // 构建分页查询,查询前50页数据
Page<AgentChatHistoryEntity> pageParam = new Page<>(0, 50); Page<AgentChatHistoryEntity> pageParam = new Page<>(0, 50);
@@ -0,0 +1,423 @@
package xiaozhi.modules.agent.service.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
import xiaozhi.modules.agent.dto.AgentChatSummaryDTO;
import xiaozhi.modules.agent.dto.AgentMemoryDTO;
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
import xiaozhi.modules.agent.service.AgentChatSummaryService;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.vo.AgentInfoVO;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.llm.service.LLMService;
import xiaozhi.modules.model.entity.ModelConfigEntity;
import xiaozhi.modules.model.service.ModelConfigService;
/**
* 智能体聊天记录总结服务实现类
* 实现Python端mem_local_short.py中的总结逻辑
*/
@Service
@RequiredArgsConstructor
public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
private static final Logger log = LoggerFactory.getLogger(AgentChatSummaryServiceImpl.class);
private final AgentChatHistoryService agentChatHistoryService;
private final AgentService agentService;
private final DeviceService deviceService;
private final LLMService llmService;
private final ModelConfigService modelConfigService;
// 总结规则常量
private static final int MAX_SUMMARY_LENGTH = 1800; // 最大总结长度
private static final Pattern JSON_PATTERN = Pattern.compile("\\{.*?\\}", Pattern.DOTALL);
private static final Pattern DEVICE_CONTROL_PATTERN = Pattern.compile("设备控制|设备操作|控制设备|设备状态",
Pattern.CASE_INSENSITIVE);
private static final Pattern WEATHER_PATTERN = Pattern.compile("天气|温度|湿度|降雨|气象", Pattern.CASE_INSENSITIVE);
private static final Pattern DATE_PATTERN = Pattern.compile("日期|时间|星期|月份|年份", Pattern.CASE_INSENSITIVE);
private AgentChatSummaryDTO generateChatSummary(String sessionId) {
try {
System.out.println("开始生成会话 " + sessionId + " 的聊天记录总结");
// 1. 根据sessionId获取聊天记录
List<AgentChatHistoryDTO> chatHistory = getChatHistoryBySessionId(sessionId);
if (chatHistory == null || chatHistory.isEmpty()) {
return new AgentChatSummaryDTO(sessionId, "未找到该会话的聊天记录");
}
// 2. 获取智能体信息
String agentId = getAgentIdFromSession(sessionId, chatHistory);
if (StringUtils.isBlank(agentId)) {
return new AgentChatSummaryDTO(sessionId, "无法获取智能体信息");
}
// 3. 提取关键对话内容
List<String> meaningfulMessages = extractMeaningfulMessages(chatHistory);
if (meaningfulMessages.isEmpty()) {
return new AgentChatSummaryDTO(sessionId, "没有有效的对话内容可总结");
}
// 4. 生成总结(generateSummaryFromMessages方法已包含长度限制逻辑)
String summary = generateSummaryFromMessages(meaningfulMessages, agentId);
log.info("成功生成会话 {} 的聊天记录总结,长度: {} 字符", sessionId, summary.length());
return new AgentChatSummaryDTO(sessionId, agentId, summary);
} catch (Exception e) {
log.error("生成会话 {} 的聊天记录总结时发生错误: {}", sessionId, e.getMessage());
return new AgentChatSummaryDTO(sessionId, "生成总结时发生错误: " + e.getMessage());
}
}
@Override
public boolean generateAndSaveChatSummary(String sessionId) {
try {
// 1. 生成总结
AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId);
if (!summaryDTO.isSuccess()) {
log.info("生成总结失败: {}", summaryDTO.getErrorMessage());
return false;
}
// 2. 获取设备信息(通过会话关联的设备)
DeviceEntity device = getDeviceBySessionId(sessionId);
if (device == null) {
log.info("未找到与会话 {} 关联的设备", sessionId);
return false;
}
// 3. 更新智能体记忆
AgentMemoryDTO memoryDTO = new AgentMemoryDTO();
memoryDTO.setSummaryMemory(summaryDTO.getSummary());
// 调用现有接口更新记忆
agentService.updateAgentById(device.getAgentId(),
new AgentUpdateDTO() {
{
setSummaryMemory(summaryDTO.getSummary());
}
});
log.info("成功保存会话 {} 的聊天记录总结到智能体 {}", sessionId, device.getAgentId());
return true;
} catch (Exception e) {
log.error("保存会话 {} 的聊天记录总结时发生错误: {}", sessionId, e.getMessage());
return false;
}
}
/**
* 根据会话ID获取聊天记录
*/
private List<AgentChatHistoryDTO> getChatHistoryBySessionId(String sessionId) {
try {
// 这里需要根据sessionId获取聊天记录
// 由于现有接口需要agentId,我们需要先找到关联的agentId
String agentId = findAgentIdBySessionId(sessionId);
if (StringUtils.isBlank(agentId)) {
return null;
}
return agentChatHistoryService.getChatHistoryBySessionId(agentId, sessionId);
} catch (Exception e) {
log.error("获取会话 {} 的聊天记录失败: {}", sessionId, e.getMessage());
return null;
}
}
/**
* 根据会话ID查找关联的智能体ID
*/
private String findAgentIdBySessionId(String sessionId) {
try {
// 查询该会话的第一条记录获取agentId
QueryWrapper<AgentChatHistoryEntity> wrapper = new QueryWrapper<>();
wrapper.select("agent_id")
.eq("session_id", sessionId)
.last("LIMIT 1");
AgentChatHistoryEntity entity = agentChatHistoryService.getOne(wrapper);
return entity != null ? entity.getAgentId() : null;
} catch (Exception e) {
log.error("根据会话ID {} 查找智能体ID失败: {}", sessionId, e.getMessage());
return null;
}
}
/**
* 从会话中获取智能体ID
*/
private String getAgentIdFromSession(String sessionId, List<AgentChatHistoryDTO> chatHistory) {
// 直接从数据库查询智能体ID
return findAgentIdBySessionId(sessionId);
}
/**
* 提取有意义的对话内容(只提取用户消息,排除AI回复)
*/
private List<String> extractMeaningfulMessages(List<AgentChatHistoryDTO> chatHistory) {
List<String> meaningfulMessages = new ArrayList<>();
for (AgentChatHistoryDTO message : chatHistory) {
// 只处理用户消息(chatType = 1
if (message.getChatType() != null && message.getChatType() == 1) {
String content = extractContentFromMessage(message);
if (isMeaningfulMessage(content)) {
meaningfulMessages.add(content);
}
}
}
return meaningfulMessages;
}
/**
* 从消息中提取内容(处理JSON格式)
*/
private String extractContentFromMessage(AgentChatHistoryDTO message) {
String content = message.getContent();
if (StringUtils.isBlank(content)) {
return "";
}
// 处理JSON格式内容(与前端ChatHistoryDialog.vue逻辑一致)
Matcher matcher = JSON_PATTERN.matcher(content);
if (matcher.find()) {
String jsonContent = matcher.group();
// 简化处理:提取JSON中的文本内容
return extractTextFromJson(jsonContent);
}
return content;
}
/**
* 从JSON中提取文本内容
*/
private String extractTextFromJson(String jsonContent) {
// 简化处理:提取"content"字段的值
Pattern contentPattern = Pattern.compile("\"content\"\s*:\s*\"([^\"]*)\"");
Matcher matcher = contentPattern.matcher(jsonContent);
if (matcher.find()) {
return matcher.group(1);
}
return jsonContent;
}
/**
* 判断是否为有意义的消息
*/
private boolean isMeaningfulMessage(String content) {
if (StringUtils.isBlank(content)) {
return false;
}
// 排除设备控制信息
if (DEVICE_CONTROL_PATTERN.matcher(content).find()) {
return false;
}
// 排除日期天气等无关内容
if (WEATHER_PATTERN.matcher(content).find() || DATE_PATTERN.matcher(content).find()) {
return false;
}
// 排除过短的消息
return content.length() >= 5;
}
/**
* 从消息生成总结
*/
private String generateSummaryFromMessages(List<String> messages, String agentId) {
if (messages.isEmpty()) {
return "本次对话内容较少,没有需要总结的重要信息。";
}
// 构建完整的对话内容
StringBuilder conversation = new StringBuilder();
for (int i = 0; i < messages.size(); i++) {
conversation.append("消息").append(i + 1).append(": ").append(messages.get(i)).append("\n");
}
try {
// 获取当前智能体的历史记忆
String historyMemory = getCurrentAgentMemory(agentId);
// 调用LLM服务进行智能总结,传递agentId以获取正确的模型配置
String summary = callJavaLLMForSummaryWithHistory(conversation.toString(), historyMemory, agentId);
// 应用总结规则:限制最大长度
if (summary.length() > MAX_SUMMARY_LENGTH) {
summary = summary.substring(0, MAX_SUMMARY_LENGTH) + "...";
}
return summary;
} catch (Exception e) {
log.error("调用Java端LLM服务失败: {}", e.getMessage());
throw new RuntimeException("LLM服务不可用,无法生成聊天总结");
}
}
/**
* 获取当前智能体的历史记忆
*/
private String getCurrentAgentMemory(String agentId) {
try {
if (StringUtils.isBlank(agentId)) {
return null;
}
// 获取智能体信息
AgentInfoVO agentInfo = agentService.getAgentById(agentId);
if (agentInfo == null) {
return null;
}
// 返回智能体的当前总结记忆
return agentInfo.getSummaryMemory();
} catch (Exception e) {
log.error("获取智能体历史记忆失败,agentId: {}, 错误: {}", agentId, e.getMessage());
return null;
}
}
/**
* 调用Java端LLM服务进行智能总结(支持历史记忆合并)
*/
private String callJavaLLMForSummaryWithHistory(String conversation, String historyMemory, String agentId) {
try {
// 获取智能体配置,从中提取记忆总结的模型ID
String modelId = getMemorySummaryModelId(agentId);
if (StringUtils.isBlank(modelId)) {
log.info("未找到记忆总结的LLM模型配置,使用默认LLM服务");
return llmService.generateSummaryWithHistory(conversation, historyMemory, null, null);
}
// 使用指定的模型ID调用LLM服务(支持历史记忆合并)
String summary = llmService.generateSummaryWithHistory(conversation, historyMemory, null, modelId);
if (StringUtils.isNotBlank(summary) && !summary.equals("服务暂不可用") && !summary.equals("总结生成失败")) {
return summary;
}
throw new RuntimeException("Java端LLM服务返回异常: " + summary);
} catch (Exception e) {
log.error("调用Java端LLM服务异常,agentId: {}, 错误: {}", agentId, e.getMessage());
throw e;
}
}
/**
* 调用Java端LLM服务进行智能总结
*/
private String callJavaLLMForSummary(String conversation, String agentId) {
try {
// 获取智能体配置,从中提取记忆总结的模型ID
String modelId = getMemorySummaryModelId(agentId);
if (StringUtils.isBlank(modelId)) {
log.info("未找到记忆总结的LLM模型配置,使用默认LLM服务");
return llmService.generateSummary(conversation);
}
// 使用指定的模型ID调用LLM服务
String summary = llmService.generateSummaryWithModel(conversation, modelId);
if (StringUtils.isNotBlank(summary) && !summary.equals("服务暂不可用") && !summary.equals("总结生成失败")) {
return summary;
}
throw new RuntimeException("Java端LLM服务返回异常: " + summary);
} catch (Exception e) {
log.error("调用Java端LLM服务异常,agentId: {}, 错误: {}", agentId, e.getMessage());
throw e;
}
}
/**
* 获取记忆总结的LLM模型ID
*/
private String getMemorySummaryModelId(String agentId) {
try {
if (StringUtils.isBlank(agentId)) {
return null;
}
// 获取智能体信息
AgentInfoVO agentInfo = agentService.getAgentById(agentId);
if (agentInfo == null) {
return null;
}
// 获取智能体的记忆模型ID
String memModelId = agentInfo.getMemModelId();
if (StringUtils.isBlank(memModelId)) {
return null;
}
// 获取记忆模型配置
ModelConfigEntity memModelConfig = modelConfigService.getModelByIdFromCache(memModelId);
if (memModelConfig == null || memModelConfig.getConfigJson() == null) {
return null;
}
// 从记忆模型配置中提取对应的LLM模型ID
Map<String, Object> configMap = memModelConfig.getConfigJson();
String llmModelId = (String) configMap.get("llm");
if (StringUtils.isBlank(llmModelId)) {
// 如果记忆模型没有配置独立的LLM,则使用智能体的默认LLM模型
return agentInfo.getLlmModelId();
}
return llmModelId;
} catch (Exception e) {
log.error("获取记忆总结LLM模型ID失败,agentId: {}, 错误: {}", agentId, e.getMessage());
return null;
}
}
/**
* 根据会话ID获取设备信息
*/
private DeviceEntity getDeviceBySessionId(String sessionId) {
try {
// 查询该会话的第一条记录获取macAddress
QueryWrapper<AgentChatHistoryEntity> wrapper = new QueryWrapper<>();
wrapper.select("mac_address")
.eq("session_id", sessionId)
.last("LIMIT 1");
AgentChatHistoryEntity entity = agentChatHistoryService.getOne(wrapper);
if (entity != null && StringUtils.isNotBlank(entity.getMacAddress())) {
return deviceService.getDeviceByMacAddress(entity.getMacAddress());
}
return null;
} catch (Exception e) {
log.error("根据会话ID {} 查找设备信息失败: {}", sessionId, e.getMessage());
return null;
}
}
}
@@ -147,6 +147,7 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
List<String> result = toolsList.stream() List<String> result = toolsList.stream()
.map(tool -> (String) tool.get("name")) .map(tool -> (String) tool.get("name"))
.filter(name -> name != null) .filter(name -> name != null)
.sorted()
.collect(Collectors.toList()); .collect(Collectors.toList());
log.info("成功获取MCP工具列表,智能体ID: {}, 工具数量: {}", id, result.size()); log.info("成功获取MCP工具列表,智能体ID: {}, 工具数量: {}", id, result.size());
return result; return result;
@@ -5,6 +5,7 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -28,6 +29,7 @@ import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.user.UserDetail; import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.JsonUtils; import xiaozhi.common.utils.JsonUtils;
import xiaozhi.common.utils.ToolUtil;
import xiaozhi.modules.agent.dao.AgentDao; import xiaozhi.modules.agent.dao.AgentDao;
import xiaozhi.modules.agent.dto.AgentCreateDTO; import xiaozhi.modules.agent.dto.AgentCreateDTO;
import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.dto.AgentDTO;
@@ -42,6 +44,7 @@ import xiaozhi.modules.agent.service.AgentPluginMappingService;
import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentTemplateService; import xiaozhi.modules.agent.service.AgentTemplateService;
import xiaozhi.modules.agent.vo.AgentInfoVO; import xiaozhi.modules.agent.vo.AgentInfoVO;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.dto.VoiceDTO; import xiaozhi.modules.model.dto.VoiceDTO;
@@ -84,17 +87,17 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode()); agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
if (agent.getChatHistoryConf() == null) {
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
}
} }
if (agent.getChatHistoryConf() == null) {
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
}
// 查询上下文源配置 // 查询上下文源配置
AgentContextProviderEntity contextProviderEntity = agentContextProviderService.getByAgentId(id); AgentContextProviderEntity contextProviderEntity = agentContextProviderService.getByAgentId(id);
if (contextProviderEntity != null) { if (contextProviderEntity != null) {
agent.setContextProviders(contextProviderEntity.getContextProviders()); agent.setContextProviders(contextProviderEntity.getContextProviders());
} }
// 无需额外查询插件列表,已通过SQL查询出来 // 无需额外查询插件列表,已通过SQL查询出来
return agent; return agent;
} }
@@ -127,38 +130,70 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
} }
@Override @Override
public List<AgentDTO> getUserAgents(Long userId) { public List<AgentDTO> getUserAgents(Long userId, String keyword, String searchType) {
QueryWrapper<AgentEntity> wrapper = new QueryWrapper<>(); QueryWrapper<AgentEntity> queryWrapper = new QueryWrapper<>();
wrapper.eq("user_id", userId); queryWrapper.eq("user_id", userId).orderByDesc("created_at");
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())); if (StringUtils.isNotBlank(keyword)) {
if ("mac".equals(searchType)) {
// 按MAC地址搜索:先搜索设备,再获取对应的智能体
List<DeviceEntity> devices = Optional
.ofNullable(deviceService.searchDevicesByMacAddress(keyword, userId)).orElseGet(ArrayList::new);
// 获取设备对应的智能体ID列表
List<String> agentIds = devices.stream()
.map(DeviceEntity::getAgentId)
.distinct()
.collect(Collectors.toList());
if (ToolUtil.isNotEmpty(agentIds)) {
queryWrapper.in("id", agentIds);
} else {
return new ArrayList<>();
}
} else {
// 按名称搜索
queryWrapper.like("agent_name", keyword);
}
}
// 获取 LLM 模型名称 // 执行查询
dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId())); List<AgentEntity> agentEntities = baseDao.selectList(queryWrapper);
// 获取 VLLM 模型名称 // 转换为DTO并设置所有必要字段
dto.setVllmModelName(modelConfigService.getModelNameById(agent.getVllmModelId())); return agentEntities.stream().map(this::buildAgentDTO).collect(Collectors.toList());
}
// 获取记忆模型名称 /**
dto.setMemModelId(agent.getMemModelId()); * 将AgentEntity转换为AgentDTO
*/
private AgentDTO buildAgentDTO(AgentEntity agent) {
AgentDTO dto = new AgentDTO();
dto.setId(agent.getId());
dto.setAgentName(agent.getAgentName());
dto.setSystemPrompt(agent.getSystemPrompt());
// 获取 TTS 音色名称 // 获取 TTS 模型名称
dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId())); dto.setTtsModelName(modelConfigService.getModelNameById(agent.getTtsModelId()));
// 获取智能体最近的最后连接时长 // 获取 LLM 模型名称
dto.setLastConnectedAt(deviceService.getLatestLastConnectionTime(agent.getId())); dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId()));
// 获取设备数量 // 获取 VLLM 模型名称
dto.setDeviceCount(getDeviceCountByAgentId(agent.getId())); dto.setVllmModelName(modelConfigService.getModelNameById(agent.getVllmModelId()));
return dto;
}).collect(Collectors.toList()); // 获取记忆模型名称
dto.setMemModelId(agent.getMemModelId());
// 获取 TTS 音色名称
dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId()));
// 获取智能体最近的最后连接时长
dto.setLastConnectedAt(deviceService.getLatestLastConnectionTime(agent.getId()));
// 获取设备数量
dto.setDeviceCount(getDeviceCountByAgentId(agent.getId()));
return dto;
} }
@Override @Override
@@ -468,4 +503,5 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
agentPluginMappingService.saveBatch(toInsert); agentPluginMappingService.saveBatch(toInsert);
return entity.getId(); return entity.getId();
} }
}
}
@@ -106,6 +106,15 @@ public class ConfigServiceImpl implements ConfigService {
@Override @Override
public Map<String, Object> getAgentModels(String macAddress, Map<String, String> selectedModule) { public Map<String, Object> getAgentModels(String macAddress, Map<String, String> selectedModule) {
// 检查是否为管理控制台请求
String redisKey = RedisKeys.getTmpRegisterMacKey(macAddress);
Object isAdminRequest = redisUtils.get(redisKey);
if (isAdminRequest != null && "true".equals(isAdminRequest)) {
// 管理控制台请求,返回getConfig的结果
redisUtils.delete(redisKey); // 使用后清理
return (Map<String, Object>) getConfig(true);
}
// 根据MAC地址查找设备 // 根据MAC地址查找设备
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress); DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
if (device == null) { if (device == null) {
@@ -154,11 +163,11 @@ public class ConfigServiceImpl implements ConfigService {
} }
result.put("chat_history_conf", chatHistoryConf); result.put("chat_history_conf", chatHistoryConf);
// 如果客户端已实例化模型,则不返回 // 如果客户端已实例化模型,则不返回
String alreadySelectedVadModelId = (String) selectedModule.get("VAD"); String alreadySelectedVadModelId = selectedModule.get("VAD");
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) { if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {
agent.setVadModelId(null); agent.setVadModelId(null);
} }
String alreadySelectedAsrModelId = (String) selectedModule.get("ASR"); String alreadySelectedAsrModelId = selectedModule.get("ASR");
if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) { if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) {
agent.setAsrModelId(null); agent.setAsrModelId(null);
} }
@@ -293,7 +302,7 @@ public class ConfigServiceImpl implements ConfigService {
private void buildVoiceprintConfig(String agentId, Map<String, Object> result) { private void buildVoiceprintConfig(String agentId, Map<String, Object> result) {
try { try {
// 获取声纹接口地址 // 获取声纹接口地址
String voiceprintUrl = sysParamsService.getValue("server.voice_print", true); String voiceprintUrl = sysParamsService.getValue(Constant.SERVER_VOICE_PRINT, true);
if (StringUtils.isBlank(voiceprintUrl) || "null".equals(voiceprintUrl)) { if (StringUtils.isBlank(voiceprintUrl) || "null".equals(voiceprintUrl)) {
return; return;
} }
@@ -1,14 +1,11 @@
package xiaozhi.modules.device.controller; package xiaozhi.modules.device.controller;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -16,9 +13,6 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -30,6 +24,7 @@ import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.Result; import xiaozhi.common.utils.Result;
import xiaozhi.modules.device.dto.DeviceManualAddDTO; import xiaozhi.modules.device.dto.DeviceManualAddDTO;
import xiaozhi.modules.device.dto.DeviceRegisterDTO; import xiaozhi.modules.device.dto.DeviceRegisterDTO;
import xiaozhi.modules.device.dto.DeviceToolsCallReqDTO;
import xiaozhi.modules.device.dto.DeviceUnBindDTO; import xiaozhi.modules.device.dto.DeviceUnBindDTO;
import xiaozhi.modules.device.dto.DeviceUpdateDTO; import xiaozhi.modules.device.dto.DeviceUpdateDTO;
import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.entity.DeviceEntity;
@@ -44,16 +39,11 @@ public class DeviceController {
private final DeviceService deviceService; private final DeviceService deviceService;
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
private final SysParamsService sysParamsService; private final SysParamsService sysParamsService;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
public DeviceController(DeviceService deviceService, RedisUtils redisUtils, SysParamsService sysParamsService, public DeviceController(DeviceService deviceService, RedisUtils redisUtils, SysParamsService sysParamsService) {
RestTemplate restTemplate, ObjectMapper objectMapper) {
this.deviceService = deviceService; this.deviceService = deviceService;
this.redisUtils = redisUtils; this.redisUtils = redisUtils;
this.sysParamsService = sysParamsService; this.sysParamsService = sysParamsService;
this.restTemplate = restTemplate;
this.objectMapper = objectMapper;
} }
@PostMapping("/bind/{agentId}/{deviceCode}") @PostMapping("/bind/{agentId}/{deviceCode}")
@@ -99,83 +89,12 @@ public class DeviceController {
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result<String> forwardToMqttGateway(@PathVariable String agentId, @RequestBody String requestBody) { public Result<String> forwardToMqttGateway(@PathVariable String agentId, @RequestBody String requestBody) {
try { try {
// 从系统参数中获取MQTT网关地址 return new Result<String>().ok(deviceService.getDeviceOnlineData(agentId));
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
return new Result<>();
}
// 获取当前用户的设备列表
UserDetail user = SecurityUser.getUser();
List<DeviceEntity> devices = deviceService.getUserDevices(user.getId(), agentId);
// 构建deviceIds数组
java.util.List<String> deviceIds = new java.util.ArrayList<>();
for (DeviceEntity device : devices) {
String macAddress = device.getMacAddress() != null ? device.getMacAddress() : "unknown";
String groupId = device.getBoard() != null ? device.getBoard() : "GID_default";
// 替换冒号为下划线
groupId = groupId.replace(":", "_");
macAddress = macAddress.replace(":", "_");
// 构建mqtt客户端ID格式:groupId@@@macAddress@@@macAddress
String mqttClientId = groupId + "@@@" + macAddress + "@@@" + macAddress;
deviceIds.add(mqttClientId);
}
// 构建完整的URL
String url = "http://" + mqttGatewayUrl + "/api/devices/status";
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
// 生成Bearer令牌
String token = generateBearerToken();
if (token == null) {
return new Result<String>().error("令牌生成失败");
}
headers.set("Authorization", "Bearer " + token);
// 构建请求体JSON
String jsonBody = "{\"clientIds\":" + objectMapper.writeValueAsString(deviceIds) + "}";
HttpEntity<String> requestEntity = new HttpEntity<>(jsonBody, headers);
// 发送POST请求
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
// 返回响应
return new Result<String>().ok(response.getBody());
} catch (Exception e) { } catch (Exception e) {
return new Result<String>().error("转发请求失败: " + e.getMessage()); return new Result<String>().error("转发请求失败: " + e.getMessage());
} }
} }
private String generateBearerToken() {
try {
// 获取当前日期,格式为yyyy-MM-dd
String dateStr = java.time.LocalDate.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
// 获取MQTT签名密钥
String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", false);
if (StringUtils.isBlank(signatureKey)) {
return null;
}
// 将日期字符串与MQTT_SIGNATURE_KEY连接
String tokenContent = dateStr + signatureKey;
// 对连接后的字符串进行SHA256哈希计算
String token = org.apache.commons.codec.digest.DigestUtils.sha256Hex(tokenContent);
return token;
} catch (Exception e) {
return null;
}
}
@PostMapping("/unbind") @PostMapping("/unbind")
@Operation(summary = "解绑设备") @Operation(summary = "解绑设备")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
@@ -210,4 +129,34 @@ public class DeviceController {
deviceService.manualAddDevice(user.getId(), dto); deviceService.manualAddDevice(user.getId(), dto);
return new Result<>(); return new Result<>();
} }
@PostMapping("/tools/list/{deviceId}")
@Operation(summary = "获取设备工具列表")
@RequiresPermissions("sys:role:normal")
public Result<Object> getDeviceTools(@PathVariable String deviceId) {
Object toolsData = deviceService.getDeviceTools(deviceId);
if (toolsData == null) {
return new Result<Object>().error(ErrorCode.DEVICE_NOT_EXIST);
}
return new Result<Object>().ok(toolsData);
}
@PostMapping("/tools/call/{deviceId}")
@Operation(summary = "调用设备工具")
@RequiresPermissions("sys:role:normal")
public Result<Object> callDeviceTool(@PathVariable String deviceId,
@Valid @RequestBody DeviceToolsCallReqDTO request) {
String toolName = request.getName();
Map<String, Object> arguments = request.getArguments();
Object result = deviceService.callDeviceTool(deviceId, toolName, arguments);
if (result == null) {
return new Result<Object>().error(ErrorCode.DEVICE_NOT_EXIST);
}
Result<Object> response = new Result<Object>();
response.setMsg("Tools called successfully");
return response.ok(result);
}
} }
@@ -7,7 +7,9 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -36,6 +38,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.constant.Constant; import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.redis.RedisUtils;
@@ -43,8 +46,11 @@ import xiaozhi.common.utils.Result;
import xiaozhi.common.validator.ValidatorUtils; import xiaozhi.common.validator.ValidatorUtils;
import xiaozhi.modules.device.entity.OtaEntity; import xiaozhi.modules.device.entity.OtaEntity;
import xiaozhi.modules.device.service.OtaService; import xiaozhi.modules.device.service.OtaService;
import xiaozhi.modules.security.user.SecurityUser;
import xiaozhi.modules.sys.enums.SuperAdminEnum;
import xiaozhi.modules.sys.service.SysParamsService;
@Tag(name = "设备管理", description = "OTA 相关接口") @Tag(name = "固件升级管理", description = "OTA 相关接口")
@Slf4j @Slf4j
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -53,6 +59,7 @@ public class OTAMagController {
private static final Logger logger = LoggerFactory.getLogger(OTAController.class); private static final Logger logger = LoggerFactory.getLogger(OTAController.class);
private final OtaService otaService; private final OtaService otaService;
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
private final SysParamsService sysParamsService;
@GetMapping @GetMapping
@Operation(summary = "分页查询 OTA 固件信息") @Operation(summary = "分页查询 OTA 固件信息")
@@ -145,15 +152,11 @@ public class OTAMagController {
// 检查下载次数 // 检查下载次数
String downloadCountKey = RedisKeys.getOtaDownloadCountKey(uuid); String downloadCountKey = RedisKeys.getOtaDownloadCountKey(uuid);
Integer downloadCount = (Integer) redisUtils.get(downloadCountKey); Integer downloadCount = (Integer) Optional.ofNullable(redisUtils.get(downloadCountKey)).orElse(0);
if (downloadCount == null) {
downloadCount = 0;
}
// 如果下载次数超过3次,返回404 // 如果下载次数超过3次,返回404
if (downloadCount >= 3) { if (downloadCount >= 3) {
redisUtils.delete(downloadCountKey); redisUtils.delete(List.of(downloadCountKey, RedisKeys.getOtaIdKey(uuid)));
redisUtils.delete(RedisKeys.getOtaIdKey(uuid));
logger.warn("Download limit exceeded for UUID: {}", uuid); logger.warn("Download limit exceeded for UUID: {}", uuid);
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
@@ -162,7 +165,17 @@ public class OTAMagController {
try { try {
// 获取固件信息 // 获取固件信息
OtaEntity otaEntity = otaService.selectById(id); OtaEntity otaEntity = null;
if (id.indexOf("file:") == 0) {
id = id.substring(5);
otaEntity = new OtaEntity();
otaEntity.setFirmwarePath(id);
otaEntity.setType("assets");
otaEntity.setVersion("1.0.0");
} else {
otaEntity = otaService.selectById(id);
}
if (otaEntity == null || StringUtils.isBlank(otaEntity.getFirmwarePath())) { if (otaEntity == null || StringUtils.isBlank(otaEntity.getFirmwarePath())) {
logger.warn("Firmware not found or path is empty for ID: {}", id); logger.warn("Firmware not found or path is empty for ID: {}", id);
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
@@ -170,6 +183,7 @@ public class OTAMagController {
// 获取文件路径 - 确保路径是绝对路径或正确的相对路径 // 获取文件路径 - 确保路径是绝对路径或正确的相对路径
String firmwarePath = otaEntity.getFirmwarePath(); String firmwarePath = otaEntity.getFirmwarePath();
String originalFilename = otaEntity.getType() + "_" + otaEntity.getVersion();
Path path; Path path;
// 检查是否是绝对路径 // 检查是否是绝对路径
@@ -203,7 +217,7 @@ public class OTAMagController {
byte[] fileContent = Files.readAllBytes(path); byte[] fileContent = Files.readAllBytes(path);
// 设置响应头 // 设置响应头
String originalFilename = otaEntity.getType() + "_" + otaEntity.getVersion();
if (firmwarePath.contains(".")) { if (firmwarePath.contains(".")) {
String extension = firmwarePath.substring(firmwarePath.lastIndexOf(".")); String extension = firmwarePath.substring(firmwarePath.lastIndexOf("."));
originalFilename += extension; originalFilename += extension;
@@ -279,6 +293,42 @@ public class OTAMagController {
} }
} }
@PostMapping("/uploadAssetsBin")
@Operation(summary = "上传资源固件文件")
@RequiresPermissions("sys:role:normal")
public Result<String> uploadAssetsBin(@RequestParam("file") MultipartFile file) {
String otaUrl = sysParamsService.getValue(Constant.SERVER_OTA, true);
if (StringUtils.isBlank(otaUrl) || otaUrl.equals("null")) {
return new Result<String>().error(ErrorCode.OTA_URL_EMPTY);
}
logger.info("username:{},uploadAssetsBin size: {}", SecurityUser.getUser().getUsername(), file.getSize());
// 验证文件大小 (资源固件最大20MB)
if (file.getSize() > 20 * 1024 * 1024) {
return new Result<String>().error(ErrorCode.VOICE_CLONE_AUDIO_TOO_LARGE);
}
// 普通用户只能每天上传50次
if (SecurityUser.getUser().getSuperAdmin() == SuperAdminEnum.NO.value()) {
String uploadCountKey = RedisKeys.getOtaUploadCountKey(SecurityUser.getUser().getId());
Integer uploadCount = (Integer) Optional.ofNullable(redisUtils.get(uploadCountKey)).orElse(0);
if (uploadCount >= 50) {
return new Result<String>().error(ErrorCode.OTA_UPLOAD_COUNT_EXCEED);
}
// 增加上传次数
redisUtils.increment(RedisKeys.getOtaUploadCountKey(SecurityUser.getUser().getId()),
RedisUtils.DEFAULT_EXPIRE);
}
Result<String> result = uploadFirmware(file);
// 生成资源文件路径
if (StringUtils.isNotBlank(result.getData())) {
String uuid = UUID.randomUUID().toString();
redisUtils.set(RedisKeys.getOtaIdKey(uuid), "file:" + result.getData());
String downloadUrl = otaUrl.replace("/ota/", "/otaMag/download/") + uuid;
result.setData(downloadUrl);
}
return result;
}
private String calculateMD5(MultipartFile file) throws IOException, NoSuchAlgorithmException { private String calculateMD5(MultipartFile file) throws IOException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5"); MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(file.getBytes()); byte[] digest = md.digest(file.getBytes());
@@ -0,0 +1,15 @@
package xiaozhi.modules.device.dto;
import java.util.Map;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class DeviceToolsCallReqDTO {
@NotBlank(message = "工具名称不能为空")
private String name;
private Map<String, Object> arguments;
}
@@ -2,17 +2,22 @@ package xiaozhi.modules.device.service;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService; import xiaozhi.common.service.BaseService;
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
import xiaozhi.modules.device.dto.DevicePageUserDTO; import xiaozhi.modules.device.dto.DevicePageUserDTO;
import xiaozhi.modules.device.dto.DeviceReportReqDTO; import xiaozhi.modules.device.dto.DeviceReportReqDTO;
import xiaozhi.modules.device.dto.DeviceReportRespDTO; import xiaozhi.modules.device.dto.DeviceReportRespDTO;
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.vo.UserShowDeviceListVO; import xiaozhi.modules.device.vo.UserShowDeviceListVO;
public interface DeviceService extends BaseService<DeviceEntity> { public interface DeviceService extends BaseService<DeviceEntity> {
/**
* 获取设备在线数据
*/
String getDeviceOnlineData(String agentId);
/** /**
* 检查设备是否激活 * 检查设备是否激活
@@ -83,6 +88,7 @@ public interface DeviceService extends BaseService<DeviceEntity> {
/** /**
* 获取这个智能体设备理的最近的最后连接时间 * 获取这个智能体设备理的最近的最后连接时间
*
* @param agentId 智能体id * @param agentId 智能体id
* @return 返回设备最近的最后连接时间 * @return 返回设备最近的最后连接时间
*/ */
@@ -98,4 +104,33 @@ public interface DeviceService extends BaseService<DeviceEntity> {
*/ */
void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion); void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion);
/**
* 生成WebSocket认证token
*
* @param clientId 客户端ID
* @param username 用户名(通常为deviceId)
* @return 认证token字符串
* @throws Exception 生成token时的异常
*/
String generateWebSocketToken(String clientId, String username) throws Exception;
/**
* 根据MAC地址搜索设备
*
* @param macAddress MAC地址关键词
* @param userId 用户ID
* @return 设备列表
*/
List<DeviceEntity> searchDevicesByMacAddress(String macAddress, Long userId);
/**
* 获取设备工具列表
*/
Object getDeviceTools(String deviceId);
/**
* 调用设备工具
*/
Object callDeviceTool(String deviceId, String toolName, Map<String, Object> arguments);
} }
@@ -9,8 +9,11 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
@@ -26,7 +29,16 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -40,6 +52,7 @@ import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.user.UserDetail; import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.DateUtils; import xiaozhi.common.utils.DateUtils;
import xiaozhi.common.utils.ToolUtil;
import xiaozhi.modules.device.dao.DeviceDao; import xiaozhi.modules.device.dao.DeviceDao;
import xiaozhi.modules.device.dto.DeviceManualAddDTO; import xiaozhi.modules.device.dto.DeviceManualAddDTO;
import xiaozhi.modules.device.dto.DevicePageUserDTO; import xiaozhi.modules.device.dto.DevicePageUserDTO;
@@ -88,16 +101,16 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
if (StringUtils.isBlank(activationCode)) { if (StringUtils.isBlank(activationCode)) {
throw new RenException(ErrorCode.ACTIVATION_CODE_EMPTY); throw new RenException(ErrorCode.ACTIVATION_CODE_EMPTY);
} }
String deviceKey = "ota:activation:code:" + activationCode; String deviceKey = RedisKeys.getOtaActivationCode(activationCode);
Object cacheDeviceId = redisUtils.get(deviceKey); Object cacheDeviceId = redisUtils.get(deviceKey);
if (cacheDeviceId == null) { if (ToolUtil.isEmpty(cacheDeviceId)) {
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR); throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
} }
String deviceId = (String) cacheDeviceId; String deviceId = (String) cacheDeviceId;
String safeDeviceId = deviceId.replace(":", "_").toLowerCase(); String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
String cacheDeviceKey = String.format("ota:activation:data:%s", safeDeviceId); String cacheDeviceKey = RedisKeys.getOtaDeviceActivationInfo(safeDeviceId);
Map<String, Object> cacheMap = (Map<String, Object>) redisUtils.get(cacheDeviceKey); Map<String, Object> cacheMap = (Map<String, Object>) redisUtils.get(cacheDeviceKey);
if (cacheMap == null) { if (ToolUtil.isEmpty(cacheMap)) {
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR); throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
} }
String cachedCode = (String) cacheMap.get("activation_code"); String cachedCode = (String) cacheMap.get("activation_code");
@@ -133,19 +146,56 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
deviceEntity.setLastConnectedAt(currentTime); deviceEntity.setLastConnectedAt(currentTime);
deviceDao.insert(deviceEntity); deviceDao.insert(deviceEntity);
// 清理redis缓存 // 清理redis缓存、清除智能体设备数量缓存
redisUtils.delete(cacheDeviceKey); redisUtils.delete(List.of(cacheDeviceKey, deviceKey, RedisKeys.getAgentDeviceCountById(agentId)));
redisUtils.delete(deviceKey);
// 添加:清除智能体设备数量缓存
redisUtils.delete(RedisKeys.getAgentDeviceCountById(agentId));
return true; return true;
} }
/**
* 获取设备在线数据
*/
@Override @Override
public DeviceReportRespDTO checkDeviceActive(String macAddress, String clientId, public String getDeviceOnlineData(String agentId) {
DeviceReportReqDTO deviceReport) { // 从系统参数中获取MQTT网关地址
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
return "";
}
// 构建完整的URL
String url = StrUtil.format("http://{}/api/devices/status", mqttGatewayUrl);
// 获取当前用户的设备列表
UserDetail user = SecurityUser.getUser();
List<DeviceEntity> devices = getUserDevices(user.getId(), agentId);
// 构建deviceIds数组
Set<String> deviceIds = devices.stream().map(o -> {
String macAddress = Optional.ofNullable(o.getMacAddress()).orElse("unknown").replace(":", "_");
String groupId = Optional.ofNullable(o.getBoard()).orElse("GID_default").replace(":", "_");
return StrUtil.format("{}@@@{}@@@{}", groupId, macAddress, macAddress);
}).collect(Collectors.toSet());
// 构建请求入参
Map<String, Set<String>> params = MapUtil
.builder(new HashMap<String, Set<String>>())
.put("clientIds", deviceIds).build();
if (ToolUtil.isNotEmpty(deviceIds)) {
// 发送请求
String resultMessage = HttpRequest.post(url)
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
.header(Header.AUTHORIZATION, "Bearer " + generateBearerToken())
.body(JSONUtil.toJsonStr(params))
.timeout(10000) // 超时,毫秒
.execute().body();
return resultMessage;
}
// 返回响应
return "";
}
@Override
public DeviceReportRespDTO checkDeviceActive(String macAddress, String clientId, DeviceReportReqDTO deviceReport) {
DeviceReportRespDTO response = new DeviceReportRespDTO(); DeviceReportRespDTO response = new DeviceReportRespDTO();
response.setServer_time(buildServerTime()); response.setServer_time(buildServerTime());
@@ -356,8 +406,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
private String getDeviceCacheKey(String deviceId) { private String getDeviceCacheKey(String deviceId) {
String safeDeviceId = deviceId.replace(":", "_").toLowerCase(); String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
String dataKey = String.format("ota:activation:data:%s", safeDeviceId); return RedisKeys.getOtaDeviceActivationInfo(safeDeviceId);
return dataKey;
} }
public DeviceReportRespDTO.Activation buildActivation(String deviceId, DeviceReportReqDTO deviceReport) { public DeviceReportRespDTO.Activation buildActivation(String deviceId, DeviceReportReqDTO deviceReport) {
@@ -396,7 +445,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
redisUtils.set(dataKey, dataMap); redisUtils.set(dataKey, dataMap);
// 写入反查激活码 key // 写入反查激活码 key
String codeKey = "ota:activation:code:" + newCode; String codeKey = RedisKeys.getOtaActivationCode(newCode);
redisUtils.set(codeKey, deviceId); redisUtils.set(codeKey, deviceId);
} }
return code; return code;
@@ -496,6 +545,14 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
redisUtils.delete(RedisKeys.getAgentDeviceCountById(dto.getAgentId())); redisUtils.delete(RedisKeys.getAgentDeviceCountById(dto.getAgentId()));
} }
@Override
public List<DeviceEntity> searchDevicesByMacAddress(String macAddress, Long userId) {
QueryWrapper<DeviceEntity> wrapper = new QueryWrapper<>();
wrapper.like("mac_address", macAddress);
wrapper.eq("user_id", userId);
return deviceDao.selectList(wrapper);
}
/** /**
* 生成MQTT密码签名 * 生成MQTT密码签名
* *
@@ -518,7 +575,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
* @param username 用户名 (通常为deviceId/macAddress) * @param username 用户名 (通常为deviceId/macAddress)
* @return 认证token字符串 * @return 认证token字符串
*/ */
private String generateWebSocketToken(String clientId, String username) public String generateWebSocketToken(String clientId, String username)
throws NoSuchAlgorithmException, InvalidKeyException { throws NoSuchAlgorithmException, InvalidKeyException {
// 从系统参数获取密钥 // 从系统参数获取密钥
String secretKey = sysParamsService.getValue(Constant.SERVER_SECRET, false); String secretKey = sysParamsService.getValue(Constant.SERVER_SECRET, false);
@@ -598,4 +655,176 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
return mqtt; return mqtt;
} }
/**
* 生成BearerToken
*/
private String generateBearerToken() {
try {
String dateStr = DateUtil.format(new Date(), DatePattern.NORM_DATE_PATTERN);
String signatureKey = sysParamsService.getValue(Constant.SERVER_MQTT_SECRET, false);
if (ToolUtil.isEmpty(signatureKey)) {
return null;
}
return DigestUtil.sha256Hex(dateStr + signatureKey);
} catch (Exception e) {
return null;
}
}
@Override
public Object getDeviceTools(String deviceId) {
// 从系统参数中获取MQTT网关地址
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
return null;
}
// 获取设备信息
DeviceEntity device = baseDao.selectById(deviceId);
if (device == null) {
return null;
}
// 检查设备是否属于当前用户
UserDetail user = SecurityUser.getUser();
if (!device.getUserId().equals(user.getId())) {
return null;
}
// 构建clientId
String macAddress = Optional.ofNullable(device.getMacAddress()).orElse("unknown").replace(":", "_");
String groupId = Optional.ofNullable(device.getBoard()).orElse("GID_default").replace(":", "_");
String clientId = StrUtil.format("{}@@@{}@@@{}", groupId, macAddress, macAddress);
// 构建完整的URL
String url = StrUtil.format("http://{}/api/commands/{}", mqttGatewayUrl, clientId);
// 构建请求体
Map<String, Object> payload = MapUtil
.builder(new HashMap<String, Object>())
.put("jsonrpc", "2.0")
.put("id", 2)
.put("method", "tools/list")
.put("params", MapUtil.builder(new HashMap<String, Object>())
.put("withUserTools", true)
.build())
.build();
Map<String, Object> requestBody = MapUtil
.builder(new HashMap<String, Object>())
.put("type", "mcp")
.put("payload", payload)
.build();
// 发送请求
String resultMessage = HttpRequest.post(url)
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
.header(Header.AUTHORIZATION, "Bearer " + generateBearerToken())
.body(JSONUtil.toJsonStr(requestBody))
.timeout(10000) // 超时,毫秒
.execute().body();
// 解析响应
if (StringUtils.isNotBlank(resultMessage)) {
cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(resultMessage);
if (jsonObject.getBool("success", false)) {
return jsonObject.get("data");
}
}
return null;
}
@Override
public Object callDeviceTool(String deviceId, String toolName, Map<String, Object> arguments) {
// 从系统参数中获取MQTT网关地址
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
return null;
}
// 获取设备信息
DeviceEntity device = baseDao.selectById(deviceId);
if (device == null) {
return null;
}
// 检查设备是否属于当前用户
UserDetail user = SecurityUser.getUser();
if (!device.getUserId().equals(user.getId())) {
return null;
}
// 构建clientId
String macAddress = Optional.ofNullable(device.getMacAddress()).orElse("unknown").replace(":", "_");
String groupId = Optional.ofNullable(device.getBoard()).orElse("GID_default").replace(":", "_");
String clientId = StrUtil.format("{}@@@{}@@@{}", groupId, macAddress, macAddress);
// 构建完整的URL
String url = StrUtil.format("http://{}/api/commands/{}", mqttGatewayUrl, clientId);
// 构建请求体
Map<String, Object> params = MapUtil
.builder(new HashMap<String, Object>())
.put("name", toolName)
.put("arguments", arguments)
.build();
Map<String, Object> payload = MapUtil
.builder(new HashMap<String, Object>())
.put("jsonrpc", "2.0")
.put("id", 2)
.put("method", "tools/call")
.put("params", params)
.build();
Map<String, Object> requestBody = MapUtil
.builder(new HashMap<String, Object>())
.put("type", "mcp")
.put("payload", payload)
.build();
// 发送请求
String resultMessage = HttpRequest.post(url)
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
.header(Header.AUTHORIZATION, "Bearer " + generateBearerToken())
.body(JSONUtil.toJsonStr(requestBody))
.timeout(10000) // 超时,毫秒
.execute().body();
// 解析响应
if (StringUtils.isNotBlank(resultMessage)) {
cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(resultMessage);
if (jsonObject.getBool("success", false)) {
cn.hutool.json.JSONObject data = jsonObject.getJSONObject("data");
if (data != null) {
cn.hutool.json.JSONArray content = data.getJSONArray("content");
if (content != null && content.size() > 0) {
cn.hutool.json.JSONObject firstContent = content.getJSONObject(0);
if (firstContent != null && "text".equals(firstContent.getStr("type"))) {
String text = firstContent.getStr("text");
if (StringUtils.isNotBlank(text)) {
String trimmedText = text.trim();
if (trimmedText.startsWith("{") || trimmedText.startsWith("[")) {
try {
return JSONUtil.parseObj(trimmedText);
} catch (Exception e) {
return trimmedText;
}
} else if ("true".equals(trimmedText)) {
return true;
} else if ("false".equals(trimmedText)) {
return false;
} else {
return trimmedText;
}
}
}
}
}
}
}
return null;
}
} }
@@ -1,7 +1,6 @@
package xiaozhi.modules.knowledge.controller; package xiaozhi.modules.knowledge.controller;
import java.util.List; import java.util.*;
import java.util.Map;
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;
@@ -24,8 +23,10 @@ import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.Result; import xiaozhi.common.utils.Result;
import xiaozhi.common.utils.ToolUtil;
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO; import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
import xiaozhi.modules.knowledge.service.KnowledgeBaseService; import xiaozhi.modules.knowledge.service.KnowledgeBaseService;
import xiaozhi.modules.model.entity.ModelConfigEntity;
import xiaozhi.modules.security.user.SecurityUser; import xiaozhi.modules.security.user.SecurityUser;
@AllArgsConstructor @AllArgsConstructor
@@ -131,20 +132,17 @@ public class KnowledgeBaseController {
// 获取当前登录用户ID // 获取当前登录用户ID
Long currentUserId = SecurityUser.getUserId(); Long currentUserId = SecurityUser.getUserId();
String[] idArray = ids.split(","); List<String> idList = Arrays.asList(ids.split(","));
for (String datasetId : idArray) { List<KnowledgeBaseDTO> knowledgeBaseDTOs = Optional.ofNullable(knowledgeBaseService.getByDatasetIdList(idList)).orElseGet(ArrayList::new);
if (StringUtils.isNotBlank(datasetId)) { if (ToolUtil.isNotEmpty(knowledgeBaseDTOs)) {
// 先获取现有知识库信息以检查权限 knowledgeBaseDTOs.forEach(item->{
KnowledgeBaseDTO existingKnowledgeBase = knowledgeBaseService.getByDatasetId(datasetId.trim());
// 检查权限用户只能删除自己创建的知识库 // 检查权限用户只能删除自己创建的知识库
if (existingKnowledgeBase.getCreator() == null if (item.getCreator() == null || !item.getCreator().equals(currentUserId)) {
|| !existingKnowledgeBase.getCreator().equals(currentUserId)) {
throw new RenException(ErrorCode.NO_PERMISSION); throw new RenException(ErrorCode.NO_PERMISSION);
} }
//删除
knowledgeBaseService.deleteByDatasetId(datasetId.trim()); knowledgeBaseService.deleteByDatasetId(item.getDatasetId());
} });
} }
return new Result<>(); return new Result<>();
} }
@@ -152,8 +150,8 @@ public class KnowledgeBaseController {
@GetMapping("/rag-models") @GetMapping("/rag-models")
@Operation(summary = "获取RAG模型列表") @Operation(summary = "获取RAG模型列表")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result<List<Map<String, Object>>> getRAGModels() { public Result<List<ModelConfigEntity>> getRAGModels() {
List<Map<String, Object>> result = knowledgeBaseService.getRAGModels(); List<ModelConfigEntity> result = knowledgeBaseService.getRAGModels();
return new Result<List<Map<String, Object>>>().ok(result); return new Result<List<ModelConfigEntity>>().ok(result);
} }
} }
@@ -63,16 +63,11 @@ public class KnowledgeFilesController {
// 验证知识库权限 // 验证知识库权限
validateKnowledgeBasePermission(datasetId); validateKnowledgeBasePermission(datasetId);
// 如果指定了状态参数使用状态查询接口 //组装参数
if (status != null) {
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageListByStatus(datasetId, status, page, page_size);
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
}
// 否则使用通用查询接口
KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO(); KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO();
knowledgeFilesDTO.setDatasetId(datasetId); knowledgeFilesDTO.setDatasetId(datasetId);
knowledgeFilesDTO.setName(name); knowledgeFilesDTO.setName(name);
knowledgeFilesDTO.setStatus(status);
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageList(knowledgeFilesDTO, page, page_size); PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageList(knowledgeFilesDTO, page, page_size);
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData); return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
} }
@@ -87,8 +82,11 @@ public class KnowledgeFilesController {
@RequestParam(required = false, defaultValue = "10") Integer page_size) { @RequestParam(required = false, defaultValue = "10") Integer page_size) {
// 验证知识库权限 // 验证知识库权限
validateKnowledgeBasePermission(datasetId); validateKnowledgeBasePermission(datasetId);
//组装参数
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageListByStatus(datasetId, status, page, page_size); KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO();
knowledgeFilesDTO.setDatasetId(datasetId);
knowledgeFilesDTO.setStatus(status);
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageList(knowledgeFilesDTO, page, page_size);
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData); return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
} }
@@ -159,8 +157,7 @@ public class KnowledgeFilesController {
// 验证知识库权限 // 验证知识库权限
validateKnowledgeBasePermission(datasetId); validateKnowledgeBasePermission(datasetId);
Map<String, Object> result = knowledgeFilesService.listChunks(datasetId, documentId, keywords, page, page_size, Map<String, Object> result = knowledgeFilesService.listChunks(datasetId, documentId, keywords, page, page_size, id);
id);
return new Result<Map<String, Object>>().ok(result); return new Result<Map<String, Object>>().ok(result);
} }
@@ -7,6 +7,7 @@ import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService; import xiaozhi.common.service.BaseService;
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO; import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
import xiaozhi.modules.knowledge.entity.KnowledgeBaseEntity; import xiaozhi.modules.knowledge.entity.KnowledgeBaseEntity;
import xiaozhi.modules.model.entity.ModelConfigEntity;
/** /**
* 知识库知识库服务接口 * 知识库知识库服务接口
@@ -55,6 +56,14 @@ public interface KnowledgeBaseService extends BaseService<KnowledgeBaseEntity> {
*/ */
KnowledgeBaseDTO getByDatasetId(String datasetId); KnowledgeBaseDTO getByDatasetId(String datasetId);
/**
* 根据知识库ID集合查询知识库
*
* @param datasetIdList 知识库ID集合
* @return 知识库详情
*/
List<KnowledgeBaseDTO> getByDatasetIdList(List<String> datasetIdList);
/** /**
* 根据知识库ID删除知识库 * 根据知识库ID删除知识库
* *
@@ -83,5 +92,5 @@ public interface KnowledgeBaseService extends BaseService<KnowledgeBaseEntity> {
* *
* @return RAG模型列表 * @return RAG模型列表
*/ */
List<Map<String, Object>> getRAGModels(); List<ModelConfigEntity> getRAGModels();
} }
@@ -47,17 +47,6 @@ public interface KnowledgeFilesService {
Map<String, Object> metaFields, String chunkMethod, Map<String, Object> metaFields, String chunkMethod,
Map<String, Object> parserConfig); Map<String, Object> parserConfig);
/**
* 根据状态分页查询文档列表
*
* @param datasetId 知识库ID
* @param status 文档解析状态0-未开始1-进行中2-已取消3-已完成4-失败
* @param page 页码
* @param limit 每页数量
* @return 分页数据
*/
PageData<KnowledgeFilesDTO> getPageListByStatus(String datasetId, Integer status, Integer page, Integer limit);
/** /**
* 根据文档ID和知识库ID删除文档 * 根据文档ID和知识库ID删除文档
* *
@@ -1,21 +1,12 @@
package xiaozhi.modules.knowledge.service.impl; package xiaozhi.modules.knowledge.service.impl;
import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -32,6 +23,7 @@ import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.MessageUtils; import xiaozhi.common.utils.MessageUtils;
import xiaozhi.common.utils.ToolUtil;
import xiaozhi.modules.knowledge.dao.KnowledgeBaseDao; import xiaozhi.modules.knowledge.dao.KnowledgeBaseDao;
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO; import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
import xiaozhi.modules.knowledge.entity.KnowledgeBaseEntity; import xiaozhi.modules.knowledge.entity.KnowledgeBaseEntity;
@@ -53,8 +45,6 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
private final ModelConfigService modelConfigService; private final ModelConfigService modelConfigService;
private final ModelConfigDao modelConfigDao; private final ModelConfigDao modelConfigDao;
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
private RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();
@Override @Override
public KnowledgeBaseEntity selectById(Serializable datasetId) { public KnowledgeBaseEntity selectById(Serializable datasetId) {
@@ -269,6 +259,26 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class); return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
} }
/**
* 根据知识库ID集合查询知识库
* @param datasetIdList 知识库ID集合
* @return
*/
@Override
public List<KnowledgeBaseDTO> getByDatasetIdList(List<String> datasetIdList) {
//判断参数
if (ToolUtil.isEmpty(datasetIdList)) {
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
}
//批量查询
List<KnowledgeBaseEntity> entityList = knowledgeBaseDao.selectList(
new QueryWrapper<KnowledgeBaseEntity>().in("dataset_id", datasetIdList));
if (ToolUtil.isEmpty(entityList)) {
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
}
return ConvertUtils.sourceToTarget(entityList, KnowledgeBaseDTO.class);
}
@Override @Override
public void deleteByDatasetId(String datasetId) { public void deleteByDatasetId(String datasetId) {
if (StringUtils.isBlank(datasetId)) { if (StringUtils.isBlank(datasetId)) {
@@ -380,24 +390,17 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
} }
@Override @Override
public List<Map<String, Object>> getRAGModels() { public List<ModelConfigEntity> getRAGModels() {
// 查询RAG类型的模型配置 // 查询RAG类型的模型配置
QueryWrapper<ModelConfigEntity> queryWrapper = new QueryWrapper<>(); QueryWrapper<ModelConfigEntity> queryWrapper = new QueryWrapper<ModelConfigEntity>()
queryWrapper.eq("model_type", Constant.RAG_CONFIG_TYPE) .select("id", "model_name")
.eq("model_type", Constant.RAG_CONFIG_TYPE)
.eq("is_enabled", 1) .eq("is_enabled", 1)
.orderByDesc("is_default") .orderByDesc("is_default")
.orderByDesc("create_date"); .orderByDesc("create_date");
List<ModelConfigEntity> modelConfigs = modelConfigDao.selectList(queryWrapper); List<ModelConfigEntity> modelConfigs = modelConfigDao.selectList(queryWrapper);
return modelConfigs;
List<Map<String, Object>> modelList = new ArrayList<>();
for (ModelConfigEntity modelConfig : modelConfigs) {
Map<String, Object> modelInfo = new HashMap<>();
modelInfo.put("id", modelConfig.getId());
modelInfo.put("modelName", modelConfig.getModelName());
modelList.add(modelInfo);
}
return modelList;
} }
/** /**
@@ -3,24 +3,10 @@ package xiaozhi.modules.knowledge.service.impl;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.AbstractResource; import org.springframework.core.io.AbstractResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@@ -31,6 +17,7 @@ import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.ToolUtil;
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO; import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapter; import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapter;
import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapterFactory; import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapterFactory;
@@ -43,8 +30,6 @@ import xiaozhi.modules.knowledge.service.KnowledgeFilesService;
public class KnowledgeFilesServiceImpl implements KnowledgeFilesService { public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
private final KnowledgeBaseService knowledgeBaseService; private final KnowledgeBaseService knowledgeBaseService;
private RestTemplate restTemplate = new RestTemplate();
private ObjectMapper objectMapper = new ObjectMapper();
@Override @Override
public Map<String, Object> getRAGConfig(String ragModelId) { public Map<String, Object> getRAGConfig(String ragModelId) {
@@ -55,32 +40,29 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
public PageData<KnowledgeFilesDTO> getPageList(KnowledgeFilesDTO knowledgeFilesDTO, Integer page, Integer limit) { public PageData<KnowledgeFilesDTO> getPageList(KnowledgeFilesDTO knowledgeFilesDTO, Integer page, Integer limit) {
try { try {
log.info("=== 开始获取文档列表 ==="); log.info("=== 开始获取文档列表 ===");
log.info("查询条件: datasetId={}, name={}, status={}, page={}, limit={}", log.info("查询条件: datasetId={}, name={}, status={}, page={}, limit={}", knowledgeFilesDTO.getDatasetId(), knowledgeFilesDTO.getName(), knowledgeFilesDTO.getStatus(), page, limit);
knowledgeFilesDTO != null ? knowledgeFilesDTO.getDatasetId() : null,
knowledgeFilesDTO != null ? knowledgeFilesDTO.getName() : null,
knowledgeFilesDTO != null ? knowledgeFilesDTO.getStatus() : null,
page, limit);
// 获取数据集ID // 获取数据集ID
String datasetId = knowledgeFilesDTO != null ? knowledgeFilesDTO.getDatasetId() : null; String datasetId = knowledgeFilesDTO.getDatasetId();
if (StringUtils.isBlank(datasetId)) { if (ToolUtil.isEmpty(datasetId)) {
throw new RenException(ErrorCode.RAG_DATASET_ID_NOT_NULL); throw new RenException(ErrorCode.RAG_DATASET_ID_NOT_NULL);
} }
// 获取RAG配置 // 获取RAG配置
Map<String, Object> ragConfig = knowledgeBaseService.getRAGConfigByDatasetId(datasetId); Map<String, Object> ragConfig = knowledgeBaseService.getRAGConfigByDatasetId(datasetId);
// 提取适配器类型 // 提取适配器类型
String adapterType = extractAdapterType(ragConfig); String adapterType = extractAdapterType(ragConfig);
// 使用适配器工厂获取适配器实例 // 使用适配器工厂获取适配器实例
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig); KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig);
// 构建查询参数 // 构建查询参数
Map<String, Object> queryParams = new HashMap<>(); Map<String, Object> queryParams = new HashMap<>();
if (knowledgeFilesDTO != null && StringUtils.isNotBlank(knowledgeFilesDTO.getName())) { if (ToolUtil.isNotEmpty(knowledgeFilesDTO.getName())) {
queryParams.put("keywords", knowledgeFilesDTO.getName()); queryParams.put("keywords", knowledgeFilesDTO.getName());
} }
if (ToolUtil.isNotEmpty(knowledgeFilesDTO.getStatus())) {
queryParams.put("status", knowledgeFilesDTO.getStatus());
}
if (page > 0) { if (page > 0) {
queryParams.put("page", page); queryParams.put("page", page);
} }
@@ -90,10 +72,8 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
// 调用适配器获取文档列表 // 调用适配器获取文档列表
PageData<KnowledgeFilesDTO> result = adapter.getDocumentList(datasetId, queryParams, page, limit); PageData<KnowledgeFilesDTO> result = adapter.getDocumentList(datasetId, queryParams, page, limit);
log.info("获取文档列表成功,共{}个文档,总数: {}", result.getList().size(), result.getTotal()); log.info("获取文档列表成功,共{}个文档,总数: {}", result.getList().size(), result.getTotal());
return result; return result;
} catch (Exception e) { } catch (Exception e) {
log.error("获取文档列表失败: {}", e.getMessage(), e); log.error("获取文档列表失败: {}", e.getMessage(), e);
if (e instanceof RenException) { if (e instanceof RenException) {
@@ -424,57 +404,6 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
} }
} }
@Override
public PageData<KnowledgeFilesDTO> getPageListByStatus(String datasetId, Integer status, Integer page,
Integer limit) {
if (StringUtils.isBlank(datasetId)) {
throw new RenException(ErrorCode.RAG_DATASET_ID_NOT_NULL);
}
log.info("=== 开始根据状态查询文档列表 ===");
log.info("datasetId: {}, status: {}, page: {}, limit: {}", datasetId, status, page, limit);
try {
// 获取RAG配置
Map<String, Object> ragConfig = knowledgeBaseService.getRAGConfigByDatasetId(datasetId);
// 提取适配器类型
String adapterType = extractAdapterType(ragConfig);
// 使用适配器工厂获取适配器实例
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig);
// 构建查询参数
Map<String, Object> queryParams = new HashMap<>();
if (page != null && page > 0) {
queryParams.put("page", page);
}
if (limit != null && limit > 0) {
queryParams.put("page_size", limit);
}
if (status != null) {
queryParams.put("status", status);
}
// 使用适配器获取文档列表
PageData<KnowledgeFilesDTO> pageData = adapter.getDocumentList(datasetId, queryParams, page, limit);
if (pageData != null) {
log.info("根据状态查询文档列表成功,datasetId: {}, 状态: {}, 文档数量: {}",
datasetId, status, pageData.getList().size());
return pageData;
} else {
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
}
} catch (Exception e) {
log.error("根据状态查询文档列表失败: {}", e.getMessage(), e);
throw new RenException(ErrorCode.RAG_API_ERROR, e.getMessage());
} finally {
log.info("=== 根据状态查询文档列表操作结束 ===");
}
}
@Override @Override
public KnowledgeFilesDTO uploadDocument(String datasetId, MultipartFile file, String name, public KnowledgeFilesDTO uploadDocument(String datasetId, MultipartFile file, String name,
Map<String, Object> metaFields, String chunkMethod, Map<String, Object> metaFields, String chunkMethod,
@@ -0,0 +1,70 @@
package xiaozhi.modules.llm.service;
/**
* LLM服务接口
* 支持多种大模型调用
*/
public interface LLMService {
/**
* 生成聊天记录总结
*
* @param conversation 对话内容
* @param promptTemplate 提示词模板
* @return 总结结果
*/
String generateSummary(String conversation, String promptTemplate);
/**
* 生成聊天记录总结使用默认提示词
*
* @param conversation 对话内容
* @return 总结结果
*/
String generateSummary(String conversation);
/**
* 生成聊天记录总结指定模型ID
*
* @param conversation 对话内容
* @param modelId 模型ID
* @return 总结结果
*/
String generateSummaryWithModel(String conversation, String modelId);
/**
* 生成聊天记录总结指定模型ID和提示词模板
*
* @param conversation 对话内容
* @param promptTemplate 提示词模板
* @param modelId 模型ID
* @return 总结结果
*/
String generateSummary(String conversation, String promptTemplate, String modelId);
/**
* 生成聊天记录总结包含历史记忆合并
*
* @param conversation 对话内容
* @param historyMemory 历史记忆
* @param promptTemplate 提示词模板
* @param modelId 模型ID
* @return 总结结果
*/
String generateSummaryWithHistory(String conversation, String historyMemory, String promptTemplate, String modelId);
/**
* 检查服务是否可用
*
* @return 是否可用
*/
boolean isAvailable();
/**
* 检查指定模型的服务是否可用
*
* @param modelId 模型ID
* @return 是否可用
*/
boolean isAvailable(String modelId);
}
@@ -0,0 +1,305 @@
package xiaozhi.modules.llm.service.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.modules.llm.service.LLMService;
import xiaozhi.modules.model.entity.ModelConfigEntity;
import xiaozhi.modules.model.service.ModelConfigService;
/**
* OpenAI风格API的LLM服务实现
* 支持阿里云DeepSeekChatGLM等兼容OpenAI API的模型
*/
@Slf4j
@Service
public class OpenAIStyleLLMServiceImpl implements LLMService {
@Autowired
private ModelConfigService modelConfigService;
private final RestTemplate restTemplate = new RestTemplate();
private static final String DEFAULT_SUMMARY_PROMPT = "你是一个经验丰富的记忆总结者,擅长将对话内容进行总结摘要,遵循以下规则:\n1、总结用户的重要信息,以便在未来的对话中提供更个性化的服务\n2、不要重复总结,不要遗忘之前记忆,除非原来的记忆超过了1800字,否则不要遗忘、不要压缩用户的历史记忆\n3、用户操控的设备音量、播放音乐、天气、退出、不想对话等和用户本身无关的内容,这些信息不需要加入到总结中\n4、聊天内容中的今天的日期时间、今天的天气情况与用户事件无关的数据,这些信息如果当成记忆存储会影响后续对话,这些信息不需要加入到总结中\n5、不要把设备操控的成果结果和失败结果加入到总结中,也不要把用户的一些废话加入到总结中\n6、不要为了总结而总结,如果用户的聊天没有意义,请返回原来的历史记录也是可以的\n7、只需要返回总结摘要,严格控制在1800字内\n8、不要包含代码、xml,不需要解释、注释和说明,保存记忆时仅从对话提取信息,不要混入示例内容\n9、如果提供了历史记忆,请将新对话内容与历史记忆进行智能合并,保留有价值的历史信息,同时添加新的重要信息\n\n历史记忆:\n{history_memory}\n\n新对话内容:\n{conversation}";
@Override
public String generateSummary(String conversation) {
return generateSummary(conversation, null, null);
}
@Override
public String generateSummaryWithModel(String conversation, String modelId) {
return generateSummary(conversation, null, modelId);
}
@Override
public String generateSummary(String conversation, String promptTemplate, String modelId) {
if (!isAvailable()) {
log.warn("LLM服务不可用,无法生成总结");
return "LLM服务不可用,无法生成总结";
}
try {
// 从智控台获取LLM模型配置
ModelConfigEntity llmConfig;
if (modelId != null && !modelId.trim().isEmpty()) {
// 通过具体模型ID获取配置
llmConfig = modelConfigService.getModelByIdFromCache(modelId);
} else {
// 保持向后兼容使用默认配置
llmConfig = getDefaultLLMConfig();
}
if (llmConfig == null || llmConfig.getConfigJson() == null) {
log.error("未找到可用的LLM模型配置,modelId: {}", modelId);
return "未找到可用的LLM模型配置";
}
JSONObject configJson = llmConfig.getConfigJson();
String baseUrl = configJson.getStr("base_url");
String model = configJson.getStr("model_name");
String apiKey = configJson.getStr("api_key");
Double temperature = configJson.getDouble("temperature");
Integer maxTokens = configJson.getInt("max_tokens");
if (StringUtils.isBlank(baseUrl) || StringUtils.isBlank(apiKey)) {
log.error("LLM配置不完整,baseUrl或apiKey为空");
return "LLM配置不完整,无法生成总结";
}
// 构建提示词
String prompt = (promptTemplate != null ? promptTemplate : DEFAULT_SUMMARY_PROMPT).replace("{conversation}",
conversation);
// 构建请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
Map<String, Object>[] messages = new Map[1];
Map<String, Object> message = new HashMap<>();
message.put("role", "user");
message.put("content", prompt);
messages[0] = message;
requestBody.put("messages", messages);
requestBody.put("temperature", temperature != null ? temperature : 0.7);
requestBody.put("max_tokens", maxTokens != null ? maxTokens : 2000);
// 发送HTTP请求
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + apiKey);
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
// 构建完整的API URL
String apiUrl = baseUrl;
if (!apiUrl.endsWith("/chat/completions")) {
if (!apiUrl.endsWith("/")) {
apiUrl += "/";
}
apiUrl += "chat/completions";
}
ResponseEntity<String> response = restTemplate.exchange(
apiUrl, HttpMethod.POST, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
JSONObject responseJson = JSONUtil.parseObj(response.getBody());
JSONArray choices = responseJson.getJSONArray("choices");
if (choices != null && choices.size() > 0) {
JSONObject choice = choices.getJSONObject(0);
JSONObject messageObj = choice.getJSONObject("message");
return messageObj.getStr("content");
}
} else {
log.error("LLM API调用失败,状态码:{},响应:{}", response.getStatusCode(), response.getBody());
}
} catch (Exception e) {
log.error("调用LLM服务生成总结时发生异常,modelId: {}", modelId, e);
}
return "生成总结失败,请稍后重试";
}
@Override
public String generateSummary(String conversation, String promptTemplate) {
return generateSummary(conversation, promptTemplate, null);
}
@Override
public String generateSummaryWithHistory(String conversation, String historyMemory, String promptTemplate,
String modelId) {
if (!isAvailable()) {
log.warn("LLM服务不可用,无法生成总结");
return "LLM服务不可用,无法生成总结";
}
try {
// 从智控台获取LLM模型配置
ModelConfigEntity llmConfig;
if (modelId != null && !modelId.trim().isEmpty()) {
// 通过具体模型ID获取配置
llmConfig = modelConfigService.getModelByIdFromCache(modelId);
} else {
// 保持向后兼容使用默认配置
llmConfig = getDefaultLLMConfig();
}
if (llmConfig == null || llmConfig.getConfigJson() == null) {
log.error("未找到可用的LLM模型配置,modelId: {}", modelId);
return "未找到可用的LLM模型配置";
}
JSONObject configJson = llmConfig.getConfigJson();
String baseUrl = configJson.getStr("base_url");
String model = configJson.getStr("model_name");
String apiKey = configJson.getStr("api_key");
if (StringUtils.isBlank(baseUrl) || StringUtils.isBlank(apiKey)) {
log.error("LLM配置不完整,baseUrl或apiKey为空");
return "LLM配置不完整,无法生成总结";
}
// 构建提示词包含历史记忆
String prompt = (promptTemplate != null ? promptTemplate : DEFAULT_SUMMARY_PROMPT)
.replace("{history_memory}", historyMemory != null ? historyMemory : "无历史记忆")
.replace("{conversation}", conversation);
// 构建请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
Map<String, Object>[] messages = new Map[1];
Map<String, Object> message = new HashMap<>();
message.put("role", "user");
message.put("content", prompt);
messages[0] = message;
requestBody.put("messages", messages);
requestBody.put("temperature", 0.2);
requestBody.put("max_tokens", 2000);
// 发送HTTP请求
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + apiKey);
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
// 构建完整的API URL
String apiUrl = baseUrl;
if (!apiUrl.endsWith("/chat/completions")) {
if (!apiUrl.endsWith("/")) {
apiUrl += "/";
}
apiUrl += "chat/completions";
}
ResponseEntity<String> response = restTemplate.exchange(
apiUrl, HttpMethod.POST, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
JSONObject responseJson = JSONUtil.parseObj(response.getBody());
JSONArray choices = responseJson.getJSONArray("choices");
if (choices != null && choices.size() > 0) {
JSONObject choice = choices.getJSONObject(0);
JSONObject messageObj = choice.getJSONObject("message");
return messageObj.getStr("content");
}
} else {
log.error("LLM API调用失败,状态码:{},响应:{}", response.getStatusCode(), response.getBody());
}
} catch (Exception e) {
log.error("调用LLM服务生成总结时发生异常,modelId: {}", modelId, e);
}
return "生成总结失败,请稍后重试";
}
@Override
public boolean isAvailable() {
try {
ModelConfigEntity defaultLLMConfig = getDefaultLLMConfig();
if (defaultLLMConfig == null || defaultLLMConfig.getConfigJson() == null) {
return false;
}
JSONObject configJson = defaultLLMConfig.getConfigJson();
String baseUrl = configJson.getStr("base_url");
String apiKey = configJson.getStr("api_key");
return baseUrl != null && !baseUrl.trim().isEmpty() &&
apiKey != null && !apiKey.trim().isEmpty();
} catch (Exception e) {
log.error("检查LLM服务可用性时发生异常:", e);
return false;
}
}
@Override
public boolean isAvailable(String modelId) {
try {
if (modelId == null || modelId.trim().isEmpty()) {
return isAvailable();
}
// 通过具体模型ID获取配置
ModelConfigEntity modelConfig = modelConfigService.getModelByIdFromCache(modelId);
if (modelConfig == null || modelConfig.getConfigJson() == null) {
log.warn("未找到指定的LLM模型配置,modelId: {}", modelId);
return false;
}
JSONObject configJson = modelConfig.getConfigJson();
String baseUrl = configJson.getStr("base_url");
String apiKey = configJson.getStr("api_key");
return baseUrl != null && !baseUrl.trim().isEmpty() &&
apiKey != null && !apiKey.trim().isEmpty();
} catch (Exception e) {
log.error("检查LLM服务可用性时发生异常,modelId: {}", modelId, e);
return false;
}
}
/**
* 从智控台获取默认的LLM模型配置
*/
private ModelConfigEntity getDefaultLLMConfig() {
try {
// 获取所有启用的LLM模型配置
List<ModelConfigEntity> llmConfigs = modelConfigService.getEnabledModelsByType("LLM");
if (llmConfigs == null || llmConfigs.isEmpty()) {
return null;
}
// 优先返回默认配置如果没有默认配置则返回第一个启用的配置
for (ModelConfigEntity config : llmConfigs) {
if (config.getIsDefault() != null && config.getIsDefault() == 1) {
return config;
}
}
return llmConfigs.get(0);
} catch (Exception e) {
log.error("获取LLM模型配置时发生异常:", e);
return null;
}
}
}
@@ -22,12 +22,24 @@ public class VoiceDTO implements Serializable {
@Schema(description = "音频播放地址") @Schema(description = "音频播放地址")
private String voiceDemo; private String voiceDemo;
@Schema(description = "是否为克隆音色")
private Boolean isClone;
// 添加双参数构造函数保持向后兼容 // 添加双参数构造函数保持向后兼容
public VoiceDTO(String id, String name) { public VoiceDTO(String id, String name) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.voiceDemo = null; this.voiceDemo = null;
this.isClone = false; // 默认不是克隆音色
}
// 添加三参数构造函数用于普通音色
public VoiceDTO(String id, String name, String voiceDemo) {
this.id = id;
this.name = name;
this.voiceDemo = voiceDemo;
this.isClone = false;
} }
} }
@@ -55,4 +55,12 @@ public interface ModelConfigService extends BaseService<ModelConfigEntity> {
* @return TTS平台列表(id和modelName) * @return TTS平台列表(id和modelName)
*/ */
List<Map<String, Object>> getTtsPlatformList(); List<Map<String, Object>> getTtsPlatformList();
/**
* 根据模型类型获取所有启用的模型配置
*
* @param modelType 模型类型LLM, TTS, ASR等
* @return 启用的模型配置列表
*/
List<ModelConfigEntity> getEnabledModelsByType(String modelType);
} }
@@ -56,7 +56,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
new QueryWrapper<ModelConfigEntity>() new QueryWrapper<ModelConfigEntity>()
.eq("model_type", modelType) .eq("model_type", modelType)
.eq("is_enabled", 1) .eq("is_enabled", 1)
.like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%") .like(StringUtils.isNotBlank(modelName), "model_name", modelName)
.select("id", "model_name")); .select("id", "model_name"));
return ConvertUtils.sourceToTarget(entities, ModelBasicInfoDTO.class); return ConvertUtils.sourceToTarget(entities, ModelBasicInfoDTO.class);
} }
@@ -67,14 +67,14 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
new QueryWrapper<ModelConfigEntity>() new QueryWrapper<ModelConfigEntity>()
.eq("model_type", "llm") .eq("model_type", "llm")
.eq("is_enabled", 1) .eq("is_enabled", 1)
.like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%") .like(StringUtils.isNotBlank(modelName), "model_name", modelName)
.select("id", "model_name", "config_json")); .select("id", "model_name", "config_json"));
return entities.stream().map(item -> { return entities.stream().map(item -> {
LlmModelBasicInfoDTO dto = new LlmModelBasicInfoDTO(); LlmModelBasicInfoDTO dto = new LlmModelBasicInfoDTO();
dto.setId(item.getId()); dto.setId(item.getId());
dto.setModelName(item.getModelName()); dto.setModelName(item.getModelName());
String type = item.getConfigJson().get("type").toString(); String type = item.getConfigJson().getOrDefault("type", "").toString();
dto.setType(type); dto.setType(type);
return dto; return dto;
}).toList(); }).toList();
@@ -91,14 +91,13 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
Page<ModelConfigEntity> pageInfo = new Page<>(curPage, pageSize); Page<ModelConfigEntity> pageInfo = new Page<>(curPage, pageSize);
// 添加排序规则先按is_enabled降序再按sort升序 // 添加排序规则先按is_enabled降序再按sort升序
pageInfo.addOrder(OrderItem.desc("is_enabled")); pageInfo.addOrder(OrderItem.desc("is_enabled"), OrderItem.asc("sort"));
pageInfo.addOrder(OrderItem.asc("sort"));
IPage<ModelConfigEntity> modelConfigEntityIPage = modelConfigDao.selectPage( IPage<ModelConfigEntity> modelConfigEntityIPage = modelConfigDao.selectPage(
pageInfo, pageInfo,
new QueryWrapper<ModelConfigEntity>() new QueryWrapper<ModelConfigEntity>()
.eq("model_type", modelType) .eq("model_type", modelType)
.like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%")); .like(StringUtils.isNotBlank(modelName), "model_name", modelName));
return getPageData(modelConfigEntityIPage, ModelConfigDTO.class); return getPageData(modelConfigEntityIPage, ModelConfigDTO.class);
} }
@@ -346,6 +345,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
modelConfigEntity.setModelName(modelConfigBodyDTO.getModelName()); modelConfigEntity.setModelName(modelConfigBodyDTO.getModelName());
modelConfigEntity.setSort(modelConfigBodyDTO.getSort()); modelConfigEntity.setSort(modelConfigBodyDTO.getSort());
modelConfigEntity.setIsEnabled(modelConfigBodyDTO.getIsEnabled()); modelConfigEntity.setIsEnabled(modelConfigBodyDTO.getIsEnabled());
modelConfigEntity.setRemark(modelConfigBodyDTO.getRemark());
// 3. 处理配置JSON仅更新非敏感字段和明确修改的敏感字段 // 3. 处理配置JSON仅更新非敏感字段和明确修改的敏感字段
if (modelConfigBodyDTO.getConfigJson() != null && originalEntity.getConfigJson() != null) { if (modelConfigBodyDTO.getConfigJson() != null && originalEntity.getConfigJson() != null) {
JSONObject originalJson = originalEntity.getConfigJson(); JSONObject originalJson = originalEntity.getConfigJson();
@@ -488,7 +488,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
List<ModelConfigEntity> intentConfigs = modelConfigDao.selectList( List<ModelConfigEntity> intentConfigs = modelConfigDao.selectList(
new QueryWrapper<ModelConfigEntity>() new QueryWrapper<ModelConfigEntity>()
.eq("model_type", "Intent") .eq("model_type", "Intent")
.like("config_json", "%" + modelId + "%")); .like("config_json", modelId));
if (!intentConfigs.isEmpty()) { if (!intentConfigs.isEmpty()) {
throw new RenException(ErrorCode.LLM_REFERENCED_BY_INTENT); throw new RenException(ErrorCode.LLM_REFERENCED_BY_INTENT);
} }
@@ -502,4 +502,22 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
public List<Map<String, Object>> getTtsPlatformList() { public List<Map<String, Object>> getTtsPlatformList() {
return modelConfigDao.getTtsPlatformList(); return modelConfigDao.getTtsPlatformList();
} }
/**
* 根据模型类型获取所有启用的模型配置
*/
@Override
public List<ModelConfigEntity> getEnabledModelsByType(String modelType) {
if (StringUtils.isBlank(modelType)) {
return null;
}
List<ModelConfigEntity> entities = modelConfigDao.selectList(
new QueryWrapper<ModelConfigEntity>()
.eq("model_type", modelType)
.eq("is_enabled", 1)
.orderByAsc("sort"));
return entities;
}
} }
@@ -124,12 +124,6 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
return getPageData(modelProviderDao.selectPage(pageParam, wrapper), ModelProviderDTO.class); return getPageData(modelProviderDao.selectPage(pageParam, wrapper), ModelProviderDTO.class);
} }
public static void main(String[] args) {
String jsonString = "\"[]\"";
JSONArray jsonArray = new JSONArray(jsonString);
System.out.println("字符串转 JSONArray: " + jsonArray.toString());
}
@Override @Override
public ModelProviderDTO add(ModelProviderDTO modelProviderDTO) { public ModelProviderDTO add(ModelProviderDTO modelProviderDTO) {
UserDetail user = SecurityUser.getUser(); UserDetail user = SecurityUser.getUser();
@@ -153,8 +147,7 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
UserDetail user = SecurityUser.getUser(); UserDetail user = SecurityUser.getUser();
modelProviderDTO.setUpdater(user.getId()); modelProviderDTO.setUpdater(user.getId());
modelProviderDTO.setUpdateDate(new Date()); modelProviderDTO.setUpdateDate(new Date());
if (modelProviderDao if (modelProviderDao.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
throw new RenException(ErrorCode.UPDATE_DATA_FAILED); throw new RenException(ErrorCode.UPDATE_DATA_FAILED);
} }
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class); return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
@@ -89,7 +89,7 @@ public class ShiroConfig {
filterMap.put("/config/**", "server"); filterMap.put("/config/**", "server");
filterMap.put("/agent/chat-history/report", "server"); filterMap.put("/agent/chat-history/report", "server");
filterMap.put("/agent/chat-history/download/**", "anon"); filterMap.put("/agent/chat-history/download/**", "anon");
filterMap.put("/agent/saveMemory/**", "server"); filterMap.put("/agent/chat-summary/**", "server");
filterMap.put("/agent/play/**", "anon"); filterMap.put("/agent/play/**", "anon");
filterMap.put("/voiceClone/play/**", "anon"); filterMap.put("/voiceClone/play/**", "anon");
filterMap.put("/**", "oauth2"); filterMap.put("/**", "oauth2");
@@ -31,6 +31,8 @@ import xiaozhi.modules.sys.dto.ServerActionResponseDTO;
import xiaozhi.modules.sys.enums.ServerActionEnum; import xiaozhi.modules.sys.enums.ServerActionEnum;
import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.service.SysParamsService;
import xiaozhi.modules.sys.utils.WebSocketClientManager; import xiaozhi.modules.sys.utils.WebSocketClientManager;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.common.redis.RedisUtils;
/** /**
* 服务端管理控制器 * 服务端管理控制器
@@ -41,6 +43,8 @@ import xiaozhi.modules.sys.utils.WebSocketClientManager;
@AllArgsConstructor @AllArgsConstructor
public class ServerSideManageController { public class ServerSideManageController {
private final SysParamsService sysParamsService; private final SysParamsService sysParamsService;
private final DeviceService deviceService;
private final RedisUtils redisUtils;
private static final ObjectMapper objectMapper; private static final ObjectMapper objectMapper;
static { static {
objectMapper = new ObjectMapper(); objectMapper = new ObjectMapper();
@@ -85,9 +89,22 @@ public class ServerSideManageController {
return false; return false;
} }
String serverSK = sysParamsService.getValue(Constant.SERVER_SECRET, true); String serverSK = sysParamsService.getValue(Constant.SERVER_SECRET, true);
String deviceId = UUID.randomUUID().toString();
String clientId = UUID.randomUUID().toString();
String redisKey = xiaozhi.common.redis.RedisKeys.getTmpRegisterMacKey(deviceId);
redisUtils.set(redisKey, "true", 300); // 5分钟有效期
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.add("device-id", UUID.randomUUID().toString()); headers.add("device-id", deviceId);
headers.add("client-id", UUID.randomUUID().toString()); headers.add("client-id", clientId);
try {
String token = deviceService.generateWebSocketToken(clientId, deviceId);
headers.add("authorization", "Bearer " + token);
} catch (Exception e) {
throw new RenException(ErrorCode.WEB_SOCKET_CONNECT_FAILED);
}
try (WebSocketClientManager client = new WebSocketClientManager.Builder() try (WebSocketClientManager client = new WebSocketClientManager.Builder()
.connectTimeout(3, TimeUnit.SECONDS) .connectTimeout(3, TimeUnit.SECONDS)
@@ -4,6 +4,7 @@ import java.util.List;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import xiaozhi.common.dao.BaseDao; import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.sys.entity.SysDictDataEntity; import xiaozhi.modules.sys.entity.SysDictDataEntity;
import xiaozhi.modules.sys.vo.SysDictDataItem; import xiaozhi.modules.sys.vo.SysDictDataItem;
@@ -23,4 +24,9 @@ public interface SysDictDataDao extends BaseDao<SysDictDataEntity> {
* @return 字典类型编码 * @return 字典类型编码
*/ */
String getTypeByTypeId(Long dictTypeId); String getTypeByTypeId(Long dictTypeId);
/**
* 根据字典数据ID集合获取字典类型编码集合
*/
List<String> getDictTypesByIdList(@Param("dictDataIdList") List<Long> dictDataIdList);
} }
@@ -1,9 +1,6 @@
package xiaozhi.modules.sys.service.impl; package xiaozhi.modules.sys.service.impl;
import java.util.List; import java.util.*;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -23,6 +20,7 @@ import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.ToolUtil;
import xiaozhi.modules.sys.dao.SysDictDataDao; import xiaozhi.modules.sys.dao.SysDictDataDao;
import xiaozhi.modules.sys.dao.SysUserDao; import xiaozhi.modules.sys.dao.SysUserDao;
import xiaozhi.modules.sys.dto.SysDictDataDTO; import xiaozhi.modules.sys.dto.SysDictDataDTO;
@@ -104,13 +102,19 @@ public class SysDictDataServiceImpl extends BaseServiceImpl<SysDictDataDao, SysD
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void delete(Long[] ids) { public void delete(Long[] ids) {
for (Long id : ids) { List<Long> idList = Arrays.asList(ids);
SysDictDataEntity entity = baseDao.selectById(id); if (ToolUtil.isNotEmpty(idList)) {
// 删除Redis缓存 //批量删除redis字典
String dictType = baseDao.getTypeByTypeId(entity.getDictTypeId()); List<String> redisKeyList = new ArrayList<>();
redisUtils.delete(RedisKeys.getDictDataByTypeKey(dictType)); //批量获取字典类型
// 删除 List<String> dictTypeList = Optional.ofNullable(baseDao.getDictTypesByIdList(idList)).orElseGet(ArrayList::new);
deleteById(id); dictTypeList.forEach(dictType -> redisKeyList.add(RedisKeys.getDictDataByTypeKey(dictType)));
if (ToolUtil.isNotEmpty(redisKeyList)) {
//清除缓存
redisUtils.delete(redisKeyList);
}
//批量删除字典数据
deleteBatchIds(Arrays.asList(ids));
} }
} }
@@ -1,10 +1,6 @@
package xiaozhi.modules.timbre.service.impl; package xiaozhi.modules.timbre.service.impl;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -128,14 +124,12 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
if (StringUtils.isNotBlank(voiceName)) { if (StringUtils.isNotBlank(voiceName)) {
queryWrapper.like("name", voiceName); queryWrapper.like("name", voiceName);
} }
List<TimbreEntity> timbreEntities = timbreDao.selectList(queryWrapper); List<TimbreEntity> timbreEntities = Optional.ofNullable(timbreDao.selectList(queryWrapper)).orElseGet(ArrayList::new);
if (timbreEntities == null) {
timbreEntities = new ArrayList<>();
}
List<VoiceDTO> voiceDTOs = timbreEntities.stream() List<VoiceDTO> voiceDTOs = timbreEntities.stream()
.map(entity -> { .map(entity -> {
VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName()); VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName());
dto.setVoiceDemo(entity.getVoiceDemo()); dto.setVoiceDemo(entity.getVoiceDemo());
dto.setIsClone(false); // 设置为普通音色
return dto; return dto;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
@@ -152,6 +146,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
voiceDTO.setName(MessageUtils.getMessage(ErrorCode.VOICE_CLONE_PREFIX) + entity.getName()); voiceDTO.setName(MessageUtils.getMessage(ErrorCode.VOICE_CLONE_PREFIX) + entity.getName());
// 保留从数据库查询到的voiceDemo字段 // 保留从数据库查询到的voiceDemo字段
voiceDTO.setVoiceDemo(entity.getVoiceDemo()); voiceDTO.setVoiceDemo(entity.getVoiceDemo());
voiceDTO.setIsClone(true); // 设置为克隆音色
redisUtils.set(RedisKeys.getTimbreNameById(voiceDTO.getId()), voiceDTO.getName(), redisUtils.set(RedisKeys.getTimbreNameById(voiceDTO.getId()), voiceDTO.getName(),
RedisUtils.NOT_EXPIRE); RedisUtils.NOT_EXPIRE);
voiceDTOs.add(0, voiceDTO); voiceDTOs.add(0, voiceDTO);
@@ -214,6 +209,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
TimbreEntity entity = list.get(0); TimbreEntity entity = list.get(0);
VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName()); VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName());
dto.setVoiceDemo(entity.getVoiceDemo()); dto.setVoiceDemo(entity.getVoiceDemo());
dto.setIsClone(false); // 设置为普通音色
return dto; return dto;
} }
} }
@@ -83,6 +83,13 @@ public class VoiceCloneController {
return new Result<String>().error(ErrorCode.VOICE_CLONE_NOT_AUDIO_FILE); return new Result<String>().error(ErrorCode.VOICE_CLONE_NOT_AUDIO_FILE);
} }
// 加强验证文件扩展名
String originalFilename = voiceFile.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
if (!extension.equals(".mp3") && !extension.equals(".wav")) {
return new Result<String>().error("只允许上传.mp3和.wav格式的文件");
}
// 验证文件大小 (最大10MB) // 验证文件大小 (最大10MB)
if (voiceFile.getSize() > 10 * 1024 * 1024) { if (voiceFile.getSize() > 10 * 1024 * 1024) {
return new Result<String>().error(ErrorCode.VOICE_CLONE_AUDIO_TOO_LARGE); return new Result<String>().error(ErrorCode.VOICE_CLONE_AUDIO_TOO_LARGE);
@@ -4,12 +4,8 @@ import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays; import java.util.stream.Collectors;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -30,8 +26,11 @@ import xiaozhi.common.page.PageData;
import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.DateUtils; import xiaozhi.common.utils.DateUtils;
import xiaozhi.common.utils.ToolUtil;
import xiaozhi.modules.model.entity.ModelConfigEntity; import xiaozhi.modules.model.entity.ModelConfigEntity;
import xiaozhi.modules.model.service.ModelConfigService; import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.sys.dao.SysUserDao;
import xiaozhi.modules.sys.entity.SysUserEntity;
import xiaozhi.modules.sys.service.SysUserService; import xiaozhi.modules.sys.service.SysUserService;
import xiaozhi.modules.voiceclone.dao.VoiceCloneDao; import xiaozhi.modules.voiceclone.dao.VoiceCloneDao;
import xiaozhi.modules.voiceclone.dto.VoiceCloneDTO; import xiaozhi.modules.voiceclone.dto.VoiceCloneDTO;
@@ -47,6 +46,7 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl<VoiceCloneDao, VoiceC
private final ModelConfigService modelConfigService; private final ModelConfigService modelConfigService;
private final SysUserService sysUserService; private final SysUserService sysUserService;
private final SysUserDao sysUserDao;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
@Override @Override
@@ -104,9 +104,11 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl<VoiceCloneDao, VoiceC
} }
} }
// 批量保存
List<VoiceCloneEntity> batchInsertList = new ArrayList<>();
// 遍历选择的音色ID为每个音色ID创建一条记录 // 遍历选择的音色ID为每个音色ID创建一条记录
int index = 0; int index = 0;
String namePrefix = DateUtils.format(new java.util.Date(), "MMddHHmm"); String namePrefix = DateUtils.format(new Date(), "MMddHHmm");
for (String voiceId : dto.getVoiceIds()) { for (String voiceId : dto.getVoiceIds()) {
index++; index++;
VoiceCloneEntity entity = new VoiceCloneEntity(); VoiceCloneEntity entity = new VoiceCloneEntity();
@@ -115,8 +117,10 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl<VoiceCloneDao, VoiceC
entity.setName(namePrefix + "_" + index); entity.setName(namePrefix + "_" + index);
entity.setUserId(dto.getUserId()); entity.setUserId(dto.getUserId());
entity.setTrainStatus(0); // 默认训练中 entity.setTrainStatus(0); // 默认训练中
batchInsertList.add(entity);
baseDao.insert(entity); }
if (ToolUtil.isNotEmpty(batchInsertList)) {
insertBatch(batchInsertList);
} }
} }
@@ -187,6 +191,11 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl<VoiceCloneDao, VoiceC
List<VoiceCloneResponseDTO> dtoList = new ArrayList<>(entityList.size()); List<VoiceCloneResponseDTO> dtoList = new ArrayList<>(entityList.size());
// 获取用户名称ID集合
Set<Long> userIdList = entityList.stream().map(VoiceCloneEntity::getUserId).collect(Collectors.toSet());
List<SysUserEntity> userList = sysUserDao.selectList(new QueryWrapper<SysUserEntity>().in("id", userIdList));
Map<Long, String> userMap = userList.stream().collect(Collectors.toMap(SysUserEntity::getId, SysUserEntity::getUsername));
// 转换每个实体为DTO // 转换每个实体为DTO
for (VoiceCloneEntity entity : entityList) { for (VoiceCloneEntity entity : entityList) {
VoiceCloneResponseDTO dto = ConvertUtils.sourceToTarget(entity, VoiceCloneResponseDTO.class); VoiceCloneResponseDTO dto = ConvertUtils.sourceToTarget(entity, VoiceCloneResponseDTO.class);
@@ -198,7 +207,7 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl<VoiceCloneDao, VoiceC
// 设置用户名称 // 设置用户名称
if (entity.getUserId() != null) { if (entity.getUserId() != null) {
dto.setUserName(sysUserService.getByUserId(entity.getUserId()).getUsername()); dto.setUserName(userMap.get(entity.getUserId()));
} }
// 确保trainStatus字段被正确设置前端需要这个字段来判断是否为克隆音频 // 确保trainStatus字段被正确设置前端需要这个字段来判断是否为克隆音频
@@ -0,0 +1,17 @@
-- 更新HuoshanDoubleStreamTTS供应器配置,增加开启链接复用选项
UPDATE `ai_model_provider`
SET fields = '[{"key": "ws_url", "type": "string", "label": "WebSocket地址"}, {"key": "appid", "type": "string", "label": "应用ID"}, {"key": "access_token", "type": "string", "label": "访问令牌"}, {"key": "resource_id", "type": "string", "label": "资源ID"}, {"key": "speaker", "type": "string", "label": "默认音色"}, {"key": "enable_ws_reuse", "type": "boolean", "label": "是否开启链接复用", "default": true}, {"key": "speech_rate", "type": "number", "label": "语速(-50~100)"}, {"key": "loudness_rate", "type": "number", "label": "音量(-50~100)"}, {"key": "pitch", "type": "number", "label": "音高(-12~12)"}]'
WHERE id = 'SYSTEM_TTS_HSDSTTS';
UPDATE `ai_model_config` SET
`doc_link` = 'https://console.volcengine.com/speech/service/10007',
`remark` = '火山引擎语音合成服务配置说明:
1. 访 https://www.volcengine.com/
2. 访 https://console.volcengine.com/speech/service/10007
3. appid和access_token
5. ID固定为volc.service_type.10029
6. WebSocket连接复用true减少链接损耗
7. -50~1000-50~100
8. -50~1000-50~100
9. -12~120-12~12
10. ' WHERE `id` = 'TTS_HuoshanDoubleStreamTTS';
@@ -0,0 +1 @@
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (311, 'enable_websocket_ping', 'false', 'boolean', 1, '是否启用WebSocket心跳保活机制');
@@ -0,0 +1,2 @@
-- 为智能体聊天历史记录添加音频ID索引
ALTER TABLE ai_agent_chat_history ADD INDEX idx_ai_agent_chat_history_audio_id (audio_id);
@@ -0,0 +1,10 @@
-- 更新豆包流式ASR供应器,增加end_window_size配置
delete from `ai_model_provider` where id = 'SYSTEM_ASR_DoubaoStreamASR';
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
('SYSTEM_ASR_DoubaoStreamASR', 'ASR', 'doubao_stream', '火山引擎语音识别(流式)', '[{"key":"appid","label":"应用ID","type":"string"},{"key":"access_token","label":"访问令牌","type":"string"},{"key":"cluster","label":"集群","type":"string"},{"key":"boosting_table_name","label":"热词文件名称","type":"string"},{"key":"correct_table_name","label":"替换词文件名称","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"end_window_size","label":"静音判定时长(ms)","type":"number"}]', 3, 1, NOW(), 1, NOW());
-- 更新豆包流式ASR模型配置,增加end_window_size默认值
UPDATE `ai_model_config` SET
`config_json` = JSON_SET(`config_json`, '$.end_window_size', 200)
WHERE `id` = 'ASR_DoubaoStreamASR' AND JSON_EXTRACT(`config_json`, '$.end_window_size') IS NULL;
@@ -0,0 +1,94 @@
-- 添加阿里百炼Paraformer实时语音识别服务配置
delete from `ai_model_provider` where id = 'SYSTEM_ASR_AliyunBLStream';
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
('SYSTEM_ASR_AliyunBLStream', 'ASR', 'aliyunbl_stream', '阿里百炼Paraformer实时语音识别', '[{"key":"api_key","label":"API密钥","type":"password"},{"key":"model","label":"模型名称","type":"string"},{"key":"format","label":"音频格式","type":"string"},{"key":"sample_rate","label":"采样率","type":"number"},{"key":"output_dir","label":"输出目录","type":"string"}]', 18, 1, NOW(), 1, NOW());
delete from `ai_model_config` where id = 'ASR_AliyunBLStream';
INSERT INTO `ai_model_config` VALUES ('ASR_AliyunBLStream', 'ASR', 'AliyunBLStream', '阿里百炼Paraformer实时语音识别', 0, 1, '{"type": "aliyunbl_stream", "api_key": "", "model": "paraformer-realtime-v2", "format": "pcm", "sample_rate": 16000, "disfluency_removal_enabled": false, "semantic_punctuation_enabled": false, "max_sentence_silence": 200, "multi_threshold_mode_enabled": false, "punctuation_prediction_enabled": true, "inverse_text_normalization_enabled": true, "output_dir": "tmp/"}', 'https://help.aliyun.com/zh/model-studio/websocket-for-paraformer-real-time-service', '支持多语言、热词定制、语义断句等高级功能', 21, NULL, NULL, NULL, NULL);
-- 更新阿里百炼Paraformer模型配置的说明文档
UPDATE `ai_model_config` SET
`doc_link` = 'https://help.aliyun.com/zh/model-studio/websocket-for-paraformer-real-time-service',
`remark` = '阿里百炼Paraformer实时语音识别配置说明:
1. https://bailian.console.aliyun.com/
2. API-KEY https://bailian.console.aliyun.com/#/api-key
3. paraformer-realtime-v2()paraformer-realtime-8k-v2paraformer-realtime-v1paraformer-realtime-8k-v1
4.
- ()
- (vocabulary_id参数)https://help.aliyun.com/zh/model-studio/custom-hot-words?
- /VAD断句(semantic_punctuation_enabled参数)
- ITN
5.
- model: paraformer-realtime-v2
- sample_rate: (Hz)v2支持任意采样率v1仅支持160008k版本仅支持8000
- semantic_punctuation_enabled: false为VAD断句()true为语义断句()
- max_sentence_silence: VAD断句静音时长阈值(200-6000ms)
' WHERE `id` = 'ASR_AliyunBLStream';
-- 更新豆包流式ASR供应器,增加配置
delete from `ai_model_provider` where id = 'SYSTEM_ASR_DoubaoStreamASR';
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
('SYSTEM_ASR_DoubaoStreamASR', 'ASR', 'doubao_stream', '火山引擎语音识别(流式)', '[{"key":"appid","label":"应用ID","type":"string"},{"key":"access_token","label":"访问令牌","type":"string"},{"key":"cluster","label":"集群","type":"string"},{"key":"boosting_table_name","label":"热词文件名称","type":"string"},{"key":"correct_table_name","label":"替换词文件名称","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"end_window_size","label":"静音判定时长(ms)","type":"number"},{"key":"enable_multilingual","label":"是否开启多语种识别模式","type":"boolean"},{"key":"language","label":"指定语言编码","type":"string"}]', 3, 1, NOW(), 1, NOW());
UPDATE `ai_model_config` SET
`remark` = '豆包ASR配置说明:
1. ASR和豆包()ASR的区别是ASR是按次收费()ASR是按时收费
2. 便()ASR使用了大模型技术
3. appid和access_token
4.
5.
6. tmp/
1. 访 https://console.volcengine.com/speech/app
2.
3. appid和access_token
4.
https://www.volcengine.com/docs/6561/155738
language当该键为空时西https://www.volcengine.com/docs/6561/1354869
' WHERE `id` = 'ASR_DoubaoStreamASR';
-- 更新豆包流式ASR模型配置,增加enable_multilingual默认值
UPDATE `ai_model_config` SET
`config_json` = JSON_SET(
`config_json`,
'$.enable_multilingual', false,
'$.language', 'zh-CN'
)
WHERE `id` = 'ASR_DoubaoStreamASR'
AND JSON_EXTRACT(`config_json`, '$.enable_multilingual') IS NULL
AND JSON_EXTRACT(`config_json`, '$.language') IS NULL;
-- 更新HuoshanDoubleStreamTTS供应器配置,增加多情感音色参数
UPDATE `ai_model_provider`
SET `fields` = '[{"key": "ws_url", "type": "string", "label": "WebSocket地址"}, {"key": "appid", "type": "string", "label": "应用ID"}, {"key": "access_token", "type": "string", "label": "访问令牌"}, {"key": "resource_id", "type": "string", "label": "资源ID"}, {"key": "speaker", "type": "string", "label": "默认音色"}, {"key": "enable_ws_reuse", "type": "boolean", "label": "是否开启链接复用", "default": true}, {"key": "speech_rate", "type": "number", "label": "语速(-50~100)"}, {"key": "loudness_rate", "type": "number", "label": "音量(-50~100)"}, {"key": "pitch", "type": "number", "label": "音高(-12~12)"}, {"key": "emotion_scale", "type": "number", "label": "情感强度(1-5)"}, {"key": "emotion", "type": "string", "label": "情感类型"}]'
WHERE `id` = 'SYSTEM_TTS_HSDSTTS';
-- 更新默认值
UPDATE `ai_model_config` SET
`config_json` = JSON_SET(
`config_json`,
'$.emotion', 'neutral',
'$.emotion_scale', 4
)
WHERE `id` = 'TTS_HuoshanDoubleStreamTTS'
AND JSON_EXTRACT(`config_json`, '$.emotion') IS NULL
AND JSON_EXTRACT(`config_json`, '$.emotion_scale') IS NULL;
-- 增加文档链接和备注
UPDATE `ai_model_config` SET
`doc_link` = 'https://console.volcengine.com/speech/service/10007',
`remark` = '火山引擎语音合成服务配置说明:
1. 访 https://www.volcengine.com/
2. 访 https://console.volcengine.com/speech/service/10007
3. appid和access_token
5. ID固定为volc.service_type.10029
6. WebSocket连接复用true减少链接损耗
7. -50~1000-50~100
8. -50~1000-50~100
9. -12~120-12~12
10.
https://www.volcengine.com/docs/6561/1257544
- emotion_scale1~54
- emotionneutralhappysadangryfearfuldisgustedsurprised
' WHERE `id` = 'TTS_HuoshanDoubleStreamTTS';
@@ -0,0 +1,11 @@
-- 更改FunASRServer说明文档模型为SenseVoiceSmall
UPDATE `ai_model_config` SET
`doc_link` = 'https://github.com/modelscope/FunASR/blob/main/runtime/docs/SDK_advanced_guide_online_zh.md',
`remark` = '独立部署FunASR,使用FunASR的API服务,只需要五句话
mkdir -p ./funasr-runtime-resources/models
sudo docker run -d -p 10096:10095 --privileged=true -v $PWD/funasr-runtime-resources/models:/workspace/models registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.12
cd FunASR/runtime
退nohup bash run_server_2pass.sh --download-model-dir /workspace/models --vad-dir damo/speech_fsmn_vad_zh-cn-16k-common-onnx --model-dir iic/SenseVoiceSmall-onnx --online-model-dir damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online-onnx --punc-dir damo/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727-onnx --lm-dir damo/speech_ngram_lm_zh-cn-ai-wesp-fst --itn-dir thuduj12/fst_itn_zh --hotword /workspace/models/hotwords.txt > log.txt 2>&1 &
tail -f log.txt
使
使CPU推理GPUhttps://github.com/modelscope/FunASR/blob/main/runtime/docs/SDK_advanced_guide_online_zh.md' WHERE `id` = 'ASR_FunASRServer';
@@ -0,0 +1,31 @@
-- 批量清理 ai_model_provider 中的 sample_rate 字段定义
UPDATE `ai_model_provider` ap
JOIN (
SELECT
id,
JSON_ARRAYAGG(
JSON_OBJECT('key', jt.k, 'label', jt.l, 'type', jt.t)
) AS new_fields
FROM `ai_model_provider`,
JSON_TABLE(`fields`, '$[*]' COLUMNS (
k VARCHAR(50) PATH '$.key',
l VARCHAR(100) PATH '$.label',
t VARCHAR(20) PATH '$.type'
)) AS jt
WHERE `model_type` = 'TTS'
AND jt.k != 'sample_rate'
GROUP BY id
) filtered ON ap.id = filtered.id
SET ap.fields = filtered.new_fields;
-- 清理 config_json 顶层的 sample_rate
UPDATE `ai_model_config`
SET `config_json` = JSON_REMOVE(`config_json`, '$.sample_rate')
WHERE `model_type` = 'TTS'
AND JSON_EXTRACT(`config_json`, '$.sample_rate') IS NOT NULL;
-- 清理Minimax流式TTS的sample_rate参数(位于audio_setting内部)
UPDATE `ai_model_config` SET
`config_json` = JSON_SET(`config_json`, '$.audio_setting', JSON_REMOVE(JSON_EXTRACT(`config_json`, '$.audio_setting'), '$.sample_rate'))
WHERE `id` = 'TTS_MinimaxStreamTTS'
AND JSON_EXTRACT(`config_json`, '$.audio_setting.sample_rate') IS NOT NULL;
@@ -0,0 +1,87 @@
-- 更新HuoshanDoubleStreamTTS供应器配置,将分散的参数改为JSON字典配置
-- 将 speech_rate, loudness_rate, pitch, emotion, emotion_scale 等参数整合为 audio_params, additions, mix_speaker 三个JSON字典
UPDATE `ai_model_provider`
SET `fields` = '[
{"key": "ws_url", "type": "string", "label": "WebSocket地址"},
{"key": "appid", "type": "string", "label": "应用ID"},
{"key": "access_token", "type": "string", "label": "访问令牌"},
{"key": "resource_id", "type": "string", "label": "资源ID"},
{"key": "speaker", "type": "string", "label": "默认音色"},
{"key": "enable_ws_reuse", "type": "boolean", "label": "是否开启链接复用", "default": true},
{"key": "audio_params", "type": "dict", "label": "音频输出配置"},
{"key": "additions", "type": "dict", "label": "高级文本处理配置"},
{"key": "mix_speaker", "type": "dict", "label": "混音控制配置"}
]'
WHERE `id` = 'SYSTEM_TTS_HSDSTTS';
-- 更新现有配置,将旧的分散参数迁移到新的JSON字典结构
UPDATE `ai_model_config`
SET `config_json` = JSON_SET(
`config_json`,
'$.audio_params', JSON_OBJECT(
'speech_rate', CAST(COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.speech_rate')), ''), '0') AS SIGNED),
'loudness_rate', CAST(COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.loudness_rate')), ''), '0') AS SIGNED)
),
'$.additions', JSON_OBJECT(
'aigc_metadata', JSON_OBJECT(),
'cache_config', JSON_OBJECT(),
'post_process', JSON_OBJECT(
'pitch', CAST(COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.pitch')), ''), '0') AS SIGNED)
)
),
'$.mix_speaker', JSON_OBJECT()
)
WHERE `id` = 'TTS_HuoshanDoubleStreamTTS';
-- 删除旧的分散参数字段
UPDATE `ai_model_config`
SET `config_json` = JSON_REMOVE(
`config_json`,
'$.speech_rate',
'$.loudness_rate',
'$.pitch',
'$.emotion',
'$.emotion_scale'
)
WHERE `id` = 'TTS_HuoshanDoubleStreamTTS';
-- 更新文档链接和备注说明
UPDATE `ai_model_config` SET
`doc_link` = 'https://www.volcengine.com/docs/6561/1329505',
`remark` = '火山引擎双向流式TTS配置说明:
1. 访 https://www.volcengine.com/
2. 访 https://console.volcengine.com/speech/service/10007
3. appid和access_token
4. ID固定为volc.service_type.10029
5. WebSocket连接复用true减少链接损耗
https://www.volcengine.com/docs/6561/1329505
audio_params -
- speech_rate: (-50~100)0
- loudness_rate: (-50~100)0
- emotion: neutralhappysadangryfearfuldisgustedsurprised
- emotion_scale: (1~5)4
{"speech_rate": 10, "loudness_rate": 5, "emotion": "happy", "emotion_scale": 4}
additions -
- post_process.pitch: (-12~12)0
- aigc_metadata: AIGC元数据配置
- cache_config:
{"post_process": {"pitch": 2}, "aigc_metadata": {}, "cache_config": {}}
mix_speaker - TTS 1.0
{"speakers": [
{"source_speaker": "zh_male_bvlazysheep","mix_factor": 0.3},
{"source_speaker": "BV120_streaming","mix_factor": 0.3},
{"source_speaker": "zh_male_ahu_conversation_wvae_bigtts","mix_factor": 0.4}
]}
- emotionemotion_scale
- https://www.volcengine.com/docs/6561/1257544
- API文档自行添加更多参数
- 1.0使req_params.speaker设置为custom_mix_bigtts
'
WHERE `id` = 'TTS_HuoshanDoubleStreamTTS';
@@ -0,0 +1,14 @@
-- 更新小智参数中的默认采样率从 16000 改为 24000
UPDATE `sys_params`
SET `param_value` = '{
"type": "hello",
"version": 1,
"transport": "websocket",
"audio_params": {
"format": "opus",
"sample_rate": 24000,
"channels": 1,
"frame_duration": 60
}
}'
WHERE `id` = 309 AND `param_code` = 'xiaozhi';
@@ -424,6 +424,13 @@ databaseChangeLog:
encoding: utf8 encoding: utf8
path: classpath:db/changelog/202511131023.sql path: classpath:db/changelog/202511131023.sql
- changeSet: - changeSet:
id: 202511221450
author: RanChen
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202511221450.sql
- changeSet:
id: 202512031517 id: 202512031517
author: rainv123 author: rainv123
changes: changes:
@@ -445,3 +452,59 @@ databaseChangeLog:
- sqlFile: - sqlFile:
encoding: utf8 encoding: utf8
path: classpath:db/changelog/202512131453.sql path: classpath:db/changelog/202512131453.sql
- changeSet:
id: 202512161529
author: RanChen
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202512161529.sql
- changeSet:
id: 202512192245
author: hrz
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202512192245.sql
- changeSet:
id: 202512221117
author: RanChen
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202512221117.sql
- changeSet:
id: 202512301430
author: RanChen
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202512301430.sql
- changeSet:
id: 202601051433
author: RanChen
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202601051433.sql
- changeSet:
id: 202601141645
author: RanChen
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202601141645.sql
- changeSet:
id: 202601231530
author: RanChen
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202601231530.sql
- changeSet:
id: 202601261730
author: RanChen
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202601261730.sql
@@ -198,4 +198,7 @@
10189=\u9002\u914D\u5668\u64CD\u4F5C\u5931\u8D25 10189=\u9002\u914D\u5668\u64CD\u4F5C\u5931\u8D25
10190=\u9002\u914D\u5668\u672A\u627E\u5230 10190=\u9002\u914D\u5668\u672A\u627E\u5230
10191=\u9002\u914D\u5668\u7F13\u5B58\u9519\u8BEF 10191=\u9002\u914D\u5668\u7F13\u5B58\u9519\u8BEF
10192=\u9002\u914D\u5668\u7C7B\u578B\u672A\u627E\u5230 10192=\u9002\u914D\u5668\u7C7B\u578B\u672A\u627E\u5230
10193=\u8BBE\u5907ID\u4E0D\u80FD\u4E3A\u7A7A
10194=\u8BBE\u5907\u4E0D\u5B58\u5728\u6216\u4E0D\u5728\u7EBF
10195=OTA\u4E0A\u4F20\u6B21\u6570\u8D85\u8FC7\u9650\u5236
@@ -8,23 +8,23 @@
10004=Die Kontonummer oder das Passwort ist falsch 10004=Die Kontonummer oder das Passwort ist falsch
10005=Konto wurde deaktiviert 10005=Konto wurde deaktiviert
10006=Eindeutige ID darf nicht leer sein 10006=Eindeutige ID darf nicht leer sein
10007=Der Bestätigungscode ist falsch 10007=Der Best\u00E4tigungscode ist falsch
10008=Zuerst Untermenü oder Schaltfläche löschen 10008=Zuerst Untermen\u00FC oder Schaltfl\u00E4che l\u00F6schen
10009=Das ursprüngliche Passwort ist falsch 10009=Das urspr\u00FCngliche Passwort ist falsch
10010=Das Konto oder Passwort ist falsch, Sie können es noch {0} Mal versuchen 10010=Das Konto oder Passwort ist falsch, Sie k\u00F6nnen es noch {0} Mal versuchen
10011=Die übergeordnete Abteilung hat eine falsche Wahl getroffen 10011=Die \u00FCbergeordnete Abteilung hat eine falsche Wahl getroffen
10012=Oberes Menü kann nicht sich selbst sein 10012=Oberes Men\u00FC kann nicht sich selbst sein
10013=Datenberechtigungsschnittstelle, die nur ein Map-Typ-Parameter sein kann 10013=Datenberechtigungsschnittstelle, die nur ein Map-Typ-Parameter sein kann
10014=Bitte löschen Sie zuerst die untergeordnete Abteilung 10014=Bitte l\u00F6schen Sie zuerst die untergeordnete Abteilung
10015=Bitte löschen Sie zuerst den Benutzer unter der Abteilung 10015=Bitte l\u00F6schen Sie zuerst den Benutzer unter der Abteilung
10016=Bereitstellung fehlgeschlagen, kein Prozess 10016=Bereitstellung fehlgeschlagen, kein Prozess
10017=Das ModellDiagramm ist falsch, bitte überprüfen 10017=Das ModellDiagramm ist falsch, bitte \u00FCberpr\u00FCfen
10018=Export fehlgeschlagen, Modell-ID ist {0} 10018=Export fehlgeschlagen, Modell-ID ist {0}
10019=Bitte laden Sie eine Datei hoch 10019=Bitte laden Sie eine Datei hoch
10020=Token darf nicht leer sein 10020=Token darf nicht leer sein
10021=Token ist ungültig, bitte melden Sie sich erneut an 10021=Token ist ung\u00FCltig, bitte melden Sie sich erneut an
10022=Das Konto wurde gesperrt 10022=Das Konto wurde gesperrt
10023=Bitte laden Sie Dateien im Format zip, bar, bpmn, bpmn20.xml hoch 10023=Bitte laden Sie Dateien im Format zip, bar, bpmn, bpmn20.xml hoch
@@ -37,25 +37,25 @@
10029=Darf keine illegalen Zeichen enthalten 10029=Darf keine illegalen Zeichen enthalten
10030=Das Passwort hat weniger als {0} Ziffern 10030=Das Passwort hat weniger als {0} Ziffern
10031=Das Passwort muss gleichzeitig aus Zahlen, Groß- und Kleinbuchstaben sowie Sonderzeichen bestehen 10031=Das Passwort muss gleichzeitig aus Zahlen, Gro\u00DF- und Kleinbuchstaben sowie Sonderzeichen bestehen
10032=Ausnahme beim Löschen dieser Daten 10032=Ausnahme beim L\u00F6schen dieser Daten
10033=Gerätebestätigungscode Fehler 10033=Ger\u00E4tebest\u00E4tigungscode Fehler
10034=Parameterwert darf nicht leer sein 10034=Parameterwert darf nicht leer sein
10035=Parametertyp darf nicht leer sein 10035=Parametertyp darf nicht leer sein
10036=Nicht unterstützter Parametertyp 10036=Nicht unterst\u00FCtzter Parametertyp
10037=Parameterwert muss eine gültige Zahl sein 10037=Parameterwert muss eine g\u00FCltige Zahl sein
10038=Parameterwert muss true oder false sein 10038=Parameterwert muss true oder false sein
10039=Parameterwert muss ein gültiges JSON-Array-Format sein 10039=Parameterwert muss ein g\u00FCltiges JSON-Array-Format sein
10040=Parameterwert muss ein gültiges JSON-Format sein 10040=Parameterwert muss ein g\u00FCltiges JSON-Format sein
10041=Gerät nicht gefunden 10041=Ger\u00E4t nicht gefunden
10042={0} 10042={0}
10043=Löschen der Daten fehlgeschlagen 10043=L\u00F6schen der Daten fehlgeschlagen
10044=Benutzer nicht angemeldet 10044=Benutzer nicht angemeldet
10045=WebSocket-Verbindung fehlgeschlagen oder Zeitüberschreitung 10045=WebSocket-Verbindung fehlgeschlagen oder Zeit\u00FCberschreitung
10046=Stimmabdruck speichern fehlgeschlagen, bitte Administrator kontaktieren 10046=Stimmabdruck speichern fehlgeschlagen, bitte Administrator kontaktieren
10047=Tägliches SendeLimit erreicht 10047=T\u00E4gliches SendeLimit erreicht
10048=Altes Passwort ist falsch 10048=Altes Passwort ist falsch
10049=Das eingestellte LLM ist nicht openai oder ollama 10049=Das eingestellte LLM ist nicht openai oder ollama
10050=Token-Generierung fehlgeschlagen 10050=Token-Generierung fehlgeschlagen
@@ -65,45 +65,45 @@
10054=Stimmabdruck-Schnittstelle nicht konfiguriert, bitte konfigurieren Sie zuerst die Stimmabdruck-Schnittstellenadresse (server.voice_print) in der Parameterkonfiguration 10054=Stimmabdruck-Schnittstelle nicht konfiguriert, bitte konfigurieren Sie zuerst die Stimmabdruck-Schnittstellenadresse (server.voice_print) in der Parameterkonfiguration
10055=SMS-Sendung fehlgeschlagen 10055=SMS-Sendung fehlgeschlagen
10056=SMS-Verbindung konnte nicht hergestellt werden 10056=SMS-Verbindung konnte nicht hergestellt werden
10057=Stimmabdruck für Agent konnte nicht erstellt werden 10057=Stimmabdruck f\u00FCr Agent konnte nicht erstellt werden
10058=Stimmabdruck für Agent konnte nicht aktualisiert werden 10058=Stimmabdruck f\u00FCr Agent konnte nicht aktualisiert werden
10059=Stimmabdruck für Agent konnte nicht gelöscht werden 10059=Stimmabdruck f\u00FCr Agent konnte nicht gel\u00F6scht werden
10060=Sendung zu häufig, bitte versuchen Sie es nach {0} Sekunden erneut 10060=Sendung zu h\u00E4ufig, bitte versuchen Sie es nach {0} Sekunden erneut
10061=Aktivierungscode darf nicht leer sein 10061=Aktivierungscode darf nicht leer sein
10062=Aktivierungscode Fehler 10062=Aktivierungscode Fehler
10063=Gerät bereits aktiviert 10063=Ger\u00E4t bereits aktiviert
10064=Dieses Modell ist das Standardmodell, bitte setzen Sie zuerst ein anderes Modell als Standard 10064=Dieses Modell ist das Standardmodell, bitte setzen Sie zuerst ein anderes Modell als Standard
10065=Hinzufügen der Daten fehlgeschlagen 10065=Hinzuf\u00FCgen der Daten fehlgeschlagen
10066=Aktualisieren der Daten fehlgeschlagen 10066=Aktualisieren der Daten fehlgeschlagen
10067=Grafischer Bestätigungscode Fehler 10067=Grafischer Best\u00E4tigungscode Fehler
10068=Mobilregistrierung nicht aktiviert, SMS-Bestätigungscode-Funktion kann nicht verwendet werden 10068=Mobilregistrierung nicht aktiviert, SMS-Best\u00E4tigungscode-Funktion kann nicht verwendet werden
10069=Benutzername ist keine Handynummer, bitte erneut eingeben 10069=Benutzername ist keine Handynummer, bitte erneut eingeben
10070=Diese Handynummer wurde bereits registriert 10070=Diese Handynummer wurde bereits registriert
10071=Die eingegebene Handynummer ist nicht registriert 10071=Die eingegebene Handynummer ist nicht registriert
10072=Normale Benutzerregistrierung derzeit nicht erlaubt 10072=Normale Benutzerregistrierung derzeit nicht erlaubt
10073=Mobilregistrierung nicht aktiviert, Passwort-Wiederherstellungsfunktion kann nicht verwendet werden 10073=Mobilregistrierung nicht aktiviert, Passwort-Wiederherstellungsfunktion kann nicht verwendet werden
10074=Das Format der eingegebenen Handynummer ist falsch 10074=Das Format der eingegebenen Handynummer ist falsch
10075=Der eingegebene SMS-Bestätigungscode ist falsch 10075=Der eingegebene SMS-Best\u00E4tigungscode ist falsch
10076=Wörterbuchtyp existiert nicht 10076=W\u00F6rterbuchtyp existiert nicht
10077=Wörterbuchtypcode ist dupliziert 10077=W\u00F6rterbuchtypcode ist dupliziert
10078=Lesen der Ressource fehlgeschlagen 10078=Lesen der Ressource fehlgeschlagen
10079=LLM-Modell und Absichtserkennung, Parameterauswahl stimmt nicht überein 10079=LLM-Modell und Absichtserkennung, Parameterauswahl stimmt nicht \u00FCberein
10080=Dieser Stimmabdruck gehört zu {0}, der bereits registriert ist, bitte wählen Sie eine andere Stimme 10080=Dieser Stimmabdruck geh\u00F6rt zu {0}, der bereits registriert ist, bitte w\u00E4hlen Sie eine andere Stimme
10081=Fehler beim Löschen des Stimmabdrucks aufgetreten 10081=Fehler beim L\u00F6schen des Stimmabdrucks aufgetreten
10082=Änderung nicht erlaubt, diese Stimme wurde bereits als Stimmabdruck registriert ({0}) 10082=\u00C4nderung nicht erlaubt, diese Stimme wurde bereits als Stimmabdruck registriert ({0})
10083=Fehler beim Ändern des Stimmabdrucks, bitte Administrator kontaktieren 10083=Fehler beim \u00C4ndern des Stimmabdrucks, bitte Administrator kontaktieren
10084=Stimmabdruck-Schnittstellenadresse Fehler, bitte gehen Sie zur Parameterverwaltung, um die Stimmabdruck-Schnittstellenadresse zu ändern 10084=Stimmabdruck-Schnittstellenadresse Fehler, bitte gehen Sie zur Parameterverwaltung, um die Stimmabdruck-Schnittstellenadresse zu \u00E4ndern
10085=Audiodaten gehören nicht zu diesem Agent 10085=Audiodaten geh\u00F6ren nicht zu diesem Agent
10086=Audiodaten sind leer, bitte überprüfen Sie hochgeladene Daten 10086=Audiodaten sind leer, bitte \u00FCberpr\u00FCfen Sie hochgeladene Daten
10087=Stimmabdruck-Registrierung fehlgeschlagen, Anfrage nicht erfolgreich 10087=Stimmabdruck-Registrierung fehlgeschlagen, Anfrage nicht erfolgreich
10088=Stimmabdruck-Registrierung fehlgeschlagen, Anfrageverarbeitung fehlgeschlagen 10088=Stimmabdruck-Registrierung fehlgeschlagen, Anfrageverarbeitung fehlgeschlagen
10089=Stimmabdruck-Stornierung fehlgeschlagen, Anfrage nicht erfolgreich 10089=Stimmabdruck-Stornierung fehlgeschlagen, Anfrage nicht erfolgreich
10090=Stimmabdruck-Stornierung fehlgeschlagen, Anfrageverarbeitung fehlgeschlagen 10090=Stimmabdruck-Stornierung fehlgeschlagen, Anfrageverarbeitung fehlgeschlagen
10091=Modellanbieter existiert nicht 10091=Modellanbieter existiert nicht
10092=Das konfigurierte LLM existiert nicht 10092=Das konfigurierte LLM existiert nicht
10093=Diese Modellkonfiguration wird von Agent {0} referenziert und kann nicht gelöscht werden 10093=Diese Modellkonfiguration wird von Agent {0} referenziert und kann nicht gel\u00F6scht werden
10094=Dieses LLM-Modell wird von der Absichtserkennungskonfiguration referenziert und kann nicht gelöscht werden 10094=Dieses LLM-Modell wird von der Absichtserkennungskonfiguration referenziert und kann nicht gel\u00F6scht werden
10095=Ungültige Serveroperation 10095=Ung\u00FCltige Serveroperation
10096=Server WebSocket-Adresse nicht konfiguriert 10096=Server WebSocket-Adresse nicht konfiguriert
10097=Ziel-WebSocket-Adresse existiert nicht 10097=Ziel-WebSocket-Adresse existiert nicht
10098=WebSocket-Adressliste darf nicht leer sein 10098=WebSocket-Adressliste darf nicht leer sein
@@ -115,47 +115,47 @@
10104=OTA-Adresse muss mit http oder https beginnen 10104=OTA-Adresse muss mit http oder https beginnen
10105=OTA-Adresse muss mit /ota/ enden 10105=OTA-Adresse muss mit /ota/ enden
10106=OTA-Schnittstellenzugriff fehlgeschlagen 10106=OTA-Schnittstellenzugriff fehlgeschlagen
10107=OTA-Schnittstelle Rückgabeinhaltformat ist falsch 10107=OTA-Schnittstelle R\u00FCckgabeinhaltformat ist falsch
10108=OTA-Schnittstellenvalidierung fehlgeschlagen 10108=OTA-Schnittstellenvalidierung fehlgeschlagen
10109=MCP-Adresse darf nicht leer sein 10109=MCP-Adresse darf nicht leer sein
10110=MCP-Adresse darf nicht localhost oder 127.0.0.1 verwenden 10110=MCP-Adresse darf nicht localhost oder 127.0.0.1 verwenden
10111=Keine gültige MCP-Adresse 10111=Keine g\u00FCltige MCP-Adresse
10112=MCP-Schnittstellenzugriff fehlgeschlagen 10112=MCP-Schnittstellenzugriff fehlgeschlagen
10113=MCP-Schnittstelle Rückgabeinhaltformat ist falsch 10113=MCP-Schnittstelle R\u00FCckgabeinhaltformat ist falsch
10114=MCP-Schnittstellenvalidierung fehlgeschlagen 10114=MCP-Schnittstellenvalidierung fehlgeschlagen
10115=Stimmabdruck-Schnittstellenadresse darf nicht leer sein 10115=Stimmabdruck-Schnittstellenadresse darf nicht leer sein
10116=Stimmabdruck-Schnittstellenadresse darf nicht localhost oder 127.0.0.1 verwenden 10116=Stimmabdruck-Schnittstellenadresse darf nicht localhost oder 127.0.0.1 verwenden
10117=Keine gültige Stimmabdruck-Schnittstellenadresse 10117=Keine g\u00FCltige Stimmabdruck-Schnittstellenadresse
10118=Stimmabdruck-Schnittstellenadresse muss mit http oder https beginnen 10118=Stimmabdruck-Schnittstellenadresse muss mit http oder https beginnen
10119=Stimmabdruck-Schnittstellenzugriff fehlgeschlagen 10119=Stimmabdruck-Schnittstellenzugriff fehlgeschlagen
10120=Stimmabdruck-Schnittstelle Rückgabeinhaltformat ist falsch 10120=Stimmabdruck-Schnittstelle R\u00FCckgabeinhaltformat ist falsch
10121=Stimmabdruck-Schnittstellenvalidierung fehlgeschlagen 10121=Stimmabdruck-Schnittstellenvalidierung fehlgeschlagen
10122=MQTT-Geheimnis darf nicht leer sein 10122=MQTT-Geheimnis darf nicht leer sein
10123=Ihr MQTT-Geheimnis ist nicht sicher, es muss mindestens 8 Zeichen lang sein und muss sowohl Groß- als auch Kleinbuchstaben enthalten 10123=Ihr MQTT-Geheimnis ist nicht sicher, es muss mindestens 8 Zeichen lang sein und muss sowohl Gro\u00DF- als auch Kleinbuchstaben enthalten
10124=Ihr MQTT-Geheimnis ist nicht sicher, MQTT-Geheimnis muss sowohl Groß- als auch Kleinbuchstaben enthalten 10124=Ihr MQTT-Geheimnis ist nicht sicher, MQTT-Geheimnis muss sowohl Gro\u00DF- als auch Kleinbuchstaben enthalten
10125=Ihr MQTT-Geheimnis enthält schwaches Passwort 10125=Ihr MQTT-Geheimnis enth\u00E4lt schwaches Passwort
10128=Wörterbuchlabel ist dupliziert 10128=W\u00F6rterbuchlabel ist dupliziert
10129=SM2-Schlüssel nicht konfiguriert 10129=SM2-Schl\u00FCssel nicht konfiguriert
10130=SM2-Entschlüsselung fehlgeschlagen 10130=SM2-Entschl\u00FCsselung fehlgeschlagen
10131=modelType und provideCode dürfen nicht leer sein 10131=modelType und provideCode d\u00FCrfen nicht leer sein
10132=Keine Berechtigung, den Chat-Verlauf dieses Agents anzuzeigen 10132=Keine Berechtigung, den Chat-Verlauf dieses Agents anzuzeigen
10133=Sitzungs-ID darf nicht leer sein 10133=Sitzungs-ID darf nicht leer sein
10134=Agent-ID darf nicht leer sein 10134=Agent-ID darf nicht leer sein
10135=Chat-Verlauf-Download fehlgeschlagen 10135=Chat-Verlauf-Download fehlgeschlagen
10136=Download-Link abgelaufen oder ungültig 10136=Download-Link abgelaufen oder ung\u00FCltig
10137=Download-Link ungültig 10137=Download-Link ung\u00FCltig
10138=Benutzer 10138=Benutzer
10139=Agent 10139=Agent
10140=Audio-Datei darf nicht leer sein 10140=Audio-Datei darf nicht leer sein
10141=Nur Audio-Dateien werden unterstützt 10141=Nur Audio-Dateien werden unterst\u00FCtzt
10142=Audio-Dateigröße darf 10MB nicht überschreiten 10142=Audio-Dateigr\u00F6\u00DFe darf 10MB nicht \u00FCberschreiten
10143=Upload fehlgeschlagen 10143=Upload fehlgeschlagen
10144=Stimmenklon-Datensatz existiert nicht 10144=Stimmenklon-Datensatz existiert nicht
10145=Stimmressourceninformationen dürfen nicht leer sein 10145=Stimmressourceninformationen d\u00FCrfen nicht leer sein
10146=TTS-Plattformname darf nicht leer sein 10146=TTS-Plattformname darf nicht leer sein
10147=Stimmen-ID darf nicht leer sein 10147=Stimmen-ID darf nicht leer sein
10148=Kontoinhaber darf nicht leer sein 10148=Kontoinhaber darf nicht leer sein
10149=Zu löschende Stimmressourcen-ID darf nicht leer sein 10149=Zu l\u00F6schende Stimmressourcen-ID darf nicht leer sein
10150=Sie haben keine Berechtigung, diesen Datensatz zu bearbeiten 10150=Sie haben keine Berechtigung, diesen Datensatz zu bearbeiten
10151=Bitte laden Sie zuerst eine Audio-Datei hoch 10151=Bitte laden Sie zuerst eine Audio-Datei hoch
10152=Modellkonfiguration nicht gefunden 10152=Modellkonfiguration nicht gefunden
@@ -179,18 +179,18 @@
10170=Wissensbasis-Name existiert bereits 10170=Wissensbasis-Name existiert bereits
10171=RAG-Konfiguration base_url darf nicht leer sein 10171=RAG-Konfiguration base_url darf nicht leer sein
10172=RAG-Konfiguration api_key darf nicht leer sein 10172=RAG-Konfiguration api_key darf nicht leer sein
10173=RAG-Konfiguration api_key darf keinen Platzhalter enthalten, bitte ersetzen Sie mit tatsächlichem API-Schlüssel 10173=RAG-Konfiguration api_key darf keinen Platzhalter enthalten, bitte ersetzen Sie mit tats\u00E4chlichem API-Schl\u00FCssel
10174=RAG-Konfiguration base_url-Formatfehler, muss mit http oder https beginnen 10174=RAG-Konfiguration base_url-Formatfehler, muss mit http oder https beginnen
10175=Mac-Adresse darf nicht leer sein 10175=Mac-Adresse darf nicht leer sein
10176=RAG-Konfiguration dataset_id darf nicht leer sein 10176=RAG-Konfiguration dataset_id darf nicht leer sein
10177=RAG-Konfiguration model_id darf nicht leer sein 10177=RAG-Konfiguration model_id darf nicht leer sein
10178=RAG-Konfiguration dataset_id und model_id dürfen nicht leer sein 10178=RAG-Konfiguration dataset_id und model_id d\u00FCrfen nicht leer sein
10179=Dateiname darf nicht leer sein 10179=Dateiname darf nicht leer sein
10180=Dateiinhalt darf nicht leer sein 10180=Dateiinhalt darf nicht leer sein
10181=Stimmenklon-Name darf nicht leer sein 10181=Stimmenklon-Name darf nicht leer sein
10182=Stimmenklon-Audio nicht gefunden 10182=Stimmenklon-Audio nicht gefunden
10183=Standard-Agent-Vorlage nicht gefunden 10183=Standard-Agent-Vorlage nicht gefunden
10184=Nicht unterstützter Adaptertyp 10184=Nicht unterst\u00FCtzter Adaptertyp
10185=RAG-Konfigurationsvalidierung fehlgeschlagen 10185=RAG-Konfigurationsvalidierung fehlgeschlagen
10186=Adapter-Erstellung fehlgeschlagen 10186=Adapter-Erstellung fehlgeschlagen
10187=Adapter-Initialisierung fehlgeschlagen 10187=Adapter-Initialisierung fehlgeschlagen
@@ -198,4 +198,7 @@
10189=Adapter-Operation fehlgeschlagen 10189=Adapter-Operation fehlgeschlagen
10190=Adapter nicht gefunden 10190=Adapter nicht gefunden
10191=Adapter-Cache-Fehler 10191=Adapter-Cache-Fehler
10192=Adaptertyp nicht gefunden 10192=Adaptertyp nicht gefunden
10193=Adapter-ID darf nicht leer sein
10194=Adapter nicht gefunden oder nicht online
10195=OTA-Upload-Anzahl \u00FCberschreitet das Limit
@@ -198,4 +198,7 @@
10189=Adapter operation failed 10189=Adapter operation failed
10190=Adapter not found 10190=Adapter not found
10191=Adapter cache error 10191=Adapter cache error
10192=Adapter type not found 10192=Adapter type not found
10193=Device ID cannot be empty
10194=Device not found or not online
10195=OTA upload times exceed the limit
@@ -1,201 +1,204 @@
#Tiếng Việt #Ti\u1EBFng Vi\u1EC7t
500=Ngoại lệ nội bộ máy chủ 500=Ngo\u1EA1i l\u1EC7 n\u1ED9i b\u1ED9 m\u00E1y ch\u1EE7
401=Không được ủy quyền 401=Kh\u00F4ng \u0111\u01B0\u1EE3c \u1EE7y quy\u1EC1n
403=Truy cập bị từ chối, không có quyền 403=Truy c\u1EADp b\u1ECB t\u1EEB ch\u1ED1i, kh\u00F4ng c\u00F3 quy\u1EC1n
10001={0} không thể để trống 10001={0} kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10002=Bản ghi đã tồn tại trong cơ sở dữ liệu 10002=B\u1EA3n ghi \u0111\u00E3 t\u1ED3n t\u1EA1i trong c\u01A1 s\u1EDF d\u1EEF li\u1EC7u
10003=Không thể lấy tham s 10003=Kh\u00F4ng th\u1EC3 l\u1EA5y tham s\u1ED1
10004=Số tài khoản hoặc mật khẩu không chính xác 10004=S\u1ED1 t\u00E0i kho\u1EA3n ho\u1EB7c m\u1EADt kh\u1EA9u kh\u00F4ng ch\u00EDnh x\u00E1c
10005=Tài khoản đã bị vô hiệu hóa 10005=T\u00E0i kho\u1EA3n \u0111\u00E3 b\u1ECB v\u00F4 hi\u1EC7u h\u00F3a
10006=ID duy nhất không thể để trống 10006=ID duy nh\u1EA5t kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10007=Mã xác minh không chính xác 10007=M\u00E3 x\u00E1c minh kh\u00F4ng ch\u00EDnh x\u00E1c
10008=Trước tiên hãy xóa menu con hoặc nút 10008=Tr\u01B0\u1EDBc ti\u00EAn h\u00E3y x\u00F3a menu con ho\u1EB7c n\u00FAt
10009=Mật khẩu gốc không chính xác 10009=M\u1EADt kh\u1EA9u g\u1ED1c kh\u00F4ng ch\u00EDnh x\u00E1c
10010=Tài khoản hoặc mật khẩu không chính xác, bạn có thể thử thêm {0} lần nữa 10010=T\u00E0i kho\u1EA3n ho\u1EB7c m\u1EADt kh\u1EA9u kh\u00F4ng ch\u00EDnh x\u00E1c, b\u1EA1n c\u00F3 th\u1EC3 th\u1EED th\u00EAm {0} l\u1EA7n n\u1EEFa
10011=Phòng ban cấp trên đã lựa chọn sai 10011=Ph\u00F2ng ban c\u1EA5p tr\u00EAn \u0111\u00E3 l\u1EF1a ch\u1ECDn sai
10012=Menu trên không thể là chính nó 10012=Menu tr\u00EAn kh\u00F4ng th\u1EC3 l\u00E0 ch\u00EDnh n\u00F3
10013=Giao diện quyền dữ liệu, chỉ có thể là tham số kiểu Map 10013=Giao di\u1EC7n quy\u1EC1n d\u1EEF li\u1EC7u, ch\u1EC9 c\u00F3 th\u1EC3 l\u00E0 tham s\u1ED1 ki\u1EC3u Map
10014=Vui lòng xóa phòng ban cấp dưới trước 10014=Vui l\u00F2ng x\u00F3a ph\u00F2ng ban c\u1EA5p d\u01B0\u1EDBi tr\u01B0\u1EDBc
10015=Vui lòng xóa người dùng thuộc phòng ban trước 10015=Vui l\u00F2ng x\u00F3a ng\u01B0\u1EDDi d\u00F9ng thu\u1ED9c ph\u00F2ng ban tr\u01B0\u1EDBc
10016=Trin khai thất bại, không có quy trình 10016=Tri\u1EC3n khai th\u1EA5t b\u1EA1i, kh\u00F4ng c\u00F3 quy tr\u00ECnh
10017=Biểu đồ mô hình không chính xác, vui lòng kiểm tra 10017=Bi\u1EC3u \u0111\u1ED3 m\u00F4 h\u00ECnh kh\u00F4ng ch\u00EDnh x\u00E1c, vui l\u00F2ng ki\u1EC3m tra
10018=Xuất thất bại, ID mô hình là {0} 10018=Xu\u1EA5t th\u1EA5t b\u1EA1i, ID m\u00F4 h\u00ECnh l\u00E0 {0}
10019=Vui lòng tải lên tệp 10019=Vui l\u00F2ng t\u1EA3i l\u00EAn t\u1EC7p
10020=Token không thể để trống 10020=Token kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10021=Token không hợp lệ, vui lòng đăng nhập lại 10021=Token kh\u00F4ng h\u1EE3p l\u1EC7, vui l\u00F2ng \u0111\u0103ng nh\u1EADp l\u1EA1i
10022=Tài khoản đã bị khóa 10022=T\u00E0i kho\u1EA3n \u0111\u00E3 b\u1ECB kh\u00F3a
10023=Vui lòng tải lên tệp ở định dạng zip, bar, bpmn, bpmn20.xml 10023=Vui l\u00F2ng t\u1EA3i l\u00EAn t\u1EC7p \u1EDF \u0111\u1ECBnh d\u1EA1ng zip, bar, bpmn, bpmn20.xml
10024=Tải lên tệp {0} thất bại 10024=T\u1EA3i l\u00EAn t\u1EC7p {0} th\u1EA5t b\u1EA1i
10025=Gi SMS {0} thất bại 10025=G\u1EEDi SMS {0} th\u1EA5t b\u1EA1i
10026=Mu email không tồn tại 10026=M\u1EABu email kh\u00F4ng t\u1ED3n t\u1EA1i
10027=Ngoại lệ dịch vụ Redis 10027=Ngo\u1EA1i l\u1EC7 d\u1ECBch v\u1EE5 Redis
10028=Tác vụ hẹn giờ thất bại 10028=T\u00E1c v\u1EE5 h\u1EB9n gi\u1EDD th\u1EA5t b\u1EA1i
10029=Không thể chứa ký tự không hợp lệ 10029=Kh\u00F4ng th\u1EC3 ch\u1EE9a k\u00FD t\u1EF1 kh\u00F4ng h\u1EE3p l\u1EC7
10030=Mật khẩu ít hơn {0} chữ số 10030=M\u1EADt kh\u1EA9u \u00EDt h\u01A1n {0} ch\u1EEF s\u1ED1
10031=Mật khẩu phải bao gồm số, chữ hoa, chữ thường và ký tự đặc biệt cùng một lúc 10031=M\u1EADt kh\u1EA9u ph\u1EA3i bao g\u1ED3m s\u1ED1, ch\u1EEF hoa, ch\u1EEF th\u01B0\u1EDDng v\u00E0 k\u00FD t\u1EF1 \u0111\u1EB7c bi\u1EC7t c\u00F9ng m\u1ED9t l\u00FAc
10032=Ngoại lệ khi xóa dữ liệu này 10032=Ngo\u1EA1i l\u1EC7 khi x\u00F3a d\u1EEF li\u1EC7u n\u00E0y
10033=Lỗi mã xác minh thiết bị 10033=L\u1ED7i m\u00E3 x\u00E1c minh thi\u1EBFt b\u1ECB
10034=Giá trị tham số không thể để trống 10034=Gi\u00E1 tr\u1ECB tham s\u1ED1 kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10035=Loi tham số không thể để trống 10035=Lo\u1EA1i tham s\u1ED1 kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10036=Loi tham số không được hỗ trợ 10036=Lo\u1EA1i tham s\u1ED1 kh\u00F4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3
10037=Giá trị tham số phải là số hợp lệ 10037=Gi\u00E1 tr\u1ECB tham s\u1ED1 ph\u1EA3i l\u00E0 s\u1ED1 h\u1EE3p l\u1EC7
10038=Giá trị tham số phải là true hoc false 10038=Gi\u00E1 tr\u1ECB tham s\u1ED1 ph\u1EA3i l\u00E0 true ho\u1EB7c false
10039=Giá trị tham số phải là định dạng mảng JSON hợp lệ 10039=Gi\u00E1 tr\u1ECB tham s\u1ED1 ph\u1EA3i l\u00E0 \u0111\u1ECBnh d\u1EA1ng m\u1EA3ng JSON h\u1EE3p l\u1EC7
10040=Giá trị tham số phải là định dạng JSON hợp lệ 10040=Gi\u00E1 tr\u1ECB tham s\u1ED1 ph\u1EA3i l\u00E0 \u0111\u1ECBnh d\u1EA1ng JSON h\u1EE3p l\u1EC7
10041=Không tìm thấy thiết bị 10041=Kh\u00F4ng t\u00ECm th\u1EA5y thi\u1EBFt b\u1ECB
10042={0} 10042={0}
10043=Xóa dữ liệu thất bại 10043=X\u00F3a d\u1EEF li\u1EC7u th\u1EA5t b\u1EA1i
10044=Người dùng chưa đăng nhập 10044=Ng\u01B0\u1EDDi d\u00F9ng ch\u01B0a \u0111\u0103ng nh\u1EADp
10045=Kết nối WebSocket thất bại hoặc hết thời gian ch 10045=K\u1EBFt n\u1ED1i WebSocket th\u1EA5t b\u1EA1i ho\u1EB7c h\u1EBFt th\u1EDDi gian ch\u1EDD
10046=Không thể lưu dấu giọng nói, vui lòng liên hệ quản trị viên 10046=Kh\u00F4ng th\u1EC3 l\u01B0u d\u1EA5u gi\u1ECDng n\u00F3i, vui l\u00F2ng li\u00EAn h\u1EC7 qu\u1EA3n tr\u1ECB vi\u00EAn
10047=Đạt đến giới hạn gửi hàng ngày 10047=\u0110\u1EA1t \u0111\u1EBFn gi\u1EDBi h\u1EA1n g\u1EEDi h\u00E0ng ng\u00E0y
10048=Mật khẩu cũ không chính xác 10048=M\u1EADt kh\u1EA9u c\u0169 kh\u00F4ng ch\u00EDnh x\u00E1c
10049=LLM được đặt không phải là openai hoc ollama 10049=LLM \u0111\u01B0\u1EE3c \u0111\u1EB7t kh\u00F4ng ph\u1EA3i l\u00E0 openai ho\u1EB7c ollama
10050=Không thể tạo token 10050=Kh\u00F4ng th\u1EC3 t\u1EA1o token
10051=Không tìm thấy tài nguyên 10051=Kh\u00F4ng t\u00ECm th\u1EA5y t\u00E0i nguy\u00EAn
10052=Không tìm thấy agent mặc định 10052=Kh\u00F4ng t\u00ECm th\u1EA5y agent m\u1EB7c \u0111\u1ECBnh
10053=Không tìm thấy agent 10053=Kh\u00F4ng t\u00ECm th\u1EA5y agent
10054=Giao diện dấu giọng nói chưa được cấu hình, vui lòng cấu hình địa chỉ giao diện dấu giọng nói (server.voice_print) trong cấu hình tham số trước 10054=Giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i ch\u01B0a \u0111\u01B0\u1EE3c c\u1EA5u h\u00ECnh, vui l\u00F2ng c\u1EA5u h\u00ECnh \u0111\u1ECBa ch\u1EC9 giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i (server.voice_print) trong c\u1EA5u h\u00ECnh tham s\u1ED1 tr\u01B0\u1EDBc
10055=Gi SMS thất bại 10055=G\u1EEDi SMS th\u1EA5t b\u1EA1i
10056=Không thể thiết lập kết nối SMS 10056=Kh\u00F4ng th\u1EC3 thi\u1EBFt l\u1EADp k\u1EBFt n\u1ED1i SMS
10057=Không thể tạo dấu giọng nói cho agent 10057=Kh\u00F4ng th\u1EC3 t\u1EA1o d\u1EA5u gi\u1ECDng n\u00F3i cho agent
10058=Không thể cập nhật dấu giọng nói cho agent 10058=Kh\u00F4ng th\u1EC3 c\u1EADp nh\u1EADt d\u1EA5u gi\u1ECDng n\u00F3i cho agent
10059=Không thể xóa dấu giọng nói cho agent 10059=Kh\u00F4ng th\u1EC3 x\u00F3a d\u1EA5u gi\u1ECDng n\u00F3i cho agent
10060=Gửi quá thường xuyên, vui lòng thử lại sau {0} giây 10060=G\u1EEDi qu\u00E1 th\u01B0\u1EDDng xuy\u00EAn, vui l\u00F2ng th\u1EED l\u1EA1i sau {0} gi\u00E2y
10061=Mã kích hoạt không thể để trống 10061=M\u00E3 k\u00EDch ho\u1EA1t kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10062=Lỗi mã kích hoạt 10062=L\u1ED7i m\u00E3 k\u00EDch ho\u1EA1t
10063=Thiết bị đã được kích hoạt 10063=Thi\u1EBFt b\u1ECB \u0111\u00E3 \u0111\u01B0\u1EE3c k\u00EDch ho\u1EA1t
10064=Mô hình này là mô hình mặc định, vui lòng đặt mô hình khác làm mặc định trước 10064=M\u00F4 h\u00ECnh n\u00E0y l\u00E0 m\u00F4 h\u00ECnh m\u1EB7c \u0111\u1ECBnh, vui l\u00F2ng \u0111\u1EB7t m\u00F4 h\u00ECnh kh\u00E1c l\u00E0m m\u1EB7c \u0111\u1ECBnh tr\u01B0\u1EDBc
10065=Thêm dữ liệu thất bại 10065=Th\u00EAm d\u1EEF li\u1EC7u th\u1EA5t b\u1EA1i
10066=Cập nhật dữ liệu thất bại 10066=C\u1EADp nh\u1EADt d\u1EEF li\u1EC7u th\u1EA5t b\u1EA1i
10067=Lỗi mã xác minh đồ họa 10067=L\u1ED7i m\u00E3 x\u00E1c minh \u0111\u1ED3 h\u1ECDa
10068=Đăng ký di động không được bật, không thể sử dụng chức năng mã xác minh SMS 10068=\u0110\u0103ng k\u00FD di \u0111\u1ED9ng kh\u00F4ng \u0111\u01B0\u1EE3c b\u1EADt, kh\u00F4ng th\u1EC3 s\u1EED d\u1EE5ng ch\u1EE9c n\u0103ng m\u00E3 x\u00E1c minh SMS
10069=Tên người dùng không phải là số điện thoại di động, vui lòng nhập lại 10069=T\u00EAn ng\u01B0\u1EDDi d\u00F9ng kh\u00F4ng ph\u1EA3i l\u00E0 s\u1ED1 \u0111i\u1EC7n tho\u1EA1i di \u0111\u1ED9ng, vui l\u00F2ng nh\u1EADp l\u1EA1i
10070=Số điện thoại di động này đã được đăng ký 10070=S\u1ED1 \u0111i\u1EC7n tho\u1EA1i di \u0111\u1ED9ng n\u00E0y \u0111\u00E3 \u0111\u01B0\u1EE3c \u0111\u0103ng k\u00FD
10071=Số điện thoại di động đã nhập không được đăng ký 10071=S\u1ED1 \u0111i\u1EC7n tho\u1EA1i di \u0111\u1ED9ng \u0111\u00E3 nh\u1EADp kh\u00F4ng \u0111\u01B0\u1EE3c \u0111\u0103ng k\u00FD
10072=Hiện không cho phép đăng ký người dùng thông thường 10072=Hi\u1EC7n kh\u00F4ng cho ph\u00E9p \u0111\u0103ng k\u00FD ng\u01B0\u1EDDi d\u00F9ng th\u00F4ng th\u01B0\u1EDDng
10073=Đăng ký di động không được bật, không thể sử dụng chức năng truy xuất mật khẩu 10073=\u0110\u0103ng k\u00FD di \u0111\u1ED9ng kh\u00F4ng \u0111\u01B0\u1EE3c b\u1EADt, kh\u00F4ng th\u1EC3 s\u1EED d\u1EE5ng ch\u1EE9c n\u0103ng truy xu\u1EA5t m\u1EADt kh\u1EA9u
10074=Định dạng số điện thoại di động đã nhập không chính xác 10074=\u0110\u1ECBnh d\u1EA1ng s\u1ED1 \u0111i\u1EC7n tho\u1EA1i di \u0111\u1ED9ng \u0111\u00E3 nh\u1EADp kh\u00F4ng ch\u00EDnh x\u00E1c
10075=Mã xác minh SMS đã nhập không chính xác 10075=M\u00E3 x\u00E1c minh SMS \u0111\u00E3 nh\u1EADp kh\u00F4ng ch\u00EDnh x\u00E1c
10076=Loại từ điển không tồn tại 10076=Lo\u1EA1i t\u1EEB \u0111i\u1EC3n kh\u00F4ng t\u1ED3n t\u1EA1i
10077=Mã loại từ điển bị trùng lặp 10077=M\u00E3 lo\u1EA1i t\u1EEB \u0111i\u1EC3n b\u1ECB tr\u00F9ng l\u1EB7p
10078=Đọc tài nguyên thất bại 10078=\u0110\u1ECDc t\u00E0i nguy\u00EAn th\u1EA5t b\u1EA1i
10079=Mô hình LLM và nhận dạng ý định, lựa chọn tham số không khớp 10079=M\u00F4 h\u00ECnh LLM v\u00E0 nh\u1EADn d\u1EA1ng \u00FD \u0111\u1ECBnh, l\u1EF1a ch\u1ECDn tham s\u1ED1 kh\u00F4ng kh\u1EDBp
10080=Dấu giọng nói này thuộc về {0} đã đăng ký, vui lòng chọn giọng nói khác 10080=D\u1EA5u gi\u1ECDng n\u00F3i n\u00E0y thu\u1ED9c v\u1EC1 {0} \u0111\u00E3 \u0111\u0103ng k\u00FD, vui l\u00F2ng ch\u1ECDn gi\u1ECDng n\u00F3i kh\u00E1c
10081=Đã xảy ra lỗi khi xóa dấu giọng nói 10081=\u0110\u00E3 x\u1EA3y ra l\u1ED7i khi x\u00F3a d\u1EA5u gi\u1ECDng n\u00F3i
10082=Không cho phép sửa đổi, giọng nói này đã được đăng ký làm dấu giọng nói ({0}) 10082=Kh\u00F4ng cho ph\u00E9p s\u1EEDa \u0111\u1ED5i, gi\u1ECDng n\u00F3i n\u00E0y \u0111\u00E3 \u0111\u01B0\u1EE3c \u0111\u0103ng k\u00FD l\u00E0m d\u1EA5u gi\u1ECDng n\u00F3i ({0})
10083=Lỗi sửa đổi dấu giọng nói, vui lòng liên hệ quản trị viên 10083=L\u1ED7i s\u1EEDa \u0111\u1ED5i d\u1EA5u gi\u1ECDng n\u00F3i, vui l\u00F2ng li\u00EAn h\u1EC7 qu\u1EA3n tr\u1ECB vi\u00EAn
10084=Lỗi địa chỉ giao diện dấu giọng nói, vui lòng vào quản lý tham số để sửa đổi địa chỉ giao diện dấu giọng nói 10084=L\u1ED7i \u0111\u1ECBa ch\u1EC9 giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i, vui l\u00F2ng v\u00E0o qu\u1EA3n l\u00FD tham s\u1ED1 \u0111\u1EC3 s\u1EEDa \u0111\u1ED5i \u0111\u1ECBa ch\u1EC9 giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i
10085=Dữ liệu âm thanh không thuộc về agent này 10085=D\u1EEF li\u1EC7u \u00E2m thanh kh\u00F4ng thu\u1ED9c v\u1EC1 agent n\u00E0y
10086=Dữ liệu âm thanh trng, vui lòng kiểm tra dữ liệu đã tải lên 10086=D\u1EEF li\u1EC7u \u00E2m thanh tr\u1ED1ng, vui l\u00F2ng ki\u1EC3m tra d\u1EEF li\u1EC7u \u0111\u00E3 t\u1EA3i l\u00EAn
10087=Đăng ký dấu giọng nói thất bại, yêu cầu không thành công 10087=\u0110\u0103ng k\u00FD d\u1EA5u gi\u1ECDng n\u00F3i th\u1EA5t b\u1EA1i, y\u00EAu c\u1EA7u kh\u00F4ng th\u00E0nh c\u00F4ng
10088=Đăng ký dấu giọng nói thất bại, xử lý yêu cầu thất bại 10088=\u0110\u0103ng k\u00FD d\u1EA5u gi\u1ECDng n\u00F3i th\u1EA5t b\u1EA1i, x\u1EED l\u00FD y\u00EAu c\u1EA7u th\u1EA5t b\u1EA1i
10089=Hủy dấu giọng nói thất bại, yêu cầu không thành công 10089=H\u1EE7y d\u1EA5u gi\u1ECDng n\u00F3i th\u1EA5t b\u1EA1i, y\u00EAu c\u1EA7u kh\u00F4ng th\u00E0nh c\u00F4ng
10090=Hủy dấu giọng nói thất bại, xử lý yêu cầu thất bại 10090=H\u1EE7y d\u1EA5u gi\u1ECDng n\u00F3i th\u1EA5t b\u1EA1i, x\u1EED l\u00FD y\u00EAu c\u1EA7u th\u1EA5t b\u1EA1i
10091=Nhà cung cấp mô hình không tồn tại 10091=Nh\u00E0 cung c\u1EA5p m\u00F4 h\u00ECnh kh\u00F4ng t\u1ED3n t\u1EA1i
10092=LLM được cấu hình không tồn tại 10092=LLM \u0111\u01B0\u1EE3c c\u1EA5u h\u00ECnh kh\u00F4ng t\u1ED3n t\u1EA1i
10093=Cấu hình mô hình này được tham chiếu bởi agent {0} và không thể xóa 10093=C\u1EA5u h\u00ECnh m\u00F4 h\u00ECnh n\u00E0y \u0111\u01B0\u1EE3c tham chi\u1EBFu b\u1EDFi agent {0} v\u00E0 kh\u00F4ng th\u1EC3 x\u00F3a
10094=Mô hình LLM này được tham chiếu bởi cấu hình nhận dạng ý định và không thể xóa 10094=M\u00F4 h\u00ECnh LLM n\u00E0y \u0111\u01B0\u1EE3c tham chi\u1EBFu b\u1EDFi c\u1EA5u h\u00ECnh nh\u1EADn d\u1EA1ng \u00FD \u0111\u1ECBnh v\u00E0 kh\u00F4ng th\u1EC3 x\u00F3a
10095=Thao tác máy chủ không hợp lệ 10095=Thao t\u00E1c m\u00E1y ch\u1EE7 kh\u00F4ng h\u1EE3p l\u1EC7
10096=Địa chỉ WebSocket máy chủ chưa được cấu hình 10096=\u0110\u1ECBa ch\u1EC9 WebSocket m\u00E1y ch\u1EE7 ch\u01B0a \u0111\u01B0\u1EE3c c\u1EA5u h\u00ECnh
10097=Địa chỉ WebSocket mục tiêu không tồn tại 10097=\u0110\u1ECBa ch\u1EC9 WebSocket m\u1EE5c ti\u00EAu kh\u00F4ng t\u1ED3n t\u1EA1i
10098=Danh sách địa chỉ WebSocket không thể để trống 10098=Danh s\u00E1ch \u0111\u1ECBa ch\u1EC9 WebSocket kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10099=Địa chỉ WebSocket không thể sử dụng localhost hoc 127.0.0.1 10099=\u0110\u1ECBa ch\u1EC9 WebSocket kh\u00F4ng th\u1EC3 s\u1EED d\u1EE5ng localhost ho\u1EB7c 127.0.0.1
10100=Định dạng địa chỉ WebSocket không chính xác 10100=\u0110\u1ECBnh d\u1EA1ng \u0111\u1ECBa ch\u1EC9 WebSocket kh\u00F4ng ch\u00EDnh x\u00E1c
10101=Kim tra kết nối WebSocket thất bại 10101=Ki\u1EC3m tra k\u1EBFt n\u1ED1i WebSocket th\u1EA5t b\u1EA1i
10102=Địa chỉ OTA không thể để trống 10102=\u0110\u1ECBa ch\u1EC9 OTA kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10103=Địa chỉ OTA không thể sử dụng localhost hoc 127.0.0.1 10103=\u0110\u1ECBa ch\u1EC9 OTA kh\u00F4ng th\u1EC3 s\u1EED d\u1EE5ng localhost ho\u1EB7c 127.0.0.1
10104=Địa chỉ OTA phải bắt đầu bằng http hoc https 10104=\u0110\u1ECBa ch\u1EC9 OTA ph\u1EA3i b\u1EAFt \u0111\u1EA7u b\u1EB1ng http ho\u1EB7c https
10105=Địa chỉ OTA phải kết thúc bằng /ota/ 10105=\u0110\u1ECBa ch\u1EC9 OTA ph\u1EA3i k\u1EBFt th\u00FAc b\u1EB1ng /ota/
10106=Truy cp giao din OTA thất bại 10106=Truy c\u1EADp giao di\u1EC7n OTA th\u1EA5t b\u1EA1i
10107=Định dạng nội dung trả về giao din OTA không chính xác 10107=\u0110\u1ECBnh d\u1EA1ng n\u1ED9i dung tr\u1EA3 v\u1EC1 giao di\u1EC7n OTA kh\u00F4ng ch\u00EDnh x\u00E1c
10108=Xác thực giao din OTA thất bại 10108=X\u00E1c th\u1EF1c giao di\u1EC7n OTA th\u1EA5t b\u1EA1i
10109=Địa chỉ MCP không thể để trống 10109=\u0110\u1ECBa ch\u1EC9 MCP kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10110=Địa chỉ MCP không thể sử dụng localhost hoc 127.0.0.1 10110=\u0110\u1ECBa ch\u1EC9 MCP kh\u00F4ng th\u1EC3 s\u1EED d\u1EE5ng localhost ho\u1EB7c 127.0.0.1
10111=Không phải địa chỉ MCP hợp lệ 10111=Kh\u00F4ng ph\u1EA3i \u0111\u1ECBa ch\u1EC9 MCP h\u1EE3p l\u1EC7
10112=Truy cp giao din MCP thất bại 10112=Truy c\u1EADp giao di\u1EC7n MCP th\u1EA5t b\u1EA1i
10113=Định dạng nội dung trả về giao din MCP không chính xác 10113=\u0110\u1ECBnh d\u1EA1ng n\u1ED9i dung tr\u1EA3 v\u1EC1 giao di\u1EC7n MCP kh\u00F4ng ch\u00EDnh x\u00E1c
10114=Xác thực giao din MCP thất bại 10114=X\u00E1c th\u1EF1c giao di\u1EC7n MCP th\u1EA5t b\u1EA1i
10115=Địa chỉ giao diện dấu giọng nói không thể để trống 10115=\u0110\u1ECBa ch\u1EC9 giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10116=Địa chỉ giao diện dấu giọng nói không thể sử dụng localhost hoc 127.0.0.1 10116=\u0110\u1ECBa ch\u1EC9 giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i kh\u00F4ng th\u1EC3 s\u1EED d\u1EE5ng localhost ho\u1EB7c 127.0.0.1
10117=Không phải địa chỉ giao diện dấu giọng nói hợp lệ 10117=Kh\u00F4ng ph\u1EA3i \u0111\u1ECBa ch\u1EC9 giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i h\u1EE3p l\u1EC7
10118=Địa chỉ giao diện dấu giọng nói phải bắt đầu bằng http hoc https 10118=\u0110\u1ECBa ch\u1EC9 giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i ph\u1EA3i b\u1EAFt \u0111\u1EA7u b\u1EB1ng http ho\u1EB7c https
10119=Truy cp giao diện dấu giọng nói thất bại 10119=Truy c\u1EADp giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i th\u1EA5t b\u1EA1i
10120=Định dạng nội dung trả về giao diện dấu giọng nói không chính xác 10120=\u0110\u1ECBnh d\u1EA1ng n\u1ED9i dung tr\u1EA3 v\u1EC1 giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i kh\u00F4ng ch\u00EDnh x\u00E1c
10121=Xác thực giao diện dấu giọng nói thất bại 10121=X\u00E1c th\u1EF1c giao di\u1EC7n d\u1EA5u gi\u1ECDng n\u00F3i th\u1EA5t b\u1EA1i
10122=Bí mật MQTT không thể để trống 10122=B\u00ED m\u1EADt MQTT kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10123=Bí mật MQTT của bạn không an toàn, cần ít nhất 8 ký tự và phải chứa cả chữ hoa và chữ thường 10123=B\u00ED m\u1EADt MQTT c\u1EE7a b\u1EA1n kh\u00F4ng an to\u00E0n, c\u1EA7n \u00EDt nh\u1EA5t 8 k\u00FD t\u1EF1 v\u00E0 ph\u1EA3i ch\u1EE9a c\u1EA3 ch\u1EEF hoa v\u00E0 ch\u1EEF th\u01B0\u1EDDng
10124=Bí mật MQTT của bạn không an toàn, bí mật MQTT phải chứa cả chữ hoa và chữ thường 10124=B\u00ED m\u1EADt MQTT c\u1EE7a b\u1EA1n kh\u00F4ng an to\u00E0n, b\u00ED m\u1EADt MQTT ph\u1EA3i ch\u1EE9a c\u1EA3 ch\u1EEF hoa v\u00E0 ch\u1EEF th\u01B0\u1EDDng
10125=Bí mật MQTT của bạn chứa mật khẩu yếu 10125=B\u00ED m\u1EADt MQTT c\u1EE7a b\u1EA1n ch\u1EE9a m\u1EADt kh\u1EA9u y\u1EBFu
10128=Nhãn từ điển bị trùng lặp 10128=Nh\u00E3n t\u1EEB \u0111i\u1EC3n b\u1ECB tr\u00F9ng l\u1EB7p
10129=Khóa SM2 chưa được cấu hình 10129=Kh\u00F3a SM2 ch\u01B0a \u0111\u01B0\u1EE3c c\u1EA5u h\u00ECnh
10130=Giải mã SM2 thất bại 10130=Gi\u1EA3i m\u00E3 SM2 th\u1EA5t b\u1EA1i
10131=modelType và provideCode không thể để trống 10131=modelType v\u00E0 provideCode kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10132=Không có quyền xem lịch sử trò chuyện của agent này 10132=Kh\u00F4ng c\u00F3 quy\u1EC1n xem l\u1ECBch s\u1EED tr\u00F2 chuy\u1EC7n c\u1EE7a agent n\u00E0y
10133=ID phiên không thể để trống 10133=ID phi\u00EAn kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10134=ID agent không thể để trống 10134=ID agent kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10135=Tải xuống lịch sử trò chuyện thất bại 10135=T\u1EA3i xu\u1ED1ng l\u1ECBch s\u1EED tr\u00F2 chuy\u1EC7n th\u1EA5t b\u1EA1i
10136=Liên kết tải xuống đã hết hạn hoặc không hợp lệ 10136=Li\u00EAn k\u1EBFt t\u1EA3i xu\u1ED1ng \u0111\u00E3 h\u1EBFt h\u1EA1n ho\u1EB7c kh\u00F4ng h\u1EE3p l\u1EC7
10137=Liên kết tải xuống không hợp lệ 10137=Li\u00EAn k\u1EBFt t\u1EA3i xu\u1ED1ng kh\u00F4ng h\u1EE3p l\u1EC7
10138=Người dùng 10138=Ng\u01B0\u1EDDi d\u00F9ng
10139=Agent 10139=Agent
10140=Tệp âm thanh không thể để trống 10140=T\u1EC7p \u00E2m thanh kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10141=Chỉ hỗ trợ tệp âm thanh 10141=Ch\u1EC9 h\u1ED7 tr\u1EE3 t\u1EC7p \u00E2m thanh
10142=Kích thước tệp âm thanh không thể vượt quá 10MB 10142=K\u00EDch th\u01B0\u1EDBc t\u1EC7p \u00E2m thanh kh\u00F4ng th\u1EC3 v\u01B0\u1EE3t qu\u00E1 10MB
10143=Tải lên thất bại 10143=T\u1EA3i l\u00EAn th\u1EA5t b\u1EA1i
10144=Bn ghi nhân bản giọng nói không tồn tại 10144=B\u1EA3n ghi nh\u00E2n b\u1EA3n gi\u1ECDng n\u00F3i kh\u00F4ng t\u1ED3n t\u1EA1i
10145=Thông tin tài nguyên giọng nói không thể để trống 10145=Th\u00F4ng tin t\u00E0i nguy\u00EAn gi\u1ECDng n\u00F3i kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10146=Tên nền tảng TTS không thể để trống 10146=T\u00EAn n\u1EC1n t\u1EA3ng TTS kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10147=ID giọng nói không thể để trống 10147=ID gi\u1ECDng n\u00F3i kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10148=Tài khoản chủ sở hữu không thể để trống 10148=T\u00E0i kho\u1EA3n ch\u1EE7 s\u1EDF h\u1EEFu kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10149=ID tài nguyên giọng nói cần xóa không thể để trống 10149=ID t\u00E0i nguy\u00EAn gi\u1ECDng n\u00F3i c\u1EA7n x\u00F3a kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10150=Bạn không có quyền thao tác bản ghi này 10150=B\u1EA1n kh\u00F4ng c\u00F3 quy\u1EC1n thao t\u00E1c b\u1EA3n ghi n\u00E0y
10151=Vui lòng tải lên tệp âm thanh trước 10151=Vui l\u00F2ng t\u1EA3i l\u00EAn t\u1EC7p \u00E2m thanh tr\u01B0\u1EDBc
10152=Không tìm thấy cấu hình mô hình 10152=Kh\u00F4ng t\u00ECm th\u1EA5y c\u1EA5u h\u00ECnh m\u00F4 h\u00ECnh
10153=Không tìm thấy loại mô hình 10153=Kh\u00F4ng t\u00ECm th\u1EA5y lo\u1EA1i m\u00F4 h\u00ECnh
10154=Đào tạo thất bại: {0} 10154=\u0110\u00E0o t\u1EA1o th\u1EA5t b\u1EA1i: {0}
10155=Thiếu cấu hình Huoshan Engine 10155=Thi\u1EBFu c\u1EA5u h\u00ECnh Huoshan Engine
10156=Lỗi định dạng phản hồi, thiếu trường BaseResp 10156=L\u1ED7i \u0111\u1ECBnh d\u1EA1ng ph\u1EA3n h\u1ED3i, thi\u1EBFu tr\u01B0\u1EDDng BaseResp
10157=Yêu cầu thất bại 10157=Y\u00EAu c\u1EA7u th\u1EA5t b\u1EA1i
10158=Nhân bản âm sắc: 10158=Nh\u00E2n b\u1EA3n \u00E2m s\u1EAFc:
10159=ID giọng nói đã tồn tại 10159=ID gi\u1ECDng n\u00F3i \u0111\u00E3 t\u1ED3n t\u1EA1i
10160=Lỗi định dạng ID giọng nói Huoshan Engine, phải bắt đầu bằng S_ 10160=L\u1ED7i \u0111\u1ECBnh d\u1EA1ng ID gi\u1ECDng n\u00F3i Huoshan Engine, ph\u1EA3i b\u1EAFt \u0111\u1EA7u b\u1EB1ng S_
10161=Địa chỉ Mac đã tồn tại 10161=\u0110\u1ECBa ch\u1EC9 Mac \u0111\u00E3 t\u1ED3n t\u1EA1i
10162=Nhà cung cấp mô hình không tồn tại 10162=Nh\u00E0 cung c\u1EA5p m\u00F4 h\u00ECnh kh\u00F4ng t\u1ED3n t\u1EA1i
10163=Bn ghi cơ sở kiến thức không tồn tại 10163=B\u1EA3n ghi c\u01A1 s\u1EDF ki\u1EBFn th\u1EE9c kh\u00F4ng t\u1ED3n t\u1EA1i
10164=Không tìm thấy cấu hình RAG 10164=Kh\u00F4ng t\u00ECm th\u1EA5y c\u1EA5u h\u00ECnh RAG
10165=Lỗi loại cấu hình RAG 10165=L\u1ED7i lo\u1EA1i c\u1EA5u h\u00ECnh RAG
10166=Không tìm thấy cấu hình RAG mặc định 10166=Kh\u00F4ng t\u00ECm th\u1EA5y c\u1EA5u h\u00ECnh RAG m\u1EB7c \u0111\u1ECBnh
10167=Gi API RAG thất bại: {0} 10167=G\u1ECDi API RAG th\u1EA5t b\u1EA1i: {0}
10168=Tải lên tệp thất bại 10168=T\u1EA3i l\u00EAn t\u1EC7p th\u1EA5t b\u1EA1i
10169=Không có quyền thao tác cơ sở kiến thức này 10169=Kh\u00F4ng c\u00F3 quy\u1EC1n thao t\u00E1c c\u01A1 s\u1EDF ki\u1EBFn th\u1EE9c n\u00E0y
10170=Tên cơ sở kiến thức đã tồn tại 10170=T\u00EAn c\u01A1 s\u1EDF ki\u1EBFn th\u1EE9c \u0111\u00E3 t\u1ED3n t\u1EA1i
10171=Cấu hình RAG base_url không thể để trống 10171=C\u1EA5u h\u00ECnh RAG base_url kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10172=Cấu hình RAG api_key không thể để trống 10172=C\u1EA5u h\u00ECnh RAG api_key kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10173=Cấu hình RAG api_key không thể chứa trình giữ chỗ, vui lòng thay thế bằng khóa API thực tế 10173=C\u1EA5u h\u00ECnh RAG api_key kh\u00F4ng th\u1EC3 ch\u1EE9a tr\u00ECnh gi\u1EEF ch\u1ED7, vui l\u00F2ng thay th\u1EBF b\u1EB1ng kh\u00F3a API th\u1EF1c t\u1EBF
10174=Lỗi định dạng cấu hình RAG base_url, phải bắt đầu bằng http hoc https 10174=L\u1ED7i \u0111\u1ECBnh d\u1EA1ng c\u1EA5u h\u00ECnh RAG base_url, ph\u1EA3i b\u1EAFt \u0111\u1EA7u b\u1EB1ng http ho\u1EB7c https
10175=Địa chỉ Mac không thể để trống 10175=\u0110\u1ECBa ch\u1EC9 Mac kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10176=Cấu hình RAG dataset_id không thể để trống 10176=C\u1EA5u h\u00ECnh RAG dataset_id kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10177=Cấu hình RAG model_id không thể để trống 10177=C\u1EA5u h\u00ECnh RAG model_id kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10178=Cấu hình RAG dataset_id và model_id không thể để trống 10178=C\u1EA5u h\u00ECnh RAG dataset_id v\u00E0 model_id kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10179=Tên tệp không thể để trống 10179=T\u00EAn t\u1EC7p kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10180=Ni dung tệp không thể để trống 10180=N\u1ED9i dung t\u1EC7p kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10181=Tên nhân bản giọng nói không thể để trống 10181=T\u00EAn nh\u00E2n b\u1EA3n gi\u1ECDng n\u00F3i kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10182=Không tìm thấy âm thanh nhân bản giọng nói 10182=Kh\u00F4ng t\u00ECm th\u1EA5y \u00E2m thanh nh\u00E2n b\u1EA3n gi\u1ECDng n\u00F3i
10183=Không tìm thấy mẫu agent mặc định 10183=Kh\u00F4ng t\u00ECm th\u1EA5y m\u1EABu agent m\u1EB7c \u0111\u1ECBnh
10184=Loại bộ chuyển đổi không được hỗ trợ 10184=Lo\u1EA1i b\u1ED9 chuy\u1EC3n \u0111\u1ED5i kh\u00F4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3
10185=Kim tra cấu hình RAG thất bại 10185=Ki\u1EC3m tra c\u1EA5u h\u00ECnh RAG th\u1EA5t b\u1EA1i
10186=Tạo bộ chuyển đổi thất bại 10186=T\u1EA1o b\u1ED9 chuy\u1EC3n \u0111\u1ED5i th\u1EA5t b\u1EA1i
10187=Khởi tạo bộ chuyển đổi thất bại 10187=Kh\u1EDFi t\u1EA1o b\u1ED9 chuy\u1EC3n \u0111\u1ED5i th\u1EA5t b\u1EA1i
10188=Kim tra kết nối bộ chuyển đổi thất bại 10188=Ki\u1EC3m tra k\u1EBFt n\u1ED1i b\u1ED9 chuy\u1EC3n \u0111\u1ED5i th\u1EA5t b\u1EA1i
10189=Thao tác bộ chuyển đổi thất bại 10189=Thao t\u00E1c b\u1ED9 chuy\u1EC3n \u0111\u1ED5i th\u1EA5t b\u1EA1i
10190=Không tìm thấy bộ chuyển đổi 10190=Kh\u00F4ng t\u00ECm th\u1EA5y b\u1ED9 chuy\u1EC3n \u0111\u1ED5i
10191=Lỗi bộ nhớ đệm bộ chuyển đổi 10191=L\u1ED7i b\u1ED9 nh\u1EDB \u0111\u1EC7m b\u1ED9 chuy\u1EC3n \u0111\u1ED5i
10192=Không tìm thấy loại bộ chuyển đổi 10192=Kh\u00F4ng t\u00ECm th\u1EA5y lo\u1EA1i b\u1ED9 chuy\u1EC3n \u0111\u1ED5i
10193=ID thi\u1EBFt b\u1ECB kh\u00F4ng th\u1EC3 tr\u1ED1ng
10194=Kh\u00F4ng t\u00ECm th\u1EA5y thi\u1EBFt b\u1ECB ho\u1EB7c thi\u1EBFt b\u1ECB kh\u00F4ng tr\u1EF1c tuy\u1EBFn
10195=S\u1ED1 l\u1EA7n t\u1EA3i l\u00EAn OTA v\u01B0\u1EE3t qu\u00E1 gi\u1EDBi h\u1EA1n
@@ -198,4 +198,7 @@
10189=\u9002\u914D\u5668\u64CD\u4F5C\u5931\u8D25 10189=\u9002\u914D\u5668\u64CD\u4F5C\u5931\u8D25
10190=\u9002\u914D\u5668\u672A\u627E\u5230 10190=\u9002\u914D\u5668\u672A\u627E\u5230
10191=\u9002\u914D\u5668\u7F13\u5B58\u9519\u8BEF 10191=\u9002\u914D\u5668\u7F13\u5B58\u9519\u8BEF
10192=\u9002\u914D\u5668\u7C7B\u578B\u672A\u627E\u5230 10192=\u9002\u914D\u5668\u7C7B\u578B\u672A\u627E\u5230
10193=\u8BBE\u5907ID\u4E0D\u80FD\u4E3A\u7A7A
10194=\u8BBE\u5907\u4E0D\u5B58\u5728\u6216\u4E0D\u5728\u7EBF
10195=OTA\u4E0A\u4F20\u6B21\u6570\u8D85\u8FC7\u9650\u5236
@@ -164,7 +164,7 @@
10155=\u706B\u5C71\u5F15\u64CE\u7F3A\u5C11appid\u6216access_token 10155=\u706B\u5C71\u5F15\u64CE\u7F3A\u5C11appid\u6216access_token
10156=\u97FF\u61C9\u683C\u5F0F\u932F\u8AA4\uFF0C\u7F3A\u5C11BaseResp\u5B57\u6BB5 10156=\u97FF\u61C9\u683C\u5F0F\u932F\u8AA4\uFF0C\u7F3A\u5C11BaseResp\u5B57\u6BB5
10157=\u8ACB\u6C42\u5931\u6557 10157=\u8ACB\u6C42\u5931\u6557
10158=\u514b\u9686\u97f3\u8272: 10158=\u514B\u9686\u97F3\u8272:
10159=\u97F3\u8272ID\u5DF2\u5B58\u5728 10159=\u97F3\u8272ID\u5DF2\u5B58\u5728
10160=\u706B\u5C71\u5F15\u64CE\u97F3\u8272ID\u683C\u5F0F\u932F\u8AA4\uFF0C\u5FC5\u9808\u4EE5S_\u958B\u982D 10160=\u706B\u5C71\u5F15\u64CE\u97F3\u8272ID\u683C\u5F0F\u932F\u8AA4\uFF0C\u5FC5\u9808\u4EE5S_\u958B\u982D
10161=Mac\u5730\u5740\u5DF2\u5B58\u5728 10161=Mac\u5730\u5740\u5DF2\u5B58\u5728
@@ -173,23 +173,23 @@
10164=RAG\u914D\u7F6E\u672A\u627E\u5230 10164=RAG\u914D\u7F6E\u672A\u627E\u5230
10165=RAG\u914D\u7F6E\u985E\u578B\u932F\u8AA4 10165=RAG\u914D\u7F6E\u985E\u578B\u932F\u8AA4
10166=\u9810\u8A2DRAG\u914D\u7F6E\u672A\u627E\u5230 10166=\u9810\u8A2DRAG\u914D\u7F6E\u672A\u627E\u5230
10167=\u0052\u0041\u0047\u8abf\u7528\u5931\u6557\uFF0C{0} 10167=RAG\u8ABF\u7528\u5931\u6557\uFF0C{0}
10168=\u4E0A\u50B3\u6587\u4EF6\u5931\u6557 10168=\u4E0A\u50B3\u6587\u4EF6\u5931\u6557
10169=\u60A8\u6C92\u6709\u6B0A\u9650\u64CD\u4F5C\u8A72\u8A18\u9304 10169=\u60A8\u6C92\u6709\u6B0A\u9650\u64CD\u4F5C\u8A72\u8A18\u9304
10170=\u77E5\u8B58\u5EAB\u540D\u7A31\u91CD\u8907 10170=\u77E5\u8B58\u5EAB\u540D\u7A31\u91CD\u8907
10171=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684base_url\u4E0D\u80FD\u4E3A\u7A7A 10171=RAG\u914D\u7F6E\u4F53\u7684base_url\u4E0D\u80FD\u4E3A\u7A7A
10172=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684api_key\u4E0D\u80FD\u4E3A\u7A7A 10172=RAG\u914D\u7F6E\u4F53\u7684api_key\u4E0D\u80FD\u4E3A\u7A7A
10173=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684api_key\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u8BF7\u66F4\u6362\u4E3A\u5728\u53D6\u7684API\u53C2\u6570 10173=RAG\u914D\u7F6E\u4F53\u7684api_key\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u8BF7\u66F4\u6362\u4E3A\u5728\u53D6\u7684API\u53C2\u6570
10174=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684base_url\u683C\u5F0F\u9519\u8BEF\uFF0C\u5FC5\u987B\u4EE5http\u6216https\u5F00\u5934 10174=RAG\u914D\u7F6E\u4F53\u7684base_url\u683C\u5F0F\u9519\u8BEF\uFF0C\u5FC5\u987B\u4EE5http\u6216https\u5F00\u5934
10175=mac\u5730\u5740\u4E0D\u80FD\u4E3A\u7A7A 10175=mac\u5730\u5740\u4E0D\u80FD\u4E3A\u7A7A
10176=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684dataset_id\u4E0D\u80FD\u4E3A\u7A7A 10176=RAG\u914D\u7F6E\u4F53\u7684dataset_id\u4E0D\u80FD\u4E3A\u7A7A
10177=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684model_id\u4E0D\u80FD\u4E3A\u7A7A 10177=RAG\u914D\u7F6E\u4F53\u7684model_id\u4E0D\u80FD\u4E3A\u7A7A
10178=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684dataset_id\u548Cmodel_id\u4E0D\u80FD\u4E3A\u7A7A 10178=RAG\u914D\u7F6E\u4F53\u7684dataset_id\u548Cmodel_id\u4E0D\u80FD\u4E3A\u7A7A
10179=\u6587\u4ef6\u540d\u7a31\u4e0d\u80fd\u70ba\u7a7a 10179=\u6587\u4EF6\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A
10180=\u6587\u4ef6\u5185\u5bb9\u4e0d\u80fd\u70ba\u7a7a 10180=\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u70BA\u7A7A
10181=\u97f3\u8272\u514b\u9686\u540d\u7a31\u4e0d\u80fd\u70ba\u7a7a 10181=\u97F3\u8272\u514B\u9686\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A
10182=\u97f3\u8272\u514b\u9686\u97f3\u983b\u4e0d\u5b58\u5728 10182=\u97F3\u8272\u514B\u9686\u97F3\u983B\u4E0D\u5B58\u5728
10183=\u9ed8\u8ba4\u667a\u80fd\u4f53\u672a\u627e\u5230 10183=\u9ED8\u8BA4\u667A\u80FD\u4F53\u672A\u627E\u5230
10184=\u4E0D\u652F\u6301\u7684\u9002\u914D\u5668\u985E\u578B 10184=\u4E0D\u652F\u6301\u7684\u9002\u914D\u5668\u985E\u578B
10185=RAG\u914D\u7F6E\u9A57\u8B49\u5931\u6557 10185=RAG\u914D\u7F6E\u9A57\u8B49\u5931\u6557
10186=\u9002\u914D\u5668\u5275\u5EFA\u5931\u6557 10186=\u9002\u914D\u5668\u5275\u5EFA\u5931\u6557
@@ -198,4 +198,7 @@
10189=\u9002\u914D\u5668\u64CD\u4F5C\u5931\u6557 10189=\u9002\u914D\u5668\u64CD\u4F5C\u5931\u6557
10190=\u9002\u914D\u5668\u672A\u627E\u5230 10190=\u9002\u914D\u5668\u672A\u627E\u5230
10191=\u9002\u914D\u5668\u7F13\u5B58\u932F\u8AA4 10191=\u9002\u914D\u5668\u7F13\u5B58\u932F\u8AA4
10192=\u9002\u914D\u5668\u985E\u578B\u672A\u627E\u5230 10192=\u9002\u914D\u5668\u985E\u578B\u672A\u627E\u5230
10193=\u8A2D\u5099ID\u4E0D\u80FD\u4E3A\u7A7A
10194=\u8A2D\u5099\u4E0D\u5B58\u5728\u6216\u672A\u5728\u7DDA
10195=OTA\u4E0A\u4F20\u6B21\u6578\u8D85\u904E\u9650\u5236
@@ -22,13 +22,18 @@
created_at, updated_at created_at, updated_at
</sql> </sql>
<delete id="deleteAudioByAgentId"> <select id="getAudioIdsByAgentId" resultType="java.lang.String">
DELETE FROM ai_agent_chat_audio SELECT DISTINCT audio_id
WHERE id IN ( FROM ai_agent_chat_history
SELECT audio_id WHERE agent_id = #{agentId} AND audio_id IS NOT NULL
FROM ai_agent_chat_history </select>
WHERE agent_id = #{agentId}
) <delete id="deleteAudioByIds">
DELETE FROM ai_agent_chat_audio
WHERE id IN
<foreach collection="audioIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete> </delete>
<update id="deleteAudioIdByAgentId"> <update id="deleteAudioIdByAgentId">
@@ -15,4 +15,23 @@
FROM sys_dict_type FROM sys_dict_type
WHERE id = #{dictTypeId} WHERE id = #{dictTypeId}
</select> </select>
<select id="getDictTypesByIdList" resultType="java.lang.String">
SELECT
dict_type
FROM
sys_dict_type
WHERE
id IN (
SELECT
dict_type_id
FROM
sys_dict_data
WHERE
id IN
<foreach collection="dictDataIdList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
)
</select>
</mapper> </mapper>
@@ -235,7 +235,7 @@ function showAbout() {
title: t('settings.aboutApp', { appName: import.meta.env.VITE_APP_TITLE }), title: t('settings.aboutApp', { appName: import.meta.env.VITE_APP_TITLE }),
content: t('settings.aboutContent', { content: t('settings.aboutContent', {
appName: import.meta.env.VITE_APP_TITLE, appName: import.meta.env.VITE_APP_TITLE,
version: '0.8.10' version: '0.9.1'
}), }),
showCancel: false, showCancel: false,
confirmText: t('common.confirm'), confirmText: t('common.confirm'),
@@ -0,0 +1,89 @@
# 语音盒子主题自定义
## 项目概述
本目录包含从 [xiaozhi-assets-generator](https://github.com/xinnan-tech/xiaozhi-assets-generator) 项目打包的静态文件,用于语音盒子主题的在线自定义与生成。用户可以通过此工具配置唤醒词、字体、表情和聊天背景等元素,并导出为 `assets.bin` 文件。
## 目录结构
```
generator/
├── assets/ # 构建生成的资源文件
│ ├── ft_render-ByO_jG18.js
│ ├── index-CYcyz9xb.js
│ └── index-NXxBVrod.css
├── static/ # 静态资源目录
│ ├── charsets/ # 字符集文件
│ │ ├── deepseek.txt
│ │ ├── gb2312.txt
│ │ ├── latin1.txt
│ │ └── qwen18409.txt
│ ├── fonts/ # 字体资源
│ │ ├── font_noto_qwen_14_1.bin
│ │ ├── font_noto_qwen_16_4.bin
│ │ ├── font_noto_qwen_20_4.bin
│ │ ├── font_noto_qwen_30_4.bin
│ │ ├── font_puhui_deepseek_14_1.bin
│ │ ├── font_puhui_deepseek_16_4.bin
│ │ ├── font_puhui_deepseek_20_4.bin
│ │ ├── font_puhui_deepseek_30_4.bin
│ │ ├── noto_qwen.ttf
│ │ └── puhui_deepseek.ttf
│ ├── multinet_model/ # 自定义唤醒词模型
│ │ ├── fst/
│ │ ├── mn6_cn/
│ │ ├── mn6_en/
│ │ ├── mn7_cn/
│ │ └── mn7_en/
│ ├── twemoji32/ # 32x32 表情图片
│ ├── twemoji64/ # 64x64 表情图片
│ ├── wakenet_model/ # 预设唤醒词模型
│ └── README.md # 静态资源说明
├── index.html # 主页面
└── README.md # 项目说明文档
```
## 主要功能
### 1. 芯片与屏幕配置
- 支持多种芯片型号:ESP32-S3、ESP32-C3、ESP32-P4、ESP32-C6
- 灵活的屏幕分辨率设置
- 支持 RGB565 颜色格式
### 2. 唤醒词配置
- **预设唤醒词**:基于不同芯片支持的 WakeNet 模型
- **自定义唤醒词**:支持中文和英文命令词,可配置阈值和超时时间
### 3. 字体配置
- 预设多种字体:阿里巴巴普惠体、Noto Qwen 等
- 支持上传自定义 TTF/WOFF 字体文件
- 可配置字号和颜色深度(bpp
### 4. 表情集合
- 提供 21 种基础表情的预设方案(32x32 和 64x64 两种尺寸)
- 支持自定义表情上传
### 5. 聊天背景
- 支持浅色/深色模式切换
- 可配置纯色背景或图片背景
- 自动适配屏幕分辨率
## 使用方法
1. 以服务方式启动 `index.html` 文件
2. 选择芯片型号和屏幕配置
3. 通过不同标签页配置主题元素
4. 点击生成按钮查看资源清单
5. 确认后生成并下载 `assets.bin` 文件
## 技术说明
- 构建后的静态资源位于 `assets/` 目录
- 原始模型和资源文件位于 `static/` 目录
- 支持离线使用,无需额外依赖
## 注意事项
- 本工具为离线使用设计,所有资源已包含在目录中
- 生成的 `assets.bin` 文件需要与语音盒子硬件配合使用
- 自定义资源需注意文件格式和大小限制,以确保兼容
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="./favicon.ico">
<title>Xiaozhi AI Customization</title>
<script type="module" crossorigin src="./assets/index-B8r0c7xg.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-CrIJdTCK.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
@@ -0,0 +1,20 @@
### Alibaba Puhui Fonts
https://www.alibabafonts.com/
### twemoji32 / twemoji64
https://github.com/twitter/twemoji
### Wakenet models
The wakenet model weights are copied from the ESP-SR repo.
https://github.com/espressif/esp-sr/tree/master/model/wakenet_model
### Multinet models
The multinet model weights are copied from the ESP-SR repo.
https://github.com/espressif/esp-sr/tree/master/model/multinet_model
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,190 @@
!
"
#
$
%
&
'
(
)
*
+
,
-
.
/
0
1
2
3
4
5
6
7
8
9
:
;
<
=
>
?
@
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
[
\
]
^
_
`
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
{
|
}
~
¡
¢
£
¤
¥
¦
§
¨
©
ª
«
¬
­
®
¯
°
±
²
³
´
µ
·
¸
¹
º
»
¼
½
¾
¿
À
Á
Â
Ã
Ä
Å
Æ
Ç
È
É
Ê
Ë
Ì
Í
Î
Ï
Ð
Ñ
Ò
Ó
Ô
Õ
Ö
×
Ø
Ù
Ú
Û
Ü
Ý
Þ
ß
à
á
â
ã
ä
å
æ
ç
è
é
ê
ë
ì
í
î
ï
ð
ñ
ò
ó
ô
õ
ö
÷
ø
ù
ú
û
ü
ý
þ
ÿ
@@ -0,0 +1,313 @@
1,ba xiao shi hou guan ji
2,ba xiao shi hou kai ji
3,bi kai wo chui
4,chao qiang feng su
5,chao zhe wo chui
6,chou shi mo shi
7,chu shi mo shi
8,da kai bai feng
9,da kai bing xiang
10,da kai chou shi mo shi
11,da kai chu jun
12,da kai chu shi mo shi
13,da kai dian fu re
14,da kai ding chu feng
15,da kai ding xiang chu wu
16,da kai er tong fang leng feng
17,da kai fang zhi chui
18,da kai feng ji
19,da kai fu re
20,da kai gan zao
21,da kai huan ji tiao wen
22,da kai jia shi
23,da kai jing hua
24,da kai jun yun feng
25,da kai kong tiao
26,da kai kong tiao deng guang
27,da kai mu ying feng
28,da kai mu qing feng
29,da kai pai feng mo shi
30,da kai pai qi
31,da kai pai qi mo shi
32,da kai qiang jing
33,da kai quan wu feng gan
34,da kai rou feng gan
35,da kai shang wu feng gan
36,da kai shang xia bai feng
37,da kai shang xia sao feng
38,da kai sen ba
39,da kai shu sheng
40,da kai shu shi feng
41,da kai shui mian
42,da kai si ji chu shi
43,da kai si ji chu shi mo shi
44,da kai song feng
45,da kai song feng mo shi
46,da kai wu feng gan
47,da kai xia wu feng gan
48,da kai xin feng
49,da kai xiu xian mo shi
50,da kai xuan zhuan song feng
51,da kai yi jian chu wu
52,da kai zhi leng
53,da kai zhi leng mo shi
54,da kai zhi re
55,da kai zhi re mo shi
56,da kai zhi kong wen
57,da kai zhi neng guan jia
58,da kai zhi neng sha jun
59,da kai zhi neng sheng dian
60,da kai zhi qing jie
61,da kai zhi wen gan
62,da kai zhuan shu wen du
63,da kai zi dong mo shi
64,da kai zuo you bai feng
65,da kai zuo you sao feng
66,di feng su
67,di su feng
68,di yi dian
69,dian fu re
70,tiao cheng zui leng
71,tiao cheng zui nuan
72,tiao da feng su
73,tiao da xin feng
74,tiao dao er shi ba du
75,tiao dao er shi du
76,tiao dao er shi er du
77,tiao dao er shi jiu du
78,tiao dao er shi liu du
79,tiao dao er shi qi du
80,tiao dao er shi san du
81,tiao dao er shi si du
82,tiao dao er shi wu du
83,tiao dao er shi yi du
84,tiao dao san shi du
85,tiao dao shi ba du
86,tiao dao shi jiu du
87,tiao dao shi liu du
88,tiao dao shi qi du
89,tiao dao zui leng
90,tiao dao zui nuan
91,tiao di ling dian wu du
92,tiao di wen du
93,tiao di yi du
94,tiao gao er shi du
95,tiao gao ling dian wu du
96,tiao gao wen du
97,tiao gao wu du
98,tiao gao yi du
99,tiao leng yi dian
100,tiao nuan
101,tiao nuan yi dian
102,tiao xiao feng su
103,tiao xiao xin feng
104,ding chu feng
105,ding shi wu rao
106,ding xiang chu wu
107,er tong fang leng feng
108,er dang xin feng
109,feng da yi dian
110,feng su da dian
111,feng su er dang
112,feng su san dang
113,feng su xiao dian
114,feng su yi dang
115,feng wang shang chui
116,feng wang xia chui
117,feng wang you chui
118,feng wang zhong jian chui
119,feng wang zuo chui
120,feng xiang shang chui
121,feng xiang xia chui
122,feng xiang you chui
123,feng xiang zhong jian chui
124,feng xiang zuo chui
125,feng xiao yi dian
126,gao feng su
127,gao su feng
128,guan bi bai feng
129,fang zhi chui
130,guan bi dian fu re
131,guan bi ding chu feng
132,guan bi ding xiang chu wu
133,guan bi er tong fang leng feng
134,guan bi fang zhi chui
135,guan bi feng ji
136,guan bi fu re
137,guan bi huan qi
138,guan bi huan qi mo shi
139,guan bi jia shi
140,guan bi jie neng
141,guan bi jun yun feng
142,guan bi kong tiao
143,guan bi kong tiao deng guang
144,guan bi mu ying feng
145,guan bi mu qing feng
146,guan bi qiang jing
147,guan bi quan wu feng gan
148,guan bi rou feng gan
149,guan bi sen ba
150,guan bi shang wu feng gan
151,guan bi shang xia bai feng
152,guan bi shang xia sao feng
153,guan bi shu sheng
154,guan bi shui mian
155,guan bi si ji chu shi
156,guan bi si ji chu shi mo shi
157,guan bi wu feng gan
158,guan bi xia wu feng gan
159,guan bi xin feng
160,guan bi xuan zhuan song feng
161,guan bi yi jian chu wu
162,guan bi zhi kong wen
163,guan bi zhi qing jie
164,guan bi zhong wen bao wen
165,guan bi zhuan xiang wen du er
166,guan bi zhuan xiang wen du san
167,guan bi zhuan xiang wen du yi
168,guan bi zuo you bai feng
169,guan bi zuo you sao feng
170,guan bi zhuan shu wen du
171,guan diao bai feng
172,guan diao dian fu re
173,guan diao ding chu feng
174,guan diao ding xiang chu wu
175,guan diao er tong fang leng feng
176,guan diao fang zhi chui
177,guan diao gan zao
178,guan diao huan qi
179,guan diao huan qi mo shi
180,guan diao jia shi
181,guan diao jing hua
182,guan diao jun yun feng
183,guan diao kong tiao
184,guan diao kong tiao deng guang
185,guan diao qiang jing
186,guan diao quan wu feng gan
187,guan diao rou feng gan
188,guan diao shang wu feng gan
189,guan diao shang xia bai feng
190,guan diao shu sheng
191,guan diao shui mian
192,guan diao si ji chu shi
193,guan diao si ji chu shi mo shi
194,guan diao wu feng gan
195,guan diao xia wu feng gan
196,guan diao xin feng
197,guan diao xuan zhuan song feng
198,guan diao yi jian chu wu
199,guan diao zhi kong wen
200,guan diao zhi qing jie
201,guan diao zuo you bai feng
202,guan kong tiao
203,guan shang xia bai feng
204,guan shang xia feng
205,guan xin feng
206,guan zuo you bai feng
207,guan zuo you feng
208,guan bi zhi neng sheng dian
209,jian xiao feng su
210,jian xiao pu tong xin feng
211,jian xiao xin feng
212,jiang di wen du
213,jing yin feng
214,jing yin feng su
215,jun yun feng
216,kai kong tiao
217,kai qi bai feng
218,kai qi chu shi
219,kai qi chu shi mo shi
220,kai qi kong tiao
221,kai qi pai feng mo shi
222,kai qi shang xia sao feng
223,kai qi tong feng
224,kai qi zhi leng
225,kai qi zhi leng mo shi
226,kai qi zhi re
227,kai qi zhi re mo shi
228,kai qi zuo you sao feng
229,kai shi chu wei
230,kong tiao da kai
231,kong tiao guan bi
232,kong tiao guan diao
233,kong tiao guan ji
234,kong tiao kai ji
235,kong tiao kai shi pei wang
236,kong tiao lian wang
237,kong tiao chong xin pei wang
238,kai xin feng
239,leng yi dian
240,nuan yi dian
241,pu tong xin feng
242,qi dong kong tiao
243,qiang jing xin feng
244,qiang li feng
245,qiang li feng su
246,qu shi mo shi
247,quan wu feng gan
248,rou feng gan
249,san dang xin feng
250,shang wu feng gan
251,shang xia bai feng
252,shang xia feng
253,sheng gao wen du
254,sheng dao zui gao
255,sheng dao zui gao wei zhi
256,si dang xin feng
257,si ji chu shi
258,si ji chu shi mo shi
259,song feng mo shi
260,tai leng le
261,tai re le
262,ting zhi shang xia bai feng
263,ting zhi shang xia sao feng
264,ting zhi bai feng
265,ting zhi zhuan shu wen du
266,ting zhi zuo you bai feng
267,ting zhi zuo you sao feng
268,xia wu feng gan
269,xin feng da dian
270,xin feng er dang
271,xin feng qiang jing
272,xin feng qiang jing dang
273,xin feng san dang
274,xin feng si dang
275,xin feng xiao dian
276,xin feng yi dang
277,xin feng zui da
278,xin feng zui da dang
279,xin feng zui xiao
280,xin feng zui xiao dang
281,xuan zhuan song feng
282,yi dang xin feng
283,you dian leng
284,you dian re
285,zai gao yi dian
286,zeng da feng su
287,zeng da xin feng
288,zhi leng mo shi
289,zhi re mo shi
290,zhi kong wen
291,zhi neng wu rao
292,zhi qing jie
293,zhong deng feng
294,zhong feng su
295,zhong su feng
296,zi dong
297,zi dong feng
298,zi dong feng su
299,zi dong mo shi
300,zui da feng
301,zui da feng su
302,zui da xin feng
303,zui xiao feng
304,zui xiao feng su
305,zui xiao xin feng
306,zuo you bai feng
307,zuo you feng
308,bang wo guan deng
309,bang wo kai deng
310,da kai dian deng
311,guan bi dian deng
312,xiao le xiao le
313,xiao xin xiao xin
@@ -0,0 +1,49 @@
1,TELL ME A JOKE,TfL Mm c qbK
2,SING A SONG,Sgl c Sel
3,PLAY NEWS CHANNEL,PLd NoZ paNcL
4,TURN ON MY SOUNDBOX,TkN nN Mi StNDBnKS
5,TURN OFF MY SOUNDBOX,TkN eF Mi StNDBnKS
5,TURN OF MY SOUNDBOX,TkN cV Mi StNDBnKS
6,HIGHEST VOLUME,hicST VnLYoM
7,LOWEST VOLUME,LbcST VnLYoM
8,INCREASE THE VOLUME,gNKRmS jc VnLYoM
9,DECREASE THE VOLUME,DgKRmS jc VnLYoM
10,TURN ON THE TV,TkN nN jc TmVm
11,TURN OFF THE TV,TkN eF jc TmVm
11,TURN OF THE TV,TkN cV jc TmVm
12,MAKE ME A TEA,MdK Mm c Tm
13,MAKE ME A COFFEE,MdK Mm c KnFm
14,TURN ON THE LIGHT,TkN nN jc LiT
15,TURN OFF THE LIGHT,TkN eF jc LiT
15,TURN OF THE LIGHT,TkN cV jc LiT
16,CHANGE THE COLOR TO RED,pdNq jc KcLk To RfD
17,CHANGE THE COLOR TO GREEN,pdNq jc KcLk To GRmN
18,TURN ON ALL THE LIGHTS,TkN nN eL jc LiTS
19,TURN OFF ALL THE LIGHTS,TkN eF eL jc LiTS
19,TURN OF ALL THE LIGHTS,TkN cV eL jc LiTS
20,TURN ON THE AIR CONDITIONER,TkN nN jc fR KcNDgscNk
21,TURN OFF THE AIR CONDITIONER,TkN eF jc fR KcNDgscNk
21,TURN OF THE AIR CONDITIONER,TkN cV jc fR KcNDgscNk
22,SET THE TEMPERATURE TO SIXTEEN DEGREES,SfT jc TfMPRcpk To SgKSTmN DgGRmZ
23,SET THE TEMPERATURE TO SEVENTEEN DEGREES,SfT jc TfMPRcpk To SfVcNTmN DgGRmZ
24,SET THE TEMPERATURE TO EIGHTEEN DEGREES,SfT jc TfMPRcpk To dTmN DgGRmZ
25,SET THE TEMPERATURE TO NINETEEN DEGREES,SfT jc TfMPRcpk To NiNTmN DgGRmZ
26,SET THE TEMPERATURE TO TWENTY DEGREES,SfT jc TfMPRcpk To TWfNTm DgGRmZ
27,SET THE TEMPERATURE TO TWENTY ONE DEGREES,SfT jc TfMPRcpk To TWfNTm WcN DgGRmZ
28,SET THE TEMPERATURE TO TWENTY TWO DEGREES,SfT jc TfMPRcpk To TWfNTm To DgGRmZ
29,SET THE TEMPERATURE TO TWENTY THREE DEGREES,SfT jc TfMPRcpk To TWfNTm vRm DgGRmZ
30,SET THE TEMPERATURE TO TWENTY FOUR DEGREES,SfT jc TfMPRcpk To TWfNTm FeR DgGRmZ
31,SET THE TEMPERATURE TO TWENTY FIVE DEGREES,SfT jc TfMPRcpk To TWfNTm FiV DgGRmZ
32,SET THE TEMPERATURE TO TWENTY SIX DEGREES,SfT jc TfMPRcpk To TWfNTm SgKS DgGRmZ
33,LOWEST FAN SPEED,LbcST FaN SPmD
34,MEDIUM FAN SPEED,MmDmcM FaN SPmD
35,HIGHEST FAN SPEED,hicST FaN SPmD
36,AUTO ADJUST THE FAN SPEED,eTb cqcST jc FaN SPmD
37,DECREASE THE FAN SPEED,DgKRmS jc FaN SPmD
38,INCREASE THE FAN SPEED,gNKRmS jc FaN SPmD
39,INCREASE THE TEMPERATURE,gNKRmS jc TfMPRcpk
40,DECREASE THE TEMPERATURE,DgKRmS jc TfMPRcpk
41,COOLING MODE,KoLgl MbD
42,HEATING MODE,hmTgl MbD
43,VENTILATION MODE,VfNTcLdscN MbD
44,DEHUMIDIFY MODE,DmhYoMgDcFi MbD
@@ -0,0 +1,2 @@
# (neural network type)_(model data version)_(lable1_detection windown length_threshold for 90%_threshold for 95%)_(lable2 ...)_...
MN6_v3_chinese_8_0.9_0.90
@@ -0,0 +1,408 @@
<blk> 0
<sos/eos> 0
<unk> 0
▁a -6.00389
▁ai -6.00389
▁an -6.00389
▁ang -6.00389
▁ao -6.00389
▁ba -6.00389
▁bai -6.00389
▁ban -6.00389
▁bang -6.00389
▁bao -6.00389
▁bei -6.00389
▁ben -6.00389
▁beng -6.00389
▁bi -6.00389
▁bian -6.00389
▁biao -6.00389
▁bie -6.00389
▁bin -6.00389
▁bing -6.00389
▁bo -6.00389
▁bu -6.00389
▁ca -6.00389
▁cai -6.00389
▁can -6.00389
▁cang -6.00389
▁cao -6.00389
▁ce -6.00389
▁cen -6.00389
▁ceng -6.00389
▁cha -6.00389
▁chai -6.00389
▁chan -6.00389
▁chang -6.00389
▁chao -6.00389
▁che -6.00389
▁chen -6.00389
▁cheng -6.00389
▁chi -6.00389
▁chong -6.00389
▁chou -6.00389
▁chu -6.00389
▁chuai -6.00389
▁chuan -6.00389
▁chuang -6.00389
▁chui -6.00389
▁chun -6.00389
▁chuo -6.00389
▁ci -6.00389
▁cong -6.00389
▁cou -6.00389
▁cu -6.00389
▁cuan -6.00389
▁cui -6.00389
▁cun -6.00389
▁cuo -6.00389
▁da -6.00389
▁dai -6.00389
▁dan -6.00389
▁dang -6.00389
▁dao -6.00389
▁de -6.00389
▁dei -6.00389
▁deng -6.00389
▁di -6.00389
▁dia -6.00389
▁dian -6.00389
▁diao -6.00389
▁die -6.00389
▁ding -6.00389
▁diu -6.00389
▁dong -6.00389
▁dou -6.00389
▁du -6.00389
▁duan -6.00389
▁dui -6.00389
▁dun -6.00389
▁duo -6.00389
▁e -6.00389
▁ei -6.00389
▁en -6.00389
▁eng -6.00389
▁er -6.00389
▁fa -6.00389
▁fan -6.00389
▁fang -6.00389
▁fei -6.00389
▁fen -6.00389
▁feng -6.00389
▁fo -6.00389
▁fou -6.00389
▁fu -6.00389
▁ga -6.00389
▁gai -6.00389
▁gan -6.00389
▁gang -6.00389
▁gao -6.00389
▁ge -6.00389
▁gei -6.00389
▁gen -6.00389
▁geng -6.00389
▁gong -6.00389
▁gou -6.00389
▁gu -6.00389
▁gua -6.00389
▁guai -6.00389
▁guan -6.00389
▁guang -6.00389
▁gui -6.00389
▁gun -6.00389
▁guo -6.00389
▁ha -6.00389
▁hai -6.00389
▁han -6.00389
▁hang -6.00389
▁hao -6.00389
▁he -6.00389
▁hei -6.00389
▁hen -6.00389
▁heng -6.00389
▁hong -6.00389
▁hou -6.00389
▁hu -6.00389
▁hua -6.00389
▁huai -6.00389
▁huan -6.00389
▁huang -6.00389
▁hui -6.00389
▁hun -6.00389
▁huo -6.00389
▁ji -6.00389
▁jia -6.00389
▁jian -6.00389
▁jiang -6.00389
▁jiao -6.00389
▁jie -6.00389
▁jin -6.00389
▁jing -6.00389
▁jiong -6.00389
▁jiu -6.00389
▁ju -6.00389
▁juan -6.00389
▁jue -6.00389
▁jun -6.00389
▁ka -6.00389
▁kai -6.00389
▁kan -6.00389
▁kang -6.00389
▁kao -6.00389
▁ke -6.00389
▁ken -6.00389
▁keng -6.00389
▁kong -6.00389
▁kou -6.00389
▁ku -6.00389
▁kua -6.00389
▁kuai -6.00389
▁kuan -6.00389
▁kuang -6.00389
▁kui -6.00389
▁kun -6.00389
▁kuo -6.00389
▁la -6.00389
▁lai -6.00389
▁lan -6.00389
▁lang -6.00389
▁lao -6.00389
▁le -6.00389
▁lei -6.00389
▁leng -6.00389
▁li -6.00389
▁lia -6.00389
▁lian -6.00389
▁liang -6.00389
▁liao -6.00389
▁lie -6.00389
▁lin -6.00389
▁ling -6.00389
▁liu -6.00389
▁long -6.00389
▁lou -6.00389
▁lu -6.00389
▁luan -6.00389
▁lun -6.00389
▁luo -6.00389
▁lv -6.00389
▁lve -6.00389
▁ma -6.00389
▁mai -6.00389
▁man -6.00389
▁mang -6.00389
▁mao -6.00389
▁me -6.00389
▁mei -6.00389
▁men -6.00389
▁meng -6.00389
▁mi -6.00389
▁mian -6.00389
▁miao -6.00389
▁mie -6.00389
▁min -6.00389
▁ming -6.00389
▁miu -6.00389
▁mo -6.00389
▁mou -6.00389
▁mu -6.00389
▁n -6.00389
▁na -6.00389
▁nai -6.00389
▁nan -6.00389
▁nang -6.00389
▁nao -6.00389
▁ne -6.00389
▁nei -6.00389
▁nen -6.00389
▁neng -6.00389
▁ni -6.00389
▁nian -6.00389
▁niang -6.00389
▁niao -6.00389
▁nie -6.00389
▁nin -6.00389
▁ning -6.00389
▁niu -6.00389
▁nong -6.00389
▁nou -6.00389
▁nu -6.00389
▁nuan -6.00389
▁nuo -6.00389
▁nv -6.00389
▁nve -6.00389
▁o -6.00389
▁ou -6.00389
▁pa -6.00389
▁pai -6.00389
▁pan -6.00389
▁pang -6.00389
▁pao -6.00389
▁pei -6.00389
▁pen -6.00389
▁peng -6.00389
▁pi -6.00389
▁pian -6.00389
▁piao -6.00389
▁pie -6.00389
▁pin -6.00389
▁ping -6.00389
▁po -6.00389
▁pou -6.00389
▁pu -6.00389
▁qi -6.00389
▁qia -6.00389
▁qian -6.00389
▁qiang -6.00389
▁qiao -6.00389
▁qie -6.00389
▁qin -6.00389
▁qing -6.00389
▁qiong -6.00389
▁qiu -6.00389
▁qu -6.00389
▁quan -6.00389
▁que -6.00389
▁qun -6.00389
▁ran -6.00389
▁rang -6.00389
▁rao -6.00389
▁re -6.00389
▁ren -6.00389
▁reng -6.00389
▁ri -6.00389
▁rong -6.00389
▁rou -6.00389
▁ru -6.00389
▁ruan -6.00389
▁rui -6.00389
▁run -6.00389
▁ruo -6.00389
▁sa -6.00389
▁sai -6.00389
▁san -6.00389
▁sang -6.00389
▁sao -6.00389
▁se -6.00389
▁sen -6.00389
▁seng -6.00389
▁sha -6.00389
▁shai -6.00389
▁shan -6.00389
▁shang -6.00389
▁shao -6.00389
▁she -6.00389
▁shei -6.00389
▁shen -6.00389
▁sheng -6.00389
▁shi -6.00389
▁shou -6.00389
▁shu -6.00389
▁shua -6.00389
▁shuai -6.00389
▁shuan -6.00389
▁shuang -6.00389
▁shui -6.00389
▁shun -6.00389
▁shuo -6.00389
▁si -6.00389
▁song -6.00389
▁sou -6.00389
▁su -6.00389
▁suan -6.00389
▁sui -6.00389
▁sun -6.00389
▁suo -6.00389
▁ta -6.00389
▁tai -6.00389
▁tan -6.00389
▁tang -6.00389
▁tao -6.00389
▁te -6.00389
▁teng -6.00389
▁ti -6.00389
▁tian -6.00389
▁tiao -6.00389
▁tie -6.00389
▁ting -6.00389
▁tong -6.00389
▁tou -6.00389
▁tu -6.00389
▁tuan -6.00389
▁tui -6.00389
▁tun -6.00389
▁tuo -6.00389
▁wa -6.00389
▁wai -6.00389
▁wan -6.00389
▁wang -6.00389
▁wei -6.00389
▁wen -6.00389
▁weng -6.00389
▁wo -6.00389
▁wu -6.00389
▁xi -6.00389
▁xia -6.00389
▁xian -6.00389
▁xiang -6.00389
▁xiao -6.00389
▁xie -6.00389
▁xin -6.00389
▁xing -6.00389
▁xiong -6.00389
▁xiu -6.00389
▁xu -6.00389
▁xuan -6.00389
▁xue -6.00389
▁xun -6.00389
▁ya -6.00389
▁yan -6.00389
▁yang -6.00389
▁yao -6.00389
▁ye -6.00389
▁yi -6.00389
▁yin -6.00389
▁ying -6.00389
▁yo -6.00389
▁yong -6.00389
▁you -6.00389
▁yu -6.00389
▁yuan -6.00389
▁yue -6.00389
▁yun -6.00389
▁za -6.00389
▁zai -6.00389
▁zan -6.00389
▁zang -6.00389
▁zao -6.00389
▁ze -6.00389
▁zei -6.00389
▁zen -6.00389
▁zeng -6.00389
▁zha -6.00389
▁zhai -6.00389
▁zhan -6.00389
▁zhang -6.00389
▁zhao -6.00389
▁zhe -6.00389
▁zhen -6.00389
▁zheng -6.00389
▁zhi -6.00389
▁zhong -6.00389
▁zhou -6.00389
▁zhu -6.00389
▁zhua -6.00389
▁zhuai -6.00389
▁zhuan -6.00389
▁zhuang -6.00389
▁zhui -6.00389
▁zhun -6.00389
▁zhuo -6.00389
▁zi -6.00389
▁zong -6.00389
▁zou -6.00389
▁zu -6.00389
▁zuan -6.00389
▁zui -6.00389
▁zun -6.00389
▁zuo -6.00389
@@ -0,0 +1,2 @@
# (neural network type)_(model data version)_(lable1_detection windown length_threshold for 90%_threshold for 95%)_(lable2 ...)_...
MN6_v11_english_8_0.9_0.90

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