Add agent configuration history

This commit is contained in:
mie
2026-07-07 17:49:34 +08:00
parent 209fd3fbc8
commit 35ec3ba803
24 changed files with 3079 additions and 62 deletions
@@ -127,7 +127,7 @@ public class AgentController {
}
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
agentService.updateAgentById(device.getAgentId(), agentUpdateDTO);
agentService.updateAgentById(device.getAgentId(), agentUpdateDTO, false);
return new Result<>();
}
@@ -332,8 +332,11 @@ public class AgentController {
public Result<Void> saveAgentTags(@PathVariable String id, @RequestBody Map<String, Object> params) {
List<String> tagIds = (List<String>) params.get("tagIds");
List<String> tagNames = (List<String>) params.get("tagNames");
agentTagService.saveAgentTags(id, tagIds, tagNames);
AgentUpdateDTO dto = new AgentUpdateDTO();
dto.setTagIds(tagIds);
dto.setTagNames(tagNames);
agentService.updateAgentById(id, dto);
return new Result<Void>().ok(null);
}
}
}
@@ -0,0 +1,66 @@
package xiaozhi.modules.agent.controller;
import java.util.Map;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.Result;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentSnapshotService;
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
import xiaozhi.modules.security.user.SecurityUser;
@Tag(name = "智能体快照")
@AllArgsConstructor
@RestController
@RequestMapping("/agent/{agentId}/snapshots")
public class AgentSnapshotController {
private final AgentSnapshotService agentSnapshotService;
private final AgentService agentService;
@GetMapping
@Operation(summary = "获取智能体快照列表")
@RequiresPermissions("sys:role:normal")
public Result<PageData<AgentSnapshotVO>> page(
@PathVariable String agentId,
@RequestParam Map<String, Object> params) {
checkPermission(agentId);
return new Result<PageData<AgentSnapshotVO>>().ok(agentSnapshotService.page(agentId, params));
}
@GetMapping("/{snapshotId}")
@Operation(summary = "获取智能体快照详情")
@RequiresPermissions("sys:role:normal")
public Result<AgentSnapshotVO> getSnapshot(@PathVariable String agentId, @PathVariable String snapshotId) {
checkPermission(agentId);
return new Result<AgentSnapshotVO>().ok(agentSnapshotService.getSnapshot(agentId, snapshotId));
}
@PostMapping("/{snapshotId}/restore")
@Operation(summary = "恢复智能体快照")
@RequiresPermissions("sys:role:normal")
public Result<Void> restore(@PathVariable String agentId, @PathVariable String snapshotId) {
checkPermission(agentId);
agentSnapshotService.restoreSnapshot(agentId, snapshotId);
return new Result<>();
}
private void checkPermission(String agentId) {
UserDetail user = SecurityUser.getUser();
if (user == null || !agentService.checkAgentPermission(agentId, user.getId())) {
throw new RenException("没有权限访问该智能体快照");
}
}
}
@@ -0,0 +1,14 @@
package xiaozhi.modules.agent.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
@Mapper
public interface AgentSnapshotDao extends BaseDao<AgentSnapshotEntity> {
Integer selectMaxVersionNo(@Param("agentId") String agentId);
AgentSnapshotEntity selectNextSnapshot(@Param("agentId") String agentId, @Param("versionNo") Integer versionNo);
}
@@ -0,0 +1,40 @@
package xiaozhi.modules.agent.dto;
import java.io.Serializable;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "智能体快照数据")
public class AgentSnapshotDataDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String agentCode;
private String agentName;
private String asrModelId;
private String vadModelId;
private String llmModelId;
private String slmModelId;
private String vllmModelId;
private String ttsModelId;
private String ttsVoiceId;
private String ttsLanguage;
private Integer ttsVolume;
private Integer ttsRate;
private Integer ttsPitch;
private String memModelId;
private String intentModelId;
private Integer chatHistoryConf;
private String systemPrompt;
private String summaryMemory;
private String langCode;
private String language;
private Integer sort;
private List<AgentUpdateDTO.FunctionInfo> functions;
private List<ContextProviderDTO> contextProviders;
private List<String> correctWordFileIds;
private List<String> tagNames;
private List<AgentSnapshotTagDTO> tags;
}
@@ -0,0 +1,16 @@
package xiaozhi.modules.agent.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "智能体快照标签")
public class AgentSnapshotTagDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String tagName;
private Integer sort;
}
@@ -4,9 +4,12 @@ import java.io.Serializable;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import xiaozhi.common.utils.JsonUtils;
/**
* 智能体更新DTO
@@ -91,15 +94,50 @@ public class AgentUpdateDTO implements Serializable {
@Schema(description = "替换词文件ID列表", nullable = true)
private List<String> correctWordFileIds;
@Schema(description = "标签名称列表", nullable = true)
private List<String> tagNames;
@Schema(description = "标签ID列表", nullable = true)
private List<String> tagIds;
@Data
@Schema(description = "插件函数信息")
public static class FunctionInfo implements Serializable {
private static final TypeReference<HashMap<String, Object>> PARAM_INFO_TYPE = new TypeReference<>() {
};
@Schema(description = "插件ID", example = "plugin_01")
private String pluginId;
@Schema(description = "函数参数信息", nullable = true)
private HashMap<String, Object> paramInfo;
private HashMap<String, Object> paramInfo = new HashMap<>();
public void setParamInfo(Object paramInfo) {
this.paramInfo = normalizeParamInfo(paramInfo);
}
private static HashMap<String, Object> normalizeParamInfo(Object paramInfo) {
if (paramInfo == null) {
return new HashMap<>();
}
if (paramInfo instanceof String value) {
if (value.trim().isEmpty()) {
return new HashMap<>();
}
return JsonUtils.parseObject(value, PARAM_INFO_TYPE);
}
if (paramInfo instanceof Map<?, ?> value) {
HashMap<String, Object> normalized = new HashMap<>();
value.forEach((key, val) -> {
if (key != null) {
normalized.put(String.valueOf(key), val);
}
});
return normalized;
}
return JsonUtils.parseObject(JsonUtils.toJsonString(paramInfo), PARAM_INFO_TYPE);
}
private static final long serialVersionUID = 1L;
}
}
}
@@ -0,0 +1,44 @@
package xiaozhi.modules.agent.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@TableName("ai_agent_snapshot")
@Schema(description = "智能体配置快照")
public class AgentSnapshotEntity {
@TableId(type = IdType.ASSIGN_UUID)
@Schema(description = "快照ID")
private String id;
@Schema(description = "智能体ID")
private String agentId;
@Schema(description = "所属用户ID")
private Long userId;
@Schema(description = "版本号")
private Integer versionNo;
@Schema(description = "快照数据JSON")
private String snapshotData;
@Schema(description = "变更字段JSON")
private String changedFields;
@Schema(description = "快照来源")
private String source;
@Schema(description = "创建者")
private Long creator;
@Schema(description = "创建时间")
private Date createdAt;
}
@@ -93,6 +93,15 @@ public interface AgentService extends BaseService<AgentEntity> {
*/
void updateAgentById(String agentId, AgentUpdateDTO dto);
/**
* 更新智能体
*
* @param agentId 智能体ID
* @param dto 更新智能体所需的信息
* @param createSnapshot 是否创建配置快照
*/
void updateAgentById(String agentId, AgentUpdateDTO dto, boolean createSnapshot);
/**
* 创建智能体
*
@@ -0,0 +1,19 @@
package xiaozhi.modules.agent.service;
import java.util.Map;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
public interface AgentSnapshotService extends BaseService<AgentSnapshotEntity> {
void createSnapshot(String agentId, String source, AgentUpdateDTO pendingUpdate);
PageData<AgentSnapshotVO> page(String agentId, Map<String, Object> params);
AgentSnapshotVO getSnapshot(String agentId, String snapshotId);
void restoreSnapshot(String agentId, String snapshotId);
}
@@ -118,7 +118,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
{
setSummaryMemory(summaryDTO.getSummary());
}
});
}, false);
log.info("成功保存会话 {} 的聊天记录总结到智能体 {}", sessionId, agentId);
} else {
log.info("生成总结失败: {}", summaryDTO.getErrorMessage());
@@ -518,4 +518,4 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
return null;
}
}
}
}
@@ -43,9 +43,10 @@ import xiaozhi.modules.agent.entity.AgentTagEntity;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
import xiaozhi.modules.agent.service.AgentContextProviderService;
import xiaozhi.modules.agent.service.AgentPluginMappingService;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentTagService;
import xiaozhi.modules.agent.service.AgentPluginMappingService;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentSnapshotService;
import xiaozhi.modules.agent.service.AgentTagService;
import xiaozhi.modules.agent.service.AgentTemplateService;
import xiaozhi.modules.agent.vo.AgentInfoVO;
import xiaozhi.modules.correctword.service.CorrectWordFileService;
@@ -73,9 +74,10 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
private final AgentChatHistoryService agentChatHistoryService;
private final AgentTemplateService agentTemplateService;
private final ModelProviderService modelProviderService;
private final AgentContextProviderService agentContextProviderService;
private final AgentTagService agentTagService;
private final CorrectWordFileService correctWordFileService;
private final AgentContextProviderService agentContextProviderService;
private final AgentTagService agentTagService;
private final CorrectWordFileService correctWordFileService;
private final AgentSnapshotService agentSnapshotService;
@Override
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
@@ -272,17 +274,28 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
return userId.equals(agent.getUserId());
}
// 根据id更新智能体信息
@Override
@Transactional(rollbackFor = Exception.class)
public void updateAgentById(String agentId, AgentUpdateDTO dto) {
// 先查询现有实体
AgentEntity existingEntity = this.getAgentById(agentId);
if (existingEntity == null) {
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
}
// 只更新提供的非空字段
// 根据id更新智能体信息
@Override
@Transactional(rollbackFor = Exception.class)
public void updateAgentById(String agentId, AgentUpdateDTO dto) {
updateAgentById(agentId, dto, true);
}
// 根据id更新智能体信息
@Override
@Transactional(rollbackFor = Exception.class)
public void updateAgentById(String agentId, AgentUpdateDTO dto, boolean createSnapshot) {
// 先查询现有实体
AgentEntity existingEntity = this.getAgentById(agentId);
if (existingEntity == null) {
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
}
if (createSnapshot) {
agentSnapshotService.createSnapshot(agentId, "config", dto);
}
// 只更新提供的非空字段
if (dto.getAgentName() != null) {
existingEntity.setAgentName(dto.getAgentName());
}
@@ -426,11 +439,16 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
}
// 更新替换词文件关联
if (dto.getCorrectWordFileIds() != null) {
correctWordFileService.saveAgentCorrectWords(agentId, dto.getCorrectWordFileIds());
}
boolean b = validateLLMIntentParams(dto.getLlmModelId(), dto.getIntentModelId());
if (dto.getCorrectWordFileIds() != null) {
correctWordFileService.saveAgentCorrectWords(agentId, dto.getCorrectWordFileIds());
}
// 更新智能体标签
if (dto.getTagNames() != null || dto.getTagIds() != null) {
agentTagService.saveAgentTags(agentId, dto.getTagIds(), dto.getTagNames());
}
boolean b = validateLLMIntentParams(dto.getLlmModelId(), dto.getIntentModelId());
if (!b) {
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
}
@@ -576,4 +594,4 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
}
}
}
}
@@ -0,0 +1,475 @@
package xiaozhi.modules.agent.service.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.JsonUtils;
import xiaozhi.modules.agent.dao.AgentDao;
import xiaozhi.modules.agent.dao.AgentSnapshotDao;
import xiaozhi.modules.agent.dao.AgentTagDao;
import xiaozhi.modules.agent.dao.AgentTagRelationDao;
import xiaozhi.modules.agent.dto.AgentSnapshotDataDTO;
import xiaozhi.modules.agent.dto.AgentSnapshotTagDTO;
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentPluginMapping;
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
import xiaozhi.modules.agent.entity.AgentTagEntity;
import xiaozhi.modules.agent.entity.AgentTagRelationEntity;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
import xiaozhi.modules.agent.service.AgentContextProviderService;
import xiaozhi.modules.agent.service.AgentPluginMappingService;
import xiaozhi.modules.agent.service.AgentSnapshotService;
import xiaozhi.modules.agent.service.AgentTagService;
import xiaozhi.modules.agent.vo.AgentInfoVO;
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
import xiaozhi.modules.correctword.service.CorrectWordFileService;
import xiaozhi.modules.model.entity.ModelConfigEntity;
import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.security.user.SecurityUser;
@Service
@AllArgsConstructor
public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao, AgentSnapshotEntity>
implements AgentSnapshotService {
private static final TypeReference<List<String>> STRING_LIST_TYPE = new TypeReference<>() {
};
private static final TypeReference<HashMap<String, Object>> PARAM_INFO_TYPE = new TypeReference<>() {
};
private final AgentSnapshotDao agentSnapshotDao;
private final AgentDao agentDao;
private final AgentTagDao agentTagDao;
private final AgentTagRelationDao agentTagRelationDao;
private final AgentPluginMappingService agentPluginMappingService;
private final AgentContextProviderService agentContextProviderService;
private final AgentTagService agentTagService;
private final AgentChatHistoryService agentChatHistoryService;
private final ModelConfigService modelConfigService;
private final CorrectWordFileService correctWordFileService;
@Override
@Transactional(rollbackFor = Exception.class)
public void createSnapshot(String agentId, String source, AgentUpdateDTO pendingUpdate) {
AgentSnapshotDataDTO snapshotData = buildSnapshotData(agentId);
List<String> changedFields = getChangedFields(snapshotData, pendingUpdate);
if (pendingUpdate != null && changedFields.isEmpty()) {
return;
}
AgentInfoVO agent = agentDao.selectAgentInfoById(agentId);
if (agent == null) {
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
}
AgentSnapshotEntity entity = new AgentSnapshotEntity();
entity.setAgentId(agentId);
entity.setUserId(agent.getUserId());
entity.setSnapshotData(JsonUtils.toJsonString(snapshotData));
entity.setChangedFields(JsonUtils.toJsonString(changedFields));
entity.setSource(StringUtils.defaultIfBlank(source, "config"));
entity.setCreator(SecurityUser.getUserId());
entity.setCreatedAt(new Date());
insertSnapshotWithRetry(entity);
}
@Override
public PageData<AgentSnapshotVO> page(String agentId, Map<String, Object> params) {
IPage<AgentSnapshotEntity> page = getPage(params, "created_at", false);
IPage<AgentSnapshotEntity> result = agentSnapshotDao.selectPage(page,
new QueryWrapper<AgentSnapshotEntity>().eq("agent_id", agentId));
List<AgentSnapshotVO> list = result.getRecords().stream()
.map(entity -> toVO(entity, false))
.toList();
return new PageData<>(list, result.getTotal());
}
@Override
public AgentSnapshotVO getSnapshot(String agentId, String snapshotId) {
AgentSnapshotEntity entity = getSnapshotEntity(agentId, snapshotId);
return toVO(entity, true);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void restoreSnapshot(String agentId, String snapshotId) {
AgentSnapshotEntity snapshot = getSnapshotEntity(agentId, snapshotId);
AgentSnapshotDataDTO data = JsonUtils.parseObject(snapshot.getSnapshotData(), AgentSnapshotDataDTO.class);
if (data == null) {
throw new RenException("快照数据为空,无法恢复");
}
createSnapshot(agentId, "restore", null);
AgentEntity agent = agentDao.selectById(agentId);
if (agent == null) {
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
}
applyAgentFields(agent, data);
validateRestoreParams(agent);
applyMemoryPolicy(agent);
agent.setUpdater(SecurityUser.getUserId());
agent.setUpdatedAt(new Date());
agentDao.updateById(agent);
restoreFunctions(agentId, data.getFunctions());
restoreContextProviders(agentId, data.getContextProviders());
correctWordFileService.saveAgentCorrectWords(agentId, nullToEmpty(data.getCorrectWordFileIds()));
restoreTags(agentId, data);
}
private void insertSnapshotWithRetry(AgentSnapshotEntity entity) {
for (int attempt = 0; attempt < 3; attempt++) {
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(entity.getAgentId());
entity.setVersionNo((maxVersionNo == null ? 0 : maxVersionNo) + 1);
try {
agentSnapshotDao.insert(entity);
return;
} catch (DuplicateKeyException e) {
if (attempt == 2) {
throw e;
}
}
}
}
private AgentSnapshotEntity getSnapshotEntity(String agentId, String snapshotId) {
AgentSnapshotEntity entity = selectById(snapshotId);
if (entity == null || !Objects.equals(agentId, entity.getAgentId())) {
throw new RenException("快照不存在");
}
return entity;
}
private AgentSnapshotDataDTO buildSnapshotData(String agentId) {
AgentInfoVO agent = agentDao.selectAgentInfoById(agentId);
if (agent == null) {
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
}
AgentSnapshotDataDTO data = new AgentSnapshotDataDTO();
data.setAgentCode(agent.getAgentCode());
data.setAgentName(agent.getAgentName());
data.setAsrModelId(agent.getAsrModelId());
data.setVadModelId(agent.getVadModelId());
data.setLlmModelId(agent.getLlmModelId());
data.setSlmModelId(agent.getSlmModelId());
data.setVllmModelId(agent.getVllmModelId());
data.setTtsModelId(agent.getTtsModelId());
data.setTtsVoiceId(agent.getTtsVoiceId());
data.setTtsLanguage(agent.getTtsLanguage());
data.setTtsVolume(agent.getTtsVolume());
data.setTtsRate(agent.getTtsRate());
data.setTtsPitch(agent.getTtsPitch());
data.setMemModelId(agent.getMemModelId());
data.setIntentModelId(agent.getIntentModelId());
data.setChatHistoryConf(agent.getChatHistoryConf());
data.setSystemPrompt(agent.getSystemPrompt());
data.setSummaryMemory(agent.getSummaryMemory());
data.setLangCode(agent.getLangCode());
data.setLanguage(agent.getLanguage());
data.setSort(agent.getSort());
data.setFunctions(toFunctionInfo(agent.getFunctions()));
data.setContextProviders(getContextProviders(agentId));
data.setCorrectWordFileIds(nullToEmpty(correctWordFileService.getAgentCorrectWordFileIds(agentId)));
List<AgentSnapshotTagDTO> tags = getSnapshotTags(agentId);
data.setTags(tags);
data.setTagNames(tags.stream().map(AgentSnapshotTagDTO::getTagName).toList());
return data;
}
private List<AgentSnapshotTagDTO> getSnapshotTags(String agentId) {
List<AgentTagEntity> tags = agentTagDao.selectByAgentId(agentId);
if (tags == null || tags.isEmpty()) {
return Collections.emptyList();
}
List<AgentSnapshotTagDTO> snapshotTags = new ArrayList<>();
for (int i = 0; i < tags.size(); i++) {
AgentTagEntity tag = tags.get(i);
AgentSnapshotTagDTO snapshotTag = new AgentSnapshotTagDTO();
snapshotTag.setId(tag.getId());
snapshotTag.setTagName(tag.getTagName());
snapshotTag.setSort(i);
snapshotTags.add(snapshotTag);
}
return snapshotTags;
}
private List<AgentUpdateDTO.FunctionInfo> toFunctionInfo(List<AgentPluginMapping> mappings) {
if (mappings == null || mappings.isEmpty()) {
return Collections.emptyList();
}
return mappings.stream().map(mapping -> {
AgentUpdateDTO.FunctionInfo info = new AgentUpdateDTO.FunctionInfo();
info.setPluginId(mapping.getPluginId());
info.setParamInfo(parseParamInfo(mapping.getParamInfo()));
return info;
}).toList();
}
private HashMap<String, Object> parseParamInfo(String paramInfo) {
if (StringUtils.isBlank(paramInfo)) {
return new HashMap<>();
}
return JsonUtils.parseObject(paramInfo, PARAM_INFO_TYPE);
}
private List<xiaozhi.modules.agent.dto.ContextProviderDTO> getContextProviders(String agentId) {
AgentContextProviderEntity contextEntity = agentContextProviderService.getByAgentId(agentId);
if (contextEntity == null || contextEntity.getContextProviders() == null) {
return Collections.emptyList();
}
return contextEntity.getContextProviders();
}
private List<String> getChangedFields(AgentSnapshotDataDTO current, AgentUpdateDTO pendingUpdate) {
if (pendingUpdate == null) {
return List.of("restore");
}
List<String> fields = new ArrayList<>();
compare(fields, "agentCode", current.getAgentCode(), pendingUpdate.getAgentCode());
compare(fields, "agentName", current.getAgentName(), pendingUpdate.getAgentName());
compare(fields, "asrModelId", current.getAsrModelId(), pendingUpdate.getAsrModelId());
compare(fields, "vadModelId", current.getVadModelId(), pendingUpdate.getVadModelId());
compare(fields, "llmModelId", current.getLlmModelId(), pendingUpdate.getLlmModelId());
compare(fields, "slmModelId", current.getSlmModelId(), pendingUpdate.getSlmModelId());
compare(fields, "vllmModelId", current.getVllmModelId(), pendingUpdate.getVllmModelId());
compare(fields, "ttsModelId", current.getTtsModelId(), pendingUpdate.getTtsModelId());
compare(fields, "ttsVoiceId", current.getTtsVoiceId(), pendingUpdate.getTtsVoiceId());
compare(fields, "ttsLanguage", current.getTtsLanguage(), pendingUpdate.getTtsLanguage());
compare(fields, "ttsVolume", current.getTtsVolume(), pendingUpdate.getTtsVolume());
compare(fields, "ttsRate", current.getTtsRate(), pendingUpdate.getTtsRate());
compare(fields, "ttsPitch", current.getTtsPitch(), pendingUpdate.getTtsPitch());
compare(fields, "memModelId", current.getMemModelId(), pendingUpdate.getMemModelId());
compare(fields, "intentModelId", current.getIntentModelId(), pendingUpdate.getIntentModelId());
compare(fields, "chatHistoryConf", current.getChatHistoryConf(), pendingUpdate.getChatHistoryConf());
compare(fields, "systemPrompt", current.getSystemPrompt(), pendingUpdate.getSystemPrompt());
compare(fields, "summaryMemory", current.getSummaryMemory(), pendingUpdate.getSummaryMemory());
compare(fields, "langCode", current.getLangCode(), pendingUpdate.getLangCode());
compare(fields, "language", current.getLanguage(), pendingUpdate.getLanguage());
compare(fields, "sort", current.getSort(), pendingUpdate.getSort());
compare(fields, "functions", current.getFunctions(), pendingUpdate.getFunctions());
compare(fields, "contextProviders", current.getContextProviders(), pendingUpdate.getContextProviders());
compare(fields, "correctWordFileIds", current.getCorrectWordFileIds(), pendingUpdate.getCorrectWordFileIds());
compare(fields, "tagNames", current.getTagNames(), pendingUpdate.getTagNames());
compare(fields, "tagIds", current.getTags().stream().map(AgentSnapshotTagDTO::getId).toList(),
pendingUpdate.getTagIds());
return fields;
}
private <T> void compare(List<String> fields, String name, T current, T next) {
if (next != null && !Objects.equals(current, next)) {
fields.add(name);
}
}
private void applyAgentFields(AgentEntity agent, AgentSnapshotDataDTO data) {
agent.setAgentCode(data.getAgentCode());
agent.setAgentName(data.getAgentName());
agent.setAsrModelId(data.getAsrModelId());
agent.setVadModelId(data.getVadModelId());
agent.setLlmModelId(data.getLlmModelId());
agent.setSlmModelId(data.getSlmModelId());
agent.setVllmModelId(data.getVllmModelId());
agent.setTtsModelId(data.getTtsModelId());
agent.setTtsVoiceId(data.getTtsVoiceId());
agent.setTtsLanguage(data.getTtsLanguage());
agent.setTtsVolume(data.getTtsVolume());
agent.setTtsRate(data.getTtsRate());
agent.setTtsPitch(data.getTtsPitch());
agent.setMemModelId(data.getMemModelId());
agent.setIntentModelId(data.getIntentModelId());
agent.setChatHistoryConf(data.getChatHistoryConf());
agent.setSystemPrompt(data.getSystemPrompt());
agent.setSummaryMemory(data.getSummaryMemory());
agent.setLangCode(data.getLangCode());
agent.setLanguage(data.getLanguage());
agent.setSort(data.getSort());
}
private void validateRestoreParams(AgentEntity agent) {
if (agent == null || StringUtils.isBlank(agent.getLlmModelId())) {
return;
}
ModelConfigEntity llmModelData = modelConfigService.selectById(agent.getLlmModelId());
if (llmModelData == null || llmModelData.getConfigJson() == null) {
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
}
Object typeValue = llmModelData.getConfigJson().get("type");
String type = typeValue == null ? "" : typeValue.toString();
if ("openai".equals(type) || "ollama".equals(type)) {
return;
}
if ("Intent_function_call".equals(agent.getIntentModelId())) {
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
}
}
private void applyMemoryPolicy(AgentEntity agent) {
if (agent == null || StringUtils.isBlank(agent.getMemModelId())) {
return;
}
if (Constant.MEMORY_NO_MEM.equals(agent.getMemModelId())) {
agentChatHistoryService.deleteByAgentId(agent.getId(), true, true);
agent.setSummaryMemory("");
} else if (Constant.MEMORY_MEM_REPORT_ONLY.equals(agent.getMemModelId())) {
agent.setSummaryMemory("");
}
}
private void restoreFunctions(String agentId, List<AgentUpdateDTO.FunctionInfo> functions) {
agentPluginMappingService.deleteByAgentId(agentId);
if (functions == null || functions.isEmpty()) {
return;
}
List<AgentPluginMapping> mappings = functions.stream().map(info -> {
AgentPluginMapping mapping = new AgentPluginMapping();
mapping.setAgentId(agentId);
mapping.setPluginId(info.getPluginId());
mapping.setParamInfo(JsonUtils.toJsonString(info.getParamInfo()));
return mapping;
}).toList();
agentPluginMappingService.saveBatch(mappings);
}
private void restoreContextProviders(String agentId,
List<xiaozhi.modules.agent.dto.ContextProviderDTO> contextProviders) {
AgentContextProviderEntity entity = new AgentContextProviderEntity();
entity.setAgentId(agentId);
entity.setContextProviders(nullToEmpty(contextProviders));
entity.setUpdater(SecurityUser.getUserId());
entity.setUpdatedAt(new Date());
agentContextProviderService.saveOrUpdateByAgentId(entity);
}
private void restoreTags(String agentId, AgentSnapshotDataDTO data) {
List<AgentSnapshotTagDTO> tags = data.getTags();
if (tags == null || tags.isEmpty()) {
agentTagService.saveAgentTags(agentId, null, nullToEmpty(data.getTagNames()));
return;
}
agentTagRelationDao.deleteByAgentId(agentId);
List<AgentTagRelationEntity> relations = new ArrayList<>();
Date now = new Date();
for (int i = 0; i < tags.size(); i++) {
AgentSnapshotTagDTO snapshotTag = tags.get(i);
String tagId = restoreTag(snapshotTag, now);
if (StringUtils.isBlank(tagId)) {
continue;
}
AgentTagRelationEntity relation = new AgentTagRelationEntity();
relation.setId(UUID.randomUUID().toString().replace("-", ""));
relation.setAgentId(agentId);
relation.setTagId(tagId);
relation.setSort(i);
relation.setCreator(SecurityUser.getUserId());
relation.setCreatedAt(now);
relation.setUpdater(SecurityUser.getUserId());
relation.setUpdatedAt(now);
relations.add(relation);
}
if (!relations.isEmpty()) {
agentTagRelationDao.batchInsertRelation(relations);
}
}
private String restoreTag(AgentSnapshotTagDTO snapshotTag, Date now) {
if (snapshotTag == null || StringUtils.isBlank(snapshotTag.getTagName())) {
return null;
}
AgentTagEntity tag = null;
if (StringUtils.isNotBlank(snapshotTag.getId())) {
tag = agentTagDao.selectById(snapshotTag.getId());
}
if (tag == null) {
tag = agentTagDao.selectOne(new QueryWrapper<AgentTagEntity>()
.eq("tag_name", snapshotTag.getTagName())
.last("LIMIT 1"));
}
if (tag != null) {
if (Objects.equals(tag.getDeleted(), 1)) {
tag.setDeleted(0);
tag.setUpdater(SecurityUser.getUserId());
tag.setUpdatedAt(now);
agentTagDao.updateById(tag);
}
return tag.getId();
}
AgentTagEntity newTag = new AgentTagEntity();
if (StringUtils.isNotBlank(snapshotTag.getId())) {
newTag.setId(snapshotTag.getId());
}
newTag.setTagName(snapshotTag.getTagName());
newTag.setSort(snapshotTag.getSort() == null ? 0 : snapshotTag.getSort());
newTag.setDeleted(0);
newTag.setCreator(SecurityUser.getUserId());
newTag.setCreatedAt(now);
newTag.setUpdater(SecurityUser.getUserId());
newTag.setUpdatedAt(now);
agentTagDao.insert(newTag);
return newTag.getId();
}
private AgentSnapshotVO toVO(AgentSnapshotEntity entity, boolean includeData) {
AgentSnapshotVO vo = new AgentSnapshotVO();
vo.setId(entity.getId());
vo.setAgentId(entity.getAgentId());
vo.setUserId(entity.getUserId());
vo.setVersionNo(entity.getVersionNo());
vo.setChangedFields(parseChangedFields(entity.getChangedFields()));
vo.setSource(entity.getSource());
vo.setCreator(entity.getCreator());
vo.setCreatedAt(entity.getCreatedAt());
if (includeData) {
vo.setSnapshotData(JsonUtils.parseObject(entity.getSnapshotData(), AgentSnapshotDataDTO.class));
vo.setAfterSnapshotData(getAfterSnapshotData(entity));
}
return vo;
}
private AgentSnapshotDataDTO getAfterSnapshotData(AgentSnapshotEntity entity) {
AgentSnapshotEntity nextSnapshot = agentSnapshotDao.selectNextSnapshot(entity.getAgentId(), entity.getVersionNo());
if (nextSnapshot != null) {
return JsonUtils.parseObject(nextSnapshot.getSnapshotData(), AgentSnapshotDataDTO.class);
}
return buildSnapshotData(entity.getAgentId());
}
private List<String> parseChangedFields(String changedFields) {
if (StringUtils.isBlank(changedFields)) {
return Collections.emptyList();
}
return JsonUtils.parseObject(changedFields, STRING_LIST_TYPE);
}
private <T> List<T> nullToEmpty(List<T> list) {
return list == null ? Collections.emptyList() : list;
}
}
@@ -1,7 +1,5 @@
package xiaozhi.modules.agent.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -20,7 +18,6 @@ import java.util.List;
public class AgentInfoVO extends AgentEntity
{
@Schema(description = "插件列表Id")
@TableField(typeHandler = JacksonTypeHandler.class)
private List<AgentPluginMapping> functions;
@Schema(description = "上下文源配置")
@@ -0,0 +1,23 @@
package xiaozhi.modules.agent.vo;
import java.util.Date;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import xiaozhi.modules.agent.dto.AgentSnapshotDataDTO;
@Data
@Schema(description = "智能体配置快照")
public class AgentSnapshotVO {
private String id;
private String agentId;
private Long userId;
private Integer versionNo;
private List<String> changedFields;
private String source;
private Long creator;
private Date createdAt;
private AgentSnapshotDataDTO snapshotData;
private AgentSnapshotDataDTO afterSnapshotData;
}
@@ -0,0 +1,17 @@
-- liquibase formatted sql
-- changeset xiaozhi:202607071530
CREATE TABLE IF NOT EXISTS `ai_agent_snapshot` (
`id` VARCHAR(32) NOT NULL COMMENT '快照ID',
`agent_id` VARCHAR(32) NOT NULL COMMENT '智能体ID',
`user_id` BIGINT DEFAULT NULL COMMENT '所属用户ID',
`version_no` INT UNSIGNED NOT NULL COMMENT '版本号',
`snapshot_data` JSON NOT NULL COMMENT '快照数据',
`changed_fields` JSON DEFAULT NULL COMMENT '变更字段',
`source` VARCHAR(32) DEFAULT 'config' COMMENT '快照来源',
`creator` BIGINT DEFAULT NULL COMMENT '创建者',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_agent_version` (`agent_id`, `version_no`),
INDEX `idx_agent_created_at` (`agent_id`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能体配置快照表';
@@ -697,3 +697,10 @@ databaseChangeLog:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202607011405.sql
- changeSet:
id: 202607071530
author: Codex
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202607071530.sql
@@ -25,9 +25,6 @@
<result column="memModelId" property="memModelId"/>
<result column="intentModelId" property="intentModelId"/>
<result column="functions" property="functions"
typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
<result column="chatHistoryConf" property="chatHistoryConf"/>
<result column="systemPrompt" property="systemPrompt"/>
<result column="summaryMemory" property="summaryMemory"/>
@@ -38,6 +35,10 @@
<result column="createdAt" property="createdAt"/>
<result column="updater" property="updater"/>
<result column="updatedAt" property="updatedAt"/>
<collection property="functions"
ofType="xiaozhi.modules.agent.entity.AgentPluginMapping"
column="{agentId=id}"
select="selectAgentFunctionsByAgentId"/>
</resultMap>
<select id="selectAgentInfoById" resultMap="AgentInfoMap">
@@ -58,19 +59,6 @@
a.tts_pitch AS ttsPitch,
a.mem_model_id AS memModelId,
a.intent_model_id AS intentModelId,
COALESCE(
(SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'id', m.id,
'agentId', m.agent_id,
'pluginId', m.plugin_id,
'paramInfo', m.param_info
)
)
FROM ai_agent_plugin_mapping m
WHERE m.agent_id = a.id),
JSON_ARRAY()
) AS functions,
a.chat_history_conf AS chatHistoryConf,
a.system_prompt AS systemPrompt,
a.summary_memory AS summaryMemory,
@@ -84,4 +72,13 @@
FROM ai_agent a
WHERE a.id = #{agentId}
</select>
</mapper>
<select id="selectAgentFunctionsByAgentId" resultType="xiaozhi.modules.agent.entity.AgentPluginMapping">
SELECT id,
agent_id AS agentId,
plugin_id AS pluginId,
param_info AS paramInfo
FROM ai_agent_plugin_mapping
WHERE agent_id = #{agentId}
</select>
</mapper>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xiaozhi.modules.agent.dao.AgentSnapshotDao">
<select id="selectMaxVersionNo" resultType="java.lang.Integer">
SELECT COALESCE(MAX(version_no), 0)
FROM ai_agent_snapshot
WHERE agent_id = #{agentId}
</select>
<select id="selectNextSnapshot" resultType="xiaozhi.modules.agent.entity.AgentSnapshotEntity">
SELECT id,
agent_id AS agentId,
user_id AS userId,
version_no AS versionNo,
snapshot_data AS snapshotData,
changed_fields AS changedFields,
source,
creator,
created_at AS createdAt
FROM ai_agent_snapshot
WHERE agent_id = #{agentId}
AND version_no &gt; #{versionNo}
ORDER BY version_no ASC
LIMIT 1
</select>
</mapper>
+46
View File
@@ -81,6 +81,52 @@ export default {
});
}).send();
},
// 获取智能体配置快照列表
getAgentSnapshots(agentId, params, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/agent/${agentId}/snapshots`)
.method('GET')
.data(params)
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.networkFail(() => {
RequestService.reAjaxFun(() => {
this.getAgentSnapshots(agentId, params, callback);
});
}).send();
},
// 获取智能体配置快照详情
getAgentSnapshot(agentId, snapshotId, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/agent/${agentId}/snapshots/${snapshotId}`)
.method('GET')
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.networkFail(() => {
RequestService.reAjaxFun(() => {
this.getAgentSnapshot(agentId, snapshotId, callback);
});
}).send();
},
// 恢复智能体配置快照
restoreAgentSnapshot(agentId, snapshotId, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/agent/${agentId}/snapshots/${snapshotId}/restore`)
.method('POST')
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.networkFail(() => {
RequestService.reAjaxFun(() => {
this.restoreAgentSnapshot(agentId, snapshotId, callback);
});
}).send();
},
// 新增方法:获取智能体模板
getAgentTemplate(callback) { // 移除templateName参数
RequestService.sendRequest()
File diff suppressed because it is too large Load Diff
+55 -1
View File
@@ -806,6 +806,7 @@ export default {
'roleConfig.addTag': 'Add New Tag',
'roleConfig.restartNotice': 'After saving the configuration, you need to restart the device for the new configuration to take effect.',
'roleConfig.saveConfig': 'Save Configuration',
'roleConfig.snapshotHistory': 'History',
'roleConfig.reset': 'Reset',
'roleConfig.agentName': 'Nickname',
'roleConfig.roleTemplate': 'Template',
@@ -863,6 +864,59 @@ export default {
'roleConfig.cannotPlayAudio': 'Cannot play audio',
'roleConfig.audioPlayError': 'Error occurred during audio playback',
'agentSnapshot.title': 'Version History',
'agentSnapshot.empty': 'No versions yet',
'agentSnapshot.version': 'Version',
'agentSnapshot.createdAt': 'Saved At',
'agentSnapshot.source': 'Source',
'agentSnapshot.changedFields': 'Changes',
'agentSnapshot.actions': 'Actions',
'agentSnapshot.view': 'View',
'agentSnapshot.restore': 'Restore',
'agentSnapshot.detailTitle': 'Change Details',
'agentSnapshot.restorePreviewTitle': 'Restore Preview',
'agentSnapshot.confirmRestore': 'Confirm Restore',
'agentSnapshot.before': 'Before',
'agentSnapshot.after': 'After',
'agentSnapshot.emptyValue': 'None',
'agentSnapshot.noChangedContent': 'No changes to display',
'agentSnapshot.restoreConfirm': 'Restore to version #{version}? The current configuration will be saved as a new version first.',
'agentSnapshot.restoreSuccess': 'Version restored',
'agentSnapshot.restoreFailed': 'Failed to restore version',
'agentSnapshot.fetchFailed': 'Failed to fetch versions',
'agentSnapshot.detailFailed': 'Failed to fetch snapshot details',
'agentSnapshot.currentVersion': 'Current Version',
'agentSnapshot.source.config': 'Config Save',
'agentSnapshot.source.current': 'Current Config',
'agentSnapshot.source.restore': 'Before Restore',
'agentSnapshot.field.restore': 'Restore',
'agentSnapshot.field.agentCode': 'Agent Code',
'agentSnapshot.field.agentName': 'Nickname',
'agentSnapshot.field.asrModelId': 'Speech Recognition',
'agentSnapshot.field.vadModelId': 'Voice Detect',
'agentSnapshot.field.llmModelId': 'Main Language Model',
'agentSnapshot.field.slmModelId': 'Small Language Model',
'agentSnapshot.field.vllmModelId': 'Vision Model',
'agentSnapshot.field.ttsModelId': 'Text-to-Speech',
'agentSnapshot.field.ttsVoiceId': 'Voice',
'agentSnapshot.field.ttsLanguage': 'Language',
'agentSnapshot.field.ttsVolume': 'Volume',
'agentSnapshot.field.ttsRate': 'Speed',
'agentSnapshot.field.ttsPitch': 'Pitch',
'agentSnapshot.field.memModelId': 'Memory Model',
'agentSnapshot.field.intentModelId': 'Intent Recognition',
'agentSnapshot.field.chatHistoryConf': 'Chat History',
'agentSnapshot.field.systemPrompt': 'Introduction',
'agentSnapshot.field.summaryMemory': 'Memory',
'agentSnapshot.field.langCode': 'Language Code',
'agentSnapshot.field.language': 'Interaction Language',
'agentSnapshot.field.sort': 'Sort',
'agentSnapshot.field.functions': 'Functions',
'agentSnapshot.field.contextProviders': 'Context Providers',
'agentSnapshot.field.correctWordFileIds': 'Replacement Words',
'agentSnapshot.field.tagNames': 'Tags',
'agentSnapshot.field.tagIds': 'Tag IDs',
// Form field Tooltip descriptions
'roleConfig.tooltip.agentName': 'Set the name of your AI agent for identification and recognition',
'roleConfig.tooltip.roleTemplate': 'Choose from preset role templates to quickly configure your agent\'s basic settings',
@@ -1554,4 +1608,4 @@ export default {
// Header navigation
'header.addressBook': 'Address Book',
}
}
+55 -1
View File
@@ -806,6 +806,7 @@ export default {
'roleConfig.addTag': '添加新标签',
'roleConfig.restartNotice': '保存配置后,需要重启设备,新的配置才会生效。',
'roleConfig.saveConfig': '保存配置',
'roleConfig.snapshotHistory': '历史版本',
'roleConfig.reset': '重置',
'roleConfig.agentName': '助手昵称',
'roleConfig.roleTemplate': '角色模版',
@@ -863,6 +864,59 @@ export default {
'roleConfig.cannotPlayAudio': '无法播放音频',
'roleConfig.audioPlayError': '播放音频过程出错',
'agentSnapshot.title': '历史版本',
'agentSnapshot.empty': '暂无历史版本',
'agentSnapshot.version': '版本',
'agentSnapshot.createdAt': '保存时间',
'agentSnapshot.source': '来源',
'agentSnapshot.changedFields': '变更内容',
'agentSnapshot.actions': '操作',
'agentSnapshot.view': '查看',
'agentSnapshot.restore': '恢复',
'agentSnapshot.detailTitle': '变更详情',
'agentSnapshot.restorePreviewTitle': '恢复确认',
'agentSnapshot.confirmRestore': '确认恢复',
'agentSnapshot.before': '变化前',
'agentSnapshot.after': '变化后',
'agentSnapshot.emptyValue': '无',
'agentSnapshot.noChangedContent': '暂无可展示的变更内容',
'agentSnapshot.restoreConfirm': '确定恢复到版本 #{version} 吗?当前配置会先自动保存为新的历史版本。',
'agentSnapshot.restoreSuccess': '已恢复历史版本',
'agentSnapshot.restoreFailed': '恢复历史版本失败',
'agentSnapshot.fetchFailed': '获取历史版本失败',
'agentSnapshot.detailFailed': '获取快照详情失败',
'agentSnapshot.currentVersion': '当前版本',
'agentSnapshot.source.config': '配置保存',
'agentSnapshot.source.current': '当前配置',
'agentSnapshot.source.restore': '恢复前',
'agentSnapshot.field.restore': '恢复操作',
'agentSnapshot.field.agentCode': '助手编码',
'agentSnapshot.field.agentName': '助手昵称',
'agentSnapshot.field.asrModelId': '语音识别',
'agentSnapshot.field.vadModelId': '语音活动检测',
'agentSnapshot.field.llmModelId': '主语言模型',
'agentSnapshot.field.slmModelId': '小参数模型',
'agentSnapshot.field.vllmModelId': '视觉大模型',
'agentSnapshot.field.ttsModelId': '语音合成',
'agentSnapshot.field.ttsVoiceId': '声音音色',
'agentSnapshot.field.ttsLanguage': '对话语言',
'agentSnapshot.field.ttsVolume': '音量',
'agentSnapshot.field.ttsRate': '语速',
'agentSnapshot.field.ttsPitch': '音调',
'agentSnapshot.field.memModelId': '记忆模式',
'agentSnapshot.field.intentModelId': '意图识别',
'agentSnapshot.field.chatHistoryConf': '聊天记录配置',
'agentSnapshot.field.systemPrompt': '角色介绍',
'agentSnapshot.field.summaryMemory': '记忆',
'agentSnapshot.field.langCode': '语言编码',
'agentSnapshot.field.language': '交互语种',
'agentSnapshot.field.sort': '排序',
'agentSnapshot.field.functions': '功能插件',
'agentSnapshot.field.contextProviders': '上下文源',
'agentSnapshot.field.correctWordFileIds': '替换词',
'agentSnapshot.field.tagNames': '标签',
'agentSnapshot.field.tagIds': '标签ID',
// 表单字段 Tooltip 提示说明
'roleConfig.tooltip.agentName': '设置智能体的名称,用于标识和识别您的AI助手',
'roleConfig.tooltip.roleTemplate': '从预设的角色模板中选择,快速配置智能体的基础设定',
@@ -1551,4 +1605,4 @@ export default {
'addressBookManagement.monthsAgo': '{months}个月前',
'addressBookManagement.yearsAgo': '{years}年前',
}
}
+1
View File
@@ -39,6 +39,7 @@ function formatDateTool(date, fmt) {
const o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'H+': date.getHours(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
+89 -10
View File
@@ -16,6 +16,9 @@
<img loading="lazy" src="@/assets/home/setting-user.png" alt="" />
</div>
<span class="header-title">{{ form.agentName }}</span>
<span v-if="currentVersionNo" class="current-version-tag">
当前版本 #{{ currentVersionNo }}
</span>
</div>
<div class="header-tags">
<el-tag
@@ -45,6 +48,9 @@
<img loading="lazy" src="@/assets/home/info.png" alt="" />
<span>{{ $t("roleConfig.restartNotice") }}</span>
</div>
<el-button class="history-btn" @click="showSnapshotDialog = true">
{{ $t("roleConfig.snapshotHistory") }}
</el-button>
<el-button type="primary" class="save-btn" @click="saveConfig">
{{ $t("roleConfig.saveConfig") }}
</el-button>
@@ -457,6 +463,13 @@
:checked-replacement-word-ids="checkedReplacementWordIds"
@save="handleTtsSettingsSave"
/>
<agent-snapshot-dialog
v-if="$route.query.agentId"
:visible.sync="showSnapshotDialog"
:agent-id="$route.query.agentId"
:current-version-no="currentVersionNo"
@restored="handleSnapshotRestored"
/>
<el-footer>
<version-footer />
</el-footer>
@@ -470,6 +483,7 @@ import RequestService from "@/apis/httpRequest";
import FunctionDialog from "@/components/FunctionDialog.vue";
import ContextProviderDialog from "@/components/ContextProviderDialog.vue";
import TtsAdvancedSettings from "@/components/TtsAdvancedSettings.vue";
import AgentSnapshotDialog from "@/components/AgentSnapshotDialog.vue";
import HeaderBar from "@/components/HeaderBar.vue";
import i18n from "@/i18n";
import featureManager from "@/utils/featureManager";
@@ -477,11 +491,12 @@ import VersionFooter from "@/components/VersionFooter.vue";
export default {
name: "RoleConfigPage",
components: { HeaderBar, FunctionDialog, ContextProviderDialog, TtsAdvancedSettings, VersionFooter },
components: { HeaderBar, FunctionDialog, ContextProviderDialog, TtsAdvancedSettings, AgentSnapshotDialog, VersionFooter },
data() {
return {
showContextProviderDialog: false,
showTtsAdvancedDialog: false,
showSnapshotDialog: false,
ttsSettings: {
volume: 0,
speed: 0,
@@ -529,6 +544,7 @@ export default {
voiceOptions: [],
voiceDetails: {}, // 保存完整的音色信息
showFunctionDialog: false,
currentVersionNo: null,
currentFunctions: [],
currentContextProviders: [],
allFunctions: [],
@@ -555,14 +571,26 @@ export default {
goToHome() {
this.$router.push("/home");
},
async saveConfig() {
try {
await this.handleSaveAgentTags(this.$route.query.agentId);
} catch (error) {
console.error('保存标签失败:', error);
return;
normalizeFunctionParams(params, fallback = {}) {
if (params === null || params === undefined || params === '') {
return { ...fallback };
}
if (typeof params === 'string') {
try {
const parsed = JSON.parse(params);
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
? parsed
: { ...fallback };
} catch (error) {
return { ...fallback };
}
}
if (typeof params === 'object' && !Array.isArray(params)) {
return { ...params };
}
return { ...fallback };
},
async saveConfig() {
const configData = {
agentCode: this.form.agentCode,
agentName: this.form.agentName,
@@ -585,11 +613,12 @@ export default {
functions: this.currentFunctions.map((item) => {
return {
pluginId: item.id,
paramInfo: item.params,
paramInfo: this.normalizeFunctionParams(item.params),
};
}),
contextProviders: this.currentContextProviders,
correctWordFileIds: this.checkedReplacementWordIds,
tagNames: this.dynamicTags.map(tag => tag.tagName),
};
// 只在用户设置了TTS参数时才传递(不为null/undefined
@@ -608,6 +637,7 @@ export default {
message: i18n.t("roleConfig.saveSuccess"),
showClose: true,
});
this.fetchCurrentVersion(this.$route.query.agentId);
} else {
this.$message.error({
message: data.msg || i18n.t("roleConfig.saveFailed"),
@@ -617,6 +647,32 @@ export default {
});
},
handleSnapshotRestored() {
const agentId = this.$route.query.agentId;
if (agentId) {
this.fetchAgentConfig(agentId);
this.getAgentTags(agentId);
this.fetchCurrentVersion(agentId);
}
},
fetchCurrentVersion(agentId) {
if (!agentId) {
this.currentVersionNo = null;
return;
}
Api.agent.getAgentSnapshots(
agentId,
{ page: "1", limit: "1" },
({ data }) => {
if (data.code === 0) {
const latest = data.data?.list?.[0];
const latestVersionNo = Number(latest?.versionNo) || 0;
this.currentVersionNo = latestVersionNo + 1;
}
}
);
},
resetConfig() {
this.$confirm(i18n.t("roleConfig.confirmReset"), i18n.t("message.info"), {
confirmButtonText: i18n.t("button.ok"),
@@ -754,7 +810,7 @@ export default {
id: mapping.pluginId,
name: meta.name,
// 后端如果还有 paramInfo 字段就用 mapping.paramInfo,否则用 meta.params 默认值
params: mapping.paramInfo || { ...meta.params },
params: this.normalizeFunctionParams(mapping.paramInfo, meta.params),
fieldsMeta: meta.fieldsMeta, // 保留以便对话框渲染 tooltip
};
});
@@ -1382,6 +1438,7 @@ export default {
this.fetchAgentConfig(agentId);
this.getAgentTags(agentId);
this.fetchAllFunctions();
this.fetchCurrentVersion(agentId);
}
this.fetchModelOptions();
this.fetchTemplates();
@@ -1507,6 +1564,18 @@ export default {
flex-shrink: 0;
}
.current-version-tag {
flex-shrink: 0;
padding: 3px 9px;
border: 1px solid #dfe7ff;
border-radius: 999px;
background: #f4f7ff;
color: #5778ff;
font-size: 12px;
font-weight: 500;
line-height: 1.5;
}
.more-tag {
cursor: pointer;
flex-shrink: 0;
@@ -1785,6 +1854,16 @@ export default {
font-size: 14px;
}
.header-actions .history-btn {
background: #ffffff;
color: #4d5b7c;
border: 1px solid #d8dce8;
border-radius: 18px;
padding: 8px 16px;
height: 32px;
font-size: 14px;
}
.header-actions .reset-btn {
background: #e6ebff;
color: #5778ff;