diff --git a/README.md b/README.md index b3d6ff1f..c0d474df 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,13 @@ Spearheaded by Professor Siyuan Liu's Team (South China University of Technology + + + + MQTT指令下发 + + + @@ -84,13 +91,6 @@ Spearheaded by Professor Siyuan Liu's Team (South China University of Technology - - - - 成本最低配置 - - - @@ -241,7 +241,7 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/ ![请参考-全模块安装架构图](docs/images/deploy2.png) | 功能模块 | 描述 | |:---:|:---| -| 核心架构 | 基于MQTT+UDP网关、WebSocket、HTTP服务器,提供完整的控制台管理和认证系统 | +| 核心架构 | 基于[MQTT+UDP网关](https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/mqtt-gateway-integration.md)、WebSocket、HTTP服务器,提供完整的控制台管理和认证系统 | | 语音交互 | 支持流式ASR(语音识别)、流式TTS(语音合成)、VAD(语音活动检测),支持多语言识别和语音处理 | | 声纹识别 | 支持多用户声纹注册、管理和识别,与ASR并行处理,实时识别说话人身份并传递给LLM进行个性化回应 | | 智能对话 | 支持多种LLM(大语言模型),实现智能对话 | @@ -249,7 +249,8 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/ | 意图识别 | 支持LLM意图识别、Function Call函数调用,提供插件化意图处理机制 | | 记忆系统 | 支持本地短期记忆、mem0ai接口记忆,具备记忆总结功能 | | 工具调用 | 支持客户端IOT协议、客户MCP协议、服务端MCP协议、MCP接入点协议、自定义工具函数 | -| 管理后台 | 提供Web管理界面,支持用户管理、系统配置和设备管理 | +| 指令下发 | 依托MQTT协议,支持从智控台将MCP指令下发到ESP32设备 | +| 管理后台 | 提供Web管理界面,支持用户管理、系统配置和设备管理;界面支持中文简体、中文繁体、英文显示 | | 测试工具 | 提供性能测试工具、视觉模型测试工具和音频交互测试工具 | | 部署支持 | 支持Docker部署和本地部署,提供完整的配置文件管理 | | 插件系统 | 支持功能插件扩展、自定义插件开发和插件热加载 | @@ -263,13 +264,7 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/ --- ## 产品生态 👬 -小智是一个生态,当你使用这个产品时,也可以看看其他在这个生态圈的优秀项目 - -| 项目名称 | 项目地址 | 项目描述 | -|:---------------------|:--------|:--------| -| 小智安卓客户端 | [xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) | 一个基于xiaozhi-server的Android、IOS语音对话应用,支持实时语音交互和文字对话。
现在是flutter版本,打通IOS、Android端。 | -| 小智电脑客户端 | [py-xiaozhi](https://github.com/Huang-junsen/py-xiaozhi) | 该项目提供了一个基于 Python 实现的小白 AI 客户端,使得在不具备实体硬件条件的情况下,
依然能够体过代码体验小智 AI 的功能。 | -| 小智Java服务端 | [xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) | 小智开源后端服务 Java 版本是一个基于 Java 的开源项目。
它包括前后端的服务,旨在为用户提供一个完整的后端服务解决方案。 | +小智是一个生态,当你使用这个产品时,也可以看看其他在这个生态圈的[优秀项目](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE) --- diff --git a/README_en.md b/README_en.md index 5432eae8..a50006f8 100644 --- a/README_en.md +++ b/README_en.md @@ -62,6 +62,13 @@ Want to see the usage effects? Click the videos below 🎥 + + + + MQTT command issuance + + + @@ -83,13 +90,6 @@ Want to see the usage effects? Click the videos below 🎥 - - - - Lowest cost configuration - - - @@ -237,15 +237,16 @@ This project provides the following testing tools to help you verify the system ![请参考-全模块安装架构图](docs/images/deploy2.png) | Feature Module | Description | |:---:|:---| -| Core Architecture | Based on WebSocket and HTTP servers, provides complete console management and authentication system | +| Core Architecture | Based on [MQTT+UDP gateway](https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/mqtt-gateway-integration.md), WebSocket and HTTP servers, provides complete console management and authentication system | | Voice Interaction | Supports streaming ASR(speech recognition), streaming TTS(speech synthesis), VAD(voice activity detection), supports multi-language recognition and voice processing | | Voiceprint Recognition | Supports multi-user voiceprint registration, management, and recognition, processes in parallel with ASR, real-time speaker identity recognition and passes to LLM for personalized responses | | Intelligent Dialogue | Supports multiple LLM(large language models), implements intelligent dialogue | | Visual Perception | Supports multiple VLLM(vision large models), implements multimodal interaction | | Intent Recognition | Supports LLM intent recognition, Function Call function calling, provides plugin-based intent processing mechanism | | Memory System | Supports local short-term memory, mem0ai interface memory, with memory summarization functionality | +| Command Delivery | Supports MCP command delivery to ESP32 devices via MQTT protocol from Smart Console | | Tool Calling | Supports client IOT protocol, client MCP protocol, server MCP protocol, MCP endpoint protocol, custom tool functions | -| Management Backend | Provides Web management interface, supports user management, system configuration, and device management | +| Management Backend | Provides Web management interface, supports user management, system configuration and device management; Supports Simplified Chinese, Traditional Chinese and English display | | Testing Tools | Provides performance testing tools, vision model testing tools, and audio interaction testing tools | | Deployment Support | Supports Docker deployment and local deployment, provides complete configuration file management | | Plugin System | Supports functional plugin extensions, custom plugin development, and plugin hot-loading | @@ -259,7 +260,7 @@ If you are a software developer, here is an [Open Letter to Developers](docs/con --- ## Product Ecosystem 👬 -Xiaozhi is an ecosystem. When using this product, you can also check out other excellent projects in this ecosystem +Xiaozhi is an ecosystem. When using this product, you can also check out other [excellent projects](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE) in this ecosystem | Project Name | Project Address | Project Description | |:---------------------|:--------|:--------| @@ -280,7 +281,7 @@ Xiaozhi is an ecosystem. When using this product, you can also check out other e | FastGPT interface calls | FastGPT | - | | Coze interface calls | Coze | - | -In fact, any LLM that supports OpenAI interface calls can be integrated and used. +In fact, any LLM that supports OpenAI interface calls can be integrated and used, including Xinference and HomeAssistant interfaces. --- @@ -298,7 +299,7 @@ In fact, any VLLM that supports OpenAI interface calls can be integrated and use | Usage Method | Supported Platforms | Free Platforms | |:---:|:---:|:---:| -| Interface calls | EdgeTTS, Volcano Engine Doubao TTS, Tencent Cloud, Alibaba Cloud TTS, CosyVoiceSiliconflow, TTS302AI, CozeCnTTS, GizwitsTTS, ACGNTTS, OpenAITTS, Lingxi Streaming TTS | Lingxi Streaming TTS, EdgeTTS, CosyVoiceSiliconflow(partial) | +| Interface calls | EdgeTTS, Volcano Engine Doubao TTS, Tencent Cloud, Alibaba Cloud TTS, AliYun Stream TTS, CosyVoiceSiliconflow, TTS302AI, CozeCnTTS, GizwitsTTS, ACGNTTS, OpenAITTS, Lingxi Streaming TTS, MinimaxTTS | Lingxi Streaming TTS, EdgeTTS, CosyVoiceSiliconflow(partial) | | Local services | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, MinimaxTTS | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, MinimaxTTS | --- diff --git a/docs/images/demo4.png b/docs/images/demo4.png index 7b57ab25..e0b5ba76 100644 Binary files a/docs/images/demo4.png and b/docs/images/demo4.png differ diff --git a/docs/images/deploy2.png b/docs/images/deploy2.png index 882af706..78441534 100644 Binary files a/docs/images/deploy2.png and b/docs/images/deploy2.png differ diff --git a/docs/mqtt-gateway-integration.md b/docs/mqtt-gateway-integration.md index 931c1f37..09fe83c1 100644 --- a/docs/mqtt-gateway-integration.md +++ b/docs/mqtt-gateway-integration.md @@ -64,6 +64,7 @@ cp config/mqtt.json.example config/mqtt.json PUBLIC_IP=your-ip # 服务器公网IP MQTT_PORT=1883 # MQTT服务器端口 UDP_PORT=8884 # UDP服务器端口 +API_PORT=8007 # 管理API端口 MQTT_SIGNATURE_KEY=test # MQTT签名密钥 ``` 请注意`PUBLIC_IP`配置,确保其与实际公网IP一致,如果有域名就填域名。 @@ -106,6 +107,10 @@ pm2 restart xz-mqtt ``` 192.168.0.7:8884 ``` +4. 在智控台顶部,点击`参数管理`,搜索`server.mqtt_manager_api`,点击编辑,填入你在`.env`文件中设置的`PUBLIC_IP`+`:`+`UDP_PORT`。类似这样 +``` +192.168.0.7:8007 +``` 上面的配置完成后,你可以使用curl命令,验证你的ota地址是否会下发mqtt配置,把下面的`http://localhost:8002/xiaozhi/ota/`改成你的ota地址 ``` diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java b/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java index 6a9af011..e84fbba8 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java +++ b/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java @@ -55,4 +55,113 @@ public interface ErrorCode { int OTA_DEVICE_NOT_FOUND = 10041; int OTA_DEVICE_NEED_BIND = 10042; + + // 新增错误编码 + int DELETE_DATA_FAILED = 10043; + int USER_NOT_LOGIN = 10044; + int WEB_SOCKET_CONNECT_FAILED = 10045; + int VOICE_PRINT_SAVE_ERROR = 10046; + int TODAY_SMS_LIMIT_REACHED = 10047; + int OLD_PASSWORD_ERROR = 10048; + int INVALID_LLM_TYPE = 10049; + int TOKEN_GENERATE_ERROR = 10050; + int RESOURCE_NOT_FOUND = 10051; + + // 新增错误编码 + int DEFAULT_AGENT_NOT_FOUND = 10052; + int AGENT_NOT_FOUND = 10053; + int VOICEPRINT_API_NOT_CONFIGURED = 10054; + int SMS_SEND_FAILED = 10055; + int SMS_CONNECTION_FAILED = 10056; + int AGENT_VOICEPRINT_CREATE_FAILED = 10057; + int AGENT_VOICEPRINT_UPDATE_FAILED = 10058; + int AGENT_VOICEPRINT_DELETE_FAILED = 10059; + int SMS_SEND_TOO_FREQUENTLY = 10060; + int ACTIVATION_CODE_EMPTY = 10061; + int ACTIVATION_CODE_ERROR = 10062; + int DEVICE_ALREADY_ACTIVATED = 10063; + // 默认模型删除错误 + int DEFAULT_MODEL_DELETE_ERROR = 10064; + // 设备相关错误码 + int MAC_ADDRESS_ALREADY_EXISTS = 10090; // Mac地址已存在 + // 模型相关错误码 + int MODEL_PROVIDER_NOT_EXIST = 10091; // 供应器不存在 + int LLM_NOT_EXIST = 10092; // 设置的LLM不存在 + int MODEL_REFERENCED_BY_AGENT = 10093; // 该模型配置已被智能体引用,无法删除 + int LLM_REFERENCED_BY_INTENT = 10094; // 该LLM模型已被意图识别配置引用,无法删除 + + // 登录相关错误码 + int ADD_DATA_FAILED = 10065; // 新增数据失败 + int UPDATE_DATA_FAILED = 10066; // 修改数据失败 + int SMS_CAPTCHA_ERROR = 10067; // 短信验证码错误 + int MOBILE_REGISTER_DISABLED = 10068; // 未开启手机注册 + int USERNAME_NOT_PHONE = 10069; // 用户名不是手机号码 + int PHONE_ALREADY_REGISTERED = 10070; // 手机号码已注册 + int PHONE_NOT_REGISTERED = 10071; // 手机号码未注册 + int USER_REGISTER_DISABLED = 10072; // 不允许用户注册 + int RETRIEVE_PASSWORD_DISABLED = 10073; // 未开启找回密码功能 + int PHONE_FORMAT_ERROR = 10074; // 手机号码格式不正确 + int SMS_CODE_ERROR = 10075; // 手机验证码错误 + + // 字典类型相关错误码 + int DICT_TYPE_NOT_EXIST = 10076; // 字典类型不存在 + int DICT_TYPE_DUPLICATE = 10077; // 字典类型编码重复 + + // 资源处理相关错误码 + int RESOURCE_READ_ERROR = 10078; // 读取资源失败 + + // 智能体相关错误码 + int LLM_INTENT_PARAMS_MISMATCH = 10079; // LLM大模型和Intent意图识别,选择参数不匹配 + + // 声纹相关错误码 + int VOICEPRINT_ALREADY_REGISTERED = 10080; // 此声音声纹已经注册 + int VOICEPRINT_DELETE_ERROR = 10081; // 删除声纹出现错误 + int VOICEPRINT_UPDATE_NOT_ALLOWED = 10082; // 声纹修改不允许,声音已注册 + int VOICEPRINT_UPDATE_ADMIN_ERROR = 10083; // 修改声纹错误,请联系管理员 + int VOICEPRINT_API_URI_ERROR = 10084; // 声纹接口地址错误 + int VOICEPRINT_AUDIO_NOT_BELONG_AGENT = 10085; // 音频数据不属于智能体 + int VOICEPRINT_AUDIO_EMPTY = 10086; // 音频数据为空 + int VOICEPRINT_REGISTER_REQUEST_ERROR = 10087; // 声纹保存请求失败 + int VOICEPRINT_REGISTER_PROCESS_ERROR = 10088; // 声纹保存处理失败 + int VOICEPRINT_UNREGISTER_REQUEST_ERROR = 10089; // 声纹注销请求失败 + int VOICEPRINT_UNREGISTER_PROCESS_ERROR = 10090; // 声纹注销处理失败 + int VOICEPRINT_IDENTIFY_REQUEST_ERROR = 10091; // 声纹识别请求失败 + + // 服务端管理相关错误码 + int INVALID_SERVER_ACTION = 10095; // 无效服务端操作 + int SERVER_WEBSOCKET_NOT_CONFIGURED = 10096; // 未配置服务端WebSocket地址 + int TARGET_WEBSOCKET_NOT_EXIST = 10097; // 目标WebSocket地址不存在 + + // 参数验证相关错误码 + int WEBSOCKET_URLS_EMPTY = 10098; // WebSocket地址列表不能为空 + int WEBSOCKET_URL_LOCALHOST = 10099; // WebSocket地址不能使用localhost或127.0.0.1 + int WEBSOCKET_URL_FORMAT_ERROR = 10100; // WebSocket地址格式不正确 + int WEBSOCKET_CONNECTION_FAILED = 10101; // WebSocket连接测试失败 + int OTA_URL_EMPTY = 10102; // OTA地址不能为空 + int OTA_URL_LOCALHOST = 10103; // OTA地址不能使用localhost或127.0.0.1 + int OTA_URL_PROTOCOL_ERROR = 10104; // OTA地址必须以http或https开头 + int OTA_URL_FORMAT_ERROR = 10105; // OTA地址必须以/ota/结尾 + int OTA_INTERFACE_ACCESS_FAILED = 10106; // OTA接口访问失败 + int OTA_INTERFACE_FORMAT_ERROR = 10107; // OTA接口返回内容格式不正确 + int OTA_INTERFACE_VALIDATION_FAILED = 10108; // OTA接口验证失败 + int MCP_URL_EMPTY = 10109; // MCP地址不能为空 + int MCP_URL_LOCALHOST = 10110; // MCP地址不能使用localhost或127.0.0.1 + int MCP_URL_INVALID = 10111; // 不是正确的MCP地址 + int MCP_INTERFACE_ACCESS_FAILED = 10112; // MCP接口访问失败 + int MCP_INTERFACE_FORMAT_ERROR = 10113; // MCP接口返回内容格式不正确 + int MCP_INTERFACE_VALIDATION_FAILED = 10114; // MCP接口验证失败 + int VOICEPRINT_URL_EMPTY = 10115; // 声纹接口地址不能为空 + int VOICEPRINT_URL_LOCALHOST = 10116; // 声纹接口地址不能使用localhost或127.0.0.1 + int VOICEPRINT_URL_INVALID = 10117; // 不是正确的声纹接口地址 + int VOICEPRINT_URL_PROTOCOL_ERROR = 10118; // 声纹接口地址必须以http或https开头 + int VOICEPRINT_INTERFACE_ACCESS_FAILED = 10119; // 声纹接口访问失败 + int VOICEPRINT_INTERFACE_FORMAT_ERROR = 10120; // 声纹接口返回内容格式不正确 + int VOICEPRINT_INTERFACE_VALIDATION_FAILED = 10121; // 声纹接口验证失败 + int MQTT_SECRET_EMPTY = 10122; // mqtt密钥不能为空 + int MQTT_SECRET_LENGTH_INSECURE = 10123; // mqtt密钥长度不安全 + int MQTT_SECRET_CHARACTER_INSECURE = 10124; // mqtt密钥必须同时包含大小写字母 + int MQTT_SECRET_WEAK_PASSWORD = 10125; // mqtt密钥包含弱密码 + + // 字典相关错误码 + int DICT_LABEL_DUPLICATE = 10128; // 字典标签重复 } diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java b/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java index 01f1ccf2..e016bc09 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java +++ b/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java @@ -13,6 +13,7 @@ import org.springframework.web.servlet.resource.NoResourceFoundException; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import xiaozhi.common.utils.MessageUtils; import xiaozhi.common.utils.Result; /** @@ -62,7 +63,7 @@ public class RenExceptionHandler { @ExceptionHandler(NoResourceFoundException.class) public Result handleNoResourceFoundException(NoResourceFoundException ex) { log.warn("Resource not found: {}", ex.getMessage()); - return new Result().error(404, "资源不存在"); + return new Result().error(404, MessageUtils.getMessage(ErrorCode.RESOURCE_NOT_FOUND)); } @ExceptionHandler(MethodArgumentNotValidException.class) @@ -76,7 +77,7 @@ public class RenExceptionHandler { }) .filter(Objects::nonNull) .findFirst() - .orElse("请求参数错误!"); + .orElse(MessageUtils.getMessage(ErrorCode.PARAM_VALUE_NULL)); return new Result().error(ErrorCode.PARAM_VALUE_NULL, errorMsg); } diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/ResourcesUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/ResourcesUtils.java index 49d9a727..cf2d0bcc 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/ResourcesUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/ResourcesUtils.java @@ -6,6 +6,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; import xiaozhi.common.exception.RenException; +import xiaozhi.common.exception.ErrorCode; import java.io.BufferedReader; @@ -37,7 +38,7 @@ public class ResourcesUtils { } } catch (IOException e){ log.error("方法:loadString()读取资源失败--{}",e.getMessage()); - throw new RenException("读取资源失败"); + throw new RenException(ErrorCode.RESOURCE_READ_ERROR); } return luaScriptBuilder.toString(); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentVoicePrintController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentVoicePrintController.java index 1615cee5..9b7555eb 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentVoicePrintController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentVoicePrintController.java @@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.utils.Result; import xiaozhi.modules.agent.dto.AgentVoicePrintSaveDTO; @@ -42,7 +43,7 @@ public class AgentVoicePrintController { if (b) { return new Result<>(); } - return new Result().error("智能体的声纹创建失败"); + return new Result().error(ErrorCode.AGENT_VOICEPRINT_CREATE_FAILED); } @PutMapping @@ -54,7 +55,7 @@ public class AgentVoicePrintController { if (b) { return new Result<>(); } - return new Result().error("智能体的对应声纹更新失败"); + return new Result().error(ErrorCode.AGENT_VOICEPRINT_UPDATE_FAILED); } @DeleteMapping("/{id}") @@ -67,7 +68,7 @@ public class AgentVoicePrintController { if (delete) { return new Result<>(); } - return new Result().error("智能体的对应声纹删除失败"); + return new Result().error(ErrorCode.AGENT_VOICEPRINT_DELETE_FAILED); } @GetMapping("/list/{id}") @@ -76,7 +77,7 @@ public class AgentVoicePrintController { public Result> list(@PathVariable String id) { String voiceprintUrl = sysParamsService.getValue("server.voice_print", true); if (StringUtils.isBlank(voiceprintUrl) || "null".equals(voiceprintUrl)) { - throw new RenException("声纹接口未配置,请先在参数配置中配置声纹接口地址(server.voice_print)"); + throw new RenException(ErrorCode.VOICEPRINT_API_NOT_CONFIGURED); } Long userId = SecurityUser.getUserId(); List list = agentVoicePrintService.list(userId, id); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java index 4f5681e2..9ec04a4e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java @@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.RenException; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.page.PageData; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; @@ -74,7 +75,7 @@ public class AgentServiceImpl extends BaseServiceImpl imp AgentInfoVO agent = agentDao.selectAgentInfoById(id); if (agent == null) { - throw new RenException("智能体不存在"); + throw new RenException(ErrorCode.AGENT_NOT_FOUND); } if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { @@ -204,7 +205,7 @@ public class AgentServiceImpl extends BaseServiceImpl imp // 先查询现有实体 AgentEntity existingEntity = this.getAgentById(agentId); if (existingEntity == null) { - throw new RuntimeException("智能体不存在"); + throw new RenException(ErrorCode.AGENT_NOT_FOUND); } // 只更新提供的非空字段 @@ -328,7 +329,7 @@ public class AgentServiceImpl extends BaseServiceImpl imp boolean b = validateLLMIntentParams(dto.getLlmModelId(), dto.getIntentModelId()); if (!b) { - throw new RenException("LLM大模型和Intent意图识别,选择参数不匹配"); + throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH); } this.updateById(existingEntity); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentVoicePrintServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentVoicePrintServiceImpl.java index 31accbab..088e4663 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentVoicePrintServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentVoicePrintServiceImpl.java @@ -26,6 +26,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.JsonUtils; @@ -78,7 +79,7 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl>().ok(devices); } + @PostMapping("/bind/{agentId}") + @Operation(summary = "转发POST请求到MQTT网关") + @RequiresPermissions("sys:role:normal") + public Result forwardToMqttGateway(@PathVariable String agentId, @RequestBody String requestBody) { + try { + // 从系统参数中获取MQTT网关地址 + String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true); + if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) { + return new Result<>(); + } + + // 获取当前用户的设备列表 + UserDetail user = SecurityUser.getUser(); + List devices = deviceService.getUserDevices(user.getId(), agentId); + + // 构建deviceIds数组 + java.util.List deviceIds = new java.util.ArrayList<>(); + for (DeviceEntity device : devices) { + String macAddress = device.getMacAddress() != null ? device.getMacAddress() : "unknown"; + String groupId = device.getBoard() != null ? device.getBoard() : "GID_default"; + + // 替换冒号为下划线 + groupId = groupId.replace(":", "_"); + macAddress = macAddress.replace(":", "_"); + + // 构建mqtt客户端ID格式:groupId@@@macAddress@@@macAddress + String mqttClientId = groupId + "@@@" + macAddress + "@@@" + macAddress; + deviceIds.add(mqttClientId); + } + + // 构建完整的URL + String url = "http://" + mqttGatewayUrl + "/api/devices/status"; + + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", "application/json"); + + // 生成Bearer令牌 + String token = generateBearerToken(); + if (token == null) { + return new Result().error("令牌生成失败"); + } + headers.set("Authorization", "Bearer " + token); + + // 构建请求体JSON + String jsonBody = "{\"clientIds\":" + objectMapper.writeValueAsString(deviceIds) + "}"; + HttpEntity requestEntity = new HttpEntity<>(jsonBody, headers); + + // 发送POST请求 + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + + // 返回响应 + return new Result().ok(response.getBody()); + } catch (Exception e) { + return new Result().error("转发请求失败: " + e.getMessage()); + } + } + + private String generateBearerToken() { + try { + // 获取当前日期,格式为yyyy-MM-dd + String dateStr = java.time.LocalDate.now() + .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd")); + + // 获取MQTT签名密钥 + String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", false); + if (StringUtils.isBlank(signatureKey)) { + return null; + } + + // 将日期字符串与MQTT_SIGNATURE_KEY连接 + String tokenContent = dateStr + signatureKey; + + // 对连接后的字符串进行SHA256哈希计算 + String token = org.apache.commons.codec.digest.DigestUtils.sha256Hex(tokenContent); + + return token; + } catch (Exception e) { + return null; + } + } + @PostMapping("/unbind") @Operation(summary = "解绑设备") @RequiresPermissions("sys:role:normal") @@ -109,4 +208,63 @@ public class DeviceController { deviceService.manualAddDevice(user.getId(), dto); return new Result<>(); } + + @PostMapping("/commands/{deviceId}") + @Operation(summary = "发送设备指令") + @RequiresPermissions("sys:role:normal") + public Result sendDeviceCommand(@PathVariable String deviceId, @RequestBody String command) { + try { + // 从系统参数中获取MQTT网关地址 + String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true); + if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) { + return new Result().error("MQTT网关地址未配置"); + } + + // 构建完整的URL + // 获取设备信息以构建mqttClientId + DeviceEntity deviceById = deviceService.selectById(deviceId); + + if (!deviceById.getUserId().equals(SecurityUser.getUser().getId())) { + return new Result().error("设备不存在"); + } + String macAddress = deviceById != null ? deviceById.getMacAddress() : "unknown"; + String groupId = deviceById != null ? deviceById.getBoard() : null; + if (groupId == null) { + groupId = "GID_default"; + } + groupId = groupId.replace(":", "_"); + macAddress = macAddress.replace(":", "_"); + + // 拼接为groupId@@@macAddress@@@deviceId格式 + String mqttClientId = groupId + "@@@" + macAddress + "@@@" + macAddress; + + String url = "http://" + mqttGatewayUrl + "/api/commands/" + mqttClientId; + + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", "application/json"); + + // 生成Bearer令牌 + String dateStr = java.time.LocalDate.now() + .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd")); + String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", false); + if (StringUtils.isBlank(signatureKey)) { + return new Result().error("MQTT签名密钥未配置"); + } + String tokenContent = dateStr + signatureKey; + String token = org.apache.commons.codec.digest.DigestUtils.sha256Hex(tokenContent); + headers.set("Authorization", "Bearer " + token); + + // 构建请求体 + HttpEntity requestEntity = new HttpEntity<>(command, headers); + + // 发送POST请求 + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + + // 返回响应 + return new Result().ok(response.getBody()); + } catch (Exception e) { + return new Result().error("发送指令失败: " + e.getMessage()); + } + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java index 9e452888..f0603519 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java @@ -29,6 +29,7 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; import xiaozhi.common.redis.RedisKeys; @@ -83,27 +84,27 @@ public class DeviceServiceImpl extends BaseServiceImpl @Override public Boolean deviceActivation(String agentId, String activationCode) { if (StringUtils.isBlank(activationCode)) { - throw new RenException("激活码不能为空"); + throw new RenException(ErrorCode.ACTIVATION_CODE_EMPTY); } String deviceKey = "ota:activation:code:" + activationCode; Object cacheDeviceId = redisUtils.get(deviceKey); if (cacheDeviceId == null) { - throw new RenException("激活码错误"); + throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR); } String deviceId = (String) cacheDeviceId; String safeDeviceId = deviceId.replace(":", "_").toLowerCase(); String cacheDeviceKey = String.format("ota:activation:data:%s", safeDeviceId); Map cacheMap = (Map) redisUtils.get(cacheDeviceKey); if (cacheMap == null) { - throw new RenException("激活码错误"); + throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR); } String cachedCode = (String) cacheMap.get("activation_code"); if (!activationCode.equals(cachedCode)) { - throw new RenException("激活码错误"); + throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR); } // 检查设备有没有被激活 if (selectById(deviceId) != null) { - throw new RenException("设备已激活"); + throw new RenException(ErrorCode.DEVICE_ALREADY_ACTIVATED); } String macAddress = (String) cacheMap.get("mac_address"); @@ -111,7 +112,7 @@ public class DeviceServiceImpl extends BaseServiceImpl String appVersion = (String) cacheMap.get("app_version"); UserDetail user = SecurityUser.getUser(); if (user.getId() == null) { - throw new RenException("用户未登录"); + throw new RenException(ErrorCode.USER_NOT_LOGIN); } Date currentTime = new Date(); @@ -188,7 +189,7 @@ public class DeviceServiceImpl extends BaseServiceImpl try { String groupId = deviceById != null && deviceById.getBoard() != null ? deviceById.getBoard() : "GID_default"; - DeviceReportRespDTO.MQTT mqtt = buildMqttConfig(macAddress, clientId, groupId); + DeviceReportRespDTO.MQTT mqtt = buildMqttConfig(macAddress, groupId); if (mqtt != null) { mqtt.setEndpoint(mqttUdpConfig); response.setMqtt(mqtt); @@ -441,7 +442,7 @@ public class DeviceServiceImpl extends BaseServiceImpl wrapper.eq("mac_address", dto.getMacAddress()); DeviceEntity exist = baseDao.selectOne(wrapper); if (exist != null) { - throw new RenException("该Mac地址已存在"); + throw new RenException(ErrorCode.MAC_ADDRESS_ALREADY_EXISTS); } Date now = new Date(); DeviceEntity entity = new DeviceEntity(); @@ -479,11 +480,10 @@ public class DeviceServiceImpl extends BaseServiceImpl * 构建MQTT配置信息 * * @param macAddress MAC地址 - * @param clientId 客户端ID (UUID) * @param groupId 分组ID * @return MQTT配置对象 */ - private DeviceReportRespDTO.MQTT buildMqttConfig(String macAddress, String clientId, String groupId) + private DeviceReportRespDTO.MQTT buildMqttConfig(String macAddress, String groupId) throws Exception { // 从环境变量或系统参数获取签名密钥 String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", false); @@ -495,8 +495,7 @@ public class DeviceServiceImpl extends BaseServiceImpl // 构建客户端ID格式:groupId@@@macAddress@@@uuid String groupIdSafeStr = groupId.replace(":", "_"); String deviceIdSafeStr = macAddress.replace(":", "_"); - String clientIdSafeStr = clientId.replace(":", "_"); - String mqttClientId = String.format("%s@@@%s@@@%s", groupIdSafeStr, deviceIdSafeStr, clientIdSafeStr); + String mqttClientId = String.format("%s@@@%s@@@%s", groupIdSafeStr, deviceIdSafeStr, deviceIdSafeStr); // 构建用户数据(包含IP等信息) Map userData = new HashMap<>(); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java index c156b408..936e67f3 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java @@ -15,6 +15,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import cn.hutool.core.collection.CollectionUtil; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; import xiaozhi.common.redis.RedisKeys; @@ -94,7 +95,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl providerList = modelProviderService.getList(modelType, provideCode); if (CollectionUtil.isEmpty(providerList)) { - throw new RenException("供应器不存在"); + throw new RenException(ErrorCode.MODEL_PROVIDER_NOT_EXIST); } // 再保存供应器提供的模型 @@ -113,7 +114,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl providerList = modelProviderService.getList(modelType, provideCode); if (CollectionUtil.isEmpty(providerList)) { - throw new RenException("供应器不存在"); + throw new RenException(ErrorCode.MODEL_PROVIDER_NOT_EXIST); } if (modelConfigBodyDTO.getConfigJson().containsKey("llm")) { String llm = modelConfigBodyDTO.getConfigJson().get("llm").toString(); @@ -122,12 +123,12 @@ public class ModelConfigServiceImpl extends BaseServiceImpl ids) { if (modelProviderDao.deleteBatchIds(ids) == 0) { - throw new RenException("删除数据失败"); + throw new RenException(ErrorCode.DELETE_DATA_FAILED); } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java index 02538caf..f13194b1 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java @@ -2,8 +2,11 @@ package xiaozhi.modules.security.config; import java.text.SimpleDateFormat; import java.util.List; +import java.util.Locale; import java.util.TimeZone; +import jakarta.servlet.http.HttpServletRequest; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.ByteArrayHttpMessageConverter; @@ -12,8 +15,10 @@ import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; +import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -95,5 +100,47 @@ public class WebMvcConfig implements WebMvcConfigurer { converter.setObjectMapper(mapper); return converter; } + + /** + * 国际化配置 - 根据请求头中的Accept-Language设置语言环境 + */ + @Bean + public LocaleResolver localeResolver() { + return new AcceptHeaderLocaleResolver() { + @Override + public Locale resolveLocale(HttpServletRequest request) { + String acceptLanguage = request.getHeader("Accept-Language"); + if (acceptLanguage == null || acceptLanguage.isEmpty()) { + return Locale.getDefault(); + } + + // 解析Accept-Language请求头中的首选语言 + String[] languages = acceptLanguage.split(","); + if (languages.length > 0) { + // 提取第一个语言代码,去除可能的质量值(q=...) + String[] parts = languages[0].split(";" + "\\s*"); + String primaryLanguage = parts[0].trim(); + + // 根据前端发送的语言代码直接创建Locale对象 + if (primaryLanguage.equals("zh-CN")) { + return Locale.SIMPLIFIED_CHINESE; + } else if (primaryLanguage.equals("zh-TW")) { + return Locale.TRADITIONAL_CHINESE; + } else if (primaryLanguage.equals("en-US")) { + return Locale.US; + } else if (primaryLanguage.startsWith("zh")) { + // 对于其他中文变体,默认使用简体中文 + return Locale.SIMPLIFIED_CHINESE; + } else if (primaryLanguage.startsWith("en")) { + // 对于其他英文变体,默认使用美式英语 + return Locale.US; + } + } + + // 如果没有匹配的语言,使用默认语言 + return Locale.getDefault(); + } + }; + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java index 0215cf7e..d7333273 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java @@ -68,12 +68,12 @@ public class LoginController { // 验证图形验证码 boolean validate = captchaService.validate(dto.getCaptchaId(), dto.getCaptcha(), true); if (!validate) { - throw new RenException("图形验证码错误"); + throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR); } Boolean isMobileRegister = sysParamsService .getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class); if (!isMobileRegister) { - throw new RenException("没有开启手机注册,没法使用短信验证码功能"); + throw new RenException(ErrorCode.MOBILE_REGISTER_DISABLED); } // 发送短信验证码 captchaService.sendSMSValidateCode(dto.getPhone()); @@ -86,17 +86,17 @@ public class LoginController { // 验证是否正确输入验证码 boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true); if (!validate) { - throw new RenException("图形验证码错误,请重新获取"); + throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR); } // 按照用户名获取用户 SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername()); // 判断用户是否存在 if (userDTO == null) { - throw new RenException("请检测用户和密码是否输入错误"); + throw new RenException(ErrorCode.ACCOUNT_PASSWORD_ERROR); } // 判断密码是否正确,不一样则进入if if (!PasswordUtils.matches(login.getPassword(), userDTO.getPassword())) { - throw new RenException("请检测用户和密码是否输入错误"); + throw new RenException(ErrorCode.ACCOUNT_PASSWORD_ERROR); } return sysUserTokenService.createToken(userDTO.getId()); } @@ -105,7 +105,7 @@ public class LoginController { @Operation(summary = "注册") public Result register(@RequestBody LoginDTO login) { if (!sysUserService.getAllowUserRegister()) { - throw new RenException("当前不允许普通用户注册"); + throw new RenException(ErrorCode.USER_REGISTER_DISABLED); } // 是否开启手机注册 Boolean isMobileRegister = sysParamsService @@ -115,25 +115,25 @@ public class LoginController { // 验证用户是否是手机号码 boolean validPhone = ValidatorUtils.isValidPhone(login.getUsername()); if (!validPhone) { - throw new RenException("用户名不是手机号码,请重新输入"); + throw new RenException(ErrorCode.USERNAME_NOT_PHONE); } // 验证短信验证码是否正常 validate = captchaService.validateSMSValidateCode(login.getUsername(), login.getMobileCaptcha(), false); if (!validate) { - throw new RenException("手机验证码错误,请重新获取"); + throw new RenException(ErrorCode.SMS_CODE_ERROR); } } else { // 验证是否正确输入验证码 validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true); if (!validate) { - throw new RenException("图形验证码错误,请重新获取"); + throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR); } } // 按照用户名获取用户 SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername()); if (userDTO != null) { - throw new RenException("此手机号码已经注册过"); + throw new RenException(ErrorCode.PHONE_ALREADY_REGISTERED); } userDTO = new SysUserDTO(); userDTO.setUsername(login.getUsername()); @@ -168,26 +168,26 @@ public class LoginController { Boolean isMobileRegister = sysParamsService .getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class); if (!isMobileRegister) { - throw new RenException("没有开启手机注册,没法使用找回密码功能"); + throw new RenException(ErrorCode.RETRIEVE_PASSWORD_DISABLED); } // 判断非空 ValidatorUtils.validateEntity(dto); // 验证用户是否是手机号码 boolean validPhone = ValidatorUtils.isValidPhone(dto.getPhone()); if (!validPhone) { - throw new RenException("输入的手机号码格式不正确"); + throw new RenException(ErrorCode.PHONE_FORMAT_ERROR); } // 按照用户名获取用户 SysUserDTO userDTO = sysUserService.getByUsername(dto.getPhone()); if (userDTO == null) { - throw new RenException("输入的手机号码未注册"); + throw new RenException(ErrorCode.PHONE_NOT_REGISTERED); } // 验证短信验证码是否正常 boolean validate = captchaService.validateSMSValidateCode(dto.getPhone(), dto.getCode(), false); // 判断是否通过验证 if (!validate) { - throw new RenException("输入的手机验证码错误"); + throw new RenException(ErrorCode.SMS_CODE_ERROR); } sysUserService.changePasswordDirectly(userDTO.getId(), dto.getPassword()); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java index aad888df..59d7e09e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java @@ -18,6 +18,7 @@ import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.utils.HttpContextUtils; import xiaozhi.common.utils.JsonUtils; +import xiaozhi.common.utils.MessageUtils; import xiaozhi.common.utils.Result; /** @@ -82,8 +83,8 @@ public class Oauth2Filter extends AuthenticatingFilter { httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin()); try { - Throwable throwable = e.getCause() == null ? e : e.getCause(); - Result r = new Result().error(ErrorCode.UNAUTHORIZED, throwable.getMessage()); + // 使用国际化消息替代直接使用异常消息 + Result r = new Result().error(ErrorCode.UNAUTHORIZED); String json = JsonUtils.toJsonString(r); httpResponse.getWriter().print(json); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java index 6d7be47a..17ea9beb 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java @@ -3,6 +3,7 @@ package xiaozhi.modules.security.oauth2; import java.security.MessageDigest; import java.util.UUID; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; /** @@ -38,7 +39,7 @@ public class TokenGenerator { byte[] messageDigest = algorithm.digest(); return toHexString(messageDigest); } catch (Exception e) { - throw new RenException("token invalid", e); + throw new RenException(ErrorCode.TOKEN_GENERATE_ERROR, e); } } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java index e823d70e..0d3666db 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java @@ -16,6 +16,7 @@ import com.wf.captcha.base.Captcha; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; @@ -88,7 +89,7 @@ public class CaptchaServiceImpl implements CaptchaService { long currentTime = System.currentTimeMillis(); long timeDiff = currentTime - lastSendTimeLong; if (timeDiff < 60000) { - throw new RenException("发送太频繁,请" + (60000 - timeDiff) / 1000 + "秒后再试"); + throw new RenException(ErrorCode.SMS_SEND_TOO_FREQUENTLY, String.valueOf((60000 - timeDiff) / 1000)); } } @@ -108,7 +109,7 @@ public class CaptchaServiceImpl implements CaptchaService { } if (todayCount >= maxSendCount) { - throw new RenException("今日发送次数已达上限"); + throw new RenException(ErrorCode.TODAY_SMS_LIMIT_REACHED); } String key = RedisKeys.getSMSValidateCodeKey(phone); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java index e0ebb523..a8a378f0 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java @@ -9,6 +9,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; @@ -45,7 +46,7 @@ public class ALiYunSmsService implements SmsService { redisUtils.delete(todayCountKey); // 错误 message log.error(e.getMessage()); - throw new RenException("短信发送失败"); + throw new RenException(ErrorCode.SMS_SEND_FAILED); } } @@ -70,7 +71,7 @@ public class ALiYunSmsService implements SmsService { }catch (Exception e){ // 错误 message log.error(e.getMessage()); - throw new RenException("短信连接建立失败"); + throw new RenException(ErrorCode.SMS_CONNECTION_FAILED); } } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java index 0b6b6a5b..893ea8e2 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java @@ -22,6 +22,7 @@ import jakarta.validation.Valid; import lombok.AllArgsConstructor; import xiaozhi.common.annotation.LogOperation; import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.utils.Result; import xiaozhi.modules.sys.dto.EmitSeverActionDTO; @@ -64,17 +65,17 @@ public class ServerSideManageController { @RequiresPermissions("sys:role:superAdmin") public Result emitServerAction(@RequestBody @Valid EmitSeverActionDTO emitSeverActionDTO) { if (emitSeverActionDTO.getAction() == null) { - throw new RenException("无效服务端操作"); + throw new RenException(ErrorCode.INVALID_SERVER_ACTION); } String wsText = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true); if (StringUtils.isBlank(wsText)) { - throw new RenException("未配置服务端WebSocket地址"); + throw new RenException(ErrorCode.SERVER_WEBSOCKET_NOT_CONFIGURED); } String targetWs = emitSeverActionDTO.getTargetWs(); String[] wsList = wsText.split(";"); // 找到需要发起的 if (StringUtils.isBlank(targetWs) || !Arrays.asList(wsList).contains(targetWs)) { - throw new RenException("目标WebSocket地址不存在"); + throw new RenException(ErrorCode.TARGET_WEBSOCKET_NOT_EXIST); } return new Result().ok(emitServerActionByWs(targetWs, emitSeverActionDTO.getAction())); } @@ -114,7 +115,7 @@ public class ServerSideManageController { }); } catch (Exception e) { // 捕获全部错误,由全局异常处理器返回 - throw new RenException("WebSocket连接失败或连接超时"); + throw new RenException(ErrorCode.WEB_SOCKET_CONNECT_FAILED); } return true; } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java index d1c116c3..24b070c0 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java @@ -25,6 +25,7 @@ import lombok.AllArgsConstructor; import xiaozhi.common.annotation.LogOperation; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.RenException; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.page.PageData; import xiaozhi.common.utils.Result; import xiaozhi.common.validator.AssertUtils; @@ -130,23 +131,23 @@ public class SysParamsController { } String[] wsUrls = urls.split("\\;"); if (wsUrls.length == 0) { - throw new RenException("WebSocket地址列表不能为空"); + throw new RenException(ErrorCode.WEBSOCKET_URLS_EMPTY); } for (String url : wsUrls) { if (StringUtils.isNotBlank(url)) { // 检查是否包含localhost或127.0.0.1 if (url.contains("localhost") || url.contains("127.0.0.1")) { - throw new RenException("WebSocket地址不能使用localhost或127.0.0.1"); + throw new RenException(ErrorCode.WEBSOCKET_URL_LOCALHOST); } // 验证WebSocket地址格式 if (!WebSocketValidator.validateUrlFormat(url)) { - throw new RenException("WebSocket地址格式不正确: " + url); + throw new RenException(ErrorCode.WEBSOCKET_URL_FORMAT_ERROR); } // 测试WebSocket连接 if (!WebSocketValidator.testConnection(url)) { - throw new RenException("WebSocket连接测试失败: " + url); + throw new RenException(ErrorCode.WEBSOCKET_CONNECTION_FAILED); } } } @@ -173,35 +174,35 @@ public class SysParamsController { return; } if (StringUtils.isBlank(url) || url.equals("null")) { - throw new RenException("OTA地址不能为空"); + throw new RenException(ErrorCode.OTA_URL_EMPTY); } // 检查是否包含localhost或127.0.0.1 if (url.contains("localhost") || url.contains("127.0.0.1")) { - throw new RenException("OTA地址不能使用localhost或127.0.0.1"); + throw new RenException(ErrorCode.OTA_URL_LOCALHOST); } // 验证URL格式 if (!url.toLowerCase().startsWith("http")) { - throw new RenException("OTA地址必须以http或https开头"); + throw new RenException(ErrorCode.OTA_URL_PROTOCOL_ERROR); } if (!url.endsWith("/ota/")) { - throw new RenException("OTA地址必须以/ota/结尾"); + throw new RenException(ErrorCode.OTA_URL_FORMAT_ERROR); } try { // 发送GET请求 ResponseEntity response = restTemplate.getForEntity(url, String.class); if (response.getStatusCode() != HttpStatus.OK) { - throw new RenException("OTA接口访问失败,状态码:" + response.getStatusCode()); + throw new RenException(ErrorCode.OTA_INTERFACE_ACCESS_FAILED); } // 检查响应内容是否包含OTA相关信息 String body = response.getBody(); if (body == null || !body.contains("OTA")) { - throw new RenException("OTA接口返回内容格式不正确,可能不是一个真实的OTA接口"); + throw new RenException(ErrorCode.OTA_INTERFACE_FORMAT_ERROR); } } catch (Exception e) { - throw new RenException("OTA接口验证失败:" + e.getMessage()); + throw new RenException(ErrorCode.OTA_INTERFACE_VALIDATION_FAILED); } } @@ -210,28 +211,28 @@ public class SysParamsController { return; } if (StringUtils.isBlank(url) || url.equals("null")) { - throw new RenException("MCP地址不能为空"); + throw new RenException(ErrorCode.MCP_URL_EMPTY); } if (url.contains("localhost") || url.contains("127.0.0.1")) { - throw new RenException("MCP地址不能使用localhost或127.0.0.1"); + throw new RenException(ErrorCode.MCP_URL_LOCALHOST); } if (!url.toLowerCase().contains("key")) { - throw new RenException("不是正确的MCP地址"); + throw new RenException(ErrorCode.MCP_URL_INVALID); } try { // 发送GET请求 ResponseEntity response = restTemplate.getForEntity(url, String.class); if (response.getStatusCode() != HttpStatus.OK) { - throw new RenException("MCP接口访问失败,状态码:" + response.getStatusCode()); + throw new RenException(ErrorCode.MCP_INTERFACE_ACCESS_FAILED); } // 检查响应内容是否包含mcp相关信息 String body = response.getBody(); if (body == null || !body.contains("success")) { - throw new RenException("MCP接口返回内容格式不正确,可能不是一个真实的MCP接口"); + throw new RenException(ErrorCode.MCP_INTERFACE_FORMAT_ERROR); } } catch (Exception e) { - throw new RenException("MCP接口验证失败:" + e.getMessage()); + throw new RenException(ErrorCode.MCP_INTERFACE_VALIDATION_FAILED); } } @@ -241,31 +242,31 @@ public class SysParamsController { return; } if (StringUtils.isBlank(url) || url.equals("null")) { - throw new RenException("声纹接口地址不能为空"); + throw new RenException(ErrorCode.VOICEPRINT_URL_EMPTY); } if (url.contains("localhost") || url.contains("127.0.0.1")) { - throw new RenException("声纹接口地址不能使用localhost或127.0.0.1"); + throw new RenException(ErrorCode.VOICEPRINT_URL_LOCALHOST); } if (!url.toLowerCase().contains("key")) { - throw new RenException("不是正确的声纹接口地址"); + throw new RenException(ErrorCode.VOICEPRINT_URL_INVALID); } // 验证URL格式 if (!url.toLowerCase().startsWith("http")) { - throw new RenException("声纹接口地址必须以http或https开头"); + throw new RenException(ErrorCode.VOICEPRINT_URL_PROTOCOL_ERROR); } try { // 发送GET请求 ResponseEntity response = restTemplate.getForEntity(url, String.class); if (response.getStatusCode() != HttpStatus.OK) { - throw new RenException("声纹接口访问失败,状态码:" + response.getStatusCode()); + throw new RenException(ErrorCode.VOICEPRINT_INTERFACE_ACCESS_FAILED); } // 检查响应内容 String body = response.getBody(); if (body == null || !body.contains("healthy")) { - throw new RenException("声纹接口返回内容格式不正确,可能不是一个真实的MCP接口"); + throw new RenException(ErrorCode.VOICEPRINT_INTERFACE_FORMAT_ERROR); } } catch (Exception e) { - throw new RenException("声纹接口验证失败:" + e.getMessage()); + throw new RenException(ErrorCode.VOICEPRINT_INTERFACE_VALIDATION_FAILED); } } @@ -275,20 +276,20 @@ public class SysParamsController { return; } if (StringUtils.isBlank(secret) || secret.equals("null")) { - throw new RenException("mqtt密钥不能为空"); + throw new RenException(ErrorCode.MQTT_SECRET_EMPTY); } if (secret.length() < 8) { - throw new RenException("您的mqtt密钥长度不安全,字符数需要至少8个字符且必须同时包含大小写字母"); + throw new RenException(ErrorCode.MQTT_SECRET_LENGTH_INSECURE); } // 检查是否同时包含大小写字母 if (!secret.matches(".*[a-z].*") || !secret.matches(".*[A-Z].*")) { - throw new RenException("您的mqtt密钥长度不安全,mqtt密钥必须同时包含大小写字母"); + throw new RenException(ErrorCode.MQTT_SECRET_CHARACTER_INSECURE); } // 不允许包含弱密码 String[] weakPasswords = { "test", "1234", "admin", "password", "qwerty", "xiaozhi" }; for (String weakPassword : weakPasswords) { if (secret.toLowerCase().contains(weakPassword)) { - throw new RenException("您的mqtt密钥包含弱密码:" + weakPassword); + throw new RenException(ErrorCode.MQTT_SECRET_WEAK_PASSWORD); } } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java index ffe88986..a2adb150 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java @@ -17,6 +17,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import lombok.AllArgsConstructor; import xiaozhi.common.exception.RenException; +import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.page.PageData; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; @@ -153,7 +154,7 @@ public class SysDictDataServiceImpl extends BaseServiceImpl { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('获取设备状态失败:', err); + RequestService.reAjaxFun(() => { + this.getDeviceStatus(agentId, callback); + }); + }).send(); + }, + // 发送设备指令 + sendDeviceCommand(deviceId, mcpData, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/device/commands/${deviceId}`) + .method('POST') + .data(mcpData) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('发送设备指令失败:', err); + RequestService.reAjaxFun(() => { + this.sendDeviceCommand(deviceId, mcpData, callback); + }); + }).send(); + }, } \ No newline at end of file diff --git a/main/manager-web/src/components/AddModelDialog.vue b/main/manager-web/src/components/AddModelDialog.vue index 3dda7a9a..b2a1adfc 100644 --- a/main/manager-web/src/components/AddModelDialog.vue +++ b/main/manager-web/src/components/AddModelDialog.vue @@ -195,7 +195,7 @@ export default { this.saving = true; if (!this.formData.supplier) { - this.$message.error('请选择供应器'); + this.$message.error(this.$t('addModelDialog.requiredSupplier')); this.saving = false; return; } diff --git a/main/manager-web/src/components/DeviceItem.vue b/main/manager-web/src/components/DeviceItem.vue index 040cd2e8..bfbbb97f 100644 --- a/main/manager-web/src/components/DeviceItem.vue +++ b/main/manager-web/src/components/DeviceItem.vue @@ -73,10 +73,6 @@ export default { } else { return this.device.lastConnectedAt; } - }, - // 判断是否为英文 - isEnglish() { - return i18n.locale === 'en'; } }, methods: { diff --git a/main/manager-web/src/components/McpToolCallDialog.vue b/main/manager-web/src/components/McpToolCallDialog.vue new file mode 100644 index 00000000..d271c3ff --- /dev/null +++ b/main/manager-web/src/components/McpToolCallDialog.vue @@ -0,0 +1,1604 @@ + + + + + diff --git a/main/manager-web/src/components/ProviderDialog.vue b/main/manager-web/src/components/ProviderDialog.vue index 2f45c6d7..46945832 100644 --- a/main/manager-web/src/components/ProviderDialog.vue +++ b/main/manager-web/src/components/ProviderDialog.vue @@ -151,9 +151,7 @@ export default { providerCode: [{ required: true, message: this.$t('providerDialog.requiredCode'), trigger: 'blur' }], name: [{ required: true, message: this.$t('providerDialog.requiredName'), trigger: 'blur' }] }; - } - }, - computed: { + }, hasIncompleteFields() { return this.form.fields && this.form.fields.some(field => !field.key || !field.label || !field.type diff --git a/main/manager-web/src/i18n/en.js b/main/manager-web/src/i18n/en.js index a6a2900e..14f4d5a6 100644 --- a/main/manager-web/src/i18n/en.js +++ b/main/manager-web/src/i18n/en.js @@ -1,4 +1,11 @@ export default { + // Login page related prompt text + 'login.requiredUsername': 'Username cannot be empty', + 'login.requiredPassword': 'Password cannot be empty', + 'login.requiredCaptcha': 'Captcha cannot be empty', + 'login.requiredMobile': 'Please enter a valid mobile phone number', + 'login.loginSuccess': 'Login successful!', + // HeaderBar组件文本 // 在header相关翻译中添加 'header.smartManagement': 'Agents', @@ -10,6 +17,137 @@ export default { 'header.dictManagement': 'Dict Management', 'header.agentTemplate': 'Default Role Templates', // 添加这一行 + // McpToolCallDialog component text + 'mcpToolCall.title': 'Tool Call', + 'mcpToolCall.execute': 'Execute', + 'mcpToolCall.chooseFunction': '1、Choose Function', + 'mcpToolCall.searchFunction': 'Search Function', + 'mcpToolCall.noResults': 'No matching functions found', + 'mcpToolCall.settings': '2、Parameter Settings', + 'mcpToolCall.inputPlaceholder': 'Please enter {label}', + 'mcpToolCall.valueRange': 'Value range: {min} - {max}', + 'mcpToolCall.selectPlaceholder': 'Please select {label}', + 'mcpToolCall.lightTheme': 'Light Theme', + 'mcpToolCall.darkTheme': 'Dark Theme', + 'mcpToolCall.pleaseSelect': 'Please select a function', + 'mcpToolCall.cancel': 'Cancel', + 'mcpToolCall.requiredField': 'Please enter {field}', + 'mcpToolCall.minValue': 'Minimum value is {value}', + 'mcpToolCall.maxValue': 'Maximum value is {value}', + 'mcpToolCall.selectTool': 'Please select a tool to execute', + 'mcpToolCall.executionResult': '3、Execution Result', + 'mcpToolCall.copyResult': 'Copy Result', + 'mcpToolCall.noResultYet': 'No result yet', + 'mcpToolCall.loadingToolList': 'Loading tool list...', + + // Tool names + 'mcpToolCall.toolName.getDeviceStatus': 'View Device Status', + 'mcpToolCall.toolName.setVolume': 'Set Volume', + 'mcpToolCall.toolName.setBrightness': 'Set Brightness', + 'mcpToolCall.toolName.setTheme': 'Set Theme', + 'mcpToolCall.toolName.takePhoto': 'Take Photo & Recognize', + 'mcpToolCall.toolName.getSystemInfo': 'System Info', + 'mcpToolCall.toolName.reboot': 'Reboot Device', + 'mcpToolCall.toolName.upgradeFirmware': 'Upgrade Firmware', + 'mcpToolCall.toolName.getScreenInfo': 'Screen Info', + 'mcpToolCall.toolName.snapshot': 'Screen Snapshot', + 'mcpToolCall.toolName.previewImage': 'Preview Image', + 'mcpToolCall.toolName.setDownloadUrl': 'Set Download URL', + + // Tool categories + 'mcpToolCall.category.audio': 'Audio', + 'mcpToolCall.category.display': 'Display', + 'mcpToolCall.category.camera': 'Camera', + 'mcpToolCall.category.system': 'System', + 'mcpToolCall.category.assets': 'Assets', + 'mcpToolCall.category.deviceInfo': 'Device Info', + + // Table categories and properties + 'mcpToolCall.table.audioSpeaker': 'Audio Speaker', + 'mcpToolCall.table.screen': 'Screen', + 'mcpToolCall.table.network': 'Network', + 'mcpToolCall.table.audioControl': 'Audio Control', + 'mcpToolCall.table.screenControl': 'Screen Control', + 'mcpToolCall.table.systemControl': 'System Control', + 'mcpToolCall.table.screenInfo': 'Screen Info', + 'mcpToolCall.table.hardwareInfo': 'Hardware Info', + 'mcpToolCall.table.memoryInfo': 'Memory Info', + 'mcpToolCall.table.applicationInfo': 'Application Info', + 'mcpToolCall.table.networkInfo': 'Network Info', + 'mcpToolCall.table.displayInfo': 'Display Info', + 'mcpToolCall.table.deviceInfo': 'Device Info', + 'mcpToolCall.table.systemInfo': 'System Info', + // Table column headers + 'mcpToolCall.table.component': 'Component', + 'mcpToolCall.table.property': 'Property', + 'mcpToolCall.table.value': 'Value', + + 'mcpToolCall.prop.volume': 'Volume', + 'mcpToolCall.prop.brightness': 'Brightness', + 'mcpToolCall.prop.theme': 'Theme', + 'mcpToolCall.prop.type': 'Type', + 'mcpToolCall.prop.ssid': 'SSID', + 'mcpToolCall.prop.signalStrength': 'Signal Strength', + 'mcpToolCall.prop.operationResult': 'Operation Result', + 'mcpToolCall.prop.width': 'Width', + 'mcpToolCall.prop.height': 'Height', + 'mcpToolCall.prop.screenType': 'Type', + 'mcpToolCall.prop.chipModel': 'Chip Model', + 'mcpToolCall.prop.cpuCores': 'CPU Cores', + 'mcpToolCall.prop.chipVersion': 'Chip Version', + 'mcpToolCall.prop.flashSize': 'Flash Size', + 'mcpToolCall.prop.minFreeHeap': 'Minimum Free Heap', + 'mcpToolCall.prop.applicationName': 'Application Name', + 'mcpToolCall.prop.applicationVersion': 'Application Version', + 'mcpToolCall.prop.compileTime': 'Compile Time', + 'mcpToolCall.prop.idfVersion': 'IDF Version', + 'mcpToolCall.prop.macAddress': 'MAC Address', + 'mcpToolCall.prop.ipAddress': 'IP Address', + 'mcpToolCall.prop.wifiName': 'WiFi Name', + 'mcpToolCall.prop.wifiChannel': 'WiFi Channel', + 'mcpToolCall.prop.screenSize': 'Screen Size', + 'mcpToolCall.prop.deviceUuid': 'Device UUID', + 'mcpToolCall.prop.systemLanguage': 'System Language', + 'mcpToolCall.prop.currentOtaPartition': 'Current OTA Partition', + 'mcpToolCall.prop.getResult': 'Get Result', + 'mcpToolCall.prop.url': 'URL', + 'mcpToolCall.prop.quality': 'Quality', + 'mcpToolCall.prop.question': 'Question', + + // Tool help texts + 'mcpToolCall.help.getDeviceStatus': 'View the current running status of the device, including volume, screen, battery and other information.', + 'mcpToolCall.help.setVolume': 'Adjust the volume of the device, please enter a value between 0-100.', + 'mcpToolCall.help.setBrightness': 'Adjust the brightness of the device screen, please enter a value between 0-100.', + 'mcpToolCall.help.setTheme': 'Switch the display theme of the device screen, you can choose light or dark mode.', + 'mcpToolCall.help.takePhoto': 'Take photos with the device camera and perform recognition analysis, please enter the question you want to ask.', + 'mcpToolCall.help.getSystemInfo': 'Get the system information of the device, including hardware specifications, software version, etc.', + 'mcpToolCall.help.reboot': 'Reboot the device, the device will restart after execution.', + 'mcpToolCall.help.upgradeFirmware': 'Download and upgrade the device firmware from the specified URL, the device will restart automatically after the upgrade.', + 'mcpToolCall.help.getScreenInfo': 'Get detailed information about the screen, such as resolution, size and other parameters.', + 'mcpToolCall.help.snapshot': 'Take a screenshot of the current screen and upload it to the specified URL.', + 'mcpToolCall.help.previewImage': 'Preview images from the specified URL on the device screen.', + 'mcpToolCall.help.setDownloadUrl': 'Set the download address for device resource files.', + + // Other text + 'mcpToolCall.text.strong': 'Strong', + 'mcpToolCall.text.medium': 'Medium', + 'mcpToolCall.text.weak': 'Weak', + 'mcpToolCall.text.dark': 'Dark', + 'mcpToolCall.text.light': 'Light', + 'mcpToolCall.text.setSuccess': 'Setting successful', + 'mcpToolCall.text.setFailed': 'Setting failed', + 'mcpToolCall.text.brightnessSetSuccess': 'Brightness setting successful', + 'mcpToolCall.text.brightnessSetFailed': 'Brightness setting failed', + 'mcpToolCall.text.themeSetSuccess': 'Theme setting successful', + 'mcpToolCall.text.themeSetFailed': 'Theme setting failed', + 'mcpToolCall.text.rebootCommandSent': 'Reboot command sent', + 'mcpToolCall.text.rebootFailed': 'Reboot failed', + 'mcpToolCall.text.monochrome': 'Monochrome Screen', + 'mcpToolCall.text.color': 'Color Screen', + 'mcpToolCall.text.getSuccessParseFailed': 'Get successful, but parse failed', + 'mcpToolCall.text.getFailed': 'Get failed', + 'mcpToolCall.text.getSuccessFormatError': 'Get successful, but data format is abnormal', + // Dictionary data dialog related 'dictDataDialog.addDictData': 'Add Dictionary Data', 'dictDataDialog.dictLabel': 'Dictionary Label', @@ -91,6 +229,38 @@ export default { // Manual add device dialog related 'manualAddDeviceDialog.title': 'Manual Add Device', + + // AddModelDialog component related + 'addModelDialog.requiredSupplier': 'Please select a supplier', + + // Register page related + 'register.title': 'Create Account', + 'register.welcome': 'Welcome to XiaoZhi AI', + 'register.usernamePlaceholder': 'Please enter username', + 'register.mobilePlaceholder': 'Please enter mobile phone number', + 'register.captchaPlaceholder': 'Please enter captcha', + 'register.mobileCaptchaPlaceholder': 'Please enter SMS verification code', + 'register.passwordPlaceholder': 'Please set password', + 'register.confirmPasswordPlaceholder': 'Please confirm password', + 'register.goToLogin': 'Already have an account? Login', + 'register.registerButton': 'Register', + 'register.agreeTo': 'By registering, you agree to our', + 'register.userAgreement': 'User Agreement', + 'register.privacyPolicy': 'Privacy Policy', + 'register.notAllowRegister': 'User registration is not allowed', + 'register.captchaLoadFailed': 'Failed to load captcha', + 'register.inputCaptcha': 'Please enter captcha', + 'register.inputCorrectMobile': 'Please enter correct mobile phone number', + 'register.captchaSendSuccess': 'Verification code sent successfully', + 'register.captchaSendFailed': 'Failed to send verification code', + 'register.passwordsNotMatch': 'Passwords do not match', + 'register.registerSuccess': 'Registration successful!', + 'register.registerFailed': 'Registration failed', + 'register.requiredUsername': 'Username cannot be empty', + 'register.requiredPassword': 'Password cannot be empty', + 'register.requiredCaptcha': 'Captcha cannot be empty', + 'register.requiredMobileCaptcha': 'Please enter SMS verification code', + 'manualAddDeviceDialog.deviceType': 'Device Type', 'manualAddDeviceDialog.deviceTypePlaceholder': 'Please select device type', 'manualAddDeviceDialog.firmwareVersion': 'Firmware Version', @@ -287,9 +457,10 @@ export default { 'device.search': 'Search', 'device.selectAll': 'Select All/Deselect All', 'deviceManagement.loading': 'Loading...', - 'device.bindWithCode': 'Bind with Verification Code', + 'device.bindWithCode': '6-digit Verification Code Binding', 'device.manualAdd': 'Manual Add', 'device.unbind': 'Unbind', + 'device.toolCall': 'Tool Call', 'device.selectAtLeastOne': 'Please select at least one record', 'device.confirmBatchUnbind': 'Are you sure you want to unbind {count} selected devices?', 'device.batchUnbindSuccess': 'Successfully unbound {count} devices', @@ -305,6 +476,9 @@ export default { 'device.autoUpdateDisabled': 'Auto update disabled', 'device.batchUnbindSuccess': 'Successfully unbound {count} devices', 'device.getFirmwareTypeFailed': 'Failed to fetch firmware type', + 'device.deviceStatus': 'Status', + 'device.online': 'Online', + 'device.offline': 'Offline', // Message tips 'message.success': 'Operation Successful', diff --git a/main/manager-web/src/i18n/zh_CN.js b/main/manager-web/src/i18n/zh_CN.js index e4250776..27b0b9e0 100644 --- a/main/manager-web/src/i18n/zh_CN.js +++ b/main/manager-web/src/i18n/zh_CN.js @@ -1,4 +1,11 @@ export default { + // 登录页面相关提示文本 + 'login.requiredUsername': '用户名不能为空', + 'login.requiredPassword': '密码不能为空', + 'login.requiredCaptcha': '验证码不能为空', + 'login.requiredMobile': '请输入正确的手机号码', + 'login.loginSuccess': '登录成功!', + // HeaderBar组件文本 'header.smartManagement': '智能体管理', 'header.modelConfig': '模型配置', @@ -9,6 +16,137 @@ export default { 'header.dictManagement': '字典管理', 'header.agentTemplate': '默认角色模板', + // McpToolCallDialog组件文本 + 'mcpToolCall.title': '工具调用', + 'mcpToolCall.execute': '执行', + 'mcpToolCall.chooseFunction': '1、选择功能', + 'mcpToolCall.searchFunction': '搜索功能', + 'mcpToolCall.noResults': '未找到匹配的功能', + 'mcpToolCall.settings': '2、参数设置', + 'mcpToolCall.inputPlaceholder': '请输入{label}', + 'mcpToolCall.valueRange': '取值范围:{min} - {max}', + 'mcpToolCall.selectPlaceholder': '请选择{label}', + 'mcpToolCall.lightTheme': '浅色主题', + 'mcpToolCall.darkTheme': '深色主题', + 'mcpToolCall.pleaseSelect': '请选择一个功能', + 'mcpToolCall.cancel': '取消', + 'mcpToolCall.requiredField': '请输入{field}', + 'mcpToolCall.minValue': '最小值为{value}', + 'mcpToolCall.maxValue': '最大值为{value}', + 'mcpToolCall.selectTool': '请选择要执行的工具', + 'mcpToolCall.executionResult': '3、执行结果', + 'mcpToolCall.copyResult': '复制结果', + 'mcpToolCall.noResultYet': '暂无执行结果', + 'mcpToolCall.loadingToolList': '正在获取工具列表...', + + // 工具名称 + 'mcpToolCall.toolName.getDeviceStatus': '查看设备状态', + 'mcpToolCall.toolName.setVolume': '设置音量', + 'mcpToolCall.toolName.setBrightness': '设置亮度', + 'mcpToolCall.toolName.setTheme': '设置主题', + 'mcpToolCall.toolName.takePhoto': '拍照识别', + 'mcpToolCall.toolName.getSystemInfo': '系统信息', + 'mcpToolCall.toolName.reboot': '重启设备', + 'mcpToolCall.toolName.upgradeFirmware': '升级固件', + 'mcpToolCall.toolName.getScreenInfo': '屏幕信息', + 'mcpToolCall.toolName.snapshot': '屏幕截图', + 'mcpToolCall.toolName.previewImage': '预览图片', + 'mcpToolCall.toolName.setDownloadUrl': '设置下载地址', + + // 工具分类 + 'mcpToolCall.category.audio': '音频', + 'mcpToolCall.category.display': '显示', + 'mcpToolCall.category.camera': '拍摄', + 'mcpToolCall.category.system': '系统', + 'mcpToolCall.category.assets': '资源', + 'mcpToolCall.category.deviceInfo': '设备信息', + + // 表格分类和属性 + 'mcpToolCall.table.audioSpeaker': '音频扬声器', + 'mcpToolCall.table.screen': '屏幕', + 'mcpToolCall.table.network': '网络', + 'mcpToolCall.table.audioControl': '音频控制', + 'mcpToolCall.table.screenControl': '屏幕控制', + 'mcpToolCall.table.systemControl': '系统控制', + 'mcpToolCall.table.screenInfo': '屏幕信息', + 'mcpToolCall.table.hardwareInfo': '硬件信息', + 'mcpToolCall.table.memoryInfo': '内存信息', + 'mcpToolCall.table.applicationInfo': '应用信息', + 'mcpToolCall.table.networkInfo': '网络信息', + 'mcpToolCall.table.displayInfo': '显示信息', + 'mcpToolCall.table.deviceInfo': '设备信息', + 'mcpToolCall.table.systemInfo': '系统信息', + // 表格列标题 + 'mcpToolCall.table.component': '组件', + 'mcpToolCall.table.property': '属性', + 'mcpToolCall.table.value': '值', + + 'mcpToolCall.prop.volume': '音量', + 'mcpToolCall.prop.brightness': '亮度', + 'mcpToolCall.prop.theme': '主题', + 'mcpToolCall.prop.type': '类型', + 'mcpToolCall.prop.ssid': 'SSID', + 'mcpToolCall.prop.signalStrength': '信号强度', + 'mcpToolCall.prop.operationResult': '操作结果', + 'mcpToolCall.prop.width': '宽度', + 'mcpToolCall.prop.height': '高度', + 'mcpToolCall.prop.screenType': '类型', + 'mcpToolCall.prop.chipModel': '芯片型号', + 'mcpToolCall.prop.cpuCores': 'CPU核心数', + 'mcpToolCall.prop.chipVersion': '芯片版本', + 'mcpToolCall.prop.flashSize': 'Flash大小', + 'mcpToolCall.prop.minFreeHeap': '最小可用堆', + 'mcpToolCall.prop.applicationName': '应用名称', + 'mcpToolCall.prop.applicationVersion': '应用版本', + 'mcpToolCall.prop.compileTime': '编译时间', + 'mcpToolCall.prop.idfVersion': 'IDF版本', + 'mcpToolCall.prop.macAddress': 'MAC地址', + 'mcpToolCall.prop.ipAddress': 'IP地址', + 'mcpToolCall.prop.wifiName': 'WiFi名称', + 'mcpToolCall.prop.wifiChannel': 'WiFi信道', + 'mcpToolCall.prop.screenSize': '屏幕尺寸', + 'mcpToolCall.prop.deviceUuid': '设备UUID', + 'mcpToolCall.prop.systemLanguage': '系统语言', + 'mcpToolCall.prop.currentOtaPartition': '当前OTA分区', + 'mcpToolCall.prop.getResult': '获取结果', + 'mcpToolCall.prop.url': 'URL', + 'mcpToolCall.prop.quality': '质量', + 'mcpToolCall.prop.question': '问题', + + // 工具帮助文本 + 'mcpToolCall.help.getDeviceStatus': '查看设备的当前运行状态,包括音量、屏幕、电池等信息。', + 'mcpToolCall.help.setVolume': '调整设备的音量大小,请输入0-100之间的数值。', + 'mcpToolCall.help.setBrightness': '调整设备屏幕的亮度,请输入0-100之间的数值。', + 'mcpToolCall.help.setTheme': '切换设备屏幕的显示主题,可以选择浅色或深色模式。', + 'mcpToolCall.help.takePhoto': '使用设备摄像头拍摄照片并进行识别分析,请输入要询问的问题。', + 'mcpToolCall.help.getSystemInfo': '获取设备的系统信息,包括硬件规格、软件版本等。', + 'mcpToolCall.help.reboot': '重启设备,执行后设备将重新启动。', + 'mcpToolCall.help.upgradeFirmware': '从指定URL下载并升级设备固件,升级后设备会自动重启。', + 'mcpToolCall.help.getScreenInfo': '获取屏幕的详细信息,如分辨率、尺寸等参数。', + 'mcpToolCall.help.snapshot': '对当前屏幕进行截图并上传到指定URL。', + 'mcpToolCall.help.previewImage': '在设备屏幕上预览指定URL的图片。', + 'mcpToolCall.help.setDownloadUrl': '设置设备资源文件的下载地址。', + + // 其他文本 + 'mcpToolCall.text.strong': '强', + 'mcpToolCall.text.medium': '中', + 'mcpToolCall.text.weak': '弱', + 'mcpToolCall.text.dark': '深色', + 'mcpToolCall.text.light': '浅色', + 'mcpToolCall.text.setSuccess': '设置成功', + 'mcpToolCall.text.setFailed': '设置失败', + 'mcpToolCall.text.brightnessSetSuccess': '亮度设置成功', + 'mcpToolCall.text.brightnessSetFailed': '亮度设置失败', + 'mcpToolCall.text.themeSetSuccess': '主题设置成功', + 'mcpToolCall.text.themeSetFailed': '主题设置失败', + 'mcpToolCall.text.rebootCommandSent': '重启指令已发送', + 'mcpToolCall.text.rebootFailed': '重启失败', + 'mcpToolCall.text.monochrome': '单色屏', + 'mcpToolCall.text.color': '彩色屏', + 'mcpToolCall.text.getSuccessParseFailed': '获取成功,但解析失败', + 'mcpToolCall.text.getFailed': '获取失败', + 'mcpToolCall.text.getSuccessFormatError': '获取成功,但数据格式异常', + // 字典数据对话框相关 'dictDataDialog.addDictData': '新增字典数据', 'dictDataDialog.dictLabel': '字典标签', @@ -90,6 +228,38 @@ export default { // 手動添加設備對話框相關 'manualAddDeviceDialog.title': '手动添加设备', + + // AddModelDialog组件相关 + 'addModelDialog.requiredSupplier': '请选择供应器', + + // 注册页面相关 + 'register.title': '创建账号', + 'register.welcome': '欢迎使用小智AI', + 'register.usernamePlaceholder': '请输入用户名', + 'register.mobilePlaceholder': '请输入手机号码', + 'register.captchaPlaceholder': '请输入验证码', + 'register.mobileCaptchaPlaceholder': '请输入短信验证码', + 'register.passwordPlaceholder': '请设置密码', + 'register.confirmPasswordPlaceholder': '请确认密码', + 'register.goToLogin': '已有账号?登录', + 'register.registerButton': '注册', + 'register.agreeTo': '注册即表示您同意我们的', + 'register.userAgreement': '用户协议', + 'register.privacyPolicy': '隐私政策', + 'register.notAllowRegister': '当前不允许用户注册', + 'register.captchaLoadFailed': '验证码加载失败', + 'register.inputCaptcha': '请输入验证码', + 'register.inputCorrectMobile': '请输入正确的手机号码', + 'register.captchaSendSuccess': '验证码发送成功', + 'register.captchaSendFailed': '验证码发送失败', + 'register.passwordsNotMatch': '两次输入的密码不一致', + 'register.registerSuccess': '注册成功!', + 'register.registerFailed': '注册失败', + 'register.requiredUsername': '用户名不能为空', + 'register.requiredPassword': '密码不能为空', + 'register.requiredCaptcha': '验证码不能为空', + 'register.requiredMobileCaptcha': '请输入短信验证码', + 'manualAddDeviceDialog.deviceType': '设备型号', 'manualAddDeviceDialog.deviceTypePlaceholder': '请选择设备型号', 'manualAddDeviceDialog.firmwareVersion': '固件版本', @@ -287,9 +457,10 @@ export default { 'device.search': '搜索', 'device.selectAll': '全选/取消全选', 'deviceManagement.loading': '拼命加载中', - 'device.bindWithCode': '扫码绑定', + 'device.bindWithCode': '6位验证码绑定', 'device.manualAdd': '手动添加', 'device.unbind': '解绑', + 'device.toolCall': '工具调用', 'device.selectAtLeastOne': '请至少选择一条记录', 'device.confirmBatchUnbind': '确认要解绑选中的 {count} 台设备吗?', 'device.batchUnbindSuccess': '成功解绑 {count} 台设备', @@ -305,6 +476,9 @@ export default { 'device.autoUpdateDisabled': '已关闭自动升级', 'device.batchUnbindSuccess': '成功解绑 {count} 台设备', 'device.getFirmwareTypeFailed': '获取固件类型失败', + 'device.deviceStatus': '状态', + 'device.online': '在线', + 'device.offline': '离线', // 消息提示 'message.success': '操作成功', diff --git a/main/manager-web/src/i18n/zh_TW.js b/main/manager-web/src/i18n/zh_TW.js index 3fa9874d..a9f224f1 100644 --- a/main/manager-web/src/i18n/zh_TW.js +++ b/main/manager-web/src/i18n/zh_TW.js @@ -1,4 +1,11 @@ export default { + // 登錄頁面相關提示文本 + 'login.requiredUsername': '用戶名不能為空', + 'login.requiredPassword': '密碼不能為空', + 'login.requiredCaptcha': '驗證碼不能為空', + 'login.requiredMobile': '請輸入正確的手機號碼', + 'login.loginSuccess': '登錄成功!', + // HeaderBar组件文本 // 在header相关翻译中添加 'header.smartManagement': '智能體管理', @@ -10,6 +17,137 @@ export default { 'header.dictManagement': '字典管理', 'header.agentTemplate': '預設角色模板', // 添加这一行 + // McpToolCallDialog组件文本 + 'mcpToolCall.title': '工具調用', + 'mcpToolCall.execute': '執行', + 'mcpToolCall.chooseFunction': '1、選擇功能', + 'mcpToolCall.searchFunction': '搜索功能', + 'mcpToolCall.noResults': '未找到匹配的功能', + 'mcpToolCall.settings': '2、參數設置', + 'mcpToolCall.inputPlaceholder': '請輸入{label}', + 'mcpToolCall.valueRange': '取值範圍:{min} - {max}', + 'mcpToolCall.selectPlaceholder': '請選擇{label}', + 'mcpToolCall.lightTheme': '淺色主題', + 'mcpToolCall.darkTheme': '深色主題', + 'mcpToolCall.pleaseSelect': '請選擇一個功能', + 'mcpToolCall.cancel': '取消', + 'mcpToolCall.requiredField': '請輸入{field}', + 'mcpToolCall.minValue': '最小值為{value}', + 'mcpToolCall.maxValue': '最大值為{value}', + 'mcpToolCall.selectTool': '請選擇要執行的工具', + 'mcpToolCall.executionResult': '3、執行結果', + 'mcpToolCall.copyResult': '複製結果', + 'mcpToolCall.noResultYet': '暫無執行結果', + 'mcpToolCall.loadingToolList': '正在獲取工具列表...', + + // 工具名稱 + 'mcpToolCall.toolName.getDeviceStatus': '查看設備狀態', + 'mcpToolCall.toolName.setVolume': '設置音量', + 'mcpToolCall.toolName.setBrightness': '設置亮度', + 'mcpToolCall.toolName.setTheme': '設置主題', + 'mcpToolCall.toolName.takePhoto': '拍照識別', + 'mcpToolCall.toolName.getSystemInfo': '系統資訊', + 'mcpToolCall.toolName.reboot': '重啟設備', + 'mcpToolCall.toolName.upgradeFirmware': '升級固件', + 'mcpToolCall.toolName.getScreenInfo': '螢幕資訊', + 'mcpToolCall.toolName.snapshot': '螢幕截圖', + 'mcpToolCall.toolName.previewImage': '預覽圖片', + 'mcpToolCall.toolName.setDownloadUrl': '設置下載地址', + + // 工具分類 + 'mcpToolCall.category.audio': '音頻', + 'mcpToolCall.category.display': '顯示', + 'mcpToolCall.category.camera': '拍攝', + 'mcpToolCall.category.system': '系統', + 'mcpToolCall.category.assets': '資源', + 'mcpToolCall.category.deviceInfo': '設備資訊', + + // 表格分類和屬性 + 'mcpToolCall.table.audioSpeaker': '音頻揚聲器', + 'mcpToolCall.table.screen': '螢幕', + 'mcpToolCall.table.network': '網路', + 'mcpToolCall.table.audioControl': '音頻控制', + 'mcpToolCall.table.screenControl': '螢幕控制', + 'mcpToolCall.table.systemControl': '系統控制', + 'mcpToolCall.table.screenInfo': '螢幕資訊', + 'mcpToolCall.table.hardwareInfo': '硬體資訊', + 'mcpToolCall.table.memoryInfo': '記憶體資訊', + 'mcpToolCall.table.applicationInfo': '應用資訊', + 'mcpToolCall.table.networkInfo': '網路資訊', + 'mcpToolCall.table.displayInfo': '顯示資訊', + 'mcpToolCall.table.deviceInfo': '設備資訊', + 'mcpToolCall.table.systemInfo': '系統資訊', + // 表格列標題 + 'mcpToolCall.table.component': '組件', + 'mcpToolCall.table.property': '屬性', + 'mcpToolCall.table.value': '值', + + 'mcpToolCall.prop.volume': '音量', + 'mcpToolCall.prop.brightness': '亮度', + 'mcpToolCall.prop.theme': '主題', + 'mcpToolCall.prop.type': '類型', + 'mcpToolCall.prop.ssid': 'SSID', + 'mcpToolCall.prop.signalStrength': '信號強度', + 'mcpToolCall.prop.operationResult': '操作結果', + 'mcpToolCall.prop.width': '寬度', + 'mcpToolCall.prop.height': '高度', + 'mcpToolCall.prop.screenType': '類型', + 'mcpToolCall.prop.chipModel': '晶片型號', + 'mcpToolCall.prop.cpuCores': 'CPU核心數', + 'mcpToolCall.prop.chipVersion': '晶片版本', + 'mcpToolCall.prop.flashSize': 'Flash大小', + 'mcpToolCall.prop.minFreeHeap': '最小可用堆', + 'mcpToolCall.prop.applicationName': '應用名稱', + 'mcpToolCall.prop.applicationVersion': '應用版本', + 'mcpToolCall.prop.compileTime': '編譯時間', + 'mcpToolCall.prop.idfVersion': 'IDF版本', + 'mcpToolCall.prop.macAddress': 'MAC地址', + 'mcpToolCall.prop.ipAddress': 'IP地址', + 'mcpToolCall.prop.wifiName': 'WiFi名稱', + 'mcpToolCall.prop.wifiChannel': 'WiFi信道', + 'mcpToolCall.prop.screenSize': '螢幕尺寸', + 'mcpToolCall.prop.deviceUuid': '設備UUID', + 'mcpToolCall.prop.systemLanguage': '系統語言', + 'mcpToolCall.prop.currentOtaPartition': '當前OTA分區', + 'mcpToolCall.prop.getResult': '獲取結果', + 'mcpToolCall.prop.url': 'URL', + 'mcpToolCall.prop.quality': '品質', + 'mcpToolCall.prop.question': '問題', + + // 工具幫助文本 + 'mcpToolCall.help.getDeviceStatus': '查看設備的當前運行狀態,包括音量、螢幕、電池等資訊。', + 'mcpToolCall.help.setVolume': '調整設備的音量大小,請輸入0-100之間的數值。', + 'mcpToolCall.help.setBrightness': '調整設備螢幕的亮度,請輸入0-100之間的數值。', + 'mcpToolCall.help.setTheme': '切換設備螢幕的顯示主題,可以選擇淺色或深色模式。', + 'mcpToolCall.help.takePhoto': '使用設備攝像頭拍攝照片並進行識別分析,請輸入要詢問的問題。', + 'mcpToolCall.help.getSystemInfo': '獲取設備的系統資訊,包括硬體規格、軟體版本等。', + 'mcpToolCall.help.reboot': '重啟設備,執行後設備將重新啟動。', + 'mcpToolCall.help.upgradeFirmware': '從指定URL下載並升級設備固件,升級後設備會自動重啟。', + 'mcpToolCall.help.getScreenInfo': '獲取螢幕的詳細資訊,如解析度、尺寸等參數。', + 'mcpToolCall.help.snapshot': '對當前螢幕進行截圖並上傳到指定URL。', + 'mcpToolCall.help.previewImage': '在設備螢幕上預覽指定URL的圖片。', + 'mcpToolCall.help.setDownloadUrl': '設置設備資源文件的下載地址。', + + // 其他文本 + 'mcpToolCall.text.strong': '強', + 'mcpToolCall.text.medium': '中', + 'mcpToolCall.text.weak': '弱', + 'mcpToolCall.text.dark': '深色', + 'mcpToolCall.text.light': '淺色', + 'mcpToolCall.text.setSuccess': '設置成功', + 'mcpToolCall.text.setFailed': '設置失敗', + 'mcpToolCall.text.brightnessSetSuccess': '亮度設置成功', + 'mcpToolCall.text.brightnessSetFailed': '亮度設置失敗', + 'mcpToolCall.text.themeSetSuccess': '主題設置成功', + 'mcpToolCall.text.themeSetFailed': '主題設置失敗', + 'mcpToolCall.text.rebootCommandSent': '重啟指令已發送', + 'mcpToolCall.text.rebootFailed': '重啟失敗', + 'mcpToolCall.text.monochrome': '單色屏', + 'mcpToolCall.text.color': '彩色屏', + 'mcpToolCall.text.getSuccessParseFailed': '獲取成功,但解析失敗', + 'mcpToolCall.text.getFailed': '獲取失敗', + 'mcpToolCall.text.getSuccessFormatError': '獲取成功,但數據格式異常', + // 字典數據對話框相關 'dictDataDialog.addDictData': '新增字典數據', 'dictDataDialog.dictLabel': '字典標籤', @@ -91,6 +229,38 @@ export default { // 手動添加設備對話框相關 'manualAddDeviceDialog.title': '手動添加設備', + + // AddModelDialog組件相關 + 'addModelDialog.requiredSupplier': '請選擇供應器', + + // 註冊頁面相關 + 'register.title': '建立帳號', + 'register.welcome': '歡迎使用小智慧AI', + 'register.usernamePlaceholder': '請輸入用戶名', + 'register.mobilePlaceholder': '請輸入手機號碼', + 'register.captchaPlaceholder': '請輸入驗證碼', + 'register.mobileCaptchaPlaceholder': '請輸入簡訊驗證碼', + 'register.passwordPlaceholder': '請設定密碼', + 'register.confirmPasswordPlaceholder': '請確認密碼', + 'register.goToLogin': '已有帳號?登錄', + 'register.registerButton': '註冊', + 'register.agreeTo': '註冊即表示您同意我們的', + 'register.userAgreement': '用戶協議', + 'register.privacyPolicy': '隱私政策', + 'register.notAllowRegister': '當前不允許用戶註冊', + 'register.captchaLoadFailed': '驗證碼加載失敗', + 'register.inputCaptcha': '請輸入驗證碼', + 'register.inputCorrectMobile': '請輸入正確的手機號碼', + 'register.captchaSendSuccess': '驗證碼發送成功', + 'register.captchaSendFailed': '驗證碼發送失敗', + 'register.passwordsNotMatch': '兩次輸入的密碼不一致', + 'register.registerSuccess': '註冊成功!', + 'register.registerFailed': '註冊失敗', + 'register.requiredUsername': '用戶名不能為空', + 'register.requiredPassword': '密碼不能為空', + 'register.requiredCaptcha': '驗證碼不能為空', + 'register.requiredMobileCaptcha': '請輸入簡訊驗證碼', + 'manualAddDeviceDialog.deviceType': '設備型號', 'manualAddDeviceDialog.deviceTypePlaceholder': '請選擇設備型號', 'manualAddDeviceDialog.firmwareVersion': '固件版本', @@ -287,9 +457,10 @@ export default { 'device.search': '搜索', 'device.selectAll': '全選/取消全選', 'deviceManagement.loading': '拼命加載中', - 'device.bindWithCode': '驗證碼綁定', + 'device.bindWithCode': '6位驗證碼綁定', 'device.manualAdd': '手動添加', 'device.unbind': '解綁', + 'device.toolCall': '工具調用', 'device.selectAtLeastOne': '請至少選擇一條記錄', 'device.confirmBatchUnbind': '確認要解綁選中的 {count} 台設備嗎?', 'device.batchUnbindSuccess': '成功解綁 {count} 台設備', @@ -305,6 +476,9 @@ export default { 'device.autoUpdateDisabled': '已關閉自動升級', 'device.batchUnbindSuccess': '成功解綁 {count} 台設備', 'device.getFirmwareTypeFailed': '獲取固件類型失敗', + 'device.deviceStatus': '狀態', + 'device.online': '在線', + 'device.offline': '離線', // 消息提示 'message.success': '操作成功', diff --git a/main/manager-web/src/views/DeviceManagement.vue b/main/manager-web/src/views/DeviceManagement.vue index 65945eb8..053fcbc6 100644 --- a/main/manager-web/src/views/DeviceManagement.vue +++ b/main/manager-web/src/views/DeviceManagement.vue @@ -1,12 +1,12 @@ @@ -115,19 +125,23 @@