├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── code_improvement.md │ ├── documentation_improvement.md │ └── feature_request.md └── workflows │ └── docker-image.yml ├── .gitignore ├── Dockerfile-server ├── Dockerfile-web ├── LICENSE ├── README.md ├── README_en.md ├── docker-setup.sh ├── docs ├── Deployment.md ├── Deployment_all.md ├── FAQ.md ├── contributor_open_letter.md ├── docker-build.md ├── docker │ ├── nginx.conf │ └── start.sh ├── firmware-build.md ├── firmware-setting.md ├── fish-speech-integration.md ├── homeassistant-integration.md └── images │ ├── __init__.py │ ├── banner1.png │ ├── banner2.png │ ├── conda_env_1.png │ ├── conda_env_2.png │ ├── demo0.png │ ├── demo1.png │ ├── demo2.png │ ├── demo3.png │ ├── demo4.png │ ├── demo5.png │ ├── demo6.png │ ├── demo7.png │ ├── demo8.png │ ├── demo9.png │ ├── deploy1.png │ ├── deploy2.png │ ├── firmware-setting-ota.png │ ├── fishspeech │ ├── autodl-01.png │ └── autodl-02.png │ ├── image-ha-integration-01.png │ ├── image-ha-integration-02.png │ ├── image-ha-integration-03.png │ ├── image-ha-integration-04.png │ ├── image-ha-integration-05.png │ ├── logo_bailing.png │ ├── logo_huiyuan.png │ ├── logo_qinren.png │ ├── logo_tenclass.png │ └── logo_xuanfeng.png └── main ├── README.md ├── manager-api ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── xiaozhi │ │ │ ├── AdminApplication.java │ │ │ ├── common │ │ │ ├── annotation │ │ │ │ ├── DataFilter.java │ │ │ │ └── LogOperation.java │ │ │ ├── aspect │ │ │ │ └── RedisAspect.java │ │ │ ├── config │ │ │ │ ├── AsyncConfig.java │ │ │ │ ├── MybatisPlusConfig.java │ │ │ │ ├── RestTemplateConfig.java │ │ │ │ └── SwaggerConfig.java │ │ │ ├── constant │ │ │ │ └── Constant.java │ │ │ ├── convert │ │ │ │ └── DateConverter.java │ │ │ ├── dao │ │ │ │ └── BaseDao.java │ │ │ ├── entity │ │ │ │ └── BaseEntity.java │ │ │ ├── exception │ │ │ │ ├── ErrorCode.java │ │ │ │ ├── RenException.java │ │ │ │ └── RenExceptionHandler.java │ │ │ ├── handler │ │ │ │ └── FieldMetaObjectHandler.java │ │ │ ├── interceptor │ │ │ │ ├── DataFilterInterceptor.java │ │ │ │ └── DataScope.java │ │ │ ├── page │ │ │ │ ├── PageData.java │ │ │ │ └── TokenDTO.java │ │ │ ├── redis │ │ │ │ ├── RedisConfig.java │ │ │ │ ├── RedisKeys.java │ │ │ │ └── RedisUtils.java │ │ │ ├── service │ │ │ │ ├── BaseService.java │ │ │ │ ├── CrudService.java │ │ │ │ └── impl │ │ │ │ │ ├── BaseServiceImpl.java │ │ │ │ │ └── CrudServiceImpl.java │ │ │ ├── user │ │ │ │ └── UserDetail.java │ │ │ ├── utils │ │ │ │ ├── ConvertUtils.java │ │ │ │ ├── DateUtils.java │ │ │ │ ├── HttpContextUtils.java │ │ │ │ ├── IpUtils.java │ │ │ │ ├── JsonUtils.java │ │ │ │ ├── MessageUtils.java │ │ │ │ ├── PropertiesUtils.java │ │ │ │ ├── Result.java │ │ │ │ ├── SpringContextUtils.java │ │ │ │ ├── TreeNode.java │ │ │ │ └── TreeUtils.java │ │ │ ├── validator │ │ │ │ ├── AssertUtils.java │ │ │ │ ├── ValidatorUtils.java │ │ │ │ └── group │ │ │ │ │ ├── AddGroup.java │ │ │ │ │ ├── DefaultGroup.java │ │ │ │ │ └── UpdateGroup.java │ │ │ └── xss │ │ │ │ ├── SqlFilter.java │ │ │ │ ├── XssConfig.java │ │ │ │ ├── XssFilter.java │ │ │ │ ├── XssHttpServletRequestWrapper.java │ │ │ │ ├── XssProperties.java │ │ │ │ └── XssUtils.java │ │ │ └── modules │ │ │ ├── agent │ │ │ ├── controller │ │ │ │ ├── AgentChatHistoryController.java │ │ │ │ └── AgentController.java │ │ │ ├── dao │ │ │ │ ├── AgentDao.java │ │ │ │ ├── AgentTemplateDao.java │ │ │ │ ├── AiAgentChatAudioDao.java │ │ │ │ └── AiAgentChatHistoryDao.java │ │ │ ├── dto │ │ │ │ ├── AgentChatHistoryDTO.java │ │ │ │ ├── AgentChatHistoryReportDTO.java │ │ │ │ ├── AgentChatSessionDTO.java │ │ │ │ ├── AgentCreateDTO.java │ │ │ │ ├── AgentDTO.java │ │ │ │ ├── AgentMemoryDTO.java │ │ │ │ └── AgentUpdateDTO.java │ │ │ ├── entity │ │ │ │ ├── AgentChatAudioEntity.java │ │ │ │ ├── AgentChatHistoryEntity.java │ │ │ │ ├── AgentEntity.java │ │ │ │ └── AgentTemplateEntity.java │ │ │ ├── service │ │ │ │ ├── AgentChatAudioService.java │ │ │ │ ├── AgentChatHistoryService.java │ │ │ │ ├── AgentService.java │ │ │ │ ├── AgentTemplateService.java │ │ │ │ ├── biz │ │ │ │ │ ├── AgentChatHistoryBizService.java │ │ │ │ │ └── impl │ │ │ │ │ │ └── AgentChatHistoryBizServiceImpl.java │ │ │ │ └── impl │ │ │ │ │ ├── AgentChatAudioServiceImpl.java │ │ │ │ │ ├── AgentChatHistoryServiceImpl.java │ │ │ │ │ ├── AgentServiceImpl.java │ │ │ │ │ └── AgentTemplateServiceImpl.java │ │ │ └── vo │ │ │ │ └── AgentTemplateVO.java │ │ │ ├── config │ │ │ ├── controller │ │ │ │ └── ConfigController.java │ │ │ ├── dto │ │ │ │ └── AgentModelsDTO.java │ │ │ ├── init │ │ │ │ └── SystemInitConfig.java │ │ │ └── service │ │ │ │ ├── ConfigService.java │ │ │ │ └── impl │ │ │ │ └── ConfigServiceImpl.java │ │ │ ├── device │ │ │ ├── controller │ │ │ │ ├── DeviceController.java │ │ │ │ ├── OTAController.java │ │ │ │ └── OTAMagController.java │ │ │ ├── dao │ │ │ │ ├── DeviceDao.java │ │ │ │ └── OtaDao.java │ │ │ ├── dto │ │ │ │ ├── DeviceBindDTO.java │ │ │ │ ├── DevicePageUserDTO.java │ │ │ │ ├── DeviceRegisterDTO.java │ │ │ │ ├── DeviceReportReqDTO.java │ │ │ │ ├── DeviceReportRespDTO.java │ │ │ │ └── DeviceUnBindDTO.java │ │ │ ├── entity │ │ │ │ ├── DeviceEntity.java │ │ │ │ └── OtaEntity.java │ │ │ ├── service │ │ │ │ ├── DeviceService.java │ │ │ │ ├── OtaService.java │ │ │ │ └── impl │ │ │ │ │ ├── DeviceServiceImpl.java │ │ │ │ │ └── OtaServiceImpl.java │ │ │ └── vo │ │ │ │ ├── DeviceOtaVO.java │ │ │ │ └── UserShowDeviceListVO.java │ │ │ ├── model │ │ │ ├── controller │ │ │ │ └── ModelController.java │ │ │ ├── dao │ │ │ │ ├── ModelConfigDao.java │ │ │ │ └── ModelProviderDao.java │ │ │ ├── dto │ │ │ │ ├── ModelBasicInfoDTO.java │ │ │ │ ├── ModelConfigBodyDTO.java │ │ │ │ ├── ModelConfigDTO.java │ │ │ │ ├── ModelProviderDTO.java │ │ │ │ └── VoiceDTO.java │ │ │ ├── entity │ │ │ │ ├── ModelConfigEntity.java │ │ │ │ └── ModelProviderEntity.java │ │ │ └── service │ │ │ │ ├── ModelConfigService.java │ │ │ │ ├── ModelProviderService.java │ │ │ │ └── impl │ │ │ │ ├── ModelConfigServiceImpl.java │ │ │ │ └── ModelProviderServiceImpl.java │ │ │ ├── security │ │ │ ├── config │ │ │ │ ├── FilterConfig.java │ │ │ │ ├── ShiroConfig.java │ │ │ │ └── WebMvcConfig.java │ │ │ ├── controller │ │ │ │ └── LoginController.java │ │ │ ├── dao │ │ │ │ └── SysUserTokenDao.java │ │ │ ├── dto │ │ │ │ └── LoginDTO.java │ │ │ ├── entity │ │ │ │ └── SysUserTokenEntity.java │ │ │ ├── oauth2 │ │ │ │ ├── Oauth2Filter.java │ │ │ │ ├── Oauth2Realm.java │ │ │ │ ├── Oauth2Token.java │ │ │ │ └── TokenGenerator.java │ │ │ ├── password │ │ │ │ ├── BCrypt.java │ │ │ │ ├── BCryptPasswordEncoder.java │ │ │ │ ├── PasswordEncoder.java │ │ │ │ └── PasswordUtils.java │ │ │ ├── secret │ │ │ │ ├── ServerSecretFilter.java │ │ │ │ └── ServerSecretToken.java │ │ │ ├── service │ │ │ │ ├── CaptchaService.java │ │ │ │ ├── ShiroService.java │ │ │ │ ├── SysUserTokenService.java │ │ │ │ └── impl │ │ │ │ │ ├── CaptchaServiceImpl.java │ │ │ │ │ ├── ShiroServiceImpl.java │ │ │ │ │ └── SysUserTokenServiceImpl.java │ │ │ └── user │ │ │ │ └── SecurityUser.java │ │ │ ├── sys │ │ │ ├── controller │ │ │ │ ├── AdminController.java │ │ │ │ ├── SysDictDataController.java │ │ │ │ ├── SysDictTypeController.java │ │ │ │ └── SysParamsController.java │ │ │ ├── dao │ │ │ │ ├── SysDictDataDao.java │ │ │ │ ├── SysDictTypeDao.java │ │ │ │ ├── SysParamsDao.java │ │ │ │ └── SysUserDao.java │ │ │ ├── dto │ │ │ │ ├── AdminPageUserDTO.java │ │ │ │ ├── PasswordDTO.java │ │ │ │ ├── SysDictDataDTO.java │ │ │ │ ├── SysDictTypeDTO.java │ │ │ │ ├── SysParamsDTO.java │ │ │ │ └── SysUserDTO.java │ │ │ ├── entity │ │ │ │ ├── SysDictDataEntity.java │ │ │ │ ├── SysDictTypeEntity.java │ │ │ │ ├── SysParamsEntity.java │ │ │ │ └── SysUserEntity.java │ │ │ ├── enums │ │ │ │ └── SuperAdminEnum.java │ │ │ ├── redis │ │ │ │ └── SysParamsRedis.java │ │ │ ├── service │ │ │ │ ├── SysDictDataService.java │ │ │ │ ├── SysDictTypeService.java │ │ │ │ ├── SysParamsService.java │ │ │ │ ├── SysUserService.java │ │ │ │ ├── SysUserUtilService.java │ │ │ │ ├── TokenService.java │ │ │ │ └── impl │ │ │ │ │ ├── SysDictDataServiceImpl.java │ │ │ │ │ ├── SysDictTypeServiceImpl.java │ │ │ │ │ ├── SysParamsServiceImpl.java │ │ │ │ │ ├── SysUserServiceImpl.java │ │ │ │ │ ├── SysUserUtilServiceImpl.java │ │ │ │ │ └── TokenServiceImpl.java │ │ │ ├── utils │ │ │ │ ├── WebSocketTestHandler.java │ │ │ │ └── WebSocketValidator.java │ │ │ └── vo │ │ │ │ ├── AdminPageUserVO.java │ │ │ │ ├── SysDictDataItem.java │ │ │ │ ├── SysDictDataVO.java │ │ │ │ └── SysDictTypeVO.java │ │ │ └── timbre │ │ │ ├── controller │ │ │ └── TimbreController.java │ │ │ ├── dao │ │ │ └── TimbreDao.java │ │ │ ├── dto │ │ │ ├── TimbreDataDTO.java │ │ │ └── TimbrePageDTO.java │ │ │ ├── entity │ │ │ └── TimbreEntity.java │ │ │ ├── service │ │ │ ├── TimbreService.java │ │ │ └── impl │ │ │ │ └── TimbreServiceImpl.java │ │ │ └── vo │ │ │ └── TimbreDetailsVO.java │ └── resources │ │ ├── application-dev.yml │ │ ├── application.yml │ │ ├── db │ │ └── changelog │ │ │ ├── 202503141335.sql │ │ │ ├── 202503141346.sql │ │ │ ├── 202504082211.sql │ │ │ ├── 202504092335.sql │ │ │ ├── 202504112044.sql │ │ │ ├── 202504112058.sql │ │ │ ├── 202504151206.sql │ │ │ ├── 202504181536.sql │ │ │ ├── 202504221135.sql │ │ │ ├── 202504221555.sql │ │ │ ├── 202504251422.sql │ │ │ ├── 202504291043.sql │ │ │ ├── 202504301341.sql │ │ │ ├── 202505022134.sql │ │ │ ├── 202505081146.sql │ │ │ ├── 202505091409.sql │ │ │ ├── 202505091555.sql │ │ │ ├── 202505111914.sql │ │ │ ├── 202505122348.sql │ │ │ ├── 202505142037.sql │ │ │ └── db.changelog-master.yaml │ │ ├── i18n │ │ ├── messages.properties │ │ ├── messages_en_US.properties │ │ ├── messages_zh_CN.properties │ │ ├── messages_zh_TW.properties │ │ ├── validation.properties │ │ ├── validation_en_US.properties │ │ ├── validation_zh_CN.properties │ │ └── validation_zh_TW.properties │ │ ├── logback-spring.xml │ │ └── mapper │ │ ├── agent │ │ ├── AgentDao.xml │ │ ├── AgentTemplateMapper.xml │ │ └── AiAgentChatHistoryDao.xml │ │ ├── device │ │ └── DeviceDao.xml │ │ ├── model │ │ ├── ModelConfigDao.xml │ │ └── ModelProviderDao.xml │ │ ├── security │ │ └── SysUserTokenDao.xml │ │ └── sys │ │ ├── SysDictDataDao.xml │ │ ├── SysDictTypeDao.xml │ │ ├── SysParamsDao.xml │ │ └── SysUserDao.xml │ └── test │ ├── java │ └── xiaozhi │ │ └── modules │ │ └── device │ │ └── DeviceTest.java │ └── resources │ └── application.yml ├── manager-web ├── .env ├── .env.development ├── .env.production ├── .npmrc ├── README.md ├── babel.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── offline.html ├── src │ ├── App.vue │ ├── apis │ │ ├── api.js │ │ ├── httpRequest.js │ │ └── module │ │ │ ├── admin.js │ │ │ ├── agent.js │ │ │ ├── device.js │ │ │ ├── dict.js │ │ │ ├── model.js │ │ │ ├── ota.js │ │ │ ├── timbre.js │ │ │ └── user.js │ ├── assets │ │ ├── header │ │ │ ├── firmware_update.png │ │ │ ├── model_config.png │ │ │ ├── param_management.png │ │ │ ├── robot.png │ │ │ └── user_management.png │ │ ├── home │ │ │ ├── avatar.png │ │ │ ├── close.png │ │ │ ├── delete.png │ │ │ ├── equipment.png │ │ │ ├── info.png │ │ │ ├── main-top-bg.png │ │ │ ├── red-info.png │ │ │ ├── search.png │ │ │ └── setting-user.png │ │ ├── login │ │ │ ├── down-arrow.png │ │ │ ├── hi.png │ │ │ ├── login-person.png │ │ │ ├── password.png │ │ │ ├── phone.png │ │ │ ├── register-person.png │ │ │ ├── shield.png │ │ │ └── username.png │ │ ├── model │ │ │ ├── inner_conf.png │ │ │ ├── model.png │ │ │ └── output_conf.png │ │ ├── user-avatar1.png │ │ ├── user-avatar2.png │ │ ├── user-avatar3.png │ │ ├── user-avatar4.png │ │ ├── user-avatar5.png │ │ ├── xiaozhi-ai.png │ │ └── xiaozhi-logo.png │ ├── components │ │ ├── AddDeviceDialog.vue │ │ ├── AddModelDialog.vue │ │ ├── AddWisdomBodyDialog.vue │ │ ├── AudioPlayer.vue │ │ ├── CacheViewer.vue │ │ ├── ChangePasswordDialog.vue │ │ ├── ChatHistoryDialog.vue │ │ ├── DeviceItem.vue │ │ ├── DictDataDialog.vue │ │ ├── DictTypeDialog.vue │ │ ├── EditVoiceDialog.vue │ │ ├── FirmwareDialog.vue │ │ ├── FunctionDialog.vue │ │ ├── HeaderBar.vue │ │ ├── ModelEditDialog.vue │ │ ├── ParamDialog.vue │ │ ├── ProviderDialog.vue │ │ ├── TtsModel.vue │ │ ├── VersionFooter.vue │ │ └── ViewPasswordDialog.vue │ ├── main.js │ ├── registerServiceWorker.js │ ├── router │ │ └── index.js │ ├── service-worker.js │ ├── store │ │ └── index.js │ ├── styles │ │ └── global.scss │ ├── utils │ │ ├── cacheViewer.js │ │ ├── constant.js │ │ ├── date.js │ │ ├── format.js │ │ └── index.js │ └── views │ │ ├── DeviceManagement.vue │ │ ├── DictManagement.vue │ │ ├── ModelConfig.vue │ │ ├── OtaManagement.vue │ │ ├── ParamsManagement.vue │ │ ├── ProviderManagement.vue │ │ ├── UserManagement.vue │ │ ├── auth.scss │ │ ├── home.vue │ │ ├── login.vue │ │ ├── register.vue │ │ └── roleConfig.vue └── vue.config.js └── xiaozhi-server ├── app.py ├── config.yaml ├── config ├── assets │ ├── bind_code.wav │ ├── bind_code │ │ ├── 0.wav │ │ ├── 1.wav │ │ ├── 2.wav │ │ ├── 3.wav │ │ ├── 4.wav │ │ ├── 5.wav │ │ ├── 6.wav │ │ ├── 7.wav │ │ ├── 8.wav │ │ └── 9.wav │ ├── bind_not_found.wav │ ├── max_output_size.wav │ ├── tts_notify.mp3 │ └── wakeup_words.wav ├── config_loader.py ├── logger.py ├── manage_api_client.py └── settings.py ├── config_from_api.yaml ├── core ├── auth.py ├── connection.py ├── handle │ ├── abortHandle.py │ ├── functionHandler.py │ ├── helloHandle.py │ ├── intentHandler.py │ ├── iotHandle.py │ ├── receiveAudioHandle.py │ ├── reportHandle.py │ ├── sendAudioHandle.py │ └── textHandle.py ├── mcp │ ├── MCPClient.py │ └── manager.py ├── ota_server.py ├── providers │ ├── asr │ │ ├── aliyun.py │ │ ├── baidu.py │ │ ├── base.py │ │ ├── doubao.py │ │ ├── fun_local.py │ │ ├── fun_server.py │ │ ├── sherpa_onnx_local.py │ │ └── tencent.py │ ├── intent │ │ ├── base.py │ │ ├── function_call │ │ │ └── function_call.py │ │ ├── intent_llm │ │ │ └── intent_llm.py │ │ └── nointent │ │ │ └── nointent.py │ ├── llm │ │ ├── AliBL │ │ │ └── AliBL.py │ │ ├── base.py │ │ ├── coze │ │ │ └── coze.py │ │ ├── dify │ │ │ └── dify.py │ │ ├── fastgpt │ │ │ └── fastgpt.py │ │ ├── gemini │ │ │ └── gemini.py │ │ ├── homeassistant │ │ │ └── homeassistant.py │ │ ├── ollama │ │ │ └── ollama.py │ │ ├── openai │ │ │ └── openai.py │ │ ├── system_prompt.py │ │ └── xinference │ │ │ └── xinference.py │ ├── memory │ │ ├── base.py │ │ ├── mem0ai │ │ │ └── mem0ai.py │ │ ├── mem_local_short │ │ │ └── mem_local_short.py │ │ └── nomem │ │ │ └── nomem.py │ ├── tts │ │ ├── aliyun.py │ │ ├── base.py │ │ ├── cozecn.py │ │ ├── custom.py │ │ ├── doubao.py │ │ ├── edge.py │ │ ├── fishspeech.py │ │ ├── gpt_sovits_v2.py │ │ ├── gpt_sovits_v3.py │ │ ├── minimax.py │ │ ├── openai.py │ │ ├── siliconflow.py │ │ ├── tencent.py │ │ └── ttson.py │ └── vad │ │ ├── base.py │ │ └── silero.py ├── utils │ ├── asr.py │ ├── dialogue.py │ ├── intent.py │ ├── llm.py │ ├── memory.py │ ├── output_counter.py │ ├── p3.py │ ├── tts.py │ ├── util.py │ └── vad.py └── websocket_server.py ├── docker-compose.yml ├── docker-compose_all.yml ├── mcp_server_settings.json ├── models ├── SenseVoiceSmall │ ├── chn_jpn_yue_eng_ko_spectok.bpe.model │ ├── config.yaml │ ├── configuration.json │ ├── demo.py │ └── example │ │ ├── en.mp3 │ │ ├── ja.mp3 │ │ ├── ko.mp3 │ │ ├── yue.mp3 │ │ └── zh.mp3 └── snakers4_silero-vad │ ├── hubconf.py │ └── src │ └── silero_vad │ ├── __init__.py │ ├── data │ ├── __init__.py │ ├── silero_vad.jit │ ├── silero_vad.onnx │ ├── silero_vad_16k_op15.onnx │ └── silero_vad_half.onnx │ ├── model.py │ └── utils_vad.py ├── music ├── 一念千年_国风版.mp3 ├── 中秋月.mp3 └── 廉波老矣,尚能饭否.mp3 ├── performance_tester.py ├── plugins_func ├── functions │ ├── change_role.py │ ├── get_news_from_chinanews.py │ ├── get_news_from_newsnow.py │ ├── get_time.py │ ├── get_weather.py │ ├── handle_device.py │ ├── handle_exit_intent.py │ ├── hass_get_state.py │ ├── hass_init.py │ ├── hass_play_music.py │ ├── hass_set_state.py │ ├── play_music.py │ └── plugin_loader.py ├── loadplugins.py └── register.py ├── requirements.txt └── test ├── abbreviated_version ├── app.js └── test.html ├── libopus.js ├── opus_test ├── app.js └── test.html └── test_page.html /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | __pycache__ 3 | *.pyc 4 | .env 5 | Dockerfile 6 | tmp/ 7 | data/ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 报告(Bug Report) 3 | about: 反馈项目中的缺陷或问题 4 | title: "[Bug] 简短描述问题" 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## 🐛 问题描述 10 | 11 | 12 | ## 🖥️ 环境信息 13 | - 部署方式: 全模块部署 还是 单Server部署 14 | - 版本号: 例如 0.3.x 15 | 16 | ## 🔍 告诉我们,应该怎么复现这个问题 17 | 18 | 1. 打开 '...' 19 | 2. 点击 '...' 20 | 3. 滚动到 '...' 21 | 4. 看到错误 22 | 23 | ## 🤔 你原本希望是怎么样的 24 | 25 | 26 | ## 😯 提供一些截图 27 | 28 | 1. 比如日志截图,越多越好 29 | 2. 比如界面反应 30 | 31 | ## 📋 其他信息 32 | 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/code_improvement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 代码优化建议(Code Improvement) 3 | about: 提出对现有代码的优化或改进建议 4 | title: "[Improvement] 简短描述改进内容" 5 | labels: refactor 6 | assignees: '' 7 | --- 8 | 9 | ## 💡 改进描述 10 | 11 | 12 | ## 🌟 改进建议 13 | 14 | 15 | ## 🛠️ 相关代码 16 | 17 | 18 | ## 📋 其他信息 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_improvement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 文档改进建议(Documentation Improvement) 3 | about: 提出对项目文档的改进或补充建议 4 | title: "[Docs] 简短描述改进内容" 5 | labels: documentation 6 | assignees: '' 7 | --- 8 | 9 | ## 📚 改进描述 10 | 11 | 12 | ## ✨ 改进建议 13 | 14 | 15 | ## 📋 其他信息 16 | 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能请求(Feature Request) 3 | about: 提出新的功能或改进建议 4 | title: "[Feature] 简短描述功能" 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## 🚀 需求描述 10 | 11 | 12 | ## 🎯 解决方案 13 | 14 | 15 | ## 📝 备选方案 16 | 17 | 18 | ## 📋 其他信息 19 | 20 | -------------------------------------------------------------------------------- /Dockerfile-server: -------------------------------------------------------------------------------- 1 | # 第一阶段:构建Python依赖 2 | FROM python:3.10-slim AS builder 3 | 4 | WORKDIR /app 5 | 6 | COPY main/xiaozhi-server/requirements.txt . 7 | 8 | # 安装Python依赖 9 | RUN pip install --no-cache-dir -r requirements.txt 10 | 11 | # 第二阶段:生产镜像 12 | FROM python:3.10-slim 13 | 14 | WORKDIR /opt/xiaozhi-esp32-server 15 | 16 | # 安装系统依赖 17 | RUN apt-get update && \ 18 | apt-get install -y --no-install-recommends libopus0 ffmpeg && \ 19 | apt-get clean && \ 20 | rm -rf /var/lib/apt/lists/* 21 | 22 | # 从构建阶段复制Python包和前端构建产物 23 | COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages 24 | 25 | # 复制应用代码 26 | COPY main/xiaozhi-server . 27 | 28 | # 启动应用 29 | CMD ["python", "app.py"] -------------------------------------------------------------------------------- /Dockerfile-web: -------------------------------------------------------------------------------- 1 | # 第一阶段:构建Vue前端 2 | FROM node:18 as web-builder 3 | WORKDIR /app 4 | COPY main/manager-web/package*.json ./ 5 | RUN npm install 6 | COPY main/manager-web . 7 | RUN npm run build 8 | 9 | # 第二阶段:构建Java后端 10 | FROM maven:3-eclipse-temurin-21-alpine as api-builder 11 | WORKDIR /app 12 | COPY main/manager-api/pom.xml . 13 | COPY main/manager-api/src ./src 14 | RUN mvn clean package -Dmaven.test.skip=true 15 | 16 | # 第三阶段:构建最终镜像 17 | FROM eclipse-temurin:21-jdk-jammy 18 | 19 | # 安装Nginx并清理缓存 20 | RUN apt-get update && \ 21 | apt-get install -y nginx && \ 22 | apt-get clean && \ 23 | rm -rf /var/lib/apt/lists/* 24 | 25 | # 配置Nginx 26 | COPY docs/docker/nginx.conf /etc/nginx/nginx.conf 27 | 28 | # 复制前端构建产物 29 | COPY --from=web-builder /app/dist /usr/share/nginx/html 30 | 31 | # 复制Java后端JAR包 32 | COPY --from=api-builder /app/target/xiaozhi-esp32-api.jar /app/xiaozhi-esp32-api.jar 33 | 34 | # 暴露端口 35 | EXPOSE 8002 36 | 37 | # 启动脚本 38 | COPY docs/docker/start.sh /start.sh 39 | RUN chmod +x /start.sh 40 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 xinnan-tech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/contributor_open_letter.md: -------------------------------------------------------------------------------- 1 | # 致开发者的公开信 2 | 3 | "春江水暖鸭先知,正是河豚欲上时!" 4 | 5 | 亲爱的朋友,我是John,是一名普通公司里的Java程序员,今天,我怀着无比真挚的心情,向热爱AI技术与创新的你发出这封公开信。 6 | 7 | 半年前我看到很多优秀的项目,比如`Dify`、`Chat2DB`等人工智能相关的项目,我在想,我要是能参与这些项目多好,可惜“报国无门,空打十年代码”。 8 | 9 | 我是2025年初刷到虾哥团队的视频,我非常好奇他是怎么实现的,我想复刻他们的后端服务,打造一个低成本民用贾维斯。很可惜现在做的作品依然只是一个人工智障,它并发低、没有灵魂,响应很慢,bug很多。 10 | 11 | 虾哥团队是我们学习的对象,我很想拥有像虾哥团队一样智能的小智后端服务。但是我也能理解虾哥不开源的决定。“一花独放不是春,百花齐放春满园”,人工智能遍地开花的时代,也许就在我们这代实现,我们可以用自己的双手,实现低成本民用贾维斯。我个人认为,他能实现的,我们也能实现,只是时间问题而已,我称之为“我们的取经之路”。 12 | 13 | 那么这条取经之路,我们会遇到什么困难?我想应该不少于八十一难。这一路必然会出现各种妖怪,当然也有神仙暗中帮助我们,也有人加入取经队伍。 14 | 15 | 以上内容,如果你觉得好笑。那我也觉得非常的幸运。我能够在你人生3万多天里博你笑五秒,也算是为你做了一次贡献。 16 | 17 | 民用低成本贾维斯这个想法,会失败吗,我不知道,但是我们普通人的一生,这种失败不是很常见吗? 18 | 19 | 未来,有一点是可以确定的,就一定会有人完全复刻虾哥团队的功能,实现民用低成本贾维斯。这个项目会是我们吗? 20 | 21 | 期待与你携手前行,共创未来。 22 | 23 | John,2025.3.11,广州 24 | 25 | # 附 开发贡献指南 26 | ## 项目目标 27 | 28 | 1. **民用低成本贾维斯解决方案** 29 | 30 | 2. **智能联动周边硬件的解决方案** 31 | 32 | ## 加入我们 33 | 34 | 我们热忱欢迎志同道合的朋友加入,共同为项目贡献力量。您可在[这个链接](https://github.com/users/xinnan-tech/projects/3)查看我们近期要实现的功能,功能列表中还没指派相关人员处理的,正是急需您的参与。参与方式如下: 35 | 36 | ### 1、成为普通贡献者 37 | 38 | Fork 项目,提交 PR,由开发者审核后合入主分支。 39 | 40 | ### 2、成为开发者 41 | 42 | 当你累计提交 3 次有效 PR 后,可以联系群主申请成为开发者,群主将邀请你加入独立的开发者群,共同探讨项目未来。 43 | 44 | ## 开发者开发流程 45 | 46 | 1. **创建新分支** 47 | 每个功能点请以新分支方式开发,分支名称应简洁明了,让人一眼看出所实现的功能,避免功能撞车。 48 | 49 | 2. **提交 PR 审核** 50 | 功能开发完成后,请在 GitHub 上提交 PR,由其他开发者审核,审核通过后合并入主分支。 51 | -------------------------------------------------------------------------------- /docs/docker-build.md: -------------------------------------------------------------------------------- 1 | # 本地编译docker镜像方法 2 | 3 | 现在本项目已经使用github自动编译docker功能,本文档是提供给有本地编译docker镜像需求的朋友准备的。 4 | 5 | 1、安装docker 6 | ``` 7 | sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 8 | ``` 9 | 2、编译docker镜像 10 | ``` 11 | #进入项目根目录 12 | # 编译server 13 | docker build -t xiaozhi-esp32-server:server_latest -f ./Dockerfile-server . 14 | # 编译web 15 | docker build -t xiaozhi-esp32-server:web_latest -f ./Dockerfile-web . 16 | 17 | # 编译完成后,可以使用docker-compose启动项目 18 | # docker-compose.yml你需要修改成自己编译的镜像版本 19 | cd main/xiaozhi-server 20 | docker-compose up -d 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/docker/nginx.conf: -------------------------------------------------------------------------------- 1 | user root; 2 | worker_processes 4; 3 | 4 | events { 5 | worker_connections 1024; 6 | } 7 | 8 | http { 9 | include mime.types; 10 | default_type application/octet-stream; 11 | sendfile on; 12 | keepalive_timeout 300; 13 | client_header_timeout 180s; 14 | client_body_timeout 180s; 15 | client_max_body_size 1024M; 16 | 17 | gzip on; 18 | gzip_buffers 32 4K; 19 | gzip_comp_level 6; 20 | gzip_min_length 100; 21 | gzip_types application/javascript text/css text/xml image/jpeg image/gif image/png; 22 | gzip_disable "MSIE [1-6]\."; 23 | gzip_vary on; 24 | 25 | server { 26 | # 无域名访问,就用localhost 27 | server_name localhost; 28 | # 80端口 29 | listen 8002; 30 | 31 | # 转发到编译后到web目录 32 | location / { 33 | root /usr/share/nginx/html; 34 | try_files $uri $uri/ /index.html; 35 | } 36 | 37 | # 转发到manager-api 38 | location /xiaozhi/ { 39 | proxy_pass http://127.0.0.1:8003; 40 | proxy_set_header Host $host; 41 | proxy_cookie_path /manager/ /; 42 | proxy_set_header Referer $http_referer; 43 | proxy_set_header Cookie $http_cookie; 44 | 45 | proxy_connect_timeout 10; 46 | proxy_send_timeout 10; 47 | proxy_read_timeout 10; 48 | 49 | proxy_set_header X-Real-IP $remote_addr; 50 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /docs/docker/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 启动Java后端(docker内监听8003端口) 3 | java -jar /app/xiaozhi-esp32-api.jar \ 4 | --server.port=8003 \ 5 | --spring.datasource.druid.url=${SPRING_DATASOURCE_DRUID_URL} \ 6 | --spring.datasource.druid.username=${SPRING_DATASOURCE_DRUID_USERNAME} \ 7 | --spring.datasource.druid.password=${SPRING_DATASOURCE_DRUID_PASSWORD} \ 8 | --spring.data.redis.host=${SPRING_DATA_REDIS_HOST} \ 9 | --spring.data.redis.port=${SPRING_DATA_REDIS_PORT} & 10 | 11 | # 启动Nginx(前台运行保持容器存活) 12 | nginx -g 'daemon off;' -------------------------------------------------------------------------------- /docs/firmware-setting.md: -------------------------------------------------------------------------------- 1 | # 基于虾哥编译好的固件配置自定义服务器 2 | 3 | ## 第1步 确认版本 4 | 烧录虾哥已经编译好的[1.6.1版本以上固件](https://github.com/78/xiaozhi-esp32/releases) 5 | 6 | ## 第2步 准备你的ota地址 7 | 如果你按照教程使用的是全模块部署,就应该会有ota地址。 8 | 9 | 此刻,请你用浏览器打开你的ota地址,例如我的ota地址 10 | ``` 11 | https://2662r3426b.vicp.fun/xiaozhi/ota/ 12 | ``` 13 | 14 | 如果显示“OTA接口运行正常,websocket集群数量:X”。那就往下。 15 | 16 | 如果显示“OTA接口运行不正常”,大概是你还没在`智控台`配置`Websocket`地址。那就: 17 | 18 | - 1、使用超级管理员登录智控台 19 | 20 | - 2、顶部菜单点击`参数管理` 21 | 22 | - 3、在列表中找到`server.websocket`项目,输入你的`Websocket`地址。例如我的就是 23 | 24 | ``` 25 | wss://2662r3426b.vicp.fun/xiaozhi/v1/ 26 | ``` 27 | 28 | 配置完后,再使用浏览器刷新你的ota接口地址,看看是不是正常了。如果还不正常就,就再次确认一下Websocket是否正常启动,是否配置了Websocket地址。 29 | 30 | ## 第3步 进入配网模式 31 | 进入机器的配网模式,在页面顶部,点击“高级选项”,在里面输入你服务器的`ota`地址,点击保存。重启设备 32 | ![请参考-OTA地址设置](../docs/images/firmware-setting-ota.png) 33 | 34 | ## 第4步 唤醒小智,查看日志输出 35 | 36 | 唤醒小智,看看日志是不是正常输出。 37 | 38 | 39 | ## 常见问题 40 | 以下是一些常见问题,供参考: 41 | 42 | [1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md) 43 | 44 | [2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md) 45 | 46 | [3、TTS 经常失败,经常超时](./FAQ.md) 47 | 48 | [4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md) 49 | 50 | [5、如何提高小智对话响应速度?](./FAQ.md) 51 | 52 | [6、我说话很慢,停顿时小智老是抢话](./FAQ.md) 53 | 54 | [7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md) 55 | -------------------------------------------------------------------------------- /docs/fish-speech-integration.md: -------------------------------------------------------------------------------- 1 | 登录AutoDL,租赁镜像 2 | 选择镜像: 3 | ``` 4 | PyTorch / 2.1.0 / 3.10(ubuntu22.04) / cuda 12.1 5 | ``` 6 | 7 | 机器开机后,设置学术加速 8 | ``` 9 | source /etc/network_turbo 10 | ``` 11 | 12 | 进入工作目录 13 | ``` 14 | cd autodl-tmp/ 15 | ``` 16 | 17 | 拉取项目 18 | ``` 19 | git clone https://gitclone.com/github.com/fishaudio/fish-speech.git ; cd fish-speech 20 | ``` 21 | 22 | 安装依赖 23 | ``` 24 | pip install -e. 25 | ``` 26 | 27 | 如果报错,安装portaudio 28 | ``` 29 | apt-get install portaudio19-dev -y 30 | ``` 31 | 32 | 安装后执行 33 | ``` 34 | pip install torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 --index-url https://download.pytorch.org/whl/cu121 35 | ``` 36 | 37 | 下载模型 38 | ``` 39 | cd tools 40 | python download_models.py 41 | ``` 42 | 43 | 下载完模型后运行接口 44 | ``` 45 | python -m tools.api_server --listen 0.0.0.0:6006 46 | ``` 47 | 48 | 然后用浏览器去到aotodl实例页面 49 | ``` 50 | https://autodl.com/console/instance/list 51 | ``` 52 | 53 | 如下图点击你刚才机器的`自定义服务`按钮,开启端口转发服务 54 | ![自定义服务](images/fishspeech/autodl-01.png) 55 | 56 | 端口转发服务设置完成后,你本地电脑打开网址`http://localhost:6006/`,就可以访问fish-speech的接口了 57 | ![服务预览](images/fishspeech/autodl-02.png) 58 | 59 | 60 | 如果你是单模块部署,核心配置如下 61 | ``` 62 | selected_module: 63 | TTS: FishSpeech 64 | TTS: 65 | FishSpeech: 66 | reference_audio: ["config/assets/wakeup_words.wav",] 67 | reference_text: ["哈啰啊,我是小智啦,声音好听的台湾女孩一枚,超开心认识你耶,最近在忙啥,别忘了给我来点有趣的料哦,我超爱听八卦的啦",] 68 | api_key: "123" 69 | api_url: "http://127.0.0.1:6006/v1/tts" 70 | ``` 71 | 72 | 然后重启服务 -------------------------------------------------------------------------------- /docs/images/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/__init__.py -------------------------------------------------------------------------------- /docs/images/banner1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/banner1.png -------------------------------------------------------------------------------- /docs/images/banner2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/banner2.png -------------------------------------------------------------------------------- /docs/images/conda_env_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/conda_env_1.png -------------------------------------------------------------------------------- /docs/images/conda_env_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/conda_env_2.png -------------------------------------------------------------------------------- /docs/images/demo0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/demo0.png -------------------------------------------------------------------------------- /docs/images/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/demo1.png -------------------------------------------------------------------------------- /docs/images/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/demo2.png -------------------------------------------------------------------------------- /docs/images/demo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/demo3.png -------------------------------------------------------------------------------- /docs/images/demo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/demo4.png -------------------------------------------------------------------------------- /docs/images/demo5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/demo5.png -------------------------------------------------------------------------------- /docs/images/demo6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/demo6.png -------------------------------------------------------------------------------- /docs/images/demo7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/demo7.png -------------------------------------------------------------------------------- /docs/images/demo8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/demo8.png -------------------------------------------------------------------------------- /docs/images/demo9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/demo9.png -------------------------------------------------------------------------------- /docs/images/deploy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/deploy1.png -------------------------------------------------------------------------------- /docs/images/deploy2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/deploy2.png -------------------------------------------------------------------------------- /docs/images/firmware-setting-ota.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/firmware-setting-ota.png -------------------------------------------------------------------------------- /docs/images/fishspeech/autodl-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/fishspeech/autodl-01.png -------------------------------------------------------------------------------- /docs/images/fishspeech/autodl-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/fishspeech/autodl-02.png -------------------------------------------------------------------------------- /docs/images/image-ha-integration-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/image-ha-integration-01.png -------------------------------------------------------------------------------- /docs/images/image-ha-integration-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/image-ha-integration-02.png -------------------------------------------------------------------------------- /docs/images/image-ha-integration-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/image-ha-integration-03.png -------------------------------------------------------------------------------- /docs/images/image-ha-integration-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/image-ha-integration-04.png -------------------------------------------------------------------------------- /docs/images/image-ha-integration-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/image-ha-integration-05.png -------------------------------------------------------------------------------- /docs/images/logo_bailing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/logo_bailing.png -------------------------------------------------------------------------------- /docs/images/logo_huiyuan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/logo_huiyuan.png -------------------------------------------------------------------------------- /docs/images/logo_qinren.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/logo_qinren.png -------------------------------------------------------------------------------- /docs/images/logo_tenclass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/logo_tenclass.png -------------------------------------------------------------------------------- /docs/images/logo_xuanfeng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/docs/images/logo_xuanfeng.png -------------------------------------------------------------------------------- /main/README.md: -------------------------------------------------------------------------------- 1 | 本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3) 2 | 3 | # 项目目录介绍 4 | 如果你是一名软件开发者,这里有一份[《致开发者的公开信》](../docs/contributor_open_letter.md),欢迎归队! 5 | 6 | ``` 7 | xiaozhi-esp32-server 8 | ├─ xiaozhi-server 8000 端口 Python语言开发 负责与esp32通信 9 | ├─ manager-web 8001 端口 Node.js+Vue开发 负责提供控制台的web界面 10 | ├─ manager-api 8002 端口 Java语言开发 负责提供控制台的api 11 | ``` 12 | 13 | # xiaozhi-server 和ESP32通讯协议 14 | 15 | https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh 16 | 17 | # manager-web 、manager-api接口协议 18 | 19 | https://2662r3426b.vicp.fun/xiaozhi/doc.html 20 | -------------------------------------------------------------------------------- /main/manager-api/README.md: -------------------------------------------------------------------------------- 1 | 本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3) 2 | 3 | # 项目介绍 4 | 5 | manager-api 该项目基于SpringBoot框架开发。 6 | 7 | 开发使用代码编辑器,导入项目时,选择`manager-api`文件夹作为项目目录 8 | 9 | # 开发环境 10 | JDK 21 11 | Maven 3.8+ 12 | MySQL 8.0+ 13 | Redis 5.0+ 14 | Vue 3.x 15 | 16 | # 接口文档 17 | 启动后打开:http://localhost:8002/xiaozhi/doc.html 18 | 19 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/AdminApplication.java: -------------------------------------------------------------------------------- 1 | package xiaozhi; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class AdminApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(AdminApplication.class, args); 11 | System.out.println("http://localhost:8002/xiaozhi/doc.html"); 12 | } 13 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/annotation/DataFilter.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * 数据过滤注解 11 | * Copyright (c) 人人开源 All rights reserved. 12 | * Website: https://www.renren.io 13 | */ 14 | @Target(ElementType.METHOD) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | public @interface DataFilter { 18 | /** 19 | * 表的别名 20 | */ 21 | String tableAlias() default ""; 22 | 23 | /** 24 | * 用户ID 25 | */ 26 | String userId() default "creator"; 27 | 28 | /** 29 | * 部门ID 30 | */ 31 | String deptId() default "dept_id"; 32 | 33 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/annotation/LogOperation.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * 操作日志注解 11 | * Copyright (c) 人人开源 All rights reserved. 12 | * Website: https://www.renren.io 13 | */ 14 | @Target(ElementType.METHOD) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | public @interface LogOperation { 18 | 19 | String value() default ""; 20 | } 21 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/aspect/RedisAspect.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.aspect; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.annotation.Around; 5 | import org.aspectj.lang.annotation.Aspect; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Component; 8 | 9 | import lombok.extern.slf4j.Slf4j; 10 | import xiaozhi.common.exception.ErrorCode; 11 | import xiaozhi.common.exception.RenException; 12 | 13 | /** 14 | * Redis切面处理类 15 | * Copyright (c) 人人开源 All rights reserved. 16 | * Website: https://www.renren.io 17 | */ 18 | @Slf4j 19 | @Aspect 20 | @Component 21 | public class RedisAspect { 22 | /** 23 | * 是否开启redis缓存 true开启 false关闭 24 | */ 25 | @Value("${renren.redis.open}") 26 | private boolean open; 27 | 28 | @Around("execution(* xiaozhi.common.redis.RedisUtils.*(..))") 29 | public Object around(ProceedingJoinPoint point) throws Throwable { 30 | Object result = null; 31 | if (open) { 32 | try { 33 | result = point.proceed(); 34 | } catch (Exception e) { 35 | log.error("redis error", e); 36 | throw new RenException(ErrorCode.REDIS_ERROR); 37 | } 38 | } 39 | return result; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/config/AsyncConfig.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.config; 2 | 3 | import java.util.concurrent.Executor; 4 | import java.util.concurrent.RejectedExecutionHandler; 5 | import java.util.concurrent.ThreadPoolExecutor; 6 | 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 10 | import org.springframework.scheduling.annotation.EnableAsync; 11 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 12 | 13 | @Configuration 14 | @EnableAsync 15 | @EnableAspectJAutoProxy(exposeProxy = true) 16 | public class AsyncConfig { 17 | 18 | @Bean(name = "taskExecutor") 19 | public Executor taskExecutor() { 20 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 21 | executor.setCorePoolSize(2); 22 | executor.setMaxPoolSize(4); 23 | executor.setQueueCapacity(1000); 24 | executor.setThreadNamePrefix("AsyncThread-"); 25 | // 设置拒绝策略:由调用线程执行 26 | executor.setRejectedExecutionHandler(new RejectedExecutionHandler() { 27 | @Override 28 | public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { 29 | try { 30 | // 如果线程池已满,则由调用线程执行 31 | r.run(); 32 | } catch (Exception e) { 33 | throw new RuntimeException("执行异步任务失败", e); 34 | } 35 | } 36 | }); 37 | executor.initialize(); 38 | return executor; 39 | } 40 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/config/MybatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 7 | import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; 8 | import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; 9 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 10 | 11 | import xiaozhi.common.interceptor.DataFilterInterceptor; 12 | 13 | /** 14 | * mybatis-plus配置 15 | * Copyright (c) 人人开源 All rights reserved. 16 | * Website: https://www.renren.io 17 | */ 18 | @Configuration 19 | public class MybatisPlusConfig { 20 | 21 | @Bean 22 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 23 | MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); 24 | // 数据权限 25 | mybatisPlusInterceptor.addInnerInterceptor(new DataFilterInterceptor()); 26 | // 分页插件 27 | mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 28 | // 乐观锁 29 | mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); 30 | // 防止全表更新与删除 31 | mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); 32 | 33 | return mybatisPlusInterceptor; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.client.RestTemplate; 6 | 7 | /** 8 | * RestTemplate配置 9 | */ 10 | @Configuration 11 | public class RestTemplateConfig { 12 | 13 | @Bean 14 | public RestTemplate restTemplate() { 15 | return new RestTemplate(); 16 | } 17 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/dao/BaseDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.dao; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | 5 | /** 6 | * 基础Dao 7 | * Copyright (c) 人人开源 All rights reserved. 8 | * Website: https://www.renren.io 9 | */ 10 | public interface BaseDao extends BaseMapper { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.entity; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | import com.baomidou.mybatisplus.annotation.FieldFill; 7 | import com.baomidou.mybatisplus.annotation.TableField; 8 | import com.baomidou.mybatisplus.annotation.TableId; 9 | 10 | import lombok.Data; 11 | 12 | /** 13 | * 基础实体类,所有实体都需要继承 14 | * Copyright (c) 人人开源 All rights reserved. 15 | * Website: https://www.renren.io 16 | */ 17 | @Data 18 | public abstract class BaseEntity implements Serializable { 19 | /** 20 | * id 21 | */ 22 | @TableId 23 | private Long id; 24 | /** 25 | * 创建者 26 | */ 27 | @TableField(fill = FieldFill.INSERT) 28 | private Long creator; 29 | /** 30 | * 创建时间 31 | */ 32 | @TableField(fill = FieldFill.INSERT) 33 | private Date createDate; 34 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/exception/RenException.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.exception; 2 | 3 | import xiaozhi.common.utils.MessageUtils; 4 | 5 | /** 6 | * 自定义异常 7 | * Copyright (c) 人人开源 All rights reserved. 8 | * Website: https://www.renren.io 9 | */ 10 | public class RenException extends RuntimeException { 11 | 12 | private int code; 13 | private String msg; 14 | 15 | public RenException(int code) { 16 | this.code = code; 17 | this.msg = MessageUtils.getMessage(code); 18 | } 19 | 20 | public RenException(int code, String... params) { 21 | this.code = code; 22 | this.msg = MessageUtils.getMessage(code, params); 23 | } 24 | 25 | public RenException(int code, Throwable e) { 26 | super(e); 27 | this.code = code; 28 | this.msg = MessageUtils.getMessage(code); 29 | } 30 | 31 | public RenException(int code, Throwable e, String... params) { 32 | super(e); 33 | this.code = code; 34 | this.msg = MessageUtils.getMessage(code, params); 35 | } 36 | 37 | public RenException(String msg) { 38 | super(msg); 39 | this.code = ErrorCode.INTERNAL_SERVER_ERROR; 40 | this.msg = msg; 41 | } 42 | 43 | public RenException(String msg, Throwable e) { 44 | super(msg, e); 45 | this.code = ErrorCode.INTERNAL_SERVER_ERROR; 46 | this.msg = msg; 47 | } 48 | 49 | public String getMsg() { 50 | return msg; 51 | } 52 | 53 | public void setMsg(String msg) { 54 | this.msg = msg; 55 | } 56 | 57 | public int getCode() { 58 | return code; 59 | } 60 | 61 | public void setCode(int code) { 62 | this.code = code; 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/interceptor/DataScope.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.interceptor; 2 | 3 | /** 4 | * 数据范围 5 | * Copyright (c) 人人开源 All rights reserved. 6 | * Website: https://www.renren.io 7 | */ 8 | public class DataScope { 9 | private String sqlFilter; 10 | 11 | public DataScope(String sqlFilter) { 12 | this.sqlFilter = sqlFilter; 13 | } 14 | 15 | public String getSqlFilter() { 16 | return sqlFilter; 17 | } 18 | 19 | public void setSqlFilter(String sqlFilter) { 20 | this.sqlFilter = sqlFilter; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return this.sqlFilter; 26 | } 27 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/page/PageData.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.page; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.Data; 8 | 9 | /** 10 | * 分页工具类 11 | * Copyright (c) 人人开源 All rights reserved. 12 | * Website: https://www.renren.io 13 | */ 14 | @Data 15 | @Schema(description = "分页数据") 16 | public class PageData implements Serializable { 17 | @Schema(description = "总记录数") 18 | private int total; 19 | 20 | @Schema(description = "列表数据") 21 | private List list; 22 | 23 | /** 24 | * 分页 25 | * 26 | * @param list 列表数据 27 | * @param total 总记录数 28 | */ 29 | public PageData(List list, long total) { 30 | this.list = list; 31 | this.total = (int) total; 32 | } 33 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/page/TokenDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.page; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Data; 7 | 8 | /** 9 | * 令牌信息 10 | * 11 | * @author Jack 12 | */ 13 | @Data 14 | @Schema(description = "令牌信息") 15 | public class TokenDTO implements Serializable { 16 | 17 | @Schema(description = "密码") 18 | private String token; 19 | 20 | @Schema(description = "过期时间") 21 | private int expire; 22 | 23 | @Schema(description = "客户端指纹") 24 | private String clientHash; 25 | } 26 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/redis/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.redis; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.connection.RedisConnectionFactory; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.data.redis.serializer.RedisSerializer; 8 | import org.springframework.data.redis.serializer.StringRedisSerializer; 9 | 10 | import jakarta.annotation.Resource; 11 | 12 | /** 13 | * Redis配置 14 | * Copyright (c) 人人开源 All rights reserved. 15 | * Website: https://www.renren.io 16 | */ 17 | @Configuration 18 | public class RedisConfig { 19 | @Resource 20 | private RedisConnectionFactory factory; 21 | 22 | @Bean 23 | public RedisTemplate redisTemplate() { 24 | RedisTemplate redisTemplate = new RedisTemplate<>(); 25 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 26 | redisTemplate.setValueSerializer(RedisSerializer.json()); 27 | redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 28 | redisTemplate.setHashValueSerializer(RedisSerializer.json()); 29 | redisTemplate.setConnectionFactory(factory); 30 | 31 | return redisTemplate; 32 | } 33 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/service/CrudService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.service; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import xiaozhi.common.page.PageData; 8 | 9 | /** 10 | * CRUD基础服务接口 11 | * Copyright (c) 人人开源 All rights reserved. 12 | * Website: https://www.renren.io 13 | */ 14 | public interface CrudService extends BaseService { 15 | 16 | PageData page(Map params); 17 | 18 | List list(Map params); 19 | 20 | D get(Serializable id); 21 | 22 | void save(D dto); 23 | 24 | void update(D dto); 25 | 26 | void delete(Serializable[] ids); 27 | 28 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/user/UserDetail.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.user; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * 登录用户信息 9 | * Copyright (c) 人人开源 All rights reserved. 10 | * Website: https://www.renren.io 11 | */ 12 | @Data 13 | public class UserDetail implements Serializable { 14 | private Long id; 15 | private String username; 16 | private Integer superAdmin; 17 | private String token; 18 | private Integer status; 19 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/utils/ConvertUtils.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | 7 | import org.springframework.beans.BeanUtils; 8 | 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | /** 12 | * 转换工具类 13 | * Copyright (c) 人人开源 All rights reserved. 14 | * Website: https://www.renren.io 15 | */ 16 | @Slf4j 17 | public class ConvertUtils { 18 | public static T sourceToTarget(Object source, Class target) { 19 | if (source == null) { 20 | return null; 21 | } 22 | T targetObject = null; 23 | try { 24 | targetObject = target.getDeclaredConstructor().newInstance(); 25 | BeanUtils.copyProperties(source, targetObject); 26 | } catch (Exception e) { 27 | log.error("convert error ", e); 28 | } 29 | 30 | return targetObject; 31 | } 32 | 33 | public static List sourceToTarget(Collection sourceList, Class target) { 34 | if (sourceList == null) { 35 | return null; 36 | } 37 | 38 | List targetList = new ArrayList<>(sourceList.size()); 39 | try { 40 | for (Object source : sourceList) { 41 | T targetObject = target.getDeclaredConstructor().newInstance(); 42 | BeanUtils.copyProperties(source, targetObject); 43 | targetList.add(targetObject); 44 | } 45 | } catch (Exception e) { 46 | log.error("convert error ", e); 47 | } 48 | 49 | return targetList; 50 | } 51 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/utils/MessageUtils.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.utils; 2 | 3 | import org.springframework.context.MessageSource; 4 | import org.springframework.context.i18n.LocaleContextHolder; 5 | 6 | /** 7 | * 国际化 8 | * Copyright (c) 人人开源 All rights reserved. 9 | * Website: https://www.renren.io 10 | */ 11 | public class MessageUtils { 12 | private static MessageSource messageSource; 13 | 14 | static { 15 | messageSource = (MessageSource) SpringContextUtils.getBean("messageSource"); 16 | } 17 | 18 | public static String getMessage(int code) { 19 | return getMessage(code, new String[0]); 20 | } 21 | 22 | public static String getMessage(int code, String... params) { 23 | return messageSource.getMessage(code + "", params, LocaleContextHolder.getLocale()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/utils/PropertiesUtils.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.utils; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * 获取配置文件信息 7 | * Copyright xin-nan.com 8 | */ 9 | @Component 10 | public class PropertiesUtils { 11 | } 12 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/utils/Result.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.utils; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Data; 7 | import xiaozhi.common.exception.ErrorCode; 8 | 9 | /** 10 | * 响应数据 11 | * Copyright (c) 人人开源 All rights reserved. 12 | * Website: https://www.renren.io 13 | */ 14 | @Data 15 | @Schema(description = "响应") 16 | public class Result implements Serializable { 17 | 18 | /** 19 | * 编码:0表示成功,其他值表示失败 20 | */ 21 | @Schema(description = "编码:0表示成功,其他值表示失败") 22 | private int code = 0; 23 | /** 24 | * 消息内容 25 | */ 26 | @Schema(description = "消息内容") 27 | private String msg = "success"; 28 | /** 29 | * 响应数据 30 | */ 31 | @Schema(description = "响应数据") 32 | private T data; 33 | 34 | public Result ok(T data) { 35 | this.setData(data); 36 | return this; 37 | } 38 | 39 | public Result error() { 40 | this.code = ErrorCode.INTERNAL_SERVER_ERROR; 41 | this.msg = MessageUtils.getMessage(this.code); 42 | return this; 43 | } 44 | 45 | public Result error(int code) { 46 | this.code = code; 47 | this.msg = MessageUtils.getMessage(this.code); 48 | return this; 49 | } 50 | 51 | public Result error(int code, String msg) { 52 | this.code = code; 53 | this.msg = msg; 54 | return this; 55 | } 56 | 57 | public Result error(String msg) { 58 | this.code = ErrorCode.INTERNAL_SERVER_ERROR; 59 | this.msg = msg; 60 | return this; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/utils/SpringContextUtils.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.utils; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Spring Context 工具类 10 | */ 11 | @Component 12 | public class SpringContextUtils implements ApplicationContextAware { 13 | public static ApplicationContext applicationContext; 14 | 15 | @Override 16 | public void setApplicationContext(ApplicationContext applicationContext) 17 | throws BeansException { 18 | SpringContextUtils.applicationContext = applicationContext; 19 | } 20 | 21 | public static Object getBean(String name) { 22 | return applicationContext.getBean(name); 23 | } 24 | 25 | public static T getBean(Class requiredType) { 26 | return applicationContext.getBean(requiredType); 27 | } 28 | 29 | public static T getBean(String name, Class requiredType) { 30 | return applicationContext.getBean(name, requiredType); 31 | } 32 | 33 | public static boolean containsBean(String name) { 34 | return applicationContext.containsBean(name); 35 | } 36 | 37 | public static boolean isSingleton(String name) { 38 | return applicationContext.isSingleton(name); 39 | } 40 | 41 | public static Class getType(String name) { 42 | return applicationContext.getType(name); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/utils/TreeNode.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.utils; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import lombok.Data; 8 | 9 | /** 10 | * 树节点,所有需要实现树节点的,都需要继承该类 11 | * Copyright (c) 人人开源 All rights reserved. 12 | * Website: https://www.renren.io 13 | */ 14 | @Data 15 | public class TreeNode implements Serializable { 16 | 17 | /** 18 | * 主键 19 | */ 20 | private Long id; 21 | /** 22 | * 上级ID 23 | */ 24 | private Long pid; 25 | /** 26 | * 子节点列表 27 | */ 28 | private List children = new ArrayList<>(); 29 | 30 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/validator/group/AddGroup.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.validator.group; 2 | 3 | /** 4 | * 新增 Group 5 | */ 6 | public interface AddGroup { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/validator/group/DefaultGroup.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.validator.group; 2 | 3 | /** 4 | * 默认 Group 5 | */ 6 | public interface DefaultGroup { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/validator/group/UpdateGroup.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.validator.group; 2 | 3 | /** 4 | * 修改 Group 5 | */ 6 | public interface UpdateGroup { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/xss/SqlFilter.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.xss; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import xiaozhi.common.exception.ErrorCode; 6 | import xiaozhi.common.exception.RenException; 7 | 8 | /** 9 | * SQL过滤 10 | * Copyright (c) 人人开源 All rights reserved. 11 | * Website: https://www.renren.io 12 | */ 13 | public class SqlFilter { 14 | 15 | /** 16 | * SQL注入过滤 17 | * 18 | * @param str 待验证的字符串 19 | */ 20 | public static String sqlInject(String str) { 21 | if (StringUtils.isBlank(str)) { 22 | return null; 23 | } 24 | // 去掉'|"|;|\字符 25 | str = StringUtils.replace(str, "'", ""); 26 | str = StringUtils.replace(str, "\"", ""); 27 | str = StringUtils.replace(str, ";", ""); 28 | str = StringUtils.replace(str, "\\", ""); 29 | 30 | // 转换成小写 31 | str = str.toLowerCase(); 32 | 33 | // 非法字符 34 | String[] keywords = { "master", "truncate", "insert", "select", "delete", "update", "declare", "alter", 35 | "drop" }; 36 | 37 | // 判断是否包含非法字符 38 | for (String keyword : keywords) { 39 | if (str.contains(keyword)) { 40 | throw new RenException(ErrorCode.INVALID_SYMBOL); 41 | } 42 | } 43 | 44 | return str; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/xss/XssConfig.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.xss; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 5 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.util.PathMatcher; 9 | 10 | import jakarta.servlet.DispatcherType; 11 | 12 | /** 13 | * XSS 配置文件 14 | * Copyright (c) 人人开源 All rights reserved. 15 | * Website: https://www.renren.io 16 | */ 17 | @Configuration 18 | @EnableConfigurationProperties(XssProperties.class) 19 | @ConditionalOnProperty(prefix = "renren.xss", value = "enabled") 20 | public class XssConfig { 21 | 22 | @Bean 23 | public FilterRegistrationBean xssFilter(XssProperties properties, PathMatcher pathMatcher) { 24 | FilterRegistrationBean bean = new FilterRegistrationBean<>(); 25 | bean.setDispatcherTypes(DispatcherType.REQUEST); 26 | bean.setFilter(new XssFilter(properties, pathMatcher)); 27 | bean.addUrlPatterns("/*"); 28 | bean.setOrder(Integer.MAX_VALUE); 29 | bean.setName("xssFilter"); 30 | 31 | return bean; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/common/xss/XssProperties.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.common.xss; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | 8 | import lombok.Data; 9 | 10 | /** 11 | * XSS 配置项 12 | * Copyright (c) 人人开源 All rights reserved. 13 | * Website: https://www.renren.io 14 | */ 15 | @Data 16 | @ConfigurationProperties(prefix = "renren.xss") 17 | public class XssProperties { 18 | /** 19 | * 是否开启 XSS 20 | */ 21 | private boolean enabled; 22 | /** 23 | * 排除的URL列表 24 | */ 25 | private List excludeUrls = Collections.emptyList(); 26 | } 27 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentChatHistoryController.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.controller; 2 | 3 | import org.springframework.web.bind.annotation.PostMapping; 4 | import org.springframework.web.bind.annotation.RequestBody; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import io.swagger.v3.oas.annotations.Operation; 9 | import io.swagger.v3.oas.annotations.tags.Tag; 10 | import jakarta.validation.Valid; 11 | import lombok.RequiredArgsConstructor; 12 | import xiaozhi.common.utils.Result; 13 | import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO; 14 | import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService; 15 | 16 | @Tag(name = "智能体聊天历史管理") 17 | @RequiredArgsConstructor 18 | @RestController 19 | @RequestMapping("/agent/chat-history") 20 | public class AgentChatHistoryController { 21 | private final AgentChatHistoryBizService agentChatHistoryBizService; 22 | 23 | /** 24 | * 小智服务聊天上报请求 25 | *

