From ee0da9fc3a34bcd2b591fe43afcfddf36cba49a9 Mon Sep 17 00:00:00 2001 From: Erlei Chen Date: Wed, 26 Mar 2025 17:00:54 +0800 Subject: [PATCH] =?UTF-8?q?feature=EF=BC=9AOTA=E8=83=BD=E5=8A=9B=E5=AE=8C?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/controller/UserAgentController.java | 54 +++-- .../controller/UserDeviceController.java | 49 ++++- .../modules/model/domain/ModelProvider.java | 71 +++++++ .../model/mapper/ModelProviderMapper.java | 20 ++ .../model/service/ModelProviderService.java | 13 ++ .../impl/ModelProviderServiceImpl.java | 22 ++ .../controller/OtaController.java | 85 +++++++- .../java/xiaozhi/modules/ota/domain/Ota.java | 66 ++++++ .../xiaozhi/modules/ota/mapper/OtaMapper.java | 20 ++ .../modules/ota/service/OtaService.java | 13 ++ .../ota/service/impl/OtaServiceImpl.java | 22 ++ .../src/main/resources/application-dev.yml | 37 ++-- .../resources/db/changelog/202503241020.sql | 15 ++ .../db/changelog/db.changelog-master.yaml | 9 +- .../mapper/model/ModelProviderMapper.xml | 24 +++ .../main/resources/mapper/ota/OtaMapper.xml | 23 +++ main/manager-web/src/apis/api.js | 6 +- main/manager-web/src/apis/module/agent.js | 34 ++-- main/manager-web/src/apis/module/device.js | 49 ++++- main/manager-web/src/apis/module/ota.js | 67 ++++++ main/manager-web/src/apis/module/user.js | 143 +++---------- .../src/components/AddAgentDialog.vue | 4 +- .../src/components/AddDeviceDialog.vue | 7 +- .../src/components/AddDeviceOtaDialog.vue | 104 ++++++++++ main/manager-web/src/components/HeaderBar.vue | 41 +++- main/manager-web/src/router/index.js | 19 ++ main/manager-web/src/utils/index.js | 4 +- main/manager-web/src/views/device.vue | 42 +++- main/manager-web/src/views/home.vue | 6 +- main/manager-web/src/views/login.vue | 10 +- main/manager-web/src/views/ota.vue | 190 ++++++++++++++++++ 31 files changed, 1044 insertions(+), 225 deletions(-) create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelProvider.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelProviderMapper.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java rename main/manager-api/src/main/java/xiaozhi/modules/{device => ota}/controller/OtaController.java (51%) create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/ota/domain/Ota.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/ota/mapper/OtaMapper.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/ota/service/OtaService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/ota/service/impl/OtaServiceImpl.java create mode 100644 main/manager-api/src/main/resources/db/changelog/202503241020.sql create mode 100644 main/manager-api/src/main/resources/mapper/model/ModelProviderMapper.xml create mode 100644 main/manager-api/src/main/resources/mapper/ota/OtaMapper.xml create mode 100755 main/manager-web/src/apis/module/ota.js create mode 100644 main/manager-web/src/components/AddDeviceOtaDialog.vue create mode 100644 main/manager-web/src/views/ota.vue diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java index 0e13fac2..3e5a9c57 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java @@ -9,6 +9,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.web.bind.annotation.*; import xiaozhi.common.controller.BaseController; import xiaozhi.common.redis.RedisUtils; @@ -48,21 +49,22 @@ public class UserAgentController extends BaseController { @PostMapping @Operation(summary = "添加智能体") + @RequiresPermissions("sys:role:normal") public Result addAgent(@RequestBody Agent agent) { UserDetail user = SecurityUser.getUser(); - if(StringUtils.isBlank(agent.getAgentName())){ + if (StringUtils.isBlank(agent.getAgentName())) { log.error("智能体名称不能为空"); } Agent oldAgent = agentService.getOne(new QueryWrapper().eq("agent_name", agent.getAgentName())); - if(ObjectUtils.isNull(oldAgent)){ + if (ObjectUtils.isNull(oldAgent)) { AgentTemplate agentTemplate = agentTemplateService.getOne(new QueryWrapper().eq("is_default", 1)); - if(ObjectUtils.isNull(agentTemplate)){ + if (ObjectUtils.isNull(agentTemplate)) { - }else{ + } else { try { oldAgent = new Agent(); BeanUtils.copyProperties(oldAgent, agentTemplate); - oldAgent.setId(UUID.randomUUID().toString().replace("-","")); + oldAgent.setId(UUID.randomUUID().toString().replace("-", "")); oldAgent.setAgentName(agent.getAgentName()); oldAgent.setUserId(user.getId()); oldAgent.setCreator(user.getId()); @@ -72,17 +74,33 @@ public class UserAgentController extends BaseController { log.error("对象赋值异常", e); } } - }else{ + } else { } return new Result().ok(agent); } + @DeleteMapping("/{agentId}") + @Operation(summary = "删除智能体") + @RequiresPermissions("sys:role:normal") + public Result delAgent(@PathVariable String agentId) { + UserDetail user = SecurityUser.getUser(); + if (StringUtils.isBlank(agentId)) { + log.error("智能体ID不能为空"); + } + boolean bool = agentService.removeById(agentId); + if (!bool) { + return new Result().error("删除失败"); + } + return new Result().ok(null); + } + @GetMapping @Operation(summary = "智能体列表") + @RequiresPermissions("sys:role:normal") public Result> agentList() { UserDetail user = SecurityUser.getUser(); - List agents = agentService.list(new QueryWrapper().eq("user_id",user.getId())); + List agents = agentService.list(new QueryWrapper().eq("user_id", user.getId())); List list = new ArrayList<>(); this.convertAgetVOList(list, agents); return new Result>().ok(list); @@ -90,19 +108,18 @@ public class UserAgentController extends BaseController { @GetMapping("/loadAgentConfig/{deviceId}") @Operation(summary = "下载智能体配置") -// public Result loadAgentConfig(@PathVariable String deviceId){ - public Result loadAgentConfig(@PathVariable String deviceId){ + public Result loadAgentConfig(@PathVariable String deviceId) { Device device = deviceService.getOne(new QueryWrapper().eq("mac_address", deviceId.toUpperCase())); - if(ObjectUtils.isNull(device)){ + if (ObjectUtils.isNull(device)) { return new Result().error("设备不存在"); } Agent agent = agentService.getOne(new QueryWrapper().eq("id", device.getAgentId())); - if(ObjectUtils.isNull(agent)){ + if (ObjectUtils.isNull(agent)) { return new Result().error("智能体不存在"); } AgentConfigVO agentConfigVO = new AgentConfigVO(); // return new Result().ok(agentConfigVO); - String json = "{\n" + + String json = "{\n" + " \"ASR\": {\n" + " \"FunASR\": {\n" + " \"model_dir\": \"models/SenseVoiceSmall\",\n" + @@ -160,9 +177,9 @@ public class UserAgentController extends BaseController { * @param agentVOList 转换后的AgentVO对象列表 * @param agentList 原始的Agent对象列表 */ - private void convertAgetVOList(List agentVOList, List agentList){ + private void convertAgetVOList(List agentVOList, List agentList) { // 遍历Agent对象列表 - for(Agent agent : agentList) { + for (Agent agent : agentList) { // 创建一个新的AgentVO对象 AgentVO agentVO = new AgentVO(); // 将当前Agent对象的属性值转换并设置到AgentVO对象中 @@ -171,17 +188,18 @@ public class UserAgentController extends BaseController { agentVOList.add(agentVO); } } - private void convertAgentVO(AgentVO agentVO, Agent agent){ + + private void convertAgentVO(AgentVO agentVO, Agent agent) { try { BeanUtils.copyProperties(agentVO, agent); agentVO.setTtsModelName("未知"); TtsVoice ttsVoice = ttsVoiceService.getOne(new QueryWrapper().eq("id", agent.getTtsVoiceId())); - if(ObjectUtils.isNotNull(ttsVoice)){ + if (ObjectUtils.isNotNull(ttsVoice)) { agentVO.setTtsModelName(ttsVoice.getName()); } agentVO.setLlmModelName("未知"); ModelConfig modelConfig = modelConfigService.getOne(new QueryWrapper().eq("id", agent.getLlmModelId())); - if(ObjectUtils.isNotNull(modelConfig)){ + if (ObjectUtils.isNotNull(modelConfig)) { agentVO.setLlmModelName(modelConfig.getModelName()); } agentVO.setLastConnectedAt("今天"); @@ -189,7 +207,7 @@ public class UserAgentController extends BaseController { long deviceCount = deviceService.count(new QueryWrapper().eq("agent_id", agent.getId())); agentVO.setDeviceCount(deviceCount); } catch (Exception e) { - log.error("[convertAgentVO]对象转换报错",e); + log.error("[convertAgentVO]对象转换报错", e); } } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java index b5900faa..b0ce6efc 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java @@ -2,6 +2,7 @@ package xiaozhi.modules.device.controller; import cn.hutool.json.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.v3.oas.annotations.Operation; @@ -9,6 +10,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.web.bind.annotation.*; import xiaozhi.common.controller.BaseController; import xiaozhi.common.redis.RedisUtils; @@ -20,6 +22,7 @@ import xiaozhi.modules.device.constant.DeviceConstant; import xiaozhi.modules.device.domain.Device; import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.device.vo.DeviceCodeVO; +import xiaozhi.modules.ota.domain.Ota; import xiaozhi.modules.security.user.SecurityUser; import java.util.Date; @@ -36,6 +39,7 @@ public class UserDeviceController extends BaseController { @GetMapping("/bind/{agentId}") @Operation(summary = "已绑定设备列表") + @RequiresPermissions("sys:role:normal") public Result> bindDeviceList(@PathVariable String agentId, @RequestParam Integer pageNo, @RequestParam Integer pageSize) { if (StringUtils.isBlank(agentId)) { log.error("智能体ID不能为空"); @@ -56,6 +60,7 @@ public class UserDeviceController extends BaseController { @PostMapping("/bind/{agentId}") @Operation(summary = "绑定设备") + @RequiresPermissions("sys:role:normal") public Result bindDevice(@PathVariable String agentId, @RequestBody DeviceCodeVO deviceCodeVO) { if (ObjectUtils.isNull(deviceCodeVO) || StringUtils.isBlank(deviceCodeVO.getDeviceCode())) { log.error("授权码不能为空"); @@ -112,16 +117,58 @@ public class UserDeviceController extends BaseController { @PutMapping("/unbind/{deviceId}") @Operation(summary = "解绑设备") + @RequiresPermissions("sys:role:normal") public Result bindDevice(@PathVariable String deviceId) { if (StringUtils.isBlank(deviceId)) { log.error("设备ID不能为空"); return new Result().error("设备ID不能为空"); } - UserDetail user = SecurityUser.getUser(); boolean bool = deviceService.removeById(deviceId); if (!bool) { return new Result().error("解绑失败"); } return new Result().ok(null); } + + @PostMapping("/autoUpdate/{deviceId}") + @Operation(summary = "切换设备OTA升级状态") + @RequiresPermissions("sys:role:normal") + public Result toggleAutoUpdate(@PathVariable String deviceId, @RequestBody Device device) { + if (StringUtils.isBlank(deviceId)) { + log.error("设备ID不能为空"); + return new Result().error("设备ID不能为空"); + } + UserDetail user = SecurityUser.getUser(); + boolean bool = deviceService.update( + new UpdateWrapper().eq("id", deviceId) + .set("auto_update", device.getAutoUpdate()) + .set("updater", user.getId()) + .set("update_date", new Date()) + ); + if (!bool) { + return new Result().error("切换设备OTA升级状态失败"); + } + return new Result().ok(null); + } + + @PostMapping("/alias/{deviceId}") + @Operation(summary = "设置设备别名") + @RequiresPermissions("sys:role:normal") + public Result setAlias(@PathVariable String deviceId, @RequestBody Device device) { + if (StringUtils.isBlank(deviceId)) { + log.error("设备ID不能为空"); + return new Result().error("设备ID不能为空"); + } + UserDetail user = SecurityUser.getUser(); + boolean bool = deviceService.update( + new UpdateWrapper().eq("id", deviceId) + .set("alias", device.getAlias()) + .set("updater", user.getId()) + .set("update_date", new Date()) + ); + if (!bool) { + return new Result().error("设置设备别名失败"); + } + return new Result().ok(null); + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelProvider.java b/main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelProvider.java new file mode 100644 index 00000000..af21ffac --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelProvider.java @@ -0,0 +1,71 @@ +package xiaozhi.modules.model.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 模型配置表 + * @TableName ai_model_provider + */ +@TableName(value ="ai_model_provider") +@Data +public class ModelProvider implements Serializable { + /** + * 主键 + */ + @TableId + private String id; + + /** + * 模型类型(Memory/ASR/VAD/LLM/TTS) + */ + private String modelType; + + /** + * 供应器类型 + */ + private String providerCode; + + /** + * 供应器名称 + */ + private String name; + + /** + * 供应器字段列表(JSON格式) + */ + private Object fields; + + /** + * 排序 + */ + private Integer sort; + + /** + * 创建者 + */ + private Long creator; + + /** + * 创建时间 + */ + private Date createDate; + + /** + * 更新者 + */ + private Long updater; + + /** + * 更新时间 + */ + private Date updateDate; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelProviderMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelProviderMapper.java new file mode 100644 index 00000000..f3fa01ce --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelProviderMapper.java @@ -0,0 +1,20 @@ +package xiaozhi.modules.model.mapper; + +import org.apache.ibatis.annotations.Mapper; +import xiaozhi.modules.model.domain.ModelProvider; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author chenerlei +* @description 针对表【ai_model_provider(模型配置表)】的数据库操作Mapper +* @createDate 2025-03-24 18:24:13 +* @Entity xiaozhi.modules.model.domain.ModelProvider +*/ +@Mapper +public interface ModelProviderMapper extends BaseMapper { + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java new file mode 100644 index 00000000..f5bf272f --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java @@ -0,0 +1,13 @@ +package xiaozhi.modules.model.service; + +import xiaozhi.modules.model.domain.ModelProvider; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author chenerlei +* @description 针对表【ai_model_provider(模型配置表)】的数据库操作Service +* @createDate 2025-03-24 18:24:13 +*/ +public interface ModelProviderService extends IService { + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java new file mode 100644 index 00000000..d327e7b4 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.model.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import xiaozhi.modules.model.domain.ModelProvider; +import xiaozhi.modules.model.service.ModelProviderService; +import xiaozhi.modules.model.mapper.ModelProviderMapper; +import org.springframework.stereotype.Service; + +/** +* @author chenerlei +* @description 针对表【ai_model_provider(模型配置表)】的数据库操作Service实现 +* @createDate 2025-03-24 18:24:13 +*/ +@Service +public class ModelProviderServiceImpl extends ServiceImpl + implements ModelProviderService{ + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OtaController.java b/main/manager-api/src/main/java/xiaozhi/modules/ota/controller/OtaController.java similarity index 51% rename from main/manager-api/src/main/java/xiaozhi/modules/device/controller/OtaController.java rename to main/manager-api/src/main/java/xiaozhi/modules/ota/controller/OtaController.java index c222a264..7bee0c61 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OtaController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/ota/controller/OtaController.java @@ -1,26 +1,33 @@ -package xiaozhi.modules.device.controller; +package xiaozhi.modules.ota.controller; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.data.redis.core.RedisTemplate; +import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import xiaozhi.common.controller.BaseController; import xiaozhi.common.redis.RedisUtils; +import xiaozhi.common.user.UserDetail; +import xiaozhi.common.utils.Result; +import xiaozhi.modules.agent.domain.Agent; import xiaozhi.modules.device.constant.DeviceConstant; import xiaozhi.modules.device.domain.Device; import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.device.utils.CodeGeneratorUtil; import xiaozhi.modules.device.vo.DeviceOtaVO; -import xiaozhi.modules.sys.service.SysParamsService; +import xiaozhi.modules.ota.domain.Ota; +import xiaozhi.modules.ota.service.OtaService; +import xiaozhi.modules.security.user.SecurityUser; import java.util.Date; @@ -31,10 +38,9 @@ import java.util.Date; @RequestMapping("/ota") public class OtaController extends BaseController { private final DeviceService deviceService; - private final SysParamsService sysParamsService; + private final OtaService otaService; @Resource private RedisUtils redisUtils; - private final String OTA_PARAM_CODE = "OTA"; @CrossOrigin(originPatterns = "*", methods = {RequestMethod.GET, RequestMethod.POST}) @RequestMapping(path = "/", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") @@ -66,16 +72,77 @@ public class OtaController extends BaseController { if (ObjectUtils.isNull(device) || device.getAutoUpdate() == 1) { //{"version":"1.0.0","url":"http://https://youxlife.oss-cn-zhangjiakou.aliyuncs.com/v1.4.5.bin"} - String value = sysParamsService.getValue(OTA_PARAM_CODE); - if (StringUtils.isBlank(value)) { + String board = ObjectUtils.isNull(device)?headers.getFirst("user-agent").split("/")[0]:device.getBoard(); + Ota ota = otaService.getOne(new UpdateWrapper().eq("board", board).eq("is_enabled", 1)); + if (ObjectUtils.isNull(ota)) { log.warn("OTA升级信息未配置"); } else { - JSONObject object = new JSONObject(value); - otaVO.setFirmware(new DeviceOtaVO.Firmware(object.getStr("version"), object.getStr("url"))); + otaVO.setFirmware(new DeviceOtaVO.Firmware(ota.getAppVersion(), ota.getUrl())); } } otaVO.setServer_time(new DeviceOtaVO.ServerTime(new Date().getTime(), 8 * 60)); return JSONUtil.toJsonStr(otaVO); } + + @GetMapping("/sys/getOtalist") + @Operation(summary = "设备OTA列表") + @RequiresPermissions("sys:role:superAdmin") + public Result> getOtalist(@RequestParam Integer pageNo, @RequestParam Integer pageSize) { + UserDetail user = SecurityUser.getUser(); + Page page = new Page(pageNo, pageSize); + page = otaService.page(page); + return new Result>().ok(page); + } + + @PostMapping("/sys/save") + @Operation(summary = "添加设备OTA") + @RequiresPermissions("sys:role:superAdmin") + public Result save(@RequestBody Ota ota) { + UserDetail user = SecurityUser.getUser(); + ota.setCreator(user.getId()); + ota.setCreateDate(new Date()); + boolean bool = otaService.save(ota); + if (!bool) { + return new Result().error("设备OTA添加失败"); + } + return new Result().ok(null); + } + + @PostMapping("/sys/update") + @Operation(summary = "更新设备OTA") + @RequiresPermissions("sys:role:superAdmin") + public Result update(@RequestBody Ota ota) { + UserDetail user = SecurityUser.getUser(); + boolean bool = otaService.update( + new UpdateWrapper().eq("id", ota.getId()) + .set("board", ota.getBoard().trim().toLowerCase()) + .set("app_version", ota.getAppVersion()) + .set("url", ota.getUrl()) + .set("is_enabled", ota.getIsEnabled()) + .set("updater", user.getId()) + .set("update_date", new Date()) + ); + if (!bool) { + return new Result().error("设备OTA更新失败"); + } + return new Result().ok(null); + } + + @PostMapping("/sys/toggleEnabled") + @Operation(summary = "切换设备OTA可用状态") + @RequiresPermissions("sys:role:superAdmin") + public Result toggleEnabled(@RequestBody Ota ota) { + UserDetail user = SecurityUser.getUser(); + boolean bool = otaService.update( + new UpdateWrapper().eq("id", ota.getId()) + .set("is_enabled", ota.getIsEnabled()) + .set("updater", user.getId()) + .set("update_date", new Date()) + ); + if (!bool) { + return new Result().error("切换设备OTA可用状态失败"); + } + return new Result().ok(null); + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/ota/domain/Ota.java b/main/manager-api/src/main/java/xiaozhi/modules/ota/domain/Ota.java new file mode 100644 index 00000000..e07fc4e0 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/ota/domain/Ota.java @@ -0,0 +1,66 @@ +package xiaozhi.modules.ota.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * OTA升级信息表 + * @TableName ai_ota + */ +@TableName(value ="ai_ota") +@Data +public class Ota implements Serializable { + /** + * 记录唯一标识 + */ + @TableId + private String id; + + /** + * 设备硬件型号 + */ + private String board; + + /** + * 固件版本号 + */ + private String appVersion; + + /** + * 下载地址 + */ + private String url; + + /** + * 是否启用 + */ + private Integer isEnabled; + + /** + * 创建者 + */ + private Long creator; + + /** + * 创建时间 + */ + private Date createDate; + + /** + * 更新者 + */ + private Long updater; + + /** + * 更新时间 + */ + private Date updateDate; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/ota/mapper/OtaMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/ota/mapper/OtaMapper.java new file mode 100644 index 00000000..e083316f --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/ota/mapper/OtaMapper.java @@ -0,0 +1,20 @@ +package xiaozhi.modules.ota.mapper; + +import org.apache.ibatis.annotations.Mapper; +import xiaozhi.modules.ota.domain.Ota; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author chenerlei +* @description 针对表【ai_ota(OTA升级信息表)】的数据库操作Mapper +* @createDate 2025-03-24 18:25:14 +* @Entity xiaozhi.modules.ota.domain.Ota +*/ +@Mapper +public interface OtaMapper extends BaseMapper { + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/ota/service/OtaService.java b/main/manager-api/src/main/java/xiaozhi/modules/ota/service/OtaService.java new file mode 100644 index 00000000..cad7e830 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/ota/service/OtaService.java @@ -0,0 +1,13 @@ +package xiaozhi.modules.ota.service; + +import xiaozhi.modules.ota.domain.Ota; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author chenerlei +* @description 针对表【ai_ota(OTA升级信息表)】的数据库操作Service +* @createDate 2025-03-24 18:25:14 +*/ +public interface OtaService extends IService { + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/ota/service/impl/OtaServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/ota/service/impl/OtaServiceImpl.java new file mode 100644 index 00000000..1956a95d --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/ota/service/impl/OtaServiceImpl.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.ota.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import xiaozhi.modules.ota.domain.Ota; +import xiaozhi.modules.ota.service.OtaService; +import xiaozhi.modules.ota.mapper.OtaMapper; +import org.springframework.stereotype.Service; + +/** +* @author chenerlei +* @description 针对表【ai_ota(OTA升级信息表)】的数据库操作Service实现 +* @createDate 2025-03-24 18:25:14 +*/ +@Service +public class OtaServiceImpl extends ServiceImpl + implements OtaService{ + +} + + + + diff --git a/main/manager-api/src/main/resources/application-dev.yml b/main/manager-api/src/main/resources/application-dev.yml index 90a62b6a..f149133e 100644 --- a/main/manager-api/src/main/resources/application-dev.yml +++ b/main/manager-api/src/main/resources/application-dev.yml @@ -42,21 +42,22 @@ spring: wall: config: multi-statement-allow: true - redis: - host: localhost # Redis服务器地址 - port: 6379 # Redis服务器连接端口 - password: # Redis服务器连接密码(默认为空) - database: 0 # Redis数据库索引(默认为0) -# jedis: -# pool: -# max-active: 8 # 连接池最大连接数(使用负值表示没有限制) -# max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) -# max-idle: 8 # 连接池中的最大空闲连接 -# min-idle: 0 # 连接池中的最小空闲连接 - timeout: 10000ms # 连接超时时间(毫秒) - lettuce: - pool: - max-active: 8 # 连接池最大连接数(使用负值表示没有限制) - max-idle: 8 # 连接池中的最大空闲连接 - min-idle: 0 # 连接池中的最小空闲连接 - shutdown-timeout: 100ms # 客户端优雅关闭的等待时间 \ No newline at end of file + data: + redis: + host: localhost # Redis服务器地址 + port: 6379 # Redis服务器连接端口 + password: # Redis服务器连接密码(默认为空) + database: 0 # Redis数据库索引(默认为0) + # jedis: + # pool: + # max-active: 8 # 连接池最大连接数(使用负值表示没有限制) + # max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) + # max-idle: 8 # 连接池中的最大空闲连接 + # min-idle: 0 # 连接池中的最小空闲连接 + timeout: 10000ms # 连接超时时间(毫秒) + lettuce: + pool: + max-active: 8 # 连接池最大连接数(使用负值表示没有限制) + max-idle: 8 # 连接池中的最大空闲连接 + min-idle: 0 # 连接池中的最小空闲连接 + shutdown-timeout: 100ms # 客户端优雅关闭的等待时间 \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202503241020.sql b/main/manager-api/src/main/resources/db/changelog/202503241020.sql new file mode 100644 index 00000000..55c019f6 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202503241020.sql @@ -0,0 +1,15 @@ +-- OTA升级信息表 +DROP TABLE IF EXISTS `ai_ota`; +CREATE TABLE `ai_ota` ( + `id` VARCHAR(32) NOT NULL COMMENT '记录唯一标识', + `board` VARCHAR(50) COMMENT '设备硬件型号', + `app_version` VARCHAR(20) COMMENT '固件版本号', + `url` VARCHAR(500) COMMENT '下载地址', + `is_enabled` TINYINT(1) DEFAULT 0 COMMENT '是否启用', + `creator` BIGINT COMMENT '创建者', + `create_date` DATETIME COMMENT '创建时间', + `updater` BIGINT COMMENT '更新者', + `update_date` DATETIME COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uni_ai_ota_board` (`board`) COMMENT '设备型号唯一索引,用于快速查找升级信息' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='OTA升级信息表'; diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index 89ed7ff5..41d9f61a 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -15,4 +15,11 @@ databaseChangeLog: changes: - sqlFile: encoding: utf8 - path: classpath:db/changelog/202503141346.sql \ No newline at end of file + path: classpath:db/changelog/202503141346.sql + - changeSet: + id: 202503241020 + author: dreamchen + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202503241020.sql \ No newline at end of file diff --git a/main/manager-api/src/main/resources/mapper/model/ModelProviderMapper.xml b/main/manager-api/src/main/resources/mapper/model/ModelProviderMapper.xml new file mode 100644 index 00000000..c4d2ae8f --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/model/ModelProviderMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + id,model_type,provider_code,name,fields,sort, + creator,create_date,updater,update_date + + diff --git a/main/manager-api/src/main/resources/mapper/ota/OtaMapper.xml b/main/manager-api/src/main/resources/mapper/ota/OtaMapper.xml new file mode 100644 index 00000000..a3ab550c --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/ota/OtaMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + id,board,app_version,url,is_enabled,creator, + create_date,updater,update_date + + diff --git a/main/manager-web/src/apis/api.js b/main/manager-web/src/apis/api.js index 03a5b5a2..23169518 100755 --- a/main/manager-web/src/apis/api.js +++ b/main/manager-web/src/apis/api.js @@ -2,6 +2,7 @@ import agent from './module/agent.js' import device from './module/device.js' import user from './module/user.js' +import ota from './module/ota.js' /** * 接口地址 @@ -11,7 +12,7 @@ import user from './module/user.js' */ // const DEV_API_SERVICE = 'https://m1.apifoxmock.com/m1/5931378-5618560-default' // 8002开发完成完成后使用这个 -const DEV_API_SERVICE = '/xiaozhi-esp32-api' +const DEV_API_SERVICE = '/xiaozhi-esp32-api/api/v1' /** * 根据开发环境返回接口url @@ -27,5 +28,6 @@ export default { getServiceUrl, user, agent, - device + device, + ota } diff --git a/main/manager-web/src/apis/module/agent.js b/main/manager-web/src/apis/module/agent.js index e3c2c538..de6e9324 100755 --- a/main/manager-web/src/apis/module/agent.js +++ b/main/manager-web/src/apis/module/agent.js @@ -5,7 +5,7 @@ import {getServiceUrl} from '../api' export default { //智能体列表 getAgentList(callback) { - RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/agent`) + RequestService.sendRequest().url(`${getServiceUrl()}/user/agent`) .method('GET') .success((res) => { RequestService.clearRequestTime() @@ -18,7 +18,7 @@ export default { //添加智能体 addAgent(agentName, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent`) + .url(`${getServiceUrl()}/user/agent`) .method('POST') .data({agentName}) .success((res) => { @@ -30,9 +30,9 @@ export default { }).send(); }, //获取智能体配置 - getAgentConfig(agent_id, callback) { + getAgentConfig(agentId, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent/${agent_id}`) + .url(`${getServiceUrl()}/user/agent/${agentId}`) .method('GET') .success((res) => { RequestService.clearRequestTime(); @@ -40,15 +40,15 @@ export default { }) .fail((err) => { console.error('获取智能体配置失败:', err); - RequestService.reAjaxFun(() => { - this.getAgentConfig(agent_id, callback); - }); + // RequestService.reAjaxFun(() => { + // this.getAgentConfig(agent_id, callback); + // }); }).send(); }, //配置智能体 - saveAgentConfig(agent_id, configData, callback) { + saveAgentConfig(agentId, configData, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent/${agent_id}`) + .url(`${getServiceUrl()}/user/agent/${agentId}`) .method('PUT') .data(configData) .success((res) => { @@ -57,15 +57,15 @@ export default { }) .fail((err) => { console.error('保存智能体配置失败:', err); - RequestService.reAjaxFun(() => { - this.saveAgentConfig(device_id, configData, callback); - }); + // RequestService.reAjaxFun(() => { + // this.saveAgentConfig(device_id, configData, callback); + // }); }).send(); }, //删除智能体 - delAgent(agent_id, callback) { + delAgent(agentId, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent/${agent_id}`) + .url(`${getServiceUrl()}/user/agent/${agentId}`) .method('DELETE') .success((res) => { RequestService.clearRequestTime(); @@ -73,9 +73,9 @@ export default { }) .fail((err) => { console.error('删除智能体失败:', err); - RequestService.reAjaxFun(() => { - this.deleteAgent(agent_id, callback); - }); + // RequestService.reAjaxFun(() => { + // this.deleteAgent(agent_id, callback); + // }); }).send(); }, } diff --git a/main/manager-web/src/apis/module/device.js b/main/manager-web/src/apis/module/device.js index e16bec25..fdf74bbc 100755 --- a/main/manager-web/src/apis/module/device.js +++ b/main/manager-web/src/apis/module/device.js @@ -5,7 +5,7 @@ import {getServiceUrl} from '../api' export default { //设备列表 getDeviceList(agent_id, page, callback) { - RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agent_id}`) + RequestService.sendRequest().url(`${getServiceUrl()}/user/agent/device/bind/${agent_id}`) .method('GET') .data(page) .success((res) => { @@ -19,7 +19,7 @@ export default { //绑定设备 bindDevice(agentId, deviceCode, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agentId}`) + .url(`${getServiceUrl()}/user/agent/device/bind/${agentId}`) .method('POST') .data({deviceCode}) .success((res) => { @@ -28,24 +28,53 @@ export default { }) .fail((err) => { console.error('绑定设备失败:', err); - RequestService.reAjaxFun(() => { - this.addDevice(agentId, deviceCode, callback); - }); + // RequestService.reAjaxFun(() => { + // this.addDevice(agentId, deviceCode, callback); + // }); }).send(); }, // 解绑设备 unbindDevice(deviceId, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent/device/unbind/${deviceId}`) + .url(`${getServiceUrl()}/user/agent/device/unbind/${deviceId}`) .method('PUT') .success((res) => { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { - RequestService.reAjaxFun(() => { - this.unbindDevice(deviceId, callback); - }); + .fail((err) => { + console.error('解绑设备失败:', err); + // RequestService.reAjaxFun(() => { + // this.unbindDevice(deviceId, callback); + // }); + }).send() + }, + // 切换设备OTA升级状态 + toggleAutoUpdate(deviceId, autoUpdate, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/user/agent/device/autoUpdate/${deviceId}`) + .method('POST') + .data({autoUpdate}) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('切换设备OTA升级状态失败:', err); + }).send() + }, + // 设置设备别名 + setAlias(deviceId, alias, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/user/agent/device/alias/${deviceId}`) + .method('POST') + .data({alias}) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('设置设备别名失败:', err); }).send() }, diff --git a/main/manager-web/src/apis/module/ota.js b/main/manager-web/src/apis/module/ota.js new file mode 100755 index 00000000..23344b9c --- /dev/null +++ b/main/manager-web/src/apis/module/ota.js @@ -0,0 +1,67 @@ +import RequestService from '../httpRequest' +import {getServiceUrl} from '../api' + + +export default { + //设备OTA列表 + getOtaList(page, callback) { + RequestService.sendRequest().url(`${getServiceUrl()}/ota/sys/getOtalist`) + .method('GET') + .data(page) + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .fail((err) => { + console.error('获取设备OTA列表失败:', err); + }).send() + }, + //添加设备OTA + saveOta(ota, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/ota/sys/save`) + .method('POST') + .data(ota) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('添加设备OTA失败:', err); + }).send(); + }, + //配置设备OTA + updateOta(ota, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/ota/sys/update`) + .method('POST') + .data(ota) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('保存设备OTA配置失败:', err); + // RequestService.reAjaxFun(() => { + // this.saveAgentConfig(device_id, configData, callback); + // }); + }).send(); + }, + //切换设备OTA可用状态失败 + toggleEnabled(ota, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/ota/sys/toggleEnabled`) + .method('POST') + .data(ota) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('切换设备OTA可用状态失败:', err); + // RequestService.reAjaxFun(() => { + // this.deleteAgent(agent_id, callback); + // }); + }).send(); + }, +} diff --git a/main/manager-web/src/apis/module/user.js b/main/manager-web/src/apis/module/user.js index 1879afaf..28859db6 100755 --- a/main/manager-web/src/apis/module/user.js +++ b/main/manager-web/src/apis/module/user.js @@ -5,53 +5,24 @@ import {getServiceUrl} from '../api' export default { // 登录 login(loginForm, callback) { - RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/login`) + RequestService.sendRequest().url(`${getServiceUrl()}/user/login`) .method('POST') .data(loginForm) .success((res) => { RequestService.clearRequestTime() callback(res) }) - .fail(() => { - RequestService.reAjaxFun(() => { - this.login(loginForm, callback) - }) + .fail((err) => { + console.error('登录失败:', err); + // RequestService.reAjaxFun(() => { + // this.login(loginForm, callback) + // }) }).send() - }, - // 获取设备信息 - getHomeList(callback) { - RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/device/bind`) - .method('GET') - .success((res) => { - RequestService.clearRequestTime() - callback(res) - }) - .fail(() => { - RequestService.reAjaxFun(() => { - this.getUserInfo() - }) - }).send() - }, - // 解绑设备 - unbindDevice(device_id, callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/device/unbind/${device_id}`) - .method('PUT') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail(() => { - RequestService.reAjaxFun(() => { - this.unbindDevice(device_id, callback); - }); - }).send() - }, - // 获取验证码 + }, // 获取验证码 getCaptcha(uuid, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/captcha?uuid=${uuid}`) + .url(`${getServiceUrl()}/user/captcha?uuid=${uuid}`) .method('GET') .type('blob') .header({ @@ -64,104 +35,57 @@ export default { callback(res); }) .fail((err) => { // 添加错误参数 - + console.error('获取验证码失败:', err); }).send() }, // 注册账号 register(registerForm, callback) { - RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/register`).method('POST') + RequestService.sendRequest().url(`${getServiceUrl()}/user/register`).method('POST') .data(registerForm) .success((res) => { RequestService.clearRequestTime() callback(res) }) - .fail(() => { + .fail((err) => { + console.error('注册账号失败:', err); }).send() }, - // 保存设备配置 - saveDeviceConfig(device_id, configData, callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/configDevice/${device_id}`) - .method('PUT') - .data(configData) - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail((err) => { - console.error('保存配置失败:', err); - RequestService.reAjaxFun(() => { - this.saveDeviceConfig(device_id, configData, callback); - }); - }).send(); - }, - // 获取设备配置 - getDeviceConfig(device_id, callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/configDevice/${device_id}`) - .method('GET') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail((err) => { - console.error('获取配置失败:', err); - RequestService.reAjaxFun(() => { - this.getDeviceConfig(device_id, callback); - }); - }).send(); - }, // 获取所有模型名称 getModelNames(callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/models/names`) + .url(`${getServiceUrl()}/models/names`) .method('GET') .success((res) => { RequestService.clearRequestTime(); callback(res); }) .fail(() => { - RequestService.reAjaxFun(() => { - this.getModelNames(callback); - }); + // RequestService.reAjaxFun(() => { + // this.getModelNames(callback); + // }); }).send(); }, // 获取模型音色 getModelVoices(modelName, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/models/${modelName}/voices`) + .url(`${getServiceUrl()}/models/${modelName}/voices`) .method('GET') .success((res) => { RequestService.clearRequestTime(); callback(res); }) .fail(() => { - RequestService.reAjaxFun(() => { - this.getModelVoices(modelName, callback); - }); + // RequestService.reAjaxFun(() => { + // this.getModelVoices(modelName, callback); + // }); }).send(); }, - // 获取智能体列表 - getAgentList(callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent`) - .method('GET') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail(() => { - RequestService.reAjaxFun(() => { - this.getAgentList(callback); - }); - }).send(); - }, - + // 获取用户信息 getUserInfo(callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/info`) + .url(`${getServiceUrl()}/user/info`) .method('GET') .success((res) => { RequestService.clearRequestTime() @@ -169,26 +93,9 @@ export default { }) .fail((err) => { console.error('接口请求失败:', err) - RequestService.reAjaxFun(() => { - this.getUserInfo(callback) - }) + // RequestService.reAjaxFun(() => { + // this.getUserInfo(callback) + // }) }).send() }, - // 添加智能体 - addAgent(agentName, callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent`) - .method('POST') - .data({ name: agentName }) - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail(() => { - RequestService.reAjaxFun(() => { - this.addAgent(agentName, callback); - }); - }).send(); - }, - } diff --git a/main/manager-web/src/components/AddAgentDialog.vue b/main/manager-web/src/components/AddAgentDialog.vue index e805897f..af3b0214 100644 --- a/main/manager-web/src/components/AddAgentDialog.vue +++ b/main/manager-web/src/components/AddAgentDialog.vue @@ -1,8 +1,8 @@