mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 23:53:55 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89ef36711a | ||
|
|
0ba3e2c964 | ||
|
|
e5bc029d87 | ||
|
|
3fb925f13d | ||
|
|
4e958ce57c | ||
|
|
7f6ce89e39 | ||
|
|
71274cf0a3 | ||
|
|
58127cdfdc | ||
|
|
6d7bbfca95 | ||
|
|
3de39c8cdc | ||
|
|
387398a069 | ||
|
|
f17011939c | ||
|
|
9b6f564d30 | ||
|
|
e23989add0 | ||
|
|
4f64316144 | ||
|
|
68e10f42f8 | ||
|
|
3d1cae4692 | ||
|
|
31e84dadd1 | ||
|
|
ba4423684b | ||
|
|
62eaf46f34 | ||
|
|
3767bf8c81 | ||
|
|
300a33ddc2 | ||
|
|
34d7bd92ca | ||
|
|
44e7380c6b | ||
|
|
15ca08ce14 | ||
|
|
83b88eeaa0 | ||
|
|
290c92ee39 | ||
|
|
c2985c0db3 | ||
|
|
334dfc7917 | ||
|
|
03b51f73e7 | ||
|
|
af079d6c18 | ||
|
|
99008cf813 | ||
|
|
5fcb670821 | ||
|
|
3bdf8e20c7 | ||
|
|
e8d44b13cf | ||
|
|
c6a7871e5b | ||
|
|
4c9824b16f | ||
|
|
3e54a3272a | ||
|
|
8851406f84 | ||
|
|
fe85ea049a | ||
|
|
221c01e642 | ||
|
|
753aa280f6 | ||
|
|
64f3062eb2 | ||
|
|
f5143d60b7 | ||
|
|
e9fb36bf8f |
@@ -64,11 +64,16 @@ cp config/mqtt.json.example config/mqtt.json
|
|||||||
PUBLIC_IP=your-ip # 服务器公网IP
|
PUBLIC_IP=your-ip # 服务器公网IP
|
||||||
MQTT_PORT=1883 # MQTT服务器端口
|
MQTT_PORT=1883 # MQTT服务器端口
|
||||||
UDP_PORT=8884 # UDP服务器端口
|
UDP_PORT=8884 # UDP服务器端口
|
||||||
|
API_PORT=8007 # 管理API端口
|
||||||
MQTT_SIGNATURE_KEY=test # MQTT签名密钥
|
MQTT_SIGNATURE_KEY=test # MQTT签名密钥
|
||||||
```
|
```
|
||||||
请注意`PUBLIC_IP`配置,确保其与实际公网IP一致,如果有域名就填域名。
|
请注意`PUBLIC_IP`配置,确保其与实际公网IP一致,如果有域名就填域名。
|
||||||
|
|
||||||
`MQTT_SIGNATURE_KEY` 是用于MQTT连接认证的密钥,最好设置成复杂一点的,这个密钥稍后还会用到。
|
`MQTT_SIGNATURE_KEY` 是用于MQTT连接认证的密钥,最好设置成复杂一点的,最好是设置成8个字符以上且同时包含大小写字母,这个密钥稍后还会用到。
|
||||||
|
|
||||||
|
- 注意不要用简单的密码,比如`123456`、`test`等。
|
||||||
|
- 注意不要用简单的密码,比如`123456`、`test`等。
|
||||||
|
- 注意不要用简单的密码,比如`123456`、`test`等。
|
||||||
|
|
||||||
6. 启动MQTT网关
|
6. 启动MQTT网关
|
||||||
```
|
```
|
||||||
@@ -102,6 +107,10 @@ pm2 restart xz-mqtt
|
|||||||
```
|
```
|
||||||
192.168.0.7:8884
|
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地址
|
上面的配置完成后,你可以使用curl命令,验证你的ota地址是否会下发mqtt配置,把下面的`http://localhost:8002/xiaozhi/ota/`改成你的ota地址
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -126,6 +126,11 @@ public interface Constant {
|
|||||||
*/
|
*/
|
||||||
String SERVER_VOICE_PRINT = "server.voice_print";
|
String SERVER_VOICE_PRINT = "server.voice_print";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mqtt密钥
|
||||||
|
*/
|
||||||
|
String SERVER_MQTT_SECRET = "server.mqtt_signature_key";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无记忆
|
* 无记忆
|
||||||
*/
|
*/
|
||||||
@@ -242,7 +247,7 @@ public interface Constant {
|
|||||||
/**
|
/**
|
||||||
* 版本号
|
* 版本号
|
||||||
*/
|
*/
|
||||||
public static final String VERSION = "0.8.1";
|
public static final String VERSION = "0.8.2";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无效固件URL
|
* 无效固件URL
|
||||||
|
|||||||
@@ -55,4 +55,113 @@ public interface ErrorCode {
|
|||||||
|
|
||||||
int OTA_DEVICE_NOT_FOUND = 10041;
|
int OTA_DEVICE_NOT_FOUND = 10041;
|
||||||
int OTA_DEVICE_NEED_BIND = 10042;
|
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; // 字典标签重复
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.springframework.web.servlet.resource.NoResourceFoundException;
|
|||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xiaozhi.common.utils.MessageUtils;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,7 +63,7 @@ public class RenExceptionHandler {
|
|||||||
@ExceptionHandler(NoResourceFoundException.class)
|
@ExceptionHandler(NoResourceFoundException.class)
|
||||||
public Result<Void> handleNoResourceFoundException(NoResourceFoundException ex) {
|
public Result<Void> handleNoResourceFoundException(NoResourceFoundException ex) {
|
||||||
log.warn("Resource not found: {}", ex.getMessage());
|
log.warn("Resource not found: {}", ex.getMessage());
|
||||||
return new Result<Void>().error(404, "资源不存在");
|
return new Result<Void>().error(404, MessageUtils.getMessage(ErrorCode.RESOURCE_NOT_FOUND));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
@@ -76,7 +77,7 @@ public class RenExceptionHandler {
|
|||||||
})
|
})
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse("请求参数错误!");
|
.orElse(MessageUtils.getMessage(ErrorCode.PARAM_VALUE_NULL));
|
||||||
|
|
||||||
return new Result<Void>().error(ErrorCode.PARAM_VALUE_NULL, errorMsg);
|
return new Result<Void>().error(ErrorCode.PARAM_VALUE_NULL, errorMsg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.springframework.core.io.ResourceLoader;
|
|||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@@ -37,7 +38,7 @@ public class ResourcesUtils {
|
|||||||
}
|
}
|
||||||
} catch (IOException e){
|
} catch (IOException e){
|
||||||
log.error("方法:loadString()读取资源失败--{}",e.getMessage());
|
log.error("方法:loadString()读取资源失败--{}",e.getMessage());
|
||||||
throw new RenException("读取资源失败");
|
throw new RenException(ErrorCode.RESOURCE_READ_ERROR);
|
||||||
}
|
}
|
||||||
return luaScriptBuilder.toString();
|
return luaScriptBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.modules.agent.dto.AgentVoicePrintSaveDTO;
|
import xiaozhi.modules.agent.dto.AgentVoicePrintSaveDTO;
|
||||||
@@ -42,7 +43,7 @@ public class AgentVoicePrintController {
|
|||||||
if (b) {
|
if (b) {
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
return new Result<Void>().error("智能体的声纹创建失败");
|
return new Result<Void>().error(ErrorCode.AGENT_VOICEPRINT_CREATE_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping
|
@PutMapping
|
||||||
@@ -54,7 +55,7 @@ public class AgentVoicePrintController {
|
|||||||
if (b) {
|
if (b) {
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
return new Result<Void>().error("智能体的对应声纹更新失败");
|
return new Result<Void>().error(ErrorCode.AGENT_VOICEPRINT_UPDATE_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@@ -67,7 +68,7 @@ public class AgentVoicePrintController {
|
|||||||
if (delete) {
|
if (delete) {
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
return new Result<Void>().error("智能体的对应声纹删除失败");
|
return new Result<Void>().error(ErrorCode.AGENT_VOICEPRINT_DELETE_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/list/{id}")
|
@GetMapping("/list/{id}")
|
||||||
@@ -76,7 +77,7 @@ public class AgentVoicePrintController {
|
|||||||
public Result<List<AgentVoicePrintVO>> list(@PathVariable String id) {
|
public Result<List<AgentVoicePrintVO>> list(@PathVariable String id) {
|
||||||
String voiceprintUrl = sysParamsService.getValue("server.voice_print", true);
|
String voiceprintUrl = sysParamsService.getValue("server.voice_print", true);
|
||||||
if (StringUtils.isBlank(voiceprintUrl) || "null".equals(voiceprintUrl)) {
|
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();
|
Long userId = SecurityUser.getUserId();
|
||||||
List<AgentVoicePrintVO> list = agentVoicePrintService.list(userId, id);
|
List<AgentVoicePrintVO> list = agentVoicePrintService.list(userId, id);
|
||||||
|
|||||||
+4
-3
@@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
@@ -74,7 +75,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
AgentInfoVO agent = agentDao.selectAgentInfoById(id);
|
AgentInfoVO agent = agentDao.selectAgentInfoById(id);
|
||||||
|
|
||||||
if (agent == null) {
|
if (agent == null) {
|
||||||
throw new RenException("智能体不存在");
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||||
@@ -204,7 +205,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
// 先查询现有实体
|
// 先查询现有实体
|
||||||
AgentEntity existingEntity = this.getAgentById(agentId);
|
AgentEntity existingEntity = this.getAgentById(agentId);
|
||||||
if (existingEntity == null) {
|
if (existingEntity == null) {
|
||||||
throw new RuntimeException("智能体不存在");
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只更新提供的非空字段
|
// 只更新提供的非空字段
|
||||||
@@ -328,7 +329,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
|
|
||||||
boolean b = validateLLMIntentParams(dto.getLlmModelId(), dto.getIntentModelId());
|
boolean b = validateLLMIntentParams(dto.getLlmModelId(), dto.getIntentModelId());
|
||||||
if (!b) {
|
if (!b) {
|
||||||
throw new RenException("LLM大模型和Intent意图识别,选择参数不匹配");
|
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
|
||||||
}
|
}
|
||||||
this.updateById(existingEntity);
|
this.updateById(existingEntity);
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-13
@@ -26,6 +26,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.common.utils.JsonUtils;
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
@@ -78,7 +79,7 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao,
|
|||||||
// 根据识别出的声纹ID查询对应的用户信息
|
// 根据识别出的声纹ID查询对应的用户信息
|
||||||
AgentVoicePrintEntity existingVoicePrint = baseMapper.selectById(response.getSpeakerId());
|
AgentVoicePrintEntity existingVoicePrint = baseMapper.selectById(response.getSpeakerId());
|
||||||
String existingUserName = existingVoicePrint != null ? existingVoicePrint.getSourceName() : "未知用户";
|
String existingUserName = existingVoicePrint != null ? existingVoicePrint.getSourceName() : "未知用户";
|
||||||
throw new RenException("此声音声纹对应的人(" + existingUserName + ")已经注册,请选择其他声音注册");
|
throw new RenException(ErrorCode.VOICEPRINT_ALREADY_REGISTERED, existingUserName);
|
||||||
}
|
}
|
||||||
AgentVoicePrintEntity entity = ConvertUtils.sourceToTarget(dto, AgentVoicePrintEntity.class);
|
AgentVoicePrintEntity entity = ConvertUtils.sourceToTarget(dto, AgentVoicePrintEntity.class);
|
||||||
// 开启事务
|
// 开启事务
|
||||||
@@ -100,7 +101,7 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao,
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
status.setRollbackOnly(); // 标记事务回滚
|
status.setRollbackOnly(); // 标记事务回滚
|
||||||
log.error("保存声纹错误原因:{}", e.getMessage());
|
log.error("保存声纹错误原因:{}", e.getMessage());
|
||||||
throw new RenException("保存声纹错误,请联系管理员");
|
throw new RenException(ErrorCode.VOICE_PRINT_SAVE_ERROR);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -123,7 +124,7 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao,
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
status.setRollbackOnly(); // 标记事务回滚
|
status.setRollbackOnly(); // 标记事务回滚
|
||||||
log.error("删除声纹存在错误原因:{}", e.getMessage());
|
log.error("删除声纹存在错误原因:{}", e.getMessage());
|
||||||
throw new RenException("删除声纹出现了错误");
|
throw new RenException(ErrorCode.VOICEPRINT_DELETE_ERROR);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
// 数据库声纹数据删除成功才继续执行删除声纹服务的数据
|
// 数据库声纹数据删除成功才继续执行删除声纹服务的数据
|
||||||
@@ -179,7 +180,7 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao,
|
|||||||
// 根据识别出的声纹ID查询对应的用户信息
|
// 根据识别出的声纹ID查询对应的用户信息
|
||||||
AgentVoicePrintEntity existingVoicePrint = baseMapper.selectById(response.getSpeakerId());
|
AgentVoicePrintEntity existingVoicePrint = baseMapper.selectById(response.getSpeakerId());
|
||||||
String existingUserName = existingVoicePrint != null ? existingVoicePrint.getSourceName() : "未知用户";
|
String existingUserName = existingVoicePrint != null ? existingVoicePrint.getSourceName() : "未知用户";
|
||||||
throw new RenException("此次修改不允许,此声音已经注册为声纹了(" + existingUserName + ")");
|
throw new RenException(ErrorCode.VOICEPRINT_UPDATE_NOT_ALLOWED, existingUserName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -208,7 +209,7 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao,
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
status.setRollbackOnly(); // 标记事务回滚
|
status.setRollbackOnly(); // 标记事务回滚
|
||||||
log.error("修改声纹错误原因:{}", e.getMessage());
|
log.error("修改声纹错误原因:{}", e.getMessage());
|
||||||
throw new RenException("修改声纹错误,请联系管理员");
|
throw new RenException(ErrorCode.VOICEPRINT_UPDATE_ADMIN_ERROR);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -225,7 +226,7 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao,
|
|||||||
return new URI(voicePrint);
|
return new URI(voicePrint);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
log.error("路径格式不正确路径:{},\n错误信息:{}", voicePrint, e.getMessage());
|
log.error("路径格式不正确路径:{},\n错误信息:{}", voicePrint, e.getMessage());
|
||||||
throw new RuntimeException("声纹接口的地址存在错误,请进入参数管理修改声纹接口地址");
|
throw new RenException(ErrorCode.VOICEPRINT_API_URI_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,13 +271,13 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao,
|
|||||||
// 判断这个音频是否属于当前智能体
|
// 判断这个音频是否属于当前智能体
|
||||||
boolean b = agentChatHistoryService.isAudioOwnedByAgent(audioId, agentId);
|
boolean b = agentChatHistoryService.isAudioOwnedByAgent(audioId, agentId);
|
||||||
if (!b) {
|
if (!b) {
|
||||||
throw new RenException("音频数据不属于这个智能体");
|
throw new RenException(ErrorCode.VOICEPRINT_AUDIO_NOT_BELONG_AGENT);
|
||||||
}
|
}
|
||||||
// 获取到音频数据
|
// 获取到音频数据
|
||||||
byte[] audio = agentChatAudioService.getAudio(audioId);
|
byte[] audio = agentChatAudioService.getAudio(audioId);
|
||||||
// 如果音频数据为空的直接报错不进行下去
|
// 如果音频数据为空的直接报错不进行下去
|
||||||
if (audio == null || audio.length == 0) {
|
if (audio == null || audio.length == 0) {
|
||||||
throw new RenException("音频数据是空的请检查上传数据");
|
throw new RenException(ErrorCode.VOICEPRINT_AUDIO_EMPTY);
|
||||||
}
|
}
|
||||||
// 将字节数组包装为资源,返回
|
// 将字节数组包装为资源,返回
|
||||||
return new ByteArrayResource(audio) {
|
return new ByteArrayResource(audio) {
|
||||||
@@ -314,13 +315,13 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao,
|
|||||||
|
|
||||||
if (response.getStatusCode() != HttpStatus.OK) {
|
if (response.getStatusCode() != HttpStatus.OK) {
|
||||||
log.error("声纹注册失败,请求路径:{}", requestUrl);
|
log.error("声纹注册失败,请求路径:{}", requestUrl);
|
||||||
throw new RenException("声纹保存失败,请求不成功");
|
throw new RenException(ErrorCode.VOICEPRINT_REGISTER_REQUEST_ERROR);
|
||||||
}
|
}
|
||||||
// 检查响应内容
|
// 检查响应内容
|
||||||
String responseBody = response.getBody();
|
String responseBody = response.getBody();
|
||||||
if (responseBody == null || !responseBody.contains("true")) {
|
if (responseBody == null || !responseBody.contains("true")) {
|
||||||
log.error("声纹注册失败,请求处理失败内容:{}", responseBody == null ? "空内容" : responseBody);
|
log.error("声纹注册失败,请求处理失败内容:{}", responseBody == null ? "空内容" : responseBody);
|
||||||
throw new RenException("声纹保存失败,请求处理失败");
|
throw new RenException(ErrorCode.VOICEPRINT_REGISTER_PROCESS_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,13 +345,13 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao,
|
|||||||
String.class);
|
String.class);
|
||||||
if (response.getStatusCode() != HttpStatus.OK) {
|
if (response.getStatusCode() != HttpStatus.OK) {
|
||||||
log.error("声纹注销失败,请求路径:{}", requestUrl);
|
log.error("声纹注销失败,请求路径:{}", requestUrl);
|
||||||
throw new RenException("声纹注销失败,请求不成功");
|
throw new RenException(ErrorCode.VOICEPRINT_UNREGISTER_REQUEST_ERROR);
|
||||||
}
|
}
|
||||||
// 检查响应内容
|
// 检查响应内容
|
||||||
String responseBody = response.getBody();
|
String responseBody = response.getBody();
|
||||||
if (responseBody == null || !responseBody.contains("true")) {
|
if (responseBody == null || !responseBody.contains("true")) {
|
||||||
log.error("声纹注销失败,请求处理失败内容:{}", responseBody == null ? "空内容" : responseBody);
|
log.error("声纹注销失败,请求处理失败内容:{}", responseBody == null ? "空内容" : responseBody);
|
||||||
throw new RenException("声纹注销失败,请求处理失败");
|
throw new RenException(ErrorCode.VOICEPRINT_UNREGISTER_PROCESS_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +399,7 @@ public class AgentVoicePrintServiceImpl extends ServiceImpl<AgentVoicePrintDao,
|
|||||||
|
|
||||||
if (response.getStatusCode() != HttpStatus.OK) {
|
if (response.getStatusCode() != HttpStatus.OK) {
|
||||||
log.error("声纹识别请求失败,请求路径:{}", requestUrl);
|
log.error("声纹识别请求失败,请求路径:{}", requestUrl);
|
||||||
throw new RenException("声纹识别失败,请求不成功");
|
throw new RenException(ErrorCode.VOICEPRINT_IDENTIFY_REQUEST_ERROR);
|
||||||
}
|
}
|
||||||
// 检查响应内容
|
// 检查响应内容
|
||||||
String responseBody = response.getBody();
|
String responseBody = response.getBody();
|
||||||
|
|||||||
+2
-2
@@ -70,7 +70,7 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
// 查询默认智能体
|
// 查询默认智能体
|
||||||
AgentTemplateEntity agent = agentTemplateService.getDefaultTemplate();
|
AgentTemplateEntity agent = agentTemplateService.getDefaultTemplate();
|
||||||
if (agent == null) {
|
if (agent == null) {
|
||||||
throw new RenException("默认智能体未找到");
|
throw new RenException(ErrorCode.DEFAULT_AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建模块配置
|
// 构建模块配置
|
||||||
@@ -113,7 +113,7 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
// 获取智能体信息
|
// 获取智能体信息
|
||||||
AgentEntity agent = agentService.getAgentById(device.getAgentId());
|
AgentEntity agent = agentService.getAgentById(device.getAgentId());
|
||||||
if (agent == null) {
|
if (agent == null) {
|
||||||
throw new RenException("智能体未找到");
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
// 获取音色信息
|
// 获取音色信息
|
||||||
String voice = null;
|
String voice = null;
|
||||||
|
|||||||
+162
-4
@@ -5,6 +5,10 @@ import java.util.List;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -12,32 +16,45 @@ import org.springframework.web.bind.annotation.PutMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.user.UserDetail;
|
import xiaozhi.common.user.UserDetail;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceRegisterDTO;
|
import xiaozhi.modules.device.dto.DeviceRegisterDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceUpdateDTO;
|
import xiaozhi.modules.device.dto.DeviceUpdateDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
@Tag(name = "设备管理")
|
@Tag(name = "设备管理")
|
||||||
@AllArgsConstructor
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/device")
|
@RequestMapping("/device")
|
||||||
public class DeviceController {
|
public class DeviceController {
|
||||||
private final DeviceService deviceService;
|
private final DeviceService deviceService;
|
||||||
|
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
|
private final SysParamsService sysParamsService;
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public DeviceController(DeviceService deviceService, RedisUtils redisUtils, SysParamsService sysParamsService,
|
||||||
|
RestTemplate restTemplate, ObjectMapper objectMapper) {
|
||||||
|
this.deviceService = deviceService;
|
||||||
|
this.redisUtils = redisUtils;
|
||||||
|
this.sysParamsService = sysParamsService;
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/bind/{agentId}/{deviceCode}")
|
@PostMapping("/bind/{agentId}/{deviceCode}")
|
||||||
@Operation(summary = "绑定设备")
|
@Operation(summary = "绑定设备")
|
||||||
@@ -75,6 +92,88 @@ public class DeviceController {
|
|||||||
return new Result<List<DeviceEntity>>().ok(devices);
|
return new Result<List<DeviceEntity>>().ok(devices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/bind/{agentId}")
|
||||||
|
@Operation(summary = "转发POST请求到MQTT网关")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<String> 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<DeviceEntity> devices = deviceService.getUserDevices(user.getId(), agentId);
|
||||||
|
|
||||||
|
// 构建deviceIds数组
|
||||||
|
java.util.List<String> deviceIds = new java.util.ArrayList<>();
|
||||||
|
for (DeviceEntity device : devices) {
|
||||||
|
String macAddress = device.getMacAddress() != null ? device.getMacAddress() : "unknown";
|
||||||
|
String groupId = device.getBoard() != null ? device.getBoard() : "GID_default";
|
||||||
|
|
||||||
|
// 替换冒号为下划线
|
||||||
|
groupId = groupId.replace(":", "_");
|
||||||
|
macAddress = macAddress.replace(":", "_");
|
||||||
|
|
||||||
|
// 构建mqtt客户端ID格式:groupId@@@macAddress@@@macAddress
|
||||||
|
String mqttClientId = groupId + "@@@" + macAddress + "@@@" + macAddress;
|
||||||
|
deviceIds.add(mqttClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整的URL
|
||||||
|
String url = "http://" + mqttGatewayUrl + "/api/devices/status";
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.set("Content-Type", "application/json");
|
||||||
|
|
||||||
|
// 生成Bearer令牌
|
||||||
|
String token = generateBearerToken();
|
||||||
|
if (token == null) {
|
||||||
|
return new Result<String>().error("令牌生成失败");
|
||||||
|
}
|
||||||
|
headers.set("Authorization", "Bearer " + token);
|
||||||
|
|
||||||
|
// 构建请求体JSON
|
||||||
|
String jsonBody = "{\"clientIds\":" + objectMapper.writeValueAsString(deviceIds) + "}";
|
||||||
|
HttpEntity<String> requestEntity = new HttpEntity<>(jsonBody, headers);
|
||||||
|
|
||||||
|
// 发送POST请求
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
|
||||||
|
|
||||||
|
// 返回响应
|
||||||
|
return new Result<String>().ok(response.getBody());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new Result<String>().error("转发请求失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateBearerToken() {
|
||||||
|
try {
|
||||||
|
// 获取当前日期,格式为yyyy-MM-dd
|
||||||
|
String dateStr = java.time.LocalDate.now()
|
||||||
|
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||||
|
|
||||||
|
// 获取MQTT签名密钥
|
||||||
|
String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", false);
|
||||||
|
if (StringUtils.isBlank(signatureKey)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将日期字符串与MQTT_SIGNATURE_KEY连接
|
||||||
|
String tokenContent = dateStr + signatureKey;
|
||||||
|
|
||||||
|
// 对连接后的字符串进行SHA256哈希计算
|
||||||
|
String token = org.apache.commons.codec.digest.DigestUtils.sha256Hex(tokenContent);
|
||||||
|
|
||||||
|
return token;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/unbind")
|
@PostMapping("/unbind")
|
||||||
@Operation(summary = "解绑设备")
|
@Operation(summary = "解绑设备")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
@@ -109,4 +208,63 @@ public class DeviceController {
|
|||||||
deviceService.manualAddDevice(user.getId(), dto);
|
deviceService.manualAddDevice(user.getId(), dto);
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/commands/{deviceId}")
|
||||||
|
@Operation(summary = "发送设备指令")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<String> 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<String>().error("MQTT网关地址未配置");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整的URL
|
||||||
|
// 获取设备信息以构建mqttClientId
|
||||||
|
DeviceEntity deviceById = deviceService.selectById(deviceId);
|
||||||
|
|
||||||
|
if (!deviceById.getUserId().equals(SecurityUser.getUser().getId())) {
|
||||||
|
return new Result<String>().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<String>().error("MQTT签名密钥未配置");
|
||||||
|
}
|
||||||
|
String tokenContent = dateStr + signatureKey;
|
||||||
|
String token = org.apache.commons.codec.digest.DigestUtils.sha256Hex(tokenContent);
|
||||||
|
headers.set("Authorization", "Bearer " + token);
|
||||||
|
|
||||||
|
// 构建请求体
|
||||||
|
HttpEntity<String> requestEntity = new HttpEntity<>(command, headers);
|
||||||
|
|
||||||
|
// 发送POST请求
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
|
||||||
|
|
||||||
|
// 返回响应
|
||||||
|
return new Result<String>().ok(response.getBody());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new Result<String>().error("发送指令失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+11
-12
@@ -29,6 +29,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
@@ -83,27 +84,27 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
@Override
|
@Override
|
||||||
public Boolean deviceActivation(String agentId, String activationCode) {
|
public Boolean deviceActivation(String agentId, String activationCode) {
|
||||||
if (StringUtils.isBlank(activationCode)) {
|
if (StringUtils.isBlank(activationCode)) {
|
||||||
throw new RenException("激活码不能为空");
|
throw new RenException(ErrorCode.ACTIVATION_CODE_EMPTY);
|
||||||
}
|
}
|
||||||
String deviceKey = "ota:activation:code:" + activationCode;
|
String deviceKey = "ota:activation:code:" + activationCode;
|
||||||
Object cacheDeviceId = redisUtils.get(deviceKey);
|
Object cacheDeviceId = redisUtils.get(deviceKey);
|
||||||
if (cacheDeviceId == null) {
|
if (cacheDeviceId == null) {
|
||||||
throw new RenException("激活码错误");
|
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
||||||
}
|
}
|
||||||
String deviceId = (String) cacheDeviceId;
|
String deviceId = (String) cacheDeviceId;
|
||||||
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
||||||
String cacheDeviceKey = String.format("ota:activation:data:%s", safeDeviceId);
|
String cacheDeviceKey = String.format("ota:activation:data:%s", safeDeviceId);
|
||||||
Map<String, Object> cacheMap = (Map<String, Object>) redisUtils.get(cacheDeviceKey);
|
Map<String, Object> cacheMap = (Map<String, Object>) redisUtils.get(cacheDeviceKey);
|
||||||
if (cacheMap == null) {
|
if (cacheMap == null) {
|
||||||
throw new RenException("激活码错误");
|
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
||||||
}
|
}
|
||||||
String cachedCode = (String) cacheMap.get("activation_code");
|
String cachedCode = (String) cacheMap.get("activation_code");
|
||||||
if (!activationCode.equals(cachedCode)) {
|
if (!activationCode.equals(cachedCode)) {
|
||||||
throw new RenException("激活码错误");
|
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
||||||
}
|
}
|
||||||
// 检查设备有没有被激活
|
// 检查设备有没有被激活
|
||||||
if (selectById(deviceId) != null) {
|
if (selectById(deviceId) != null) {
|
||||||
throw new RenException("设备已激活");
|
throw new RenException(ErrorCode.DEVICE_ALREADY_ACTIVATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
String macAddress = (String) cacheMap.get("mac_address");
|
String macAddress = (String) cacheMap.get("mac_address");
|
||||||
@@ -111,7 +112,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
String appVersion = (String) cacheMap.get("app_version");
|
String appVersion = (String) cacheMap.get("app_version");
|
||||||
UserDetail user = SecurityUser.getUser();
|
UserDetail user = SecurityUser.getUser();
|
||||||
if (user.getId() == null) {
|
if (user.getId() == null) {
|
||||||
throw new RenException("用户未登录");
|
throw new RenException(ErrorCode.USER_NOT_LOGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
Date currentTime = new Date();
|
Date currentTime = new Date();
|
||||||
@@ -188,7 +189,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
try {
|
try {
|
||||||
String groupId = deviceById != null && deviceById.getBoard() != null ? deviceById.getBoard()
|
String groupId = deviceById != null && deviceById.getBoard() != null ? deviceById.getBoard()
|
||||||
: "GID_default";
|
: "GID_default";
|
||||||
DeviceReportRespDTO.MQTT mqtt = buildMqttConfig(macAddress, clientId, groupId);
|
DeviceReportRespDTO.MQTT mqtt = buildMqttConfig(macAddress, groupId);
|
||||||
if (mqtt != null) {
|
if (mqtt != null) {
|
||||||
mqtt.setEndpoint(mqttUdpConfig);
|
mqtt.setEndpoint(mqttUdpConfig);
|
||||||
response.setMqtt(mqtt);
|
response.setMqtt(mqtt);
|
||||||
@@ -441,7 +442,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
wrapper.eq("mac_address", dto.getMacAddress());
|
wrapper.eq("mac_address", dto.getMacAddress());
|
||||||
DeviceEntity exist = baseDao.selectOne(wrapper);
|
DeviceEntity exist = baseDao.selectOne(wrapper);
|
||||||
if (exist != null) {
|
if (exist != null) {
|
||||||
throw new RenException("该Mac地址已存在");
|
throw new RenException(ErrorCode.MAC_ADDRESS_ALREADY_EXISTS);
|
||||||
}
|
}
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
DeviceEntity entity = new DeviceEntity();
|
DeviceEntity entity = new DeviceEntity();
|
||||||
@@ -479,11 +480,10 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
* 构建MQTT配置信息
|
* 构建MQTT配置信息
|
||||||
*
|
*
|
||||||
* @param macAddress MAC地址
|
* @param macAddress MAC地址
|
||||||
* @param clientId 客户端ID (UUID)
|
|
||||||
* @param groupId 分组ID
|
* @param groupId 分组ID
|
||||||
* @return MQTT配置对象
|
* @return MQTT配置对象
|
||||||
*/
|
*/
|
||||||
private DeviceReportRespDTO.MQTT buildMqttConfig(String macAddress, String clientId, String groupId)
|
private DeviceReportRespDTO.MQTT buildMqttConfig(String macAddress, String groupId)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
// 从环境变量或系统参数获取签名密钥
|
// 从环境变量或系统参数获取签名密钥
|
||||||
String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", false);
|
String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", false);
|
||||||
@@ -495,8 +495,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
// 构建客户端ID格式:groupId@@@macAddress@@@uuid
|
// 构建客户端ID格式:groupId@@@macAddress@@@uuid
|
||||||
String groupIdSafeStr = groupId.replace(":", "_");
|
String groupIdSafeStr = groupId.replace(":", "_");
|
||||||
String deviceIdSafeStr = macAddress.replace(":", "_");
|
String deviceIdSafeStr = macAddress.replace(":", "_");
|
||||||
String clientIdSafeStr = clientId.replace(":", "_");
|
String mqttClientId = String.format("%s@@@%s@@@%s", groupIdSafeStr, deviceIdSafeStr, deviceIdSafeStr);
|
||||||
String mqttClientId = String.format("%s@@@%s@@@%s", groupIdSafeStr, deviceIdSafeStr, clientIdSafeStr);
|
|
||||||
|
|
||||||
// 构建用户数据(包含IP等信息)
|
// 构建用户数据(包含IP等信息)
|
||||||
Map<String, String> userData = new HashMap<>();
|
Map<String, String> userData = new HashMap<>();
|
||||||
|
|||||||
+8
-7
@@ -15,6 +15,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
@@ -94,7 +95,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
}
|
}
|
||||||
List<ModelProviderDTO> providerList = modelProviderService.getList(modelType, provideCode);
|
List<ModelProviderDTO> providerList = modelProviderService.getList(modelType, provideCode);
|
||||||
if (CollectionUtil.isEmpty(providerList)) {
|
if (CollectionUtil.isEmpty(providerList)) {
|
||||||
throw new RenException("供应器不存在");
|
throw new RenException(ErrorCode.MODEL_PROVIDER_NOT_EXIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 再保存供应器提供的模型
|
// 再保存供应器提供的模型
|
||||||
@@ -113,7 +114,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
}
|
}
|
||||||
List<ModelProviderDTO> providerList = modelProviderService.getList(modelType, provideCode);
|
List<ModelProviderDTO> providerList = modelProviderService.getList(modelType, provideCode);
|
||||||
if (CollectionUtil.isEmpty(providerList)) {
|
if (CollectionUtil.isEmpty(providerList)) {
|
||||||
throw new RenException("供应器不存在");
|
throw new RenException(ErrorCode.MODEL_PROVIDER_NOT_EXIST);
|
||||||
}
|
}
|
||||||
if (modelConfigBodyDTO.getConfigJson().containsKey("llm")) {
|
if (modelConfigBodyDTO.getConfigJson().containsKey("llm")) {
|
||||||
String llm = modelConfigBodyDTO.getConfigJson().get("llm").toString();
|
String llm = modelConfigBodyDTO.getConfigJson().get("llm").toString();
|
||||||
@@ -122,12 +123,12 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
String selectModelType = (modelConfigEntity == null || modelConfigEntity.getModelType() == null) ? null
|
String selectModelType = (modelConfigEntity == null || modelConfigEntity.getModelType() == null) ? null
|
||||||
: modelConfigEntity.getModelType().toUpperCase();
|
: modelConfigEntity.getModelType().toUpperCase();
|
||||||
if (modelConfigEntity == null || !"LLM".equals(selectModelType)) {
|
if (modelConfigEntity == null || !"LLM".equals(selectModelType)) {
|
||||||
throw new RenException("设置的LLM不存在");
|
throw new RenException(ErrorCode.LLM_NOT_EXIST);
|
||||||
}
|
}
|
||||||
String type = modelConfigEntity.getConfigJson().get("type").toString();
|
String type = modelConfigEntity.getConfigJson().get("type").toString();
|
||||||
// 如果查询大语言模型是openai或者ollama,意图识别选参数都可以
|
// 如果查询大语言模型是openai或者ollama,意图识别选参数都可以
|
||||||
if (!"openai".equals(type) && !"ollama".equals(type)) {
|
if (!"openai".equals(type) && !"ollama".equals(type)) {
|
||||||
throw new RenException("设置的LLM不是openai和ollama");
|
throw new RenException(ErrorCode.INVALID_LLM_TYPE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +147,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
// 查看是否是默认
|
// 查看是否是默认
|
||||||
ModelConfigEntity modelConfig = modelConfigDao.selectById(id);
|
ModelConfigEntity modelConfig = modelConfigDao.selectById(id);
|
||||||
if (modelConfig != null && modelConfig.getIsDefault() == 1) {
|
if (modelConfig != null && modelConfig.getIsDefault() == 1) {
|
||||||
throw new RenException("该模型为默认模型,请先设置其他模型为默认模型");
|
throw new RenException(ErrorCode.DEFAULT_MODEL_DELETE_ERROR);
|
||||||
}
|
}
|
||||||
// 验证是否有引用
|
// 验证是否有引用
|
||||||
checkAgentReference(id);
|
checkAgentReference(id);
|
||||||
@@ -180,7 +181,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
String agentNames = agents.stream()
|
String agentNames = agents.stream()
|
||||||
.map(AgentEntity::getAgentName)
|
.map(AgentEntity::getAgentName)
|
||||||
.collect(Collectors.joining("、"));
|
.collect(Collectors.joining("、"));
|
||||||
throw new RenException(String.format("该模型配置已被智能体[%s]引用,无法删除", agentNames));
|
throw new RenException(ErrorCode.MODEL_REFERENCED_BY_AGENT, agentNames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +199,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
.eq("model_type", "Intent")
|
.eq("model_type", "Intent")
|
||||||
.like("config_json", "%" + modelId + "%"));
|
.like("config_json", "%" + modelId + "%"));
|
||||||
if (!intentConfigs.isEmpty()) {
|
if (!intentConfigs.isEmpty()) {
|
||||||
throw new RenException("该LLM模型已被意图识别配置引用,无法删除");
|
throw new RenException(ErrorCode.LLM_REFERENCED_BY_INTENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -16,6 +16,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
import cn.hutool.json.JSONArray;
|
import cn.hutool.json.JSONArray;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
@@ -109,7 +110,7 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
|||||||
modelProviderDTO.setFields(modelProviderDTO.getFields());
|
modelProviderDTO.setFields(modelProviderDTO.getFields());
|
||||||
ModelProviderEntity entity = ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class);
|
ModelProviderEntity entity = ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class);
|
||||||
if (modelProviderDao.insert(entity) == 0) {
|
if (modelProviderDao.insert(entity) == 0) {
|
||||||
throw new RenException("新增数据失败");
|
throw new RenException(ErrorCode.ADD_DATA_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
|
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
|
||||||
@@ -122,7 +123,7 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
|||||||
modelProviderDTO.setUpdateDate(new Date());
|
modelProviderDTO.setUpdateDate(new Date());
|
||||||
if (modelProviderDao
|
if (modelProviderDao
|
||||||
.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
|
.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
|
||||||
throw new RenException("修改数据失败");
|
throw new RenException(ErrorCode.UPDATE_DATA_FAILED);
|
||||||
}
|
}
|
||||||
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
|
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
|
||||||
}
|
}
|
||||||
@@ -130,14 +131,14 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
|||||||
@Override
|
@Override
|
||||||
public void delete(String id) {
|
public void delete(String id) {
|
||||||
if (modelProviderDao.deleteById(id) == 0) {
|
if (modelProviderDao.deleteById(id) == 0) {
|
||||||
throw new RenException("删除数据失败");
|
throw new RenException(ErrorCode.DELETE_DATA_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(List<String> ids) {
|
public void delete(List<String> ids) {
|
||||||
if (modelProviderDao.deleteBatchIds(ids) == 0) {
|
if (modelProviderDao.deleteBatchIds(ids) == 0) {
|
||||||
throw new RenException("删除数据失败");
|
throw new RenException(ErrorCode.DELETE_DATA_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package xiaozhi.modules.security.config;
|
|||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
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.StringHttpMessageConverter;
|
||||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||||
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
|
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.CorsRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
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.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@@ -95,5 +100,47 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
|||||||
converter.setObjectMapper(mapper);
|
converter.setObjectMapper(mapper);
|
||||||
return converter;
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+14
-14
@@ -68,12 +68,12 @@ public class LoginController {
|
|||||||
// 验证图形验证码
|
// 验证图形验证码
|
||||||
boolean validate = captchaService.validate(dto.getCaptchaId(), dto.getCaptcha(), true);
|
boolean validate = captchaService.validate(dto.getCaptchaId(), dto.getCaptcha(), true);
|
||||||
if (!validate) {
|
if (!validate) {
|
||||||
throw new RenException("图形验证码错误");
|
throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR);
|
||||||
}
|
}
|
||||||
Boolean isMobileRegister = sysParamsService
|
Boolean isMobileRegister = sysParamsService
|
||||||
.getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class);
|
.getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class);
|
||||||
if (!isMobileRegister) {
|
if (!isMobileRegister) {
|
||||||
throw new RenException("没有开启手机注册,没法使用短信验证码功能");
|
throw new RenException(ErrorCode.MOBILE_REGISTER_DISABLED);
|
||||||
}
|
}
|
||||||
// 发送短信验证码
|
// 发送短信验证码
|
||||||
captchaService.sendSMSValidateCode(dto.getPhone());
|
captchaService.sendSMSValidateCode(dto.getPhone());
|
||||||
@@ -86,17 +86,17 @@ public class LoginController {
|
|||||||
// 验证是否正确输入验证码
|
// 验证是否正确输入验证码
|
||||||
boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true);
|
boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true);
|
||||||
if (!validate) {
|
if (!validate) {
|
||||||
throw new RenException("图形验证码错误,请重新获取");
|
throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR);
|
||||||
}
|
}
|
||||||
// 按照用户名获取用户
|
// 按照用户名获取用户
|
||||||
SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername());
|
SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername());
|
||||||
// 判断用户是否存在
|
// 判断用户是否存在
|
||||||
if (userDTO == null) {
|
if (userDTO == null) {
|
||||||
throw new RenException("请检测用户和密码是否输入错误");
|
throw new RenException(ErrorCode.ACCOUNT_PASSWORD_ERROR);
|
||||||
}
|
}
|
||||||
// 判断密码是否正确,不一样则进入if
|
// 判断密码是否正确,不一样则进入if
|
||||||
if (!PasswordUtils.matches(login.getPassword(), userDTO.getPassword())) {
|
if (!PasswordUtils.matches(login.getPassword(), userDTO.getPassword())) {
|
||||||
throw new RenException("请检测用户和密码是否输入错误");
|
throw new RenException(ErrorCode.ACCOUNT_PASSWORD_ERROR);
|
||||||
}
|
}
|
||||||
return sysUserTokenService.createToken(userDTO.getId());
|
return sysUserTokenService.createToken(userDTO.getId());
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ public class LoginController {
|
|||||||
@Operation(summary = "注册")
|
@Operation(summary = "注册")
|
||||||
public Result<Void> register(@RequestBody LoginDTO login) {
|
public Result<Void> register(@RequestBody LoginDTO login) {
|
||||||
if (!sysUserService.getAllowUserRegister()) {
|
if (!sysUserService.getAllowUserRegister()) {
|
||||||
throw new RenException("当前不允许普通用户注册");
|
throw new RenException(ErrorCode.USER_REGISTER_DISABLED);
|
||||||
}
|
}
|
||||||
// 是否开启手机注册
|
// 是否开启手机注册
|
||||||
Boolean isMobileRegister = sysParamsService
|
Boolean isMobileRegister = sysParamsService
|
||||||
@@ -115,25 +115,25 @@ public class LoginController {
|
|||||||
// 验证用户是否是手机号码
|
// 验证用户是否是手机号码
|
||||||
boolean validPhone = ValidatorUtils.isValidPhone(login.getUsername());
|
boolean validPhone = ValidatorUtils.isValidPhone(login.getUsername());
|
||||||
if (!validPhone) {
|
if (!validPhone) {
|
||||||
throw new RenException("用户名不是手机号码,请重新输入");
|
throw new RenException(ErrorCode.USERNAME_NOT_PHONE);
|
||||||
}
|
}
|
||||||
// 验证短信验证码是否正常
|
// 验证短信验证码是否正常
|
||||||
validate = captchaService.validateSMSValidateCode(login.getUsername(), login.getMobileCaptcha(), false);
|
validate = captchaService.validateSMSValidateCode(login.getUsername(), login.getMobileCaptcha(), false);
|
||||||
if (!validate) {
|
if (!validate) {
|
||||||
throw new RenException("手机验证码错误,请重新获取");
|
throw new RenException(ErrorCode.SMS_CODE_ERROR);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 验证是否正确输入验证码
|
// 验证是否正确输入验证码
|
||||||
validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true);
|
validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true);
|
||||||
if (!validate) {
|
if (!validate) {
|
||||||
throw new RenException("图形验证码错误,请重新获取");
|
throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按照用户名获取用户
|
// 按照用户名获取用户
|
||||||
SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername());
|
SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername());
|
||||||
if (userDTO != null) {
|
if (userDTO != null) {
|
||||||
throw new RenException("此手机号码已经注册过");
|
throw new RenException(ErrorCode.PHONE_ALREADY_REGISTERED);
|
||||||
}
|
}
|
||||||
userDTO = new SysUserDTO();
|
userDTO = new SysUserDTO();
|
||||||
userDTO.setUsername(login.getUsername());
|
userDTO.setUsername(login.getUsername());
|
||||||
@@ -168,26 +168,26 @@ public class LoginController {
|
|||||||
Boolean isMobileRegister = sysParamsService
|
Boolean isMobileRegister = sysParamsService
|
||||||
.getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class);
|
.getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class);
|
||||||
if (!isMobileRegister) {
|
if (!isMobileRegister) {
|
||||||
throw new RenException("没有开启手机注册,没法使用找回密码功能");
|
throw new RenException(ErrorCode.RETRIEVE_PASSWORD_DISABLED);
|
||||||
}
|
}
|
||||||
// 判断非空
|
// 判断非空
|
||||||
ValidatorUtils.validateEntity(dto);
|
ValidatorUtils.validateEntity(dto);
|
||||||
// 验证用户是否是手机号码
|
// 验证用户是否是手机号码
|
||||||
boolean validPhone = ValidatorUtils.isValidPhone(dto.getPhone());
|
boolean validPhone = ValidatorUtils.isValidPhone(dto.getPhone());
|
||||||
if (!validPhone) {
|
if (!validPhone) {
|
||||||
throw new RenException("输入的手机号码格式不正确");
|
throw new RenException(ErrorCode.PHONE_FORMAT_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按照用户名获取用户
|
// 按照用户名获取用户
|
||||||
SysUserDTO userDTO = sysUserService.getByUsername(dto.getPhone());
|
SysUserDTO userDTO = sysUserService.getByUsername(dto.getPhone());
|
||||||
if (userDTO == null) {
|
if (userDTO == null) {
|
||||||
throw new RenException("输入的手机号码未注册");
|
throw new RenException(ErrorCode.PHONE_NOT_REGISTERED);
|
||||||
}
|
}
|
||||||
// 验证短信验证码是否正常
|
// 验证短信验证码是否正常
|
||||||
boolean validate = captchaService.validateSMSValidateCode(dto.getPhone(), dto.getCode(), false);
|
boolean validate = captchaService.validateSMSValidateCode(dto.getPhone(), dto.getCode(), false);
|
||||||
// 判断是否通过验证
|
// 判断是否通过验证
|
||||||
if (!validate) {
|
if (!validate) {
|
||||||
throw new RenException("输入的手机验证码错误");
|
throw new RenException(ErrorCode.SMS_CODE_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
sysUserService.changePasswordDirectly(userDTO.getId(), dto.getPassword());
|
sysUserService.changePasswordDirectly(userDTO.getId(), dto.getPassword());
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import xiaozhi.common.constant.Constant;
|
|||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.utils.HttpContextUtils;
|
import xiaozhi.common.utils.HttpContextUtils;
|
||||||
import xiaozhi.common.utils.JsonUtils;
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
import xiaozhi.common.utils.MessageUtils;
|
||||||
import xiaozhi.common.utils.Result;
|
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-Credentials", "true");
|
||||||
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
|
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
|
||||||
try {
|
try {
|
||||||
Throwable throwable = e.getCause() == null ? e : e.getCause();
|
// 使用国际化消息替代直接使用异常消息
|
||||||
Result<Void> r = new Result<Void>().error(ErrorCode.UNAUTHORIZED, throwable.getMessage());
|
Result<Void> r = new Result<Void>().error(ErrorCode.UNAUTHORIZED);
|
||||||
|
|
||||||
String json = JsonUtils.toJsonString(r);
|
String json = JsonUtils.toJsonString(r);
|
||||||
httpResponse.getWriter().print(json);
|
httpResponse.getWriter().print(json);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package xiaozhi.modules.security.oauth2;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +39,7 @@ public class TokenGenerator {
|
|||||||
byte[] messageDigest = algorithm.digest();
|
byte[] messageDigest = algorithm.digest();
|
||||||
return toHexString(messageDigest);
|
return toHexString(messageDigest);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RenException("token invalid", e);
|
throw new RenException(ErrorCode.TOKEN_GENERATE_ERROR, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -16,6 +16,7 @@ import com.wf.captcha.base.Captcha;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
@@ -88,7 +89,7 @@ public class CaptchaServiceImpl implements CaptchaService {
|
|||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
long timeDiff = currentTime - lastSendTimeLong;
|
long timeDiff = currentTime - lastSendTimeLong;
|
||||||
if (timeDiff < 60000) {
|
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) {
|
if (todayCount >= maxSendCount) {
|
||||||
throw new RenException("今日发送次数已达上限");
|
throw new RenException(ErrorCode.TODAY_SMS_LIMIT_REACHED);
|
||||||
}
|
}
|
||||||
|
|
||||||
String key = RedisKeys.getSMSValidateCodeKey(phone);
|
String key = RedisKeys.getSMSValidateCodeKey(phone);
|
||||||
|
|||||||
+3
-2
@@ -9,6 +9,7 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
@@ -45,7 +46,7 @@ public class ALiYunSmsService implements SmsService {
|
|||||||
redisUtils.delete(todayCountKey);
|
redisUtils.delete(todayCountKey);
|
||||||
// 错误 message
|
// 错误 message
|
||||||
log.error(e.getMessage());
|
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){
|
}catch (Exception e){
|
||||||
// 错误 message
|
// 错误 message
|
||||||
log.error(e.getMessage());
|
log.error(e.getMessage());
|
||||||
throw new RenException("短信连接建立失败");
|
throw new RenException(ErrorCode.SMS_CONNECTION_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -22,6 +22,7 @@ import jakarta.validation.Valid;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.annotation.LogOperation;
|
import xiaozhi.common.annotation.LogOperation;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.modules.sys.dto.EmitSeverActionDTO;
|
import xiaozhi.modules.sys.dto.EmitSeverActionDTO;
|
||||||
@@ -64,17 +65,17 @@ public class ServerSideManageController {
|
|||||||
@RequiresPermissions("sys:role:superAdmin")
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
public Result<Boolean> emitServerAction(@RequestBody @Valid EmitSeverActionDTO emitSeverActionDTO) {
|
public Result<Boolean> emitServerAction(@RequestBody @Valid EmitSeverActionDTO emitSeverActionDTO) {
|
||||||
if (emitSeverActionDTO.getAction() == null) {
|
if (emitSeverActionDTO.getAction() == null) {
|
||||||
throw new RenException("无效服务端操作");
|
throw new RenException(ErrorCode.INVALID_SERVER_ACTION);
|
||||||
}
|
}
|
||||||
String wsText = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true);
|
String wsText = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true);
|
||||||
if (StringUtils.isBlank(wsText)) {
|
if (StringUtils.isBlank(wsText)) {
|
||||||
throw new RenException("未配置服务端WebSocket地址");
|
throw new RenException(ErrorCode.SERVER_WEBSOCKET_NOT_CONFIGURED);
|
||||||
}
|
}
|
||||||
String targetWs = emitSeverActionDTO.getTargetWs();
|
String targetWs = emitSeverActionDTO.getTargetWs();
|
||||||
String[] wsList = wsText.split(";");
|
String[] wsList = wsText.split(";");
|
||||||
// 找到需要发起的
|
// 找到需要发起的
|
||||||
if (StringUtils.isBlank(targetWs) || !Arrays.asList(wsList).contains(targetWs)) {
|
if (StringUtils.isBlank(targetWs) || !Arrays.asList(wsList).contains(targetWs)) {
|
||||||
throw new RenException("目标WebSocket地址不存在");
|
throw new RenException(ErrorCode.TARGET_WEBSOCKET_NOT_EXIST);
|
||||||
}
|
}
|
||||||
return new Result<Boolean>().ok(emitServerActionByWs(targetWs, emitSeverActionDTO.getAction()));
|
return new Result<Boolean>().ok(emitServerActionByWs(targetWs, emitSeverActionDTO.getAction()));
|
||||||
}
|
}
|
||||||
@@ -114,7 +115,7 @@ public class ServerSideManageController {
|
|||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 捕获全部错误,由全局异常处理器返回
|
// 捕获全部错误,由全局异常处理器返回
|
||||||
throw new RenException("WebSocket连接失败或连接超时");
|
throw new RenException(ErrorCode.WEB_SOCKET_CONNECT_FAILED);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-25
@@ -25,6 +25,7 @@ import lombok.AllArgsConstructor;
|
|||||||
import xiaozhi.common.annotation.LogOperation;
|
import xiaozhi.common.annotation.LogOperation;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.common.validator.AssertUtils;
|
import xiaozhi.common.validator.AssertUtils;
|
||||||
@@ -107,9 +108,12 @@ public class SysParamsController {
|
|||||||
// 验证MCP地址
|
// 验证MCP地址
|
||||||
validateMcpUrl(dto.getParamCode(), dto.getParamValue());
|
validateMcpUrl(dto.getParamCode(), dto.getParamValue());
|
||||||
|
|
||||||
//
|
// 验证声纹地址
|
||||||
validateVoicePrint(dto.getParamCode(), dto.getParamValue());
|
validateVoicePrint(dto.getParamCode(), dto.getParamValue());
|
||||||
|
|
||||||
|
// 校验mqtt密钥长度
|
||||||
|
validateMqttSecretLength(dto.getParamCode(), dto.getParamValue());
|
||||||
|
|
||||||
sysParamsService.update(dto);
|
sysParamsService.update(dto);
|
||||||
configService.getConfig(false);
|
configService.getConfig(false);
|
||||||
return new Result<Void>();
|
return new Result<Void>();
|
||||||
@@ -127,23 +131,23 @@ public class SysParamsController {
|
|||||||
}
|
}
|
||||||
String[] wsUrls = urls.split("\\;");
|
String[] wsUrls = urls.split("\\;");
|
||||||
if (wsUrls.length == 0) {
|
if (wsUrls.length == 0) {
|
||||||
throw new RenException("WebSocket地址列表不能为空");
|
throw new RenException(ErrorCode.WEBSOCKET_URLS_EMPTY);
|
||||||
}
|
}
|
||||||
for (String url : wsUrls) {
|
for (String url : wsUrls) {
|
||||||
if (StringUtils.isNotBlank(url)) {
|
if (StringUtils.isNotBlank(url)) {
|
||||||
// 检查是否包含localhost或127.0.0.1
|
// 检查是否包含localhost或127.0.0.1
|
||||||
if (url.contains("localhost") || url.contains("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地址格式
|
// 验证WebSocket地址格式
|
||||||
if (!WebSocketValidator.validateUrlFormat(url)) {
|
if (!WebSocketValidator.validateUrlFormat(url)) {
|
||||||
throw new RenException("WebSocket地址格式不正确: " + url);
|
throw new RenException(ErrorCode.WEBSOCKET_URL_FORMAT_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 测试WebSocket连接
|
// 测试WebSocket连接
|
||||||
if (!WebSocketValidator.testConnection(url)) {
|
if (!WebSocketValidator.testConnection(url)) {
|
||||||
throw new RenException("WebSocket连接测试失败: " + url);
|
throw new RenException(ErrorCode.WEBSOCKET_CONNECTION_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,35 +174,35 @@ public class SysParamsController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (StringUtils.isBlank(url) || url.equals("null")) {
|
if (StringUtils.isBlank(url) || url.equals("null")) {
|
||||||
throw new RenException("OTA地址不能为空");
|
throw new RenException(ErrorCode.OTA_URL_EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否包含localhost或127.0.0.1
|
// 检查是否包含localhost或127.0.0.1
|
||||||
if (url.contains("localhost") || url.contains("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格式
|
// 验证URL格式
|
||||||
if (!url.toLowerCase().startsWith("http")) {
|
if (!url.toLowerCase().startsWith("http")) {
|
||||||
throw new RenException("OTA地址必须以http或https开头");
|
throw new RenException(ErrorCode.OTA_URL_PROTOCOL_ERROR);
|
||||||
}
|
}
|
||||||
if (!url.endsWith("/ota/")) {
|
if (!url.endsWith("/ota/")) {
|
||||||
throw new RenException("OTA地址必须以/ota/结尾");
|
throw new RenException(ErrorCode.OTA_URL_FORMAT_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 发送GET请求
|
// 发送GET请求
|
||||||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
||||||
if (response.getStatusCode() != HttpStatus.OK) {
|
if (response.getStatusCode() != HttpStatus.OK) {
|
||||||
throw new RenException("OTA接口访问失败,状态码:" + response.getStatusCode());
|
throw new RenException(ErrorCode.OTA_INTERFACE_ACCESS_FAILED);
|
||||||
}
|
}
|
||||||
// 检查响应内容是否包含OTA相关信息
|
// 检查响应内容是否包含OTA相关信息
|
||||||
String body = response.getBody();
|
String body = response.getBody();
|
||||||
if (body == null || !body.contains("OTA")) {
|
if (body == null || !body.contains("OTA")) {
|
||||||
throw new RenException("OTA接口返回内容格式不正确,可能不是一个真实的OTA接口");
|
throw new RenException(ErrorCode.OTA_INTERFACE_FORMAT_ERROR);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RenException("OTA接口验证失败:" + e.getMessage());
|
throw new RenException(ErrorCode.OTA_INTERFACE_VALIDATION_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,61 +211,86 @@ public class SysParamsController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (StringUtils.isBlank(url) || url.equals("null")) {
|
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")) {
|
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")) {
|
if (!url.toLowerCase().contains("key")) {
|
||||||
throw new RenException("不是正确的MCP地址");
|
throw new RenException(ErrorCode.MCP_URL_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 发送GET请求
|
// 发送GET请求
|
||||||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
||||||
if (response.getStatusCode() != HttpStatus.OK) {
|
if (response.getStatusCode() != HttpStatus.OK) {
|
||||||
throw new RenException("MCP接口访问失败,状态码:" + response.getStatusCode());
|
throw new RenException(ErrorCode.MCP_INTERFACE_ACCESS_FAILED);
|
||||||
}
|
}
|
||||||
// 检查响应内容是否包含mcp相关信息
|
// 检查响应内容是否包含mcp相关信息
|
||||||
String body = response.getBody();
|
String body = response.getBody();
|
||||||
if (body == null || !body.contains("success")) {
|
if (body == null || !body.contains("success")) {
|
||||||
throw new RenException("MCP接口返回内容格式不正确,可能不是一个真实的MCP接口");
|
throw new RenException(ErrorCode.MCP_INTERFACE_FORMAT_ERROR);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RenException("MCP接口验证失败:" + e.getMessage());
|
throw new RenException(ErrorCode.MCP_INTERFACE_VALIDATION_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证声纹接口地址是否正常
|
// 验证声纹接口地址是否正常
|
||||||
private void validateVoicePrint(String paramCode, String url) {
|
private void validateVoicePrint(String paramCode, String url) {
|
||||||
if (!paramCode.equals(Constant.SERVER_VOICE_PRINT)) {
|
if (!paramCode.equals(Constant.SERVER_VOICE_PRINT)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (StringUtils.isBlank(url) || url.equals("null")) {
|
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")) {
|
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")) {
|
if (!url.toLowerCase().contains("key")) {
|
||||||
throw new RenException("不是正确的声纹接口地址");
|
throw new RenException(ErrorCode.VOICEPRINT_URL_INVALID);
|
||||||
}
|
}
|
||||||
// 验证URL格式
|
// 验证URL格式
|
||||||
if (!url.toLowerCase().startsWith("http")) {
|
if (!url.toLowerCase().startsWith("http")) {
|
||||||
throw new RenException("声纹接口地址必须以http或https开头");
|
throw new RenException(ErrorCode.VOICEPRINT_URL_PROTOCOL_ERROR);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// 发送GET请求
|
// 发送GET请求
|
||||||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
||||||
if (response.getStatusCode() != HttpStatus.OK) {
|
if (response.getStatusCode() != HttpStatus.OK) {
|
||||||
throw new RenException("声纹接口访问失败,状态码:" + response.getStatusCode());
|
throw new RenException(ErrorCode.VOICEPRINT_INTERFACE_ACCESS_FAILED);
|
||||||
}
|
}
|
||||||
// 检查响应内容
|
// 检查响应内容
|
||||||
String body = response.getBody();
|
String body = response.getBody();
|
||||||
if (body == null || !body.contains("healthy")) {
|
if (body == null || !body.contains("healthy")) {
|
||||||
throw new RenException("声纹接口返回内容格式不正确,可能不是一个真实的MCP接口");
|
throw new RenException(ErrorCode.VOICEPRINT_INTERFACE_FORMAT_ERROR);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RenException("声纹接口验证失败:" + e.getMessage());
|
throw new RenException(ErrorCode.VOICEPRINT_INTERFACE_VALIDATION_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验mqtt密钥长度和复杂度
|
||||||
|
private void validateMqttSecretLength(String paramCode, String secret) {
|
||||||
|
if (!paramCode.equals(Constant.SERVER_MQTT_SECRET)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(secret) || secret.equals("null")) {
|
||||||
|
throw new RenException(ErrorCode.MQTT_SECRET_EMPTY);
|
||||||
|
}
|
||||||
|
if (secret.length() < 8) {
|
||||||
|
throw new RenException(ErrorCode.MQTT_SECRET_LENGTH_INSECURE);
|
||||||
|
}
|
||||||
|
// 检查是否同时包含大小写字母
|
||||||
|
if (!secret.matches(".*[a-z].*") || !secret.matches(".*[A-Z].*")) {
|
||||||
|
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(ErrorCode.MQTT_SECRET_WEAK_PASSWORD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -17,6 +17,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
@@ -153,7 +154,7 @@ public class SysDictDataServiceImpl extends BaseServiceImpl<SysDictDataDao, SysD
|
|||||||
}
|
}
|
||||||
boolean exists = baseDao.exists(queryWrapper);
|
boolean exists = baseDao.exists(queryWrapper);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
throw new RenException("字典标签重复");
|
throw new RenException(ErrorCode.DICT_LABEL_DUPLICATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -18,6 +18,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
@@ -66,7 +67,7 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeDao, SysD
|
|||||||
public SysDictTypeVO get(Long id) {
|
public SysDictTypeVO get(Long id) {
|
||||||
SysDictTypeEntity entity = baseDao.selectById(id);
|
SysDictTypeEntity entity = baseDao.selectById(id);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw new RenException("字典类型不存在");
|
throw new RenException(ErrorCode.DICT_TYPE_NOT_EXIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConvertUtils.sourceToTarget(entity, SysDictTypeVO.class);
|
return ConvertUtils.sourceToTarget(entity, SysDictTypeVO.class);
|
||||||
@@ -147,7 +148,7 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeDao, SysD
|
|||||||
}
|
}
|
||||||
boolean exists = baseDao.exists(queryWrapper);
|
boolean exists = baseDao.exists(queryWrapper);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
throw new RenException("字典类型编码重复");
|
throw new RenException(ErrorCode.DICT_TYPE_DUPLICATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -115,7 +115,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
|
|||||||
|
|
||||||
// 判断旧密码是否正确
|
// 判断旧密码是否正确
|
||||||
if (!PasswordUtils.matches(passwordDTO.getPassword(), sysUserEntity.getPassword())) {
|
if (!PasswordUtils.matches(passwordDTO.getPassword(), sysUserEntity.getPassword())) {
|
||||||
throw new RenException("旧密码输入错误");
|
throw new RenException(ErrorCode.OLD_PASSWORD_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新密码强度
|
// 新密码强度
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
delete from `sys_params` where param_code = 'server.mqtt_manager_api';
|
||||||
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark)
|
||||||
|
VALUES (119, 'server.mqtt_manager_api', 'null', 'string', 1, 'MQTT网关管理API的地址');
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
-- 添加阿里百炼流式TTS 供应器
|
||||||
|
delete from `ai_model_provider` where id = 'SYSTEM_TTS_AliBLStreamTTS';
|
||||||
|
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
|
||||||
|
('SYSTEM_TTS_AliBLStreamTTS', 'TTS', 'alibl_stream', '阿里百炼流式语音合成', '[{"key":"api_key","label":"API密钥","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"model","label":"模型","type":"string"},{"key":"voice","label":"音色","type":"string"},{"key":"format","label":"音频格式","type":"string"},{"key":"sample_rate","label":"采样率","type":"number"},{"key": "volume", "type": "number", "label": "音量"},{"key": "rate", "type": "number", "label": "语速"},{"key": "pitch", "type": "number", "label": "音调"}]', 19, 1, NOW(), 1, NOW());
|
||||||
|
|
||||||
|
-- 添加阿里百炼流式TTS模型配置
|
||||||
|
delete from `ai_model_config` where id = 'TTS_AliBLStreamTTS';
|
||||||
|
INSERT INTO `ai_model_config` VALUES ('TTS_AliBLStreamTTS', 'TTS', 'AliBLStreamTTS', '阿里百炼流式语音合成', 0, 1, '{\"type\": \"alibl_stream\", \"appkey\": \"\", \"output_dir\": \"tmp/\", \"model\": \"cosyvoice-v2\", \"voice\": \"longcheng_v2\", \"format\": \"pcm\", \"sample_rate\": 24000, \"volume\": 50, \"rate\": 1, \"pitch\": 1}', NULL, NULL, 22, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
-- 更新阿里百炼流式TTS配置说明
|
||||||
|
UPDATE `ai_model_config` SET
|
||||||
|
`doc_link` = 'https://bailian.console.aliyun.com/?apiKey=1#/api-key',
|
||||||
|
`remark` = '阿里百炼流式TTS说明:
|
||||||
|
1. 访问 https://bailian.console.aliyun.com/?apiKey=1#/api-key 创建项目并获取appkey
|
||||||
|
2. 支持实时流式合成,具有较低的延迟
|
||||||
|
3. 支持多种音色设置和音频参数调整
|
||||||
|
4. 支持CosyVoice-V3大模型音色,价格实惠(0.4元/万字符)
|
||||||
|
5. 支持实时调节音量、语速、音调等参数
|
||||||
|
6. 如果需要使用CosyVoice-V3模型和一些限制类型的音色,需要联系阿里百炼客服申请
|
||||||
|
' WHERE `id` = 'TTS_AliBLStreamTTS';
|
||||||
|
|
||||||
|
-- 添加阿里百炼流式TTS音色
|
||||||
|
delete from `ai_tts_voice` where tts_model_id = 'TTS_AliBLStreamTTS';
|
||||||
|
|
||||||
|
-- 语音助手
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0001', 'TTS_AliBLStreamTTS', '龙小淳-知性积极女', 'longxiaochun_v2', '中文及中英文混合', NULL, NULL, NULL, NULL, 1, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0002', 'TTS_AliBLStreamTTS', '龙小夏-沉稳权威女', 'longxiaoxia_v2', '中文及中英文混合', NULL, NULL, NULL, NULL, 2, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
-- 直播带货
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0003', 'TTS_AliBLStreamTTS', '龙安燃-活泼质感女', 'longanran', '中文及中英文混合', NULL, NULL, NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0004', 'TTS_AliBLStreamTTS', '龙安宣-经典直播女', 'longanxuan', '中文及中英文混合', NULL, NULL, NULL, NULL, 4, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
-- 社交陪伴
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0005', 'TTS_AliBLStreamTTS', '龙寒-温暖痴情男', 'longhan_v2', '中文及中英文混合', NULL, NULL, NULL, NULL, 5, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0006', 'TTS_AliBLStreamTTS', '龙颜-温暖春风女', 'longyan_v2', '中文及中英文混合', NULL, NULL, NULL, NULL, 6, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0007', 'TTS_AliBLStreamTTS', '龙菲菲-甜美矫情女', 'longfeifei_v2', '中文及中英文混合', NULL, NULL, NULL, NULL, 7, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
-- 方言
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0008', 'TTS_AliBLStreamTTS', '龙老铁-东北直率男', 'longlaotie_v2', '中文(东北)及中英文混合', NULL, NULL, NULL, NULL, 8, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0009', 'TTS_AliBLStreamTTS', '龙嘉怡-知性粤语女', 'longjiayi_v2', '中文(粤语)及中英文混合', NULL, NULL, NULL, NULL, 9, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
-- 童声
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0010', 'TTS_AliBLStreamTTS', '龙杰力豆-阳光顽皮男', 'longjielidou_v2', '中文及中英文混合', NULL, NULL, NULL, NULL, 10, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0011', 'TTS_AliBLStreamTTS', '龙铃-稚气呆板女', 'longling_v2', '中文及中英文混合', NULL, NULL, NULL, NULL, 11, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
-- 诗歌朗诵
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0012', 'TTS_AliBLStreamTTS', '李白-古代诗仙男', 'libai_v2', '中文及中英文混合', NULL, NULL, NULL, NULL, 12, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
-- 出海营销
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0013', 'TTS_AliBLStreamTTS', 'loongeva-知性英文女', 'loongeva_v2', '英式英文', NULL, NULL, NULL, NULL, 13, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0014', 'TTS_AliBLStreamTTS', 'loongbrian-沉稳英文男', 'loongbrian_v2', '英式英文', NULL, NULL, NULL, NULL, 14, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0015', 'TTS_AliBLStreamTTS', 'loongkyong-韩语女', 'loongkyong_v2', '韩语', NULL, NULL, NULL, NULL, 15, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0016', 'TTS_AliBLStreamTTS', 'loongtomoka-日语女', 'loongtomoka_v2', '日语', NULL, NULL, NULL, NULL, 16, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_AliBLStreamTTS_0017', 'TTS_AliBLStreamTTS', 'loongtomoya-日语男', 'loongtomoya_v2', '日语', NULL, NULL, NULL, NULL, 17, NULL, NULL, NULL, NULL);
|
||||||
@@ -345,3 +345,17 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202509080922.sql
|
path: classpath:db/changelog/202509080922.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202509161610
|
||||||
|
author: fyb
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202509161610.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202509161701
|
||||||
|
author: RanChen
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202509161701.sql
|
||||||
|
|||||||
@@ -1,47 +1,137 @@
|
|||||||
#Default
|
#简体中文
|
||||||
500=\u670D\u52A1\u5668\u5185\u90E8\u5F02\u5E38
|
500=服务器内部异常
|
||||||
401=\u672A\u6388\u6743
|
401=未授权
|
||||||
403=\u62D2\u7EDD\u8BBF\u95EE\uFF0C\u6CA1\u6709\u6743\u9650
|
403=拒绝访问,没有权限
|
||||||
10001={0}\u4E0D\u80FD\u4E3A\u7A7A
|
|
||||||
10002=\u6570\u636E\u5E93\u4E2D\u5DF2\u5B58\u5728\u8BE5\u8BB0\u5F55
|
|
||||||
10003=\u83B7\u53D6\u53C2\u6570\u5931\u8D25
|
|
||||||
10004=\u8D26\u53F7\u6216\u5BC6\u7801\u9519\u8BEF
|
|
||||||
10005=\u8D26\u53F7\u5DF2\u88AB\u505C\u7528
|
|
||||||
10006=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
|
|
||||||
10007=\u9A8C\u8BC1\u7801\u4E0D\u6B63\u786E
|
|
||||||
10008=\u624B\u673A\u53F7\u7801\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
|
|
||||||
10009=\u539F\u5BC6\u7801\u4E0D\u6B63\u786E
|
|
||||||
10010=\u8D26\u53F7\u6216\u5BC6\u7801\u4E0D\u6B63\u786E,\u60A8\u8FD8\u6709\u53EF\u4EE5\u5C1D\u8BD5{0}\u6B21
|
|
||||||
10011=\u4E0A\u7EA7\u90E8\u95E8\u9009\u62E9\u9519\u8BEF
|
|
||||||
10012=\u4E0A\u7EA7\u83DC\u5355\u4E0D\u80FD\u4E3A\u81EA\u8EAB
|
|
||||||
10013=\u6570\u636E\u6743\u9650\u63A5\u53E3\uFF0C\u53EA\u80FD\u662FMap\u7C7B\u578B\u53C2\u6570
|
|
||||||
10014=\u8BF7\u5148\u5220\u9664\u4E0B\u7EA7\u90E8\u95E8
|
|
||||||
10015=\u8BF7\u5148\u5220\u9664\u90E8\u95E8\u4E0B\u7684\u7528\u6237
|
|
||||||
10016=\u90E8\u7F72\u5931\u8D25\uFF0C\u6CA1\u6709\u6D41\u7A0B
|
|
||||||
10017=\u6A21\u578B\u56FE\u4E0D\u6B63\u786E\uFF0C\u8BF7\u68C0\u67E5
|
|
||||||
10018=\u5BFC\u51FA\u5931\u8D25\uFF0C\u6A21\u578BID\u4E3A{0}
|
|
||||||
10019=\u8BF7\u4E0A\u4F20\u6587\u4EF6
|
|
||||||
10020=token\u4E0D\u80FD\u4E3A\u7A7A
|
|
||||||
10021=token\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55
|
|
||||||
10022=\u8D26\u53F7\u5DF2\u88AB\u9501\u5B9A
|
|
||||||
10023=\u8BF7\u4E0A\u4F20zip\u3001bar\u3001bpmn\u3001bpmn20.xml\u683C\u5F0F\u6587\u4EF6
|
|
||||||
10024=\u4E0A\u4F20\u6587\u4EF6\u5931\u8D25{0}
|
|
||||||
10025=\u53D1\u9001\u77ED\u4FE1\u5931\u8D25{0}
|
|
||||||
10026=\u90AE\u4EF6\u6A21\u677F\u4E0D\u5B58\u5728
|
|
||||||
10027=Redis\u670D\u52A1\u5F02\u5E38
|
|
||||||
10028=\u5B9A\u65F6\u4EFB\u52A1\u5931\u8D25
|
|
||||||
10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26
|
|
||||||
10030=\u5BC6\u7801\u957F\u5EA6\u4E0D\u8DB3{0}\u4F4D
|
|
||||||
10031=\u5F31\u5BC6\u7801
|
|
||||||
10032=\u5220\u9664\u672C\u6570\u636E\u5F02\u5E38
|
|
||||||
10033=\u8BBE\u5907\u9A8C\u8BC1\u7801\u9519\u8BEF
|
|
||||||
10034=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A
|
|
||||||
10035=\u53C2\u6570\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
|
||||||
10036=\u4E0D\u652F\u6301\u7684\u53C2\u6570\u7C7B\u578B
|
|
||||||
10037=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684\u6570\u5B57
|
|
||||||
10038=\u53C2\u6570\u503C\u5FC5\u987B\u662Ftrue\u6216false
|
|
||||||
10039=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u6570\u7EC4\u683C\u5F0F
|
|
||||||
10040=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u683C\u5F0F
|
|
||||||
|
|
||||||
10041=\u8BBE\u5907\u672A\u627E\u5230
|
10001={0}不能为空
|
||||||
|
10002=数据库中已存在该记录
|
||||||
|
10003=获取参数失败
|
||||||
|
10004=账号或密码错误
|
||||||
|
10005=账号已被停用
|
||||||
|
10006=唯一标识不能为空
|
||||||
|
10007=验证码不正确
|
||||||
|
10008=先删除子菜单或按钮
|
||||||
|
10009=原密码不正确
|
||||||
|
10010=账号或密码不正确,您还有可以尝试{0}次
|
||||||
|
|
||||||
|
10011=上级部门选择错误
|
||||||
|
10012=上级菜单不能为自身
|
||||||
|
10013=数据权限接口,只能是Map类型参数
|
||||||
|
10014=请先删除下级部门
|
||||||
|
10015=请先删除部门下的用户
|
||||||
|
10016=部署失败,没有流程
|
||||||
|
10017=模型图不正确,请检查
|
||||||
|
10018=导出失败,模型ID为{0}
|
||||||
|
|
||||||
|
10019=请上传文件
|
||||||
|
10020=token不能为空
|
||||||
|
10021=token失效,请重新登录
|
||||||
|
10022=账号已被锁定
|
||||||
|
10023=请上传zip、bar、bpmn、bpmn20.xml格式文件
|
||||||
|
|
||||||
|
10024=上传文件失败{0}
|
||||||
|
10025=发送短信失败{0}
|
||||||
|
10026=邮件模板不存在
|
||||||
|
|
||||||
|
10027=Redis服务异常
|
||||||
|
10028=定时任务失败
|
||||||
|
10029=不能包含非法字符
|
||||||
|
10030=密码长度不足{0}位
|
||||||
|
10031=密码必须同时包含数字、大小写字母和特殊字符组成
|
||||||
|
10032=删除本数据异常
|
||||||
|
10033=设备验证码错误
|
||||||
|
|
||||||
|
10034=参数值不能为空
|
||||||
|
10035=参数类型不能为空
|
||||||
|
10036=不支持的参数类型
|
||||||
|
10037=参数值必须是有效的数字
|
||||||
|
10038=参数值必须是true或false
|
||||||
|
10039=参数值必须是有效的JSON数组格式
|
||||||
|
10040=参数值必须是有效的JSON格式
|
||||||
|
|
||||||
|
10041=设备未找到
|
||||||
10042={0}
|
10042={0}
|
||||||
|
10043=删除数据失败
|
||||||
|
10044=用户未登录
|
||||||
|
10045=WebSocket连接失败或连接超时
|
||||||
|
10046=保存声纹错误,请联系管理员
|
||||||
|
10047=每日发送次数已达上限
|
||||||
|
10048=旧密码输入错误
|
||||||
|
10049=设置的LLM不是openai和ollama
|
||||||
|
10050=token生成失败
|
||||||
|
10051=资源不存在
|
||||||
|
10052=默认智能体未找到
|
||||||
|
10053=智能体未找到
|
||||||
|
10054=声纹接口未配置,请先在参数配置中配置声纹接口地址(server.voice_print)
|
||||||
|
10055=短信发送失败
|
||||||
|
10056=短信连接建立失败
|
||||||
|
10057=智能体的声纹创建失败
|
||||||
|
10058=智能体的对应声纹更新失败
|
||||||
|
10059=智能体的对应声纹删除失败
|
||||||
|
10060=发送太频繁,请{0}秒后再试
|
||||||
|
10061=激活码不能为空
|
||||||
|
10062=激活码错误
|
||||||
|
10063=设备已激活
|
||||||
|
10064=该模型为默认模型,请先设置其他模型为默认模型
|
||||||
|
10065=新增数据失败
|
||||||
|
10066=修改数据失败
|
||||||
|
10067=图形验证码错误
|
||||||
|
10068=没有开启手机注册,没法使用短信验证码功能
|
||||||
|
10069=用户名不是手机号码,请重新输入
|
||||||
|
10070=此手机号码已经注册过
|
||||||
|
10071=输入的手机号码未注册
|
||||||
|
10072=当前不允许普通用户注册
|
||||||
|
10073=没有开启手机注册,没法使用找回密码功能
|
||||||
|
10074=输入的手机号码格式不正确
|
||||||
|
10075=输入的手机验证码错误
|
||||||
|
10076=字典类型不存在
|
||||||
|
10077=字典类型编码重复
|
||||||
|
10078=读取资源失败
|
||||||
|
10079=LLM大模型和Intent意图识别,选择参数不匹配
|
||||||
|
10080=此声音声纹对应的人({0})已经注册,请选择其他声音注册
|
||||||
|
10081=删除声纹出现错误
|
||||||
|
10082=此次修改不允许,此声音已经注册为声纹了({0})
|
||||||
|
10083=修改声纹错误,请联系管理员
|
||||||
|
10084=声纹接口地址存在错误,请进入参数管理修改声纹接口地址
|
||||||
|
10085=音频数据不属于这个智能体
|
||||||
|
10086=音频数据是空的请检查上传数据
|
||||||
|
10087=声纹保存失败,请求不成功
|
||||||
|
10088=声纹保存失败,请求处理失败
|
||||||
|
10089=声纹注销失败,请求不成功
|
||||||
|
10090=声纹注销失败,请求处理失败
|
||||||
|
10091=供应器不存在
|
||||||
|
10092=设置的LLM不存在
|
||||||
|
10093=该模型配置已被智能体{0}引用,无法删除
|
||||||
|
10094=该LLM模型已被意图识别配置引用,无法删除
|
||||||
|
10095=无效服务端操作
|
||||||
|
10096=未配置服务端WebSocket地址
|
||||||
|
10097=目标WebSocket地址不存在
|
||||||
|
10098=WebSocket地址列表不能为空
|
||||||
|
10099=WebSocket地址不能使用localhost或127.0.0.1
|
||||||
|
10100=WebSocket地址格式不正确
|
||||||
|
10101=WebSocket连接测试失败
|
||||||
|
10102=OTA地址不能为空
|
||||||
|
10103=OTA地址不能使用localhost或127.0.0.1
|
||||||
|
10104=OTA地址必须以http或https开头
|
||||||
|
10105=OTA地址必须以/ota/结尾
|
||||||
|
10106=OTA接口访问失败
|
||||||
|
10107=OTA接口返回内容格式不正确
|
||||||
|
10108=OTA接口验证失败
|
||||||
|
10109=MCP地址不能为空
|
||||||
|
10110=MCP地址不能使用localhost或127.0.0.1
|
||||||
|
10111=不是正确的MCP地址
|
||||||
|
10112=MCP接口访问失败
|
||||||
|
10113=MCP接口返回内容格式不正确
|
||||||
|
10114=MCP接口验证失败
|
||||||
|
10115=声纹接口地址不能为空
|
||||||
|
10116=声纹接口地址不能使用localhost或127.0.0.1
|
||||||
|
10117=不是正确的声纹接口地址
|
||||||
|
10118=声纹接口地址必须以http或https开头
|
||||||
|
10119=声纹接口访问失败
|
||||||
|
10120=声纹接口返回内容格式不正确
|
||||||
|
10121=声纹接口验证失败
|
||||||
|
10122=mqtt密钥不能为空
|
||||||
|
10123=您的mqtt密钥长度不安全,字符数需要至少8个字符且必须同时包含大小写字母
|
||||||
|
10124=您的mqtt密钥长度不安全,mqtt密钥必须同时包含大小写字母
|
||||||
|
10125=您的mqtt密钥包含弱密码
|
||||||
|
10128=字典标签重复
|
||||||
@@ -11,19 +11,26 @@
|
|||||||
10007=The verification code is incorrect
|
10007=The verification code is incorrect
|
||||||
10008=First delete submenu or button
|
10008=First delete submenu or button
|
||||||
10009=The original password is incorrect
|
10009=The original password is incorrect
|
||||||
|
10010=The account or password is incorrect, you can try {0} more times
|
||||||
|
|
||||||
10011=The superior department made a wrong choice
|
10011=The superior department made a wrong choice
|
||||||
10012=Upper menu cannot be for itself
|
10012=Upper menu cannot be for itself
|
||||||
10013=Data permission interface, which can only be a Map type parameter.
|
10013=Data permission interface, which can only be a Map type parameter.
|
||||||
10014=Please delete the subordinate department first
|
10014=Please delete the subordinate department first
|
||||||
10015=Please delete the user under the department first
|
10015=Please delete the user under the department first
|
||||||
|
10016=Deployment failed, no process
|
||||||
|
10017=The model diagram is incorrect, please check
|
||||||
|
10018=Export failed, model ID is {0}
|
||||||
|
|
||||||
10019=Please upload a file
|
10019=Please upload a file
|
||||||
10020=token cannot be empty
|
10020=token cannot be empty
|
||||||
10021=token is invalid, please log in again
|
10021=token is invalid, please log in again
|
||||||
10022=The account has been locked
|
10022=The account has been locked
|
||||||
|
10023=Please upload files in zip, bar, bpmn, bpmn20.xml format
|
||||||
|
|
||||||
10024=Failed to upload file {0}
|
10024=Failed to upload file {0}
|
||||||
|
10025=Failed to send SMS {0}
|
||||||
|
10026=Email template does not exist
|
||||||
|
|
||||||
10027=Redis service exception
|
10027=Redis service exception
|
||||||
10028=Timed task failed
|
10028=Timed task failed
|
||||||
@@ -43,4 +50,88 @@
|
|||||||
10040=Parameter value must be a valid JSON format
|
10040=Parameter value must be a valid JSON format
|
||||||
|
|
||||||
10041=Device not found
|
10041=Device not found
|
||||||
10042={0}
|
10042={0}
|
||||||
|
10043=Failed to delete data
|
||||||
|
10044=User not logged in
|
||||||
|
10045=WebSocket connection failed or timeout
|
||||||
|
10046=Failed to save voiceprint, please contact administrator
|
||||||
|
10047=Daily sending limit reached
|
||||||
|
10048=Old password is incorrect
|
||||||
|
10049=The set LLM is not openai or ollama
|
||||||
|
10050=Failed to generate token
|
||||||
|
10051=Resource not found
|
||||||
|
10052=Default agent not found
|
||||||
|
10053=Agent not found
|
||||||
|
10054=Voiceprint interface not configured, please configure voiceprint interface address (server.voice_print) in parameter configuration first
|
||||||
|
10055=Failed to send SMS
|
||||||
|
10056=Failed to establish SMS connection
|
||||||
|
10057=Failed to create voiceprint for agent
|
||||||
|
10058=Failed to update voiceprint for agent
|
||||||
|
10059=Failed to delete voiceprint for agent
|
||||||
|
10060=Sending too frequently, please try again after {0} seconds
|
||||||
|
10061=Activation code cannot be empty
|
||||||
|
10062=Activation code error
|
||||||
|
10063=Device already activated
|
||||||
|
10064=This model is the default model, please set another model as default first
|
||||||
|
10065=Failed to add data
|
||||||
|
10066=Failed to update data
|
||||||
|
10067=Graphic verification code error
|
||||||
|
10068=Mobile registration is not enabled, SMS verification code function cannot be used
|
||||||
|
10069=Username is not a mobile phone number, please re-enter
|
||||||
|
10070=This mobile phone number has already been registered
|
||||||
|
10071=The entered mobile phone number is not registered
|
||||||
|
10072=Ordinary user registration is not allowed currently
|
||||||
|
10073=Mobile registration is not enabled, password retrieval function cannot be used
|
||||||
|
10074=The entered mobile phone number format is incorrect
|
||||||
|
10075=The entered SMS verification code is incorrect
|
||||||
|
10076=Dictionary type does not exist
|
||||||
|
10077=Dictionary type code is duplicated
|
||||||
|
10078=Failed to read resource
|
||||||
|
10079=LLM model and Intent recognition, parameter selection does not match
|
||||||
|
10080=This voiceprint belongs to {0} who has already registered, please select another voice
|
||||||
|
10081=Error occurred while deleting voiceprint
|
||||||
|
10082=Modification not allowed, this voice has already been registered as a voiceprint ({0})
|
||||||
|
10083=Error modifying voiceprint, please contact administrator
|
||||||
|
10084=Voiceprint interface address error, please go to parameter management to modify the voiceprint interface address
|
||||||
|
10085=Audio data does not belong to this agent
|
||||||
|
10086=Audio data is empty, please check uploaded data
|
||||||
|
10087=Voiceprint registration failed, request unsuccessful
|
||||||
|
10088=Voiceprint registration failed, request processing failed
|
||||||
|
10089=Voiceprint cancellation failed, request unsuccessful
|
||||||
|
10090=Voiceprint cancellation failed, request processing failed
|
||||||
|
10091=Model provider does not exist
|
||||||
|
10092=The configured LLM does not exist
|
||||||
|
10093=This model configuration is referenced by agent {0} and cannot be deleted
|
||||||
|
10094=This LLM model is referenced by intent recognition configuration and cannot be deleted
|
||||||
|
10095=Invalid server operation
|
||||||
|
10096=Server WebSocket address not configured
|
||||||
|
10097=Target WebSocket address does not exist
|
||||||
|
10098=WebSocket address list cannot be empty
|
||||||
|
10099=WebSocket address cannot use localhost or 127.0.0.1
|
||||||
|
10100=WebSocket address format is incorrect
|
||||||
|
10101=WebSocket connection test failed
|
||||||
|
10102=OTA address cannot be empty
|
||||||
|
10103=OTA address cannot use localhost or 127.0.0.1
|
||||||
|
10104=OTA address must start with http or https
|
||||||
|
10105=OTA address must end with /ota/
|
||||||
|
10106=OTA interface access failed
|
||||||
|
10107=OTA interface return content format is incorrect
|
||||||
|
10108=OTA interface validation failed
|
||||||
|
10109=MCP address cannot be empty
|
||||||
|
10110=MCP address cannot use localhost or 127.0.0.1
|
||||||
|
10111=Not a valid MCP address
|
||||||
|
10112=MCP interface access failed
|
||||||
|
10113=MCP interface return content format is incorrect
|
||||||
|
10114=MCP interface validation failed
|
||||||
|
10115=Voiceprint interface address cannot be empty
|
||||||
|
10116=Voiceprint interface address cannot use localhost or 127.0.0.1
|
||||||
|
10117=Not a valid voiceprint interface address
|
||||||
|
10118=Voiceprint interface address must start with http or https
|
||||||
|
10119=Voiceprint interface access failed
|
||||||
|
10120=Voiceprint interface return content format is incorrect
|
||||||
|
10121=Voiceprint interface validation failed
|
||||||
|
10122=MQTT secret cannot be empty
|
||||||
|
10123=Your MQTT secret is not secure, it needs to be at least 8 characters and must contain both uppercase and lowercase letters
|
||||||
|
10124=Your MQTT secret is not secure, MQTT secret must contain both uppercase and lowercase letters
|
||||||
|
10125=Your MQTT secret contains weak password
|
||||||
|
10128=Dictionary label is duplicated
|
||||||
@@ -1,46 +1,137 @@
|
|||||||
#\u7B80\u4F53\u4E2D\u6587
|
#简体中文
|
||||||
500=\u670D\u52A1\u5668\u5185\u90E8\u5F02\u5E38
|
500=服务器内部异常
|
||||||
401=\u672A\u6388\u6743
|
401=未授权
|
||||||
403=\u62D2\u7EDD\u8BBF\u95EE\uFF0C\u6CA1\u6709\u6743\u9650
|
403=拒绝访问,没有权限
|
||||||
|
|
||||||
10001={0}\u4E0D\u80FD\u4E3A\u7A7A
|
10001={0}不能为空
|
||||||
10002=\u6570\u636E\u5E93\u4E2D\u5DF2\u5B58\u5728\u8BE5\u8BB0\u5F55
|
10002=数据库中已存在该记录
|
||||||
10003=\u83B7\u53D6\u53C2\u6570\u5931\u8D25
|
10003=获取参数失败
|
||||||
10004=\u8D26\u53F7\u6216\u5BC6\u7801\u9519\u8BEF
|
10004=账号或密码错误
|
||||||
10005=\u8D26\u53F7\u5DF2\u88AB\u505C\u7528
|
10005=账号已被停用
|
||||||
10006=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
|
10006=唯一标识不能为空
|
||||||
10007=\u9A8C\u8BC1\u7801\u4E0D\u6B63\u786E
|
10007=验证码不正确
|
||||||
10008=\u5148\u5220\u9664\u5B50\u83DC\u5355\u6216\u6309\u94AE
|
10008=先删除子菜单或按钮
|
||||||
10009=\u539F\u5BC6\u7801\u4E0D\u6B63\u786E
|
10009=原密码不正确
|
||||||
|
10010=账号或密码不正确,您还有可以尝试{0}次
|
||||||
|
|
||||||
10011=\u4E0A\u7EA7\u90E8\u95E8\u9009\u62E9\u9519\u8BEF
|
10011=上级部门选择错误
|
||||||
10012=\u4E0A\u7EA7\u83DC\u5355\u4E0D\u80FD\u4E3A\u81EA\u8EAB
|
10012=上级菜单不能为自身
|
||||||
10013=\u6570\u636E\u6743\u9650\u63A5\u53E3\uFF0C\u53EA\u80FD\u662FMap\u7C7B\u578B\u53C2\u6570
|
10013=数据权限接口,只能是Map类型参数
|
||||||
10014=\u8BF7\u5148\u5220\u9664\u4E0B\u7EA7\u90E8\u95E8
|
10014=请先删除下级部门
|
||||||
10015=\u8BF7\u5148\u5220\u9664\u90E8\u95E8\u4E0B\u7684\u7528\u6237
|
10015=请先删除部门下的用户
|
||||||
|
10016=部署失败,没有流程
|
||||||
|
10017=模型图不正确,请检查
|
||||||
|
10018=导出失败,模型ID为{0}
|
||||||
|
|
||||||
10019=\u8BF7\u4E0A\u4F20\u6587\u4EF6
|
10019=请上传文件
|
||||||
10020=token\u4E0D\u80FD\u4E3A\u7A7A
|
10020=token不能为空
|
||||||
10021=token\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55
|
10021=token失效,请重新登录
|
||||||
10022=\u8D26\u53F7\u5DF2\u88AB\u9501\u5B9A
|
10022=账号已被锁定
|
||||||
|
10023=请上传zip、bar、bpmn、bpmn20.xml格式文件
|
||||||
|
|
||||||
10024=\u4E0A\u4F20\u6587\u4EF6\u5931\u8D25{0}
|
10024=上传文件失败{0}
|
||||||
|
10025=发送短信失败{0}
|
||||||
|
10026=邮件模板不存在
|
||||||
|
|
||||||
10027=Redis\u670D\u52A1\u5F02\u5E38
|
10027=Redis服务异常
|
||||||
10028=\u5B9A\u65F6\u4EFB\u52A1\u5931\u8D25
|
10028=定时任务失败
|
||||||
10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26
|
10029=不能包含非法字符
|
||||||
10030=\u5BC6\u7801\u957F\u5EA6\u4E0D\u8DB3{0}\u4F4D
|
10030=密码长度不足{0}位
|
||||||
10031=\u5BC6\u7801\u5FC5\u987B\u540C\u65F6\u5305\u542B\u6570\u5B57\u3001\u5927\u5C0F\u5B57\u6BCD\u548C\u7279\u6B8A\u5B57\u7B26\u7EC4\u6210
|
10031=密码必须同时包含数字、大小写字母和特殊字符组成
|
||||||
10032=\u5220\u9664\u672C\u6570\u636E\u5F02\u5E38
|
10032=删除本数据异常
|
||||||
10033=\u8BBE\u5907\u9A8C\u8BC1\u7801\u9519\u8BEF
|
10033=设备验证码错误
|
||||||
|
|
||||||
10034=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A
|
10034=参数值不能为空
|
||||||
10035=\u53C2\u6570\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
10035=参数类型不能为空
|
||||||
10036=\u4E0D\u652F\u6301\u7684\u53C2\u6570\u7C7B\u578B
|
10036=不支持的参数类型
|
||||||
10037=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684\u6570\u5B57
|
10037=参数值必须是有效的数字
|
||||||
10038=\u53C2\u6570\u503C\u5FC5\u987B\u662Ftrue\u6216false
|
10038=参数值必须是true或false
|
||||||
10039=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u6570\u7EC4\u683C\u5F0F
|
10039=参数值必须是有效的JSON数组格式
|
||||||
10040=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u683C\u5F0F
|
10040=参数值必须是有效的JSON格式
|
||||||
|
|
||||||
10041=\u8BBE\u5907\u672A\u627E\u5230
|
10041=设备未找到
|
||||||
10042={0}
|
10042={0}
|
||||||
|
10043=删除数据失败
|
||||||
|
10044=用户未登录
|
||||||
|
10045=WebSocket连接失败或连接超时
|
||||||
|
10046=保存声纹错误,请联系管理员
|
||||||
|
10047=每日发送次数已达上限
|
||||||
|
10048=旧密码输入错误
|
||||||
|
10049=设置的LLM不是openai和ollama
|
||||||
|
10050=token生成失败
|
||||||
|
10051=资源不存在
|
||||||
|
10052=默认智能体未找到
|
||||||
|
10053=智能体未找到
|
||||||
|
10054=声纹接口未配置,请先在参数配置中配置声纹接口地址(server.voice_print)
|
||||||
|
10055=短信发送失败
|
||||||
|
10056=短信连接建立失败
|
||||||
|
10057=智能体的声纹创建失败
|
||||||
|
10058=智能体的对应声纹更新失败
|
||||||
|
10059=智能体的对应声纹删除失败
|
||||||
|
10060=发送太频繁,请{0}秒后再试
|
||||||
|
10061=激活码不能为空
|
||||||
|
10062=激活码错误
|
||||||
|
10063=设备已激活
|
||||||
|
10064=该模型为默认模型,请先设置其他模型为默认模型
|
||||||
|
10065=新增数据失败
|
||||||
|
10066=修改数据失败
|
||||||
|
10067=图形验证码错误
|
||||||
|
10068=没有开启手机注册,没法使用短信验证码功能
|
||||||
|
10069=用户名不是手机号码,请重新输入
|
||||||
|
10070=此手机号码已经注册过
|
||||||
|
10071=输入的手机号码未注册
|
||||||
|
10072=当前不允许普通用户注册
|
||||||
|
10073=没有开启手机注册,没法使用找回密码功能
|
||||||
|
10074=输入的手机号码格式不正确
|
||||||
|
10075=输入的手机验证码错误
|
||||||
|
10076=字典类型不存在
|
||||||
|
10077=字典类型编码重复
|
||||||
|
10078=读取资源失败
|
||||||
|
10079=LLM大模型和Intent意图识别,选择参数不匹配
|
||||||
|
10080=此声音声纹对应的人({0})已经注册,请选择其他声音注册
|
||||||
|
10081=删除声纹出现错误
|
||||||
|
10082=此次修改不允许,此声音已经注册为声纹了({0})
|
||||||
|
10083=修改声纹错误,请联系管理员
|
||||||
|
10084=声纹接口地址存在错误,请进入参数管理修改声纹接口地址
|
||||||
|
10085=音频数据不属于这个智能体
|
||||||
|
10086=音频数据是空的请检查上传数据
|
||||||
|
10087=声纹保存失败,请求不成功
|
||||||
|
10088=声纹保存失败,请求处理失败
|
||||||
|
10089=声纹注销失败,请求不成功
|
||||||
|
10090=声纹注销失败,请求处理失败
|
||||||
|
10091=供应器不存在
|
||||||
|
10092=设置的LLM不存在
|
||||||
|
10093=该模型配置已被智能体{0}引用,无法删除
|
||||||
|
10094=该LLM模型已被意图识别配置引用,无法删除
|
||||||
|
10095=无效服务端操作
|
||||||
|
10096=未配置服务端WebSocket地址
|
||||||
|
10097=目标WebSocket地址不存在
|
||||||
|
10098=WebSocket地址列表不能为空
|
||||||
|
10099=WebSocket地址不能使用localhost或127.0.0.1
|
||||||
|
10100=WebSocket地址格式不正确
|
||||||
|
10101=WebSocket连接测试失败
|
||||||
|
10102=OTA地址不能为空
|
||||||
|
10103=OTA地址不能使用localhost或127.0.0.1
|
||||||
|
10104=OTA地址必须以http或https开头
|
||||||
|
10105=OTA地址必须以/ota/结尾
|
||||||
|
10106=OTA接口访问失败
|
||||||
|
10107=OTA接口返回内容格式不正确
|
||||||
|
10108=OTA接口验证失败
|
||||||
|
10109=MCP地址不能为空
|
||||||
|
10110=MCP地址不能使用localhost或127.0.0.1
|
||||||
|
10111=不是正确的MCP地址
|
||||||
|
10112=MCP接口访问失败
|
||||||
|
10113=MCP接口返回内容格式不正确
|
||||||
|
10114=MCP接口验证失败
|
||||||
|
10115=声纹接口地址不能为空
|
||||||
|
10116=声纹接口地址不能使用localhost或127.0.0.1
|
||||||
|
10117=不是正确的声纹接口地址
|
||||||
|
10118=声纹接口地址必须以http或https开头
|
||||||
|
10119=声纹接口访问失败
|
||||||
|
10120=声纹接口返回内容格式不正确
|
||||||
|
10121=声纹接口验证失败
|
||||||
|
10122=mqtt密钥不能为空
|
||||||
|
10123=您的mqtt密钥长度不安全,字符数需要至少8个字符且必须同时包含大小写字母
|
||||||
|
10124=您的mqtt密钥长度不安全,mqtt密钥必须同时包含大小写字母
|
||||||
|
10125=您的mqtt密钥包含弱密码
|
||||||
|
10128=字典标签重复
|
||||||
@@ -1,46 +1,137 @@
|
|||||||
#\u7E41\u4F53\u4E2D\u6587
|
#繁體中文
|
||||||
500=\u670D\u52D9\u5668\u5167\u90E8\u7570\u5E38
|
500=服務器內部異常
|
||||||
401=\u672A\u6388\u6B0A
|
401=未授權
|
||||||
403=\u62D2\u7D55\u8A2A\u554F\uFF0C\u6C92\u6709\u6B0A\u9650
|
403=拒絕訪問,沒有权限
|
||||||
|
|
||||||
10001={0}\u4E0D\u80FD\u70BA\u7A7A
|
10001={0}不能為空
|
||||||
10002=\u6578\u64DA\u5EAB\u4E2D\u5DF2\u5B58\u5728\u8A72\u8A18\u9304
|
10002=數據庫中已存在該記錄
|
||||||
10003=\u7372\u53D6\u53C3\u6578\u5931\u6557
|
10003=獲取參數失敗
|
||||||
10004=\u8CEC\u865F\u6216\u5BC6\u78BC\u932F\u8AA4
|
10004=賬號或密碼錯誤
|
||||||
10005=\u8CEC\u865F\u5DF2\u88AB\u505C\u7528
|
10005=賬號已被停用
|
||||||
10006=\u552F\u4E00\u6A19\u8B58\u4E0D\u80FD\u70BA\u7A7A
|
10006=唯一標識不能為空
|
||||||
10007=\u9A57\u8B49\u78BC\u4E0D\u6B63\u78BA
|
10007=驗證碼不正確
|
||||||
10008=\u5148\u522A\u9664\u5B50\u83DC\u55AE\u6216\u6309\u9215
|
10008=先刪除子菜單或按鈕
|
||||||
10009=\u539F\u5BC6\u78BC\u4E0D\u6B63\u78BA
|
10009=原密碼不正確
|
||||||
|
10010=帳號或密碼不正確,您還有可以嘗試{0}次
|
||||||
|
|
||||||
10011=\u4E0A\u7D1A\u90E8\u9580\u9078\u64C7\u932F\u8AA4
|
10011=上級部門選擇錯誤
|
||||||
10012=\u4E0A\u7D1A\u83DC\u55AE\u4E0D\u80FD\u70BA\u81EA\u8EAB
|
10012=上級菜單不能為自身
|
||||||
10013=\u6578\u64DA\u6B0A\u9650\u63A5\u53E3\uFF0C\u53EA\u80FD\u662FMap\u985E\u578B\u53C3\u6578
|
10013=數據權限接口,只能是Map類型參數
|
||||||
10014=\u8ACB\u5148\u522A\u9664\u4E0B\u7D1A\u90E8\u9580
|
10014=請先刪除下級部門
|
||||||
10015=\u8ACB\u5148\u522A\u9664\u90E8\u9580\u4E0B\u7684\u7528\u6236
|
10015=請先刪除部門下的用戶
|
||||||
|
10016=部署失敗,沒有流程
|
||||||
|
10017=模型圖不正確,請檢查
|
||||||
|
10018=導出失敗,模型ID為{0}
|
||||||
|
|
||||||
10019=\u8ACB\u4E0A\u50B3\u6587\u4EF6
|
10019=請上傳文件
|
||||||
10020=token\u4E0D\u80FD\u70BA\u7A7A
|
10020=token不能為空
|
||||||
10021=token\u5931\u6548\uFF0C\u8ACB\u91CD\u65B0\u767B\u9304
|
10021=token失效,請重新登錄
|
||||||
10022=\u8CEC\u865F\u5DF2\u88AB\u9396\u5B9A
|
10022=賬號已被鎖定
|
||||||
|
10023=請上傳zip、bar、bpmn、bpmn20.xml格式文件
|
||||||
|
|
||||||
10024=\u4E0A\u50B3\u6587\u4EF6\u5931\u6557{0}
|
10024=上傳文件失敗{0}
|
||||||
|
10025=發送短信失敗{0}
|
||||||
|
10026=郵件模板不存在
|
||||||
|
|
||||||
10027=Redis\u670D\u52D9\u7570\u5E38
|
10027=Redis服務異常
|
||||||
10028=\u5B9A\u6642\u4EFB\u52D9\u5931\u6557
|
10028=定時任務失敗
|
||||||
10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26
|
10029=不能包含非法字符
|
||||||
10030=\u5BC6\u78BC\u9577\u5EA6\u4E0D\u8DB3{0}\u4F4D
|
10030=密碼長度不足{0}位
|
||||||
10031=\u5BC6\u78BC\u5FC5\u9808\u540C\u6642\u5305\u542B\u6578\u5B57\u3001\u5927\u5C0F\u5B57\u6BCD\u548C\u7279\u6B8A\u5B57\u7B26\u7D44\u6210
|
10031=密碼必須同時包含數字、大小寫字母和特殊字符組成
|
||||||
10032=\u522A\u9664\u6B64\u6578\u64DA\u7570\u5E38
|
10032=刪除本數據異常
|
||||||
10033=\u8A2D\u5099\u9A57\u8B49\u78BC\u932F\u8AA4
|
10033=設備驗證碼錯誤
|
||||||
|
|
||||||
10034=\u53C3\u6578\u503C\u4E0D\u80FD\u70BA\u7A7A
|
10034=參數值不能為空
|
||||||
10035=\u53C3\u6578\u985E\u578B\u4E0D\u80FD\u70BA\u7A7A
|
10035=參數類型不能為空
|
||||||
10036=\u4E0D\u652F\u63F4\u7684\u53C3\u6578\u985E\u578B
|
10036=不支持的參數類型
|
||||||
10037=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684\u6578\u5B57
|
10037=參數值必須是有效的數字
|
||||||
10038=\u53C3\u6578\u503C\u5FC5\u9808\u662Ftrue\u6216false
|
10038=參數值必須是true或false
|
||||||
10039=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684JSON\u6578\u7D44\u683C\u5F0F
|
10039=參數值必須是有效的JSON數組格式
|
||||||
10040=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684JSON\u683C\u5F0F
|
10040=參數值必須是有效的JSON格式
|
||||||
|
|
||||||
10041=\u8A2D\u5099\u672A\u627E\u5230
|
10041=設備未找到
|
||||||
10042={0}
|
10042={0}
|
||||||
|
10043=刪除數據失敗
|
||||||
|
10044=用戶未登錄
|
||||||
|
10045=WebSocket連接失敗或連接超時
|
||||||
|
10046=保存聲紋錯誤,請聯繫管理員
|
||||||
|
10047=每日發送次數已達上限
|
||||||
|
10048=舊密碼輸入錯誤
|
||||||
|
10049=設置的LLM不是openai和ollama
|
||||||
|
10050=token生成失敗
|
||||||
|
10051=資源不存在
|
||||||
|
10052=默認智能體未找到
|
||||||
|
10053=智能體未找到
|
||||||
|
10054=聲紋接口未配置,請先在參數配置中配置聲紋接口地址(server.voice_print)
|
||||||
|
10055=短信發送失敗
|
||||||
|
10056=短信連接建立失敗
|
||||||
|
10057=智能體的聲紋創建失敗
|
||||||
|
10058=智能體的對應聲紋更新失敗
|
||||||
|
10059=智能體的對應聲紋刪除失敗
|
||||||
|
10060=發送太頻繁,請{0}秒後再試
|
||||||
|
10061=激活碼不能為空
|
||||||
|
10062=激活碼錯誤
|
||||||
|
10063=設備已激活
|
||||||
|
10064=該模型為預設模型,請先設置其他模型為預設模型
|
||||||
|
10065=新增數據失敗
|
||||||
|
10066=更新數據失敗
|
||||||
|
10067=圖形驗證碼錯誤
|
||||||
|
10068=未開啟手機註冊,無法使用短信驗證碼功能
|
||||||
|
10069=用戶名不是手機號碼,請重新輸入
|
||||||
|
10070=該手機號碼已註冊
|
||||||
|
10071=輸入的手機號碼未註冊
|
||||||
|
10072=目前不允許普通用戶註冊
|
||||||
|
10073=未開啟手機註冊,無法使用密碼找回功能
|
||||||
|
10074=輸入的手機號碼格式不正確
|
||||||
|
10075=輸入的短信驗證碼不正確
|
||||||
|
10076=字典類型不存在
|
||||||
|
10077=字典類型編碼重複
|
||||||
|
10078=讀取資源失敗
|
||||||
|
10079=LLM模型與意圖識別、參數選擇不匹配
|
||||||
|
10080=此聲紋屬於已註冊的{0},請選擇其他聲音
|
||||||
|
10081=刪除聲紋時發生錯誤
|
||||||
|
10082=不允許修改,此聲音已註冊為聲紋({0})
|
||||||
|
10083=修改聲紋錯誤,請聯繫管理員
|
||||||
|
10084=聲紋接口地址錯誤,請前往參數管理修改聲紋接口地址
|
||||||
|
10085=音頻數據不屬於此智能體
|
||||||
|
10086=音頻數據為空,請檢查上傳數據
|
||||||
|
10087=聲紋註冊失敗,請求未成功
|
||||||
|
10088=聲紋註冊失敗,請求處理失敗
|
||||||
|
10089=聲紋註銷失敗,請求未成功
|
||||||
|
10090=聲紋註銷失敗,請求處理失敗
|
||||||
|
10091=模型提供商不存在
|
||||||
|
10092=配置的LLM不存在
|
||||||
|
10093=此模型配置被智能體{0}引用,無法刪除
|
||||||
|
10094=此LLM模型被意圖識別配置引用,無法刪除
|
||||||
|
10095=無效的服務器操作
|
||||||
|
10096=未配置服務器WebSocket地址
|
||||||
|
10097=目標WebSocket地址不存在
|
||||||
|
10098=WebSocket地址列表不能為空
|
||||||
|
10099=WebSocket地址不能使用localhost或127.0.0.1
|
||||||
|
10100=WebSocket地址格式不正確
|
||||||
|
10101=WebSocket連接測試失敗
|
||||||
|
10102=OTA地址不能為空
|
||||||
|
10103=OTA地址不能使用localhost或127.0.0.1
|
||||||
|
10104=OTA地址必須以http或https開頭
|
||||||
|
10105=OTA地址必須以/ota/結尾
|
||||||
|
10106=OTA接口訪問失敗
|
||||||
|
10107=OTA接口返回內容格式不正確
|
||||||
|
10108=OTA接口驗證失敗
|
||||||
|
10109=MCP地址不能為空
|
||||||
|
10110=MCP地址不能使用localhost或127.0.0.1
|
||||||
|
10111=不是有效的MCP地址
|
||||||
|
10112=MCP接口訪問失敗
|
||||||
|
10113=MCP接口返回內容格式不正確
|
||||||
|
10114=MCP接口驗證失敗
|
||||||
|
10115=聲紋接口地址不能為空
|
||||||
|
10116=聲紋接口地址不能使用localhost或127.0.0.1
|
||||||
|
10117=不是有效的聲紋接口地址
|
||||||
|
10118=聲紋接口地址必須以http或https開頭
|
||||||
|
10119=聲紋接口訪問失敗
|
||||||
|
10120=聲紋接口返回內容格式不正確
|
||||||
|
10121=聲紋接口驗證失敗
|
||||||
|
10122=MQTT密鑰不能為空
|
||||||
|
10123=您的MQTT密鑰不安全,需至少8個字符且必須同時包含大小寫字母
|
||||||
|
10124=您的MQTT密鑰不安全,MQTT密鑰必須同時包含大小寫字母
|
||||||
|
10125=您的MQTT密鑰包含弱密碼
|
||||||
|
10128=字典標籤重複
|
||||||
@@ -222,7 +222,7 @@ function showAbout() {
|
|||||||
title: `关于${import.meta.env.VITE_APP_TITLE}`,
|
title: `关于${import.meta.env.VITE_APP_TITLE}`,
|
||||||
content: `${import.meta.env.VITE_APP_TITLE}\n\n基于 Vue.js 3 + uni-app 构建的跨平台移动端管理应用,为小智ESP32智能硬件提供设备管理、智能体配置等功能。\n\n© 2025 xiaozhi-esp32-server`,
|
content: `${import.meta.env.VITE_APP_TITLE}\n\n基于 Vue.js 3 + uni-app 构建的跨平台移动端管理应用,为小智ESP32智能硬件提供设备管理、智能体配置等功能。\n\n© 2025 xiaozhi-esp32-server`,
|
||||||
title: `关于小智智控台`,
|
title: `关于小智智控台`,
|
||||||
content: `小智智控台\n\n基于 Vue.js 3 + uni-app 构建的跨平台移动端管理应用,为小智ESP32智能硬件提供设备管理、智能体配置等功能。\n\n© 2025 xiaozhi-esp32-server 0.8.1`,
|
content: `小智智控台\n\n基于 Vue.js 3 + uni-app 构建的跨平台移动端管理应用,为小智ESP32智能硬件提供设备管理、智能体配置等功能。\n\n© 2025 xiaozhi-esp32-server 0.8.2`,
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: '确定',
|
confirmText: '确定',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Fly from 'flyio/dist/npm/fly';
|
|||||||
import store from '../store/index';
|
import store from '../store/index';
|
||||||
import Constant from '../utils/constant';
|
import Constant from '../utils/constant';
|
||||||
import { goToPage, isNotNull, showDanger, showWarning } from '../utils/index';
|
import { goToPage, isNotNull, showDanger, showWarning } from '../utils/index';
|
||||||
|
import i18n from '../i18n/index';
|
||||||
|
|
||||||
const fly = new Fly()
|
const fly = new Fly()
|
||||||
// 设置超时
|
// 设置超时
|
||||||
@@ -27,6 +28,16 @@ function sendRequest() {
|
|||||||
_url: '',
|
_url: '',
|
||||||
_responseType: undefined, // 新增响应类型字段
|
_responseType: undefined, // 新增响应类型字段
|
||||||
'send'() {
|
'send'() {
|
||||||
|
// 设置语言请求头
|
||||||
|
const currentLang = i18n.locale;
|
||||||
|
// 转换语言代码格式,将zh_CN转换为zh-CN
|
||||||
|
let acceptLanguage = currentLang.replace('_', '-');
|
||||||
|
// 为英语添加默认地区代码
|
||||||
|
if (acceptLanguage === 'en') {
|
||||||
|
acceptLanguage = 'en-US';
|
||||||
|
}
|
||||||
|
this._header['Accept-Language'] = acceptLanguage;
|
||||||
|
|
||||||
if (isNotNull(store.getters.getToken)) {
|
if (isNotNull(store.getters.getToken)) {
|
||||||
this._header.Authorization = 'Bearer ' + (JSON.parse(store.getters.getToken)).token
|
this._header.Authorization = 'Bearer ' + (JSON.parse(store.getters.getToken)).token
|
||||||
}
|
}
|
||||||
@@ -116,10 +127,13 @@ function httpHandlerError(info, failCallback, networkFailCallback) {
|
|||||||
goToPage(Constant.PAGE.LOGIN, true);
|
goToPage(Constant.PAGE.LOGIN, true);
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
// 直接使用后端返回的国际化消息
|
||||||
|
let errorMessage = info.data.msg;
|
||||||
|
|
||||||
if (failCallback) {
|
if (failCallback) {
|
||||||
failCallback(info)
|
failCallback(info)
|
||||||
} else {
|
} else {
|
||||||
showDanger(info.data.msg)
|
showDanger(errorMessage)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export default {
|
|||||||
}).send()
|
}).send()
|
||||||
},
|
},
|
||||||
// 修改
|
// 修改
|
||||||
updateParam(data, callback) {
|
updateParam(data, callback, failCallback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/admin/params`)
|
.url(`${getServiceUrl()}/admin/params`)
|
||||||
.method('PUT')
|
.method('PUT')
|
||||||
@@ -106,6 +106,10 @@ export default {
|
|||||||
RequestService.clearRequestTime()
|
RequestService.clearRequestTime()
|
||||||
callback(res)
|
callback(res)
|
||||||
})
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
failCallback(err)
|
||||||
|
})
|
||||||
.networkFail((err) => {
|
.networkFail((err) => {
|
||||||
console.error('更新参数失败:', err)
|
console.error('更新参数失败:', err)
|
||||||
RequestService.reAjaxFun(() => {
|
RequestService.reAjaxFun(() => {
|
||||||
|
|||||||
@@ -85,4 +85,38 @@ export default {
|
|||||||
});
|
});
|
||||||
}).send();
|
}).send();
|
||||||
},
|
},
|
||||||
|
// 获取设备状态
|
||||||
|
getDeviceStatus(agentId, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/device/bind/${agentId}`)
|
||||||
|
.method('POST')
|
||||||
|
.data({}) // 发送空对象作为请求体
|
||||||
|
.success((res) => {
|
||||||
|
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();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ export default {
|
|||||||
this.saving = true;
|
this.saving = true;
|
||||||
|
|
||||||
if (!this.formData.supplier) {
|
if (!this.formData.supplier) {
|
||||||
this.$message.error('请选择供应器');
|
this.$message.error(this.$t('addModelDialog.requiredSupplier'));
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,10 +73,6 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
return this.device.lastConnectedAt;
|
return this.device.lastConnectedAt;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
// 判断是否为英文
|
|
||||||
isEnglish() {
|
|
||||||
return i18n.locale === 'en';
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -192,8 +192,8 @@ export default {
|
|||||||
const model = data.data;
|
const model = data.data;
|
||||||
|
|
||||||
if (this.modelData.duplicateMode) {
|
if (this.modelData.duplicateMode) {
|
||||||
model.modelName = this.modelData.modelName + '_副本';
|
model.modelName = this.modelData.modelName + this.$t('modelConfigDialog.copySuffix');
|
||||||
model.modelCode = this.modelData.modelCode + '_副本';
|
model.modelCode = this.modelData.modelCode + this.$t('modelConfigDialog.copySuffix');
|
||||||
}
|
}
|
||||||
this.pendingProviderType = model.configJson.type;
|
this.pendingProviderType = model.configJson.type;
|
||||||
this.pendingModelData = model;
|
this.pendingModelData = model;
|
||||||
|
|||||||
@@ -1,51 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :title="title"
|
<el-dialog :title="title" :visible.sync="visible" width="520px" class="param-dialog-wrapper" :append-to-body="true"
|
||||||
:visible.sync="visible"
|
:close-on-click-modal="false" :key="dialogKey" custom-class="custom-param-dialog" :show-close="false">
|
||||||
width="520px"
|
|
||||||
class="param-dialog-wrapper"
|
|
||||||
:append-to-body="true"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
:key="dialogKey"
|
|
||||||
custom-class="custom-param-dialog"
|
|
||||||
:show-close="false"
|
|
||||||
>
|
|
||||||
<div class="dialog-container">
|
<div class="dialog-container">
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
<h2 class="dialog-title">{{ title }}</h2>
|
<h2 class="dialog-title">{{ title }}</h2>
|
||||||
<button class="custom-close-btn" @click="cancel">
|
<button class="custom-close-btn" @click="cancel">
|
||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M13 1L1 13M1 1L13 13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
<path d="M13 1L1 13M1 1L13 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-form :model="form" :rules="rules" ref="form" label-width="auto" label-position="left" class="param-form">
|
<el-form :model="form" :rules="rules" ref="form" label-width="auto" label-position="left" class="param-form">
|
||||||
<el-form-item :label="$t('paramDialog.paramCode')" prop="paramCode" class="form-item">
|
<el-form-item :label="$t('paramDialog.paramCode')" prop="paramCode" class="form-item">
|
||||||
<el-input v-model="form.paramCode" :placeholder="$t('paramDialog.paramCodePlaceholder')" class="custom-input"></el-input>
|
<el-input v-model="form.paramCode" :placeholder="$t('paramDialog.paramCodePlaceholder')"
|
||||||
|
class="custom-input"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="$t('paramDialog.paramValue')" prop="paramValue" class="form-item">
|
<el-form-item :label="$t('paramDialog.paramValue')" prop="paramValue" class="form-item">
|
||||||
<el-input v-model="form.paramValue" :placeholder="$t('paramDialog.paramValuePlaceholder')" class="custom-input"></el-input>
|
<el-input v-model="form.paramValue" :placeholder="$t('paramDialog.paramValuePlaceholder')"
|
||||||
|
class="custom-input"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="$t('paramDialog.valueType')" prop="valueType" class="form-item">
|
<el-form-item :label="$t('paramDialog.valueType')" prop="valueType" class="form-item">
|
||||||
<el-select v-model="form.valueType" :placeholder="$t('paramDialog.valueTypePlaceholder')" class="custom-select">
|
<el-select v-model="form.valueType" :placeholder="$t('paramDialog.valueTypePlaceholder')"
|
||||||
<el-option v-for="item in valueTypeOptions" :key="item.value" :label="$t(`paramDialog.${item.value}Type`)" :value="item.value"/>
|
class="custom-select">
|
||||||
|
<el-option v-for="item in valueTypeOptions" :key="item.value" :label="$t(`paramDialog.${item.value}Type`)"
|
||||||
|
:value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="$t('paramDialog.remark')" prop="remark" class="form-item remark-item">
|
<el-form-item :label="$t('paramDialog.remark')" prop="remark" class="form-item remark-item">
|
||||||
<el-input type="textarea" v-model="form.remark" :placeholder="$t('paramDialog.remarkPlaceholder')" :rows="3" class="custom-textarea"></el-input>
|
<el-input type="textarea" v-model="form.remark" :placeholder="$t('paramDialog.remarkPlaceholder')" :rows="3"
|
||||||
|
class="custom-textarea"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button
|
<el-button type="primary" @click="submit" class="save-btn" :loading="saving" :disabled="saving">
|
||||||
type="primary"
|
|
||||||
@click="submit"
|
|
||||||
class="save-btn"
|
|
||||||
:loading="saving"
|
|
||||||
:disabled="saving">
|
|
||||||
{{ $t('paramDialog.save') }}
|
{{ $t('paramDialog.save') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="cancel" class="cancel-btn">
|
<el-button @click="cancel" class="cancel-btn">
|
||||||
@@ -108,20 +100,26 @@ export default {
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
this.saving = true; // 开始加载
|
this.saving = true; // 开始加载
|
||||||
this.$emit('submit', this.form);
|
this.$emit('submit', this.form);
|
||||||
|
|
||||||
// 在父组件处理完成后,通过watch visible的变化来重置saving状态
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
this.saving = false; // 取消时重置状态
|
this.saving = false; // 取消时重置状态
|
||||||
this.$emit('cancel');
|
this.$emit('cancel');
|
||||||
|
},
|
||||||
|
|
||||||
|
// 提供给父组件调用以重置saving状态
|
||||||
|
resetSaving() {
|
||||||
|
this.saving = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
visible(newVal) {
|
visible(newVal) {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
this.dialogKey = Date.now();
|
this.dialogKey = Date.now();
|
||||||
|
} else {
|
||||||
|
// 当对话框关闭时,重置saving状态
|
||||||
|
this.saving = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,9 +151,7 @@ export default {
|
|||||||
providerCode: [{ required: true, message: this.$t('providerDialog.requiredCode'), trigger: 'blur' }],
|
providerCode: [{ required: true, message: this.$t('providerDialog.requiredCode'), trigger: 'blur' }],
|
||||||
name: [{ required: true, message: this.$t('providerDialog.requiredName'), trigger: 'blur' }]
|
name: [{ required: true, message: this.$t('providerDialog.requiredName'), trigger: 'blur' }]
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
hasIncompleteFields() {
|
hasIncompleteFields() {
|
||||||
return this.form.fields && this.form.fields.some(field =>
|
return this.form.fields && this.form.fields.some(field =>
|
||||||
!field.key || !field.label || !field.type
|
!field.key || !field.label || !field.type
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
export default {
|
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组件文本
|
// HeaderBar组件文本
|
||||||
'header.smartManagement': 'Agents',
|
'header.smartManagement': 'Agents',
|
||||||
'header.modelConfig': 'Models',
|
'header.modelConfig': 'Models',
|
||||||
@@ -8,6 +15,137 @@ export default {
|
|||||||
'header.paramManagement': 'Params Management',
|
'header.paramManagement': 'Params Management',
|
||||||
'header.dictManagement': 'Dict Management',
|
'header.dictManagement': 'Dict Management',
|
||||||
|
|
||||||
|
// 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
|
// Dictionary data dialog related
|
||||||
'dictDataDialog.addDictData': 'Add Dictionary Data',
|
'dictDataDialog.addDictData': 'Add Dictionary Data',
|
||||||
'dictDataDialog.dictLabel': 'Dictionary Label',
|
'dictDataDialog.dictLabel': 'Dictionary Label',
|
||||||
@@ -89,6 +227,38 @@ export default {
|
|||||||
|
|
||||||
// Manual add device dialog related
|
// Manual add device dialog related
|
||||||
'manualAddDeviceDialog.title': 'Manual Add Device',
|
'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.deviceType': 'Device Type',
|
||||||
'manualAddDeviceDialog.deviceTypePlaceholder': 'Please select device type',
|
'manualAddDeviceDialog.deviceTypePlaceholder': 'Please select device type',
|
||||||
'manualAddDeviceDialog.firmwareVersion': 'Firmware Version',
|
'manualAddDeviceDialog.firmwareVersion': 'Firmware Version',
|
||||||
@@ -273,7 +443,6 @@ export default {
|
|||||||
'device.input6DigitCode': 'Please enter a 6-digit verification code',
|
'device.input6DigitCode': 'Please enter a 6-digit verification code',
|
||||||
'device.bindSuccess': 'Device binding successful',
|
'device.bindSuccess': 'Device binding successful',
|
||||||
'device.bindFailed': 'Binding failed',
|
'device.bindFailed': 'Binding failed',
|
||||||
// DeviceManagement page extended translations
|
|
||||||
'device.searchPlaceholder': 'Please enter device model or Mac address to search',
|
'device.searchPlaceholder': 'Please enter device model or Mac address to search',
|
||||||
'device.model': 'Device Model',
|
'device.model': 'Device Model',
|
||||||
'device.macAddress': 'Mac Address',
|
'device.macAddress': 'Mac Address',
|
||||||
@@ -285,9 +454,11 @@ export default {
|
|||||||
'device.operation': 'Operation',
|
'device.operation': 'Operation',
|
||||||
'device.search': 'Search',
|
'device.search': 'Search',
|
||||||
'device.selectAll': 'Select All/Deselect All',
|
'device.selectAll': 'Select All/Deselect All',
|
||||||
'device.bindWithCode': 'Bind with Verification Code',
|
'deviceManagement.loading': 'Loading...',
|
||||||
|
'device.bindWithCode': '6-digit Verification Code Binding',
|
||||||
'device.manualAdd': 'Manual Add',
|
'device.manualAdd': 'Manual Add',
|
||||||
'device.unbind': 'Unbind',
|
'device.unbind': 'Unbind',
|
||||||
|
'device.toolCall': 'Tool Call',
|
||||||
'device.selectAtLeastOne': 'Please select at least one record',
|
'device.selectAtLeastOne': 'Please select at least one record',
|
||||||
'device.confirmBatchUnbind': 'Are you sure you want to unbind {count} selected devices?',
|
'device.confirmBatchUnbind': 'Are you sure you want to unbind {count} selected devices?',
|
||||||
'device.batchUnbindSuccess': 'Successfully unbound {count} devices',
|
'device.batchUnbindSuccess': 'Successfully unbound {count} devices',
|
||||||
@@ -303,6 +474,9 @@ export default {
|
|||||||
'device.autoUpdateDisabled': 'Auto update disabled',
|
'device.autoUpdateDisabled': 'Auto update disabled',
|
||||||
'device.batchUnbindSuccess': 'Successfully unbound {count} devices',
|
'device.batchUnbindSuccess': 'Successfully unbound {count} devices',
|
||||||
'device.getFirmwareTypeFailed': 'Failed to fetch firmware type',
|
'device.getFirmwareTypeFailed': 'Failed to fetch firmware type',
|
||||||
|
'device.deviceStatus': 'Status',
|
||||||
|
'device.online': 'Online',
|
||||||
|
'device.offline': 'Offline',
|
||||||
|
|
||||||
// Message tips
|
// Message tips
|
||||||
'message.success': 'Operation Successful',
|
'message.success': 'Operation Successful',
|
||||||
@@ -583,7 +757,8 @@ export default {
|
|||||||
'roleConfig.llm': 'Language Model',
|
'roleConfig.llm': 'Language Model',
|
||||||
'roleConfig.vllm': 'Vision Model',
|
'roleConfig.vllm': 'Vision Model',
|
||||||
'roleConfig.tts': 'Text-to-Speech',
|
'roleConfig.tts': 'Text-to-Speech',
|
||||||
'roleConfig.memory': 'Memory',
|
'roleConfig.memoryHis': 'Memory',
|
||||||
|
'roleConfig.memory': 'Memory Model',
|
||||||
'roleConfig.intent': 'Intent Recognition',
|
'roleConfig.intent': 'Intent Recognition',
|
||||||
'roleConfig.voiceType': 'Voice Type',
|
'roleConfig.voiceType': 'Voice Type',
|
||||||
'roleConfig.pleaseEnterContent': 'Please enter content',
|
'roleConfig.pleaseEnterContent': 'Please enter content',
|
||||||
@@ -591,8 +766,8 @@ export default {
|
|||||||
'roleConfig.pleaseEnterLangName': 'Please enter interaction language, e.g.: English',
|
'roleConfig.pleaseEnterLangName': 'Please enter interaction language, e.g.: English',
|
||||||
'roleConfig.pleaseSelect': 'Please select',
|
'roleConfig.pleaseSelect': 'Please select',
|
||||||
'roleConfig.editFunctions': 'Edit Functions',
|
'roleConfig.editFunctions': 'Edit Functions',
|
||||||
'roleConfig.reportText': 'Report Text',
|
'roleConfig.reportText': 'Text Only',
|
||||||
'roleConfig.reportTextVoice': 'Report Text + Voice',
|
'roleConfig.reportTextVoice': 'Text & Voice',
|
||||||
'roleConfig.saveSuccess': 'Configuration saved successfully',
|
'roleConfig.saveSuccess': 'Configuration saved successfully',
|
||||||
'roleConfig.saveFailed': 'Configuration save failed',
|
'roleConfig.saveFailed': 'Configuration save failed',
|
||||||
'roleConfig.confirmReset': 'Are you sure you want to reset the configuration?',
|
'roleConfig.confirmReset': 'Are you sure you want to reset the configuration?',
|
||||||
@@ -685,6 +860,8 @@ export default {
|
|||||||
// Model Configuration Dialog Text
|
// Model Configuration Dialog Text
|
||||||
'modelConfigDialog.addModel': 'Add Model',
|
'modelConfigDialog.addModel': 'Add Model',
|
||||||
'modelConfigDialog.editModel': 'Edit Model',
|
'modelConfigDialog.editModel': 'Edit Model',
|
||||||
|
'modelConfigDialog.duplicateModel': 'Duplicate Model',
|
||||||
|
'modelConfigDialog.copySuffix': '_copy',
|
||||||
'modelConfigDialog.modelInfo': 'Model Information',
|
'modelConfigDialog.modelInfo': 'Model Information',
|
||||||
'modelConfigDialog.enable': 'Enable',
|
'modelConfigDialog.enable': 'Enable',
|
||||||
'modelConfigDialog.setDefault': 'Set as Default',
|
'modelConfigDialog.setDefault': 'Set as Default',
|
||||||
@@ -718,6 +895,9 @@ export default {
|
|||||||
'ttsModel.referenceText': 'Reference Audio Text',
|
'ttsModel.referenceText': 'Reference Audio Text',
|
||||||
'ttsModel.enterReferenceText': 'This is the text for reference audio',
|
'ttsModel.enterReferenceText': 'This is the text for reference audio',
|
||||||
'ttsModel.action': 'Action',
|
'ttsModel.action': 'Action',
|
||||||
|
'ttsModel.operation': 'Operation',
|
||||||
|
'ttsModel.operationFailed': 'Operation failed',
|
||||||
|
'ttsModel.operationClosed': 'Operation closed',
|
||||||
'ttsModel.edit': 'Edit',
|
'ttsModel.edit': 'Edit',
|
||||||
'ttsModel.delete': 'Delete',
|
'ttsModel.delete': 'Delete',
|
||||||
'ttsModel.save': 'Save',
|
'ttsModel.save': 'Save',
|
||||||
@@ -727,6 +907,8 @@ export default {
|
|||||||
'ttsModel.fetchVoicesFailed': 'Failed to fetch voice list',
|
'ttsModel.fetchVoicesFailed': 'Failed to fetch voice list',
|
||||||
'ttsModel.loadVoicesFailed': 'Failed to load voice data',
|
'ttsModel.loadVoicesFailed': 'Failed to load voice data',
|
||||||
'ttsModel.unnamedVoice': 'Unnamed Voice',
|
'ttsModel.unnamedVoice': 'Unnamed Voice',
|
||||||
|
'ttsModel.finishEditingFirst': 'Please finish current editing first',
|
||||||
|
'ttsModel.selectVoiceToDelete': 'Please select voices to delete',
|
||||||
|
|
||||||
// OTA Management Page Text
|
// OTA Management Page Text
|
||||||
'otaManagement.firmwareManagement': 'Firmware Management',
|
'otaManagement.firmwareManagement': 'Firmware Management',
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
export default {
|
export default {
|
||||||
|
// 登录页面相关提示文本
|
||||||
|
'login.requiredUsername': '用户名不能为空',
|
||||||
|
'login.requiredPassword': '密码不能为空',
|
||||||
|
'login.requiredCaptcha': '验证码不能为空',
|
||||||
|
'login.requiredMobile': '请输入正确的手机号码',
|
||||||
|
'login.loginSuccess': '登录成功!',
|
||||||
|
|
||||||
// HeaderBar组件文本
|
// HeaderBar组件文本
|
||||||
'header.smartManagement': '智能体管理',
|
'header.smartManagement': '智能体管理',
|
||||||
'header.modelConfig': '模型配置',
|
'header.modelConfig': '模型配置',
|
||||||
@@ -8,6 +15,137 @@ export default {
|
|||||||
'header.paramManagement': '参数管理',
|
'header.paramManagement': '参数管理',
|
||||||
'header.dictManagement': '字典管理',
|
'header.dictManagement': '字典管理',
|
||||||
|
|
||||||
|
// 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.addDictData': '新增字典数据',
|
||||||
'dictDataDialog.dictLabel': '字典标签',
|
'dictDataDialog.dictLabel': '字典标签',
|
||||||
@@ -89,6 +227,38 @@ export default {
|
|||||||
|
|
||||||
// 手動添加設備對話框相關
|
// 手動添加設備對話框相關
|
||||||
'manualAddDeviceDialog.title': '手动添加设备',
|
'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.deviceType': '设备型号',
|
||||||
'manualAddDeviceDialog.deviceTypePlaceholder': '请选择设备型号',
|
'manualAddDeviceDialog.deviceTypePlaceholder': '请选择设备型号',
|
||||||
'manualAddDeviceDialog.firmwareVersion': '固件版本',
|
'manualAddDeviceDialog.firmwareVersion': '固件版本',
|
||||||
@@ -285,9 +455,11 @@ export default {
|
|||||||
'device.operation': '操作',
|
'device.operation': '操作',
|
||||||
'device.search': '搜索',
|
'device.search': '搜索',
|
||||||
'device.selectAll': '全选/取消全选',
|
'device.selectAll': '全选/取消全选',
|
||||||
'device.bindWithCode': '验证码绑定',
|
'deviceManagement.loading': '拼命加载中',
|
||||||
|
'device.bindWithCode': '6位验证码绑定',
|
||||||
'device.manualAdd': '手动添加',
|
'device.manualAdd': '手动添加',
|
||||||
'device.unbind': '解绑',
|
'device.unbind': '解绑',
|
||||||
|
'device.toolCall': '工具调用',
|
||||||
'device.selectAtLeastOne': '请至少选择一条记录',
|
'device.selectAtLeastOne': '请至少选择一条记录',
|
||||||
'device.confirmBatchUnbind': '确认要解绑选中的 {count} 台设备吗?',
|
'device.confirmBatchUnbind': '确认要解绑选中的 {count} 台设备吗?',
|
||||||
'device.batchUnbindSuccess': '成功解绑 {count} 台设备',
|
'device.batchUnbindSuccess': '成功解绑 {count} 台设备',
|
||||||
@@ -303,6 +475,9 @@ export default {
|
|||||||
'device.autoUpdateDisabled': '已关闭自动升级',
|
'device.autoUpdateDisabled': '已关闭自动升级',
|
||||||
'device.batchUnbindSuccess': '成功解绑 {count} 台设备',
|
'device.batchUnbindSuccess': '成功解绑 {count} 台设备',
|
||||||
'device.getFirmwareTypeFailed': '获取固件类型失败',
|
'device.getFirmwareTypeFailed': '获取固件类型失败',
|
||||||
|
'device.deviceStatus': '状态',
|
||||||
|
'device.online': '在线',
|
||||||
|
'device.offline': '离线',
|
||||||
|
|
||||||
// 消息提示
|
// 消息提示
|
||||||
'message.success': '操作成功',
|
'message.success': '操作成功',
|
||||||
@@ -582,7 +757,8 @@ export default {
|
|||||||
'roleConfig.llm': '大语言模型(LLM)',
|
'roleConfig.llm': '大语言模型(LLM)',
|
||||||
'roleConfig.vllm': '视觉大模型(VLLM)',
|
'roleConfig.vllm': '视觉大模型(VLLM)',
|
||||||
'roleConfig.intent': '意图识别(Intent)',
|
'roleConfig.intent': '意图识别(Intent)',
|
||||||
'roleConfig.memory': '记忆(Memory)',
|
'roleConfig.memoryHis': '记忆',
|
||||||
|
'roleConfig.memory': '记忆模式',
|
||||||
'roleConfig.tts': '语音合成(TTS)',
|
'roleConfig.tts': '语音合成(TTS)',
|
||||||
'roleConfig.voiceType': '声音音色(Voice)',
|
'roleConfig.voiceType': '声音音色(Voice)',
|
||||||
'roleConfig.pleaseEnterContent': '请输入内容',
|
'roleConfig.pleaseEnterContent': '请输入内容',
|
||||||
@@ -684,6 +860,8 @@ export default {
|
|||||||
// 模型配置对话框文本
|
// 模型配置对话框文本
|
||||||
'modelConfigDialog.addModel': '添加模型',
|
'modelConfigDialog.addModel': '添加模型',
|
||||||
'modelConfigDialog.editModel': '修改模型',
|
'modelConfigDialog.editModel': '修改模型',
|
||||||
|
'modelConfigDialog.duplicateModel': '创建副本',
|
||||||
|
'modelConfigDialog.copySuffix': '_副本',
|
||||||
'modelConfigDialog.modelInfo': '模型信息',
|
'modelConfigDialog.modelInfo': '模型信息',
|
||||||
'modelConfigDialog.enable': '是否启用',
|
'modelConfigDialog.enable': '是否启用',
|
||||||
'modelConfigDialog.setDefault': '设为默认',
|
'modelConfigDialog.setDefault': '设为默认',
|
||||||
@@ -717,6 +895,9 @@ export default {
|
|||||||
'ttsModel.referenceText': '克隆音频文本',
|
'ttsModel.referenceText': '克隆音频文本',
|
||||||
'ttsModel.enterReferenceText': '这里是克隆音频对应文本',
|
'ttsModel.enterReferenceText': '这里是克隆音频对应文本',
|
||||||
'ttsModel.action': '操作',
|
'ttsModel.action': '操作',
|
||||||
|
'ttsModel.operation': '操作',
|
||||||
|
'ttsModel.operationFailed': '操作失败',
|
||||||
|
'ttsModel.operationClosed': '操作已关闭',
|
||||||
'ttsModel.edit': '编辑',
|
'ttsModel.edit': '编辑',
|
||||||
'ttsModel.delete': '删除',
|
'ttsModel.delete': '删除',
|
||||||
'ttsModel.save': '保存',
|
'ttsModel.save': '保存',
|
||||||
@@ -726,6 +907,8 @@ export default {
|
|||||||
'ttsModel.fetchVoicesFailed': '获取音色列表失败',
|
'ttsModel.fetchVoicesFailed': '获取音色列表失败',
|
||||||
'ttsModel.loadVoicesFailed': '加载音色数据失败',
|
'ttsModel.loadVoicesFailed': '加载音色数据失败',
|
||||||
'ttsModel.unnamedVoice': '未命名音色',
|
'ttsModel.unnamedVoice': '未命名音色',
|
||||||
|
'ttsModel.finishEditingFirst': '请先完成当前编辑',
|
||||||
|
'ttsModel.selectVoiceToDelete': '请选择要删除的音色',
|
||||||
|
|
||||||
// OTA管理页面文本
|
// OTA管理页面文本
|
||||||
'otaManagement.firmwareManagement': '固件管理',
|
'otaManagement.firmwareManagement': '固件管理',
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
export default {
|
export default {
|
||||||
|
// 登錄頁面相關提示文本
|
||||||
|
'login.requiredUsername': '用戶名不能為空',
|
||||||
|
'login.requiredPassword': '密碼不能為空',
|
||||||
|
'login.requiredCaptcha': '驗證碼不能為空',
|
||||||
|
'login.requiredMobile': '請輸入正確的手機號碼',
|
||||||
|
'login.loginSuccess': '登錄成功!',
|
||||||
|
|
||||||
// HeaderBar组件文本
|
// HeaderBar组件文本
|
||||||
'header.smartManagement': '智能體管理',
|
'header.smartManagement': '智能體管理',
|
||||||
'header.modelConfig': '模型配置',
|
'header.modelConfig': '模型配置',
|
||||||
@@ -8,6 +15,137 @@ export default {
|
|||||||
'header.paramManagement': '參數管理',
|
'header.paramManagement': '參數管理',
|
||||||
'header.dictManagement': '字典管理',
|
'header.dictManagement': '字典管理',
|
||||||
|
|
||||||
|
// 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.addDictData': '新增字典數據',
|
||||||
'dictDataDialog.dictLabel': '字典標籤',
|
'dictDataDialog.dictLabel': '字典標籤',
|
||||||
@@ -89,6 +227,38 @@ export default {
|
|||||||
|
|
||||||
// 手動添加設備對話框相關
|
// 手動添加設備對話框相關
|
||||||
'manualAddDeviceDialog.title': '手動添加設備',
|
'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.deviceType': '設備型號',
|
||||||
'manualAddDeviceDialog.deviceTypePlaceholder': '請選擇設備型號',
|
'manualAddDeviceDialog.deviceTypePlaceholder': '請選擇設備型號',
|
||||||
'manualAddDeviceDialog.firmwareVersion': '固件版本',
|
'manualAddDeviceDialog.firmwareVersion': '固件版本',
|
||||||
@@ -273,7 +443,6 @@ export default {
|
|||||||
'device.input6DigitCode': '請輸入6位數字驗證碼',
|
'device.input6DigitCode': '請輸入6位數字驗證碼',
|
||||||
'device.bindSuccess': '設備綁定成功',
|
'device.bindSuccess': '設備綁定成功',
|
||||||
'device.bindFailed': '綁定失敗',
|
'device.bindFailed': '綁定失敗',
|
||||||
// DeviceManagement頁面擴展翻譯
|
|
||||||
'device.searchPlaceholder': '請輸入設備型號或Mac地址查詢',
|
'device.searchPlaceholder': '請輸入設備型號或Mac地址查詢',
|
||||||
'device.model': '設備型號',
|
'device.model': '設備型號',
|
||||||
'device.macAddress': 'Mac地址',
|
'device.macAddress': 'Mac地址',
|
||||||
@@ -285,9 +454,11 @@ export default {
|
|||||||
'device.operation': '操作',
|
'device.operation': '操作',
|
||||||
'device.search': '搜索',
|
'device.search': '搜索',
|
||||||
'device.selectAll': '全選/取消全選',
|
'device.selectAll': '全選/取消全選',
|
||||||
'device.bindWithCode': '驗證碼綁定',
|
'deviceManagement.loading': '拼命加載中',
|
||||||
|
'device.bindWithCode': '6位驗證碼綁定',
|
||||||
'device.manualAdd': '手動添加',
|
'device.manualAdd': '手動添加',
|
||||||
'device.unbind': '解綁',
|
'device.unbind': '解綁',
|
||||||
|
'device.toolCall': '工具調用',
|
||||||
'device.selectAtLeastOne': '請至少選擇一條記錄',
|
'device.selectAtLeastOne': '請至少選擇一條記錄',
|
||||||
'device.confirmBatchUnbind': '確認要解綁選中的 {count} 台設備嗎?',
|
'device.confirmBatchUnbind': '確認要解綁選中的 {count} 台設備嗎?',
|
||||||
'device.batchUnbindSuccess': '成功解綁 {count} 台設備',
|
'device.batchUnbindSuccess': '成功解綁 {count} 台設備',
|
||||||
@@ -303,6 +474,9 @@ export default {
|
|||||||
'device.autoUpdateDisabled': '已關閉自動升級',
|
'device.autoUpdateDisabled': '已關閉自動升級',
|
||||||
'device.batchUnbindSuccess': '成功解綁 {count} 台設備',
|
'device.batchUnbindSuccess': '成功解綁 {count} 台設備',
|
||||||
'device.getFirmwareTypeFailed': '獲取固件類型失敗',
|
'device.getFirmwareTypeFailed': '獲取固件類型失敗',
|
||||||
|
'device.deviceStatus': '狀態',
|
||||||
|
'device.online': '在線',
|
||||||
|
'device.offline': '離線',
|
||||||
|
|
||||||
// 消息提示
|
// 消息提示
|
||||||
'message.success': '操作成功',
|
'message.success': '操作成功',
|
||||||
@@ -583,7 +757,8 @@ export default {
|
|||||||
'roleConfig.llm': '大語言模型(LLM)',
|
'roleConfig.llm': '大語言模型(LLM)',
|
||||||
'roleConfig.vllm': '視覺大模型(VLLM)',
|
'roleConfig.vllm': '視覺大模型(VLLM)',
|
||||||
'roleConfig.tts': '語音合成(TTS)',
|
'roleConfig.tts': '語音合成(TTS)',
|
||||||
'roleConfig.memory': '記憶(Memory)',
|
'roleConfig.memoryHis': '記憶',
|
||||||
|
'roleConfig.memory': '記憶模式',
|
||||||
'roleConfig.intent': '意圖識別(Intent)',
|
'roleConfig.intent': '意圖識別(Intent)',
|
||||||
'roleConfig.voiceType': '聲音音色(Voice)',
|
'roleConfig.voiceType': '聲音音色(Voice)',
|
||||||
'roleConfig.pleaseEnterContent': '請輸入內容',
|
'roleConfig.pleaseEnterContent': '請輸入內容',
|
||||||
@@ -685,6 +860,8 @@ export default {
|
|||||||
// 模型配置對話框文本
|
// 模型配置對話框文本
|
||||||
'modelConfigDialog.addModel': '添加模型',
|
'modelConfigDialog.addModel': '添加模型',
|
||||||
'modelConfigDialog.editModel': '修改模型',
|
'modelConfigDialog.editModel': '修改模型',
|
||||||
|
'modelConfigDialog.duplicateModel': '建立副本',
|
||||||
|
'modelConfigDialog.copySuffix': '_副本',
|
||||||
'modelConfigDialog.modelInfo': '模型信息',
|
'modelConfigDialog.modelInfo': '模型信息',
|
||||||
'modelConfigDialog.enable': '是否啟用',
|
'modelConfigDialog.enable': '是否啟用',
|
||||||
'modelConfigDialog.setDefault': '設為默認',
|
'modelConfigDialog.setDefault': '設為默認',
|
||||||
@@ -718,6 +895,7 @@ export default {
|
|||||||
'ttsModel.referenceText': '克隆音頻文本',
|
'ttsModel.referenceText': '克隆音頻文本',
|
||||||
'ttsModel.enterReferenceText': '這裡是克隆音頻對應文本',
|
'ttsModel.enterReferenceText': '這裡是克隆音頻對應文本',
|
||||||
'ttsModel.action': '操作',
|
'ttsModel.action': '操作',
|
||||||
|
'ttsModel.operation': '操作',
|
||||||
'ttsModel.edit': '編輯',
|
'ttsModel.edit': '編輯',
|
||||||
'ttsModel.delete': '刪除',
|
'ttsModel.delete': '刪除',
|
||||||
'ttsModel.save': '保存',
|
'ttsModel.save': '保存',
|
||||||
@@ -727,6 +905,10 @@ export default {
|
|||||||
'ttsModel.fetchVoicesFailed': '獲取音色列表失敗',
|
'ttsModel.fetchVoicesFailed': '獲取音色列表失敗',
|
||||||
'ttsModel.loadVoicesFailed': '加載音色數據失敗',
|
'ttsModel.loadVoicesFailed': '加載音色數據失敗',
|
||||||
'ttsModel.unnamedVoice': '未命名音色',
|
'ttsModel.unnamedVoice': '未命名音色',
|
||||||
|
'ttsModel.finishEditingFirst': '請先完成目前編輯',
|
||||||
|
'ttsModel.selectVoiceToDelete': '請選擇要刪除的音色',
|
||||||
|
'ttsModel.operationFailed': '操作失敗',
|
||||||
|
'ttsModel.operationClosed': '操作已關閉',
|
||||||
|
|
||||||
// OTA管理頁面文本
|
// OTA管理頁面文本
|
||||||
'otaManagement.firmwareManagement': '固件管理',
|
'otaManagement.firmwareManagement': '固件管理',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import ElementUI from 'element-ui';
|
|
||||||
import 'element-ui/lib/theme-chalk/index.css';
|
import 'element-ui/lib/theme-chalk/index.css';
|
||||||
import 'normalize.css/normalize.css'; // A modern alternative to CSS resets
|
import 'normalize.css/normalize.css'; // A modern alternative to CSS resets
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import ElementUI from 'element-ui';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<HeaderBar/>
|
<HeaderBar />
|
||||||
|
|
||||||
<div class="operation-bar">
|
<div class="operation-bar">
|
||||||
<h2 class="page-title">{{ $t('device.management') }}</h2>
|
<h2 class="page-title">{{ $t('device.management') }}</h2>
|
||||||
<div class="right-operations">
|
<div class="right-operations">
|
||||||
<el-input :placeholder="$t('device.searchPlaceholder')" v-model="searchKeyword" class="search-input"
|
<el-input :placeholder="$t('device.searchPlaceholder')" v-model="searchKeyword" class="search-input"
|
||||||
@keyup.enter.native="handleSearch" clearable/>
|
@keyup.enter.native="handleSearch" clearable />
|
||||||
<el-button class="btn-search" @click="handleSearch">{{ $t('device.search') }}</el-button>
|
<el-button class="btn-search" @click="handleSearch">{{ $t('device.search') }}</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<el-card class="device-card" shadow="never">
|
<el-card class="device-card" shadow="never">
|
||||||
<el-table ref="deviceTable" :data="paginatedDeviceList" class="transparent-table"
|
<el-table ref="deviceTable" :data="paginatedDeviceList" class="transparent-table"
|
||||||
:header-cell-class-name="headerCellClassName" v-loading="loading"
|
:header-cell-class-name="headerCellClassName" v-loading="loading"
|
||||||
:element-loading-text="$t('message.loading')"
|
:element-loading-text="$t('deviceManagement.loading')" element-loading-spinner="el-icon-loading"
|
||||||
element-loading-spinner="el-icon-loading" element-loading-background="rgba(255, 255, 255, 0.7)">
|
element-loading-background="rgba(255, 255, 255, 0.7)">
|
||||||
<el-table-column :label="$t('modelConfig.select')" align="center" width="120">
|
<el-table-column :label="$t('modelConfig.select')" align="center" width="120">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-checkbox v-model="scope.row.selected"></el-checkbox>
|
<el-checkbox v-model="scope.row.selected"></el-checkbox>
|
||||||
@@ -29,74 +29,83 @@
|
|||||||
{{ getFirmwareTypeName(scope.row.model) }}
|
{{ getFirmwareTypeName(scope.row.model) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('device.firmwareVersion')" prop="firmwareVersion" align="center"></el-table-column>
|
<el-table-column :label="$t('device.firmwareVersion')" prop="firmwareVersion"
|
||||||
|
align="center"></el-table-column>
|
||||||
<el-table-column :label="$t('device.macAddress')" prop="macAddress" align="center"></el-table-column>
|
<el-table-column :label="$t('device.macAddress')" prop="macAddress" align="center"></el-table-column>
|
||||||
<el-table-column :label="$t('device.bindTime')" prop="bindTime" align="center"></el-table-column>
|
<el-table-column :label="$t('device.bindTime')" prop="bindTime" align="center"></el-table-column>
|
||||||
<el-table-column :label="$t('device.lastConversation')" prop="lastConversation" align="center"></el-table-column>
|
<el-table-column :label="$t('device.lastConversation')" prop="lastConversation"
|
||||||
|
align="center"></el-table-column>
|
||||||
|
<el-table-column :label="$t('device.deviceStatus')" prop="deviceStatus" align="center">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag v-if="scope.row.deviceStatus === 'online'" type="success">{{ $t('device.online') }}</el-tag>
|
||||||
|
<el-tag v-else type="danger">{{ $t('device.offline') }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column :label="$t('device.remark')" align="center">
|
<el-table-column :label="$t('device.remark')" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input
|
<el-input v-show="row.isEdit" v-model="row.remark" size="mini" maxlength="64" show-word-limit
|
||||||
v-show="row.isEdit"
|
@blur="onRemarkBlur(row)" @keyup.enter.native="onRemarkEnter(row)" />
|
||||||
v-model="row.remark"
|
|
||||||
size="mini"
|
|
||||||
maxlength="64"
|
|
||||||
show-word-limit
|
|
||||||
@blur="onRemarkBlur(row)"
|
|
||||||
@keyup.enter.native="onRemarkEnter(row)"
|
|
||||||
/>
|
|
||||||
<span v-show="!row.isEdit" class="remark-view">
|
<span v-show="!row.isEdit" class="remark-view">
|
||||||
<i
|
<i class="el-icon-edit" @click="row.isEdit = true" style="cursor: pointer;"></i>
|
||||||
class="el-icon-edit"
|
<span @click="row.isEdit = true">
|
||||||
@click="row.isEdit = true"
|
{{ row.remark || '-' }}
|
||||||
style="cursor: pointer;"
|
</span>
|
||||||
></i>
|
|
||||||
<span @click="row.isEdit = true">
|
|
||||||
{{ row.remark || '-' }}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('device.autoUpdate')" align="center">
|
<el-table-column :label="$t('device.autoUpdate')" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-switch v-model="scope.row.otaSwitch" size="mini" active-color="#13ce66" inactive-color="#ff4949"
|
<el-switch v-model="scope.row.otaSwitch" size="mini" active-color="#13ce66" inactive-color="#ff4949"
|
||||||
@change="handleOtaSwitchChange(scope.row)"></el-switch>
|
@change="handleOtaSwitchChange(scope.row)"></el-switch>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('device.operation')" align="center">
|
<el-table-column :label="$t('device.operation')" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button size="mini" type="text" @click="handleUnbind(scope.row.device_id)">
|
<el-button size="mini" type="text" @click="handleUnbind(scope.row.device_id)">
|
||||||
{{ $t('device.unbind') }}
|
{{ $t('device.unbind') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button v-if="scope.row.deviceStatus === 'online'" size="mini" type="text" @click="handleMcpToolCall(scope.row.device_id)">
|
||||||
|
{{ $t('device.toolCall') }}
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<div class="table_bottom">
|
<div class="table_bottom">
|
||||||
<div class="ctrl_btn">
|
<div class="ctrl_btn">
|
||||||
<el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">
|
<el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">
|
||||||
{{ isCurrentPageAllSelected ? $t('common.deselectAll') : $t('common.selectAll') }}
|
{{ isCurrentPageAllSelected ? $t('common.deselectAll') : $t('common.selectAll') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="success" size="mini" class="add-device-btn" @click="handleAddDevice">
|
<el-button type="success" size="mini" class="add-device-btn" @click="handleAddDevice">
|
||||||
{{ $t('device.bindWithCode') }}
|
{{ $t('device.bindWithCode') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="success" size="mini" class="add-device-btn" @click="handleManualAddDevice">
|
<el-button type="success" size="mini" class="add-device-btn" @click="handleManualAddDevice">
|
||||||
{{ $t('device.manualAdd') }}
|
{{ $t('device.manualAdd') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteSelected">{{ $t('device.unbind') }}</el-button>
|
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteSelected">{{
|
||||||
</div>
|
$t('device.unbind')
|
||||||
|
}}</el-button>
|
||||||
|
</div>
|
||||||
<div class="custom-pagination">
|
<div class="custom-pagination">
|
||||||
<el-select v-model="pageSize" @change="handlePageSizeChange" class="page-size-select">
|
<el-select v-model="pageSize" @change="handlePageSizeChange" class="page-size-select">
|
||||||
<el-option v-for="item in pageSizeOptions" :key="item" :label="$t('dictManagement.itemsPerPage').replace('{items}', item)" :value="item">
|
<el-option v-for="item in pageSizeOptions" :key="item"
|
||||||
</el-option>
|
:label="$t('dictManagement.itemsPerPage').replace('{items}', item)" :value="item">
|
||||||
</el-select>
|
</el-option>
|
||||||
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">{{ $t('dictManagement.firstPage') }}</button>
|
</el-select>
|
||||||
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">{{ $t('dictManagement.prevPage') }}</button>
|
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">{{
|
||||||
|
$t('dictManagement.firstPage')
|
||||||
|
}}</button>
|
||||||
|
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">{{
|
||||||
|
$t('dictManagement.prevPage')
|
||||||
|
}}</button>
|
||||||
<button v-for="page in visiblePages" :key="page" class="pagination-btn"
|
<button v-for="page in visiblePages" :key="page" class="pagination-btn"
|
||||||
:class="{ active: page === currentPage }" @click="goToPage(page)">
|
:class="{ active: page === currentPage }" @click="goToPage(page)">
|
||||||
{{ page }}
|
{{ page }}
|
||||||
</button>
|
</button>
|
||||||
<button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">{{ $t('dictManagement.nextPage') }}</button>
|
<button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">{{
|
||||||
<span class="total-text">{{ $t('dictManagement.totalRecords').replace('{total}', deviceList.length) }}</span>
|
$t('dictManagement.nextPage') }}</button>
|
||||||
|
<span class="total-text">{{ $t('dictManagement.totalRecords').replace('{total}', deviceList.length)
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -105,9 +114,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AddDeviceDialog :visible.sync="addDeviceDialogVisible" :agent-id="currentAgentId"
|
<AddDeviceDialog :visible.sync="addDeviceDialogVisible" :agent-id="currentAgentId"
|
||||||
@refresh="fetchBindDevices(currentAgentId)"/>
|
@refresh="fetchBindDevices(currentAgentId)" />
|
||||||
<ManualAddDeviceDialog :visible.sync="manualAddDeviceDialogVisible" :agent-id="currentAgentId"
|
<ManualAddDeviceDialog :visible.sync="manualAddDeviceDialogVisible" :agent-id="currentAgentId"
|
||||||
@refresh="fetchBindDevices(currentAgentId)"/>
|
@refresh="fetchBindDevices(currentAgentId)" />
|
||||||
|
<McpToolCallDialog :visible.sync="mcpToolCallDialogVisible" :device-id="selectedDeviceId" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -115,19 +125,23 @@
|
|||||||
<script>
|
<script>
|
||||||
import Api from '@/apis/api';
|
import Api from '@/apis/api';
|
||||||
import AddDeviceDialog from "@/components/AddDeviceDialog.vue";
|
import AddDeviceDialog from "@/components/AddDeviceDialog.vue";
|
||||||
import ManualAddDeviceDialog from "@/components/ManualAddDeviceDialog.vue";
|
|
||||||
import HeaderBar from "@/components/HeaderBar.vue";
|
import HeaderBar from "@/components/HeaderBar.vue";
|
||||||
|
import ManualAddDeviceDialog from "@/components/ManualAddDeviceDialog.vue";
|
||||||
|
import McpToolCallDialog from "@/components/McpToolCallDialog.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
HeaderBar,
|
HeaderBar,
|
||||||
AddDeviceDialog,
|
AddDeviceDialog,
|
||||||
ManualAddDeviceDialog
|
ManualAddDeviceDialog,
|
||||||
|
McpToolCallDialog
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
addDeviceDialogVisible: false,
|
addDeviceDialogVisible: false,
|
||||||
manualAddDeviceDialogVisible: false,
|
manualAddDeviceDialogVisible: false,
|
||||||
|
mcpToolCallDialogVisible: false,
|
||||||
|
selectedDeviceId: '',
|
||||||
searchKeyword: "",
|
searchKeyword: "",
|
||||||
activeSearchKeyword: "",
|
activeSearchKeyword: "",
|
||||||
currentAgentId: this.$route.query.agentId || '',
|
currentAgentId: this.$route.query.agentId || '',
|
||||||
@@ -145,8 +159,8 @@ export default {
|
|||||||
const keyword = this.activeSearchKeyword.toLowerCase();
|
const keyword = this.activeSearchKeyword.toLowerCase();
|
||||||
if (!keyword) return this.deviceList;
|
if (!keyword) return this.deviceList;
|
||||||
return this.deviceList.filter(device =>
|
return this.deviceList.filter(device =>
|
||||||
(device.model && device.model.toLowerCase().includes(keyword)) ||
|
(device.model && device.model.toLowerCase().includes(keyword)) ||
|
||||||
(device.macAddress && device.macAddress.toLowerCase().includes(keyword))
|
(device.macAddress && device.macAddress.toLowerCase().includes(keyword))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -160,8 +174,8 @@ export default {
|
|||||||
},
|
},
|
||||||
// 计算当前页是否全选
|
// 计算当前页是否全选
|
||||||
isCurrentPageAllSelected() {
|
isCurrentPageAllSelected() {
|
||||||
return this.paginatedDeviceList.length > 0 &&
|
return this.paginatedDeviceList.length > 0 &&
|
||||||
this.paginatedDeviceList.every(device => device.selected);
|
this.paginatedDeviceList.every(device => device.selected);
|
||||||
},
|
},
|
||||||
visiblePages() {
|
visiblePages() {
|
||||||
const pages = [];
|
const pages = [];
|
||||||
@@ -236,7 +250,7 @@ export default {
|
|||||||
batchUnbindDevices(deviceIds) {
|
batchUnbindDevices(deviceIds) {
|
||||||
const promises = deviceIds.map(id => {
|
const promises = deviceIds.map(id => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Api.device.unbindDevice(id, ({data}) => {
|
Api.device.unbindDevice(id, ({ data }) => {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
@@ -246,19 +260,19 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
Promise.all(promises)
|
Promise.all(promises)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$message.success({
|
this.$message.success({
|
||||||
message: this.$t('device.batchUnbindSuccess').replace('{count}', deviceIds.length),
|
message: this.$t('device.batchUnbindSuccess').replace('{count}', deviceIds.length),
|
||||||
showClose: true
|
showClose: true
|
||||||
});
|
|
||||||
this.fetchBindDevices(this.currentAgentId);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
this.$message.error({
|
|
||||||
message: error || this.$t('device.batchUnbindError'),
|
|
||||||
showClose: true
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
this.fetchBindDevices(this.currentAgentId);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.$message.error({
|
||||||
|
message: error || this.$t('device.batchUnbindError'),
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
handleAddDevice() {
|
handleAddDevice() {
|
||||||
this.addDeviceDialogVisible = true;
|
this.addDeviceDialogVisible = true;
|
||||||
@@ -266,6 +280,11 @@ export default {
|
|||||||
handleManualAddDevice() {
|
handleManualAddDevice() {
|
||||||
this.manualAddDeviceDialogVisible = true;
|
this.manualAddDeviceDialogVisible = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleMcpToolCall(deviceId) {
|
||||||
|
this.selectedDeviceId = deviceId;
|
||||||
|
this.mcpToolCallDialogVisible = true;
|
||||||
|
},
|
||||||
submitRemark(row) {
|
submitRemark(row) {
|
||||||
if (row._submitting) return;
|
if (row._submitting) return;
|
||||||
|
|
||||||
@@ -308,18 +327,18 @@ export default {
|
|||||||
cancelButtonText: this.$t('button.cancel'),
|
cancelButtonText: this.$t('button.cancel'),
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
Api.device.unbindDevice(device_id, ({data}) => {
|
Api.device.unbindDevice(device_id, ({ data }) => {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
this.$message.success({
|
this.$message.success({
|
||||||
message: this.$t('device.unbindSuccess'),
|
message: this.$t('device.unbindSuccess'),
|
||||||
showClose: true
|
showClose: true
|
||||||
});
|
});
|
||||||
this.fetchBindDevices(this.$route.query.agentId);
|
this.fetchBindDevices(this.$route.query.agentId);
|
||||||
} else {
|
} else {
|
||||||
this.$message.error({
|
this.$message.error({
|
||||||
message: data.msg || this.$t('device.unbindFailed'),
|
message: data.msg || this.$t('device.unbindFailed'),
|
||||||
showClose: true
|
showClose: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -339,7 +358,7 @@ export default {
|
|||||||
|
|
||||||
fetchBindDevices(agentId) {
|
fetchBindDevices(agentId) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
Api.device.getAgentBindDevices(agentId, ({data}) => {
|
Api.device.getAgentBindDevices(agentId, ({ data }) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
this.deviceList = data.data.map(device => {
|
this.deviceList = data.data.map(device => {
|
||||||
@@ -356,18 +375,74 @@ export default {
|
|||||||
_submitting: false,
|
_submitting: false,
|
||||||
otaSwitch: device.autoUpdate === 1,
|
otaSwitch: device.autoUpdate === 1,
|
||||||
rawBindTime: new Date(device.createDate).getTime(),
|
rawBindTime: new Date(device.createDate).getTime(),
|
||||||
selected: false
|
selected: false,
|
||||||
|
// 初始设置为离线状态
|
||||||
|
deviceStatus: 'offline'
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.sort((a, b) => a.rawBindTime - b.rawBindTime);
|
.sort((a, b) => a.rawBindTime - b.rawBindTime);
|
||||||
this.activeSearchKeyword = "";
|
this.activeSearchKeyword = "";
|
||||||
this.searchKeyword = "";
|
this.searchKeyword = "";
|
||||||
|
|
||||||
|
// 获取设备列表后,立即获取设备状态
|
||||||
|
this.fetchDeviceStatus(agentId);
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(data.msg || this.$t('device.getListFailed'));
|
this.$message.error(data.msg || this.$t('device.getListFailed'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
headerCellClassName({columnIndex}) {
|
|
||||||
|
// 获取设备状态
|
||||||
|
fetchDeviceStatus(agentId) {
|
||||||
|
Api.device.getDeviceStatus(agentId, ({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
try {
|
||||||
|
// 解析后端返回的设备状态JSON
|
||||||
|
const statusData = JSON.parse(data.data);
|
||||||
|
|
||||||
|
// 直接使用解析后的数据作为设备状态映射(不需要devices字段包装)
|
||||||
|
if (statusData && typeof statusData === 'object') {
|
||||||
|
// 更新设备状态
|
||||||
|
this.updateDeviceStatusFromResponse(statusData);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// JSON解析失败,忽略状态更新
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 根据API响应更新设备状态
|
||||||
|
updateDeviceStatusFromResponse(deviceStatusMap) {
|
||||||
|
this.deviceList.forEach(device => {
|
||||||
|
// 构建设备的MQTT客户端ID
|
||||||
|
const macAddress = device.macAddress ? device.macAddress.replace(/:/g, '_') : 'unknown';
|
||||||
|
const groupId = device.model ? device.model.replace(/:/g, '_') : 'GID_default';
|
||||||
|
const mqttClientId = `${groupId}@@@${macAddress}@@@${macAddress}`;
|
||||||
|
|
||||||
|
// 从状态映射中获取设备状态
|
||||||
|
if (deviceStatusMap[mqttClientId]) {
|
||||||
|
const statusInfo = deviceStatusMap[mqttClientId];
|
||||||
|
|
||||||
|
let isOnline = false;
|
||||||
|
if (statusInfo.isAlive === true) {
|
||||||
|
isOnline = true;
|
||||||
|
} else if (statusInfo.isAlive === false) {
|
||||||
|
isOnline = false;
|
||||||
|
} else if (statusInfo.isAlive === null && statusInfo.exists === true) {
|
||||||
|
isOnline = true;
|
||||||
|
} else {
|
||||||
|
isOnline = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
device.deviceStatus = isOnline ? 'online' : 'offline';
|
||||||
|
} else {
|
||||||
|
// 如果没有找到对应的状态信息,默认为离线
|
||||||
|
device.deviceStatus = 'offline';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
headerCellClassName({ columnIndex }) {
|
||||||
if (columnIndex === 0) {
|
if (columnIndex === 0) {
|
||||||
return "custom-selection-header";
|
return "custom-selection-header";
|
||||||
}
|
}
|
||||||
@@ -378,12 +453,12 @@ export default {
|
|||||||
return firmwareType ? firmwareType.name : type
|
return firmwareType ? firmwareType.name : type
|
||||||
},
|
},
|
||||||
updateDeviceInfo(device_id, payload, callback) {
|
updateDeviceInfo(device_id, payload, callback) {
|
||||||
return Api.device.updateDeviceInfo(device_id, payload, ({data}) => {
|
return Api.device.updateDeviceInfo(device_id, payload, ({ data }) => {
|
||||||
callback(data.code === 0, data);
|
callback(data.code === 0, data);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleOtaSwitchChange(row) {
|
handleOtaSwitchChange(row) {
|
||||||
this.updateDeviceInfo(row.device_id, {autoUpdate: row.otaSwitch ? 1 : 0}, (result, {msg}) => {
|
this.updateDeviceInfo(row.device_id, { autoUpdate: row.otaSwitch ? 1 : 0 }, (result, { msg }) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
this.$message.success(row.otaSwitch ? this.$t('device.autoUpdateEnabled') : this.$t('device.autoUpdateDisabled'));
|
this.$message.success(row.otaSwitch ? this.$t('device.autoUpdateEnabled') : this.$t('device.autoUpdateDisabled'));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
<div class="operation-bar">
|
<div class="operation-bar">
|
||||||
<h2 class="page-title">{{ $t('paramManagement.pageTitle') }}</h2>
|
<h2 class="page-title">{{ $t('paramManagement.pageTitle') }}</h2>
|
||||||
<div class="right-operations">
|
<div class="right-operations">
|
||||||
<el-input :placeholder="$t('paramManagement.searchPlaceholder')" v-model="searchCode" class="search-input"
|
<el-input :placeholder="$t('paramManagement.searchPlaceholder')" v-model="searchCode"
|
||||||
@keyup.enter.native="handleSearch" clearable />
|
class="search-input" @keyup.enter.native="handleSearch" clearable />
|
||||||
<el-button class="btn-search" @click="handleSearch">{{ $t('paramManagement.search') }}</el-button>
|
<el-button class="btn-search" @click="handleSearch">{{ $t('paramManagement.search') }}</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -24,25 +24,31 @@
|
|||||||
<el-checkbox v-model="scope.row.selected"></el-checkbox>
|
<el-checkbox v-model="scope.row.selected"></el-checkbox>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('paramManagement.paramCode')" prop="paramCode" align="center"></el-table-column>
|
<el-table-column :label="$t('paramManagement.paramCode')" prop="paramCode"
|
||||||
<el-table-column :label="$t('paramManagement.paramValue')" prop="paramValue" align="center" show-overflow-tooltip>
|
align="center"></el-table-column>
|
||||||
|
<el-table-column :label="$t('paramManagement.paramValue')" prop="paramValue" align="center"
|
||||||
|
show-overflow-tooltip>
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<div v-if="isSensitiveParam(scope.row.paramCode)">
|
<div v-if="isSensitiveParam(scope.row.paramCode)">
|
||||||
<span v-if="!scope.row.showValue">{{ maskSensitiveValue(scope.row.paramValue)
|
<span v-if="!scope.row.showValue">{{ maskSensitiveValue(scope.row.paramValue)
|
||||||
}}</span>
|
}}</span>
|
||||||
<span v-else>{{ scope.row.paramValue }}</span>
|
<span v-else>{{ scope.row.paramValue }}</span>
|
||||||
<el-button size="mini" type="text" @click="toggleSensitiveValue(scope.row)">
|
<el-button size="mini" type="text" @click="toggleSensitiveValue(scope.row)">
|
||||||
{{ scope.row.showValue ? $t('paramManagement.hide') : $t('paramManagement.view') }}
|
{{ scope.row.showValue ? $t('paramManagement.hide') :
|
||||||
|
$t('paramManagement.view') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<span v-else>{{ scope.row.paramValue }}</span>
|
<span v-else>{{ scope.row.paramValue }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('paramManagement.remark')" prop="remark" align="center"></el-table-column>
|
<el-table-column :label="$t('paramManagement.remark')" prop="remark"
|
||||||
|
align="center"></el-table-column>
|
||||||
<el-table-column :label="$t('paramManagement.operation')" align="center">
|
<el-table-column :label="$t('paramManagement.operation')" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button size="mini" type="text" @click="editParam(scope.row)">{{ $t('paramManagement.edit') }}</el-button>
|
<el-button size="mini" type="text" @click="editParam(scope.row)">{{
|
||||||
<el-button size="mini" type="text" @click="deleteParam(scope.row)">{{ $t('paramManagement.delete') }}</el-button>
|
$t('paramManagement.edit') }}</el-button>
|
||||||
|
<el-button size="mini" type="text" @click="deleteParam(scope.row)">{{
|
||||||
|
$t('paramManagement.delete') }}</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -50,16 +56,19 @@
|
|||||||
<div class="table_bottom">
|
<div class="table_bottom">
|
||||||
<div class="ctrl_btn">
|
<div class="ctrl_btn">
|
||||||
<el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">
|
<el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">
|
||||||
{{ isAllSelected ? $t('paramManagement.deselectAll') : $t('paramManagement.selectAll') }}
|
{{ isAllSelected ? $t('paramManagement.deselectAll') :
|
||||||
|
$t('paramManagement.selectAll') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="mini" type="success" @click="showAddDialog">{{ $t('paramManagement.add') }}</el-button>
|
<el-button size="mini" type="success" @click="showAddDialog">{{
|
||||||
|
$t('paramManagement.add') }}</el-button>
|
||||||
<el-button size="mini" type="danger" icon="el-icon-delete"
|
<el-button size="mini" type="danger" icon="el-icon-delete"
|
||||||
@click="deleteSelectedParams">{{ $t('paramManagement.delete') }}</el-button>
|
@click="deleteSelectedParams">{{
|
||||||
|
$t('paramManagement.delete') }}</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="custom-pagination">
|
<div class="custom-pagination">
|
||||||
<el-select v-model="pageSize" @change="handlePageSizeChange" class="page-size-select">
|
<el-select v-model="pageSize" @change="handlePageSizeChange" class="page-size-select">
|
||||||
<el-option v-for="item in pageSizeOptions" :key="item" :label="`${item}${$t('paramManagement.itemsPerPage')}`"
|
<el-option v-for="item in pageSizeOptions" :key="item"
|
||||||
:value="item">
|
:label="`${item}${$t('paramManagement.itemsPerPage')}`" :value="item">
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">
|
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">
|
||||||
@@ -84,8 +93,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 新增/编辑参数对话框 -->
|
<!-- 新增/编辑参数对话框 -->
|
||||||
<param-dialog :title="dialogTitle" :visible.sync="dialogVisible" :form="paramForm" @submit="handleSubmit"
|
<param-dialog ref="paramDialog" :title="dialogTitle" :visible.sync="dialogVisible" :form="paramForm"
|
||||||
@cancel="dialogVisible = false" />
|
@submit="handleSubmit" @cancel="dialogVisible = false" />
|
||||||
<el-footer>
|
<el-footer>
|
||||||
<version-footer />
|
<version-footer />
|
||||||
</el-footer>
|
</el-footer>
|
||||||
@@ -97,7 +106,6 @@ import Api from "@/apis/api";
|
|||||||
import HeaderBar from "@/components/HeaderBar.vue";
|
import HeaderBar from "@/components/HeaderBar.vue";
|
||||||
import ParamDialog from "@/components/ParamDialog.vue";
|
import ParamDialog from "@/components/ParamDialog.vue";
|
||||||
import VersionFooter from "@/components/VersionFooter.vue";
|
import VersionFooter from "@/components/VersionFooter.vue";
|
||||||
import i18n from '@/i18n';
|
|
||||||
export default {
|
export default {
|
||||||
components: { HeaderBar, ParamDialog, VersionFooter },
|
components: { HeaderBar, ParamDialog, VersionFooter },
|
||||||
data() {
|
data() {
|
||||||
@@ -116,9 +124,9 @@ export default {
|
|||||||
paramForm: {
|
paramForm: {
|
||||||
id: null,
|
id: null,
|
||||||
paramCode: "",
|
paramCode: "",
|
||||||
paramValue: "",
|
paramValue: "",
|
||||||
valueType: "string",
|
valueType: "string",
|
||||||
remark: ""
|
remark: ""
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -166,7 +174,7 @@ export default {
|
|||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
this.paramsList = data.data.list.map(item => ({
|
this.paramsList = data.data.list.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
valueType: item.valueType || "string",
|
valueType: item.valueType || "string",
|
||||||
selected: false,
|
selected: false,
|
||||||
showValue: false
|
showValue: false
|
||||||
}));
|
}));
|
||||||
@@ -193,41 +201,43 @@ export default {
|
|||||||
showAddDialog() {
|
showAddDialog() {
|
||||||
this.dialogTitle = this.$t('paramManagement.addParam');
|
this.dialogTitle = this.$t('paramManagement.addParam');
|
||||||
this.paramForm = {
|
this.paramForm = {
|
||||||
id: null,
|
id: null,
|
||||||
paramCode: "",
|
paramCode: "",
|
||||||
paramValue: "",
|
paramValue: "",
|
||||||
valueType: "string", // 默认值
|
valueType: "string", // 默认值
|
||||||
remark: ""
|
remark: ""
|
||||||
};
|
};
|
||||||
this.dialogVisible = true;
|
this.dialogVisible = true;
|
||||||
},
|
},
|
||||||
editParam(row) {
|
editParam(row) {
|
||||||
this.dialogTitle = this.$t('paramManagement.editParam');
|
this.dialogTitle = this.$t('paramManagement.editParam');
|
||||||
this.paramForm = {
|
this.paramForm = {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
paramCode: row.paramCode,
|
paramCode: row.paramCode,
|
||||||
paramValue: row.paramValue,
|
paramValue: row.paramValue,
|
||||||
valueType: row.valueType || "string", // 确保有值
|
valueType: row.valueType || "string", // 确保有值
|
||||||
remark: row.remark
|
remark: row.remark
|
||||||
};
|
};
|
||||||
this.dialogVisible = true;
|
this.dialogVisible = true;
|
||||||
},
|
},
|
||||||
handleSubmit(form) {
|
handleSubmit(form) {
|
||||||
if (form.id) {
|
if (form.id) {
|
||||||
// 更新参数
|
// 更新参数
|
||||||
Api.admin.updateParam(form, ({ data }) => {
|
Api.admin.updateParam(form, ({ data }) => {
|
||||||
if (data.code === 0) {
|
this.dialogVisible = false;
|
||||||
this.dialogVisible = false;
|
this.fetchParams();
|
||||||
this.fetchParams();
|
this.$message.success({
|
||||||
this.$message.success({
|
message: this.$t('paramManagement.updateSuccess'),
|
||||||
message: this.$t('paramManagement.updateSuccess'),
|
showClose: true
|
||||||
showClose: true
|
});
|
||||||
});
|
}, ({ data }) => {
|
||||||
} else {
|
this.$message.error({
|
||||||
this.$message.error({
|
message: data.msg || this.$t('paramManagement.updateFailed'),
|
||||||
message: data.msg || this.$t('paramManagement.updateFailed'),
|
showClose: true
|
||||||
showClose: true
|
});
|
||||||
});
|
// 调用ParamDialog的resetSaving方法重置保存状态
|
||||||
|
if (this.$refs.paramDialog && typeof this.$refs.paramDialog.resetSaving === 'function') {
|
||||||
|
this.$refs.paramDialog.resetSaving();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -245,6 +255,10 @@ export default {
|
|||||||
message: data.msg || this.$t('paramManagement.addFailed'),
|
message: data.msg || this.$t('paramManagement.addFailed'),
|
||||||
showClose: true
|
showClose: true
|
||||||
});
|
});
|
||||||
|
// 调用ParamDialog的resetSaving方法重置保存状态
|
||||||
|
if (this.$refs.paramDialog && typeof this.$refs.paramDialog.resetSaving === 'function') {
|
||||||
|
this.$refs.paramDialog.resetSaving();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export default {
|
|||||||
this.fetchParams();
|
this.fetchParams();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getServerList();
|
|
||||||
this.dialogTitle = this.$t('paramManagement.addParam');
|
this.dialogTitle = this.$t('paramManagement.addParam');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<el-card class="user-card" shadow="never">
|
<el-card class="user-card" shadow="never">
|
||||||
<el-table ref="userTable" :data="userList" class="transparent-table" v-loading="loading"
|
<el-table ref="userTable" :data="userList" class="transparent-table" v-loading="loading"
|
||||||
:element-loading-text="$t('modelConfig.loading')" element-loading-spinner="el-icon-loading"
|
:element-loading-text="$t('modelConfig.loading')" element-loading-spinner="el-icon-loading"
|
||||||
element-loading-background="rgba(255, 255, 255, 0.7)">
|
element-loading-background="rgba(255, 255, 255, 0.7)">
|
||||||
<el-table-column :label="$t('modelConfig.select')" align="center" width="120">
|
<el-table-column :label="$t('modelConfig.select')" align="center" width="120">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-checkbox v-model="scope.row.selected"></el-checkbox>
|
<el-checkbox v-model="scope.row.selected"></el-checkbox>
|
||||||
@@ -36,13 +36,13 @@
|
|||||||
<el-table-column :label="$t('modelConfig.action')" align="center" width="300px">
|
<el-table-column :label="$t('modelConfig.action')" align="center" width="300px">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button size="mini" type="text" @click="resetPassword(scope.row)">{{ $t('user.resetPassword')
|
<el-button size="mini" type="text" @click="resetPassword(scope.row)">{{ $t('user.resetPassword')
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
<el-button size="mini" type="text" v-if="scope.row.status === 1"
|
<el-button size="mini" type="text" v-if="scope.row.status === 1"
|
||||||
@click="handleChangeStatus(scope.row, 0)">{{ $t('user.disableAccount') }}</el-button>
|
@click="handleChangeStatus(scope.row, 0)">{{ $t('user.disableAccount') }}</el-button>
|
||||||
<el-button size="mini" type="text" v-if="scope.row.status === 0"
|
<el-button size="mini" type="text" v-if="scope.row.status === 0"
|
||||||
@click="handleChangeStatus(scope.row, 1)">{{ $t('user.enableAccount') }}</el-button>
|
@click="handleChangeStatus(scope.row, 1)">{{ $t('user.enableAccount') }}</el-button>
|
||||||
<el-button size="mini" type="text" @click="deleteUser(scope.row)">{{ $t('user.deleteUser')
|
<el-button size="mini" type="text" @click="deleteUser(scope.row)">{{ $t('user.deleteUser')
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -54,18 +54,18 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="mini" type="success" icon="el-icon-circle-check" @click="batchEnable">{{
|
<el-button size="mini" type="success" icon="el-icon-circle-check" @click="batchEnable">{{
|
||||||
$t('user.enable')
|
$t('user.enable')
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
<el-button size="mini" type="warning" @click="batchDisable"><i
|
<el-button size="mini" type="warning" @click="batchDisable"><i
|
||||||
class="el-icon-remove-outline rotated-icon"></i>{{
|
class="el-icon-remove-outline rotated-icon"></i>{{
|
||||||
$t('user.disable') }}</el-button>
|
$t('user.disable') }}</el-button>
|
||||||
<el-button size="mini" type="danger" icon="el-icon-delete" @click="batchDelete">{{ $t('user.delete')
|
<el-button size="mini" type="danger" icon="el-icon-delete" @click="batchDelete">{{ $t('user.delete')
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="custom-pagination">
|
<div class="custom-pagination">
|
||||||
<el-select v-model="pageSize" @change="handlePageSizeChange"
|
<el-select v-model="pageSize" @change="handlePageSizeChange"
|
||||||
:class="['page-size-select', { 'page-size-select-en': isEnglish }]">
|
:class="['page-size-select', { 'page-size-select-en': $i18n.locale === 'en' }]">
|
||||||
<el-option v-for="item in pageSizeOptions" :key="item"
|
<el-option v-for="item in pageSizeOptions" :key="item"
|
||||||
:label="$t('modelConfig.itemsPerPage', { count: item })" :value="item">
|
:label="$t('modelConfig.itemsPerPage', { items: item })" :value="item">
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
</button> <button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">
|
</button> <button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">
|
||||||
{{ $t('modelConfig.nextPage') }}
|
{{ $t('modelConfig.nextPage') }}
|
||||||
</button>
|
</button>
|
||||||
<span class="total-text">{{ $t('modelConfig.totalRecords', { count: total }) }}</span>
|
<span class="total-text">{{ $t('modelConfig.totalRecords', { total: total }) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -125,9 +125,6 @@ export default {
|
|||||||
pageCount() {
|
pageCount() {
|
||||||
return Math.ceil(this.total / this.pageSize);
|
return Math.ceil(this.total / this.pageSize);
|
||||||
},
|
},
|
||||||
isEnglish() {
|
|
||||||
return i18n.locale === 'en';
|
|
||||||
},
|
|
||||||
visiblePages() {
|
visiblePages() {
|
||||||
const pages = [];
|
const pages = [];
|
||||||
const maxVisible = 3;
|
const maxVisible = 3;
|
||||||
@@ -142,7 +139,7 @@ export default {
|
|||||||
pages.push(i);
|
pages.push(i);
|
||||||
}
|
}
|
||||||
return pages;
|
return pages;
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handlePageSizeChange(val) {
|
handlePageSizeChange(val) {
|
||||||
@@ -346,7 +343,7 @@ export default {
|
|||||||
this.$message.warning(this.$t('user.selectUsersFirst'));
|
this.$message.warning(this.$t('user.selectUsersFirst'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userIds = this.selection.map(item => item.userId);
|
const userIds = this.selection.map(item => item.userId);
|
||||||
this.$confirm(this.$t('user.confirmDeleteSelected', { count: this.selection.length }), this.$t('common.warning'), {
|
this.$confirm(this.$t('user.confirmDeleteSelected', { count: this.selection.length }), this.$t('common.warning'), {
|
||||||
confirmButtonText: this.$t('common.confirm'),
|
confirmButtonText: this.$t('common.confirm'),
|
||||||
@@ -379,10 +376,10 @@ export default {
|
|||||||
this.$message.warning(this.$t('user.selectUsersFirst'));
|
this.$message.warning(this.$t('user.selectUsersFirst'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionText = status === 1 ? this.$t('user.enable') : this.$t('user.disable');
|
const actionText = status === 1 ? this.$t('user.enable') : this.$t('user.disable');
|
||||||
const userIds = this.selection.map(item => item.userId);
|
const userIds = this.selection.map(item => item.userId);
|
||||||
|
|
||||||
this.$confirm(this.$t('user.confirmStatusChange', { action: actionText, count: this.selection.length }), this.$t('common.warning'), {
|
this.$confirm(this.$t('user.confirmStatusChange', { action: actionText, count: this.selection.length }), this.$t('common.warning'), {
|
||||||
confirmButtonText: this.$t('common.confirm'),
|
confirmButtonText: this.$t('common.confirm'),
|
||||||
cancelButtonText: this.$t('common.cancel'),
|
cancelButtonText: this.$t('common.cancel'),
|
||||||
|
|||||||
@@ -252,9 +252,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 封装输入验证逻辑
|
// 封装输入验证逻辑
|
||||||
validateInput(input, message) {
|
validateInput(input, messageKey) {
|
||||||
if (!input.trim()) {
|
if (!input.trim()) {
|
||||||
showDanger(message);
|
showDanger(this.$t(messageKey));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -264,24 +264,24 @@ export default {
|
|||||||
if (this.isMobileLogin) {
|
if (this.isMobileLogin) {
|
||||||
// 手机号登录验证
|
// 手机号登录验证
|
||||||
if (!validateMobile(this.form.mobile, this.form.areaCode)) {
|
if (!validateMobile(this.form.mobile, this.form.areaCode)) {
|
||||||
showDanger("请输入正确的手机号码");
|
showDanger(this.$t('login.requiredMobile'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 拼接手机号作为用户名
|
// 拼接手机号作为用户名
|
||||||
this.form.username = this.form.areaCode + this.form.mobile;
|
this.form.username = this.form.areaCode + this.form.mobile;
|
||||||
} else {
|
} else {
|
||||||
// 用户名登录验证
|
// 用户名登录验证
|
||||||
if (!this.validateInput(this.form.username, "用户名不能为空")) {
|
if (!this.validateInput(this.form.username, 'login.requiredUsername')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证密码
|
// 验证密码
|
||||||
if (!this.validateInput(this.form.password, "密码不能为空")) {
|
if (!this.validateInput(this.form.password, 'login.requiredPassword')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 验证验证码
|
// 验证验证码
|
||||||
if (!this.validateInput(this.form.captcha, "验证码不能为空")) {
|
if (!this.validateInput(this.form.captcha, 'login.requiredCaptcha')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,16 +289,19 @@ export default {
|
|||||||
Api.user.login(
|
Api.user.login(
|
||||||
this.form,
|
this.form,
|
||||||
({ data }) => {
|
({ data }) => {
|
||||||
showSuccess("登录成功!");
|
showSuccess(this.$t('login.loginSuccess'));
|
||||||
this.$store.commit("setToken", JSON.stringify(data.data));
|
this.$store.commit("setToken", JSON.stringify(data.data));
|
||||||
goToPage("/home");
|
goToPage("/home");
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
showDanger(err.data.msg || "登录失败");
|
// 直接使用后端返回的国际化消息
|
||||||
|
let errorMessage = err.data.msg || "登录失败";
|
||||||
|
|
||||||
|
showDanger(errorMessage);
|
||||||
if (
|
if (
|
||||||
err.data != null &&
|
err.data != null &&
|
||||||
err.data.msg != null &&
|
err.data.msg != null &&
|
||||||
err.data.msg.indexOf("图形验证码") > -1
|
err.data.msg.indexOf("图形验证码") > -1 || err.data.msg.indexOf("Captcha") > -1
|
||||||
) {
|
) {
|
||||||
this.fetchCaptcha();
|
this.fetchCaptcha();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
<img loading="lazy" alt="" class="input-icon" src="@/assets/login/shield.png" />
|
<img loading="lazy" alt="" class="input-icon" src="@/assets/login/shield.png" />
|
||||||
<el-input v-model="form.captcha" :placeholder="$t('register.captchaPlaceholder')" style="flex: 1;" />
|
<el-input v-model="form.captcha" :placeholder="$t('register.captchaPlaceholder')" style="flex: 1;" />
|
||||||
</div>
|
</div>
|
||||||
<img loading="lazy" v-if="captchaUrl" :src="captchaUrl" alt="验证码"
|
<img loading="lazy" v-if="captchaUrl" :src="captchaUrl" alt="验证码"
|
||||||
style="width: 150px; height: 40px; cursor: pointer;" @click="fetchCaptcha" />
|
style="width: 150px; height: 40px; cursor: pointer;" @click="fetchCaptcha" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<el-button type="primary" class="send-captcha-btn" :disabled="!canSendMobileCaptcha"
|
<el-button type="primary" class="send-captcha-btn" :disabled="!canSendMobileCaptcha"
|
||||||
@click="sendMobileCaptcha">
|
@click="sendMobileCaptcha">
|
||||||
<span>
|
<span>
|
||||||
{{ countdown > 0 ? `${countdown}秒后重试` : $t('register.sendCaptcha') }}
|
{{ countdown > 0 ? `${countdown}${$t('register.secondsLater')}` : $t('register.sendCaptcha') }}
|
||||||
</span>
|
</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -248,18 +248,18 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.form.mobileCaptcha) {
|
if (!this.form.mobileCaptcha) {
|
||||||
showDanger('请输入手机验证码');
|
showDanger(this.$t('register.requiredMobileCaptcha'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 用户名注册验证
|
// 用户名注册验证
|
||||||
if (!this.validateInput(this.form.username, '用户名不能为空')) {
|
if (!this.validateInput(this.form.username, this.$t('register.requiredUsername'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证密码
|
// 验证密码
|
||||||
if (!this.validateInput(this.form.password, '密码不能为空')) {
|
if (!this.validateInput(this.form.password, this.$t('register.requiredPassword'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.form.password !== this.form.confirmPassword) {
|
if (this.form.password !== this.form.confirmPassword) {
|
||||||
@@ -267,7 +267,7 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 验证验证码
|
// 验证验证码
|
||||||
if (!this.validateInput(this.form.captcha, '验证码不能为空')) {
|
if (!this.validateInput(this.form.captcha, this.$t('register.requiredCaptcha'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,9 @@
|
|||||||
<img loading="lazy" src="@/assets/home/info.png" alt="">
|
<img loading="lazy" src="@/assets/home/info.png" alt="">
|
||||||
<span>{{ $t('roleConfig.restartNotice') }}</span>
|
<span>{{ $t('roleConfig.restartNotice') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<el-button type="primary" class="save-btn" @click="saveConfig">{{ $t('roleConfig.saveConfig') }}</el-button>
|
<el-button type="primary" class="save-btn" @click="saveConfig">
|
||||||
|
{{ $t('roleConfig.saveConfig') }}
|
||||||
|
</el-button>
|
||||||
<el-button class="reset-btn" @click="resetConfig">{{ $t('roleConfig.reset') }}</el-button>
|
<el-button class="reset-btn" @click="resetConfig">{{ $t('roleConfig.reset') }}</el-button>
|
||||||
<button class="custom-close-btn" @click="goToHome">
|
<button class="custom-close-btn" @click="goToHome">
|
||||||
×
|
×
|
||||||
@@ -45,29 +47,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('roleConfig.roleIntroduction') + ':'">
|
<el-form-item :label="$t('roleConfig.roleIntroduction') + ':'">
|
||||||
<el-input type="textarea" rows="9" resize="none" :placeholder="$t('roleConfig.pleaseEnterContent')" v-model="form.systemPrompt"
|
<el-input type="textarea" rows="9" resize="none"
|
||||||
maxlength="2000" show-word-limit class="form-textarea" />
|
:placeholder="$t('roleConfig.pleaseEnterContent')" v-model="form.systemPrompt" maxlength="2000"
|
||||||
|
show-word-limit class="form-textarea" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="$t('roleConfig.memory') + ':'">
|
<el-form-item :label="$t('roleConfig.memoryHis') + ':'">
|
||||||
<el-input type="textarea" rows="6" resize="none" v-model="form.summaryMemory" maxlength="2000"
|
<el-input type="textarea" rows="6" resize="none" v-model="form.summaryMemory" maxlength="2000"
|
||||||
show-word-limit class="form-textarea"
|
show-word-limit class="form-textarea"
|
||||||
:disabled="form.model.memModelId !== 'Memory_mem_local_short'" />
|
:disabled="form.model.memModelId !== 'Memory_mem_local_short'" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('roleConfig.languageCode') + ':'" style="display: none;">
|
<el-form-item :label="$t('roleConfig.languageCode') + ':'" style="display: none;">
|
||||||
<el-input v-model="form.langCode" :placeholder="$t('roleConfig.pleaseEnterLangCode')" maxlength="10" show-word-limit
|
<el-input v-model="form.langCode" :placeholder="$t('roleConfig.pleaseEnterLangCode')"
|
||||||
class="form-input" />
|
maxlength="10" show-word-limit class="form-input" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('roleConfig.interactionLanguage') + ':'" style="display: none;">
|
<el-form-item :label="$t('roleConfig.interactionLanguage') + ':'" style="display: none;">
|
||||||
<el-input v-model="form.language" :placeholder="$t('roleConfig.pleaseEnterLangName')" maxlength="10" show-word-limit
|
<el-input v-model="form.language" :placeholder="$t('roleConfig.pleaseEnterLangName')"
|
||||||
class="form-input" />
|
maxlength="10" show-word-limit class="form-input" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-column">
|
<div class="form-column">
|
||||||
<div class="model-row">
|
<div class="model-row">
|
||||||
<el-form-item :label="$t('roleConfig.vad')" class="model-item">
|
<el-form-item :label="$t('roleConfig.vad')" class="model-item">
|
||||||
<div class="model-select-wrapper">
|
<div class="model-select-wrapper">
|
||||||
<el-select v-model="form.model.vadModelId" filterable :placeholder="$t('roleConfig.pleaseSelect')" class="form-select"
|
<el-select v-model="form.model.vadModelId" filterable
|
||||||
|
:placeholder="$t('roleConfig.pleaseSelect')" class="form-select"
|
||||||
@change="handleModelChange('VAD', $event)">
|
@change="handleModelChange('VAD', $event)">
|
||||||
<el-option v-for="(item, optionIndex) in modelOptions['VAD']"
|
<el-option v-for="(item, optionIndex) in modelOptions['VAD']"
|
||||||
:key="`option-vad-${optionIndex}`" :label="item.label" :value="item.value" />
|
:key="`option-vad-${optionIndex}`" :label="item.label" :value="item.value" />
|
||||||
@@ -76,7 +80,8 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('roleConfig.asr')" class="model-item">
|
<el-form-item :label="$t('roleConfig.asr')" class="model-item">
|
||||||
<div class="model-select-wrapper">
|
<div class="model-select-wrapper">
|
||||||
<el-select v-model="form.model.asrModelId" filterable :placeholder="$t('roleConfig.pleaseSelect')" class="form-select"
|
<el-select v-model="form.model.asrModelId" filterable
|
||||||
|
:placeholder="$t('roleConfig.pleaseSelect')" class="form-select"
|
||||||
@change="handleModelChange('ASR', $event)">
|
@change="handleModelChange('ASR', $event)">
|
||||||
<el-option v-for="(item, optionIndex) in modelOptions['ASR']"
|
<el-option v-for="(item, optionIndex) in modelOptions['ASR']"
|
||||||
:key="`option-asr-${optionIndex}`" :label="item.label" :value="item.value" />
|
:key="`option-asr-${optionIndex}`" :label="item.label" :value="item.value" />
|
||||||
@@ -84,11 +89,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<el-form-item v-for="(model, index) in models.slice(2)" :key="`model-${index}`" :label="$t('roleConfig.' + model.type.toLowerCase())"
|
<el-form-item v-for="(model, index) in models.slice(2)" :key="`model-${index}`"
|
||||||
class="model-item">
|
:label="$t('roleConfig.' + model.type.toLowerCase())" class="model-item">
|
||||||
<div class="model-select-wrapper">
|
<div class="model-select-wrapper">
|
||||||
<el-select v-model="form.model[model.key]" filterable :placeholder="$t('roleConfig.pleaseSelect')" class="form-select"
|
<el-select v-model="form.model[model.key]" filterable
|
||||||
@change="handleModelChange(model.type, $event)">
|
:placeholder="$t('roleConfig.pleaseSelect')" class="form-select"
|
||||||
|
@change="handleModelChange(model.type, $event)">
|
||||||
<el-option v-for="(item, optionIndex) in modelOptions[model.type]" v-if="!item.isHidden"
|
<el-option v-for="(item, optionIndex) in modelOptions[model.type]" v-if="!item.isHidden"
|
||||||
:key="`option-${index}-${optionIndex}`" :label="item.label" :value="item.value" />
|
:key="`option-${index}-${optionIndex}`" :label="item.label" :value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -110,14 +116,15 @@
|
|||||||
<div v-if="model.type === 'Memory' && form.model.memModelId !== 'Memory_nomem'"
|
<div v-if="model.type === 'Memory' && form.model.memModelId !== 'Memory_nomem'"
|
||||||
class="chat-history-options">
|
class="chat-history-options">
|
||||||
<el-radio-group v-model="form.chatHistoryConf" @change="updateChatHistoryConf">
|
<el-radio-group v-model="form.chatHistoryConf" @change="updateChatHistoryConf">
|
||||||
<el-radio-button :label="1">{{ $t('roleConfig.reportText') }}</el-radio-button>
|
<el-radio-button :label="1">{{ $t('roleConfig.reportText') }}</el-radio-button>
|
||||||
<el-radio-button :label="2">{{ $t('roleConfig.reportTextVoice') }}</el-radio-button>
|
<el-radio-button :label="2">{{ $t('roleConfig.reportTextVoice') }}</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('roleConfig.voiceType')">
|
<el-form-item :label="$t('roleConfig.voiceType')">
|
||||||
<el-select v-model="form.ttsVoiceId" :placeholder="$t('roleConfig.pleaseSelect')" class="form-select">
|
<el-select v-model="form.ttsVoiceId" :placeholder="$t('roleConfig.pleaseSelect')"
|
||||||
|
class="form-select">
|
||||||
<el-option v-for="(item, index) in voiceOptions" :key="`voice-${index}`" :label="item.label"
|
<el-option v-for="(item, index) in voiceOptions" :key="`voice-${index}`" :label="item.label"
|
||||||
:value="item.value" />
|
:value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
|
|
||||||
<context>
|
<context>
|
||||||
【重要!以下信息已实时提供,无需调用工具查询,请直接使用:】
|
【重要!以下信息已实时提供,无需调用工具查询,请直接使用:】
|
||||||
|
- **设备ID:** {{device_id}}
|
||||||
- **当前时间:** {{current_time}}
|
- **当前时间:** {{current_time}}
|
||||||
- **今天日期:** {{today_date}} ({{today_weekday}})
|
- **今天日期:** {{today_date}} ({{today_weekday}})
|
||||||
- **今天农历:** {{lunar_date}}
|
- **今天农历:** {{lunar_date}}
|
||||||
|
|||||||
@@ -922,3 +922,18 @@ TTS:
|
|||||||
# 默认音色,如需其他音色可到项目assets文件夹下注册
|
# 默认音色,如需其他音色可到项目assets文件夹下注册
|
||||||
voice: "jay_klee"
|
voice: "jay_klee"
|
||||||
output_dir: tmp/
|
output_dir: tmp/
|
||||||
|
AliBLTTS:
|
||||||
|
# 阿里百炼CosyVoice大模型流式文本语音合成
|
||||||
|
# 可在这里找到你的 api_key https://bailian.console.aliyun.com/?apiKey=1#/api-key
|
||||||
|
# cosyvoice-v3和部分音色需要申请开通
|
||||||
|
type: alibl_stream
|
||||||
|
api_key: 你的api_key
|
||||||
|
model: "cosyvoice-v2"
|
||||||
|
voice: "longcheng_v2"
|
||||||
|
output_dir: tmp/
|
||||||
|
# 以下可不用设置,使用默认设置
|
||||||
|
# format: pcm # 音频格式:pcm、wav、mp3、opus
|
||||||
|
# sample_rate: 24000 # 采样率:16000, 24000, 48000
|
||||||
|
# volume: 50 # 音量:0-100
|
||||||
|
# rate: 1 # 语速:0.5~2
|
||||||
|
# pitch: 1 # 语调:0.5~2
|
||||||
@@ -114,6 +114,9 @@ async def sendAudio(conn, audios, frame_duration=60):
|
|||||||
delay = expected_time - current_time
|
delay = expected_time - current_time
|
||||||
if delay > 0:
|
if delay > 0:
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
|
else:
|
||||||
|
# 纠正误差
|
||||||
|
flow_control["start_time"] += abs(delay)
|
||||||
|
|
||||||
if conn.conn_from_mqtt_gateway:
|
if conn.conn_from_mqtt_gateway:
|
||||||
# 计算时间戳和序列号
|
# 计算时间戳和序列号
|
||||||
|
|||||||
@@ -0,0 +1,522 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import asyncio
|
||||||
|
import traceback
|
||||||
|
import websockets
|
||||||
|
from asyncio import Task
|
||||||
|
from config.logger import setup_logging
|
||||||
|
from core.utils import opus_encoder_utils
|
||||||
|
from core.utils.tts import MarkdownCleaner
|
||||||
|
from core.providers.tts.base import TTSProviderBase
|
||||||
|
from core.providers.tts.dto.dto import SentenceType, ContentType, InterfaceType
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
|
class TTSProvider(TTSProviderBase):
|
||||||
|
def __init__(self, config, delete_audio_file):
|
||||||
|
super().__init__(config, delete_audio_file)
|
||||||
|
|
||||||
|
self.interface_type = InterfaceType.DUAL_STREAM
|
||||||
|
# 基础配置
|
||||||
|
self.api_key = config.get("api_key")
|
||||||
|
if not self.api_key:
|
||||||
|
raise ValueError("api_key is required for CosyVoice TTS")
|
||||||
|
|
||||||
|
# WebSocket配置
|
||||||
|
self.ws_url = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/"
|
||||||
|
self.ws = None
|
||||||
|
self._monitor_task = None
|
||||||
|
self.last_active_time = None
|
||||||
|
|
||||||
|
# 模型和音色配置
|
||||||
|
self.model = config.get("model", "cosyvoice-v2")
|
||||||
|
self.voice = config.get("voice", "longxiaochun_v2") # 默认音色
|
||||||
|
if config.get("private_voice"):
|
||||||
|
self.voice = config.get("private_voice")
|
||||||
|
|
||||||
|
# 音频参数配置
|
||||||
|
self.format = config.get("format", "pcm")
|
||||||
|
sample_rate = config.get("sample_rate", "24000")
|
||||||
|
self.sample_rate = int(sample_rate) if sample_rate else 24000
|
||||||
|
|
||||||
|
volume = config.get("volume", "50")
|
||||||
|
self.volume = int(volume) if volume else 50
|
||||||
|
|
||||||
|
rate = config.get("rate", "1.0")
|
||||||
|
self.rate = float(rate) if rate else 1.0
|
||||||
|
|
||||||
|
pitch = config.get("pitch", "1.0")
|
||||||
|
self.pitch = float(pitch) if pitch else 1.0
|
||||||
|
|
||||||
|
self.header = {
|
||||||
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
|
# "user-agent": "your_platform_info", // 可选
|
||||||
|
# "X-DashScope-WorkSpace": workspace, // 可选,阿里云百炼业务空间ID
|
||||||
|
"X-DashScope-DataInspection": "enable",
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建Opus编码器
|
||||||
|
self.opus_encoder = opus_encoder_utils.OpusEncoderUtils(
|
||||||
|
sample_rate=self.sample_rate, channels=1, frame_size_ms=60
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _ensure_connection(self):
|
||||||
|
"""确保WebSocket连接可用,支持60秒内连接复用"""
|
||||||
|
try:
|
||||||
|
current_time = time.time()
|
||||||
|
if self.ws and current_time - self.last_active_time < 60:
|
||||||
|
# 一分钟内才可以复用链接进行连续对话
|
||||||
|
logger.bind(tag=TAG).info(f"使用已有链接...")
|
||||||
|
return self.ws
|
||||||
|
logger.bind(tag=TAG).info("开始建立新连接...")
|
||||||
|
|
||||||
|
self.ws = await websockets.connect(
|
||||||
|
self.ws_url,
|
||||||
|
additional_headers=self.header,
|
||||||
|
ping_interval=30,
|
||||||
|
ping_timeout=10,
|
||||||
|
close_timeout=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.bind(tag=TAG).info("WebSocket连接建立成功")
|
||||||
|
self.last_active_time = current_time
|
||||||
|
return self.ws
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"建立连接失败: {str(e)}")
|
||||||
|
self.ws = None
|
||||||
|
self.last_active_time = None
|
||||||
|
raise
|
||||||
|
|
||||||
|
def tts_text_priority_thread(self):
|
||||||
|
"""流式TTS文本处理线程"""
|
||||||
|
while not self.conn.stop_event.is_set():
|
||||||
|
try:
|
||||||
|
message = self.tts_text_queue.get(timeout=1)
|
||||||
|
logger.bind(tag=TAG).debug(
|
||||||
|
f"收到TTS任务|{message.sentence_type.name} | {message.content_type.name} | 会话ID: {self.conn.sentence_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if message.sentence_type == SentenceType.FIRST:
|
||||||
|
self.conn.client_abort = False
|
||||||
|
|
||||||
|
if self.conn.client_abort:
|
||||||
|
try:
|
||||||
|
logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"取消TTS会话失败: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if message.sentence_type == SentenceType.FIRST:
|
||||||
|
# 初始化会话
|
||||||
|
try:
|
||||||
|
if not getattr(self.conn, "sentence_id", None):
|
||||||
|
self.conn.sentence_id = uuid.uuid4().hex
|
||||||
|
logger.bind(tag=TAG).info(f"自动生成新的 会话ID: {self.conn.sentence_id}")
|
||||||
|
|
||||||
|
logger.bind(tag=TAG).info("开始启动TTS会话...")
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
self.start_session(self.conn.sentence_id),
|
||||||
|
loop=self.conn.loop,
|
||||||
|
)
|
||||||
|
future.result()
|
||||||
|
self.before_stop_play_files.clear()
|
||||||
|
logger.bind(tag=TAG).info("TTS会话启动成功")
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"启动TTS会话失败: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif ContentType.TEXT == message.content_type:
|
||||||
|
if message.content_detail:
|
||||||
|
try:
|
||||||
|
logger.bind(tag=TAG).debug(
|
||||||
|
f"开始发送TTS文本: {message.content_detail}"
|
||||||
|
)
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
self.text_to_speak(message.content_detail, None),
|
||||||
|
loop=self.conn.loop,
|
||||||
|
)
|
||||||
|
future.result()
|
||||||
|
logger.bind(tag=TAG).debug("TTS文本发送成功")
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif ContentType.FILE == message.content_type:
|
||||||
|
logger.bind(tag=TAG).info(
|
||||||
|
f"添加音频文件到待播放列表: {message.content_file}"
|
||||||
|
)
|
||||||
|
if message.content_file and os.path.exists(message.content_file):
|
||||||
|
# 先处理文件音频数据
|
||||||
|
self._process_audio_file_stream(message.content_file, callback=lambda audio_data: self.handle_audio_file(audio_data, message.content_detail))
|
||||||
|
|
||||||
|
if message.sentence_type == SentenceType.LAST:
|
||||||
|
try:
|
||||||
|
logger.bind(tag=TAG).info("开始结束TTS会话...")
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
self.finish_session(self.conn.sentence_id),
|
||||||
|
loop=self.conn.loop,
|
||||||
|
)
|
||||||
|
future.result()
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"结束TTS会话失败: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(
|
||||||
|
f"处理TTS文本失败: {str(e)}, 类型: {type(e).__name__}, 堆栈: {traceback.format_exc()}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
async def text_to_speak(self, text, _):
|
||||||
|
"""发送文本到TTS服务进行合成"""
|
||||||
|
try:
|
||||||
|
if self.ws is None:
|
||||||
|
logger.bind(tag=TAG).warning("WebSocket连接不存在,终止发送文本")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 过滤Markdown
|
||||||
|
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||||
|
|
||||||
|
# 发送continue-task消息
|
||||||
|
continue_task_message = {
|
||||||
|
"header": {
|
||||||
|
"action": "continue-task",
|
||||||
|
"task_id": self.conn.sentence_id,
|
||||||
|
"streaming": "duplex",
|
||||||
|
},
|
||||||
|
"payload": {"input": {"text": filtered_text}},
|
||||||
|
}
|
||||||
|
|
||||||
|
await self.ws.send(json.dumps(continue_task_message))
|
||||||
|
self.last_active_time = time.time()
|
||||||
|
logger.bind(tag=TAG).debug(f"已发送文本: {filtered_text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}")
|
||||||
|
if self.ws:
|
||||||
|
try:
|
||||||
|
await self.ws.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.ws = None
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def start_session(self, session_id):
|
||||||
|
"""启动TTS会话"""
|
||||||
|
logger.bind(tag=TAG).info(f"开始会话~~{session_id}")
|
||||||
|
try:
|
||||||
|
# 检查并清理上一个会话的监听任务
|
||||||
|
if (
|
||||||
|
self._monitor_task is not None
|
||||||
|
and isinstance(self._monitor_task, Task)
|
||||||
|
and not self._monitor_task.done()
|
||||||
|
):
|
||||||
|
logger.bind(tag=TAG).info("检测到未完成的上个会话,关闭监听任务...")
|
||||||
|
await self.close()
|
||||||
|
|
||||||
|
# 确保连接可用
|
||||||
|
await self._ensure_connection()
|
||||||
|
|
||||||
|
# 启动监听任务
|
||||||
|
self._monitor_task = asyncio.create_task(self._start_monitor_tts_response())
|
||||||
|
|
||||||
|
# 发送run-task消息启动会话
|
||||||
|
run_task_message = {
|
||||||
|
"header": {
|
||||||
|
"action": "run-task",
|
||||||
|
"task_id": session_id,
|
||||||
|
"streaming": "duplex",
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"task_group": "audio",
|
||||||
|
"task": "tts",
|
||||||
|
"function": "SpeechSynthesizer",
|
||||||
|
"model": self.model,
|
||||||
|
"parameters": {
|
||||||
|
"text_type": "PlainText",
|
||||||
|
"voice": self.voice,
|
||||||
|
"format": self.format,
|
||||||
|
"sample_rate": self.sample_rate,
|
||||||
|
"volume": self.volume,
|
||||||
|
"rate": self.rate,
|
||||||
|
"pitch": self.pitch,
|
||||||
|
},
|
||||||
|
"input": {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
await self.ws.send(json.dumps(run_task_message))
|
||||||
|
self.last_active_time = time.time()
|
||||||
|
logger.bind(tag=TAG).info("会话启动请求已发送")
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"启动会话失败: {str(e)}")
|
||||||
|
await self.close()
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def finish_session(self, session_id):
|
||||||
|
"""结束TTS会话"""
|
||||||
|
logger.bind(tag=TAG).info(f"关闭会话~~{session_id}")
|
||||||
|
try:
|
||||||
|
if self.ws and session_id:
|
||||||
|
# 发送finish-task消息
|
||||||
|
finish_task_message = {
|
||||||
|
"header": {
|
||||||
|
"action": "finish-task",
|
||||||
|
"task_id": session_id,
|
||||||
|
"streaming": "duplex",
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"input": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await self.ws.send(json.dumps(finish_task_message))
|
||||||
|
self.last_active_time = time.time()
|
||||||
|
logger.bind(tag=TAG).info("会话结束请求已发送")
|
||||||
|
# 等待监听任务完成
|
||||||
|
if self._monitor_task:
|
||||||
|
try:
|
||||||
|
await self._monitor_task
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(
|
||||||
|
f"等待监听任务完成时发生错误: {str(e)}"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
self._monitor_task = None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"关闭会话失败: {str(e)}")
|
||||||
|
await self.close()
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
"""清理资源"""
|
||||||
|
# 取消监听任务
|
||||||
|
if self._monitor_task:
|
||||||
|
try:
|
||||||
|
self._monitor_task.cancel()
|
||||||
|
await self._monitor_task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).warning(f"关闭时取消监听任务错误: {e}")
|
||||||
|
self._monitor_task = None
|
||||||
|
|
||||||
|
# 关闭WebSocket连接
|
||||||
|
if self.ws:
|
||||||
|
try:
|
||||||
|
await self.ws.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.ws = None
|
||||||
|
self.last_active_time = None
|
||||||
|
|
||||||
|
async def _start_monitor_tts_response(self):
|
||||||
|
"""监听TTS响应"""
|
||||||
|
try:
|
||||||
|
session_finished = False
|
||||||
|
while not self.conn.stop_event.is_set():
|
||||||
|
try:
|
||||||
|
msg = await self.ws.recv()
|
||||||
|
self.last_active_time = time.time()
|
||||||
|
|
||||||
|
# 检查客户端是否中止
|
||||||
|
if self.conn.client_abort:
|
||||||
|
logger.bind(tag=TAG).info("收到打断信息,终止监听TTS响应")
|
||||||
|
break
|
||||||
|
|
||||||
|
if isinstance(msg, str): # JSON控制消息
|
||||||
|
try:
|
||||||
|
data = json.loads(msg)
|
||||||
|
event = data["header"].get("event")
|
||||||
|
|
||||||
|
if event == "task-started":
|
||||||
|
logger.bind(tag=TAG).debug("TTS任务启动成功~")
|
||||||
|
self.tts_audio_queue.put((SentenceType.FIRST, [], None))
|
||||||
|
elif event == "result-generated":
|
||||||
|
# 发送缓存的数据
|
||||||
|
if self.conn.tts_MessageText:
|
||||||
|
logger.bind(tag=TAG).info(
|
||||||
|
f"句子语音生成成功: {self.conn.tts_MessageText}"
|
||||||
|
)
|
||||||
|
self.tts_audio_queue.put(
|
||||||
|
(SentenceType.FIRST, [], self.conn.tts_MessageText)
|
||||||
|
)
|
||||||
|
self.conn.tts_MessageText = None
|
||||||
|
elif event == "task-finished":
|
||||||
|
logger.bind(tag=TAG).debug("TTS任务完成~")
|
||||||
|
self._process_before_stop_play_files()
|
||||||
|
session_finished = True
|
||||||
|
break
|
||||||
|
elif event == "task-failed":
|
||||||
|
error_code = data["header"].get("error_code", "unknown")
|
||||||
|
error_message = data["header"].get("error_message", "未知错误")
|
||||||
|
logger.bind(tag=TAG).error(
|
||||||
|
f"TTS任务失败: {error_code} - {error_message}"
|
||||||
|
)
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.bind(tag=TAG).warning("收到无效的JSON消息")
|
||||||
|
elif isinstance(msg, (bytes, bytearray)):
|
||||||
|
self.opus_encoder.encode_pcm_to_opus_stream(
|
||||||
|
msg, False, callback=self.handle_opus
|
||||||
|
)
|
||||||
|
except websockets.ConnectionClosed:
|
||||||
|
logger.bind(tag=TAG).warning("WebSocket连接已关闭")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(
|
||||||
|
f"处理TTS响应时出错: {e}\n{traceback.format_exc()}"
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
# 仅在连接异常且非正常结束时才关闭连接
|
||||||
|
if not session_finished and self.ws:
|
||||||
|
try:
|
||||||
|
await self.ws.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.ws = None
|
||||||
|
# 监听任务退出时清理引用
|
||||||
|
finally:
|
||||||
|
self._monitor_task = None
|
||||||
|
|
||||||
|
def to_tts(self, text: str) -> list:
|
||||||
|
"""非流式生成音频数据,用于生成音频及测试场景"""
|
||||||
|
try:
|
||||||
|
# 创建事件循环
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
# 生成会话ID
|
||||||
|
session_id = uuid.uuid4().hex
|
||||||
|
# 存储音频数据
|
||||||
|
audio_data = []
|
||||||
|
|
||||||
|
async def _generate_audio():
|
||||||
|
ws = await websockets.connect(
|
||||||
|
self.ws_url,
|
||||||
|
additional_headers=self.header,
|
||||||
|
ping_interval=30,
|
||||||
|
ping_timeout=10,
|
||||||
|
close_timeout=10,
|
||||||
|
max_size=10 * 1024 * 1024,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 发送run-task消息启动会话
|
||||||
|
run_task_message = {
|
||||||
|
"header": {
|
||||||
|
"action": "run-task",
|
||||||
|
"task_id": session_id,
|
||||||
|
"streaming": "duplex",
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"task_group": "audio",
|
||||||
|
"task": "tts",
|
||||||
|
"function": "SpeechSynthesizer",
|
||||||
|
"model": self.model,
|
||||||
|
"parameters": {
|
||||||
|
"text_type": "PlainText",
|
||||||
|
"voice": self.voice,
|
||||||
|
"format": self.format,
|
||||||
|
"sample_rate": self.sample_rate,
|
||||||
|
"volume": self.volume,
|
||||||
|
"rate": self.rate,
|
||||||
|
"pitch": self.pitch,
|
||||||
|
},
|
||||||
|
"input": {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await ws.send(json.dumps(run_task_message))
|
||||||
|
|
||||||
|
# 等待任务启动
|
||||||
|
task_started = False
|
||||||
|
while not task_started:
|
||||||
|
msg = await ws.recv()
|
||||||
|
if isinstance(msg, str):
|
||||||
|
data = json.loads(msg)
|
||||||
|
header = data.get("header", {})
|
||||||
|
if header.get("event") == "task-started":
|
||||||
|
task_started = True
|
||||||
|
logger.bind(tag=TAG).debug("TTS任务已启动")
|
||||||
|
elif header.get("event") == "task-failed":
|
||||||
|
error_code = header.get("error_code", "unknown")
|
||||||
|
error_message = header.get("error_message", "未知错误")
|
||||||
|
raise Exception(
|
||||||
|
f"启动任务失败: {error_code} - {error_message}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 发送文本
|
||||||
|
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||||
|
# 发送continue-task消息
|
||||||
|
continue_task_message = {
|
||||||
|
"header": {
|
||||||
|
"action": "continue-task",
|
||||||
|
"task_id": session_id,
|
||||||
|
"streaming": "duplex",
|
||||||
|
},
|
||||||
|
"payload": {"input": {"text": filtered_text}},
|
||||||
|
}
|
||||||
|
await ws.send(json.dumps(continue_task_message))
|
||||||
|
|
||||||
|
# 发送finish-task消息
|
||||||
|
finish_task_message = {
|
||||||
|
"header": {
|
||||||
|
"action": "finish-task",
|
||||||
|
"task_id": session_id,
|
||||||
|
"streaming": "duplex",
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"input": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await ws.send(json.dumps(finish_task_message))
|
||||||
|
|
||||||
|
# 接收音频数据
|
||||||
|
task_finished = False
|
||||||
|
while not task_finished:
|
||||||
|
msg = await ws.recv()
|
||||||
|
if isinstance(msg, (bytes, bytearray)):
|
||||||
|
self.opus_encoder.encode_pcm_to_opus_stream(
|
||||||
|
msg,
|
||||||
|
end_of_stream=False,
|
||||||
|
callback=lambda opus: audio_data.append(opus)
|
||||||
|
)
|
||||||
|
elif isinstance(msg, str):
|
||||||
|
data = json.loads(msg)
|
||||||
|
header = data.get("header", {})
|
||||||
|
if header.get("event") == "task-finished":
|
||||||
|
task_finished = True
|
||||||
|
logger.bind(tag=TAG).debug("TTS任务完成")
|
||||||
|
elif header.get("event") == "task-failed":
|
||||||
|
error_code = header.get("error_code", "unknown")
|
||||||
|
error_message = header.get("error_message", "未知错误")
|
||||||
|
raise Exception(
|
||||||
|
f"合成失败: {error_code} - {error_message}"
|
||||||
|
)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 清理资源
|
||||||
|
try:
|
||||||
|
await ws.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 运行异步任务
|
||||||
|
loop.run_until_complete(_generate_audio())
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
return audio_data
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"生成音频数据失败: {str(e)}")
|
||||||
|
return []
|
||||||
@@ -457,7 +457,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
logger.bind(tag=TAG).warning("收到无效的JSON消息")
|
logger.bind(tag=TAG).warning("收到无效的JSON消息")
|
||||||
# 二进制消息(音频数据)
|
# 二进制消息(音频数据)
|
||||||
elif isinstance(msg, (bytes, bytearray)):
|
elif isinstance(msg, (bytes, bytearray)):
|
||||||
logger.bind(tag=TAG).debug(f"推送数据到队列里面~~")
|
|
||||||
self.opus_encoder.encode_pcm_to_opus_stream(msg, False, self.handle_opus)
|
self.opus_encoder.encode_pcm_to_opus_stream(msg, False, self.handle_opus)
|
||||||
except websockets.ConnectionClosed:
|
except websockets.ConnectionClosed:
|
||||||
logger.bind(tag=TAG).warning("WebSocket连接已关闭")
|
logger.bind(tag=TAG).warning("WebSocket连接已关闭")
|
||||||
|
|||||||
@@ -331,15 +331,6 @@ class TTSProviderBase(ABC):
|
|||||||
|
|
||||||
# 收到下一个文本开始或会话结束时进行上报
|
# 收到下一个文本开始或会话结束时进行上报
|
||||||
if sentence_type is not SentenceType.MIDDLE:
|
if sentence_type is not SentenceType.MIDDLE:
|
||||||
# 重置音频流控状态(新句子开始或者结束)
|
|
||||||
if hasattr(self.conn, 'audio_flow_control'):
|
|
||||||
self.conn.audio_flow_control = {
|
|
||||||
'last_send_time': 0,
|
|
||||||
'packet_count': 0,
|
|
||||||
'start_time': time.perf_counter(),
|
|
||||||
'sequence': 0 # 添加序列号
|
|
||||||
}
|
|
||||||
|
|
||||||
# 上报TTS数据
|
# 上报TTS数据
|
||||||
if enqueue_text is not None and enqueue_audio is not None:
|
if enqueue_text is not None and enqueue_audio is not None:
|
||||||
enqueue_tts_report(self.conn, enqueue_text, enqueue_audio)
|
enqueue_tts_report(self.conn, enqueue_text, enqueue_audio)
|
||||||
|
|||||||
@@ -449,7 +449,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
res.optional.event == EVENT_TTSResponse
|
res.optional.event == EVENT_TTSResponse
|
||||||
and res.header.message_type == AUDIO_ONLY_RESPONSE
|
and res.header.message_type == AUDIO_ONLY_RESPONSE
|
||||||
):
|
):
|
||||||
logger.bind(tag=TAG).debug(f"推送数据到队列里面~~")
|
|
||||||
self.wav_to_opus_data_audio_raw_stream(res.payload, callback=self.handle_opus)
|
self.wav_to_opus_data_audio_raw_stream(res.payload, callback=self.handle_opus)
|
||||||
elif res.optional.event == EVENT_TTSSentenceEnd:
|
elif res.optional.event == EVENT_TTSSentenceEnd:
|
||||||
logger.bind(tag=TAG).info(f"句子语音生成成功:{self.tts_text}")
|
logger.bind(tag=TAG).info(f"句子语音生成成功:{self.tts_text}")
|
||||||
@@ -616,12 +615,13 @@ class TTSProvider(TTSProviderBase):
|
|||||||
"speech_rate": self.speech_rate,
|
"speech_rate": self.speech_rate,
|
||||||
"loudness_rate": self.loudness_rate
|
"loudness_rate": self.loudness_rate
|
||||||
},
|
},
|
||||||
|
"additions": json.dumps({
|
||||||
|
"post_process": {
|
||||||
|
"pitch": self.pitch
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
"additions": {
|
|
||||||
"post_process": {
|
|
||||||
"pitch": self.pitch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
text = MarkdownCleaner.clean_markdown(text)
|
text = MarkdownCleaner.clean_markdown(text)
|
||||||
|
|
||||||
payload = {"text": text, "character": self.character}
|
payload = {"text": text, "character": self.voice}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with requests.post(self.api_url, json=payload, timeout=5) as response:
|
with requests.post(self.api_url, json=payload, timeout=5) as response:
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ class PromptManager:
|
|||||||
local_address=local_address,
|
local_address=local_address,
|
||||||
weather_info=weather_info,
|
weather_info=weather_info,
|
||||||
emojiList=EMOJI_List,
|
emojiList=EMOJI_List,
|
||||||
|
device_id=device_id,
|
||||||
)
|
)
|
||||||
device_cache_key = f"device_prompt:{device_id}"
|
device_cache_key = f"device_prompt:{device_id}"
|
||||||
self.cache_manager.set(
|
self.cache_manager.set(
|
||||||
|
|||||||
@@ -35,3 +35,4 @@ PyJWT==2.8.0
|
|||||||
psutil==7.0.0
|
psutil==7.0.0
|
||||||
portalocker==3.2.0
|
portalocker==3.2.0
|
||||||
Jinja2==3.1.6
|
Jinja2==3.1.6
|
||||||
|
vosk==0.3.45
|
||||||
|
|||||||
Reference in New Issue
Block a user