26 | * 小智服务聊天上报请求,包含Base64编码的音频数据和相关信息。 27 | * 28 | * @param request 包含上传文件及相关信息的请求对象 29 | */ 30 | @Operation(summary = "小智服务聊天上报请求") 31 | @PostMapping("/report") 32 | public Result uploadFile(@Valid @RequestBody AgentChatHistoryReportDTO request) { 33 | Boolean result = agentChatHistoryBizService.report(request); 34 | return new Result().ok(result); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.dao; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.apache.ibatis.annotations.Param; 5 | 6 | import org.apache.ibatis.annotations.Select; 7 | import xiaozhi.common.dao.BaseDao; 8 | import xiaozhi.modules.agent.entity.AgentEntity; 9 | 10 | @Mapper 11 | public interface AgentDao extends BaseDao { 12 | /** 13 | * 获取智能体的设备数量 14 | * 15 | * @param agentId 智能体ID 16 | * @return 设备数量 17 | */ 18 | Integer getDeviceCountByAgentId(@Param("agentId") String agentId); 19 | 20 | /** 21 | * 根据设备MAC地址查询对应设备的默认智能体信息 22 | * 23 | * @param macAddress 设备MAC地址 24 | * @return 默认智能体信息 25 | */ 26 | @Select(" SELECT a.* FROM ai_device d " + 27 | " LEFT JOIN ai_agent a ON d.agent_id = a.id " + 28 | " WHERE d.mac_address = #{macAddress} " + 29 | " ORDER BY d.id DESC LIMIT 1") 30 | AgentEntity getDefaultAgentByMacAddress(@Param("macAddress") String macAddress); 31 | } 32 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentTemplateDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.dao; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | 7 | import xiaozhi.modules.agent.entity.AgentTemplateEntity; 8 | 9 | /** 10 | * @author chenerlei 11 | * @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Mapper 12 | * @createDate 2025-03-22 11:48:18 13 | */ 14 | @Mapper 15 | public interface AgentTemplateDao extends BaseMapper { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatAudioDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.dao; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | 7 | import xiaozhi.modules.agent.entity.AgentChatAudioEntity; 8 | 9 | /** 10 | * {@link AgentChatAudioEntity} 智能体聊天音频数据Dao对象 11 | * 12 | * @author Goody 13 | * @version 1.0, 2025/5/8 14 | * @since 1.0.0 15 | */ 16 | @Mapper 17 | public interface AiAgentChatAudioDao extends BaseMapper { 18 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatHistoryDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.dao; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | 7 | import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; 8 | 9 | /** 10 | * {@link AgentChatHistoryEntity} 智能体聊天历史记录Dao对象 11 | * 12 | * @author Goody 13 | * @version 1.0, 2025/4/30 14 | * @since 1.0.0 15 | */ 16 | @Mapper 17 | public interface AiAgentChatHistoryDao extends BaseMapper { 18 | /** 19 | * 根据智能体ID删除音频 20 | * 21 | * @param agentId 智能体ID 22 | */ 23 | void deleteAudioByAgentId(String agentId); 24 | 25 | /** 26 | * 根据智能体ID删除聊天历史记录 27 | * 28 | * @param agentId 智能体ID 29 | */ 30 | void deleteHistoryByAgentId(String agentId); 31 | 32 | /** 33 | * 根据智能体ID删除音频ID 34 | * 35 | * @param agentId 智能体ID 36 | */ 37 | void deleteAudioIdByAgentId(String agentId); 38 | } 39 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.dto; 2 | 3 | import java.util.Date; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Data; 7 | 8 | /** 9 | * 智能体聊天记录DTO 10 | */ 11 | @Data 12 | @Schema(description = "智能体聊天记录") 13 | public class AgentChatHistoryDTO { 14 | @Schema(description = "创建时间") 15 | private Date createdAt; 16 | 17 | @Schema(description = "消息类型: 1-用户, 2-智能体") 18 | private Byte chatType; 19 | 20 | @Schema(description = "聊天内容") 21 | private String content; 22 | 23 | @Schema(description = "音频ID") 24 | private String audioId; 25 | 26 | @Schema(description = "MAC地址") 27 | private String macAddress; 28 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryReportDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | import lombok.Data; 7 | 8 | /** 9 | * 小智设备聊天上报请求 10 | * 11 | * @author Haotian 12 | * @version 1.0, 2025/5/8 13 | */ 14 | @Data 15 | @Schema(description = "小智设备聊天上报请求") 16 | public class AgentChatHistoryReportDTO { 17 | @Schema(description = "MAC地址", example = "00:11:22:33:44:55") 18 | @NotBlank 19 | private String macAddress; 20 | @Schema(description = "会话ID", example = "79578c31-f1fb-426a-900e-1e934215f05a") 21 | @NotBlank 22 | private String sessionId; 23 | @Schema(description = "消息类型: 1-用户, 2-智能体", example = "1") 24 | @NotNull 25 | private Byte chatType; 26 | @Schema(description = "聊天内容", example = "你好呀") 27 | @NotBlank 28 | private String content; 29 | @Schema(description = "base64编码的opus音频数据", example = "") 30 | private String audioBase64; 31 | } 32 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.dto; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * 智能体会话列表DTO 9 | */ 10 | @Data 11 | public class AgentChatSessionDTO { 12 | /** 13 | * 会话ID 14 | */ 15 | private String sessionId; 16 | 17 | /** 18 | * 会话时间 19 | */ 20 | private LocalDateTime createdAt; 21 | 22 | /** 23 | * 聊天条数 24 | */ 25 | private Integer chatCount; 26 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentCreateDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import jakarta.validation.constraints.NotBlank; 7 | import lombok.Data; 8 | 9 | /** 10 | * 智能体创建DTO 11 | * 专用于新增智能体,不包含id、agentCode和sort字段,这些字段由系统自动生成/设置默认值 12 | */ 13 | @Data 14 | @Schema(description = "智能体创建对象") 15 | public class AgentCreateDTO implements Serializable { 16 | private static final long serialVersionUID = 1L; 17 | 18 | @Schema(description = "智能体名称", example = "客服助手") 19 | @NotBlank(message = "智能体名称不能为空") 20 | private String agentName; 21 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.dto; 2 | 3 | import java.util.Date; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Data; 7 | 8 | /** 9 | * 智能体数据传输对象 10 | * 用于在服务层和控制器层之间传递智能体相关的数据 11 | */ 12 | @Data 13 | @Schema(description = "智能体对象") 14 | public class AgentDTO { 15 | @Schema(description = "智能体编码", example = "AGT_1234567890") 16 | private String id; 17 | 18 | @Schema(description = "智能体名称", example = "客服助手") 19 | private String agentName; 20 | 21 | @Schema(description = "语音合成模型名称", example = "tts_model_01") 22 | private String ttsModelName; 23 | 24 | @Schema(description = "音色名称", example = "voice_01") 25 | private String ttsVoiceName; 26 | 27 | @Schema(description = "大语言模型名称", example = "llm_model_01") 28 | private String llmModelName; 29 | 30 | @Schema(description = "记忆模型ID", example = "mem_model_01") 31 | private String memModelId; 32 | 33 | @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助") 34 | private String systemPrompt; 35 | 36 | @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + 37 | "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) 38 | private String summaryMemory; 39 | 40 | @Schema(description = "最后连接时间", example = "2024-03-20 10:00:00") 41 | private Date lastConnectedAt; 42 | 43 | @Schema(description = "设备数量", example = "10") 44 | private Integer deviceCount; 45 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentMemoryDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Data; 7 | 8 | /** 9 | * 智能体记忆更新DTO 10 | */ 11 | @Data 12 | @Schema(description = "智能体记忆更新对象") 13 | public class AgentMemoryDTO implements Serializable { 14 | private static final long serialVersionUID = 1L; 15 | 16 | @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + 17 | "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) 18 | private String summaryMemory; 19 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatAudioEntity.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | 7 | import lombok.Data; 8 | 9 | /** 10 | * 智能体聊天音频数据表 11 | * 12 | * @author Goody 13 | * @version 1.0, 2025/5/8 14 | * @since 1.0.0 15 | */ 16 | @Data 17 | @TableName("ai_agent_chat_audio") 18 | public class AgentChatAudioEntity { 19 | /** 20 | * 主键ID 21 | */ 22 | @TableId(type = IdType.ASSIGN_UUID) 23 | private String id; 24 | 25 | /** 26 | * 音频opus数据 27 | */ 28 | private byte[] audio; 29 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatAudioService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | 5 | import xiaozhi.modules.agent.entity.AgentChatAudioEntity; 6 | 7 | /** 8 | * 智能体聊天音频数据表处理service 9 | * 10 | * @author Goody 11 | * @version 1.0, 2025/5/8 12 | * @since 1.0.0 13 | */ 14 | public interface AgentChatAudioService extends IService { 15 | /** 16 | * 保存音频数据 17 | * 18 | * @param audioData 音频数据 19 | * @return 音频ID 20 | */ 21 | String saveAudio(byte[] audioData); 22 | 23 | /** 24 | * 获取音频数据 25 | * 26 | * @param audioId 音频ID 27 | * @return 音频数据 28 | */ 29 | byte[] getAudio(String audioId); 30 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.service; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import com.baomidou.mybatisplus.extension.service.IService; 7 | 8 | import xiaozhi.common.page.PageData; 9 | import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; 10 | import xiaozhi.modules.agent.dto.AgentChatSessionDTO; 11 | import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; 12 | 13 | /** 14 | * 智能体聊天记录表处理service 15 | * 16 | * @author Goody 17 | * @version 1.0, 2025/4/30 18 | * @since 1.0.0 19 | */ 20 | public interface AgentChatHistoryService extends IService { 21 | 22 | /** 23 | * 根据智能体ID获取会话列表 24 | * 25 | * @param params 查询参数,包含agentId、page、limit 26 | * @return 分页的会话列表 27 | */ 28 | PageData getSessionListByAgentId(Map params); 29 | 30 | /** 31 | * 根据会话ID获取聊天记录列表 32 | * 33 | * @param agentId 智能体ID 34 | * @param sessionId 会话ID 35 | * @return 聊天记录列表 36 | */ 37 | List getChatHistoryBySessionId(String agentId, String sessionId); 38 | 39 | /** 40 | * 根据智能体ID删除聊天记录 41 | * 42 | * @param agentId 智能体ID 43 | * @param deleteAudio 是否删除音频 44 | * @param deleteText 是否删除文本 45 | */ 46 | void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText); 47 | } 48 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | 5 | import xiaozhi.modules.agent.entity.AgentTemplateEntity; 6 | 7 | /** 8 | * @author chenerlei 9 | * @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service 10 | * @createDate 2025-03-22 11:48:18 11 | */ 12 | public interface AgentTemplateService extends IService { 13 | 14 | /** 15 | * 获取默认模板 16 | * 17 | * @return 默认模板实体 18 | */ 19 | AgentTemplateEntity getDefaultTemplate(); 20 | 21 | /** 22 | * 更新默认模板中的模型ID 23 | * 24 | * @param modelType 模型类型 25 | * @param modelId 模型ID 26 | */ 27 | void updateDefaultTemplateModelId(String modelType, String modelId); 28 | } 29 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/AgentChatHistoryBizService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.service.biz; 2 | 3 | import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO; 4 | 5 | /** 6 | * 智能体聊天历史业务逻辑层 7 | * 8 | * @author Goody 9 | * @version 1.0, 2025/4/30 10 | * @since 1.0.0 11 | */ 12 | public interface AgentChatHistoryBizService { 13 | 14 | /** 15 | * 聊天上报方法 16 | * 17 | * @param agentChatHistoryReportDTO 包含聊天上报所需信息的输入对象 18 | * 例如:设备MAC地址、文件类型、内容等 19 | * @return 上传结果,true表示成功,false表示失败 20 | */ 21 | Boolean report(AgentChatHistoryReportDTO agentChatHistoryReportDTO); 22 | } 23 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatAudioServiceImpl.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.service.impl; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | 7 | import xiaozhi.modules.agent.dao.AiAgentChatAudioDao; 8 | import xiaozhi.modules.agent.entity.AgentChatAudioEntity; 9 | import xiaozhi.modules.agent.service.AgentChatAudioService; 10 | 11 | /** 12 | * 智能体聊天音频数据表处理service {@link AgentChatAudioService} impl 13 | * 14 | * @author Goody 15 | * @version 1.0, 2025/5/8 16 | * @since 1.0.0 17 | */ 18 | @Service 19 | public class AgentChatAudioServiceImpl extends ServiceImpl 20 | implements AgentChatAudioService { 21 | @Override 22 | public String saveAudio(byte[] audioData) { 23 | AgentChatAudioEntity entity = new AgentChatAudioEntity(); 24 | entity.setAudio(audioData); 25 | save(entity); 26 | return entity.getId(); 27 | } 28 | 29 | @Override 30 | public byte[] getAudio(String audioId) { 31 | AgentChatAudioEntity entity = getById(audioId); 32 | return entity != null ? entity.getAudio() : null; 33 | } 34 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentTemplateVO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.agent.vo; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import xiaozhi.modules.agent.entity.AgentTemplateEntity; 6 | 7 | @Data 8 | @EqualsAndHashCode(callSuper = true) 9 | public class AgentTemplateVO extends AgentTemplateEntity { 10 | // 角色音色 11 | private String ttsModelName; 12 | 13 | // 角色模型 14 | private String llmModelName; 15 | } 16 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/config/controller/ConfigController.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.config.controller; 2 | 3 | import org.springframework.web.bind.annotation.PostMapping; 4 | import org.springframework.web.bind.annotation.RequestBody; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import io.swagger.v3.oas.annotations.Operation; 9 | import io.swagger.v3.oas.annotations.tags.Tag; 10 | import jakarta.validation.Valid; 11 | import lombok.AllArgsConstructor; 12 | import xiaozhi.common.utils.Result; 13 | import xiaozhi.common.validator.ValidatorUtils; 14 | import xiaozhi.modules.config.dto.AgentModelsDTO; 15 | import xiaozhi.modules.config.service.ConfigService; 16 | 17 | /** 18 | * xiaozhi-server 配置获取 19 | * 20 | * @since 1.0.0 21 | */ 22 | @RestController 23 | @RequestMapping("config") 24 | @Tag(name = "参数管理") 25 | @AllArgsConstructor 26 | public class ConfigController { 27 | private final ConfigService configService; 28 | 29 | @PostMapping("server-base") 30 | @Operation(summary = "获取配置") 31 | public Result getConfig() { 32 | Object config = configService.getConfig(true); 33 | return new Result().ok(config); 34 | } 35 | 36 | @PostMapping("agent-models") 37 | @Operation(summary = "获取智能体模型") 38 | public Result getAgentModels(@Valid @RequestBody AgentModelsDTO dto) { 39 | // 效验数据 40 | ValidatorUtils.validateEntity(dto); 41 | Object models = configService.getAgentModels(dto.getMacAddress(), dto.getSelectedModule()); 42 | return new Result().ok(models); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/config/dto/AgentModelsDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.config.dto; 2 | 3 | import java.util.Map; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import jakarta.validation.constraints.NotBlank; 7 | import jakarta.validation.constraints.NotNull; 8 | import lombok.Data; 9 | 10 | @Data 11 | @Schema(description = "获取智能体模型配置DTO") 12 | public class AgentModelsDTO { 13 | 14 | @NotBlank(message = "设备MAC地址不能为空") 15 | @Schema(description = "设备MAC地址") 16 | private String macAddress; 17 | 18 | @NotBlank(message = "客户端ID不能为空") 19 | @Schema(description = "客户端ID") 20 | private String clientId; 21 | 22 | @NotNull(message = "客户端已实例化的模型不能为空") 23 | @Schema(description = "客户端已实例化的模型") 24 | private Map selectedModule; 25 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/config/init/SystemInitConfig.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.config.init; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.DependsOn; 6 | 7 | import jakarta.annotation.PostConstruct; 8 | import xiaozhi.common.constant.Constant; 9 | import xiaozhi.common.redis.RedisKeys; 10 | import xiaozhi.common.redis.RedisUtils; 11 | import xiaozhi.modules.config.service.ConfigService; 12 | import xiaozhi.modules.sys.service.SysParamsService; 13 | 14 | @Configuration 15 | @DependsOn("liquibase") 16 | public class SystemInitConfig { 17 | 18 | @Autowired 19 | private SysParamsService sysParamsService; 20 | 21 | @Autowired 22 | private ConfigService configService; 23 | 24 | @Autowired 25 | private RedisUtils redisUtils; 26 | 27 | @PostConstruct 28 | public void init() { 29 | // 检查版本号 30 | String redisVersion = (String) redisUtils.get(RedisKeys.getVersionKey()); 31 | if (!Constant.VERSION.equals(redisVersion)) { 32 | // 如果版本不一致,清空Redis 33 | redisUtils.emptyAll(); 34 | // 存储新版本号 35 | redisUtils.set(RedisKeys.getVersionKey(), Constant.VERSION); 36 | } 37 | 38 | sysParamsService.initServerSecret(); 39 | configService.getConfig(false); 40 | } 41 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/config/service/ConfigService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.config.service; 2 | 3 | import java.util.Map; 4 | 5 | public interface ConfigService { 6 | /** 7 | * 获取服务器配置 8 | * 9 | * @param isCache 是否缓存 10 | * @return 配置信息 11 | */ 12 | Object getConfig(Boolean isCache); 13 | 14 | /** 15 | * 获取智能体模型配置 16 | * 17 | * @param macAddress MAC地址 18 | * @param selectedModule 客户端已实例化的模型 19 | * @return 模型配置信息 20 | */ 21 | Map getAgentModels(String macAddress, Map selectedModule); 22 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.device.dao; 2 | 3 | import java.util.Date; 4 | 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 8 | 9 | import xiaozhi.modules.device.entity.DeviceEntity; 10 | 11 | @Mapper 12 | public interface DeviceDao extends BaseMapper { 13 | /** 14 | * 获取此智能体全部设备的最后连接时间 15 | * 16 | * @param agentId 智能体id 17 | * @return 18 | */ 19 | Date getAllLastConnectedAtByAgentId(String agentId); 20 | 21 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/device/dao/OtaDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.device.dao; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | 7 | import xiaozhi.modules.device.entity.OtaEntity; 8 | 9 | /** 10 | * OTA固件管理 11 | */ 12 | @Mapper 13 | public interface OtaDao extends BaseMapper { 14 | 15 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceBindDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.device.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | 7 | /** 8 | * 设备绑定的DTO 9 | * 10 | * @author zjy 11 | * @since 2025-3-28 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @Schema(description = "设备连接头信息") 16 | public class DeviceBindDTO { 17 | 18 | @Schema(description = "mac地址") 19 | private String macAddress; 20 | 21 | @Schema(description = "所属用户id") 22 | private Long userId; 23 | 24 | @Schema(description = "智能体id") 25 | private String agentId; 26 | 27 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/device/dto/DevicePageUserDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.device.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.Min; 5 | import lombok.Data; 6 | 7 | /** 8 | * 查询所有设备的DTO 9 | * 10 | * @author zjy 11 | * @since 2025-3-21 12 | */ 13 | @Data 14 | @Schema(description = "查询所有设备的DTO") 15 | public class DevicePageUserDTO { 16 | 17 | @Schema(description = "设备关键词") 18 | private String keywords; 19 | 20 | @Schema(description = "页数") 21 | @Min(value = 0, message = "{page.number}") 22 | private String page; 23 | 24 | @Schema(description = "显示列数") 25 | @Min(value = 0, message = "{limit.number}") 26 | private String limit; 27 | } 28 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceRegisterDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.device.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | /** 10 | * 设备注册头信息 11 | * 12 | * @author zjy 13 | * @since 2025-3-28 14 | */ 15 | @Setter 16 | @Getter 17 | @Schema(description = "设备注册头信息") 18 | public class DeviceRegisterDTO implements Serializable { 19 | 20 | @Schema(description = "mac地址") 21 | private String macAddress; 22 | 23 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.device.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import jakarta.validation.constraints.NotBlank; 7 | import lombok.Data; 8 | 9 | /** 10 | * 设备解绑表单 11 | */ 12 | @Data 13 | @Schema(description = "设备解绑表单") 14 | public class DeviceUnBindDTO implements Serializable { 15 | 16 | @Schema(description = "设备ID") 17 | @NotBlank(message = "设备ID不能为空") 18 | private String deviceId; 19 | 20 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/device/service/OtaService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.device.service; 2 | 3 | import java.util.Map; 4 | 5 | import xiaozhi.common.page.PageData; 6 | import xiaozhi.common.service.BaseService; 7 | import xiaozhi.modules.device.entity.OtaEntity; 8 | 9 | /** 10 | * OTA固件管理 11 | */ 12 | public interface OtaService extends BaseService { 13 | PageData page(Map params); 14 | 15 | boolean save(OtaEntity entity); 16 | 17 | void update(OtaEntity entity); 18 | 19 | void delete(String[] ids); 20 | 21 | OtaEntity getLatestOta(String type); 22 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.device.vo; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class DeviceOtaVO { 7 | private Activation activation; 8 | private Mqtt mqtt; 9 | private ServerTime server_time; 10 | private Firmware firmware; 11 | private String error; 12 | 13 | @Data 14 | public static class Activation { 15 | private String code; 16 | private String message; 17 | 18 | public Activation() { 19 | } 20 | 21 | public Activation(String code, String message) { 22 | this.code = code; 23 | this.message = message; 24 | } 25 | } 26 | 27 | @Data 28 | public class Mqtt { 29 | 30 | } 31 | 32 | @Data 33 | public static class ServerTime { 34 | private Long timestamp; 35 | private Integer timezone_offset; 36 | 37 | public ServerTime() { 38 | } 39 | 40 | public ServerTime(Long timestamp, Integer timezone_offset) { 41 | this.timestamp = timestamp; 42 | this.timezone_offset = timezone_offset; 43 | } 44 | } 45 | 46 | @Data 47 | public static class Firmware { 48 | private String version; 49 | private String url; 50 | 51 | public Firmware() { 52 | } 53 | 54 | public Firmware(String version, String url) { 55 | this.version = version; 56 | this.url = url; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/device/vo/UserShowDeviceListVO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.device.vo; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Schema(description = "用户显示设备列表VO") 8 | public class UserShowDeviceListVO { 9 | 10 | @Schema(description = "app版本") 11 | private String appVersion; 12 | 13 | @Schema(description = "绑定用户名称") 14 | private String bindUserName; 15 | 16 | @Schema(description = "设备型号") 17 | private String deviceType; 18 | 19 | @Schema(description = "设备唯一标识符") 20 | private String id; 21 | 22 | @Schema(description = "mac地址") 23 | private String macAddress; 24 | 25 | @Schema(description = "开启OTA") 26 | private Integer otaUpgrade; 27 | 28 | @Schema(description = "最近对话时间") 29 | private String recentChatTime; 30 | 31 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/model/dao/ModelConfigDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.model.dao; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import xiaozhi.common.dao.BaseDao; 9 | import xiaozhi.modules.model.entity.ModelConfigEntity; 10 | 11 | @Mapper 12 | public interface ModelConfigDao extends BaseDao { 13 | 14 | /** 15 | * get model_code list 16 | */ 17 | List getModelCodeList(@Param("modelType") String modelType, @Param("modelName") String modelName); 18 | } 19 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/model/dao/ModelProviderDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.model.dao; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import xiaozhi.common.dao.BaseDao; 9 | import xiaozhi.modules.model.entity.ModelProviderEntity; 10 | 11 | @Mapper 12 | public interface ModelProviderDao extends BaseDao { 13 | 14 | List getFieldList(@Param("modelType") String modelType, @Param("providerCode") String providerCode); 15 | } 16 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelBasicInfoDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.model.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ModelBasicInfoDTO { 7 | private String id; 8 | private String modelName; 9 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigBodyDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.model.dto; 2 | 3 | import java.io.Serial; 4 | 5 | import cn.hutool.json.JSONObject; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.Data; 8 | 9 | @Data 10 | @Schema(description = "模型供应器/商") 11 | public class ModelConfigBodyDTO { 12 | 13 | @Serial 14 | private static final long serialVersionUID = 1L; 15 | 16 | // @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)") 17 | // private String modelType; 18 | // 19 | @Schema(description = "模型编码(如AliLLM、DoubaoTTS)") 20 | private String modelCode; 21 | 22 | @Schema(description = "模型名称") 23 | private String modelName; 24 | 25 | @Schema(description = "是否默认配置(0否 1是)") 26 | private Integer isDefault; 27 | 28 | @Schema(description = "是否启用") 29 | private Integer isEnabled; 30 | 31 | @Schema(description = "模型配置(JSON格式)") 32 | private JSONObject configJson; 33 | 34 | @Schema(description = "官方文档链接") 35 | private String docLink; 36 | 37 | @Schema(description = "备注") 38 | private String remark; 39 | 40 | @Schema(description = "排序") 41 | private Integer sort; 42 | } 43 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.model.dto; 2 | 3 | import java.io.Serial; 4 | import java.io.Serializable; 5 | 6 | import cn.hutool.json.JSONObject; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.Data; 9 | 10 | @Data 11 | @Schema(description = "模型供应器/商") 12 | public class ModelConfigDTO implements Serializable { 13 | 14 | @Serial 15 | private static final long serialVersionUID = 1L; 16 | 17 | @Schema(description = "主键") 18 | private String id; 19 | 20 | @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)") 21 | private String modelType; 22 | 23 | @Schema(description = "模型编码(如AliLLM、DoubaoTTS)") 24 | private String modelCode; 25 | 26 | @Schema(description = "模型名称") 27 | private String modelName; 28 | 29 | @Schema(description = "是否默认配置(0否 1是)") 30 | private Integer isDefault; 31 | 32 | @Schema(description = "是否启用") 33 | private Integer isEnabled; 34 | 35 | @Schema(description = "模型配置(JSON格式)") 36 | private JSONObject configJson; 37 | 38 | @Schema(description = "官方文档链接") 39 | private String docLink; 40 | 41 | @Schema(description = "备注") 42 | private String remark; 43 | 44 | @Schema(description = "排序") 45 | private Integer sort; 46 | } 47 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.model.dto; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | import com.baomidou.mybatisplus.annotation.FieldFill; 7 | import com.baomidou.mybatisplus.annotation.TableField; 8 | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; 9 | 10 | import io.swagger.v3.oas.annotations.media.Schema; 11 | import lombok.Data; 12 | 13 | @Data 14 | @Schema(description = "模型供应器/商") 15 | public class ModelProviderDTO implements Serializable { 16 | // 17 | // @Schema(description = "主键") 18 | // private Long id; 19 | 20 | @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)") 21 | private String modelType; 22 | 23 | @Schema(description = "供应器类型") 24 | private String providerCode; 25 | 26 | @Schema(description = "供应器名称") 27 | private String name; 28 | 29 | @Schema(description = "供应器字段列表(JSON格式)") 30 | @TableField(typeHandler = JacksonTypeHandler.class) 31 | private String fields; 32 | 33 | @Schema(description = "排序") 34 | private Integer sort; 35 | 36 | @Schema(description = "更新者") 37 | @TableField(fill = FieldFill.UPDATE) 38 | private Long updater; 39 | 40 | @Schema(description = "更新时间") 41 | @TableField(fill = FieldFill.UPDATE) 42 | private Date updateDate; 43 | 44 | @Schema(description = "创建者") 45 | @TableField(fill = FieldFill.INSERT) 46 | private Long creator; 47 | 48 | @Schema(description = "创建时间") 49 | @TableField(fill = FieldFill.INSERT) 50 | private Date createDate; 51 | } 52 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/model/dto/VoiceDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.model.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Schema(description = "音色信息") 14 | public class VoiceDTO implements Serializable { 15 | private static final long serialVersionUID = 1L; 16 | 17 | @Schema(description = "音色ID") 18 | private String id; 19 | 20 | @Schema(description = "音色名称") 21 | private String name; 22 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.model.entity; 2 | 3 | import java.util.Date; 4 | 5 | import com.baomidou.mybatisplus.annotation.IdType; 6 | import com.baomidou.mybatisplus.annotation.TableField; 7 | import com.baomidou.mybatisplus.annotation.TableId; 8 | import com.baomidou.mybatisplus.annotation.TableName; 9 | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; 10 | 11 | import io.swagger.v3.oas.annotations.media.Schema; 12 | import lombok.Data; 13 | 14 | @Data 15 | @TableName("ai_model_provider") 16 | @Schema(description = "模型供应器表") 17 | public class ModelProviderEntity { 18 | 19 | @TableId(type = IdType.ASSIGN_UUID) 20 | @Schema(description = "主键") 21 | private String id; 22 | 23 | @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)") 24 | private String modelType; 25 | 26 | @Schema(description = "供应器类型,如 openai、") 27 | private String providerCode; 28 | 29 | @Schema(description = "供应器名称") 30 | private String name; 31 | 32 | @Schema(description = "供应器字段列表(JSON格式)") 33 | @TableField(typeHandler = JacksonTypeHandler.class) 34 | private String fields; 35 | 36 | @Schema(description = "排序") 37 | private Integer sort; 38 | 39 | @Schema(description = "创建者") 40 | private Long creator; 41 | 42 | @Schema(description = "创建时间") 43 | private Date createDate; 44 | 45 | @Schema(description = "更新者") 46 | private Long updater; 47 | 48 | @Schema(description = "更新时间") 49 | private Date updateDate; 50 | } 51 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.model.service; 2 | 3 | import java.util.List; 4 | 5 | import xiaozhi.common.page.PageData; 6 | import xiaozhi.common.service.BaseService; 7 | import xiaozhi.modules.model.dto.ModelBasicInfoDTO; 8 | import xiaozhi.modules.model.dto.ModelConfigBodyDTO; 9 | import xiaozhi.modules.model.dto.ModelConfigDTO; 10 | import xiaozhi.modules.model.entity.ModelConfigEntity; 11 | 12 | public interface ModelConfigService extends BaseService { 13 | 14 | List getModelCodeList(String modelType, String modelName); 15 | 16 | PageData getPageList(String modelType, String modelName, String page, String limit); 17 | 18 | ModelConfigDTO add(String modelType, String provideCode, ModelConfigBodyDTO modelConfigBodyDTO); 19 | 20 | ModelConfigDTO edit(String modelType, String provideCode, String id, ModelConfigBodyDTO modelConfigBodyDTO); 21 | 22 | void delete(String id); 23 | 24 | /** 25 | * 根据ID获取模型名称 26 | * 27 | * @param id 模型ID 28 | * @return 模型名称 29 | */ 30 | String getModelNameById(String id); 31 | 32 | /** 33 | * 根据ID获取模型配置 34 | * 35 | * @param id 模型ID 36 | * @param isCache 是否缓存 37 | * @return 模型配置实体 38 | */ 39 | ModelConfigEntity getModelById(String id, boolean isCache); 40 | 41 | /** 42 | * 设置默认模型 43 | * 44 | * @param modelType 模型类型 45 | * @param isDefault 是否默认 46 | */ 47 | void setDefaultModel(String modelType, int isDefault); 48 | } 49 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.model.service; 2 | 3 | import java.util.List; 4 | 5 | import xiaozhi.modules.model.dto.ModelProviderDTO; 6 | import xiaozhi.modules.model.entity.ModelProviderEntity; 7 | 8 | public interface ModelProviderService { 9 | 10 | // List getModelNames(String modelType, String modelName); 11 | 12 | List getListByModelType(String modelType); 13 | 14 | ModelProviderDTO add(ModelProviderEntity modelProviderEntity); 15 | 16 | ModelProviderDTO edit(ModelProviderEntity modelProviderEntity); 17 | 18 | void delete(); 19 | 20 | List getList(String modelType, String provideCode); 21 | } 22 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/config/FilterConfig.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.config; 2 | 3 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.filter.DelegatingFilterProxy; 7 | 8 | /** 9 | * Filter配置 10 | * Copyright (c) 人人开源 All rights reserved. 11 | * Website: https://www.renren.io 12 | */ 13 | @Configuration 14 | public class FilterConfig { 15 | 16 | @Bean 17 | public FilterRegistrationBean shiroFilterRegistration() { 18 | FilterRegistrationBean registration = new FilterRegistrationBean<>(); 19 | registration.setFilter(new DelegatingFilterProxy("shiroFilter")); 20 | // 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 21 | registration.addInitParameter("targetFilterLifecycle", "true"); 22 | registration.setEnabled(true); 23 | registration.setOrder(Integer.MAX_VALUE - 1); 24 | registration.addUrlPatterns("/*"); 25 | return registration; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/dao/SysUserTokenDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.dao; 2 | 3 | import java.util.Date; 4 | 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import xiaozhi.common.dao.BaseDao; 9 | import xiaozhi.modules.security.entity.SysUserTokenEntity; 10 | 11 | /** 12 | * 系统用户Token 13 | * Copyright (c) 人人开源 All rights reserved. 14 | * Website: https://www.renren.io 15 | */ 16 | @Mapper 17 | public interface SysUserTokenDao extends BaseDao { 18 | 19 | SysUserTokenEntity getByToken(String token); 20 | 21 | SysUserTokenEntity getByUserId(Long userId); 22 | 23 | void logout(@Param("userId") Long userId, @Param("expireDate") Date expireDate); 24 | } 25 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import jakarta.validation.constraints.NotBlank; 7 | import lombok.Data; 8 | 9 | /** 10 | * 登录表单 11 | */ 12 | @Data 13 | @Schema(description = "登录表单") 14 | public class LoginDTO implements Serializable { 15 | 16 | @Schema(description = "手机号码") 17 | @NotBlank(message = "{sysuser.username.require}") 18 | private String username; 19 | 20 | @Schema(description = "密码") 21 | @NotBlank(message = "{sysuser.password.require}") 22 | private String password; 23 | 24 | @Schema(description = "验证码") 25 | @NotBlank(message = "{sysuser.captcha.require}") 26 | private String captcha; 27 | 28 | @Schema(description = "唯一标识") 29 | @NotBlank(message = "{sysuser.uuid.require}") 30 | private String captchaId; 31 | 32 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/entity/SysUserTokenEntity.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.entity; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | import com.baomidou.mybatisplus.annotation.FieldFill; 7 | import com.baomidou.mybatisplus.annotation.TableField; 8 | import com.baomidou.mybatisplus.annotation.TableId; 9 | import com.baomidou.mybatisplus.annotation.TableName; 10 | 11 | import lombok.Data; 12 | 13 | /** 14 | * 系统用户Token 15 | */ 16 | @Data 17 | @TableName("sys_user_token") 18 | public class SysUserTokenEntity implements Serializable { 19 | 20 | /** 21 | * id 22 | */ 23 | @TableId 24 | private Long id; 25 | /** 26 | * 用户ID 27 | */ 28 | private Long userId; 29 | /** 30 | * 用户token 31 | */ 32 | private String token; 33 | /** 34 | * 过期时间 35 | */ 36 | private Date expireDate; 37 | /** 38 | * 更新时间 39 | */ 40 | private Date updateDate; 41 | /** 42 | * 创建时间 43 | */ 44 | @TableField(fill = FieldFill.INSERT) 45 | private Date createDate; 46 | 47 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Token.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.oauth2; 2 | 3 | import org.apache.shiro.authc.AuthenticationToken; 4 | 5 | /** 6 | * token 7 | * Copyright (c) 人人开源 All rights reserved. 8 | * Website: https://www.renren.io 9 | */ 10 | public class Oauth2Token implements AuthenticationToken { 11 | private String token; 12 | 13 | public Oauth2Token(String token) { 14 | this.token = token; 15 | } 16 | 17 | @Override 18 | public String getPrincipal() { 19 | return token; 20 | } 21 | 22 | @Override 23 | public Object getCredentials() { 24 | return token; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.oauth2; 2 | 3 | import java.security.MessageDigest; 4 | import java.util.UUID; 5 | 6 | import xiaozhi.common.exception.RenException; 7 | 8 | /** 9 | * 生成token 10 | * Copyright (c) 人人开源 All rights reserved. 11 | * Website: https://www.renren.io 12 | */ 13 | public class TokenGenerator { 14 | 15 | public static String generateValue() { 16 | return generateValue(UUID.randomUUID().toString()); 17 | } 18 | 19 | private static final char[] HEX_CODE = "0123456789abcdef".toCharArray(); 20 | 21 | public static String toHexString(byte[] data) { 22 | if (data == null) { 23 | return null; 24 | } 25 | StringBuilder r = new StringBuilder(data.length * 2); 26 | for (byte b : data) { 27 | r.append(HEX_CODE[(b >> 4) & 0xF]); 28 | r.append(HEX_CODE[(b & 0xF)]); 29 | } 30 | return r.toString(); 31 | } 32 | 33 | public static String generateValue(String param) { 34 | try { 35 | MessageDigest algorithm = MessageDigest.getInstance("MD5"); 36 | algorithm.reset(); 37 | algorithm.update(param.getBytes()); 38 | byte[] messageDigest = algorithm.digest(); 39 | return toHexString(messageDigest); 40 | } catch (Exception e) { 41 | throw new RenException("token invalid", e); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordEncoder.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.password; 2 | 3 | /** 4 | * Service interface for encoding passwords. 5 | *

6 | * The preferred implementation is {@code BCryptPasswordEncoder}. 7 | * 8 | * @author Keith Donald 9 | */ 10 | public interface PasswordEncoder { 11 | 12 | /** 13 | * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 14 | * or 15 | * greater hash combined with an 8-byte or greater randomly generated salt. 16 | */ 17 | String encode(CharSequence rawPassword); 18 | 19 | /** 20 | * Verify the encoded password obtained from storage matches the submitted raw 21 | * password after it too is encoded. Returns true if the passwords match, false 22 | * if 23 | * they do not. The stored password itself is never decoded. 24 | * 25 | * @param rawPassword the raw password to encode and match 26 | * @param encodedPassword the encoded password from storage to compare with 27 | * @return true if the raw password, after encoding, matches the encoded 28 | * password from 29 | * storage 30 | */ 31 | boolean matches(CharSequence rawPassword, String encodedPassword); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordUtils.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.password; 2 | 3 | /** 4 | * 密码工具类 5 | * Copyright (c) 人人开源 All rights reserved. 6 | * Website: https://www.renren.io 7 | */ 8 | public class PasswordUtils { 9 | private static PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); 10 | 11 | /** 12 | * 加密 13 | * 14 | * @param str 字符串 15 | * @return 返回加密字符串 16 | */ 17 | public static String encode(String str) { 18 | return passwordEncoder.encode(str); 19 | } 20 | 21 | /** 22 | * 比较密码是否相等 23 | * 24 | * @param str 明文密码 25 | * @param password 加密后密码 26 | * @return true:成功 false:失败 27 | */ 28 | public static boolean matches(String str, String password) { 29 | return passwordEncoder.matches(str, password); 30 | } 31 | 32 | public static void main(String[] args) { 33 | String str = "admin"; 34 | String password = encode(str); 35 | 36 | System.out.println(password); 37 | System.out.println(matches(str, password)); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/secret/ServerSecretToken.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.secret; 2 | 3 | import org.apache.shiro.authc.AuthenticationToken; 4 | 5 | /** 6 | * Config API Token 7 | */ 8 | public class ServerSecretToken implements AuthenticationToken { 9 | private static final long serialVersionUID = 1L; 10 | private final String token; 11 | 12 | public ServerSecretToken(String token) { 13 | this.token = token; 14 | } 15 | 16 | @Override 17 | public Object getPrincipal() { 18 | return token; 19 | } 20 | 21 | @Override 22 | public Object getCredentials() { 23 | return token; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.service; 2 | 3 | import java.io.IOException; 4 | 5 | import jakarta.servlet.http.HttpServletResponse; 6 | 7 | /** 8 | * 验证码 9 | * Copyright (c) 人人开源 All rights reserved. 10 | * Website: https://www.renren.io 11 | */ 12 | public interface CaptchaService { 13 | 14 | /** 15 | * 图片验证码 16 | */ 17 | void create(HttpServletResponse response, String uuid) throws IOException; 18 | 19 | /** 20 | * 验证码效验 21 | * 22 | * @param uuid uuid 23 | * @param code 验证码 24 | * @return true:成功 false:失败 25 | */ 26 | boolean validate(String uuid, String code); 27 | } 28 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/service/ShiroService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.service; 2 | 3 | import xiaozhi.modules.security.entity.SysUserTokenEntity; 4 | import xiaozhi.modules.sys.entity.SysUserEntity; 5 | 6 | /** 7 | * shiro相关接口 8 | * Copyright (c) 人人开源 All rights reserved. 9 | * Website: https://www.renren.io 10 | */ 11 | public interface ShiroService { 12 | 13 | SysUserTokenEntity getByToken(String token); 14 | 15 | /** 16 | * 根据用户ID,查询用户 17 | * 18 | * @param userId 19 | */ 20 | SysUserEntity getUser(Long userId); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/service/SysUserTokenService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.service; 2 | 3 | import xiaozhi.common.page.TokenDTO; 4 | import xiaozhi.common.service.BaseService; 5 | import xiaozhi.common.utils.Result; 6 | import xiaozhi.modules.security.entity.SysUserTokenEntity; 7 | import xiaozhi.modules.sys.dto.PasswordDTO; 8 | import xiaozhi.modules.sys.dto.SysUserDTO; 9 | 10 | /** 11 | * 用户Token 12 | * Copyright (c) 人人开源 All rights reserved. 13 | * Website: https://www.renren.io 14 | */ 15 | public interface SysUserTokenService extends BaseService { 16 | 17 | /** 18 | * 生成token 19 | * 20 | * @param userId 用户ID 21 | */ 22 | Result createToken(Long userId); 23 | 24 | SysUserDTO getUserByToken(String token); 25 | 26 | /** 27 | * 退出 28 | * 29 | * @param userId 用户ID 30 | */ 31 | void logout(Long userId); 32 | 33 | /** 34 | * 修改密码 35 | * 36 | * @param userId 37 | * @param passwordDTO 38 | */ 39 | void changePassword(Long userId, PasswordDTO passwordDTO); 40 | 41 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.service.impl; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import lombok.AllArgsConstructor; 6 | import xiaozhi.modules.security.dao.SysUserTokenDao; 7 | import xiaozhi.modules.security.entity.SysUserTokenEntity; 8 | import xiaozhi.modules.security.service.ShiroService; 9 | import xiaozhi.modules.sys.dao.SysUserDao; 10 | import xiaozhi.modules.sys.entity.SysUserEntity; 11 | 12 | @AllArgsConstructor 13 | @Service 14 | public class ShiroServiceImpl implements ShiroService { 15 | private final SysUserDao sysUserDao; 16 | private final SysUserTokenDao sysUserTokenDao; 17 | 18 | @Override 19 | public SysUserTokenEntity getByToken(String token) { 20 | return sysUserTokenDao.getByToken(token); 21 | } 22 | 23 | @Override 24 | public SysUserEntity getUser(Long userId) { 25 | return sysUserDao.selectById(userId); 26 | } 27 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/security/user/SecurityUser.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.security.user; 2 | 3 | import org.apache.shiro.SecurityUtils; 4 | import org.apache.shiro.subject.Subject; 5 | 6 | import xiaozhi.common.user.UserDetail; 7 | 8 | /** 9 | * Shiro工具类 10 | * Copyright (c) 人人开源 All rights reserved. 11 | * Website: https://www.renren.io 12 | */ 13 | public class SecurityUser { 14 | 15 | public static Subject getSubject() { 16 | try { 17 | return SecurityUtils.getSubject(); 18 | } catch (Exception e) { 19 | return null; 20 | } 21 | } 22 | 23 | /** 24 | * 获取用户信息 25 | */ 26 | public static UserDetail getUser() { 27 | Subject subject = getSubject(); 28 | if (subject == null) { 29 | return new UserDetail(); 30 | } 31 | 32 | UserDetail user = (UserDetail) subject.getPrincipal(); 33 | if (user == null) { 34 | return new UserDetail(); 35 | } 36 | 37 | return user; 38 | } 39 | 40 | public static String getToken() { 41 | return getUser().getToken(); 42 | } 43 | 44 | /** 45 | * 获取用户ID 46 | */ 47 | public static Long getUserId() { 48 | return getUser().getId(); 49 | } 50 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.dao; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | import xiaozhi.common.dao.BaseDao; 8 | import xiaozhi.modules.sys.entity.SysDictDataEntity; 9 | import xiaozhi.modules.sys.vo.SysDictDataItem; 10 | 11 | /** 12 | * 字典数据 13 | */ 14 | @Mapper 15 | public interface SysDictDataDao extends BaseDao { 16 | 17 | List getDictDataByType(String dictType); 18 | 19 | /** 20 | * 根据字典类型ID获取字典类型编码 21 | * 22 | * @param dictTypeId 字典类型ID 23 | * @return 字典类型编码 24 | */ 25 | String getTypeByTypeId(Long dictTypeId); 26 | } 27 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.dao; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | 5 | import xiaozhi.common.dao.BaseDao; 6 | import xiaozhi.modules.sys.entity.SysDictTypeEntity; 7 | 8 | /** 9 | * 字典类型 10 | */ 11 | @Mapper 12 | public interface SysDictTypeDao extends BaseDao { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.dao; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import xiaozhi.common.dao.BaseDao; 9 | import xiaozhi.modules.sys.entity.SysParamsEntity; 10 | 11 | /** 12 | * 参数管理 13 | */ 14 | @Mapper 15 | public interface SysParamsDao extends BaseDao { 16 | /** 17 | * 根据参数编码,查询value 18 | * 19 | * @param paramCode 参数编码 20 | * @return 参数值 21 | */ 22 | String getValueByCode(String paramCode); 23 | 24 | /** 25 | * 获取参数编码列表 26 | * 27 | * @param ids ids 28 | * @return 返回参数编码列表 29 | */ 30 | List getParamCodeList(String[] ids); 31 | 32 | /** 33 | * 根据参数编码,更新value 34 | * 35 | * @param paramCode 参数编码 36 | * @param paramValue 参数值 37 | */ 38 | int updateValueByCode(@Param("paramCode") String paramCode, @Param("paramValue") String paramValue); 39 | } 40 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysUserDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.dao; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | 5 | import xiaozhi.common.dao.BaseDao; 6 | import xiaozhi.modules.sys.entity.SysUserEntity; 7 | 8 | /** 9 | * 系统用户 10 | */ 11 | @Mapper 12 | public interface SysUserDao extends BaseDao { 13 | 14 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.Min; 5 | import lombok.Data; 6 | 7 | /** 8 | * 管理员分页用户的参数DTO 9 | * 10 | * @author zjy 11 | * @since 2025-3-21 12 | */ 13 | @Data 14 | @Schema(description = "管理员分页用户的参数DTO") 15 | public class AdminPageUserDTO { 16 | 17 | @Schema(description = "手机号码") 18 | private String mobile; 19 | 20 | @Schema(description = "页数") 21 | @Min(value = 0, message = "{sort.number}") 22 | private String page; 23 | 24 | @Schema(description = "显示列数") 25 | @Min(value = 0, message = "{sort.number}") 26 | private String limit; 27 | } 28 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/dto/PasswordDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import jakarta.validation.constraints.NotBlank; 7 | import lombok.Data; 8 | 9 | /** 10 | * 修改密码 11 | */ 12 | @Data 13 | @Schema(description = "修改密码") 14 | public class PasswordDTO implements Serializable { 15 | 16 | @Schema(description = "原密码") 17 | @NotBlank(message = "{sysuser.password.require}") 18 | private String password; 19 | 20 | @Schema(description = "新密码") 21 | @NotBlank(message = "{sysuser.password.require}") 22 | private String newPassword; 23 | 24 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictDataEntity.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.entity; 2 | 3 | import java.util.Date; 4 | 5 | import com.baomidou.mybatisplus.annotation.FieldFill; 6 | import com.baomidou.mybatisplus.annotation.TableField; 7 | import com.baomidou.mybatisplus.annotation.TableName; 8 | 9 | import lombok.Data; 10 | import lombok.EqualsAndHashCode; 11 | import xiaozhi.common.entity.BaseEntity; 12 | 13 | /** 14 | * 数据字典 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = false) 18 | @TableName("sys_dict_data") 19 | public class SysDictDataEntity extends BaseEntity { 20 | /** 21 | * 字典类型ID 22 | */ 23 | private Long dictTypeId; 24 | /** 25 | * 字典标签 26 | */ 27 | private String dictLabel; 28 | /** 29 | * 字典值 30 | */ 31 | private String dictValue; 32 | /** 33 | * 备注 34 | */ 35 | private String remark; 36 | /** 37 | * 排序 38 | */ 39 | private Integer sort; 40 | /** 41 | * 更新者 42 | */ 43 | @TableField(fill = FieldFill.INSERT_UPDATE) 44 | private Long updater; 45 | /** 46 | * 更新时间 47 | */ 48 | @TableField(fill = FieldFill.INSERT_UPDATE) 49 | private Date updateDate; 50 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.entity; 2 | 3 | import java.util.Date; 4 | 5 | import com.baomidou.mybatisplus.annotation.FieldFill; 6 | import com.baomidou.mybatisplus.annotation.TableField; 7 | import com.baomidou.mybatisplus.annotation.TableName; 8 | 9 | import lombok.Data; 10 | import lombok.EqualsAndHashCode; 11 | import xiaozhi.common.entity.BaseEntity; 12 | 13 | /** 14 | * 字典类型 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = false) 18 | @TableName("sys_dict_type") 19 | public class SysDictTypeEntity extends BaseEntity { 20 | /** 21 | * 字典类型编码 22 | */ 23 | private String dictType; 24 | /** 25 | * 字典名称 26 | */ 27 | private String dictName; 28 | /** 29 | * 备注 30 | */ 31 | private String remark; 32 | /** 33 | * 排序 34 | */ 35 | private Integer sort; 36 | /** 37 | * 更新者 38 | */ 39 | @TableField(fill = FieldFill.INSERT_UPDATE) 40 | private Long updater; 41 | /** 42 | * 更新时间 43 | */ 44 | @TableField(fill = FieldFill.INSERT_UPDATE) 45 | private Date updateDate; 46 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.entity; 2 | 3 | import java.util.Date; 4 | 5 | import com.baomidou.mybatisplus.annotation.FieldFill; 6 | import com.baomidou.mybatisplus.annotation.TableField; 7 | import com.baomidou.mybatisplus.annotation.TableName; 8 | 9 | import lombok.Data; 10 | import lombok.EqualsAndHashCode; 11 | import xiaozhi.common.entity.BaseEntity; 12 | 13 | /** 14 | * 参数管理 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = false) 18 | @TableName("sys_params") 19 | public class SysParamsEntity extends BaseEntity { 20 | /** 21 | * 参数编码 22 | */ 23 | private String paramCode; 24 | /** 25 | * 参数值 26 | */ 27 | private String paramValue; 28 | /** 29 | * 值类型:string-字符串,number-数字,boolean-布尔,array-数组 30 | */ 31 | private String valueType; 32 | /** 33 | * 类型 0:系统参数 1:非系统参数 34 | */ 35 | private Integer paramType; 36 | /** 37 | * 备注 38 | */ 39 | private String remark; 40 | /** 41 | * 更新者 42 | */ 43 | @TableField(fill = FieldFill.INSERT_UPDATE) 44 | private Long updater; 45 | /** 46 | * 更新时间 47 | */ 48 | @TableField(fill = FieldFill.INSERT_UPDATE) 49 | private Date updateDate; 50 | 51 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysUserEntity.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.entity; 2 | 3 | import java.util.Date; 4 | 5 | import com.baomidou.mybatisplus.annotation.FieldFill; 6 | import com.baomidou.mybatisplus.annotation.TableField; 7 | import com.baomidou.mybatisplus.annotation.TableName; 8 | 9 | import lombok.Data; 10 | import lombok.EqualsAndHashCode; 11 | import xiaozhi.common.entity.BaseEntity; 12 | 13 | /** 14 | * 系统用户 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = false) 18 | @TableName("sys_user") 19 | public class SysUserEntity extends BaseEntity { 20 | /** 21 | * 用户名 22 | */ 23 | private String username; 24 | /** 25 | * 密码 26 | */ 27 | private String password; 28 | /** 29 | * 超级管理员 0:否 1:是 30 | */ 31 | private Integer superAdmin; 32 | /** 33 | * 状态 0:停用 1:正常 34 | */ 35 | private Integer status; 36 | /** 37 | * 更新者 38 | */ 39 | @TableField(fill = FieldFill.INSERT_UPDATE) 40 | private Long updater; 41 | /** 42 | * 更新时间 43 | */ 44 | @TableField(fill = FieldFill.INSERT_UPDATE) 45 | private Date updateDate; 46 | 47 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/enums/SuperAdminEnum.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.enums; 2 | 3 | /** 4 | * 超级管理员枚举 5 | */ 6 | public enum SuperAdminEnum { 7 | YES(1), 8 | NO(0); 9 | 10 | private int value; 11 | 12 | SuperAdminEnum(int value) { 13 | this.value = value; 14 | } 15 | 16 | public int value() { 17 | return this.value; 18 | } 19 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/redis/SysParamsRedis.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.redis; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import lombok.AllArgsConstructor; 6 | import xiaozhi.common.redis.RedisKeys; 7 | import xiaozhi.common.redis.RedisUtils; 8 | 9 | /** 10 | * 参数管理 11 | */ 12 | @AllArgsConstructor 13 | @Component 14 | public class SysParamsRedis { 15 | private final RedisUtils redisUtils; 16 | 17 | public void delete(Object[] paramCodes) { 18 | String key = RedisKeys.getSysParamsKey(); 19 | redisUtils.hDel(key, paramCodes); 20 | } 21 | 22 | public void set(String paramCode, String paramValue) { 23 | if (paramValue == null) { 24 | return; 25 | } 26 | String key = RedisKeys.getSysParamsKey(); 27 | redisUtils.hSet(key, paramCode, paramValue); 28 | } 29 | 30 | public String get(String paramCode) { 31 | String key = RedisKeys.getSysParamsKey(); 32 | return (String) redisUtils.hGet(key, paramCode); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.service; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import xiaozhi.common.page.PageData; 7 | import xiaozhi.common.service.BaseService; 8 | import xiaozhi.modules.sys.dto.SysDictDataDTO; 9 | import xiaozhi.modules.sys.entity.SysDictDataEntity; 10 | import xiaozhi.modules.sys.vo.SysDictDataItem; 11 | import xiaozhi.modules.sys.vo.SysDictDataVO; 12 | 13 | /** 14 | * 数据字典 15 | */ 16 | public interface SysDictDataService extends BaseService { 17 | 18 | /** 19 | * 分页查询数据字典信息 20 | * 21 | * @param params 查询参数,包含分页信息和查询条件 22 | * @return 返回数据字典的分页查询结果 23 | */ 24 | PageData page(Map params); 25 | 26 | /** 27 | * 根据ID获取数据字典实体 28 | * 29 | * @param id 数据字典实体的唯一标识 30 | * @return 返回数据字典实体的详细信息 31 | */ 32 | SysDictDataVO get(Long id); 33 | 34 | /** 35 | * 保存新的数据字典项 36 | * 37 | * @param dto 数据字典项的保存数据传输对象 38 | */ 39 | void save(SysDictDataDTO dto); 40 | 41 | /** 42 | * 更新数据字典项 43 | * 44 | * @param dto 数据字典项的更新数据传输对象 45 | */ 46 | void update(SysDictDataDTO dto); 47 | 48 | /** 49 | * 删除数据字典项 50 | * 51 | * @param ids 要删除的数据字典项的ID数组 52 | */ 53 | void delete(Long[] ids); 54 | 55 | /** 56 | * 根据字典类型ID删除对应的字典数据 57 | * 58 | * @param dictTypeId 字典类型ID 59 | */ 60 | void deleteByTypeId(Long dictTypeId); 61 | 62 | /** 63 | * 根据字典类型获取字典数据列表 64 | * 65 | * @param dictType 字典类型 66 | * @return 返回字典数据列表 67 | */ 68 | List getDictDataByType(String dictType); 69 | 70 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.service; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import xiaozhi.common.page.PageData; 7 | import xiaozhi.common.service.BaseService; 8 | import xiaozhi.modules.sys.dto.SysDictTypeDTO; 9 | import xiaozhi.modules.sys.entity.SysDictTypeEntity; 10 | import xiaozhi.modules.sys.vo.SysDictTypeVO; 11 | 12 | /** 13 | * 数据字典 14 | */ 15 | public interface SysDictTypeService extends BaseService { 16 | 17 | /** 18 | * 分页查询字典类型信息 19 | * 20 | * @param params 查询参数,包含分页信息和查询条件 21 | * @return 返回分页的字典类型数据 22 | */ 23 | PageData page(Map params); 24 | 25 | /** 26 | * 根据ID获取字典类型信息 27 | * 28 | * @param id 字典类型ID 29 | * @return 返回字典类型对象 30 | */ 31 | SysDictTypeVO get(Long id); 32 | 33 | /** 34 | * 保存字典类型信息 35 | * 36 | * @param dto 字典类型数据传输对象 37 | */ 38 | void save(SysDictTypeDTO dto); 39 | 40 | /** 41 | * 更新字典类型信息 42 | * 43 | * @param dto 字典类型数据传输对象 44 | */ 45 | void update(SysDictTypeDTO dto); 46 | 47 | /** 48 | * 删除字典类型信息 49 | * 50 | * @param ids 要删除的字典类型ID数组 51 | */ 52 | void delete(Long[] ids); 53 | 54 | /** 55 | * 列出所有字典类型信息 56 | * 57 | * @return 返回字典类型列表 58 | */ 59 | List list(Map params); 60 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.service; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import xiaozhi.common.page.PageData; 7 | import xiaozhi.common.service.BaseService; 8 | import xiaozhi.modules.sys.dto.SysParamsDTO; 9 | import xiaozhi.modules.sys.entity.SysParamsEntity; 10 | 11 | /** 12 | * 参数管理 13 | */ 14 | public interface SysParamsService extends BaseService { 15 | 16 | PageData page(Map params); 17 | 18 | List list(Map params); 19 | 20 | SysParamsDTO get(Long id); 21 | 22 | void save(SysParamsDTO dto); 23 | 24 | void update(SysParamsDTO dto); 25 | 26 | void delete(String[] ids); 27 | 28 | /** 29 | * 根据参数编码,获取参数的value值 30 | * 31 | * @param paramCode 参数编码 32 | * @param fromCache 是否从缓存中获取 33 | */ 34 | String getValue(String paramCode, Boolean fromCache); 35 | 36 | /** 37 | * 根据参数编码,获取value的Object对象 38 | * 39 | * @param paramCode 参数编码 40 | * @param clazz Object对象 41 | */ 42 | T getValueObject(String paramCode, Class clazz); 43 | 44 | /** 45 | * 根据参数编码,更新value 46 | * 47 | * @param paramCode 参数编码 48 | * @param paramValue 参数值 49 | */ 50 | int updateValueByCode(String paramCode, String paramValue); 51 | 52 | /** 53 | * 初始化服务器密钥 54 | */ 55 | void initServerSecret(); 56 | } 57 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserUtilService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.service; 2 | 3 | 4 | import java.util.function.Consumer; 5 | 6 | /** 7 | * 定义一个系统用户工具类,避免和用户模块循环依赖 8 | * 如用户和设备互相依赖,用户需要获取所有设备,设备又需要获取每个设备的用户名 9 | * @author zjy 10 | * @since 2025-4-2 11 | */ 12 | public interface SysUserUtilService { 13 | /** 14 | * 赋值用户名 15 | * @param userId 用户id 16 | * @param setter 赋值方法 17 | */ 18 | void assignUsername( Long userId, Consumer setter); 19 | } 20 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/service/TokenService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.service; 2 | 3 | public interface TokenService { 4 | /** 5 | * 生成token 6 | * 7 | * @param userId 8 | * @return 9 | */ 10 | String createToken(long userId); 11 | } 12 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserUtilServiceImpl.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.service.impl; 2 | 3 | import lombok.AllArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | import xiaozhi.common.redis.RedisKeys; 6 | import xiaozhi.common.redis.RedisUtils; 7 | import xiaozhi.common.service.impl.BaseServiceImpl; 8 | import xiaozhi.modules.sys.dao.SysUserDao; 9 | import xiaozhi.modules.sys.entity.SysUserEntity; 10 | import xiaozhi.modules.sys.service.SysUserUtilService; 11 | 12 | import java.util.function.Consumer; 13 | 14 | @Service 15 | @AllArgsConstructor 16 | public class SysUserUtilServiceImpl extends BaseServiceImpl implements SysUserUtilService { 17 | 18 | private RedisUtils redisUtils; 19 | 20 | @Override 21 | public void assignUsername(Long userId, Consumer setter) { 22 | String userIdKey = RedisKeys.getUserIdKey(userId); 23 | 24 | Object value = redisUtils.get(userIdKey); 25 | String username = (value != null) ? value.toString() : null; 26 | if(username != null){ 27 | setter.accept(username); 28 | }else { 29 | SysUserEntity entity = baseDao.selectById(userId); 30 | if (entity != null) { 31 | username = entity.getUsername(); 32 | redisUtils.set(userIdKey,username,10); 33 | setter.accept(username); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/TokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.service.impl; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import lombok.AllArgsConstructor; 6 | import xiaozhi.modules.security.oauth2.TokenGenerator; 7 | import xiaozhi.modules.sys.service.TokenService; 8 | 9 | @AllArgsConstructor 10 | @Service 11 | public class TokenServiceImpl implements TokenService { 12 | 13 | @Override 14 | public String createToken(long userId) { 15 | // 生成一个token 16 | String token = TokenGenerator.generateValue(); 17 | return token; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketTestHandler.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.utils; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | import org.springframework.web.socket.CloseStatus; 6 | import org.springframework.web.socket.WebSocketHandler; 7 | import org.springframework.web.socket.WebSocketMessage; 8 | import org.springframework.web.socket.WebSocketSession; 9 | 10 | public class WebSocketTestHandler implements WebSocketHandler { 11 | private final CompletableFuture future; 12 | 13 | public WebSocketTestHandler(CompletableFuture future) { 14 | this.future = future; 15 | } 16 | 17 | @Override 18 | public void afterConnectionEstablished(WebSocketSession session) { 19 | future.complete(true); 20 | try { 21 | session.close(); 22 | } catch (Exception e) { 23 | // 忽略关闭异常 24 | } 25 | } 26 | 27 | @Override 28 | public void handleMessage(WebSocketSession session, WebSocketMessage message) { 29 | // 不需要处理消息 30 | } 31 | 32 | @Override 33 | public void handleTransportError(WebSocketSession session, Throwable exception) { 34 | future.complete(false); 35 | } 36 | 37 | @Override 38 | public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { 39 | // 连接关闭时不做任何处理 40 | } 41 | 42 | @Override 43 | public boolean supportsPartialMessages() { 44 | return false; 45 | } 46 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/vo/AdminPageUserVO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.vo; 2 | 3 | import java.util.Date; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Data; 7 | 8 | /** 9 | * 管理员分页展示用户的VO 10 | * @ zjy 11 | * 12 | * @since 2025-3-25 13 | */ 14 | @Data 15 | public class AdminPageUserVO { 16 | 17 | @Schema(description = "设备数量") 18 | private String deviceCount; 19 | 20 | @Schema(description = "手机号码") 21 | private String mobile; 22 | 23 | @Schema(description = "状态") 24 | private Integer status; 25 | 26 | @Schema(description = "用户id") 27 | private String userid; 28 | 29 | @Schema(description = "注册时间") 30 | private Date createDate; 31 | } 32 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictDataItem.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.vo; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Data; 7 | 8 | /** 9 | * 字典数据VO 10 | */ 11 | @Data 12 | @Schema(description = "字典数据项") 13 | public class SysDictDataItem implements Serializable { 14 | 15 | @Schema(description = "字典标签") 16 | private String name; 17 | 18 | @Schema(description = "字典值") 19 | private String key; 20 | } 21 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictDataVO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.vo; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | /** 10 | * 字典数据VO 11 | */ 12 | @Data 13 | @Schema(description = "字典数据VO") 14 | public class SysDictDataVO implements Serializable { 15 | @Schema(description = "主键") 16 | private Long id; 17 | 18 | @Schema(description = "字典类型ID") 19 | private Long dictTypeId; 20 | 21 | @Schema(description = "字典标签") 22 | private String dictLabel; 23 | 24 | @Schema(description = "字典值") 25 | private String dictValue; 26 | 27 | @Schema(description = "备注") 28 | private String remark; 29 | 30 | @Schema(description = "排序") 31 | private Integer sort; 32 | 33 | @Schema(description = "创建者") 34 | private Long creator; 35 | 36 | @Schema(description = "创建者名称") 37 | private String creatorName; 38 | 39 | @Schema(description = "创建时间") 40 | private Date createDate; 41 | 42 | @Schema(description = "更新者") 43 | private Long updater; 44 | 45 | @Schema(description = "更新者名称") 46 | private String updaterName; 47 | 48 | @Schema(description = "更新时间") 49 | private Date updateDate; 50 | } 51 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictTypeVO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.sys.vo; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | /** 10 | * 字典类型VO 11 | */ 12 | @Data 13 | @Schema(description = "字典类型VO") 14 | public class SysDictTypeVO implements Serializable { 15 | @Schema(description = "主键") 16 | private Long id; 17 | 18 | @Schema(description = "字典类型") 19 | private String dictType; 20 | 21 | @Schema(description = "字典名称") 22 | private String dictName; 23 | 24 | @Schema(description = "备注") 25 | private String remark; 26 | 27 | @Schema(description = "排序") 28 | private Integer sort; 29 | 30 | @Schema(description = "创建者") 31 | private Long creator; 32 | 33 | @Schema(description = "创建者名称") 34 | private String creatorName; 35 | 36 | @Schema(description = "创建时间") 37 | private Date createDate; 38 | 39 | @Schema(description = "更新者") 40 | private Long updater; 41 | 42 | @Schema(description = "更新者名称") 43 | private String updaterName; 44 | 45 | @Schema(description = "更新时间") 46 | private Date updateDate; 47 | } 48 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.timbre.dao; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | 7 | import xiaozhi.modules.timbre.entity.TimbreEntity; 8 | 9 | /** 10 | * 音色持久层定义 11 | * 12 | * @author zjy 13 | * @since 2025-3-21 14 | */ 15 | @Mapper 16 | public interface TimbreDao extends BaseMapper { 17 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.timbre.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.NotBlank; 6 | import lombok.Data; 7 | 8 | /** 9 | * 音色表数据DTO 10 | * 11 | * @author zjy 12 | * @since 2025-3-21 13 | */ 14 | @Data 15 | @Schema(description = "音色表信息") 16 | public class TimbreDataDTO { 17 | 18 | @Schema(description = "语言") 19 | @NotBlank(message = "{timbre.languages.require}") 20 | private String languages; 21 | 22 | @Schema(description = "音色名称") 23 | @NotBlank(message = "{timbre.name.require}") 24 | private String name; 25 | 26 | @Schema(description = "备注") 27 | private String remark; 28 | 29 | @Schema(description = "排序") 30 | @Min(value = 0, message = "{sort.number}") 31 | private long sort; 32 | 33 | @Schema(description = "对应 TTS 模型主键") 34 | @NotBlank(message = "{timbre.ttsModelId.require}") 35 | private String ttsModelId; 36 | 37 | @Schema(description = "音色编码") 38 | @NotBlank(message = "{timbre.ttsVoice.require}") 39 | private String ttsVoice; 40 | 41 | @Schema(description = "音频播放地址") 42 | private String voiceDemo; 43 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.timbre.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import lombok.Data; 6 | 7 | /** 8 | * 音色分页参数DTO 9 | * 10 | * @author zjy 11 | * @since 2025-3-21 12 | */ 13 | @Data 14 | @Schema(description = "音色分页参数") 15 | public class TimbrePageDTO { 16 | 17 | @Schema(description = "对应 TTS 模型主键") 18 | @NotBlank(message = "{timbre.ttsModelId.require}") 19 | private String ttsModelId; 20 | 21 | @Schema(description = "音色名称") 22 | private String name; 23 | 24 | @Schema(description = "页数") 25 | private String page; 26 | 27 | @Schema(description = "显示列数") 28 | private String limit; 29 | } 30 | -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.timbre.entity; 2 | 3 | import java.util.Date; 4 | 5 | import com.baomidou.mybatisplus.annotation.FieldFill; 6 | import com.baomidou.mybatisplus.annotation.TableField; 7 | import com.baomidou.mybatisplus.annotation.TableName; 8 | 9 | import io.swagger.v3.oas.annotations.media.Schema; 10 | import lombok.Data; 11 | import lombok.EqualsAndHashCode; 12 | 13 | /** 14 | * 音色表实体类 15 | * 16 | * @author zjy 17 | * @since 2025-3-21 18 | */ 19 | @Data 20 | @EqualsAndHashCode(callSuper = false) 21 | @TableName("ai_tts_voice") 22 | @Schema(description = "音色信息") 23 | public class TimbreEntity { 24 | 25 | @Schema(description = "id") 26 | private String id; 27 | 28 | @Schema(description = "语言") 29 | private String languages; 30 | 31 | @Schema(description = "音色名称") 32 | private String name; 33 | 34 | @Schema(description = "备注") 35 | private String remark; 36 | 37 | @Schema(description = "排序") 38 | private long sort; 39 | 40 | @Schema(description = "对应 TTS 模型主键") 41 | private String ttsModelId; 42 | 43 | @Schema(description = "音色编码") 44 | private String ttsVoice; 45 | 46 | @Schema(description = "音频播放地址") 47 | private String voiceDemo; 48 | 49 | @Schema(description = "更新者") 50 | @TableField(fill = FieldFill.UPDATE) 51 | private Long updater; 52 | 53 | @Schema(description = "更新时间") 54 | @TableField(fill = FieldFill.UPDATE) 55 | private Date updateDate; 56 | 57 | @Schema(description = "创建者") 58 | @TableField(fill = FieldFill.INSERT) 59 | private Long creator; 60 | 61 | @Schema(description = "创建时间") 62 | @TableField(fill = FieldFill.INSERT) 63 | private Date createDate; 64 | 65 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.timbre.service; 2 | 3 | import java.util.List; 4 | 5 | import xiaozhi.common.page.PageData; 6 | import xiaozhi.common.service.BaseService; 7 | import xiaozhi.modules.model.dto.VoiceDTO; 8 | import xiaozhi.modules.timbre.dto.TimbreDataDTO; 9 | import xiaozhi.modules.timbre.dto.TimbrePageDTO; 10 | import xiaozhi.modules.timbre.entity.TimbreEntity; 11 | import xiaozhi.modules.timbre.vo.TimbreDetailsVO; 12 | 13 | /** 14 | * 音色的业务层的定义 15 | * 16 | * @author zjy 17 | * @since 2025-3-21 18 | */ 19 | public interface TimbreService extends BaseService { 20 | /** 21 | * 分页获取音色指定tts的下的音色 22 | * 23 | * @param dto 分页查找参数 24 | * @return 音色列表分页数据 25 | */ 26 | PageData page(TimbrePageDTO dto); 27 | 28 | /** 29 | * 获取音色指定id的详情信息 30 | * 31 | * @param timbreId 音色表id 32 | * @return 音色信息 33 | */ 34 | TimbreDetailsVO get(String timbreId); 35 | 36 | /** 37 | * 保存音色信息 38 | * 39 | * @param dto 需要保存数据 40 | */ 41 | void save(TimbreDataDTO dto); 42 | 43 | /** 44 | * 保存音色信息 45 | * 46 | * @param timbreId 需要修改的id 47 | * @param dto 需要修改的数据 48 | */ 49 | void update(String timbreId, TimbreDataDTO dto); 50 | 51 | /** 52 | * 批量删除音色 53 | * 54 | * @param ids 需要被删除的音色id列表 55 | */ 56 | void delete(String[] ids); 57 | 58 | List getVoiceNames(String ttsModelId, String voiceName); 59 | 60 | /** 61 | * 根据ID获取音色名称 62 | * 63 | * @param id 音色ID 64 | * @return 音色名称 65 | */ 66 | String getTimbreNameById(String id); 67 | } -------------------------------------------------------------------------------- /main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java: -------------------------------------------------------------------------------- 1 | package xiaozhi.modules.timbre.vo; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Data; 7 | 8 | /** 9 | * 音色详情展示VO 10 | * 11 | * @author zjy 12 | * @since 2025-3-21 13 | */ 14 | @Data 15 | public class TimbreDetailsVO implements Serializable { 16 | @Schema(description = "音色id") 17 | private String id; 18 | 19 | @Schema(description = "语言") 20 | private String languages; 21 | 22 | @Schema(description = "音色名称") 23 | private String name; 24 | 25 | @Schema(description = "备注") 26 | private String remark; 27 | 28 | @Schema(description = "排序") 29 | private long sort; 30 | 31 | @Schema(description = "对应 TTS 模型主键") 32 | private String ttsModelId; 33 | 34 | @Schema(description = "音色编码") 35 | private String ttsVoice; 36 | 37 | @Schema(description = "音频播放地址") 38 | private String voiceDemo; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | knife4j: 2 | production: false 3 | enable: true 4 | basic: 5 | enable: false 6 | username: renren 7 | password: 2ZABCDEUgF 8 | setting: 9 | enableFooter: false 10 | spring: 11 | datasource: 12 | druid: 13 | #MySQL 14 | driver-class-name: com.mysql.cj.jdbc.Driver 15 | url: jdbc:mysql://127.0.0.1:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true 16 | username: root 17 | password: 123456 18 | initial-size: 10 19 | max-active: 100 20 | min-idle: 10 21 | max-wait: 6000 22 | pool-prepared-statements: true 23 | max-pool-prepared-statement-per-connection-size: 20 24 | time-between-eviction-runs-millis: 60000 25 | min-evictable-idle-time-millis: 300000 26 | test-while-idle: true 27 | test-on-borrow: false 28 | test-on-return: false 29 | stat-view-servlet: 30 | enabled: false 31 | filter: 32 | stat: 33 | log-slow-sql: true 34 | slow-sql-millis: 1000 35 | merge-sql: false 36 | wall: 37 | config: 38 | multi-statement-allow: true 39 | data: 40 | redis: 41 | host: 127.0.0.1 # Redis服务器地址 42 | port: 6379 # Redis服务器连接端口 43 | password: # Redis服务器连接密码(默认为空) 44 | database: 0 # Redis数据库索引(默认为0) 45 | timeout: 10000ms # 连接超时时间(毫秒) 46 | lettuce: 47 | pool: 48 | max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 49 | max-idle: 8 # 连接池中的最大空闲连接 50 | min-idle: 0 # 连接池中的最小空闲连接 51 | shutdown-timeout: 100ms # 客户端优雅关闭的等待时间 -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # Tomcat 2 | server: 3 | tomcat: 4 | uri-encoding: UTF-8 5 | threads: 6 | max: 1000 7 | min-spare: 30 8 | port: 8002 9 | servlet: 10 | context-path: /xiaozhi 11 | session: 12 | cookie: 13 | http-only: true 14 | 15 | spring: 16 | # 环境 dev|test|prod 17 | profiles: 18 | active: dev 19 | messages: 20 | encoding: UTF-8 21 | basename: i18n/messages 22 | servlet: 23 | multipart: 24 | max-file-size: 100MB 25 | max-request-size: 100MB 26 | enabled: true 27 | main: 28 | allow-bean-definition-overriding: true 29 | 30 | knife4j: 31 | enable: true 32 | basic: 33 | enable: false 34 | username: admin 35 | password: admin 36 | setting: 37 | enableFooter: false 38 | 39 | renren: 40 | redis: 41 | open: true 42 | xss: 43 | enabled: true 44 | exclude-urls: 45 | 46 | #mybatis 47 | mybatis-plus: 48 | mapper-locations: classpath*:/mapper/**/*.xml 49 | #实体扫描,多个package用逗号或者分号分隔 50 | typeAliasesPackage: xiaozhi.modules.*.entity 51 | global-config: 52 | #数据库相关配置 53 | db-config: 54 | #主键类型 55 | id-type: ASSIGN_ID 56 | banner: false 57 | #原生配置 58 | configuration: 59 | map-underscore-to-camel-case: true 60 | cache-enabled: false 61 | call-setters-on-nulls: true 62 | jdbc-type-for-null: 'null' 63 | configuration-properties: 64 | prefix: 65 | blobType: BLOB 66 | boolValue: TRUE -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/db/changelog/202504181536.sql: -------------------------------------------------------------------------------- 1 | -- 调整意图识别配置 2 | delete from `ai_model_config` where id = 'Intent_function_call'; 3 | INSERT INTO `ai_model_config` VALUES ('Intent_function_call', 'Intent', 'function_call', '函数调用意图识别', 0, 1, '{\"type\": \"function_call\", \"functions\": \"change_role;get_weather;get_news;play_music\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL); 4 | 5 | -- 增加单台设备每天最多聊天句数 6 | delete from `sys_params` where id = 105; 7 | INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (105, 'device_max_output_size', '0', 'number', 1, '单台设备每天最多输出字数,0表示不限制'); -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/db/changelog/202504251422.sql: -------------------------------------------------------------------------------- 1 | -- 增加server.ota,用于配置ota地址 2 | 3 | delete from `sys_params` where id = 100; 4 | delete from `sys_params` where id = 101; 5 | 6 | delete from `sys_params` where id = 106; 7 | INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (106, 'server.websocket', 'null', 'string', 1, 'websocket地址,多个用;分隔'); 8 | 9 | delete from `sys_params` where id = 107; 10 | INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (107, 'server.ota', 'null', 'string', 1, 'ota地址'); 11 | 12 | 13 | -- 增加固件信息表 14 | CREATE TABLE IF NOT EXISTS `ai_ota` ( 15 | `id` varchar(32) NOT NULL COMMENT 'ID', 16 | `firmware_name` varchar(100) DEFAULT NULL COMMENT '固件名称', 17 | `type` varchar(50) DEFAULT NULL COMMENT '固件类型', 18 | `version` varchar(50) DEFAULT NULL COMMENT '版本号', 19 | `size` bigint DEFAULT NULL COMMENT '文件大小(字节)', 20 | `remark` varchar(500) DEFAULT NULL COMMENT '备注/说明', 21 | `firmware_path` varchar(255) DEFAULT NULL COMMENT '固件路径', 22 | `sort` int unsigned DEFAULT '0' COMMENT '排序', 23 | `updater` bigint DEFAULT NULL COMMENT '更新者', 24 | `update_date` datetime DEFAULT NULL COMMENT '更新时间', 25 | `creator` bigint DEFAULT NULL COMMENT '创建者', 26 | `create_date` datetime DEFAULT NULL COMMENT '创建时间', 27 | PRIMARY KEY (`id`) 28 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='固件信息表'; 29 | 30 | update ai_device set auto_update = 1; 31 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/db/changelog/202505022134.sql: -------------------------------------------------------------------------------- 1 | -- 初始化智能体聊天记录 2 | DROP TABLE IF EXISTS ai_chat_history; 3 | DROP TABLE IF EXISTS ai_chat_message; 4 | DROP TABLE IF EXISTS ai_agent_chat_history; 5 | CREATE TABLE ai_agent_chat_history 6 | ( 7 | id BIGINT AUTO_INCREMENT COMMENT '主键ID' PRIMARY KEY, 8 | mac_address VARCHAR(50) COMMENT 'MAC地址', 9 | agent_id VARCHAR(32) COMMENT '智能体id', 10 | session_id VARCHAR(50) COMMENT '会话ID', 11 | chat_type TINYINT(3) COMMENT '消息类型: 1-用户, 2-智能体', 12 | content VARCHAR(1024) COMMENT '聊天内容', 13 | audio_id VARCHAR(32) COMMENT '音频ID', 14 | created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL COMMENT '创建时间', 15 | updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', 16 | INDEX idx_ai_agent_chat_history_mac (mac_address), 17 | INDEX idx_ai_agent_chat_history_session_id (session_id), 18 | INDEX idx_ai_agent_chat_history_agent_id (agent_id), 19 | INDEX idx_ai_agent_chat_history_agent_session_created (agent_id, session_id, created_at) 20 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '智能体聊天记录表'; 21 | 22 | DROP TABLE IF EXISTS ai_agent_chat_audio; 23 | CREATE TABLE ai_agent_chat_audio 24 | ( 25 | id VARCHAR(32) COMMENT '主键ID' PRIMARY KEY, 26 | audio LONGBLOB COMMENT '音频opus数据' 27 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '智能体聊天音频数据表'; -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/db/changelog/202505091409.sql: -------------------------------------------------------------------------------- 1 | -- 更新intent_llmM供应器 2 | update `ai_model_provider` set fields = '[{"key":"llm","label":"LLM模型","type":"string"},{"key":"functions","label":"函数列表","type":"dict","dict_name":"functions"}]' where id = 'SYSTEM_Intent_intent_llm'; 3 | -- 更新ChatGLMLLM的意图识别配置 4 | update `ai_model_config` set config_json = '{\"type\": \"intent_llm\", \"llm\": \"LLM_ChatGLMLLM\", \"functions\": \"get_weather;get_news_from_newsnow;play_music\"}' where id = 'Intent_intent_llm'; 5 | -- 更新函数调用意图识别配置 6 | UPDATE `ai_model_config` SET config_json = REPLACE(config_json, ';get_news;', ';get_news_from_newsnow;') WHERE id = 'Intent_function_call'; 7 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/db/changelog/202505111914.sql: -------------------------------------------------------------------------------- 1 | -- 添加聊天记录配置字段 2 | ALTER TABLE `ai_agent` 3 | ADD COLUMN `chat_history_conf` tinyint NOT NULL DEFAULT 0 COMMENT '聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)' AFTER `system_prompt`; 4 | 5 | ALTER TABLE `ai_agent_template` 6 | ADD COLUMN `chat_history_conf` tinyint NOT NULL DEFAULT 0 COMMENT '聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)' AFTER `system_prompt`; -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/db/changelog/202505122348.sql: -------------------------------------------------------------------------------- 1 | -- 添加总结记忆字段 2 | ALTER TABLE `ai_agent` 3 | ADD COLUMN `summary_memory` text COMMENT '总结记忆' AFTER `system_prompt`; 4 | 5 | ALTER TABLE `ai_agent_template` 6 | ADD COLUMN `summary_memory` text COMMENT '总结记忆' AFTER `system_prompt`; 7 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/db/changelog/202505142037.sql: -------------------------------------------------------------------------------- 1 | update ai_agent_template set system_prompt = replace(system_prompt, '我是', '你是'); 2 | 3 | delete from sys_params where id in (500,501,402); 4 | INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (500, 'end_prompt.enable', 'true', 'boolean', 1, '是否开启结束语'); 5 | INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (501, 'end_prompt.prompt', '请你以“时间过得真快”未来头,用富有感情、依依不舍的话来结束这场对话吧!', 'string', 1, '结束提示词'); 6 | 7 | INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (402, 'plugins.get_weather.api_host', 'mj7p3y7naa.re.qweatherapi.com', 'string', 1, '开发者apihost'); -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/i18n/validation_en_US.properties: -------------------------------------------------------------------------------- 1 | #English 2 | id.require=ID can not be empty 3 | id.null=ID has to be empty 4 | 5 | sort.number=The sort value cannot be less than 0 6 | page.number=The page value cannot be less than 0 7 | limit.number=The limit value cannot be less than 0 8 | 9 | sysdict.type.require=The dictionary type cannot be empty 10 | sysdict.name.require=The dictionary name cannot be empty 11 | sysdict.label.require=Dictionary tag cannot be empty 12 | 13 | sysparams.paramcode.require=Parameter encoding cannot be empty 14 | sysparams.paramvalue.require=Parameter values cannot be empty 15 | sysparams.valuetype.require=Value type cannot be empty 16 | sysparams.valuetype.pattern=Value type must be string, number, boolean or array 17 | 18 | sysuser.username.require=The username cannot be empty 19 | sysuser.password.require=The password cannot be empty 20 | sysuser.realname.require=The realname cannot be empty 21 | sysuser.gender.range=Gender ranges from 0 to 2 22 | sysuser.email.error=Incorrect email format 23 | sysuser.deptId.require=Departments cannot be empty 24 | sysuser.status.range=State ranges from 0 to 1 25 | sysuser.captcha.require=The captcha cannot be empty 26 | sysuser.uuid.require=The unique identifier cannot be empty 27 | 28 | timbre.languages.require=The language of the timbre cannot be empty 29 | timbre.name.require=The name of the timbre cannot be empty 30 | timbre.ttsModelId.require=The TTS model ID of the timbre cannot be empty 31 | timbre.ttsVoice.require=The TTS voice of the timbre cannot be empty 32 | 33 | ota.device.not.found=Device not found 34 | ota.device.need.bind={0} -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/mapper/agent/AgentDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/mapper/device/DeviceDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/mapper/model/ModelConfigDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/mapper/model/ModelProviderDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/mapper/security/SysUserTokenDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 14 | 15 | update sys_user_token set expire_date = #{expireDate} where user_id = #{userId} 16 | 17 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/mapper/sys/SysDictDataDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/mapper/sys/SysDictTypeDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/mapper/sys/SysParamsDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 18 | 19 | 20 | 21 | update sys_params set param_value = #{paramValue} where param_code = #{paramCode} 22 | 23 | -------------------------------------------------------------------------------- /main/manager-api/src/main/resources/mapper/sys/SysUserDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /main/manager-api/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | data: 3 | redis: 4 | host: localhost 5 | port: 6379 6 | database: 0 7 | timeout: 10s 8 | lettuce: 9 | pool: 10 | min-idle: 0 11 | max-idle: 8 12 | max-active: 8 13 | max-wait: -1ms 14 | 15 | renren: 16 | redis: 17 | open: true 18 | 19 | logging: 20 | level: 21 | xiaozhi.modules.device: DEBUG 22 | org.springframework.data.redis: DEBUG -------------------------------------------------------------------------------- /main/manager-web/.env: -------------------------------------------------------------------------------- 1 | VUE_APP_TITLE=智控台 -------------------------------------------------------------------------------- /main/manager-web/.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_API_BASE_URL=/xiaozhi -------------------------------------------------------------------------------- /main/manager-web/.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_API_BASE_URL=/xiaozhi 2 | VUE_APP_PUBLIC_PATH=/ 3 | # 是否开启CDN 4 | VUE_APP_USE_CDN=false -------------------------------------------------------------------------------- /main/manager-web/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ 2 | -------------------------------------------------------------------------------- /main/manager-web/README.md: -------------------------------------------------------------------------------- 1 | 本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3) 2 | 3 | # xiaozhi 4 | 5 | ## Project setup 6 | 7 | 开发使用代码编辑器,导入项目时,选择`manager-web`文件夹作为项目目录 8 | 9 | ``` 10 | npm install 11 | ``` 12 | 13 | ### Compiles and hot-reloads for development 14 | 15 | ``` 16 | npm run serve 17 | ``` 18 | 19 | ### Compiles and minifies for production 20 | 21 | ``` 22 | npm run build 23 | ``` 24 | 25 | ### Customize configuration 26 | 27 | See [Configuration Reference](https://cli.vuejs.org/config/). 28 | -------------------------------------------------------------------------------- /main/manager-web/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@vue/cli-plugin-babel/preset', { 4 | useBuiltIns: 'usage', 5 | corejs: 3 6 | }] 7 | ], 8 | plugins: [ 9 | '@babel/plugin-syntax-dynamic-import', // 确保支持动态导入 (Lazy Loading) 10 | '@babel/plugin-transform-runtime' 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /main/manager-web/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } -------------------------------------------------------------------------------- /main/manager-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xiaozhi", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "analyze": "cross-env ANALYZE=true vue-cli-service build" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.41.0", 12 | "cross-env": "^7.0.3", 13 | "dotenv": "^16.5.0", 14 | "element-ui": "^2.15.14", 15 | "flyio": "^0.6.14", 16 | "normalize.css": "^8.0.1", 17 | "opus-decoder": "^0.7.7", 18 | "opus-recorder": "^8.0.5", 19 | "vue": "^2.6.14", 20 | "vue-axios": "^3.5.2", 21 | "vue-router": "^3.6.5", 22 | "vuex": "^3.6.2", 23 | "xiaozhi": "file:" 24 | }, 25 | "devDependencies": { 26 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 27 | "@babel/plugin-transform-runtime": "^7.26.10", 28 | "@vue/cli-plugin-router": "~5.0.0", 29 | "@vue/cli-plugin-vuex": "~5.0.0", 30 | "@vue/cli-service": "~5.0.0", 31 | "compression-webpack-plugin": "^11.1.0", 32 | "sass": "^1.32.7", 33 | "sass-loader": "^12.0.0", 34 | "vue-template-compiler": "^2.6.14", 35 | "webpack-bundle-analyzer": "^4.10.2", 36 | "workbox-webpack-plugin": "^7.3.0" 37 | }, 38 | "browserslist": [ 39 | "> 1%", 40 | "last 2 versions", 41 | "not dead" 42 | ], 43 | "sideEffects": [ 44 | "*.css", 45 | "*.scss", 46 | "*.vue" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /main/manager-web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/public/favicon.ico -------------------------------------------------------------------------------- /main/manager-web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= process.env.VUE_APP_TITLE %> 11 | 12 | <% if (htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> 13 | <% for (var i in htmlWebpackPlugin.options.cdn.css) { %> 14 | 15 | <% } %> 16 | <% } %> 17 | 18 | 19 | 20 | 24 |

25 | 26 | <% if (htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> 27 | <% for (var i in htmlWebpackPlugin.options.cdn.js) { %> 28 | 29 | <% } %> 30 | <% } %> 31 | 32 | 33 | -------------------------------------------------------------------------------- /main/manager-web/src/apis/api.js: -------------------------------------------------------------------------------- 1 | // 引入各个模块的请求 2 | import admin from './module/admin.js' 3 | import agent from './module/agent.js' 4 | import device from './module/device.js' 5 | import dict from './module/dict.js' 6 | import model from './module/model.js' 7 | import ota from './module/ota.js' 8 | import timbre from "./module/timbre.js" 9 | import user from './module/user.js' 10 | /** 11 | * 接口地址 12 | * 开发时自动读取使用.env.development文件 13 | * 编译时自动读取使用.env.production文件 14 | */ 15 | const DEV_API_SERVICE = process.env.VUE_APP_API_BASE_URL 16 | 17 | /** 18 | * 根据开发环境返回接口url 19 | * @returns {string} 20 | */ 21 | export function getServiceUrl() { 22 | return DEV_API_SERVICE 23 | } 24 | 25 | 26 | /** request服务封装 */ 27 | export default { 28 | getServiceUrl, 29 | user, 30 | admin, 31 | agent, 32 | device, 33 | model, 34 | timbre, 35 | ota, 36 | dict 37 | } 38 | -------------------------------------------------------------------------------- /main/manager-web/src/assets/header/firmware_update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/header/firmware_update.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/header/model_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/header/model_config.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/header/param_management.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/header/param_management.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/header/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/header/robot.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/header/user_management.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/header/user_management.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/home/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/home/avatar.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/home/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/home/close.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/home/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/home/delete.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/home/equipment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/home/equipment.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/home/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/home/info.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/home/main-top-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/home/main-top-bg.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/home/red-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/home/red-info.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/home/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/home/search.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/home/setting-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/home/setting-user.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/login/down-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/login/down-arrow.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/login/hi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/login/hi.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/login/login-person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/login/login-person.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/login/password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/login/password.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/login/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/login/phone.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/login/register-person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/login/register-person.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/login/shield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/login/shield.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/login/username.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/login/username.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/model/inner_conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/model/inner_conf.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/model/model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/model/model.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/model/output_conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/model/output_conf.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/user-avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/user-avatar1.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/user-avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/user-avatar2.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/user-avatar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/user-avatar3.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/user-avatar4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/user-avatar4.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/user-avatar5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/user-avatar5.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/xiaozhi-ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/xiaozhi-ai.png -------------------------------------------------------------------------------- /main/manager-web/src/assets/xiaozhi-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/manager-web/src/assets/xiaozhi-logo.png -------------------------------------------------------------------------------- /main/manager-web/src/components/VersionFooter.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | -------------------------------------------------------------------------------- /main/manager-web/src/main.js: -------------------------------------------------------------------------------- 1 | import ElementUI from 'element-ui'; 2 | import 'element-ui/lib/theme-chalk/index.css'; 3 | import 'normalize.css/normalize.css'; // A modern alternative to CSS resets 4 | import Vue from 'vue'; 5 | import App from './App.vue'; 6 | import router from './router'; 7 | import store from './store'; 8 | import './styles/global.scss'; 9 | import { register as registerServiceWorker } from './registerServiceWorker'; 10 | 11 | Vue.use(ElementUI); 12 | 13 | Vue.config.productionTip = false 14 | 15 | // 注册Service Worker 16 | registerServiceWorker(); 17 | 18 | // 创建Vue实例 19 | new Vue({ 20 | router, 21 | store, 22 | render: function (h) { return h(App) } 23 | }).$mount('#app') 24 | -------------------------------------------------------------------------------- /main/manager-web/src/styles/global.scss: -------------------------------------------------------------------------------- 1 | // 覆盖 autofill 样式 2 | input:-webkit-autofill, 3 | input:-webkit-autofill:hover, 4 | input:-webkit-autofill:focus, 5 | textarea:-webkit-autofill, 6 | textarea:-webkit-autofill:hover, 7 | textarea:-webkit-autofill:focus, 8 | select:-webkit-autofill, 9 | select:-webkit-autofill:hover, 10 | select:-webkit-autofill:focus { 11 | -webkit-box-shadow: 0 0 0px 1000px transparent inset; 12 | transition: background-color 5000s ease-in-out 0s; 13 | background-color: transparent; 14 | } -------------------------------------------------------------------------------- /main/manager-web/src/utils/constant.js: -------------------------------------------------------------------------------- 1 | const HAVE_NO_RESULT = '暂无' 2 | export default { 3 | HAVE_NO_RESULT, // 项目的配置信息 4 | PAGE: { 5 | LOGIN: '/login', 6 | }, 7 | STORAGE_KEY: { 8 | TOKEN: 'TOKEN', 9 | PUBLIC_KEY: 'PUBLIC_KEY', 10 | USER_TYPE: 'USER_TYPE' 11 | }, 12 | Lang: { 13 | 'zh_cn': 'zh_cn', 'zh_tw': 'zh_tw', 'en': 'en' 14 | }, 15 | FONT_SIZE: { 16 | 'big': 'big', 17 | 'normal': 'normal', 18 | }, // 获取map中的某key 19 | get(map, key) { 20 | return map[key] || HAVE_NO_RESULT 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /main/manager-web/src/utils/date.js: -------------------------------------------------------------------------------- 1 | export const toDate = (date) => { 2 | return isDate(date) ? new Date(date) : null 3 | } 4 | 5 | export const isDate = (date) => { 6 | if (date === null || date === undefined) return false 7 | if (isNaN(new Date(date).getTime())) return false 8 | return true 9 | } 10 | 11 | export const isDateObject = (val) => { 12 | return val instanceof Date 13 | } 14 | 15 | export const formatAddDate = (date, format, addDay) => { 16 | date = toDate(date) 17 | if (!date) { 18 | return '' 19 | } 20 | if (!addDay) { 21 | date.setDate(date.getDate() + addDay) 22 | } 23 | return formatDateTool(date, format || 'yyyy-MM-dd HH:mm:ss') 24 | } 25 | 26 | export const formatDate = (date, format) => { 27 | date = toDate(date) 28 | if (!date) return '' 29 | return formatDateTool(date, format || 'yyyy-MM-dd HH:mm:ss') 30 | } 31 | 32 | function formatDateTool(date, fmt) { 33 | if (/(y+)/.test(fmt)) { 34 | fmt = fmt.replace( 35 | RegExp.$1, 36 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 37 | ) 38 | } 39 | const o = { 40 | 'M+': date.getMonth() + 1, 41 | 'd+': date.getDate(), 42 | 'h+': date.getHours(), 43 | 'm+': date.getMinutes(), 44 | 's+': date.getSeconds() 45 | } 46 | for (const k in o) { 47 | if (new RegExp(`(${k})`).test(fmt)) { 48 | const str = o[k] + '' 49 | fmt = fmt.replace( 50 | RegExp.$1, 51 | RegExp.$1.length === 1 ? str : padLeftZero(str) 52 | ) 53 | } 54 | } 55 | return fmt 56 | } 57 | 58 | function padLeftZero(str) { 59 | return ('00' + str).substr(str.length) 60 | } 61 | -------------------------------------------------------------------------------- /main/manager-web/src/utils/format.js: -------------------------------------------------------------------------------- 1 | // 日期格式化函数 2 | export function formatDate(date) { 3 | if (!date) return ''; 4 | const d = new Date(date); 5 | const year = d.getFullYear(); 6 | const month = String(d.getMonth() + 1).padStart(2, '0'); 7 | const day = String(d.getDate()).padStart(2, '0'); 8 | const hour = String(d.getHours()).padStart(2, '0'); 9 | const minute = String(d.getMinutes()).padStart(2, '0'); 10 | const second = String(d.getSeconds()).padStart(2, '0'); 11 | return `${year}-${month}-${day} ${hour}:${minute}:${second}`; 12 | } 13 | 14 | // 文件大小格式化函数 15 | export function formatFileSize(bytes) { 16 | if (!bytes || bytes === 0) return '0 B'; 17 | const units = ['B', 'KB', 'MB', 'GB', 'TB']; 18 | const k = 1024; 19 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 20 | return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + units[i]; 21 | } -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code/0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code/0.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code/1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code/1.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code/2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code/2.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code/3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code/3.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code/4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code/4.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code/5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code/5.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code/6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code/6.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code/7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code/7.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code/8.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code/8.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_code/9.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_code/9.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/bind_not_found.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/bind_not_found.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/max_output_size.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/max_output_size.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/tts_notify.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/tts_notify.mp3 -------------------------------------------------------------------------------- /main/xiaozhi-server/config/assets/wakeup_words.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/config/assets/wakeup_words.wav -------------------------------------------------------------------------------- /main/xiaozhi-server/config/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from config.config_loader import read_config, get_project_dir, load_config 3 | 4 | 5 | default_config_file = "config.yaml" 6 | config_file_valid = False 7 | 8 | 9 | def check_config_file(): 10 | global config_file_valid 11 | if config_file_valid: 12 | return 13 | """ 14 | 简化的配置检查,仅提示用户配置文件的使用情况 15 | """ 16 | custom_config_file = get_project_dir() + "data/." + default_config_file 17 | if not os.path.exists(custom_config_file): 18 | raise FileNotFoundError( 19 | "找不到data/.config.yaml文件,请按教程确认该配置文件是否存在" 20 | ) 21 | 22 | # 检查是否从API读取配置 23 | config = load_config() 24 | if config.get("read_config_from_api", False): 25 | print("从API读取配置") 26 | old_config_origin = read_config(custom_config_file) 27 | if old_config_origin.get("selected_module") is not None: 28 | error_msg = "您的配置文件好像既包含智控台的配置又包含本地配置:\n" 29 | error_msg += "\n建议您:\n" 30 | error_msg += "1、将根目录的config_from_api.yaml文件复制到data下,重命名为.config.yaml\n" 31 | error_msg += "2、按教程配置好接口地址和密钥\n" 32 | raise ValueError(error_msg) 33 | config_file_valid = True 34 | -------------------------------------------------------------------------------- /main/xiaozhi-server/config_from_api.yaml: -------------------------------------------------------------------------------- 1 | # 如果你只想轻量化安装xiaozhi-server,只使用本地的配置文件,不需要理会这个文件,不需要改动本文件任何东西 2 | # 如果你想从manager-api获取配置,请往下看: 3 | # 请将本文件复制到xiaozhi-server/data目录下,没有data目录,请创建一个,并将复制过去的文件命名为.config.yaml 4 | # 注意如果data目录有.config.yaml文件,请先删除它 5 | # 先启动manager-api和manager-web,注册一个账号,第一个注册的账号为管理员 6 | # 使用管理员,进入【参数管理】页面,找到【server.secret】,复制它到参数值,注意每次从零部署,server.secret都会变化 7 | # 打开本data目录下的.config.yaml文件,修改manager-api.secret为刚才复制出来的server.secret 8 | server: 9 | ip: 0.0.0.0 10 | port: 8000 11 | manager-api: 12 | # 你的manager-api的地址,最好使用局域网ip 13 | url: http://127.0.0.1:8002/xiaozhi 14 | # 你的manager-api的token,就是刚才复制出来的server.secret 15 | secret: 你的server.secret值 -------------------------------------------------------------------------------- /main/xiaozhi-server/core/handle/abortHandle.py: -------------------------------------------------------------------------------- 1 | import json 2 | import queue 3 | from config.logger import setup_logging 4 | 5 | TAG = __name__ 6 | 7 | 8 | async def handleAbortMessage(conn): 9 | conn.logger.bind(tag=TAG).info("Abort message received") 10 | # 设置成打断状态,会自动打断llm、tts任务 11 | conn.client_abort = True 12 | conn.clear_queues() 13 | # 打断客户端说话状态 14 | await conn.websocket.send( 15 | json.dumps({"type": "tts", "state": "stop", "session_id": conn.session_id}) 16 | ) 17 | conn.clearSpeakStatus() 18 | conn.logger.bind(tag=TAG).info("Abort message received-end") 19 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/providers/asr/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Optional, Tuple, List 3 | import opuslib_next 4 | from config.logger import setup_logging 5 | 6 | TAG = __name__ 7 | logger = setup_logging() 8 | 9 | 10 | class ASRProviderBase(ABC): 11 | def __init__(self): 12 | self.audio_format = "opus" 13 | 14 | @abstractmethod 15 | def save_audio_to_file(self, pcm_data: List[bytes], session_id: str) -> str: 16 | """PCM数据保存为WAV文件""" 17 | pass 18 | 19 | @abstractmethod 20 | async def speech_to_text( 21 | self, opus_data: List[bytes], session_id: str 22 | ) -> Tuple[Optional[str], Optional[str]]: 23 | """将语音数据转换为文本""" 24 | pass 25 | 26 | def set_audio_format(self, format: str) -> None: 27 | """设置音频格式""" 28 | self.audio_format = format 29 | 30 | @staticmethod 31 | def decode_opus(opus_data: List[bytes]) -> bytes: 32 | """将Opus音频数据解码为PCM数据""" 33 | 34 | decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道 35 | pcm_data = [] 36 | 37 | for opus_packet in opus_data: 38 | try: 39 | pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms 40 | pcm_data.append(pcm_frame) 41 | except opuslib_next.OpusError as e: 42 | logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True) 43 | 44 | return pcm_data 45 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/providers/intent/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Dict 3 | from config.logger import setup_logging 4 | 5 | TAG = __name__ 6 | logger = setup_logging() 7 | 8 | 9 | class IntentProviderBase(ABC): 10 | def __init__(self, config): 11 | self.config = config 12 | 13 | def set_llm(self, llm): 14 | self.llm = llm 15 | # 获取模型名称和类型信息 16 | model_name = getattr(llm, "model_name", str(llm.__class__.__name__)) 17 | # 记录更详细的日志 18 | logger.bind(tag=TAG).info(f"意图识别设置LLM: {model_name}") 19 | 20 | @abstractmethod 21 | async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str: 22 | """ 23 | 检测用户最后一句话的意图 24 | Args: 25 | dialogue_history: 对话历史记录列表,每条记录包含role和content 26 | Returns: 27 | 返回识别出的意图,格式为: 28 | - "继续聊天" 29 | - "结束聊天" 30 | - "播放音乐 歌名" 或 "随机播放音乐" 31 | - "查询天气 地点名" 或 "查询天气 [当前位置]" 32 | """ 33 | pass 34 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/providers/intent/function_call/function_call.py: -------------------------------------------------------------------------------- 1 | from ..base import IntentProviderBase 2 | from typing import List, Dict 3 | from config.logger import setup_logging 4 | 5 | TAG = __name__ 6 | logger = setup_logging() 7 | 8 | 9 | class IntentProvider(IntentProviderBase): 10 | async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str: 11 | """ 12 | 默认的意图识别实现,始终返回继续聊天 13 | Args: 14 | dialogue_history: 对话历史记录列表 15 | text: 本次对话记录 16 | Returns: 17 | 固定返回"继续聊天" 18 | """ 19 | logger.bind(tag=TAG).debug( 20 | "Using functionCallProvider, always returning continue chat" 21 | ) 22 | return '{"function_call": {"name": "continue_chat"}}' 23 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/providers/intent/nointent/nointent.py: -------------------------------------------------------------------------------- 1 | from ..base import IntentProviderBase 2 | from typing import List, Dict 3 | from config.logger import setup_logging 4 | 5 | TAG = __name__ 6 | logger = setup_logging() 7 | 8 | 9 | class IntentProvider(IntentProviderBase): 10 | async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str: 11 | """ 12 | 默认的意图识别实现,始终返回继续聊天 13 | Args: 14 | dialogue_history: 对话历史记录列表 15 | text: 本次对话记录 16 | Returns: 17 | 固定返回"继续聊天" 18 | """ 19 | logger.bind(tag=TAG).debug( 20 | "Using NoIntentProvider, always returning continue chat" 21 | ) 22 | return '{"function_call": {"name": "continue_chat"}}' 23 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/providers/llm/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from config.logger import setup_logging 3 | 4 | TAG = __name__ 5 | logger = setup_logging() 6 | 7 | class LLMProviderBase(ABC): 8 | @abstractmethod 9 | def response(self, session_id, dialogue): 10 | """LLM response generator""" 11 | pass 12 | 13 | def response_no_stream(self, system_prompt, user_prompt): 14 | try: 15 | # 构造对话格式 16 | dialogue = [ 17 | {"role": "system", "content": system_prompt}, 18 | {"role": "user", "content": user_prompt} 19 | ] 20 | result = "" 21 | for part in self.response("", dialogue): 22 | result += part 23 | return result 24 | 25 | except Exception as e: 26 | logger.bind(tag=TAG).error(f"Error in Ollama response generation: {e}") 27 | return "【LLM服务响应异常】" 28 | 29 | def response_with_functions(self, session_id, dialogue, functions=None): 30 | """ 31 | Default implementation for function calling (streaming) 32 | This should be overridden by providers that support function calls 33 | 34 | Returns: generator that yields either text tokens or a special function call token 35 | """ 36 | # For providers that don't support functions, just return regular response 37 | for token in self.response(session_id, dialogue): 38 | yield token, None 39 | 40 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/providers/memory/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from config.logger import setup_logging 3 | 4 | TAG = __name__ 5 | logger = setup_logging() 6 | 7 | 8 | class MemoryProviderBase(ABC): 9 | def __init__(self, config): 10 | self.config = config 11 | self.role_id = None 12 | self.llm = None 13 | 14 | @abstractmethod 15 | async def save_memory(self, msgs): 16 | """Save a new memory for specific role and return memory ID""" 17 | print("this is base func", msgs) 18 | 19 | @abstractmethod 20 | async def query_memory(self, query: str) -> str: 21 | """Query memories for specific role based on similarity""" 22 | return "please implement query method" 23 | 24 | def init_memory(self, role_id, llm, **kwargs): 25 | self.role_id = role_id 26 | self.llm = llm 27 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/providers/memory/nomem/nomem.py: -------------------------------------------------------------------------------- 1 | """ 2 | 不使用记忆,可以选择此模块 3 | """ 4 | 5 | from ..base import MemoryProviderBase, logger 6 | 7 | TAG = __name__ 8 | 9 | 10 | class MemoryProvider(MemoryProviderBase): 11 | def __init__(self, config, summary_memory=None): 12 | super().__init__(config) 13 | 14 | async def save_memory(self, msgs): 15 | logger.bind(tag=TAG).debug("nomem mode: No memory saving is performed.") 16 | return None 17 | 18 | async def query_memory(self, query: str) -> str: 19 | logger.bind(tag=TAG).debug("nomem mode: No memory query is performed.") 20 | return "" 21 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/providers/tts/custom.py: -------------------------------------------------------------------------------- 1 | import os 2 | import uuid 3 | import requests 4 | from config.logger import setup_logging 5 | from datetime import datetime 6 | from core.providers.tts.base import TTSProviderBase 7 | 8 | TAG = __name__ 9 | logger = setup_logging() 10 | 11 | class TTSProvider(TTSProviderBase): 12 | def __init__(self, config, delete_audio_file): 13 | super().__init__(config, delete_audio_file) 14 | self.url = config.get("url") 15 | self.headers = config.get("headers", {}) 16 | self.params = config.get("params") 17 | self.format = config.get("format", "wav") 18 | self.output_file = config.get("output_dir", "tmp/") 19 | 20 | def generate_filename(self): 21 | return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}.{self.format}") 22 | 23 | async def text_to_speak(self, text, output_file): 24 | request_params = {} 25 | for k, v in self.params.items(): 26 | if isinstance(v, str) and "{prompt_text}" in v: 27 | v = v.replace("{prompt_text}", text) 28 | request_params[k] = v 29 | 30 | resp = requests.get(self.url, params=request_params, headers=self.headers) 31 | if resp.status_code == 200: 32 | with open(output_file, "wb") as file: 33 | file.write(resp.content) 34 | else: 35 | error_msg = f"Custom TTS请求失败: {resp.status_code} - {resp.text}" 36 | logger.bind(tag=TAG).error(error_msg) 37 | raise Exception(error_msg) # 抛出异常,让调用方捕获 38 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/providers/tts/edge.py: -------------------------------------------------------------------------------- 1 | import os 2 | import uuid 3 | import edge_tts 4 | from datetime import datetime 5 | from core.providers.tts.base import TTSProviderBase 6 | 7 | 8 | class TTSProvider(TTSProviderBase): 9 | def __init__(self, config, delete_audio_file): 10 | super().__init__(config, delete_audio_file) 11 | if config.get("private_voice"): 12 | self.voice = config.get("private_voice") 13 | else: 14 | self.voice = config.get("voice") 15 | 16 | def generate_filename(self, extension=".mp3"): 17 | return os.path.join( 18 | self.output_file, 19 | f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}", 20 | ) 21 | 22 | async def text_to_speak(self, text, output_file): 23 | try: 24 | communicate = edge_tts.Communicate(text, voice=self.voice) 25 | # 确保目录存在并创建空文件 26 | os.makedirs(os.path.dirname(output_file), exist_ok=True) 27 | with open(output_file, "wb") as f: 28 | pass 29 | 30 | # 流式写入音频数据 31 | with open(output_file, "ab") as f: # 改为追加模式避免覆盖 32 | async for chunk in communicate.stream(): 33 | if chunk["type"] == "audio": # 只处理音频数据块 34 | f.write(chunk["data"]) 35 | except Exception as e: 36 | error_msg = f"Edge TTS请求失败: {e}" 37 | raise Exception(error_msg) # 抛出异常,让调用方捕获 -------------------------------------------------------------------------------- /main/xiaozhi-server/core/providers/vad/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Optional 3 | 4 | 5 | class VADProviderBase(ABC): 6 | @abstractmethod 7 | def is_vad(self, conn, data) -> bool: 8 | """检测音频数据中的语音活动""" 9 | pass 10 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/utils/asr.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import logging 3 | import os 4 | import sys 5 | import time 6 | import wave 7 | import uuid 8 | from abc import ABC, abstractmethod 9 | from typing import Optional, Tuple, List 10 | from core.providers.asr.base import ASRProviderBase 11 | from config.logger import setup_logging 12 | 13 | TAG = __name__ 14 | logger = setup_logging() 15 | 16 | def create_instance(class_name: str, *args, **kwargs) -> ASRProviderBase: 17 | """工厂方法创建ASR实例""" 18 | if os.path.exists(os.path.join('core', 'providers', 'asr', f'{class_name}.py')): 19 | lib_name = f'core.providers.asr.{class_name}' 20 | if lib_name not in sys.modules: 21 | sys.modules[lib_name] = importlib.import_module(f'{lib_name}') 22 | return sys.modules[lib_name].ASRProvider(*args, **kwargs) 23 | 24 | raise ValueError(f"不支持的ASR类型: {class_name},请检查该配置的type是否设置正确") -------------------------------------------------------------------------------- /main/xiaozhi-server/core/utils/intent.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from config.logger import setup_logging 4 | import importlib 5 | 6 | logger = setup_logging() 7 | 8 | 9 | def create_instance(class_name, *args, **kwargs): 10 | # 创建intent实例 11 | if os.path.exists(os.path.join('core', 'providers', 'intent', class_name, f'{class_name}.py')): 12 | lib_name = f'core.providers.intent.{class_name}.{class_name}' 13 | if lib_name not in sys.modules: 14 | sys.modules[lib_name] = importlib.import_module(f'{lib_name}') 15 | return sys.modules[lib_name].IntentProvider(*args, **kwargs) 16 | 17 | raise ValueError(f"不支持的intent类型: {class_name},请检查该配置的type是否设置正确") -------------------------------------------------------------------------------- /main/xiaozhi-server/core/utils/llm.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | # 添加项目根目录到Python路径 5 | current_dir = os.path.dirname(os.path.abspath(__file__)) 6 | project_root = os.path.abspath(os.path.join(current_dir, "..", "..")) 7 | sys.path.insert(0, project_root) 8 | 9 | from config.logger import setup_logging 10 | import importlib 11 | 12 | logger = setup_logging() 13 | 14 | 15 | def create_instance(class_name, *args, **kwargs): 16 | # 创建LLM实例 17 | if os.path.exists(os.path.join('core', 'providers', 'llm', class_name, f'{class_name}.py')): 18 | lib_name = f'core.providers.llm.{class_name}.{class_name}' 19 | if lib_name not in sys.modules: 20 | sys.modules[lib_name] = importlib.import_module(f'{lib_name}') 21 | return sys.modules[lib_name].LLMProvider(*args, **kwargs) 22 | 23 | raise ValueError(f"不支持的LLM类型: {class_name},请检查该配置的type是否设置正确") 24 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/utils/memory.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import importlib 4 | from config.logger import setup_logging 5 | 6 | logger = setup_logging() 7 | 8 | 9 | def create_instance(class_name, *args, **kwargs): 10 | if os.path.exists( 11 | os.path.join("core", "providers", "memory", class_name, f"{class_name}.py") 12 | ): 13 | lib_name = f"core.providers.memory.{class_name}.{class_name}" 14 | if lib_name not in sys.modules: 15 | sys.modules[lib_name] = importlib.import_module(f"{lib_name}") 16 | return sys.modules[lib_name].MemoryProvider(*args, **kwargs) 17 | 18 | raise ValueError(f"不支持的记忆服务类型: {class_name}") 19 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/utils/output_counter.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Dict, Tuple 3 | 4 | # 全局字典,用于存储每个设备的每日输出字数 5 | _device_daily_output: Dict[Tuple[str, datetime.date], int] = {} 6 | # 记录最后一次检查的日期 7 | _last_check_date: datetime.date = None 8 | 9 | 10 | def reset_device_output(): 11 | """ 12 | 重置所有设备的每日输出字数 13 | 每天0点调用此函数 14 | """ 15 | _device_daily_output.clear() 16 | 17 | 18 | def get_device_output(device_id: str) -> int: 19 | """ 20 | 获取设备当日的输出字数 21 | """ 22 | current_date = datetime.datetime.now().date() 23 | return _device_daily_output.get((device_id, current_date), 0) 24 | 25 | 26 | def add_device_output(device_id: str, char_count: int): 27 | """ 28 | 增加设备的输出字数 29 | """ 30 | current_date = datetime.datetime.now().date() 31 | global _last_check_date 32 | 33 | # 如果是第一次调用或者日期发生变化,清空计数器 34 | if _last_check_date is None or _last_check_date != current_date: 35 | _device_daily_output.clear() 36 | _last_check_date = current_date 37 | 38 | current_count = _device_daily_output.get((device_id, current_date), 0) 39 | _device_daily_output[(device_id, current_date)] = current_count + char_count 40 | 41 | 42 | def check_device_output_limit(device_id: str, max_output_size: int) -> bool: 43 | """ 44 | 检查设备是否超过输出限制 45 | :return: True 如果超过限制,False 如果未超过 46 | """ 47 | if not device_id: 48 | return False 49 | current_output = get_device_output(device_id) 50 | return current_output >= max_output_size 51 | -------------------------------------------------------------------------------- /main/xiaozhi-server/core/utils/p3.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | def decode_opus_from_file(input_file): 4 | """ 5 | 从p3文件中解码 Opus 数据,并返回一个 Opus 数据包的列表以及总时长。 6 | """ 7 | opus_datas = [] 8 | total_frames = 0 9 | sample_rate = 16000 # 文件采样率 10 | frame_duration_ms = 60 # 帧时长 11 | frame_size = int(sample_rate * frame_duration_ms / 1000) 12 | 13 | with open(input_file, 'rb') as f: 14 | while True: 15 | # 读取头部(4字节):[1字节类型,1字节保留,2字节长度] 16 | header = f.read(4) 17 | if not header: 18 | break 19 | 20 | # 解包头部信息 21 | _, _, data_len = struct.unpack('>BBH', header) 22 | 23 | # 根据头部指定的长度读取 Opus 数据 24 | opus_data = f.read(data_len) 25 | if len(opus_data) != data_len: 26 | raise ValueError(f"Data length({len(opus_data)}) mismatch({data_len}) in the file.") 27 | 28 | opus_datas.append(opus_data) 29 | total_frames += 1 30 | 31 | # 计算总时长 32 | total_duration = (total_frames * frame_duration_ms) / 1000.0 33 | return opus_datas, total_duration -------------------------------------------------------------------------------- /main/xiaozhi-server/core/utils/vad.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import os 3 | import sys 4 | from core.providers.vad.base import VADProviderBase 5 | from config.logger import setup_logging 6 | 7 | TAG = __name__ 8 | logger = setup_logging() 9 | 10 | 11 | def create_instance(class_name: str, *args, **kwargs) -> VADProviderBase: 12 | """工厂方法创建VAD实例""" 13 | if os.path.exists(os.path.join("core", "providers", "vad", f"{class_name}.py")): 14 | lib_name = f"core.providers.vad.{class_name}" 15 | if lib_name not in sys.modules: 16 | sys.modules[lib_name] = importlib.import_module(f"{lib_name}") 17 | return sys.modules[lib_name].VADProvider(*args, **kwargs) 18 | 19 | raise ValueError(f"不支持的VAD类型: {class_name},请检查该配置的type是否设置正确") 20 | -------------------------------------------------------------------------------- /main/xiaozhi-server/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker安装Server 2 | 3 | version: '3' 4 | services: 5 | xiaozhi-esp32-server: 6 | image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest 7 | container_name: xiaozhi-esp32-server 8 | restart: always 9 | security_opt: 10 | - seccomp:unconfined 11 | environment: 12 | - TZ=Asia/Shanghai 13 | ports: 14 | # ws服务端 15 | - "8000:8000" 16 | # ota服务端 17 | - "8002:8002" 18 | volumes: 19 | # 配置文件目录 20 | - ./data:/opt/xiaozhi-esp32-server/data 21 | # 模型文件挂接,很重要 22 | - ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt -------------------------------------------------------------------------------- /main/xiaozhi-server/mcp_server_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "des": [ 3 | "在data目录下创建.mcp_server_settings.json文件,可以选择下面的MCP服务,也可以自行添加新的MCP服务。", 4 | "后面不断测试补充好用的mcp服务,欢迎大家一起补充。", 5 | "记得删除注释行,des属性仅为说明,不会被解析。", 6 | "des和link属性,仅为说明安装方式,方便大家查看原始链接,不是必须项。", 7 | "当前支持stdio/sse两种模式。" 8 | ], 9 | "mcpServers": { 10 | "Home Assistant": { 11 | "command": "mcp-proxy", 12 | "args": [ 13 | "http://YOUR_HA_HOST/mcp_server/sse" 14 | ], 15 | "env": { 16 | "API_ACCESS_TOKEN": "YOUR_API_ACCESS_TOKEN" 17 | } 18 | }, 19 | "filesystem": { 20 | "command": "npx", 21 | "args": [ 22 | "-y", 23 | "@modelcontextprotocol/server-filesystem", 24 | "/Users/username/Desktop", 25 | "/path/to/other/allowed/dir" 26 | ], 27 | "link":"https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem" 28 | }, 29 | "playwright": { 30 | "command": "npx", 31 | "args": ["-y", "@executeautomation/playwright-mcp-server"], 32 | "des" : "run 'npx playwright install' first", 33 | "link": "https://github.com/executeautomation/mcp-playwright" 34 | }, 35 | "windows-cli": { 36 | "command": "npx", 37 | "args": ["-y", "@simonb97/server-win-cli"], 38 | "link": "https://github.com/SimonB97/win-cli-mcp-server" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /main/xiaozhi-server/models/SenseVoiceSmall/chn_jpn_yue_eng_ko_spectok.bpe.model: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/SenseVoiceSmall/chn_jpn_yue_eng_ko_spectok.bpe.model -------------------------------------------------------------------------------- /main/xiaozhi-server/models/SenseVoiceSmall/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "pytorch", 3 | "task" : "auto-speech-recognition", 4 | "model": {"type" : "funasr"}, 5 | "pipeline": {"type":"funasr-pipeline"}, 6 | "model_name_in_hub": { 7 | "ms":"", 8 | "hf":""}, 9 | "file_path_metas": { 10 | "init_param":"model.pt", 11 | "config":"config.yaml", 12 | "tokenizer_conf": {"bpemodel": "chn_jpn_yue_eng_ko_spectok.bpe.model"}, 13 | "frontend_conf":{"cmvn_file": "am.mvn"}} 14 | } -------------------------------------------------------------------------------- /main/xiaozhi-server/models/SenseVoiceSmall/demo.py: -------------------------------------------------------------------------------- 1 | from funasr import AutoModel 2 | from funasr.utils.postprocess_utils import rich_transcription_postprocess 3 | 4 | model_dir = "./" 5 | 6 | 7 | model = AutoModel( 8 | model=model_dir, 9 | vad_model="fsmn-vad", 10 | vad_kwargs={"max_single_segment_time": 30000}, 11 | # device="cuda:0", 12 | hub="hf", 13 | ) 14 | 15 | # en 16 | res = model.generate( 17 | input=f"{model.model_path}/example/en.mp3", 18 | cache={}, 19 | language="auto", # "zn", "en", "yue", "ja", "ko", "nospeech" 20 | use_itn=True, 21 | batch_size_s=60, 22 | merge_vad=True, # 23 | merge_length_s=15, 24 | ) 25 | text = rich_transcription_postprocess(res[0]["text"]) 26 | print(text) 27 | 28 | -------------------------------------------------------------------------------- /main/xiaozhi-server/models/SenseVoiceSmall/example/en.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/SenseVoiceSmall/example/en.mp3 -------------------------------------------------------------------------------- /main/xiaozhi-server/models/SenseVoiceSmall/example/ja.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/SenseVoiceSmall/example/ja.mp3 -------------------------------------------------------------------------------- /main/xiaozhi-server/models/SenseVoiceSmall/example/ko.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/SenseVoiceSmall/example/ko.mp3 -------------------------------------------------------------------------------- /main/xiaozhi-server/models/SenseVoiceSmall/example/yue.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/SenseVoiceSmall/example/yue.mp3 -------------------------------------------------------------------------------- /main/xiaozhi-server/models/SenseVoiceSmall/example/zh.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/SenseVoiceSmall/example/zh.mp3 -------------------------------------------------------------------------------- /main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib.metadata import version 2 | try: 3 | __version__ = version(__name__) 4 | except: 5 | pass 6 | 7 | from silero_vad.model import load_silero_vad 8 | from silero_vad.utils_vad import (get_speech_timestamps, 9 | save_audio, 10 | read_audio, 11 | VADIterator, 12 | collect_chunks) -------------------------------------------------------------------------------- /main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/data/__init__.py -------------------------------------------------------------------------------- /main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/data/silero_vad.jit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/data/silero_vad.jit -------------------------------------------------------------------------------- /main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/data/silero_vad.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/data/silero_vad.onnx -------------------------------------------------------------------------------- /main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/data/silero_vad_16k_op15.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/data/silero_vad_16k_op15.onnx -------------------------------------------------------------------------------- /main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/data/silero_vad_half.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/data/silero_vad_half.onnx -------------------------------------------------------------------------------- /main/xiaozhi-server/models/snakers4_silero-vad/src/silero_vad/model.py: -------------------------------------------------------------------------------- 1 | from .utils_vad import init_jit_model, OnnxWrapper 2 | import torch 3 | torch.set_num_threads(1) 4 | 5 | 6 | def load_silero_vad(onnx=False, opset_version=16): 7 | available_ops = [15, 16] 8 | if onnx and opset_version not in available_ops: 9 | raise Exception(f'Available ONNX opset_version: {available_ops}') 10 | 11 | if onnx: 12 | if opset_version == 16: 13 | model_name = 'silero_vad.onnx' 14 | else: 15 | model_name = f'silero_vad_16k_op{opset_version}.onnx' 16 | else: 17 | model_name = 'silero_vad.jit' 18 | package_path = "silero_vad.data" 19 | 20 | try: 21 | import importlib_resources as impresources 22 | model_file_path = str(impresources.files(package_path).joinpath(model_name)) 23 | except: 24 | from importlib import resources as impresources 25 | try: 26 | with impresources.path(package_path, model_name) as f: 27 | model_file_path = f 28 | except: 29 | model_file_path = str(impresources.files(package_path).joinpath(model_name)) 30 | 31 | if onnx: 32 | model = OnnxWrapper(model_file_path, force_onnx_cpu=True) 33 | else: 34 | model = init_jit_model(model_file_path) 35 | 36 | return model 37 | -------------------------------------------------------------------------------- /main/xiaozhi-server/music/一念千年_国风版.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/music/一念千年_国风版.mp3 -------------------------------------------------------------------------------- /main/xiaozhi-server/music/中秋月.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/music/中秋月.mp3 -------------------------------------------------------------------------------- /main/xiaozhi-server/music/廉波老矣,尚能饭否.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c1pher-cn/xiaozhi-esp32-server/cf2afb9777613a6e0f08a5f9e7216dce933e5aa0/main/xiaozhi-server/music/廉波老矣,尚能饭否.mp3 -------------------------------------------------------------------------------- /main/xiaozhi-server/plugins_func/functions/handle_exit_intent.py: -------------------------------------------------------------------------------- 1 | from plugins_func.register import register_function, ToolType, ActionResponse, Action 2 | from config.logger import setup_logging 3 | 4 | TAG = __name__ 5 | logger = setup_logging() 6 | 7 | handle_exit_intent_function_desc = { 8 | "type": "function", 9 | "function": { 10 | "name": "handle_exit_intent", 11 | "description": "当用户想结束对话或需要退出系统时调用", 12 | "parameters": { 13 | "type": "object", 14 | "properties": { 15 | "say_goodbye": { 16 | "type": "string", 17 | "description": "和用户友好结束对话的告别语", 18 | } 19 | }, 20 | "required": ["say_goodbye"], 21 | }, 22 | }, 23 | } 24 | 25 | 26 | @register_function( 27 | "handle_exit_intent", handle_exit_intent_function_desc, ToolType.SYSTEM_CTL 28 | ) 29 | def handle_exit_intent(conn, say_goodbye: str | None = None): 30 | # 处理退出意图 31 | try: 32 | if say_goodbye is None: 33 | say_goodbye = "再见,祝您生活愉快!" 34 | conn.close_after_chat = True 35 | logger.bind(tag=TAG).info(f"退出意图已处理:{say_goodbye}") 36 | return ActionResponse( 37 | action=Action.RESPONSE, result="退出意图已处理", response=say_goodbye 38 | ) 39 | except Exception as e: 40 | logger.bind(tag=TAG).error(f"处理退出意图错误: {e}") 41 | return ActionResponse( 42 | action=Action.NONE, result="退出意图处理失败", response="" 43 | ) 44 | -------------------------------------------------------------------------------- /main/xiaozhi-server/plugins_func/functions/hass_init.py: -------------------------------------------------------------------------------- 1 | from config.logger import setup_logging 2 | from core.utils.util import check_model_key 3 | 4 | TAG = __name__ 5 | logger = setup_logging() 6 | 7 | HASS_CACHE = {} 8 | 9 | 10 | def append_devices_to_prompt(conn): 11 | if conn.intent_type == "function_call": 12 | funcs = conn.config["Intent"][conn.config["selected_module"]["Intent"]].get( 13 | "functions", [] 14 | ) 15 | if "hass_get_state" in funcs or "hass_set_state" in funcs: 16 | prompt = "\n下面是我家智能设备列表(位置,设备名,entity_id),可以通过homeassistant控制\n" 17 | devices = conn.config["plugins"]["home_assistant"].get("devices", []) 18 | if len(devices) == 0: 19 | return 20 | for device in devices: 21 | prompt += device + "\n" 22 | conn.prompt += prompt 23 | # 更新提示词 24 | conn.dialogue.update_system_message(conn.prompt) 25 | 26 | 27 | def initialize_hass_handler(conn): 28 | global HASS_CACHE 29 | if HASS_CACHE == {}: 30 | if conn.load_function_plugin: 31 | funcs = conn.config["Intent"][conn.config["selected_module"]["Intent"]].get( 32 | "functions", [] 33 | ) 34 | if "hass_get_state" in funcs or "hass_set_state" in funcs: 35 | HASS_CACHE["base_url"] = conn.config["plugins"]["home_assistant"].get( 36 | "base_url" 37 | ) 38 | HASS_CACHE["api_key"] = conn.config["plugins"]["home_assistant"].get( 39 | "api_key" 40 | ) 41 | 42 | check_model_key("home_assistant", HASS_CACHE["api_key"]) 43 | return HASS_CACHE 44 | -------------------------------------------------------------------------------- /main/xiaozhi-server/plugins_func/loadplugins.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import pkgutil 3 | from config.logger import setup_logging 4 | 5 | TAG = __name__ 6 | 7 | logger = setup_logging() 8 | 9 | def auto_import_modules(package_name): 10 | """ 11 | 自动导入指定包内的所有模块。 12 | 13 | Args: 14 | package_name (str): 包的名称,如 'functions'。 15 | """ 16 | # 获取包的路径 17 | package = importlib.import_module(package_name) 18 | package_path = package.__path__ 19 | 20 | # 遍历包内的所有模块 21 | for _, module_name, _ in pkgutil.iter_modules(package_path): 22 | # 导入模块 23 | full_module_name = f"{package_name}.{module_name}" 24 | importlib.import_module(full_module_name) 25 | #logger.bind(tag=TAG).info(f"模块 '{full_module_name}' 已加载") -------------------------------------------------------------------------------- /main/xiaozhi-server/requirements.txt: -------------------------------------------------------------------------------- 1 | pyyml==0.0.2 2 | torch==2.2.2 3 | silero_vad==5.1.2 4 | websockets==14.2 5 | opuslib_next==1.1.2 6 | numpy==1.26.4 7 | pydub==0.25.1 8 | funasr==1.2.3 9 | torchaudio==2.2.2 10 | openai==1.61.0 11 | google-generativeai==0.8.4 12 | edge_tts==7.0.0 13 | httpx==0.27.2 14 | aiohttp==3.9.3 15 | aiohttp_cors==0.7.0 16 | ormsgpack==1.7.0 17 | ruamel.yaml==0.18.10 18 | loguru==0.7.3 19 | requests==2.32.3 20 | cozepy==0.12.0 21 | mem0ai==0.1.62 22 | bs4==0.0.2 23 | modelscope==1.23.2 24 | sherpa_onnx==1.11.0 25 | mcp==1.8.1 26 | cnlunar==0.2.0 27 | PySocks==1.7.1 28 | dashscope==1.23.1 29 | baidu-aip==4.16.13 30 | chardet==5.2.0 31 | aioconsole==0.8.1 32 | markitdown==0.1.1 33 | mcp-proxy==0.6.0 34 | --------------------------------------------------------------------------------