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
+
@@ -237,15 +237,16 @@ This project provides the following testing tools to help you verify the system

| 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 @@
+
+
+
+
+ {{ $t("mcpToolCall.title") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("mcpToolCall.settings") }}
+
+
+
+ {{ getToolHelpText(selectedTool.name) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("mcpToolCall.pleaseSelect") }}
+
+
+
+
+
+
+ {{ $t("mcpToolCall.executionResult") }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formattedExecutionResult }}
+
+
+
+ {{ $t("mcpToolCall.noResultYet") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
-
+
{{ $t('device.management') }}
+ @keyup.enter.native="handleSearch" clearable />
{{ $t('device.search') }}
@@ -16,9 +16,9 @@
+ :header-cell-class-name="headerCellClassName" v-loading="loading"
+ :element-loading-text="$t('deviceManagement.loading')" element-loading-spinner="el-icon-loading"
+ element-loading-background="rgba(255, 255, 255, 0.7)">
@@ -29,74 +29,83 @@
{{ getFirmwareTypeName(scope.row.model) }}
-
+
-
+
+
+
+ {{ $t('device.online') }}
+ {{ $t('device.offline') }}
+
+
-
+
+ @change="handleOtaSwitchChange(scope.row)">
- {{ $t('device.unbind') }}
-
+ {{ $t('device.unbind') }}
+
+
+ {{ $t('device.toolCall') }}
+
-
- {{ isCurrentPageAllSelected ? $t('common.deselectAll') : $t('common.selectAll') }}
-
-
- {{ $t('device.bindWithCode') }}
-
-
- {{ $t('device.manualAdd') }}
-
- {{ $t('device.unbind') }}
-
+
+ {{ isCurrentPageAllSelected ? $t('common.deselectAll') : $t('common.selectAll') }}
+
+
+ {{ $t('device.bindWithCode') }}
+
+
+ {{ $t('device.manualAdd') }}
+
+ {{
+ $t('device.unbind')
+ }}
+
@@ -105,9 +114,10 @@
+ @refresh="fetchBindDevices(currentAgentId)" />
+ @refresh="fetchBindDevices(currentAgentId)" />
+
@@ -115,19 +125,23 @@
|