mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-29 18:23:59 +08:00
fix: 修复智能体快照基线与查看逻辑
This commit is contained in:
@@ -10,7 +10,11 @@ import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
|||||||
public interface AgentSnapshotDao extends BaseDao<AgentSnapshotEntity> {
|
public interface AgentSnapshotDao extends BaseDao<AgentSnapshotEntity> {
|
||||||
Integer selectMaxVersionNo(@Param("agentId") String agentId);
|
Integer selectMaxVersionNo(@Param("agentId") String agentId);
|
||||||
|
|
||||||
|
AgentSnapshotEntity selectLatestSnapshot(@Param("agentId") String agentId);
|
||||||
|
|
||||||
AgentSnapshotEntity selectNextSnapshot(@Param("agentId") String agentId, @Param("versionNo") Integer versionNo);
|
AgentSnapshotEntity selectNextSnapshot(@Param("agentId") String agentId, @Param("versionNo") Integer versionNo);
|
||||||
|
|
||||||
|
int insertWithNextVersion(@Param("snapshot") AgentSnapshotEntity snapshot);
|
||||||
|
|
||||||
int deleteOlderThanKeepLimit(@Param("agentId") String agentId, @Param("keepLimit") int keepLimit);
|
int deleteOlderThanKeepLimit(@Param("agentId") String agentId, @Param("keepLimit") int keepLimit);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -3,12 +3,11 @@ package xiaozhi.modules.agent.service;
|
|||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
import xiaozhi.modules.agent.dto.AgentSnapshotPageDTO;
|
import xiaozhi.modules.agent.dto.AgentSnapshotPageDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
|
||||||
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
||||||
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
||||||
|
|
||||||
public interface AgentSnapshotService extends BaseService<AgentSnapshotEntity> {
|
public interface AgentSnapshotService extends BaseService<AgentSnapshotEntity> {
|
||||||
void createSnapshot(String agentId, String source, AgentUpdateDTO pendingUpdate);
|
void createSnapshot(String agentId, String source);
|
||||||
|
|
||||||
PageData<AgentSnapshotVO> page(String agentId, AgentSnapshotPageDTO params);
|
PageData<AgentSnapshotVO> page(String agentId, AgentSnapshotPageDTO params);
|
||||||
|
|
||||||
|
|||||||
+13
-11
@@ -311,15 +311,14 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
|
|
||||||
// 锁定后查询现有实体和关联配置
|
// 锁定后查询现有实体和关联配置
|
||||||
AgentEntity existingEntity = this.getAgentById(agentId);
|
AgentEntity existingEntity = this.getAgentById(agentId);
|
||||||
|
if (createSnapshot && agentSnapshotService.getCurrentVersionNo(agentId) == 0) {
|
||||||
if (createSnapshot) {
|
agentSnapshotService.createSnapshot(agentId, "initial");
|
||||||
agentSnapshotService.createSnapshot(agentId, "config", dto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只更新提供的非空字段
|
// 只更新提供的非空字段
|
||||||
if (dto.getAgentName() != null) {
|
if (dto.getAgentName() != null) {
|
||||||
existingEntity.setAgentName(dto.getAgentName());
|
existingEntity.setAgentName(dto.getAgentName());
|
||||||
}
|
}
|
||||||
if (dto.getAgentCode() != null) {
|
if (dto.getAgentCode() != null) {
|
||||||
existingEntity.setAgentCode(dto.getAgentCode());
|
existingEntity.setAgentCode(dto.getAgentCode());
|
||||||
}
|
}
|
||||||
@@ -470,11 +469,14 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean b = validateLLMIntentParams(existingEntity.getLlmModelId(), existingEntity.getIntentModelId());
|
boolean b = validateLLMIntentParams(existingEntity.getLlmModelId(), existingEntity.getIntentModelId());
|
||||||
if (!b) {
|
if (!b) {
|
||||||
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
|
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
|
||||||
}
|
}
|
||||||
this.updateById(existingEntity);
|
this.updateById(existingEntity);
|
||||||
}
|
if (createSnapshot) {
|
||||||
|
agentSnapshotService.createSnapshot(agentId, "config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证大语言模型和意图识别的参数是否符合匹配
|
* 验证大语言模型和意图识别的参数是否符合匹配
|
||||||
|
|||||||
+27
-31
@@ -82,11 +82,20 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void createSnapshot(String agentId, String source, AgentUpdateDTO pendingUpdate) {
|
public void createSnapshot(String agentId, String source) {
|
||||||
|
lockAgent(agentId);
|
||||||
AgentInfoVO agent = getAgentInfo(agentId);
|
AgentInfoVO agent = getAgentInfo(agentId);
|
||||||
AgentSnapshotDataDTO snapshotData = buildSnapshotData(agent);
|
AgentSnapshotDataDTO snapshotData = buildSnapshotData(agent);
|
||||||
List<String> changedFields = getChangedFields(snapshotData, pendingUpdate);
|
AgentSnapshotEntity previousSnapshot = agentSnapshotDao.selectLatestSnapshot(agentId);
|
||||||
if (pendingUpdate != null && changedFields.isEmpty()) {
|
List<String> changedFields;
|
||||||
|
if (previousSnapshot == null) {
|
||||||
|
changedFields = List.of("initial");
|
||||||
|
} else {
|
||||||
|
AgentSnapshotDataDTO previousData = JsonUtils.parseObject(previousSnapshot.getSnapshotData(),
|
||||||
|
AgentSnapshotDataDTO.class);
|
||||||
|
changedFields = getChangedFields(previousData, snapshotData);
|
||||||
|
}
|
||||||
|
if (changedFields.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +105,6 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public PageData<AgentSnapshotVO> page(String agentId, AgentSnapshotPageDTO params) {
|
public PageData<AgentSnapshotVO> page(String agentId, AgentSnapshotPageDTO params) {
|
||||||
ensureInitialSnapshot(agentId);
|
|
||||||
AgentSnapshotPageDTO pageParams = params == null ? new AgentSnapshotPageDTO() : params;
|
AgentSnapshotPageDTO pageParams = params == null ? new AgentSnapshotPageDTO() : params;
|
||||||
Page<AgentSnapshotEntity> page = new Page<>(pageParams.pageOrDefault(), pageParams.limitOrDefault());
|
Page<AgentSnapshotEntity> page = new Page<>(pageParams.pageOrDefault(), pageParams.limitOrDefault());
|
||||||
page.addOrder(OrderItem.desc("version_no"));
|
page.addOrder(OrderItem.desc("version_no"));
|
||||||
@@ -134,8 +142,9 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
AgentSnapshotDataDTO currentData = buildSnapshotData(currentAgent);
|
AgentSnapshotDataDTO currentData = buildSnapshotData(currentAgent);
|
||||||
AgentSnapshotDataDTO restoreData = preserveCurrentSensitiveValues(data, currentData);
|
AgentSnapshotDataDTO restoreData = preserveCurrentSensitiveValues(data, currentData);
|
||||||
List<String> changedFields = getChangedFields(currentData, restoreData);
|
List<String> changedFields = getChangedFields(currentData, restoreData);
|
||||||
insertSnapshot(agentId, currentAgent.getUserId(), "restore", currentData, changedFields,
|
if (changedFields.isEmpty()) {
|
||||||
snapshot.getId(), snapshot.getVersionNo());
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
applyAgentFields(agent, restoreData);
|
applyAgentFields(agent, restoreData);
|
||||||
validateRestoreParams(agent);
|
validateRestoreParams(agent);
|
||||||
@@ -148,6 +157,8 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
restoreContextProviders(agentId, restoreData.getContextProviders());
|
restoreContextProviders(agentId, restoreData.getContextProviders());
|
||||||
correctWordFileService.saveAgentCorrectWords(agentId, nullToEmpty(restoreData.getCorrectWordFileIds()));
|
correctWordFileService.saveAgentCorrectWords(agentId, nullToEmpty(restoreData.getCorrectWordFileIds()));
|
||||||
restoreTags(agentId, restoreData);
|
restoreTags(agentId, restoreData);
|
||||||
|
insertSnapshot(agentId, currentAgent.getUserId(), "restore", restoreData, changedFields,
|
||||||
|
snapshot.getId(), snapshot.getVersionNo());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -164,7 +175,7 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
@Override
|
@Override
|
||||||
public Integer getCurrentVersionNo(String agentId) {
|
public Integer getCurrentVersionNo(String agentId) {
|
||||||
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
||||||
return (maxVersionNo == null ? 0 : maxVersionNo) + 1;
|
return maxVersionNo == null ? 0 : maxVersionNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -181,9 +192,9 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
private void insertSnapshot(String agentId, Long userId, String source, AgentSnapshotDataDTO snapshotData,
|
private void insertSnapshot(String agentId, Long userId, String source, AgentSnapshotDataDTO snapshotData,
|
||||||
List<String> changedFields, String restoreFromSnapshotId, Integer restoreFromVersionNo) {
|
List<String> changedFields, String restoreFromSnapshotId, Integer restoreFromVersionNo) {
|
||||||
AgentSnapshotEntity entity = new AgentSnapshotEntity();
|
AgentSnapshotEntity entity = new AgentSnapshotEntity();
|
||||||
|
entity.setId(UUID.randomUUID().toString().replace("-", ""));
|
||||||
entity.setAgentId(agentId);
|
entity.setAgentId(agentId);
|
||||||
entity.setUserId(userId);
|
entity.setUserId(userId);
|
||||||
entity.setVersionNo(allocateVersionNo(agentId));
|
|
||||||
entity.setSnapshotData(JsonUtils.toJsonString(redactSnapshotData(snapshotData)));
|
entity.setSnapshotData(JsonUtils.toJsonString(redactSnapshotData(snapshotData)));
|
||||||
entity.setChangedFields(JsonUtils.toJsonString(changedFields));
|
entity.setChangedFields(JsonUtils.toJsonString(changedFields));
|
||||||
entity.setSource(StringUtils.defaultIfBlank(source, "config"));
|
entity.setSource(StringUtils.defaultIfBlank(source, "config"));
|
||||||
@@ -191,39 +202,23 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
entity.setRestoreFromVersionNo(restoreFromVersionNo);
|
entity.setRestoreFromVersionNo(restoreFromVersionNo);
|
||||||
entity.setCreator(SecurityUser.getUserId());
|
entity.setCreator(SecurityUser.getUserId());
|
||||||
entity.setCreatedAt(new Date());
|
entity.setCreatedAt(new Date());
|
||||||
agentSnapshotDao.insert(entity);
|
int inserted = agentSnapshotDao.insertWithNextVersion(entity);
|
||||||
|
if (inserted != 1) {
|
||||||
|
throw new RenException("快照版本号生成失败");
|
||||||
|
}
|
||||||
pruneSnapshots(agentId);
|
pruneSnapshots(agentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureInitialSnapshot(String agentId) {
|
private void lockAgent(String agentId) {
|
||||||
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
if (agentDao.selectByIdForUpdate(agentId) == null) {
|
||||||
if (maxVersionNo != null && maxVersionNo > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AgentEntity agent = agentDao.selectByIdForUpdate(agentId);
|
|
||||||
if (agent == null) {
|
|
||||||
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
|
||||||
if (maxVersionNo != null && maxVersionNo > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AgentInfoVO agentInfo = getAgentInfo(agentId);
|
|
||||||
insertSnapshot(agentId, agentInfo.getUserId(), "initial", buildSnapshotData(agentInfo), List.of("initial"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pruneSnapshots(String agentId) {
|
private void pruneSnapshots(String agentId) {
|
||||||
agentSnapshotDao.deleteOlderThanKeepLimit(agentId, MAX_SNAPSHOTS_PER_AGENT);
|
agentSnapshotDao.deleteOlderThanKeepLimit(agentId, MAX_SNAPSHOTS_PER_AGENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer allocateVersionNo(String agentId) {
|
|
||||||
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
|
||||||
if (maxVersionNo == null || maxVersionNo < 0) {
|
|
||||||
throw new RenException("快照版本号生成失败");
|
|
||||||
}
|
|
||||||
return maxVersionNo + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AgentSnapshotEntity getSnapshotEntity(String agentId, String snapshotId) {
|
private AgentSnapshotEntity getSnapshotEntity(String agentId, String snapshotId) {
|
||||||
AgentSnapshotEntity entity = selectById(snapshotId);
|
AgentSnapshotEntity entity = selectById(snapshotId);
|
||||||
if (entity == null || !Objects.equals(agentId, entity.getAgentId())) {
|
if (entity == null || !Objects.equals(agentId, entity.getAgentId())) {
|
||||||
@@ -434,7 +429,8 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
}
|
}
|
||||||
map.forEach((key, value) -> {
|
map.forEach((key, value) -> {
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
result.put(String.valueOf(key), normalizeValue(value));
|
String keyText = String.valueOf(key);
|
||||||
|
result.put(keyText, isSensitiveKey(keyText) ? SECRET_PLACEHOLDER : normalizeValue(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
-- liquibase formatted sql
|
-- liquibase formatted sql
|
||||||
|
|
||||||
-- changeset xiaozhi:202607071530
|
-- changeset tykechen:202607071530
|
||||||
CREATE TABLE IF NOT EXISTS `ai_agent_snapshot` (
|
CREATE TABLE IF NOT EXISTS `ai_agent_snapshot` (
|
||||||
`id` VARCHAR(32) NOT NULL COMMENT '快照ID',
|
`id` VARCHAR(32) NOT NULL COMMENT '快照ID',
|
||||||
`agent_id` VARCHAR(32) NOT NULL COMMENT '智能体ID',
|
`agent_id` VARCHAR(32) NOT NULL COMMENT '智能体ID',
|
||||||
`user_id` BIGINT DEFAULT NULL COMMENT '所属用户ID',
|
`user_id` BIGINT DEFAULT NULL COMMENT '所属用户ID',
|
||||||
`version_no` INT UNSIGNED NOT NULL COMMENT '版本号',
|
`version_no` INT UNSIGNED NOT NULL COMMENT '版本号',
|
||||||
`snapshot_data` JSON NOT NULL COMMENT '快照数据',
|
`snapshot_data` JSON NOT NULL DEFAULT (JSON_OBJECT()) COMMENT '快照数据',
|
||||||
`changed_fields` JSON DEFAULT NULL COMMENT '变更字段',
|
`changed_fields` JSON DEFAULT NULL COMMENT '变更字段',
|
||||||
`source` VARCHAR(32) DEFAULT 'config' COMMENT '快照来源',
|
`source` VARCHAR(32) DEFAULT 'config' COMMENT '快照来源',
|
||||||
|
`restore_from_snapshot_id` VARCHAR(32) DEFAULT NULL COMMENT '恢复来源快照ID',
|
||||||
|
`restore_from_version_no` INT UNSIGNED DEFAULT NULL COMMENT '恢复来源版本号',
|
||||||
`creator` BIGINT DEFAULT NULL COMMENT '创建者',
|
`creator` BIGINT DEFAULT NULL COMMENT '创建者',
|
||||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uk_agent_version` (`agent_id`, `version_no`),
|
UNIQUE KEY `uk_agent_version` (`agent_id`, `version_no`),
|
||||||
INDEX `idx_agent_created_at` (`agent_id`, `created_at`)
|
INDEX `idx_agent_created_at` (`agent_id`, `created_at`),
|
||||||
|
INDEX `idx_snapshot_user_created_at` (`user_id`, `created_at`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能体配置快照表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能体配置快照表';
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
-- liquibase formatted sql
|
|
||||||
|
|
||||||
-- changeset codex:202607081150
|
|
||||||
ALTER TABLE `ai_agent_snapshot`
|
|
||||||
ADD COLUMN `restore_from_snapshot_id` VARCHAR(32) DEFAULT NULL COMMENT '恢复来源快照ID' AFTER `source`,
|
|
||||||
ADD COLUMN `restore_from_version_no` INT UNSIGNED DEFAULT NULL COMMENT '恢复来源版本号' AFTER `restore_from_snapshot_id`,
|
|
||||||
ADD INDEX `idx_snapshot_user_created_at` (`user_id`, `created_at`);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
-- liquibase formatted sql
|
|
||||||
|
|
||||||
-- changeset codex:202607081230
|
|
||||||
ALTER TABLE `ai_agent_snapshot`
|
|
||||||
ALTER COLUMN `snapshot_data` SET DEFAULT (JSON_OBJECT());
|
|
||||||
@@ -699,22 +699,8 @@ databaseChangeLog:
|
|||||||
path: classpath:db/changelog/202607011405.sql
|
path: classpath:db/changelog/202607011405.sql
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 202607071530
|
id: 202607071530
|
||||||
author: Codex
|
author: tykechen
|
||||||
changes:
|
changes:
|
||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202607071530.sql
|
path: classpath:db/changelog/202607071530.sql
|
||||||
- changeSet:
|
|
||||||
id: 202607081150
|
|
||||||
author: Codex
|
|
||||||
changes:
|
|
||||||
- sqlFile:
|
|
||||||
encoding: utf8
|
|
||||||
path: classpath:db/changelog/202607081150.sql
|
|
||||||
- changeSet:
|
|
||||||
id: 202607081230
|
|
||||||
author: Codex
|
|
||||||
changes:
|
|
||||||
- sqlFile:
|
|
||||||
encoding: utf8
|
|
||||||
path: classpath:db/changelog/202607081230.sql
|
|
||||||
|
|||||||
@@ -8,6 +8,24 @@
|
|||||||
WHERE agent_id = #{agentId}
|
WHERE agent_id = #{agentId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectLatestSnapshot" 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,
|
||||||
|
restore_from_snapshot_id AS restoreFromSnapshotId,
|
||||||
|
restore_from_version_no AS restoreFromVersionNo,
|
||||||
|
creator,
|
||||||
|
created_at AS createdAt
|
||||||
|
FROM ai_agent_snapshot
|
||||||
|
WHERE agent_id = #{agentId}
|
||||||
|
ORDER BY version_no DESC
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectNextSnapshot" resultType="xiaozhi.modules.agent.entity.AgentSnapshotEntity">
|
<select id="selectNextSnapshot" resultType="xiaozhi.modules.agent.entity.AgentSnapshotEntity">
|
||||||
SELECT id,
|
SELECT id,
|
||||||
agent_id AS agentId,
|
agent_id AS agentId,
|
||||||
@@ -27,6 +45,35 @@
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertWithNextVersion">
|
||||||
|
INSERT INTO ai_agent_snapshot (
|
||||||
|
id,
|
||||||
|
agent_id,
|
||||||
|
user_id,
|
||||||
|
version_no,
|
||||||
|
snapshot_data,
|
||||||
|
changed_fields,
|
||||||
|
source,
|
||||||
|
restore_from_snapshot_id,
|
||||||
|
restore_from_version_no,
|
||||||
|
creator,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
SELECT #{snapshot.id},
|
||||||
|
#{snapshot.agentId},
|
||||||
|
#{snapshot.userId},
|
||||||
|
COALESCE(MAX(version_no), 0) + 1,
|
||||||
|
#{snapshot.snapshotData},
|
||||||
|
#{snapshot.changedFields},
|
||||||
|
#{snapshot.source},
|
||||||
|
#{snapshot.restoreFromSnapshotId},
|
||||||
|
#{snapshot.restoreFromVersionNo},
|
||||||
|
#{snapshot.creator},
|
||||||
|
#{snapshot.createdAt}
|
||||||
|
FROM ai_agent_snapshot
|
||||||
|
WHERE agent_id = #{snapshot.agentId}
|
||||||
|
</insert>
|
||||||
|
|
||||||
<delete id="deleteOlderThanKeepLimit">
|
<delete id="deleteOlderThanKeepLimit">
|
||||||
DELETE FROM ai_agent_snapshot
|
DELETE FROM ai_agent_snapshot
|
||||||
WHERE agent_id = #{agentId}
|
WHERE agent_id = #{agentId}
|
||||||
|
|||||||
+94
-24
@@ -6,10 +6,11 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -25,6 +26,8 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import xiaozhi.common.utils.JsonUtils;
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
@@ -41,6 +44,7 @@ import xiaozhi.modules.agent.entity.AgentEntity;
|
|||||||
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
||||||
import xiaozhi.modules.agent.entity.AgentTagEntity;
|
import xiaozhi.modules.agent.entity.AgentTagEntity;
|
||||||
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentSnapshotService;
|
||||||
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
||||||
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
import xiaozhi.modules.correctword.service.CorrectWordFileService;
|
import xiaozhi.modules.correctword.service.CorrectWordFileService;
|
||||||
@@ -178,6 +182,40 @@ class AgentSnapshotServiceImplTest {
|
|||||||
assertFalse(roleConfig.contains("this.handleSaveAgentTags(agentId, tagNames)"));
|
assertFalse(roleConfig.contains("this.handleSaveAgentTags(agentId, tagNames)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void firstAgentSaveKeepsInitialBaselineBeforePersistingNewState() {
|
||||||
|
AgentDao agentDao = mock(AgentDao.class);
|
||||||
|
AgentContextProviderService contextProviderService = mock(AgentContextProviderService.class);
|
||||||
|
CorrectWordFileService correctWordFileService = mock(CorrectWordFileService.class);
|
||||||
|
AgentSnapshotService snapshotService = mock(AgentSnapshotService.class);
|
||||||
|
AgentServiceImpl service = new AgentServiceImpl(agentDao, null, null, null, null, null, null, null,
|
||||||
|
null, null, contextProviderService, null, correctWordFileService, snapshotService);
|
||||||
|
ReflectionTestUtils.setField(service, "baseDao", agentDao);
|
||||||
|
|
||||||
|
String agentId = "agent-id";
|
||||||
|
AgentEntity lockedAgent = new AgentEntity();
|
||||||
|
lockedAgent.setId(agentId);
|
||||||
|
AgentInfoVO currentAgent = new AgentInfoVO();
|
||||||
|
currentAgent.setId(agentId);
|
||||||
|
currentAgent.setAgentName("old-name");
|
||||||
|
AgentUpdateDTO update = new AgentUpdateDTO();
|
||||||
|
update.setAgentName("new-name");
|
||||||
|
|
||||||
|
when(agentDao.selectByIdForUpdate(agentId)).thenReturn(lockedAgent);
|
||||||
|
when(agentDao.selectAgentInfoById(agentId)).thenReturn(currentAgent);
|
||||||
|
when(snapshotService.getCurrentVersionNo(agentId)).thenReturn(0);
|
||||||
|
when(contextProviderService.getByAgentId(agentId)).thenReturn(null);
|
||||||
|
when(correctWordFileService.getAgentCorrectWordFileIds(agentId)).thenReturn(List.of());
|
||||||
|
when(agentDao.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
service.updateAgentById(agentId, update);
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(agentDao, snapshotService);
|
||||||
|
inOrder.verify(snapshotService).createSnapshot(agentId, "initial");
|
||||||
|
inOrder.verify(agentDao).updateById(argThat(agent -> "new-name".equals(agent.getAgentName())));
|
||||||
|
inOrder.verify(snapshotService).createSnapshot(agentId, "config");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void agentSnapshotFieldAppliesRestorableAgentFields() {
|
void agentSnapshotFieldAppliesRestorableAgentFields() {
|
||||||
AgentSnapshotDataDTO data = new AgentSnapshotDataDTO();
|
AgentSnapshotDataDTO data = new AgentSnapshotDataDTO();
|
||||||
@@ -218,40 +256,38 @@ class AgentSnapshotServiceImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void versionAllocationDoesNotDependOnSequenceTable() throws Exception {
|
void snapshotInsertAllocatesVersionInSingleSqlStatement() throws Exception {
|
||||||
String xml = Files.readString(Path.of("src/main/resources/mapper/agent/AgentSnapshotDao.xml"));
|
String xml = normalizeWhitespace(Files.readString(Path.of("src/main/resources/mapper/agent/AgentSnapshotDao.xml")));
|
||||||
String dao = Files.readString(Path.of("src/main/java/xiaozhi/modules/agent/dao/AgentSnapshotDao.java"));
|
String dao = Files.readString(Path.of("src/main/java/xiaozhi/modules/agent/dao/AgentSnapshotDao.java"));
|
||||||
String createMigration = Files.readString(Path.of("src/main/resources/db/changelog/202607071530.sql"));
|
|
||||||
String master = Files.readString(Path.of("src/main/resources/db/changelog/db.changelog-master.yaml"));
|
|
||||||
|
|
||||||
assertFalse(xml.contains("ai_agent_snapshot_sequence"));
|
assertTrue(dao.contains("insertWithNextVersion"));
|
||||||
|
assertTrue(xml.contains("<insert id=\"insertWithNextVersion\">"));
|
||||||
|
assertTrue(xml.contains("INSERT INTO ai_agent_snapshot"));
|
||||||
|
assertTrue(xml.contains("COALESCE(MAX(version_no), 0) + 1"));
|
||||||
assertFalse(xml.contains("LAST_INSERT_ID"));
|
assertFalse(xml.contains("LAST_INSERT_ID"));
|
||||||
assertFalse(dao.contains("allocateVersionNo"));
|
assertFalse(xml.contains("ai_agent_snapshot_sequence"));
|
||||||
assertFalse(dao.contains("selectLastInsertId"));
|
|
||||||
assertFalse(createMigration.contains("ai_agent_snapshot_sequence"));
|
|
||||||
assertFalse(master.contains("202607081430.sql"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void snapshotMigrationAddsRestoreTraceAndUserIndex() throws Exception {
|
void snapshotMigrationIsMergedIntoSingleChangeLog() throws Exception {
|
||||||
String sql = Files.readString(Path.of("src/main/resources/db/changelog/202607081150.sql"));
|
String sql = Files.readString(Path.of("src/main/resources/db/changelog/202607071530.sql"));
|
||||||
String defaultSql = Files.readString(Path.of("src/main/resources/db/changelog/202607081230.sql"));
|
|
||||||
String master = Files.readString(Path.of("src/main/resources/db/changelog/db.changelog-master.yaml"));
|
String master = Files.readString(Path.of("src/main/resources/db/changelog/db.changelog-master.yaml"));
|
||||||
|
|
||||||
assertTrue(sql.contains("restore_from_snapshot_id"));
|
assertTrue(sql.contains("restore_from_snapshot_id"));
|
||||||
assertTrue(sql.contains("restore_from_version_no"));
|
assertTrue(sql.contains("restore_from_version_no"));
|
||||||
assertTrue(sql.contains("idx_snapshot_user_created_at"));
|
assertTrue(sql.contains("idx_snapshot_user_created_at"));
|
||||||
assertTrue(master.contains("202607081150.sql"));
|
assertTrue(sql.contains("DEFAULT (JSON_OBJECT())"));
|
||||||
assertTrue(defaultSql.contains("DEFAULT (JSON_OBJECT())"));
|
assertTrue(master.contains("202607071530.sql"));
|
||||||
assertTrue(master.contains("202607081230.sql"));
|
assertFalse(master.contains("202607081150.sql"));
|
||||||
|
assertFalse(master.contains("202607081230.sql"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void selectNextSnapshotLoadsRestoreTraceColumns() throws Exception {
|
void selectNextSnapshotLoadsRestoreTraceColumns() throws Exception {
|
||||||
String xml = Files.readString(Path.of("src/main/resources/mapper/agent/AgentSnapshotDao.xml"));
|
String xml = normalizeWhitespace(Files.readString(Path.of("src/main/resources/mapper/agent/AgentSnapshotDao.xml")));
|
||||||
|
|
||||||
assertTrue(xml.contains("restore_from_snapshot_id AS restoreFromSnapshotId"));
|
assertTrue(xml.contains("restore_from_snapshot_id AS restoreFromSnapshotId"));
|
||||||
assertTrue(xml.contains("restore_from_version_no AS restoreFromVersionNo"));
|
assertTrue(xml.contains("restore_from_version_no AS restoreFromVersionNo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -296,7 +332,35 @@ class AgentSnapshotServiceImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void pageCreatesInitialSnapshotWhenAgentHasNoHistory() {
|
void pageDoesNotCreateInitialSnapshotWhenAgentHasNoHistory() {
|
||||||
|
AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class);
|
||||||
|
AgentDao agentDao = mock(AgentDao.class);
|
||||||
|
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(snapshotDao, agentDao, null, null, null,
|
||||||
|
null, null, null, null, null);
|
||||||
|
|
||||||
|
when(snapshotDao.selectPage(any(), any())).thenReturn(new Page<AgentSnapshotEntity>(1, 10));
|
||||||
|
|
||||||
|
service.page("agent-id", new AgentSnapshotPageDTO());
|
||||||
|
|
||||||
|
verify(agentDao, never()).selectByIdForUpdate(any());
|
||||||
|
verify(snapshotDao, never()).selectMaxVersionNo(any());
|
||||||
|
verify(snapshotDao, never()).insertWithNextVersion(any());
|
||||||
|
verify(snapshotDao, never()).deleteOlderThanKeepLimit(any(), anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getCurrentVersionNoReturnsPersistedMaxVersion() {
|
||||||
|
AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class);
|
||||||
|
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(snapshotDao, null, null, null, null,
|
||||||
|
null, null, null, null, null);
|
||||||
|
|
||||||
|
when(snapshotDao.selectMaxVersionNo("agent-id")).thenReturn(7);
|
||||||
|
|
||||||
|
assertEquals(7, service.getCurrentVersionNo("agent-id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createSnapshotStoresCurrentStateAsInitialVersionWhenHistoryIsEmpty() {
|
||||||
AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class);
|
AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class);
|
||||||
AgentDao agentDao = mock(AgentDao.class);
|
AgentDao agentDao = mock(AgentDao.class);
|
||||||
AgentTagDao tagDao = mock(AgentTagDao.class);
|
AgentTagDao tagDao = mock(AgentTagDao.class);
|
||||||
@@ -311,17 +375,19 @@ class AgentSnapshotServiceImplTest {
|
|||||||
agentInfo.setId("agent-id");
|
agentInfo.setId("agent-id");
|
||||||
agentInfo.setUserId(7L);
|
agentInfo.setUserId(7L);
|
||||||
agentInfo.setAgentName("agent");
|
agentInfo.setAgentName("agent");
|
||||||
when(snapshotDao.selectMaxVersionNo("agent-id")).thenReturn(0, 0, 0);
|
|
||||||
when(agentDao.selectByIdForUpdate("agent-id")).thenReturn(lockedAgent);
|
when(agentDao.selectByIdForUpdate("agent-id")).thenReturn(lockedAgent);
|
||||||
when(agentDao.selectAgentInfoById("agent-id")).thenReturn(agentInfo);
|
when(agentDao.selectAgentInfoById("agent-id")).thenReturn(agentInfo);
|
||||||
|
when(snapshotDao.selectLatestSnapshot("agent-id")).thenReturn(null);
|
||||||
|
when(snapshotDao.insertWithNextVersion(any())).thenReturn(1);
|
||||||
when(correctWordFileService.getAgentCorrectWordFileIds("agent-id")).thenReturn(List.of());
|
when(correctWordFileService.getAgentCorrectWordFileIds("agent-id")).thenReturn(List.of());
|
||||||
|
when(contextProviderService.getByAgentId("agent-id")).thenReturn(null);
|
||||||
when(tagDao.selectByAgentId("agent-id")).thenReturn(List.of());
|
when(tagDao.selectByAgentId("agent-id")).thenReturn(List.of());
|
||||||
when(snapshotDao.selectPage(any(), any())).thenReturn(new Page<AgentSnapshotEntity>(1, 10));
|
|
||||||
|
|
||||||
service.page("agent-id", new AgentSnapshotPageDTO());
|
service.createSnapshot("agent-id", "config");
|
||||||
|
|
||||||
verify(snapshotDao, times(3)).selectMaxVersionNo("agent-id");
|
verify(snapshotDao).insertWithNextVersion(argThat(snapshot -> "config".equals(snapshot.getSource())
|
||||||
verify(snapshotDao).insert(argThat(snapshot -> Integer.valueOf(1).equals(snapshot.getVersionNo())));
|
&& snapshot.getVersionNo() == null
|
||||||
|
&& snapshot.getChangedFields().contains("initial")));
|
||||||
verify(snapshotDao).deleteOlderThanKeepLimit("agent-id", 100);
|
verify(snapshotDao).deleteOlderThanKeepLimit("agent-id", 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,4 +443,8 @@ class AgentSnapshotServiceImplTest {
|
|||||||
data.setContextProviders(List.of(provider));
|
data.setContextProviders(List.of(provider));
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String normalizeWhitespace(String value) {
|
||||||
|
return value.replaceAll("\\s+", " ").trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<div class="version-cell">
|
<div class="version-cell">
|
||||||
<span>#{{ scope.row.versionNo }}</span>
|
<span>#{{ scope.row.versionNo }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="scope.row.isCurrentVersion"
|
v-if="scope.row.isLatestSnapshot"
|
||||||
class="latest-version-icon"
|
class="latest-version-icon"
|
||||||
:title="$t('agentSnapshot.currentVersion')"
|
:title="$t('agentSnapshot.currentVersion')"
|
||||||
></span>
|
></span>
|
||||||
@@ -130,7 +130,59 @@
|
|||||||
class="diff-item"
|
class="diff-item"
|
||||||
>
|
>
|
||||||
<div class="diff-field">{{ item.label }}</div>
|
<div class="diff-field">{{ item.label }}</div>
|
||||||
<div v-if="item.displayType === 'functions'" class="function-change-view">
|
<div v-if="item.single" class="diff-values is-single" :class="{ 'is-complex': item.complex }">
|
||||||
|
<div class="diff-pane diff-after">
|
||||||
|
<div class="diff-pane-title">{{ item.valueTitle }}</div>
|
||||||
|
<div v-if="item.displayType === 'functions'" class="function-change-view is-single">
|
||||||
|
<div v-if="item.functionStates.length" class="function-toggle-list">
|
||||||
|
<div
|
||||||
|
v-for="(functionState, stateIndex) in item.functionStates"
|
||||||
|
:key="`function-state-${item.field}-${functionState.pluginId}-${stateIndex}`"
|
||||||
|
class="function-toggle-card is-single"
|
||||||
|
>
|
||||||
|
<div class="function-toggle-head">
|
||||||
|
<span class="function-dot"></span>
|
||||||
|
<div class="function-change-title-wrap">
|
||||||
|
<div class="function-change-title-row">
|
||||||
|
<span class="function-change-name">{{ functionState.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="function-state-pane is-after is-enabled"
|
||||||
|
>
|
||||||
|
<div class="function-state-title">
|
||||||
|
<span>{{ item.valueTitle }}</span>
|
||||||
|
<span class="function-state-badge">{{ functionState.status }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="functionState.params.length" class="function-state-params">
|
||||||
|
<div
|
||||||
|
v-for="param in functionState.params"
|
||||||
|
:key="`${functionState.pluginId}-single-${param.key}`"
|
||||||
|
class="function-param-row"
|
||||||
|
>
|
||||||
|
<span class="param-key">{{ param.label }}</span>
|
||||||
|
<span class="param-value">{{ param.value }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="function-state-empty">{{ functionState.note }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="value-empty">{{ $t('agentSnapshot.emptyValue') }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="diff-value" :class="valueClass(item)">
|
||||||
|
<div
|
||||||
|
v-if="item.displayType === 'markdown'"
|
||||||
|
class="markdown-body"
|
||||||
|
v-html="renderMarkdownValue(item.afterValue)"
|
||||||
|
></div>
|
||||||
|
<pre v-else class="diff-text">{{ item.afterText }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="item.displayType === 'functions'" class="function-change-view">
|
||||||
<div v-if="item.functionChanges.length" class="function-toggle-list">
|
<div v-if="item.functionChanges.length" class="function-toggle-list">
|
||||||
<div
|
<div
|
||||||
v-for="(change, changeIndex) in item.functionChanges"
|
v-for="(change, changeIndex) in item.functionChanges"
|
||||||
@@ -453,6 +505,7 @@ const CHAT_HISTORY_CONF_LABEL_KEYS = {
|
|||||||
1: "agentSnapshot.chatHistoryConf.text",
|
1: "agentSnapshot.chatHistoryConf.text",
|
||||||
2: "agentSnapshot.chatHistoryConf.textVoice"
|
2: "agentSnapshot.chatHistoryConf.textVoice"
|
||||||
};
|
};
|
||||||
|
const SNAPSHOT_SECRET_REDACTED = "__SNAPSHOT_SECRET_REDACTED__";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AgentSnapshotDialog",
|
name: "AgentSnapshotDialog",
|
||||||
@@ -528,7 +581,18 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
snapshotDiffs() {
|
snapshotDiffs() {
|
||||||
if (!this.currentSnapshot || !this.hasAfterSnapshotData(this.currentSnapshot)) {
|
if (!this.currentSnapshot) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentSnapshot.isSingleSnapshot) {
|
||||||
|
return this.buildSingleConfigItems(
|
||||||
|
this.currentSnapshot.snapshotData || {},
|
||||||
|
this.currentSnapshot.fieldOrder || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.hasAfterSnapshotData(this.currentSnapshot)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,7 +625,9 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
restoreWillClearChatHistory() {
|
restoreWillClearChatHistory() {
|
||||||
return this.restorePreviewSnapshot?.afterSnapshotData?.memModelId === "Memory_nomem";
|
const beforeMemModelId = this.restorePreviewSnapshot?.beforeSnapshotData?.memModelId;
|
||||||
|
const afterMemModelId = this.restorePreviewSnapshot?.afterSnapshotData?.memModelId;
|
||||||
|
return beforeMemModelId !== "Memory_nomem" && afterMemModelId === "Memory_nomem";
|
||||||
},
|
},
|
||||||
restoreTargetVersion() {
|
restoreTargetVersion() {
|
||||||
return this.restorePreviewSnapshot?.versionNo || this.restorePreviewRow?.versionNo || "";
|
return this.restorePreviewSnapshot?.versionNo || this.restorePreviewRow?.versionNo || "";
|
||||||
@@ -579,6 +645,28 @@ export default {
|
|||||||
this.cancelPendingSnapshotRequests();
|
this.cancelPendingSnapshotRequests();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
buildSingleConfigItems(snapshotData = {}, fieldOrder = []) {
|
||||||
|
const fields = Array.from(new Set(this.snapshotFieldOrder(fieldOrder, {}, snapshotData)));
|
||||||
|
return fields.map((field) => {
|
||||||
|
const value = this.getFieldValue(snapshotData, field);
|
||||||
|
const displayType = this.displayType(field);
|
||||||
|
return {
|
||||||
|
field,
|
||||||
|
label: this.fieldLabel(field),
|
||||||
|
beforeValue: null,
|
||||||
|
afterValue: value,
|
||||||
|
beforeText: "",
|
||||||
|
afterText: this.formatDisplayValue(field, value, snapshotData),
|
||||||
|
displayType,
|
||||||
|
single: true,
|
||||||
|
valueTitle: this.$t("agentSnapshot.configValue"),
|
||||||
|
functionStates: displayType === "functions"
|
||||||
|
? this.buildSingleFunctionStates(value)
|
||||||
|
: [],
|
||||||
|
complex: this.isComplexValue(value)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
buildDiffs(beforeData, afterData, changedFields, options = {}) {
|
buildDiffs(beforeData, afterData, changedFields, options = {}) {
|
||||||
const fields = this.resolveDiffFields(beforeData, afterData, changedFields, options.fieldOrder);
|
const fields = this.resolveDiffFields(beforeData, afterData, changedFields, options.fieldOrder);
|
||||||
return fields.map((field) => {
|
return fields.map((field) => {
|
||||||
@@ -625,22 +713,19 @@ export default {
|
|||||||
this.deletingSnapshotId = null;
|
this.deletingSnapshotId = null;
|
||||||
},
|
},
|
||||||
snapshotRowKey(row) {
|
snapshotRowKey(row) {
|
||||||
if (row?.isCurrentVersion) {
|
|
||||||
return "current-version";
|
|
||||||
}
|
|
||||||
return row?.id || `${row?.versionNo || ""}-${row?.createdAt || ""}`;
|
return row?.id || `${row?.versionNo || ""}-${row?.createdAt || ""}`;
|
||||||
},
|
},
|
||||||
snapshotRowClassName({ row }) {
|
snapshotRowClassName({ row }) {
|
||||||
return row?.isCurrentVersion ? "current-version-row" : "";
|
return row?.isLatestSnapshot ? "current-version-row" : "";
|
||||||
},
|
},
|
||||||
canViewSnapshot(row) {
|
canViewSnapshot(row) {
|
||||||
return !!row && (row.isCurrentVersion || row.previousSnapshotId || row.previousVersionNo);
|
return !!row?.id;
|
||||||
},
|
},
|
||||||
canRestoreSnapshot(row) {
|
canRestoreSnapshot(row) {
|
||||||
return !!row && !row.isCurrentVersion;
|
return !!row && !row.isLatestSnapshot;
|
||||||
},
|
},
|
||||||
canDeleteSnapshot(row) {
|
canDeleteSnapshot(row) {
|
||||||
return !!row && !row.isCurrentVersion && !row.isLatestSnapshot;
|
return !!row && !row.isLatestSnapshot;
|
||||||
},
|
},
|
||||||
fetchSnapshots(options = {}) {
|
fetchSnapshots(options = {}) {
|
||||||
if (!this.agentId) {
|
if (!this.agentId) {
|
||||||
@@ -673,24 +758,12 @@ export default {
|
|||||||
if (!this.historyAnchorVersionNo && historyRows[0]?.versionNo) {
|
if (!this.historyAnchorVersionNo && historyRows[0]?.versionNo) {
|
||||||
this.historyAnchorVersionNo = Number(historyRows[0].versionNo);
|
this.historyAnchorVersionNo = Number(historyRows[0].versionNo);
|
||||||
}
|
}
|
||||||
const currentRow = this.buildCurrentVersionRow(historyRows[0]);
|
|
||||||
const historyOffset = currentRow ? 1 : 0;
|
|
||||||
this.historyTotal = data.data?.total || 0;
|
this.historyTotal = data.data?.total || 0;
|
||||||
this.total = this.historyTotal + historyOffset;
|
this.total = this.historyTotal;
|
||||||
if (this.page === 1) {
|
this.snapshots = this.applyPreviousChangedFields(
|
||||||
const firstPageRows = historyRows.slice(0, currentRow ? this.limit - 1 : this.limit);
|
historyRows.slice(displayStart, displayEnd),
|
||||||
this.snapshots = [
|
historyRows
|
||||||
...(currentRow ? [currentRow] : []),
|
);
|
||||||
...this.applyPreviousChangedFields(firstPageRows, historyRows)
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
const historyStart = Math.max(displayStart - historyOffset, 0);
|
|
||||||
const historyEnd = Math.max(displayEnd - historyOffset, 0);
|
|
||||||
this.snapshots = this.applyPreviousChangedFields(
|
|
||||||
historyRows.slice(historyStart, historyEnd),
|
|
||||||
historyRows
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(data.msg || this.$t("agentSnapshot.fetchFailed"));
|
this.$message.error(data.msg || this.$t("agentSnapshot.fetchFailed"));
|
||||||
}
|
}
|
||||||
@@ -827,8 +900,7 @@ export default {
|
|||||||
this.deletingSnapshotId = null;
|
this.deletingSnapshotId = null;
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
this.$message.success(this.$t("agentSnapshot.deleteSuccess"));
|
this.$message.success(this.$t("agentSnapshot.deleteSuccess"));
|
||||||
const savedRowsOnPage = this.snapshots.filter((item) => !item.isCurrentVersion);
|
if (this.snapshots.length <= 1 && this.page > 1) {
|
||||||
if (savedRowsOnPage.length <= 1 && this.page > 1) {
|
|
||||||
this.page -= 1;
|
this.page -= 1;
|
||||||
}
|
}
|
||||||
this.fetchSnapshots();
|
this.fetchSnapshots();
|
||||||
@@ -851,9 +923,9 @@ export default {
|
|||||||
return propVersionNo;
|
return propVersionNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentRow = this.snapshots.find((item) => item.isCurrentVersion);
|
const latestRow = this.snapshots.find((item) => item.isLatestSnapshot) || this.snapshots[0];
|
||||||
if (currentRow?.versionNo) {
|
if (latestRow?.versionNo) {
|
||||||
return currentRow.versionNo;
|
return latestRow.versionNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -907,10 +979,10 @@ export default {
|
|||||||
applyPreviousChangedFields(rows, sourceRows) {
|
applyPreviousChangedFields(rows, sourceRows) {
|
||||||
return rows.map((row) => {
|
return rows.map((row) => {
|
||||||
const versionNo = Number(row.versionNo);
|
const versionNo = Number(row.versionNo);
|
||||||
if (!Number.isFinite(versionNo) || versionNo <= 1) {
|
if (!Number.isFinite(versionNo)) {
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
changedFields: []
|
changedFields: row.changedFields || []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const previousRow = this.findPreviousSnapshotRow(row, sourceRows);
|
const previousRow = this.findPreviousSnapshotRow(row, sourceRows);
|
||||||
@@ -918,7 +990,7 @@ export default {
|
|||||||
...row,
|
...row,
|
||||||
previousSnapshotId: previousRow?.id || null,
|
previousSnapshotId: previousRow?.id || null,
|
||||||
previousVersionNo: previousRow?.versionNo || null,
|
previousVersionNo: previousRow?.versionNo || null,
|
||||||
changedFields: previousRow?.changedFields || []
|
changedFields: row.changedFields || []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -928,78 +1000,83 @@ export default {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (sourceRows || []).find((item) => {
|
return (sourceRows || []).find((item) => {
|
||||||
return !item.isCurrentVersion && Number(item.versionNo) < versionNo;
|
return Number(item.versionNo) < versionNo;
|
||||||
}) || null;
|
}) || null;
|
||||||
},
|
},
|
||||||
buildCurrentVersionRow(latestSnapshot) {
|
|
||||||
const latestVersionNo = Number(latestSnapshot?.versionNo) || 0;
|
|
||||||
const propVersionNo = Number(this.currentVersionNo);
|
|
||||||
if (!Number.isFinite(propVersionNo) || propVersionNo <= latestVersionNo) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id: "__current__",
|
|
||||||
agentId: this.agentId,
|
|
||||||
versionNo: propVersionNo,
|
|
||||||
source: "current",
|
|
||||||
createdAt: latestSnapshot?.createdAt || null,
|
|
||||||
changedFields: latestSnapshot?.changedFields || [],
|
|
||||||
fieldOrder: latestSnapshot?.fieldOrder || [],
|
|
||||||
isCurrentVersion: true,
|
|
||||||
previousVersionNo: latestVersionNo || null,
|
|
||||||
previousSnapshotId: latestSnapshot?.id || null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
buildVersionDiffSnapshot(row) {
|
buildVersionDiffSnapshot(row) {
|
||||||
if (row?.isCurrentVersion) {
|
|
||||||
return this.buildCurrentVersionDiffSnapshot(row);
|
|
||||||
}
|
|
||||||
return this.buildSavedVersionDiffSnapshot(row);
|
return this.buildSavedVersionDiffSnapshot(row);
|
||||||
},
|
},
|
||||||
buildCurrentVersionDiffSnapshot(row) {
|
|
||||||
const currentVersionNo = Number(row.versionNo);
|
|
||||||
return Promise.all([
|
|
||||||
this.fetchPreviousSnapshotDetail(row),
|
|
||||||
this.fetchCurrentAgentData()
|
|
||||||
]).then(([previousSnapshot, currentData]) => ({
|
|
||||||
...row,
|
|
||||||
versionNo: currentVersionNo,
|
|
||||||
changedFields: previousSnapshot.changedFields || [],
|
|
||||||
snapshotData: previousSnapshot.snapshotData || {},
|
|
||||||
afterSnapshotData: currentData,
|
|
||||||
beforeVersionNo: previousSnapshot.versionNo,
|
|
||||||
afterVersionNo: currentVersionNo,
|
|
||||||
fieldOrder: previousSnapshot.fieldOrder || row.fieldOrder || []
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
buildSavedVersionDiffSnapshot(row) {
|
buildSavedVersionDiffSnapshot(row) {
|
||||||
const versionNo = Number(row.versionNo);
|
const versionNo = Number(row.versionNo);
|
||||||
return Promise.all([
|
const displayVersionNo = Number.isFinite(versionNo) ? versionNo : row.versionNo;
|
||||||
this.fetchSnapshotDetail(row.id),
|
return this.fetchSnapshotDetail(row.id).then((selectedSnapshot) => {
|
||||||
this.fetchPreviousSnapshotDetail(row)
|
if (!this.hasPreviousSnapshot(row)) {
|
||||||
]).then(([selectedSnapshot, previousSnapshot]) => ({
|
return this.buildSingleSnapshotDetail(selectedSnapshot, row, displayVersionNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.fetchPreviousSnapshotDetail(row).then((previousSnapshot) => {
|
||||||
|
if (!previousSnapshot?.id) {
|
||||||
|
return this.buildSingleSnapshotDetail(selectedSnapshot, row, displayVersionNo);
|
||||||
|
}
|
||||||
|
const adjacentVersion = this.isAdjacentVersion(previousSnapshot.versionNo, displayVersionNo);
|
||||||
|
return {
|
||||||
|
...selectedSnapshot,
|
||||||
|
versionNo: displayVersionNo,
|
||||||
|
changedFields: adjacentVersion ? (selectedSnapshot.changedFields || row.changedFields || []) : [],
|
||||||
|
snapshotData: previousSnapshot.snapshotData || {},
|
||||||
|
afterSnapshotData: selectedSnapshot.snapshotData || {},
|
||||||
|
beforeVersionNo: previousSnapshot.versionNo,
|
||||||
|
afterVersionNo: displayVersionNo,
|
||||||
|
fieldOrder: selectedSnapshot.fieldOrder || previousSnapshot.fieldOrder || row.fieldOrder || []
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
buildSingleSnapshotDetail(selectedSnapshot, row, displayVersionNo) {
|
||||||
|
return {
|
||||||
...selectedSnapshot,
|
...selectedSnapshot,
|
||||||
versionNo,
|
versionNo: displayVersionNo,
|
||||||
changedFields: previousSnapshot.changedFields || [],
|
isSingleSnapshot: true,
|
||||||
snapshotData: previousSnapshot.snapshotData || {},
|
changedFields: selectedSnapshot.changedFields || row.changedFields || [],
|
||||||
|
snapshotData: selectedSnapshot.snapshotData || {},
|
||||||
afterSnapshotData: selectedSnapshot.snapshotData || {},
|
afterSnapshotData: selectedSnapshot.snapshotData || {},
|
||||||
beforeVersionNo: previousSnapshot.versionNo,
|
afterVersionNo: displayVersionNo,
|
||||||
afterVersionNo: versionNo,
|
fieldOrder: selectedSnapshot.fieldOrder || row.fieldOrder || []
|
||||||
fieldOrder: selectedSnapshot.fieldOrder || previousSnapshot.fieldOrder || row.fieldOrder || []
|
};
|
||||||
}));
|
},
|
||||||
|
isAdjacentVersion(beforeVersionNo, afterVersionNo) {
|
||||||
|
const beforeVersion = Number(beforeVersionNo);
|
||||||
|
const afterVersion = Number(afterVersionNo);
|
||||||
|
return Number.isFinite(beforeVersion) && Number.isFinite(afterVersion) && afterVersion - beforeVersion === 1;
|
||||||
|
},
|
||||||
|
hasPreviousSnapshot(row) {
|
||||||
|
const versionNo = Number(row?.versionNo);
|
||||||
|
return !!(
|
||||||
|
row?.previousSnapshotId ||
|
||||||
|
row?.previousVersionNo ||
|
||||||
|
(Number.isFinite(versionNo) && versionNo > 1)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
fetchPreviousSnapshotDetail(row) {
|
fetchPreviousSnapshotDetail(row) {
|
||||||
if (row?.previousSnapshotId) {
|
if (row?.previousSnapshotId) {
|
||||||
return this.fetchSnapshotDetail(row.previousSnapshotId);
|
return this.fetchSnapshotDetail(row.previousSnapshotId)
|
||||||
|
.catch(() => this.fetchPreviousSnapshotDetailWithoutId(row));
|
||||||
}
|
}
|
||||||
|
return this.fetchPreviousSnapshotDetailWithoutId(row);
|
||||||
|
},
|
||||||
|
fetchPreviousSnapshotDetailWithoutId(row) {
|
||||||
if (row?.previousVersionNo) {
|
if (row?.previousVersionNo) {
|
||||||
return this.fetchSnapshotDetailByVersion(row.previousVersionNo);
|
return this.fetchSnapshotDetailByVersion(row.previousVersionNo)
|
||||||
|
.catch(() => this.fetchNearestPreviousSnapshotDetail(row));
|
||||||
}
|
}
|
||||||
|
return this.fetchNearestPreviousSnapshotDetail(row);
|
||||||
|
},
|
||||||
|
fetchNearestPreviousSnapshotDetail(row) {
|
||||||
return this.fetchSnapshotRowBeforeVersion(row?.versionNo).then((previousRow) => {
|
return this.fetchSnapshotRowBeforeVersion(row?.versionNo).then((previousRow) => {
|
||||||
if (!previousRow?.id) {
|
if (!previousRow?.id) {
|
||||||
return Promise.reject(new Error("previous snapshot not found"));
|
return null;
|
||||||
}
|
}
|
||||||
return this.fetchSnapshotDetail(previousRow.id);
|
return this.fetchSnapshotDetail(previousRow.id).catch(() => null);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetchSnapshotDetailByVersion(versionNo) {
|
fetchSnapshotDetailByVersion(versionNo) {
|
||||||
@@ -1017,7 +1094,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cached = this.snapshots.find((item) => {
|
const cached = this.snapshots.find((item) => {
|
||||||
return !item.isCurrentVersion && Number(item.versionNo) === version;
|
return Number(item.versionNo) === version;
|
||||||
});
|
});
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return Promise.resolve(cached);
|
return Promise.resolve(cached);
|
||||||
@@ -1388,6 +1465,15 @@ export default {
|
|||||||
note: params.length ? "" : this.$t("agentSnapshot.function.noParams")
|
note: params.length ? "" : this.$t("agentSnapshot.function.noParams")
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
buildSingleFunctionStates(value) {
|
||||||
|
return this.normalizeFunctions(value).map((func) => ({
|
||||||
|
pluginId: func.pluginId,
|
||||||
|
name: func.name,
|
||||||
|
status: this.$t("agentSnapshot.function.enabled"),
|
||||||
|
params: func.params,
|
||||||
|
note: func.params.length ? "" : this.$t("agentSnapshot.function.noParams")
|
||||||
|
}));
|
||||||
|
},
|
||||||
changedFunctionParamKeys(pluginId, beforeParams, afterParams) {
|
changedFunctionParamKeys(pluginId, beforeParams, afterParams) {
|
||||||
const safeBeforeParams = beforeParams || {};
|
const safeBeforeParams = beforeParams || {};
|
||||||
const safeAfterParams = afterParams || {};
|
const safeAfterParams = afterParams || {};
|
||||||
@@ -1447,17 +1533,19 @@ export default {
|
|||||||
if (value === null || value === undefined || value === "") {
|
if (value === null || value === undefined || value === "") {
|
||||||
return this.$t("agentSnapshot.emptyValue");
|
return this.$t("agentSnapshot.emptyValue");
|
||||||
}
|
}
|
||||||
if (typeof value === "object") {
|
const displayValue = this.localizedSnapshotDisplayValue(value);
|
||||||
return JSON.stringify(value);
|
if (typeof displayValue === "object") {
|
||||||
|
return JSON.stringify(displayValue);
|
||||||
}
|
}
|
||||||
return String(value);
|
return String(displayValue);
|
||||||
},
|
},
|
||||||
renderMarkdownValue(value) {
|
renderMarkdownValue(value) {
|
||||||
if (value === null || value === undefined || String(value).trim() === "") {
|
const displayValue = this.localizedSnapshotDisplayValue(value);
|
||||||
|
if (displayValue === null || displayValue === undefined || String(displayValue).trim() === "") {
|
||||||
return `<span class="markdown-empty">${this.escapeHtml(this.$t("agentSnapshot.emptyValue"))}</span>`;
|
return `<span class="markdown-empty">${this.escapeHtml(this.$t("agentSnapshot.emptyValue"))}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = String(value).replace(/\r\n/g, "\n").split("\n");
|
const lines = String(displayValue).replace(/\r\n/g, "\n").split("\n");
|
||||||
const html = [];
|
const html = [];
|
||||||
let paragraph = [];
|
let paragraph = [];
|
||||||
const listStack = [];
|
const listStack = [];
|
||||||
@@ -1616,7 +1704,29 @@ export default {
|
|||||||
return data[field];
|
return data[field];
|
||||||
},
|
},
|
||||||
isSameValue(left, right) {
|
isSameValue(left, right) {
|
||||||
return JSON.stringify(this.normalizeValue(left)) === JSON.stringify(this.normalizeValue(right));
|
return this.isEquivalentValue(this.normalizeValue(left), this.normalizeValue(right));
|
||||||
|
},
|
||||||
|
isEquivalentValue(left, right) {
|
||||||
|
if (left === SNAPSHOT_SECRET_REDACTED || right === SNAPSHOT_SECRET_REDACTED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Array.isArray(left) || Array.isArray(right)) {
|
||||||
|
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return left.every((item, index) => this.isEquivalentValue(item, right[index]));
|
||||||
|
}
|
||||||
|
if (this.isPlainObject(left) || this.isPlainObject(right)) {
|
||||||
|
if (!this.isPlainObject(left) || !this.isPlainObject(right)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const keys = Array.from(new Set([
|
||||||
|
...Object.keys(left),
|
||||||
|
...Object.keys(right)
|
||||||
|
]));
|
||||||
|
return keys.every((key) => this.isEquivalentValue(left[key], right[key]));
|
||||||
|
}
|
||||||
|
return left === right;
|
||||||
},
|
},
|
||||||
normalizeValue(value) {
|
normalizeValue(value) {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
@@ -1630,6 +1740,9 @@ export default {
|
|||||||
}
|
}
|
||||||
return value === undefined ? null : value;
|
return value === undefined ? null : value;
|
||||||
},
|
},
|
||||||
|
isPlainObject(value) {
|
||||||
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||||
|
},
|
||||||
formatDisplayValue(field, value, data = {}) {
|
formatDisplayValue(field, value, data = {}) {
|
||||||
if (MODEL_FIELD_TYPES[field]) {
|
if (MODEL_FIELD_TYPES[field]) {
|
||||||
return this.modelDisplayName(value);
|
return this.modelDisplayName(value);
|
||||||
@@ -1663,13 +1776,29 @@ export default {
|
|||||||
if (Array.isArray(value) && value.length === 0) {
|
if (Array.isArray(value) && value.length === 0) {
|
||||||
return this.$t("agentSnapshot.emptyValue");
|
return this.$t("agentSnapshot.emptyValue");
|
||||||
}
|
}
|
||||||
if (Array.isArray(value) && value.every((item) => this.isPrimitiveValue(item))) {
|
const displayValue = this.localizedSnapshotDisplayValue(value);
|
||||||
return value.join(", ");
|
if (Array.isArray(displayValue) && displayValue.every((item) => this.isPrimitiveValue(item))) {
|
||||||
|
return displayValue.join(", ");
|
||||||
}
|
}
|
||||||
if (this.isComplexValue(value)) {
|
if (this.isComplexValue(displayValue)) {
|
||||||
return JSON.stringify(value, null, 2);
|
return JSON.stringify(displayValue, null, 2);
|
||||||
}
|
}
|
||||||
return String(value);
|
return String(displayValue);
|
||||||
|
},
|
||||||
|
localizedSnapshotDisplayValue(value) {
|
||||||
|
if (value === SNAPSHOT_SECRET_REDACTED) {
|
||||||
|
return this.$t("agentSnapshot.secretRedacted");
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map((item) => this.localizedSnapshotDisplayValue(item));
|
||||||
|
}
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
return Object.keys(value).reduce((result, key) => {
|
||||||
|
result[key] = this.localizedSnapshotDisplayValue(value[key]);
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
return value;
|
||||||
},
|
},
|
||||||
isPrimitiveValue(value) {
|
isPrimitiveValue(value) {
|
||||||
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
||||||
@@ -1821,6 +1950,10 @@ export default {
|
|||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.diff-values.is-single {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.diff-pane {
|
.diff-pane {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
@@ -1831,6 +1964,10 @@ export default {
|
|||||||
border-left: 1px solid #e9e9e9;
|
border-left: 1px solid #e9e9e9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.diff-values.is-single .diff-after {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.diff-pane-title {
|
.diff-pane-title {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
color: #3d4566;
|
color: #3d4566;
|
||||||
@@ -1883,6 +2020,10 @@ export default {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.function-change-view.is-single {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.function-toggle-list {
|
.function-toggle-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1912,6 +2053,10 @@ export default {
|
|||||||
background: #fbfdff;
|
background: #fbfdff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.function-toggle-card.is-single .function-state-pane {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.function-toggle-head {
|
.function-toggle-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
@@ -831,6 +831,8 @@ export default {
|
|||||||
'agentSnapshot.before': 'Vorher',
|
'agentSnapshot.before': 'Vorher',
|
||||||
'agentSnapshot.after': 'Nachher',
|
'agentSnapshot.after': 'Nachher',
|
||||||
'agentSnapshot.emptyValue': 'Keine',
|
'agentSnapshot.emptyValue': 'Keine',
|
||||||
|
'agentSnapshot.secretRedacted': 'Geheimer Schlüssel maskiert',
|
||||||
|
'agentSnapshot.configValue': 'Konfigurationswert',
|
||||||
'agentSnapshot.noChangedContent': 'Keine anzeigbaren Änderungen',
|
'agentSnapshot.noChangedContent': 'Keine anzeigbaren Änderungen',
|
||||||
'agentSnapshot.noFunctionChange': 'Keine Funktionsänderungen',
|
'agentSnapshot.noFunctionChange': 'Keine Funktionsänderungen',
|
||||||
'agentSnapshot.beforeChange': 'Vor der Änderung',
|
'agentSnapshot.beforeChange': 'Vor der Änderung',
|
||||||
|
|||||||
@@ -881,6 +881,8 @@ export default {
|
|||||||
'agentSnapshot.before': 'Before',
|
'agentSnapshot.before': 'Before',
|
||||||
'agentSnapshot.after': 'After',
|
'agentSnapshot.after': 'After',
|
||||||
'agentSnapshot.emptyValue': 'None',
|
'agentSnapshot.emptyValue': 'None',
|
||||||
|
'agentSnapshot.secretRedacted': 'Secret redacted',
|
||||||
|
'agentSnapshot.configValue': 'Config Value',
|
||||||
'agentSnapshot.noChangedContent': 'No changes to display',
|
'agentSnapshot.noChangedContent': 'No changes to display',
|
||||||
'agentSnapshot.noFunctionChange': 'No function changes',
|
'agentSnapshot.noFunctionChange': 'No function changes',
|
||||||
'agentSnapshot.beforeChange': 'Before Change',
|
'agentSnapshot.beforeChange': 'Before Change',
|
||||||
|
|||||||
@@ -831,6 +831,8 @@ export default {
|
|||||||
'agentSnapshot.before': 'Antes',
|
'agentSnapshot.before': 'Antes',
|
||||||
'agentSnapshot.after': 'Depois',
|
'agentSnapshot.after': 'Depois',
|
||||||
'agentSnapshot.emptyValue': 'Nenhum',
|
'agentSnapshot.emptyValue': 'Nenhum',
|
||||||
|
'agentSnapshot.secretRedacted': 'Chave secreta mascarada',
|
||||||
|
'agentSnapshot.configValue': 'Valor da configuração',
|
||||||
'agentSnapshot.noChangedContent': 'Nenhuma alteração para exibir',
|
'agentSnapshot.noChangedContent': 'Nenhuma alteração para exibir',
|
||||||
'agentSnapshot.noFunctionChange': 'Sem alterações de função',
|
'agentSnapshot.noFunctionChange': 'Sem alterações de função',
|
||||||
'agentSnapshot.beforeChange': 'Antes da Alteração',
|
'agentSnapshot.beforeChange': 'Antes da Alteração',
|
||||||
|
|||||||
@@ -831,6 +831,8 @@ export default {
|
|||||||
'agentSnapshot.before': 'Trước',
|
'agentSnapshot.before': 'Trước',
|
||||||
'agentSnapshot.after': 'Sau',
|
'agentSnapshot.after': 'Sau',
|
||||||
'agentSnapshot.emptyValue': 'Không có',
|
'agentSnapshot.emptyValue': 'Không có',
|
||||||
|
'agentSnapshot.secretRedacted': 'Khóa bí mật đã được ẩn',
|
||||||
|
'agentSnapshot.configValue': 'Giá trị cấu hình',
|
||||||
'agentSnapshot.noChangedContent': 'Không có thay đổi để hiển thị',
|
'agentSnapshot.noChangedContent': 'Không có thay đổi để hiển thị',
|
||||||
'agentSnapshot.noFunctionChange': 'Không có thay đổi chức năng',
|
'agentSnapshot.noFunctionChange': 'Không có thay đổi chức năng',
|
||||||
'agentSnapshot.beforeChange': 'Trước thay đổi',
|
'agentSnapshot.beforeChange': 'Trước thay đổi',
|
||||||
|
|||||||
@@ -881,6 +881,8 @@ export default {
|
|||||||
'agentSnapshot.before': '变化前',
|
'agentSnapshot.before': '变化前',
|
||||||
'agentSnapshot.after': '变化后',
|
'agentSnapshot.after': '变化后',
|
||||||
'agentSnapshot.emptyValue': '无',
|
'agentSnapshot.emptyValue': '无',
|
||||||
|
'agentSnapshot.secretRedacted': '密钥已脱敏',
|
||||||
|
'agentSnapshot.configValue': '配置值',
|
||||||
'agentSnapshot.noChangedContent': '暂无可展示的变更内容',
|
'agentSnapshot.noChangedContent': '暂无可展示的变更内容',
|
||||||
'agentSnapshot.noFunctionChange': '功能配置无变化',
|
'agentSnapshot.noFunctionChange': '功能配置无变化',
|
||||||
'agentSnapshot.beforeChange': '变化前',
|
'agentSnapshot.beforeChange': '变化前',
|
||||||
|
|||||||
@@ -831,6 +831,8 @@ export default {
|
|||||||
'agentSnapshot.before': '之前',
|
'agentSnapshot.before': '之前',
|
||||||
'agentSnapshot.after': '之後',
|
'agentSnapshot.after': '之後',
|
||||||
'agentSnapshot.emptyValue': '無',
|
'agentSnapshot.emptyValue': '無',
|
||||||
|
'agentSnapshot.secretRedacted': '密鑰已脫敏',
|
||||||
|
'agentSnapshot.configValue': '配置值',
|
||||||
'agentSnapshot.noChangedContent': '暫無可展示的變更內容',
|
'agentSnapshot.noChangedContent': '暫無可展示的變更內容',
|
||||||
'agentSnapshot.noFunctionChange': '功能配置無變化',
|
'agentSnapshot.noFunctionChange': '功能配置無變化',
|
||||||
'agentSnapshot.beforeChange': '變化前',
|
'agentSnapshot.beforeChange': '變化前',
|
||||||
|
|||||||
Reference in New Issue
Block a user