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 | |
|---|---|---|---|
|
|
83f86406b5 | ||
|
|
dde237ff0d | ||
|
|
6a4887e252 | ||
|
|
d945b94910 | ||
|
|
16a7aeec3b | ||
|
|
94605345e5 | ||
|
|
ec9fa32c81 | ||
|
|
264aa15281 | ||
|
|
180a6ff824 | ||
|
|
5904ee1571 | ||
|
|
bc99341f85 | ||
|
|
8b9174b67e | ||
|
|
6c04a6cf69 | ||
|
|
14a63c4b97 | ||
|
|
098d13a34b | ||
|
|
488f247744 | ||
|
|
c74dbf03cd | ||
|
|
1f836c3235 |
@@ -158,6 +158,7 @@ my_wakeup_words.mp3
|
|||||||
!main/xiaozhi-server/config/assets/bind_code.wav
|
!main/xiaozhi-server/config/assets/bind_code.wav
|
||||||
!main/xiaozhi-server/config/assets/bind_not_found.wav
|
!main/xiaozhi-server/config/assets/bind_not_found.wav
|
||||||
!main/xiaozhi-server/config/assets/bind_code/*.wav
|
!main/xiaozhi-server/config/assets/bind_code/*.wav
|
||||||
|
!main/xiaozhi-server/config/assets/max_output_size.wav
|
||||||
main/manager-api/.vscode
|
main/manager-api/.vscode
|
||||||
|
|
||||||
# Ignore webpack cache directory
|
# Ignore webpack cache directory
|
||||||
|
|||||||
+10
-1
@@ -4,7 +4,16 @@
|
|||||||
先按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf)
|
先按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf)
|
||||||
|
|
||||||
## 第2步 打开配置文件
|
## 第2步 打开配置文件
|
||||||
配置好编译环境后,下载虾哥[xiaozhi-esp32](https://github.com/78/xiaozhi-esp32)项目源码,进入虾哥项目,打开`xiaozhi-esp32/main/Kconfig.projbuild`文件。
|
配置好编译环境后,下载虾哥iaozhi-esp32项目源码,
|
||||||
|
|
||||||
|
从这里下载虾哥[xiaozhi-esp32项目源码](https://github.com/78/xiaozhi-esp32/archive/refs/tags/v1.6.0.zip)。
|
||||||
|
|
||||||
|
从这里下载虾哥[xiaozhi-esp32项目源码](https://github.com/78/xiaozhi-esp32/archive/refs/tags/v1.6.0.zip)。
|
||||||
|
|
||||||
|
从这里下载虾哥[xiaozhi-esp32项目源码](https://github.com/78/xiaozhi-esp32/archive/refs/tags/v1.6.0.zip)。
|
||||||
|
|
||||||
|
下载后,解压缩包,打开`xiaozhi-esp32/main/Kconfig.projbuild`文件。
|
||||||
|
|
||||||
|
|
||||||
## 第3步 修改WEBSOCKET地址
|
## 第3步 修改WEBSOCKET地址
|
||||||
找到`WEBSOCKET_URL`的`default`的内容,把`wss://api.tenclass.net/xiaozhi/v1/`
|
找到`WEBSOCKET_URL`的`default`的内容,把`wss://api.tenclass.net/xiaozhi/v1/`
|
||||||
|
|||||||
@@ -163,4 +163,9 @@ public interface Constant {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本号
|
||||||
|
*/
|
||||||
|
public static final String VERSION = "0.3.9";
|
||||||
}
|
}
|
||||||
@@ -75,4 +75,11 @@ public class RedisKeys {
|
|||||||
public static String getTimbreDetailsKey(String id) {
|
public static String getTimbreDetailsKey(String id) {
|
||||||
return "timbre:details:" + id;
|
return "timbre:details:" + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取版本号Key
|
||||||
|
*/
|
||||||
|
public static String getVersionKey() {
|
||||||
|
return "system:version";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package xiaozhi.common.redis;
|
package xiaozhi.common.redis;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.springframework.data.redis.core.HashOperations;
|
import org.springframework.data.redis.core.HashOperations;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -27,7 +30,7 @@ public class RedisUtils {
|
|||||||
/**
|
/**
|
||||||
* 过期时长为1小时,单位:秒
|
* 过期时长为1小时,单位:秒
|
||||||
*/
|
*/
|
||||||
public final static long HOUR_ONE_EXPIRE = 60 * 60 * 1L;
|
public final static long HOUR_ONE_EXPIRE = (long) 60 * 60;
|
||||||
/**
|
/**
|
||||||
* 过期时长为6小时,单位:秒
|
* 过期时长为6小时,单位:秒
|
||||||
*/
|
*/
|
||||||
@@ -124,4 +127,24 @@ public class RedisUtils {
|
|||||||
public Object rightPop(String key) {
|
public Object rightPop(String key) {
|
||||||
return redisTemplate.opsForList().rightPop(key);
|
return redisTemplate.opsForList().rightPop(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有 Redis 数据库中的所有键
|
||||||
|
*/
|
||||||
|
public void emptyAll() {
|
||||||
|
// Lua 脚本 FLUSHALL是redis清空所有库的命令
|
||||||
|
String luaScript ="redis.call('FLUSHALL')";
|
||||||
|
|
||||||
|
// 创建 DefaultRedisScript 对象
|
||||||
|
DefaultRedisScript<Void> redisScript = new DefaultRedisScript<>();
|
||||||
|
redisScript.setScriptText(luaScript); // 设置 Lua 脚本内容
|
||||||
|
redisScript.setResultType(Void.class); // 设置返回值类型
|
||||||
|
|
||||||
|
// 执行 Lua 脚本
|
||||||
|
List<String> keys = Collections.emptyList(); // 如果脚本不依赖 key,可以传入空列表
|
||||||
|
redisTemplate.execute(redisScript, keys);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,9 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.context.annotation.DependsOn;
|
import org.springframework.context.annotation.DependsOn;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.modules.config.service.ConfigService;
|
import xiaozhi.modules.config.service.ConfigService;
|
||||||
import xiaozhi.modules.sys.service.SysParamsService;
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
@@ -18,8 +21,20 @@ public class SystemInitConfig {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ConfigService configService;
|
private ConfigService configService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisUtils redisUtils;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
|
// 检查版本号
|
||||||
|
String redisVersion = (String) redisUtils.get(RedisKeys.getVersionKey());
|
||||||
|
if (!Constant.VERSION.equals(redisVersion)) {
|
||||||
|
// 如果版本不一致,清空Redis
|
||||||
|
redisUtils.emptyAll();
|
||||||
|
// 存储新版本号
|
||||||
|
redisUtils.set(RedisKeys.getVersionKey(), Constant.VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
sysParamsService.initServerSecret();
|
sysParamsService.initServerSecret();
|
||||||
configService.getConfig(false);
|
configService.getConfig(false);
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -91,6 +91,7 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
}
|
}
|
||||||
throw new RenException(ErrorCode.OTA_DEVICE_NOT_FOUND, "not found device");
|
throw new RenException(ErrorCode.OTA_DEVICE_NOT_FOUND, "not found device");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取智能体信息
|
// 获取智能体信息
|
||||||
AgentEntity agent = agentService.getAgentById(device.getAgentId());
|
AgentEntity agent = agentService.getAgentById(device.getAgentId());
|
||||||
if (agent == null) {
|
if (agent == null) {
|
||||||
@@ -104,7 +105,9 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
}
|
}
|
||||||
// 构建返回数据
|
// 构建返回数据
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
// 获取单台设备每天最多输出字数
|
||||||
|
String deviceMaxOutputSize = sysParamsService.getValue("device_max_output_size", true);
|
||||||
|
result.put("device_max_output_size", deviceMaxOutputSize);
|
||||||
// 如果客户端已实例化模型,则不返回
|
// 如果客户端已实例化模型,则不返回
|
||||||
String alreadySelectedVadModelId = (String) selectedModule.get("VAD");
|
String alreadySelectedVadModelId = (String) selectedModule.get("VAD");
|
||||||
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {
|
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {
|
||||||
@@ -267,6 +270,12 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
if (intentLLMModelId != null && intentLLMModelId.equals(llmModelId)) {
|
if (intentLLMModelId != null && intentLLMModelId.equals(llmModelId)) {
|
||||||
intentLLMModelId = null;
|
intentLLMModelId = null;
|
||||||
}
|
}
|
||||||
|
} else if ("function_call".equals(map.get("type"))) {
|
||||||
|
String functionStr = (String) map.get("functions");
|
||||||
|
if (StringUtils.isNotBlank(functionStr)) {
|
||||||
|
String[] functions = functionStr.split("\\;");
|
||||||
|
map.put("functions", functions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果是LLM类型,且intentLLMModelId不为空,则添加附加模型
|
// 如果是LLM类型,且intentLLMModelId不为空,则添加附加模型
|
||||||
@@ -282,7 +291,7 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
|
|
||||||
result.put("selected_module", selectedModule);
|
result.put("selected_module", selectedModule);
|
||||||
if (StringUtils.isNotBlank(prompt)) {
|
if (StringUtils.isNotBlank(prompt)) {
|
||||||
prompt = prompt.replace("{{assistant_name}}", "小智");
|
prompt = prompt.replace("{{assistant_name}}", StringUtils.isBlank(assistantName) ? "小智" : assistantName);
|
||||||
}
|
}
|
||||||
result.put("prompt", prompt);
|
result.put("prompt", prompt);
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-3
@@ -15,25 +15,29 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||||
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
import xiaozhi.modules.device.utils.NetworkUtil;
|
import xiaozhi.modules.device.utils.NetworkUtil;
|
||||||
|
|
||||||
@Tag(name = "设备管理", description = "OTA 相关接口")
|
@Tag(name = "设备管理", description = "OTA 相关接口")
|
||||||
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@RequestMapping("/ota/")
|
@RequestMapping("/ota/")
|
||||||
public class OTAController {
|
public class OTAController {
|
||||||
private final DeviceService deviceService;
|
private final DeviceService deviceService;
|
||||||
|
|
||||||
@Operation(summary = "检查 OTA 版本和设备激活状态")
|
@Operation(summary = "OTA版本和设备激活状态检查")
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<String> checkOTAVersion(
|
public ResponseEntity<String> checkOTAVersion(
|
||||||
@RequestBody DeviceReportReqDTO deviceReportReqDTO,
|
@RequestBody DeviceReportReqDTO deviceReportReqDTO,
|
||||||
@@ -54,9 +58,25 @@ public class OTAController {
|
|||||||
return createResponse(deviceService.checkDeviceActive(macAddress, clientId, deviceReportReqDTO));
|
return createResponse(deviceService.checkDeviceActive(macAddress, clientId, deviceReportReqDTO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "设备快速检查激活状态")
|
||||||
|
@PostMapping("activate")
|
||||||
|
public ResponseEntity<String> activateDevice(
|
||||||
|
@Parameter(name = "Device-Id", description = "设备唯一标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Device-Id") String deviceId,
|
||||||
|
@Parameter(name = "Client-Id", description = "客户端标识", required = false, in = ParameterIn.HEADER) @RequestHeader(value = "Client-Id", required = false) String clientId) {
|
||||||
|
if (StringUtils.isBlank(deviceId)) {
|
||||||
|
return ResponseEntity.status(202).build();
|
||||||
|
}
|
||||||
|
DeviceEntity device = deviceService.getDeviceByMacAddress(deviceId);
|
||||||
|
if (device == null) {
|
||||||
|
return ResponseEntity.status(202).build();
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok("success");
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<String> getOTAPrompt() {
|
@Hidden
|
||||||
return createResponse(DeviceReportRespDTO.createError("请提交正确的ota参数"));
|
public ResponseEntity<String> getOTA() {
|
||||||
|
return ResponseEntity.ok("OTA接口运行正常");
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ public class DeviceReportRespDTO {
|
|||||||
@Schema(description = "激活码信息: 激活地址")
|
@Schema(description = "激活码信息: 激活地址")
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
|
@Schema(description = "挑战码")
|
||||||
|
private String challenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
|||||||
+5
-2
@@ -251,17 +251,20 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
if (StringUtils.isNotBlank(cachedCode)) {
|
if (StringUtils.isNotBlank(cachedCode)) {
|
||||||
code.setCode(cachedCode);
|
code.setCode(cachedCode);
|
||||||
code.setMessage(frontedUrl + "\n" + cachedCode);
|
code.setMessage(frontedUrl + "\n" + cachedCode);
|
||||||
|
code.setChallenge(deviceId);
|
||||||
} else {
|
} else {
|
||||||
String newCode = RandomUtil.randomNumbers(6);
|
String newCode = RandomUtil.randomNumbers(6);
|
||||||
code.setCode(newCode);
|
code.setCode(newCode);
|
||||||
code.setMessage(frontedUrl + "\n" + newCode);
|
code.setMessage(frontedUrl + "\n" + newCode);
|
||||||
|
code.setChallenge(deviceId);
|
||||||
|
|
||||||
Map<String, Object> dataMap = new HashMap<>();
|
Map<String, Object> dataMap = new HashMap<>();
|
||||||
dataMap.put("id", deviceId);
|
dataMap.put("id", deviceId);
|
||||||
dataMap.put("mac_address", deviceId);
|
dataMap.put("mac_address", deviceId);
|
||||||
|
|
||||||
dataMap.put("board", (deviceReport.getChipModelName() != null) ? deviceReport.getChipModelName()
|
dataMap.put("board", (deviceReport.getBoard() != null && deviceReport.getBoard().getType() != null)
|
||||||
: (deviceReport.getBoard() != null ? deviceReport.getBoard().getType() : "unknown"));
|
? deviceReport.getBoard().getType()
|
||||||
|
: (deviceReport.getChipModelName() != null ? deviceReport.getChipModelName() : "unknown"));
|
||||||
dataMap.put("app_version", (deviceReport.getApplication() != null)
|
dataMap.put("app_version", (deviceReport.getApplication() != null)
|
||||||
? deviceReport.getApplication().getVersion()
|
? deviceReport.getApplication().getVersion()
|
||||||
: null);
|
: null);
|
||||||
|
|||||||
+2
-1
@@ -15,6 +15,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.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.TokenDTO;
|
import xiaozhi.common.page.TokenDTO;
|
||||||
@@ -121,7 +122,7 @@ public class LoginController {
|
|||||||
@Operation(summary = "公共配置")
|
@Operation(summary = "公共配置")
|
||||||
public Result<Map<String, Object>> pubConfig() {
|
public Result<Map<String, Object>> pubConfig() {
|
||||||
Map<String, Object> config = new HashMap<>();
|
Map<String, Object> config = new HashMap<>();
|
||||||
config.put("version", "0.3.6");
|
config.put("version", Constant.VERSION);
|
||||||
config.put("allowUserRegister", sysUserService.getAllowUserRegister());
|
config.put("allowUserRegister", sysUserService.getAllowUserRegister());
|
||||||
return new Result<Map<String, Object>>().ok(config);
|
return new Result<Map<String, Object>>().ok(config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
delete from `ai_model_config` where id = 'Intent_function_call';
|
||||||
|
INSERT INTO `ai_model_config` VALUES ('Intent_function_call', 'Intent', 'function_call', '函数调用意图识别', 0, 1, '{\"type\": \"function_call\", \"functions\": \"change_role;get_weather;get_news;play_music\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- 调整意图识别配置
|
||||||
|
delete from `ai_model_config` where id = 'Intent_function_call';
|
||||||
|
INSERT INTO `ai_model_config` VALUES ('Intent_function_call', 'Intent', 'function_call', '函数调用意图识别', 0, 1, '{\"type\": \"function_call\", \"functions\": \"change_role;get_weather;get_news;play_music\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
-- 增加单台设备每天最多聊天句数
|
||||||
|
delete from `sys_params` where id = 105;
|
||||||
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (105, 'device_max_output_size', '0', 'number', 1, '单台设备每天最多输出字数,0表示不限制');
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-- 删除无用模型供应器
|
||||||
|
delete from `ai_model_provider` where id = 'SYSTEM_LLM_doubao';
|
||||||
|
delete from `ai_model_provider` where id = 'SYSTEM_LLM_chatglm';
|
||||||
|
delete from `ai_model_provider` where id = 'SYSTEM_TTS_302ai';
|
||||||
|
delete from `ai_model_provider` where id = 'SYSTEM_TTS_gizwits';
|
||||||
|
|
||||||
|
-- 添加模型供应器
|
||||||
|
delete from `ai_model_provider` where id = 'SYSTEM_ASR_TencentASR';
|
||||||
|
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
|
||||||
|
('SYSTEM_ASR_TencentASR', 'ASR', 'tencent', '腾讯语音识别', '[{"key":"appid","label":"应用ID","type":"string"},{"key":"secret_id","label":"Secret ID","type":"string"},{"key":"secret_key","label":"Secret Key","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 4, 1, NOW(), 1, NOW());
|
||||||
|
|
||||||
|
-- 添加腾讯语音合成模型供应器
|
||||||
|
delete from `ai_model_provider` where id = 'SYSTEM_TTS_TencentTTS';
|
||||||
|
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
|
||||||
|
('SYSTEM_TTS_TencentTTS', 'TTS', 'tencent', '腾讯语音合成', '[{"key":"appid","label":"应用ID","type":"string"},{"key":"secret_id","label":"Secret ID","type":"string"},{"key":"secret_key","label":"Secret Key","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"region","label":"区域","type":"string"},{"key":"voice","label":"音色ID","type":"string"}]', 5, 1, NOW(), 1, NOW());
|
||||||
|
|
||||||
|
-- 添加腾讯语音合成音色
|
||||||
|
delete from `ai_tts_voice` where id = 'TTS_TencentTTS0001';
|
||||||
|
INSERT INTO `ai_tts_voice` VALUES ('TTS_TencentTTS0001', 'TTS_TencentTTS', '智瑜', '101001', '中文', NULL, NULL, 1, NULL, NULL, NULL, NULL);
|
||||||
@@ -58,3 +58,17 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202504151206.sql
|
path: classpath:db/changelog/202504151206.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202504181536
|
||||||
|
author: John
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202504181536.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202504211118
|
||||||
|
author: John
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202504211118.sql
|
||||||
@@ -77,7 +77,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex;justify-content: center;">
|
<div style="display: flex;justify-content: center;">
|
||||||
<el-button type="primary" @click="confirm" class="save-btn">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="confirm"
|
||||||
|
class="save-btn"
|
||||||
|
:loading="saving"
|
||||||
|
:disabled="saving">
|
||||||
保存
|
保存
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,6 +99,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
saving: false,
|
||||||
providers: [],
|
providers: [],
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
providersLoaded: false,
|
providersLoaded: false,
|
||||||
@@ -175,6 +181,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleClose() {
|
handleClose() {
|
||||||
|
this.saving = false;
|
||||||
this.$emit('update:visible', false);
|
this.$emit('update:visible', false);
|
||||||
},
|
},
|
||||||
initDynamicConfig() {
|
initDynamicConfig() {
|
||||||
@@ -185,8 +192,11 @@ export default {
|
|||||||
this.formData.configJson = newConfig;
|
this.formData.configJson = newConfig;
|
||||||
},
|
},
|
||||||
confirm() {
|
confirm() {
|
||||||
|
this.saving = true;
|
||||||
|
|
||||||
if (!this.formData.supplier) {
|
if (!this.formData.supplier) {
|
||||||
this.$message.error('请选择供应器');
|
this.$message.error('请选择供应器');
|
||||||
|
this.saving = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,11 +216,18 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$emit('confirm', submitData);
|
try {
|
||||||
this.$emit('update:visible', false);
|
this.$emit('confirm', submitData);
|
||||||
this.resetForm();
|
this.$emit('update:visible', false);
|
||||||
|
this.resetForm();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resetForm() {
|
resetForm() {
|
||||||
|
this.saving = false;
|
||||||
this.formData = {
|
this.formData = {
|
||||||
modelName: '',
|
modelName: '',
|
||||||
modelCode: '',
|
modelCode: '',
|
||||||
|
|||||||
@@ -23,12 +23,6 @@
|
|||||||
<div class="settings-btn" @click="handleConfigure">
|
<div class="settings-btn" @click="handleConfigure">
|
||||||
配置角色
|
配置角色
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="settings-btn">-->
|
|
||||||
<!-- 声纹识别-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- <div class="settings-btn">-->
|
|
||||||
<!-- 历史对话-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<div class="settings-btn" @click="handleDeviceManage">
|
<div class="settings-btn" @click="handleDeviceManage">
|
||||||
设备管理({{ device.deviceCount }})
|
设备管理({{ device.deviceCount }})
|
||||||
</div>
|
</div>
|
||||||
@@ -80,7 +74,7 @@ export default {
|
|||||||
|
|
||||||
.settings-btn {
|
.settings-btn {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
color: #5778ff;
|
color: #5778ff;
|
||||||
background: #e6ebff;
|
background: #e6ebff;
|
||||||
width: auto;
|
width: auto;
|
||||||
@@ -95,7 +89,7 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
color: #979db1;
|
color: #979db1;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export default {
|
|||||||
.header {
|
.header {
|
||||||
background: #f6fcfe66;
|
background: #f6fcfe66;
|
||||||
border: 1px solid #fff;
|
border: 1px solid #fff;
|
||||||
height: 53px !important;
|
height: 63px !important;
|
||||||
min-width: 900px;
|
min-width: 900px;
|
||||||
/* 设置最小宽度防止过度压缩 */
|
/* 设置最小宽度防止过度压缩 */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -197,7 +197,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.brand-img {
|
.brand-img {
|
||||||
height: 18px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-center {
|
.header-center {
|
||||||
@@ -219,13 +219,13 @@ export default {
|
|||||||
|
|
||||||
.equipment-management {
|
.equipment-management {
|
||||||
padding: 0 9px;
|
padding: 0 9px;
|
||||||
width: 82px;
|
width: px;
|
||||||
height: 24px;
|
height: 30px;
|
||||||
border-radius: 12px;
|
border-radius: 15px;
|
||||||
background: #deeafe;
|
background: #deeafe;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 10px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
color: #3d4566;
|
color: #3d4566;
|
||||||
@@ -235,6 +235,7 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
/* 防止导航按钮被压缩 */
|
/* 防止导航按钮被压缩 */
|
||||||
|
padding: 0px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.equipment-management.active-tab {
|
.equipment-management.active-tab {
|
||||||
|
|||||||
@@ -76,7 +76,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex;justify-content: center;">
|
<div style="display: flex;justify-content: center;">
|
||||||
<el-button type="primary" @click="handleSave" class="save-btn">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleSave"
|
||||||
|
class="save-btn"
|
||||||
|
:loading="saving"
|
||||||
|
:disabled="saving">
|
||||||
保存
|
保存
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,6 +107,7 @@ export default {
|
|||||||
dialogVisible: this.visible,
|
dialogVisible: this.visible,
|
||||||
providers: [],
|
providers: [],
|
||||||
providersLoaded: false,
|
providersLoaded: false,
|
||||||
|
saving: false,
|
||||||
allProvidersData: null,
|
allProvidersData: null,
|
||||||
pendingProviderType: null,
|
pendingProviderType: null,
|
||||||
pendingModelData: null,
|
pendingModelData: null,
|
||||||
@@ -195,6 +201,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleSave() {
|
handleSave() {
|
||||||
|
this.saving = true; // 开始保存加载
|
||||||
|
|
||||||
const provideCode = this.form.configJson.type;
|
const provideCode = this.form.configJson.type;
|
||||||
const formData = {
|
const formData = {
|
||||||
id: this.modelData.id,
|
id: this.modelData.id,
|
||||||
@@ -209,8 +217,19 @@ export default {
|
|||||||
...this.form.configJson,
|
...this.form.configJson,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.$emit("save", { provideCode, formData });
|
|
||||||
this.dialogVisible = false;
|
this.$emit("save", {
|
||||||
|
provideCode,
|
||||||
|
formData,
|
||||||
|
done: () => {
|
||||||
|
this.saving = false; // 保存完成后回调
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果父组件不处理done回调,3秒后自动关闭加载状态
|
||||||
|
setTimeout(() => {
|
||||||
|
this.saving = false;
|
||||||
|
}, 3000);
|
||||||
},
|
},
|
||||||
loadProviders() {
|
loadProviders() {
|
||||||
if (this.providersLoaded) return;
|
if (this.providersLoaded) return;
|
||||||
|
|||||||
@@ -40,7 +40,12 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button type="primary" @click="submit" class="save-btn">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="submit"
|
||||||
|
class="save-btn"
|
||||||
|
:loading="saving"
|
||||||
|
:disabled="saving">
|
||||||
保存
|
保存
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="cancel" class="cancel-btn">
|
<el-button @click="cancel" class="cancel-btn">
|
||||||
@@ -76,6 +81,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
dialogKey: Date.now(),
|
dialogKey: Date.now(),
|
||||||
|
saving: false,
|
||||||
valueTypeOptions: [
|
valueTypeOptions: [
|
||||||
{ value: 'string', label: '字符串(string)' },
|
{ value: 'string', label: '字符串(string)' },
|
||||||
{ value: 'number', label: '数字(number)' },
|
{ value: 'number', label: '数字(number)' },
|
||||||
@@ -100,11 +106,22 @@ export default {
|
|||||||
submit() {
|
submit() {
|
||||||
this.$refs.form.validate((valid) => {
|
this.$refs.form.validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
this.$emit('submit', this.form);
|
this.saving = true; // 开始加载
|
||||||
|
this.$emit('submit', {
|
||||||
|
form: this.form,
|
||||||
|
done: () => {
|
||||||
|
this.saving = false; // 加载完成
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.saving = false;
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
|
this.saving = false; // 取消时重置状态
|
||||||
this.$emit('cancel');
|
this.$emit('cancel');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -44,12 +44,22 @@
|
|||||||
<!-- 右侧内容 -->
|
<!-- 右侧内容 -->
|
||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<el-card class="model-card" shadow="never">
|
<el-card class="model-card" shadow="never">
|
||||||
<el-table ref="modelTable" style="width: 100%" :header-cell-style="{ background: 'transparent' }"
|
<el-table
|
||||||
:data="modelList" class="data-table" header-row-class-name="table-header"
|
ref="modelTable"
|
||||||
:header-cell-class-name="headerCellClassName" @selection-change="handleSelectionChange">
|
style="width: 100%"
|
||||||
|
v-loading="loading"
|
||||||
|
element-loading-text="拼命加载中"
|
||||||
|
element-loading-spinner="el-icon-loading"
|
||||||
|
element-loading-background="rgba(255, 255, 255, 0.7)"
|
||||||
|
:header-cell-style="{ background: 'transparent' }"
|
||||||
|
:data="modelList"
|
||||||
|
class="data-table"
|
||||||
|
header-row-class-name="table-header"
|
||||||
|
:header-cell-class-name="headerCellClassName"
|
||||||
|
@selection-change="handleSelectionChange">
|
||||||
<el-table-column type="selection" width="55" align="center"></el-table-column>
|
<el-table-column type="selection" width="55" align="center"></el-table-column>
|
||||||
|
<el-table-column label="模型ID" prop="id" align="center"></el-table-column>
|
||||||
<el-table-column label="模型名称" prop="modelName" align="center"></el-table-column>
|
<el-table-column label="模型名称" prop="modelName" align="center"></el-table-column>
|
||||||
<el-table-column label="模型编码" prop="modelCode" align="center"></el-table-column>
|
|
||||||
<el-table-column label="提供商" align="center">
|
<el-table-column label="提供商" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
{{ scope.row.configJson.type || '未知' }}
|
{{ scope.row.configJson.type || '未知' }}
|
||||||
@@ -86,41 +96,37 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="table-footer">
|
<div class="table-footer">
|
||||||
<div class="batch-actions">
|
<div class="batch-actions">
|
||||||
<el-button size="mini" type="primary" @click="selectAll">
|
<el-button size="mini" type="primary" @click="selectAll">
|
||||||
{{ isAllSelected ?
|
{{ isAllSelected ?
|
||||||
'取消全选' : '全选' }}
|
'取消全选' : '全选' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="success" size="mini" @click="addModel" class="add-btn">
|
<el-button type="success" size="mini" @click="addModel" class="add-btn">
|
||||||
新增
|
新增
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="mini" type="danger" icon="el-icon-delete" @click="batchDelete">
|
<el-button size="mini" type="danger" icon="el-icon-delete" @click="batchDelete">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</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
|
<el-option v-for="item in pageSizeOptions" :key="item" :label="`${item}条/页`" :value="item">
|
||||||
v-for="item in pageSizeOptions"
|
|
||||||
:key="item"
|
|
||||||
:label="`${item}条/页`"
|
|
||||||
:value="item">
|
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">首页</button>
|
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">首页</button>
|
||||||
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">上一页</button>
|
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">上一页</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">下一页</button>
|
<button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">下一页</button>
|
||||||
<span class="total-text">共{{ total }}条记录</span>
|
<span class="total-text">共{{ total }}条记录</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,7 +163,8 @@ export default {
|
|||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
selectedModels: [],
|
selectedModels: [],
|
||||||
isAllSelected: false
|
isAllSelected: false,
|
||||||
|
loading: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -301,9 +308,10 @@ export default {
|
|||||||
this.currentPage = page;
|
this.currentPage = page;
|
||||||
this.$refs.modelTable.clearSelection();
|
this.$refs.modelTable.clearSelection();
|
||||||
},
|
},
|
||||||
handleModelSave({ provideCode, formData }) {
|
handleModelSave({ provideCode, formData, done }) {
|
||||||
const modelType = this.activeTab;
|
const modelType = this.activeTab;
|
||||||
const id = formData.id;
|
const id = formData.id;
|
||||||
|
|
||||||
Api.model.updateModel(
|
Api.model.updateModel(
|
||||||
{ modelType, provideCode, id, formData },
|
{ modelType, provideCode, id, formData },
|
||||||
({ data }) => {
|
({ data }) => {
|
||||||
@@ -314,6 +322,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.$message.error(data.msg || '保存失败');
|
this.$message.error(data.msg || '保存失败');
|
||||||
}
|
}
|
||||||
|
done && done(); // 调用done回调关闭加载状态
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -385,6 +394,7 @@ export default {
|
|||||||
|
|
||||||
// 获取模型配置列表
|
// 获取模型配置列表
|
||||||
loadData() {
|
loadData() {
|
||||||
|
this.loading = true; // 开始加载
|
||||||
const params = {
|
const params = {
|
||||||
modelType: this.activeTab,
|
modelType: this.activeTab,
|
||||||
modelName: this.search,
|
modelName: this.search,
|
||||||
@@ -393,6 +403,7 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Api.model.getModelList(params, ({ data }) => {
|
Api.model.getModelList(params, ({ data }) => {
|
||||||
|
this.loading = false; // 结束加载
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
this.modelList = data.data.list;
|
this.modelList = data.data.list;
|
||||||
this.total = data.data.total;
|
this.total = data.data.total;
|
||||||
@@ -592,12 +603,12 @@ export default {
|
|||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .page-size-select{
|
::v-deep .page-size-select {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .page-size-select .el-input__inner{
|
::v-deep .page-size-select .el-input__inner {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -606,7 +617,8 @@ export default {
|
|||||||
color: #606266;
|
color: #606266;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
::v-deep .page-size-select .el-input__suffix{
|
|
||||||
|
::v-deep .page-size-select .el-input__suffix {
|
||||||
right: 6px;
|
right: 6px;
|
||||||
width: 15px;
|
width: 15px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@@ -617,13 +629,14 @@ export default {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .page-size-select .el-input__suffix-inner{
|
::v-deep .page-size-select .el-input__suffix-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
::v-deep .page-size-select .el-icon-arrow-up:before{
|
|
||||||
|
::v-deep .page-size-select .el-icon-arrow-up:before {
|
||||||
content: "";
|
content: "";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-left: 6px solid transparent;
|
border-left: 6px solid transparent;
|
||||||
@@ -884,7 +897,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-card{
|
.model-card {
|
||||||
background: white;
|
background: white;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -894,7 +907,7 @@ export default {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-card ::v-deep .el-card__body{
|
.model-card ::v-deep .el-card__body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -912,4 +925,21 @@ export default {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::v-deep .el-loading-mask {
|
||||||
|
background-color: rgba(255, 255, 255, 0.6) !important;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
::v-deep .el-loading-spinner .circular {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
::v-deep .el-loading-spinner .path {
|
||||||
|
stroke: #6b8cff;
|
||||||
|
}
|
||||||
|
::v-deep .el-loading-text {
|
||||||
|
color: #6b8cff !important;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -15,8 +15,15 @@
|
|||||||
<div class="content-panel">
|
<div class="content-panel">
|
||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<el-card class="params-card" shadow="never">
|
<el-card class="params-card" shadow="never">
|
||||||
<el-table ref="paramsTable" :data="paramsList" class="transparent-table"
|
<el-table
|
||||||
:header-cell-class-name="headerCellClassName">
|
ref="paramsTable"
|
||||||
|
:data="paramsList"
|
||||||
|
class="transparent-table"
|
||||||
|
v-loading="loading"
|
||||||
|
element-loading-text="拼命加载中"
|
||||||
|
element-loading-spinner="el-icon-loading"
|
||||||
|
element-loading-background="rgba(255, 255, 255, 0.7)"
|
||||||
|
:header-cell-class-name="headerCellClassName">
|
||||||
<el-table-column label="选择" align="center" width="120">
|
<el-table-column label="选择" 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>
|
||||||
@@ -92,6 +99,7 @@ export default {
|
|||||||
searchCode: "",
|
searchCode: "",
|
||||||
paramsList: [],
|
paramsList: [],
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
|
loading: false,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
pageSizeOptions: [10, 20, 50, 100],
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
total: 0,
|
total: 0,
|
||||||
@@ -138,27 +146,29 @@ export default {
|
|||||||
this.fetchParams();
|
this.fetchParams();
|
||||||
},
|
},
|
||||||
fetchParams() {
|
fetchParams() {
|
||||||
Api.admin.getParamsList(
|
this.loading = true;
|
||||||
{
|
Api.admin.getParamsList(
|
||||||
page: this.currentPage,
|
{
|
||||||
limit: this.pageSize,
|
page: this.currentPage,
|
||||||
paramCode: this.searchCode,
|
limit: this.pageSize,
|
||||||
},
|
paramCode: this.searchCode,
|
||||||
({ data }) => {
|
},
|
||||||
if (data.code === 0) {
|
({ data }) => {
|
||||||
this.paramsList = data.data.list.map(item => ({
|
this.loading = false;
|
||||||
...item,
|
if (data.code === 0) {
|
||||||
selected: false
|
this.paramsList = data.data.list.map(item => ({
|
||||||
}));
|
...item,
|
||||||
this.total = data.data.total;
|
selected: false
|
||||||
} else {
|
}));
|
||||||
this.$message.error({
|
this.total = data.data.total;
|
||||||
message: data.msg || '获取参数列表失败',
|
} else {
|
||||||
showClose: true
|
this.$message.error({
|
||||||
});
|
message: data.msg || '获取参数列表失败',
|
||||||
}
|
showClose: true
|
||||||
}
|
});
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
handleSearch() {
|
handleSearch() {
|
||||||
this.currentPage = 1;
|
this.currentPage = 1;
|
||||||
@@ -185,32 +195,35 @@ export default {
|
|||||||
this.paramForm = { ...row };
|
this.paramForm = { ...row };
|
||||||
this.dialogVisible = true;
|
this.dialogVisible = true;
|
||||||
},
|
},
|
||||||
handleSubmit(form) {
|
|
||||||
if (form.id) {
|
handleSubmit({ form, done }) {
|
||||||
// 编辑
|
if (form.id) {
|
||||||
Api.admin.updateParam(form, ({ data }) => {
|
// 编辑
|
||||||
if (data.code === 0) {
|
Api.admin.updateParam(form, ({ data }) => {
|
||||||
this.$message.success({
|
if (data.code === 0) {
|
||||||
message:"修改成功",
|
this.$message.success({
|
||||||
showClose:true
|
message:"修改成功",
|
||||||
});
|
showClose:true
|
||||||
this.dialogVisible = false;
|
|
||||||
this.fetchParams();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
this.dialogVisible = false;
|
||||||
// 新增
|
this.fetchParams();
|
||||||
Api.admin.addParam(form, ({ data }) => {
|
}
|
||||||
if (data.code === 0) {
|
done && done();
|
||||||
this.$message.success({
|
});
|
||||||
message:"新增成功",
|
} else {
|
||||||
showClose:true
|
// 新增
|
||||||
});
|
Api.admin.addParam(form, ({ data }) => {
|
||||||
this.dialogVisible = false;
|
if (data.code === 0) {
|
||||||
this.fetchParams();
|
this.$message.success({
|
||||||
}
|
message:"新增成功",
|
||||||
|
showClose:true
|
||||||
});
|
});
|
||||||
}
|
this.dialogVisible = false;
|
||||||
|
this.fetchParams();
|
||||||
|
}
|
||||||
|
done && done();
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteSelectedParams() {
|
deleteSelectedParams() {
|
||||||
@@ -656,4 +669,25 @@ export default {
|
|||||||
max-height: calc(var(--table-max-height) - 40px);
|
max-height: calc(var(--table-max-height) - 40px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-loading-mask) {
|
||||||
|
background-color: rgba(255, 255, 255, 0.6) !important;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-loading-spinner .circular) {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-loading-spinner .path) {
|
||||||
|
stroke: #6b8cff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-loading-text) {
|
||||||
|
color: #6b8cff !important;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -15,7 +15,14 @@
|
|||||||
<div class="content-panel">
|
<div class="content-panel">
|
||||||
<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">
|
<el-table
|
||||||
|
ref="userTable"
|
||||||
|
:data="userList"
|
||||||
|
class="transparent-table"
|
||||||
|
v-loading="loading"
|
||||||
|
element-loading-text="拼命加载中"
|
||||||
|
element-loading-spinner="el-icon-loading"
|
||||||
|
element-loading-background="rgba(255, 255, 255, 0.7)">
|
||||||
<el-table-column label="选择" align="center" width="120">
|
<el-table-column label="选择" 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>
|
||||||
@@ -103,7 +110,8 @@ export default {
|
|||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
isAllSelected: false
|
isAllSelected: false,
|
||||||
|
loading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -137,22 +145,24 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
fetchUsers() {
|
fetchUsers() {
|
||||||
Api.admin.getUserList(
|
this.loading = true;
|
||||||
{
|
Api.admin.getUserList(
|
||||||
page: this.currentPage,
|
{
|
||||||
limit: this.pageSize,
|
page: this.currentPage,
|
||||||
mobile: this.searchPhone,
|
limit: this.pageSize,
|
||||||
},
|
mobile: this.searchPhone,
|
||||||
({ data }) => {
|
},
|
||||||
if (data.code === 0) {
|
({ data }) => {
|
||||||
this.userList = data.data.list.map(item => ({
|
this.loading = false; // 结束加载
|
||||||
...item,
|
if (data.code === 0) {
|
||||||
selected: false
|
this.userList = data.data.list.map(item => ({
|
||||||
}));
|
...item,
|
||||||
this.total = data.data.total;
|
selected: false
|
||||||
}
|
}));
|
||||||
}
|
this.total = data.data.total;
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
handleSearch() {
|
handleSearch() {
|
||||||
this.currentPage = 1;
|
this.currentPage = 1;
|
||||||
@@ -682,5 +692,21 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-loading-mask) {
|
||||||
|
background-color: rgba(255, 255, 255, 0.6) !important;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
:deep(.el-loading-spinner .circular) {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
:deep(.el-loading-spinner .path) {
|
||||||
|
stroke: #6b8cff;
|
||||||
|
}
|
||||||
|
:deep(.el-loading-text) {
|
||||||
|
color: #6b8cff !important;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,8 +31,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="device-list-container">
|
<div class="device-list-container">
|
||||||
<DeviceItem v-for="(item, index) in devices" :key="index" :device="item" @configure="goToRoleConfig"
|
<template v-if="isLoading">
|
||||||
@deviceManage="handleDeviceManage" @delete="handleDeleteAgent" />
|
<div
|
||||||
|
v-for="i in skeletonCount"
|
||||||
|
:key="'skeleton-'+i"
|
||||||
|
class="skeleton-item"
|
||||||
|
>
|
||||||
|
<div class="skeleton-image"></div>
|
||||||
|
<div class="skeleton-content">
|
||||||
|
<div class="skeleton-line"></div>
|
||||||
|
<div class="skeleton-line-short"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<DeviceItem
|
||||||
|
v-for="(item, index) in devices"
|
||||||
|
:key="index"
|
||||||
|
:device="item"
|
||||||
|
@configure="goToRoleConfig"
|
||||||
|
@deviceManage="handleDeviceManage"
|
||||||
|
@delete="handleDeleteAgent"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AddWisdomBodyDialog :visible.sync="addDeviceDialogVisible" @confirm="handleWisdomBodyAdded" />
|
<AddWisdomBodyDialog :visible.sync="addDeviceDialogVisible" @confirm="handleWisdomBodyAdded" />
|
||||||
@@ -60,7 +82,9 @@ export default {
|
|||||||
devices: [],
|
devices: [],
|
||||||
originalDevices: [],
|
originalDevices: [],
|
||||||
isSearching: false,
|
isSearching: false,
|
||||||
searchRegex: null
|
searchRegex: null,
|
||||||
|
isLoading: true,
|
||||||
|
skeletonCount: localStorage.getItem('skeletonCount') || 8,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -109,12 +133,26 @@ export default {
|
|||||||
},
|
},
|
||||||
// 获取智能体列表
|
// 获取智能体列表
|
||||||
fetchAgentList() {
|
fetchAgentList() {
|
||||||
|
this.isLoading = true;
|
||||||
Api.agent.getAgentList(({ data }) => {
|
Api.agent.getAgentList(({ data }) => {
|
||||||
this.originalDevices = data.data.map(item => ({
|
if (data?.data) {
|
||||||
...item,
|
this.originalDevices = data.data.map(item => ({
|
||||||
agentId: item.id // 字段映射
|
...item,
|
||||||
}));
|
agentId: item.id
|
||||||
this.handleSearchReset(); // 重置搜索状态
|
}));
|
||||||
|
|
||||||
|
// 动态设置骨架屏数量(可选)
|
||||||
|
this.skeletonCount = Math.min(
|
||||||
|
Math.max(this.originalDevices.length, 3), // 最少3个
|
||||||
|
10 // 最多10个
|
||||||
|
);
|
||||||
|
|
||||||
|
this.handleSearchReset();
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
}, (error) => {
|
||||||
|
console.error('Failed to fetch agent list:', error);
|
||||||
|
this.isLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 删除智能体
|
// 删除智能体
|
||||||
@@ -199,7 +237,7 @@ export default {
|
|||||||
|
|
||||||
.hi-hint {
|
.hi-hint {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: #818cae;
|
color: #818cae;
|
||||||
margin-left: 75px;
|
margin-left: 75px;
|
||||||
@@ -220,7 +258,7 @@ export default {
|
|||||||
border-radius: 17px;
|
border-radius: 17px;
|
||||||
background: #5778ff;
|
background: #5778ff;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 10px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
@@ -262,6 +300,65 @@ export default {
|
|||||||
/* 居中显示 */
|
/* 居中显示 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 骨架屏动画 */
|
||||||
|
@keyframes shimmer {
|
||||||
|
100% { transform: translateX(100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-item {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
height: 120px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-image {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-content {
|
||||||
|
margin-left: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-line {
|
||||||
|
height: 16px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
width: 70%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-line-short {
|
||||||
|
height: 12px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-item::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(255,255,255,0),
|
||||||
|
rgba(255,255,255,0.3),
|
||||||
|
rgba(255,255,255,0)
|
||||||
|
);
|
||||||
|
animation: shimmer 1.5s infinite;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -15,6 +15,9 @@
|
|||||||
<img loading="lazy" src="@/assets/home/setting-user.png" alt="">
|
<img loading="lazy" src="@/assets/home/setting-user.png" alt="">
|
||||||
</div>
|
</div>
|
||||||
<span class="header-title">{{ form.agentName }}</span>
|
<span class="header-title">{{ form.agentName }}</span>
|
||||||
|
<button class="custom-close-btn" @click="goToHome">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
@@ -165,6 +168,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
goToHome() {
|
||||||
|
this.$router.push('/home');
|
||||||
|
},
|
||||||
saveConfig() {
|
saveConfig() {
|
||||||
const configData = {
|
const configData = {
|
||||||
agentCode: this.form.agentCode,
|
agentCode: this.form.agentCode,
|
||||||
@@ -423,6 +429,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.config-header {
|
.config-header {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 13px;
|
gap: 13px;
|
||||||
@@ -560,8 +567,35 @@ export default {
|
|||||||
background: none;
|
background: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
bottom: -10px;
|
bottom: -10%;
|
||||||
right: 21px;
|
right: 3%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 25%;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #cfcfcf;
|
||||||
|
background: none;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: lighter;
|
||||||
|
color: #cfcfcf;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-close-btn:hover {
|
||||||
|
color: #409EFF;
|
||||||
|
border-color: #409EFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
Binary file not shown.
@@ -3,7 +3,7 @@ import sys
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
from config.config_loader import load_config
|
from config.config_loader import load_config
|
||||||
|
|
||||||
SERVER_VERSION = "0.3.6"
|
SERVER_VERSION = "0.3.9"
|
||||||
|
|
||||||
|
|
||||||
def get_module_abbreviation(module_name, module_dict):
|
def get_module_abbreviation(module_name, module_dict):
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ def check_config_file():
|
|||||||
missing_keys = find_missing_keys(new_config, old_config)
|
missing_keys = find_missing_keys(new_config, old_config)
|
||||||
read_config_from_api = old_config.get("read_config_from_api", False)
|
read_config_from_api = old_config.get("read_config_from_api", False)
|
||||||
if read_config_from_api:
|
if read_config_from_api:
|
||||||
|
old_config_origin = read_config(old_config_file)
|
||||||
|
if old_config_origin.get("selected_module") is not None:
|
||||||
|
missing_keys_str = "\n".join(f"- {key}" for key in missing_keys)
|
||||||
|
error_msg = "您的配置文件好像既包含智控台的配置又包含本地配置:\n"
|
||||||
|
error_msg += "\n建议您:\n"
|
||||||
|
error_msg += "1、将根目录的config_from_api.yaml文件复制到data下,重命名为.config.yaml\n"
|
||||||
|
error_msg += "2、按教程配置好接口地址和密钥\n"
|
||||||
|
raise ValueError(error_msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
if missing_keys:
|
if missing_keys:
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from core.auth import AuthMiddleware, AuthenticationError
|
|||||||
from core.mcp.manager import MCPManager
|
from core.mcp.manager import MCPManager
|
||||||
from config.config_loader import get_private_config_from_api
|
from config.config_loader import get_private_config_from_api
|
||||||
from config.manage_api_client import DeviceNotFoundException, DeviceBindException
|
from config.manage_api_client import DeviceNotFoundException, DeviceBindException
|
||||||
|
from core.utils.output_counter import add_device_output
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ class ConnectionHandler:
|
|||||||
self.session_id = None
|
self.session_id = None
|
||||||
self.prompt = None
|
self.prompt = None
|
||||||
self.welcome_msg = None
|
self.welcome_msg = None
|
||||||
|
self.max_output_size = 0
|
||||||
|
|
||||||
# 客户端状态相关
|
# 客户端状态相关
|
||||||
self.client_abort = False
|
self.client_abort = False
|
||||||
@@ -214,8 +216,11 @@ class ConnectionHandler:
|
|||||||
|
|
||||||
def _initialize_components(self, private_config):
|
def _initialize_components(self, private_config):
|
||||||
"""初始化组件"""
|
"""初始化组件"""
|
||||||
self._initialize_models(private_config)
|
if private_config is not None:
|
||||||
|
self._initialize_models(private_config)
|
||||||
|
else:
|
||||||
|
self.prompt = self.config["prompt"]
|
||||||
|
self.change_system_prompt(self.prompt)
|
||||||
"""加载记忆"""
|
"""加载记忆"""
|
||||||
self._initialize_memory()
|
self._initialize_memory()
|
||||||
"""加载意图识别"""
|
"""加载意图识别"""
|
||||||
@@ -238,8 +243,8 @@ class ConnectionHandler:
|
|||||||
begin_time = time.time()
|
begin_time = time.time()
|
||||||
private_config = get_private_config_from_api(
|
private_config = get_private_config_from_api(
|
||||||
self.config,
|
self.config,
|
||||||
self.headers.get("device-id", None),
|
self.headers.get("device-id"),
|
||||||
self.headers.get("client-id", None),
|
self.headers.get("client-id", self.headers.get("device-id")),
|
||||||
)
|
)
|
||||||
private_config["delete_audio"] = bool(self.config.get("delete_audio", True))
|
private_config["delete_audio"] = bool(self.config.get("delete_audio", True))
|
||||||
self.logger.bind(tag=TAG).info(
|
self.logger.bind(tag=TAG).info(
|
||||||
@@ -312,7 +317,6 @@ class ConnectionHandler:
|
|||||||
self.config["selected_module"]["LLM"] = private_config["selected_module"][
|
self.config["selected_module"]["LLM"] = private_config["selected_module"][
|
||||||
"LLM"
|
"LLM"
|
||||||
]
|
]
|
||||||
|
|
||||||
if private_config.get("Memory", None) is not None:
|
if private_config.get("Memory", None) is not None:
|
||||||
init_memory = True
|
init_memory = True
|
||||||
self.config["Memory"] = private_config["Memory"]
|
self.config["Memory"] = private_config["Memory"]
|
||||||
@@ -325,6 +329,8 @@ class ConnectionHandler:
|
|||||||
self.config["selected_module"]["Intent"] = private_config[
|
self.config["selected_module"]["Intent"] = private_config[
|
||||||
"selected_module"
|
"selected_module"
|
||||||
]["Intent"]
|
]["Intent"]
|
||||||
|
if private_config.get("device_max_output_size", None) is not None:
|
||||||
|
self.max_output_size = int(private_config["device_max_output_size"])
|
||||||
try:
|
try:
|
||||||
modules = initialize_modules(
|
modules = initialize_modules(
|
||||||
self.logger,
|
self.logger,
|
||||||
@@ -616,7 +622,7 @@ class ConnectionHandler:
|
|||||||
)
|
)
|
||||||
if not bHasError:
|
if not bHasError:
|
||||||
response_message.clear()
|
response_message.clear()
|
||||||
self.logger.bind(tag=TAG).info(
|
self.logger.bind(tag=TAG).debug(
|
||||||
f"function_name={function_name}, function_id={function_id}, function_arguments={function_arguments}"
|
f"function_name={function_name}, function_id={function_id}, function_arguments={function_arguments}"
|
||||||
)
|
)
|
||||||
function_call_data = {
|
function_call_data = {
|
||||||
@@ -842,6 +848,8 @@ class ConnectionHandler:
|
|||||||
self.logger.bind(tag=TAG).error(f"tts转换失败,{text}")
|
self.logger.bind(tag=TAG).error(f"tts转换失败,{text}")
|
||||||
return None, text, text_index
|
return None, text, text_index
|
||||||
self.logger.bind(tag=TAG).debug(f"TTS 文件生成完毕: {tts_file}")
|
self.logger.bind(tag=TAG).debug(f"TTS 文件生成完毕: {tts_file}")
|
||||||
|
if self.max_output_size > 0:
|
||||||
|
add_device_output(self.headers.get("device-id"), len(text))
|
||||||
return tts_file, text, text_index
|
return tts_file, text, text_index
|
||||||
|
|
||||||
def clearSpeakStatus(self):
|
def clearSpeakStatus(self):
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class FunctionHandler:
|
|||||||
func = funcItem.func
|
func = funcItem.func
|
||||||
arguments = function_call_data["arguments"]
|
arguments = function_call_data["arguments"]
|
||||||
arguments = json.loads(arguments) if arguments else {}
|
arguments = json.loads(arguments) if arguments else {}
|
||||||
logger.bind(tag=TAG).info(f"调用函数: {function_name}, 参数: {arguments}")
|
logger.bind(tag=TAG).debug(f"调用函数: {function_name}, 参数: {arguments}")
|
||||||
if (
|
if (
|
||||||
funcItem.type == ToolType.SYSTEM_CTL
|
funcItem.type == ToolType.SYSTEM_CTL
|
||||||
or funcItem.type == ToolType.IOT_CTL
|
or funcItem.type == ToolType.IOT_CTL
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ def create_iot_function(device_name, method_name, method_info):
|
|||||||
response_failure = "操作失败"
|
response_failure = "操作失败"
|
||||||
|
|
||||||
# 打印响应参数
|
# 打印响应参数
|
||||||
logger.bind(tag=TAG).info(
|
logger.bind(tag=TAG).debug(
|
||||||
f"控制函数接收到的响应参数: success='{response_success}', failure='{response_failure}'"
|
f"控制函数接收到的响应参数: success='{response_success}', failure='{response_failure}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ class IotDescriptor:
|
|||||||
# 根据描述创建属性
|
# 根据描述创建属性
|
||||||
if properties is not None:
|
if properties is not None:
|
||||||
for key, value in properties.items():
|
for key, value in properties.items():
|
||||||
property_item = globals()[key] = {}
|
property_item = {}
|
||||||
property_item["name"] = key
|
property_item["name"] = key
|
||||||
property_item["description"] = value["description"]
|
property_item["description"] = value["description"]
|
||||||
if value["type"] == "number":
|
if value["type"] == "number":
|
||||||
@@ -160,18 +160,17 @@ class IotDescriptor:
|
|||||||
# 根据描述创建方法
|
# 根据描述创建方法
|
||||||
if methods is not None:
|
if methods is not None:
|
||||||
for key, value in methods.items():
|
for key, value in methods.items():
|
||||||
method = globals()[key] = {}
|
method = {}
|
||||||
method["description"] = value["description"]
|
method["description"] = value["description"]
|
||||||
method["name"] = key
|
method["name"] = key
|
||||||
for k, v in value["parameters"].items():
|
# 检查方法是否有参数
|
||||||
method[k] = {}
|
if "parameters" in value:
|
||||||
method[k]["description"] = v["description"]
|
method["parameters"] = {}
|
||||||
if v["type"] == "number":
|
for k, v in value["parameters"].items():
|
||||||
method[k]["value"] = 0
|
method["parameters"][k] = {
|
||||||
elif v["type"] == "boolean":
|
"description": v["description"],
|
||||||
method[k]["value"] = False
|
"type": v["type"],
|
||||||
else:
|
}
|
||||||
method[k]["value"] = ""
|
|
||||||
self.methods.append(method)
|
self.methods.append(method)
|
||||||
|
|
||||||
|
|
||||||
@@ -221,13 +220,19 @@ def register_device_type(descriptor):
|
|||||||
func_name = f"{device_name.lower()}_{method_name.lower()}"
|
func_name = f"{device_name.lower()}_{method_name.lower()}"
|
||||||
|
|
||||||
# 创建参数字典,添加原有参数
|
# 创建参数字典,添加原有参数
|
||||||
parameters = {
|
parameters = {}
|
||||||
param_name: {
|
required_params = []
|
||||||
"type": param_info["type"],
|
|
||||||
"description": param_info["description"],
|
# 如果方法有参数,则添加参数信息
|
||||||
|
if "parameters" in method_info:
|
||||||
|
parameters = {
|
||||||
|
param_name: {
|
||||||
|
"type": param_info["type"],
|
||||||
|
"description": param_info["description"],
|
||||||
|
}
|
||||||
|
for param_name, param_info in method_info["parameters"].items()
|
||||||
}
|
}
|
||||||
for param_name, param_info in method_info["parameters"].items()
|
required_params = list(method_info["parameters"].keys())
|
||||||
}
|
|
||||||
|
|
||||||
# 添加响应参数
|
# 添加响应参数
|
||||||
parameters.update(
|
parameters.update(
|
||||||
@@ -244,7 +249,6 @@ def register_device_type(descriptor):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 构建必须参数列表(原有参数 + 响应参数)
|
# 构建必须参数列表(原有参数 + 响应参数)
|
||||||
required_params = list(method_info["parameters"].keys())
|
|
||||||
required_params.extend(["response_success", "response_failure"])
|
required_params.extend(["response_success", "response_failure"])
|
||||||
|
|
||||||
func_desc = {
|
func_desc = {
|
||||||
@@ -282,6 +286,25 @@ async def handleIotDescriptors(conn, descriptors):
|
|||||||
functions_changed = False
|
functions_changed = False
|
||||||
|
|
||||||
for descriptor in descriptors:
|
for descriptor in descriptors:
|
||||||
|
|
||||||
|
# 如果descriptor没有properties和methods,则直接跳过
|
||||||
|
if "properties" not in descriptor and "methods" not in descriptor:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 处理缺失properties的情况
|
||||||
|
if "properties" not in descriptor:
|
||||||
|
descriptor["properties"] = {}
|
||||||
|
# 从methods中提取所有参数作为properties
|
||||||
|
if "methods" in descriptor:
|
||||||
|
for method_name, method_info in descriptor["methods"].items():
|
||||||
|
if "parameters" in method_info:
|
||||||
|
for param_name, param_info in method_info["parameters"].items():
|
||||||
|
# 将参数信息转换为属性信息
|
||||||
|
descriptor["properties"][param_name] = {
|
||||||
|
"description": param_info["description"],
|
||||||
|
"type": param_info["type"],
|
||||||
|
}
|
||||||
|
|
||||||
# 创建IOT设备描述符
|
# 创建IOT设备描述符
|
||||||
iot_descriptor = IotDescriptor(
|
iot_descriptor = IotDescriptor(
|
||||||
descriptor["name"],
|
descriptor["name"],
|
||||||
@@ -375,19 +398,17 @@ async def send_iot_conn(conn, name, method_name, parameters):
|
|||||||
for method in value.methods:
|
for method in value.methods:
|
||||||
# 找到了方法
|
# 找到了方法
|
||||||
if method["name"] == method_name:
|
if method["name"] == method_name:
|
||||||
await conn.websocket.send(
|
# 构建命令对象
|
||||||
json.dumps(
|
command = {
|
||||||
{
|
"name": name,
|
||||||
"type": "iot",
|
"method": method_name,
|
||||||
"commands": [
|
}
|
||||||
{
|
|
||||||
"name": name,
|
# 只有当参数不为空时才添加parameters字段
|
||||||
"method": method_name,
|
if parameters:
|
||||||
"parameters": parameters,
|
command["parameters"] = parameters
|
||||||
}
|
send_message = json.dumps({"type": "iot", "commands": [command]})
|
||||||
],
|
await conn.websocket.send(send_message)
|
||||||
}
|
logger.bind(tag=TAG).info(f"发送物联网指令: {send_message}")
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
logger.bind(tag=TAG).error(f"未找到方法{method_name}")
|
logger.bind(tag=TAG).error(f"未找到方法{method_name}")
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
import time
|
import time
|
||||||
import asyncio
|
|
||||||
from core.utils.util import remove_punctuation_and_length
|
from core.utils.util import remove_punctuation_and_length
|
||||||
from core.handle.sendAudioHandle import send_stt_message
|
from core.handle.sendAudioHandle import send_stt_message
|
||||||
from core.handle.intentHandler import handle_user_intent
|
from core.handle.intentHandler import handle_user_intent
|
||||||
|
from core.utils.output_counter import check_device_output_limit
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
@@ -51,6 +51,15 @@ async def startToChat(conn, text):
|
|||||||
if conn.need_bind:
|
if conn.need_bind:
|
||||||
await check_bind_device(conn)
|
await check_bind_device(conn)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 如果当日的输出字数大于限定的字数
|
||||||
|
if conn.max_output_size > 0:
|
||||||
|
if check_device_output_limit(
|
||||||
|
conn.headers.get("device-id"), conn.max_output_size
|
||||||
|
):
|
||||||
|
await max_out_size(conn)
|
||||||
|
return
|
||||||
|
|
||||||
# 首先进行意图分析
|
# 首先进行意图分析
|
||||||
intent_handled = await handle_user_intent(conn, text)
|
intent_handled = await handle_user_intent(conn, text)
|
||||||
|
|
||||||
@@ -89,6 +98,18 @@ async def no_voice_close_connect(conn):
|
|||||||
await startToChat(conn, prompt)
|
await startToChat(conn, prompt)
|
||||||
|
|
||||||
|
|
||||||
|
async def max_out_size(conn):
|
||||||
|
text = "不好意思,我现在有点事情要忙,明天这个时候我们再聊,约好了哦!明天不见不散,拜拜!"
|
||||||
|
await send_stt_message(conn, text)
|
||||||
|
conn.tts_first_text_index = 0
|
||||||
|
conn.tts_last_text_index = 0
|
||||||
|
conn.llm_finish_task = True
|
||||||
|
file_path = "config/assets/max_output_size.wav"
|
||||||
|
opus_packets, _ = conn.tts.audio_to_opus_data(file_path)
|
||||||
|
conn.audio_play_queue.put((opus_packets, text, 0))
|
||||||
|
conn.close_after_chat = True
|
||||||
|
|
||||||
|
|
||||||
async def check_bind_device(conn):
|
async def check_bind_device(conn):
|
||||||
if conn.bind_code:
|
if conn.bind_code:
|
||||||
# 确保bind_code是6位数字
|
# 确保bind_code是6位数字
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import openai
|
import openai
|
||||||
|
from openai.types import CompletionUsage
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
from core.utils.util import check_model_key
|
from core.utils.util import check_model_key
|
||||||
from core.providers.llm.base import LLMProviderBase
|
from core.providers.llm.base import LLMProviderBase
|
||||||
@@ -70,7 +71,17 @@ class LLMProvider(LLMProviderBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for chunk in stream:
|
for chunk in stream:
|
||||||
yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls
|
# 检查是否存在有效的choice且content不为空
|
||||||
|
if getattr(chunk, "choices", None):
|
||||||
|
yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls
|
||||||
|
# 存在 CompletionUsage 消息时,生成 Token 消耗 log
|
||||||
|
elif isinstance(getattr(chunk, 'usage', None), CompletionUsage):
|
||||||
|
usage_info = getattr(chunk, 'usage', None)
|
||||||
|
logger.bind(tag=TAG).info(
|
||||||
|
f"Token 消耗:输入 {getattr(usage_info, 'prompt_tokens', '未知')},"
|
||||||
|
f"输出 {getattr(usage_info, 'completion_tokens', '未知')},"
|
||||||
|
f"共计 {getattr(usage_info, 'total_tokens', '未知')}"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(f"Error in function call streaming: {e}")
|
logger.bind(tag=TAG).error(f"Error in function call streaming: {e}")
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ logger = setup_logging()
|
|||||||
class TTSProvider(TTSProviderBase):
|
class TTSProvider(TTSProviderBase):
|
||||||
def __init__(self, config, delete_audio_file):
|
def __init__(self, config, delete_audio_file):
|
||||||
super().__init__(config, delete_audio_file)
|
super().__init__(config, delete_audio_file)
|
||||||
self.appid = config.get("appid")
|
if config.get("appid"):
|
||||||
|
self.appid = int(config.get("appid"))
|
||||||
|
else:
|
||||||
|
self.appid = ""
|
||||||
self.access_token = config.get("access_token")
|
self.access_token = config.get("access_token")
|
||||||
self.cluster = config.get("cluster")
|
self.cluster = config.get("cluster")
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import requests
|
|||||||
import shutil
|
import shutil
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from core.providers.tts.base import TTSProviderBase
|
from core.providers.tts.base import TTSProviderBase
|
||||||
|
from config.logger import setup_logging
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
class TTSProvider(TTSProviderBase):
|
class TTSProvider(TTSProviderBase):
|
||||||
@@ -54,6 +58,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
|
|
||||||
resp = requests.request("POST", url, data=payload)
|
resp = requests.request("POST", url, data=payload)
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
|
logger.bind(tag=TAG).error(f"TTS请求失败: {resp.text}")
|
||||||
return None
|
return None
|
||||||
resp_json = resp.json()
|
resp_json = resp.json()
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import datetime
|
||||||
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
|
# 全局字典,用于存储每个设备的每日输出字数
|
||||||
|
_device_daily_output: Dict[Tuple[str, datetime.date], int] = {}
|
||||||
|
# 记录最后一次检查的日期
|
||||||
|
_last_check_date: datetime.date = None
|
||||||
|
|
||||||
|
|
||||||
|
def reset_device_output():
|
||||||
|
"""
|
||||||
|
重置所有设备的每日输出字数
|
||||||
|
每天0点调用此函数
|
||||||
|
"""
|
||||||
|
_device_daily_output.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def get_device_output(device_id: str) -> int:
|
||||||
|
"""
|
||||||
|
获取设备当日的输出字数
|
||||||
|
"""
|
||||||
|
current_date = datetime.datetime.now().date()
|
||||||
|
return _device_daily_output.get((device_id, current_date), 0)
|
||||||
|
|
||||||
|
|
||||||
|
def add_device_output(device_id: str, char_count: int):
|
||||||
|
"""
|
||||||
|
增加设备的输出字数
|
||||||
|
"""
|
||||||
|
current_date = datetime.datetime.now().date()
|
||||||
|
global _last_check_date
|
||||||
|
|
||||||
|
# 如果是第一次调用或者日期发生变化,清空计数器
|
||||||
|
if _last_check_date is None or _last_check_date != current_date:
|
||||||
|
_device_daily_output.clear()
|
||||||
|
_last_check_date = current_date
|
||||||
|
|
||||||
|
current_count = _device_daily_output.get((device_id, current_date), 0)
|
||||||
|
_device_daily_output[(device_id, current_date)] = current_count + char_count
|
||||||
|
|
||||||
|
|
||||||
|
def check_device_output_limit(device_id: str, max_output_size: int) -> bool:
|
||||||
|
"""
|
||||||
|
检查设备是否超过输出限制
|
||||||
|
:return: True 如果超过限制,False 如果未超过
|
||||||
|
"""
|
||||||
|
if not device_id:
|
||||||
|
return False
|
||||||
|
current_output = get_device_output(device_id)
|
||||||
|
return current_output >= max_output_size
|
||||||
@@ -59,7 +59,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- default
|
- default
|
||||||
expose:
|
expose:
|
||||||
- "3306:3306"
|
- "3306"
|
||||||
volumes:
|
volumes:
|
||||||
- ./mysql/data:/var/lib/mysql
|
- ./mysql/data:/var/lib/mysql
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -25,3 +25,4 @@ sherpa_onnx==1.11.0
|
|||||||
mcp==1.4.1
|
mcp==1.4.1
|
||||||
cnlunar==0.2.0
|
cnlunar==0.2.0
|
||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
|
dashscope==1.23.1
|
||||||
Reference in New Issue
Block a user