mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-21 22:53:56 +08:00
Fix agent snapshot history issues
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
<liquibase-core.version>4.20.0</liquibase-core.version>
|
<liquibase-core.version>4.20.0</liquibase-core.version>
|
||||||
<aliyun-sms-version>4.1.0</aliyun-sms-version>
|
<aliyun-sms-version>4.1.0</aliyun-sms-version>
|
||||||
<okio-version>3.4.0</okio-version>
|
<okio-version>3.4.0</okio-version>
|
||||||
|
<skipTests>true</skipTests>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -275,7 +276,7 @@
|
|||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>${skipTests}</skipTests>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package xiaozhi.modules.agent.Enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotDataDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum AgentSnapshotField {
|
||||||
|
AGENT_CODE("agentCode", AgentSnapshotDataDTO::getAgentCode, AgentUpdateDTO::getAgentCode,
|
||||||
|
(agent, data) -> agent.setAgentCode(data.getAgentCode())),
|
||||||
|
AGENT_NAME("agentName", AgentSnapshotDataDTO::getAgentName, AgentUpdateDTO::getAgentName,
|
||||||
|
(agent, data) -> agent.setAgentName(data.getAgentName())),
|
||||||
|
ASR_MODEL_ID("asrModelId", AgentSnapshotDataDTO::getAsrModelId, AgentUpdateDTO::getAsrModelId,
|
||||||
|
(agent, data) -> agent.setAsrModelId(data.getAsrModelId())),
|
||||||
|
VAD_MODEL_ID("vadModelId", AgentSnapshotDataDTO::getVadModelId, AgentUpdateDTO::getVadModelId,
|
||||||
|
(agent, data) -> agent.setVadModelId(data.getVadModelId())),
|
||||||
|
LLM_MODEL_ID("llmModelId", AgentSnapshotDataDTO::getLlmModelId, AgentUpdateDTO::getLlmModelId,
|
||||||
|
(agent, data) -> agent.setLlmModelId(data.getLlmModelId())),
|
||||||
|
SLM_MODEL_ID("slmModelId", AgentSnapshotDataDTO::getSlmModelId, AgentUpdateDTO::getSlmModelId,
|
||||||
|
(agent, data) -> agent.setSlmModelId(data.getSlmModelId())),
|
||||||
|
VLLM_MODEL_ID("vllmModelId", AgentSnapshotDataDTO::getVllmModelId, AgentUpdateDTO::getVllmModelId,
|
||||||
|
(agent, data) -> agent.setVllmModelId(data.getVllmModelId())),
|
||||||
|
TTS_MODEL_ID("ttsModelId", AgentSnapshotDataDTO::getTtsModelId, AgentUpdateDTO::getTtsModelId,
|
||||||
|
(agent, data) -> agent.setTtsModelId(data.getTtsModelId())),
|
||||||
|
TTS_VOICE_ID("ttsVoiceId", AgentSnapshotDataDTO::getTtsVoiceId, AgentUpdateDTO::getTtsVoiceId,
|
||||||
|
(agent, data) -> agent.setTtsVoiceId(data.getTtsVoiceId())),
|
||||||
|
TTS_LANGUAGE("ttsLanguage", AgentSnapshotDataDTO::getTtsLanguage, AgentUpdateDTO::getTtsLanguage,
|
||||||
|
(agent, data) -> agent.setTtsLanguage(data.getTtsLanguage())),
|
||||||
|
TTS_VOLUME("ttsVolume", AgentSnapshotDataDTO::getTtsVolume, AgentUpdateDTO::getTtsVolume,
|
||||||
|
(agent, data) -> agent.setTtsVolume(data.getTtsVolume())),
|
||||||
|
TTS_RATE("ttsRate", AgentSnapshotDataDTO::getTtsRate, AgentUpdateDTO::getTtsRate,
|
||||||
|
(agent, data) -> agent.setTtsRate(data.getTtsRate())),
|
||||||
|
TTS_PITCH("ttsPitch", AgentSnapshotDataDTO::getTtsPitch, AgentUpdateDTO::getTtsPitch,
|
||||||
|
(agent, data) -> agent.setTtsPitch(data.getTtsPitch())),
|
||||||
|
MEM_MODEL_ID("memModelId", AgentSnapshotDataDTO::getMemModelId, AgentUpdateDTO::getMemModelId,
|
||||||
|
(agent, data) -> agent.setMemModelId(data.getMemModelId())),
|
||||||
|
INTENT_MODEL_ID("intentModelId", AgentSnapshotDataDTO::getIntentModelId, AgentUpdateDTO::getIntentModelId,
|
||||||
|
(agent, data) -> agent.setIntentModelId(data.getIntentModelId())),
|
||||||
|
CHAT_HISTORY_CONF("chatHistoryConf", AgentSnapshotDataDTO::getChatHistoryConf, AgentUpdateDTO::getChatHistoryConf,
|
||||||
|
(agent, data) -> agent.setChatHistoryConf(data.getChatHistoryConf())),
|
||||||
|
SYSTEM_PROMPT("systemPrompt", AgentSnapshotDataDTO::getSystemPrompt, AgentUpdateDTO::getSystemPrompt,
|
||||||
|
(agent, data) -> agent.setSystemPrompt(data.getSystemPrompt())),
|
||||||
|
SUMMARY_MEMORY("summaryMemory", AgentSnapshotDataDTO::getSummaryMemory, AgentUpdateDTO::getSummaryMemory,
|
||||||
|
(agent, data) -> agent.setSummaryMemory(data.getSummaryMemory())),
|
||||||
|
LANG_CODE("langCode", AgentSnapshotDataDTO::getLangCode, AgentUpdateDTO::getLangCode,
|
||||||
|
(agent, data) -> agent.setLangCode(data.getLangCode())),
|
||||||
|
LANGUAGE("language", AgentSnapshotDataDTO::getLanguage, AgentUpdateDTO::getLanguage,
|
||||||
|
(agent, data) -> agent.setLanguage(data.getLanguage())),
|
||||||
|
SORT("sort", AgentSnapshotDataDTO::getSort, AgentUpdateDTO::getSort,
|
||||||
|
(agent, data) -> agent.setSort(data.getSort())),
|
||||||
|
FUNCTIONS("functions", AgentSnapshotDataDTO::getFunctions, AgentUpdateDTO::getFunctions, null),
|
||||||
|
CONTEXT_PROVIDERS("contextProviders", AgentSnapshotDataDTO::getContextProviders,
|
||||||
|
AgentUpdateDTO::getContextProviders, null),
|
||||||
|
CORRECT_WORD_FILE_IDS("correctWordFileIds", AgentSnapshotDataDTO::getCorrectWordFileIds,
|
||||||
|
AgentUpdateDTO::getCorrectWordFileIds, null),
|
||||||
|
TAG_NAMES("tagNames", AgentSnapshotDataDTO::getTagNames, AgentUpdateDTO::getTagNames, null);
|
||||||
|
|
||||||
|
private final String fieldName;
|
||||||
|
private final Function<AgentSnapshotDataDTO, Object> snapshotGetter;
|
||||||
|
private final Function<AgentUpdateDTO, Object> updateGetter;
|
||||||
|
private final BiConsumer<AgentEntity, AgentSnapshotDataDTO> restoreApplier;
|
||||||
|
|
||||||
|
AgentSnapshotField(String fieldName, Function<AgentSnapshotDataDTO, Object> snapshotGetter,
|
||||||
|
Function<AgentUpdateDTO, Object> updateGetter,
|
||||||
|
BiConsumer<AgentEntity, AgentSnapshotDataDTO> restoreApplier) {
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
this.snapshotGetter = snapshotGetter;
|
||||||
|
this.updateGetter = updateGetter;
|
||||||
|
this.restoreApplier = restoreApplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> names() {
|
||||||
|
return Arrays.stream(values()).map(AgentSnapshotField::getFieldName).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String canonicalName(String fieldName) {
|
||||||
|
return "tags".equals(fieldName) ? TAG_NAMES.getFieldName() : fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object snapshotValue(AgentSnapshotDataDTO data) {
|
||||||
|
return data == null ? null : snapshotGetter.apply(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object updateValue(AgentUpdateDTO data) {
|
||||||
|
return data == null ? null : updateGetter.apply(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRestorableAgentField() {
|
||||||
|
return restoreApplier != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyTo(AgentEntity agent, AgentSnapshotDataDTO data) {
|
||||||
|
if (restoreApplier != null) {
|
||||||
|
restoreApplier.accept(agent, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-18
@@ -48,11 +48,8 @@ import xiaozhi.modules.agent.service.AgentTagService;
|
|||||||
import xiaozhi.modules.agent.service.AgentChatAudioService;
|
import xiaozhi.modules.agent.service.AgentChatAudioService;
|
||||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||||
import xiaozhi.modules.agent.service.AgentChatSummaryService;
|
import xiaozhi.modules.agent.service.AgentChatSummaryService;
|
||||||
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
|
||||||
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
|
||||||
import xiaozhi.modules.agent.service.AgentService;
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
import xiaozhi.modules.correctword.service.CorrectWordFileService;
|
|
||||||
import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO;
|
import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO;
|
||||||
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
@@ -69,12 +66,9 @@ public class AgentController {
|
|||||||
private final DeviceService deviceService;
|
private final DeviceService deviceService;
|
||||||
private final AgentChatHistoryService agentChatHistoryService;
|
private final AgentChatHistoryService agentChatHistoryService;
|
||||||
private final AgentChatAudioService agentChatAudioService;
|
private final AgentChatAudioService agentChatAudioService;
|
||||||
private final AgentPluginMappingService agentPluginMappingService;
|
|
||||||
private final AgentContextProviderService agentContextProviderService;
|
|
||||||
private final AgentChatSummaryService agentChatSummaryService;
|
private final AgentChatSummaryService agentChatSummaryService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final AgentTagService agentTagService;
|
private final AgentTagService agentTagService;
|
||||||
private final CorrectWordFileService correctWordFileService;
|
|
||||||
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "获取用户智能体列表")
|
@Operation(summary = "获取用户智能体列表")
|
||||||
@@ -171,18 +165,7 @@ public class AgentController {
|
|||||||
@Operation(summary = "删除智能体")
|
@Operation(summary = "删除智能体")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> delete(@PathVariable String id) {
|
public Result<Void> delete(@PathVariable String id) {
|
||||||
// 先删除关联的设备
|
agentService.deleteAgent(id);
|
||||||
deviceService.deleteByAgentId(id);
|
|
||||||
// 删除关联的聊天记录
|
|
||||||
agentChatHistoryService.deleteByAgentId(id, true, true);
|
|
||||||
// 删除关联的插件
|
|
||||||
agentPluginMappingService.deleteByAgentId(id);
|
|
||||||
// 删除关联的上下文源配置
|
|
||||||
agentContextProviderService.deleteByAgentId(id);
|
|
||||||
// 删除关联的替换词文件关联记录
|
|
||||||
correctWordFileService.deleteMappingsByAgentId(id);
|
|
||||||
// 再删除智能体
|
|
||||||
agentService.deleteById(id);
|
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-4
@@ -1,13 +1,12 @@
|
|||||||
package xiaozhi.modules.agent.controller;
|
package xiaozhi.modules.agent.controller;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
|
import org.springdoc.core.annotations.ParameterObject;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -17,6 +16,7 @@ import xiaozhi.common.exception.RenException;
|
|||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.user.UserDetail;
|
import xiaozhi.common.user.UserDetail;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotPageDTO;
|
||||||
import xiaozhi.modules.agent.service.AgentService;
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
import xiaozhi.modules.agent.service.AgentSnapshotService;
|
import xiaozhi.modules.agent.service.AgentSnapshotService;
|
||||||
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
||||||
@@ -35,7 +35,7 @@ public class AgentSnapshotController {
|
|||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<PageData<AgentSnapshotVO>> page(
|
public Result<PageData<AgentSnapshotVO>> page(
|
||||||
@PathVariable String agentId,
|
@PathVariable String agentId,
|
||||||
@RequestParam Map<String, Object> params) {
|
@ParameterObject AgentSnapshotPageDTO params) {
|
||||||
checkPermission(agentId);
|
checkPermission(agentId);
|
||||||
return new Result<PageData<AgentSnapshotVO>>().ok(agentSnapshotService.page(agentId, params));
|
return new Result<PageData<AgentSnapshotVO>>().ok(agentSnapshotService.page(agentId, params));
|
||||||
}
|
}
|
||||||
@@ -57,6 +57,15 @@ public class AgentSnapshotController {
|
|||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{snapshotId}")
|
||||||
|
@Operation(summary = "删除智能体历史快照")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Void> deleteSnapshot(@PathVariable String agentId, @PathVariable String snapshotId) {
|
||||||
|
checkPermission(agentId);
|
||||||
|
agentSnapshotService.deleteSnapshot(agentId, snapshotId);
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
private void checkPermission(String agentId) {
|
private void checkPermission(String agentId) {
|
||||||
UserDetail user = SecurityUser.getUser();
|
UserDetail user = SecurityUser.getUser();
|
||||||
if (user == null || !agentService.checkAgentPermission(agentId, user.getId())) {
|
if (user == null || !agentService.checkAgentPermission(agentId, user.getId())) {
|
||||||
|
|||||||
@@ -36,4 +36,11 @@ public interface AgentDao extends BaseDao<AgentEntity> {
|
|||||||
* @param agentId 智能体ID
|
* @param agentId 智能体ID
|
||||||
*/
|
*/
|
||||||
AgentInfoVO selectAgentInfoById(@Param("agentId") String agentId);
|
AgentInfoVO selectAgentInfoById(@Param("agentId") String agentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁定智能体主记录,用于串行化同一智能体的配置写入
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
*/
|
||||||
|
AgentEntity selectByIdForUpdate(@Param("agentId") String agentId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,4 +11,6 @@ public interface AgentSnapshotDao extends BaseDao<AgentSnapshotEntity> {
|
|||||||
Integer selectMaxVersionNo(@Param("agentId") String agentId);
|
Integer selectMaxVersionNo(@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 deleteOlderThanKeepLimit(@Param("agentId") String agentId, @Param("keepLimit") int keepLimit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "智能体快照分页查询参数")
|
||||||
|
public class AgentSnapshotPageDTO {
|
||||||
|
@Schema(description = "当前页码,从1开始", example = "1")
|
||||||
|
private Integer page = 1;
|
||||||
|
|
||||||
|
@Schema(description = "每页数量", example = "10")
|
||||||
|
private Integer limit = 10;
|
||||||
|
|
||||||
|
@Schema(description = "版本锚点,只查询小于等于该版本号的历史快照", example = "20")
|
||||||
|
private Integer maxVersionNo;
|
||||||
|
|
||||||
|
public int pageOrDefault() {
|
||||||
|
return page == null || page < 1 ? 1 : page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int limitOrDefault() {
|
||||||
|
if (limit == null || limit < 1) {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -7,11 +7,11 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import xiaozhi.modules.agent.dto.ContextProviderDTO;
|
import xiaozhi.modules.agent.dto.ContextProviderDTO;
|
||||||
|
import xiaozhi.modules.agent.typehandler.ContextProviderListTypeHandler;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@TableName(value = "ai_agent_context_provider", autoResultMap = true)
|
@TableName(value = "ai_agent_context_provider", autoResultMap = true)
|
||||||
@@ -26,7 +26,7 @@ public class AgentContextProviderEntity {
|
|||||||
private String agentId;
|
private String agentId;
|
||||||
|
|
||||||
@Schema(description = "上下文源配置")
|
@Schema(description = "上下文源配置")
|
||||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
@TableField(typeHandler = ContextProviderListTypeHandler.class)
|
||||||
private List<ContextProviderDTO> contextProviders;
|
private List<ContextProviderDTO> contextProviders;
|
||||||
|
|
||||||
@Schema(description = "创建者")
|
@Schema(description = "创建者")
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ public class AgentSnapshotEntity {
|
|||||||
@Schema(description = "快照来源")
|
@Schema(description = "快照来源")
|
||||||
private String source;
|
private String source;
|
||||||
|
|
||||||
|
@Schema(description = "恢复来源快照ID")
|
||||||
|
private String restoreFromSnapshotId;
|
||||||
|
|
||||||
|
@Schema(description = "恢复来源版本号")
|
||||||
|
private Integer restoreFromVersionNo;
|
||||||
|
|
||||||
@Schema(description = "创建者")
|
@Schema(description = "创建者")
|
||||||
private Long creator;
|
private Long creator;
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
*/
|
*/
|
||||||
void deleteAgentByUserId(Long userId);
|
void deleteAgentByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除智能体及其关联数据
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
*/
|
||||||
|
void deleteAgent(String agentId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户智能体列表
|
* 获取用户智能体列表
|
||||||
*
|
*
|
||||||
|
|||||||
+8
-3
@@ -1,9 +1,8 @@
|
|||||||
package xiaozhi.modules.agent.service;
|
package xiaozhi.modules.agent.service;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
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.AgentUpdateDTO;
|
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;
|
||||||
@@ -11,9 +10,15 @@ 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, AgentUpdateDTO pendingUpdate);
|
||||||
|
|
||||||
PageData<AgentSnapshotVO> page(String agentId, Map<String, Object> params);
|
PageData<AgentSnapshotVO> page(String agentId, AgentSnapshotPageDTO params);
|
||||||
|
|
||||||
AgentSnapshotVO getSnapshot(String agentId, String snapshotId);
|
AgentSnapshotVO getSnapshot(String agentId, String snapshotId);
|
||||||
|
|
||||||
void restoreSnapshot(String agentId, String snapshotId);
|
void restoreSnapshot(String agentId, String snapshotId);
|
||||||
|
|
||||||
|
void deleteSnapshot(String agentId, String snapshotId);
|
||||||
|
|
||||||
|
Integer getCurrentVersionNo(String agentId);
|
||||||
|
|
||||||
|
void deleteByAgentId(String agentId);
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-20
@@ -15,7 +15,6 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -108,12 +107,13 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
agent.setContextProviders(contextProviderEntity.getContextProviders());
|
agent.setContextProviders(contextProviderEntity.getContextProviders());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询替换词文件ID列表
|
// 查询替换词文件ID列表
|
||||||
List<String> correctWordFileIds = correctWordFileService.getAgentCorrectWordFileIds(id);
|
List<String> correctWordFileIds = correctWordFileService.getAgentCorrectWordFileIds(id);
|
||||||
agent.setCorrectWordFileIds(correctWordFileIds);
|
agent.setCorrectWordFileIds(correctWordFileIds);
|
||||||
|
agent.setCurrentVersionNo(agentSnapshotService.getCurrentVersionNo(id));
|
||||||
// 无需额外查询插件列表,已通过SQL查询出来
|
|
||||||
return agent;
|
// 无需额外查询插件列表,已通过SQL查询出来
|
||||||
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -136,15 +136,35 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
return super.insert(entity);
|
return super.insert(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteAgentByUserId(Long userId) {
|
@Transactional(rollbackFor = Exception.class)
|
||||||
UpdateWrapper<AgentEntity> wrapper = new UpdateWrapper<>();
|
public void deleteAgentByUserId(Long userId) {
|
||||||
wrapper.eq("user_id", userId);
|
List<AgentEntity> agents = baseDao.selectList(new QueryWrapper<AgentEntity>()
|
||||||
baseDao.delete(wrapper);
|
.select("id")
|
||||||
}
|
.eq("user_id", userId));
|
||||||
|
for (AgentEntity agent : agents) {
|
||||||
@Override
|
deleteAgent(agent.getId());
|
||||||
public List<AgentDTO> getUserAgents(Long userId, String keyword, String searchType) {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteAgent(String agentId) {
|
||||||
|
if (agentDao.selectByIdForUpdate(agentId) == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deviceService.deleteByAgentId(agentId);
|
||||||
|
agentChatHistoryService.deleteByAgentId(agentId, true, true);
|
||||||
|
agentPluginMappingService.deleteByAgentId(agentId);
|
||||||
|
agentContextProviderService.deleteByAgentId(agentId);
|
||||||
|
correctWordFileService.deleteMappingsByAgentId(agentId);
|
||||||
|
agentTagService.deleteAgentTags(agentId);
|
||||||
|
agentSnapshotService.deleteByAgentId(agentId);
|
||||||
|
deleteById(agentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AgentDTO> getUserAgents(Long userId, String keyword, String searchType) {
|
||||||
QueryWrapper<AgentEntity> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<AgentEntity> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq("user_id", userId).orderByDesc("created_at");
|
queryWrapper.eq("user_id", userId).orderByDesc("created_at");
|
||||||
|
|
||||||
@@ -285,12 +305,13 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void updateAgentById(String agentId, AgentUpdateDTO dto, boolean createSnapshot) {
|
public void updateAgentById(String agentId, AgentUpdateDTO dto, boolean createSnapshot) {
|
||||||
// 先查询现有实体
|
if (agentDao.selectByIdForUpdate(agentId) == null) {
|
||||||
AgentEntity existingEntity = this.getAgentById(agentId);
|
|
||||||
if (existingEntity == null) {
|
|
||||||
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 锁定后查询现有实体和关联配置
|
||||||
|
AgentEntity existingEntity = this.getAgentById(agentId);
|
||||||
|
|
||||||
if (createSnapshot) {
|
if (createSnapshot) {
|
||||||
agentSnapshotService.createSnapshot(agentId, "config", dto);
|
agentSnapshotService.createSnapshot(agentId, "config", dto);
|
||||||
}
|
}
|
||||||
@@ -448,7 +469,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
agentTagService.saveAgentTags(agentId, dto.getTagIds(), dto.getTagNames());
|
agentTagService.saveAgentTags(agentId, dto.getTagIds(), dto.getTagNames());
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean b = validateLLMIntentParams(dto.getLlmModelId(), dto.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);
|
||||||
}
|
}
|
||||||
|
|||||||
+396
-108
@@ -7,15 +7,19 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -30,6 +34,7 @@ import xiaozhi.modules.agent.dao.AgentSnapshotDao;
|
|||||||
import xiaozhi.modules.agent.dao.AgentTagDao;
|
import xiaozhi.modules.agent.dao.AgentTagDao;
|
||||||
import xiaozhi.modules.agent.dao.AgentTagRelationDao;
|
import xiaozhi.modules.agent.dao.AgentTagRelationDao;
|
||||||
import xiaozhi.modules.agent.dto.AgentSnapshotDataDTO;
|
import xiaozhi.modules.agent.dto.AgentSnapshotDataDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotPageDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentSnapshotTagDTO;
|
import xiaozhi.modules.agent.dto.AgentSnapshotTagDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
|
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
|
||||||
@@ -38,6 +43,7 @@ import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
|||||||
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.entity.AgentTagRelationEntity;
|
import xiaozhi.modules.agent.entity.AgentTagRelationEntity;
|
||||||
|
import xiaozhi.modules.agent.Enums.AgentSnapshotField;
|
||||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||||
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
||||||
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||||
@@ -54,10 +60,14 @@ import xiaozhi.modules.security.user.SecurityUser;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao, AgentSnapshotEntity>
|
public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao, AgentSnapshotEntity>
|
||||||
implements AgentSnapshotService {
|
implements AgentSnapshotService {
|
||||||
|
private static final int MAX_SNAPSHOTS_PER_AGENT = 100;
|
||||||
private static final TypeReference<List<String>> STRING_LIST_TYPE = new TypeReference<>() {
|
private static final TypeReference<List<String>> STRING_LIST_TYPE = new TypeReference<>() {
|
||||||
};
|
};
|
||||||
private static final TypeReference<HashMap<String, Object>> PARAM_INFO_TYPE = new TypeReference<>() {
|
private static final TypeReference<HashMap<String, Object>> PARAM_INFO_TYPE = new TypeReference<>() {
|
||||||
};
|
};
|
||||||
|
private static final TypeReference<Map<String, Object>> OBJECT_MAP_TYPE = new TypeReference<>() {
|
||||||
|
};
|
||||||
|
private static final String SECRET_PLACEHOLDER = "__SNAPSHOT_SECRET_REDACTED__";
|
||||||
|
|
||||||
private final AgentSnapshotDao agentSnapshotDao;
|
private final AgentSnapshotDao agentSnapshotDao;
|
||||||
private final AgentDao agentDao;
|
private final AgentDao agentDao;
|
||||||
@@ -73,33 +83,27 @@ 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, AgentUpdateDTO pendingUpdate) {
|
||||||
AgentSnapshotDataDTO snapshotData = buildSnapshotData(agentId);
|
AgentInfoVO agent = getAgentInfo(agentId);
|
||||||
|
AgentSnapshotDataDTO snapshotData = buildSnapshotData(agent);
|
||||||
List<String> changedFields = getChangedFields(snapshotData, pendingUpdate);
|
List<String> changedFields = getChangedFields(snapshotData, pendingUpdate);
|
||||||
if (pendingUpdate != null && changedFields.isEmpty()) {
|
if (pendingUpdate != null && changedFields.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AgentInfoVO agent = agentDao.selectAgentInfoById(agentId);
|
insertSnapshot(agentId, agent.getUserId(), source, snapshotData, changedFields);
|
||||||
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
|
@Override
|
||||||
public PageData<AgentSnapshotVO> page(String agentId, Map<String, Object> params) {
|
@Transactional(rollbackFor = Exception.class)
|
||||||
IPage<AgentSnapshotEntity> page = getPage(params, "created_at", false);
|
public PageData<AgentSnapshotVO> page(String agentId, AgentSnapshotPageDTO params) {
|
||||||
|
ensureInitialSnapshot(agentId);
|
||||||
|
AgentSnapshotPageDTO pageParams = params == null ? new AgentSnapshotPageDTO() : params;
|
||||||
|
Page<AgentSnapshotEntity> page = new Page<>(pageParams.pageOrDefault(), pageParams.limitOrDefault());
|
||||||
|
page.addOrder(OrderItem.desc("version_no"));
|
||||||
IPage<AgentSnapshotEntity> result = agentSnapshotDao.selectPage(page,
|
IPage<AgentSnapshotEntity> result = agentSnapshotDao.selectPage(page,
|
||||||
new QueryWrapper<AgentSnapshotEntity>().eq("agent_id", agentId));
|
new QueryWrapper<AgentSnapshotEntity>()
|
||||||
|
.eq("agent_id", agentId)
|
||||||
|
.le(pageParams.getMaxVersionNo() != null, "version_no", pageParams.getMaxVersionNo()));
|
||||||
List<AgentSnapshotVO> list = result.getRecords().stream()
|
List<AgentSnapshotVO> list = result.getRecords().stream()
|
||||||
.map(entity -> toVO(entity, false))
|
.map(entity -> toVO(entity, false))
|
||||||
.toList();
|
.toList();
|
||||||
@@ -115,44 +119,109 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void restoreSnapshot(String agentId, String snapshotId) {
|
public void restoreSnapshot(String agentId, String snapshotId) {
|
||||||
|
AgentEntity agent = agentDao.selectByIdForUpdate(agentId);
|
||||||
|
if (agent == null) {
|
||||||
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
AgentSnapshotEntity snapshot = getSnapshotEntity(agentId, snapshotId);
|
AgentSnapshotEntity snapshot = getSnapshotEntity(agentId, snapshotId);
|
||||||
AgentSnapshotDataDTO data = JsonUtils.parseObject(snapshot.getSnapshotData(), AgentSnapshotDataDTO.class);
|
AgentSnapshotDataDTO data = JsonUtils.parseObject(snapshot.getSnapshotData(), AgentSnapshotDataDTO.class);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
throw new RenException("快照数据为空,无法恢复");
|
throw new RenException("快照数据为空,无法恢复");
|
||||||
}
|
}
|
||||||
|
|
||||||
createSnapshot(agentId, "restore", null);
|
AgentInfoVO currentAgent = getAgentInfo(agentId);
|
||||||
|
AgentSnapshotDataDTO currentData = buildSnapshotData(currentAgent);
|
||||||
|
AgentSnapshotDataDTO restoreData = preserveCurrentSensitiveValues(data, currentData);
|
||||||
|
List<String> changedFields = getChangedFields(currentData, restoreData);
|
||||||
|
insertSnapshot(agentId, currentAgent.getUserId(), "restore", currentData, changedFields,
|
||||||
|
snapshot.getId(), snapshot.getVersionNo());
|
||||||
|
|
||||||
AgentEntity agent = agentDao.selectById(agentId);
|
applyAgentFields(agent, restoreData);
|
||||||
if (agent == null) {
|
|
||||||
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
applyAgentFields(agent, data);
|
|
||||||
validateRestoreParams(agent);
|
validateRestoreParams(agent);
|
||||||
applyMemoryPolicy(agent);
|
applyMemoryPolicy(agent);
|
||||||
agent.setUpdater(SecurityUser.getUserId());
|
agent.setUpdater(SecurityUser.getUserId());
|
||||||
agent.setUpdatedAt(new Date());
|
agent.setUpdatedAt(new Date());
|
||||||
agentDao.updateById(agent);
|
agentDao.updateById(agent);
|
||||||
|
|
||||||
restoreFunctions(agentId, data.getFunctions());
|
restoreFunctions(agentId, restoreData.getFunctions());
|
||||||
restoreContextProviders(agentId, data.getContextProviders());
|
restoreContextProviders(agentId, restoreData.getContextProviders());
|
||||||
correctWordFileService.saveAgentCorrectWords(agentId, nullToEmpty(data.getCorrectWordFileIds()));
|
correctWordFileService.saveAgentCorrectWords(agentId, nullToEmpty(restoreData.getCorrectWordFileIds()));
|
||||||
restoreTags(agentId, data);
|
restoreTags(agentId, restoreData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertSnapshotWithRetry(AgentSnapshotEntity entity) {
|
@Override
|
||||||
for (int attempt = 0; attempt < 3; attempt++) {
|
@Transactional(rollbackFor = Exception.class)
|
||||||
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(entity.getAgentId());
|
public void deleteSnapshot(String agentId, String snapshotId) {
|
||||||
entity.setVersionNo((maxVersionNo == null ? 0 : maxVersionNo) + 1);
|
AgentSnapshotEntity entity = getSnapshotEntity(agentId, snapshotId);
|
||||||
try {
|
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
||||||
agentSnapshotDao.insert(entity);
|
if (Objects.equals(entity.getVersionNo(), maxVersionNo)) {
|
||||||
return;
|
throw new RenException("最新历史版本不能删除");
|
||||||
} catch (DuplicateKeyException e) {
|
|
||||||
if (attempt == 2) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
deleteById(snapshotId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getCurrentVersionNo(String agentId) {
|
||||||
|
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
||||||
|
return (maxVersionNo == null ? 0 : maxVersionNo) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteByAgentId(String agentId) {
|
||||||
|
agentSnapshotDao.delete(new QueryWrapper<AgentSnapshotEntity>().eq("agent_id", agentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertSnapshot(String agentId, Long userId, String source, AgentSnapshotDataDTO snapshotData,
|
||||||
|
List<String> changedFields) {
|
||||||
|
insertSnapshot(agentId, userId, source, snapshotData, changedFields, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertSnapshot(String agentId, Long userId, String source, AgentSnapshotDataDTO snapshotData,
|
||||||
|
List<String> changedFields, String restoreFromSnapshotId, Integer restoreFromVersionNo) {
|
||||||
|
AgentSnapshotEntity entity = new AgentSnapshotEntity();
|
||||||
|
entity.setAgentId(agentId);
|
||||||
|
entity.setUserId(userId);
|
||||||
|
entity.setVersionNo(allocateVersionNo(agentId));
|
||||||
|
entity.setSnapshotData(JsonUtils.toJsonString(redactSnapshotData(snapshotData)));
|
||||||
|
entity.setChangedFields(JsonUtils.toJsonString(changedFields));
|
||||||
|
entity.setSource(StringUtils.defaultIfBlank(source, "config"));
|
||||||
|
entity.setRestoreFromSnapshotId(restoreFromSnapshotId);
|
||||||
|
entity.setRestoreFromVersionNo(restoreFromVersionNo);
|
||||||
|
entity.setCreator(SecurityUser.getUserId());
|
||||||
|
entity.setCreatedAt(new Date());
|
||||||
|
agentSnapshotDao.insert(entity);
|
||||||
|
pruneSnapshots(agentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureInitialSnapshot(String agentId) {
|
||||||
|
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
||||||
|
if (maxVersionNo != null && maxVersionNo > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AgentEntity agent = agentDao.selectByIdForUpdate(agentId);
|
||||||
|
if (agent == null) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
@@ -163,12 +232,19 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AgentSnapshotDataDTO buildSnapshotData(String agentId) {
|
private AgentInfoVO getAgentInfo(String agentId) {
|
||||||
AgentInfoVO agent = agentDao.selectAgentInfoById(agentId);
|
AgentInfoVO agent = agentDao.selectAgentInfoById(agentId);
|
||||||
if (agent == null) {
|
if (agent == null) {
|
||||||
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentSnapshotDataDTO buildSnapshotData(String agentId) {
|
||||||
|
return buildSnapshotData(getAgentInfo(agentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentSnapshotDataDTO buildSnapshotData(AgentInfoVO agent) {
|
||||||
AgentSnapshotDataDTO data = new AgentSnapshotDataDTO();
|
AgentSnapshotDataDTO data = new AgentSnapshotDataDTO();
|
||||||
data.setAgentCode(agent.getAgentCode());
|
data.setAgentCode(agent.getAgentCode());
|
||||||
data.setAgentName(agent.getAgentName());
|
data.setAgentName(agent.getAgentName());
|
||||||
@@ -192,9 +268,9 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
data.setLanguage(agent.getLanguage());
|
data.setLanguage(agent.getLanguage());
|
||||||
data.setSort(agent.getSort());
|
data.setSort(agent.getSort());
|
||||||
data.setFunctions(toFunctionInfo(agent.getFunctions()));
|
data.setFunctions(toFunctionInfo(agent.getFunctions()));
|
||||||
data.setContextProviders(getContextProviders(agentId));
|
data.setContextProviders(getContextProviders(agent.getId()));
|
||||||
data.setCorrectWordFileIds(nullToEmpty(correctWordFileService.getAgentCorrectWordFileIds(agentId)));
|
data.setCorrectWordFileIds(nullToEmpty(correctWordFileService.getAgentCorrectWordFileIds(agent.getId())));
|
||||||
List<AgentSnapshotTagDTO> tags = getSnapshotTags(agentId);
|
List<AgentSnapshotTagDTO> tags = getSnapshotTags(agent.getId());
|
||||||
data.setTags(tags);
|
data.setTags(tags);
|
||||||
data.setTagNames(tags.stream().map(AgentSnapshotTagDTO::getTagName).toList());
|
data.setTagNames(tags.stream().map(AgentSnapshotTagDTO::getTagName).toList());
|
||||||
return data;
|
return data;
|
||||||
@@ -250,64 +326,124 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> fields = new ArrayList<>();
|
List<String> fields = new ArrayList<>();
|
||||||
compare(fields, "agentCode", current.getAgentCode(), pendingUpdate.getAgentCode());
|
for (AgentSnapshotField field : AgentSnapshotField.values()) {
|
||||||
compare(fields, "agentName", current.getAgentName(), pendingUpdate.getAgentName());
|
Object nextValue = field.updateValue(pendingUpdate);
|
||||||
compare(fields, "asrModelId", current.getAsrModelId(), pendingUpdate.getAsrModelId());
|
if (nextValue != null && isChanged(field, field.snapshotValue(current), nextValue)) {
|
||||||
compare(fields, "vadModelId", current.getVadModelId(), pendingUpdate.getVadModelId());
|
fields.add(field.getFieldName());
|
||||||
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;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> void compare(List<String> fields, String name, T current, T next) {
|
private List<String> getChangedFields(AgentSnapshotDataDTO current, AgentSnapshotDataDTO next) {
|
||||||
if (next != null && !Objects.equals(current, next)) {
|
if (next == null) {
|
||||||
fields.add(name);
|
return List.of("restore");
|
||||||
}
|
}
|
||||||
|
if (current == null) {
|
||||||
|
current = new AgentSnapshotDataDTO();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> fields = new ArrayList<>();
|
||||||
|
for (AgentSnapshotField field : AgentSnapshotField.values()) {
|
||||||
|
if (isChanged(field, field.snapshotValue(current), field.snapshotValue(next))) {
|
||||||
|
fields.add(field.getFieldName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isChanged(AgentSnapshotField field, Object current, Object next) {
|
||||||
|
return !Objects.equals(normalizeForCompare(field, current), normalizeForCompare(field, next));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Object normalizeForCompare(AgentSnapshotField field, Object value) {
|
||||||
|
return switch (field) {
|
||||||
|
case FUNCTIONS -> normalizeFunctions((List<AgentUpdateDTO.FunctionInfo>) value);
|
||||||
|
case CONTEXT_PROVIDERS -> normalizeSortedJsonList((List<?>) value);
|
||||||
|
case CORRECT_WORD_FILE_IDS -> normalizeStringList((List<String>) value);
|
||||||
|
case TAG_NAMES -> normalizeStringList((List<String>) value);
|
||||||
|
default -> value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> normalizeFunctions(List<AgentUpdateDTO.FunctionInfo> functions) {
|
||||||
|
Map<String, Object> result = new TreeMap<>();
|
||||||
|
if (functions == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (AgentUpdateDTO.FunctionInfo function : functions) {
|
||||||
|
if (function == null || StringUtils.isBlank(function.getPluginId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.put(function.getPluginId(), normalizeMap(function.getParamInfo()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> normalizeStringList(List<String> values) {
|
||||||
|
if (values == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return values.stream()
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.sorted()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> List<String> normalizeSortedJsonList(List<T> values) {
|
||||||
|
if (values == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return values.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(value -> JsonUtils.toJsonString(normalizeValue(value)))
|
||||||
|
.sorted()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object normalizeValue(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof Map<?, ?> map) {
|
||||||
|
return normalizeMap(map);
|
||||||
|
}
|
||||||
|
if (value instanceof List<?> list) {
|
||||||
|
return list.stream().map(this::normalizeValue).toList();
|
||||||
|
}
|
||||||
|
if (isScalarValue(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return normalizeMap(JsonUtils.parseObject(JsonUtils.toJsonString(value), OBJECT_MAP_TYPE));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isScalarValue(Object value) {
|
||||||
|
return value instanceof CharSequence
|
||||||
|
|| value instanceof Number
|
||||||
|
|| value instanceof Boolean
|
||||||
|
|| value instanceof Character
|
||||||
|
|| value instanceof Enum<?>
|
||||||
|
|| value instanceof Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> normalizeMap(Map<?, ?> map) {
|
||||||
|
Map<String, Object> result = new TreeMap<>();
|
||||||
|
if (map == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
map.forEach((key, value) -> {
|
||||||
|
if (key != null) {
|
||||||
|
result.put(String.valueOf(key), normalizeValue(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyAgentFields(AgentEntity agent, AgentSnapshotDataDTO data) {
|
private void applyAgentFields(AgentEntity agent, AgentSnapshotDataDTO data) {
|
||||||
agent.setAgentCode(data.getAgentCode());
|
for (AgentSnapshotField field : AgentSnapshotField.values()) {
|
||||||
agent.setAgentName(data.getAgentName());
|
field.applyTo(agent, data);
|
||||||
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) {
|
private void validateRestoreParams(AgentEntity agent) {
|
||||||
@@ -406,26 +542,22 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
AgentTagEntity tag = null;
|
AgentTagEntity tag = null;
|
||||||
if (StringUtils.isNotBlank(snapshotTag.getId())) {
|
if (StringUtils.isNotBlank(snapshotTag.getId())) {
|
||||||
tag = agentTagDao.selectById(snapshotTag.getId());
|
tag = agentTagDao.selectById(snapshotTag.getId());
|
||||||
|
if (tag != null && Objects.equals(tag.getDeleted(), 1)) {
|
||||||
|
tag = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (tag == null) {
|
if (tag == null) {
|
||||||
tag = agentTagDao.selectOne(new QueryWrapper<AgentTagEntity>()
|
tag = agentTagDao.selectOne(new QueryWrapper<AgentTagEntity>()
|
||||||
.eq("tag_name", snapshotTag.getTagName())
|
.eq("tag_name", snapshotTag.getTagName())
|
||||||
|
.eq("deleted", 0)
|
||||||
.last("LIMIT 1"));
|
.last("LIMIT 1"));
|
||||||
}
|
}
|
||||||
if (tag != null) {
|
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();
|
return tag.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
AgentTagEntity newTag = new AgentTagEntity();
|
AgentTagEntity newTag = new AgentTagEntity();
|
||||||
if (StringUtils.isNotBlank(snapshotTag.getId())) {
|
newTag.setId(UUID.randomUUID().toString().replace("-", ""));
|
||||||
newTag.setId(snapshotTag.getId());
|
|
||||||
}
|
|
||||||
newTag.setTagName(snapshotTag.getTagName());
|
newTag.setTagName(snapshotTag.getTagName());
|
||||||
newTag.setSort(snapshotTag.getSort() == null ? 0 : snapshotTag.getSort());
|
newTag.setSort(snapshotTag.getSort() == null ? 0 : snapshotTag.getSort());
|
||||||
newTag.setDeleted(0);
|
newTag.setDeleted(0);
|
||||||
@@ -444,12 +576,16 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
vo.setUserId(entity.getUserId());
|
vo.setUserId(entity.getUserId());
|
||||||
vo.setVersionNo(entity.getVersionNo());
|
vo.setVersionNo(entity.getVersionNo());
|
||||||
vo.setChangedFields(parseChangedFields(entity.getChangedFields()));
|
vo.setChangedFields(parseChangedFields(entity.getChangedFields()));
|
||||||
|
vo.setFieldOrder(AgentSnapshotField.names());
|
||||||
vo.setSource(entity.getSource());
|
vo.setSource(entity.getSource());
|
||||||
|
vo.setRestoreFromSnapshotId(entity.getRestoreFromSnapshotId());
|
||||||
|
vo.setRestoreFromVersionNo(entity.getRestoreFromVersionNo());
|
||||||
vo.setCreator(entity.getCreator());
|
vo.setCreator(entity.getCreator());
|
||||||
vo.setCreatedAt(entity.getCreatedAt());
|
vo.setCreatedAt(entity.getCreatedAt());
|
||||||
if (includeData) {
|
if (includeData) {
|
||||||
vo.setSnapshotData(JsonUtils.parseObject(entity.getSnapshotData(), AgentSnapshotDataDTO.class));
|
vo.setSnapshotData(redactSnapshotData(JsonUtils.parseObject(entity.getSnapshotData(),
|
||||||
vo.setAfterSnapshotData(getAfterSnapshotData(entity));
|
AgentSnapshotDataDTO.class)));
|
||||||
|
vo.setAfterSnapshotData(redactSnapshotData(getAfterSnapshotData(entity)));
|
||||||
}
|
}
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
@@ -459,7 +595,7 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
if (nextSnapshot != null) {
|
if (nextSnapshot != null) {
|
||||||
return JsonUtils.parseObject(nextSnapshot.getSnapshotData(), AgentSnapshotDataDTO.class);
|
return JsonUtils.parseObject(nextSnapshot.getSnapshotData(), AgentSnapshotDataDTO.class);
|
||||||
}
|
}
|
||||||
return buildSnapshotData(entity.getAgentId());
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> parseChangedFields(String changedFields) {
|
private List<String> parseChangedFields(String changedFields) {
|
||||||
@@ -469,6 +605,158 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
|||||||
return JsonUtils.parseObject(changedFields, STRING_LIST_TYPE);
|
return JsonUtils.parseObject(changedFields, STRING_LIST_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AgentSnapshotDataDTO redactSnapshotData(AgentSnapshotDataDTO data) {
|
||||||
|
if (data == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AgentSnapshotDataDTO copy = JsonUtils.parseObject(JsonUtils.toJsonString(data), AgentSnapshotDataDTO.class);
|
||||||
|
if (copy.getFunctions() != null) {
|
||||||
|
copy.getFunctions().forEach(function -> {
|
||||||
|
if (function != null) {
|
||||||
|
function.setParamInfo(redactSensitiveMap(function.getParamInfo()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (copy.getContextProviders() != null) {
|
||||||
|
copy.getContextProviders().forEach(provider -> {
|
||||||
|
if (provider != null) {
|
||||||
|
provider.setHeaders(redactSensitiveMap(provider.getHeaders()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentSnapshotDataDTO preserveCurrentSensitiveValues(AgentSnapshotDataDTO target, AgentSnapshotDataDTO current) {
|
||||||
|
if (target == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AgentSnapshotDataDTO copy = JsonUtils.parseObject(JsonUtils.toJsonString(target), AgentSnapshotDataDTO.class);
|
||||||
|
Map<String, AgentUpdateDTO.FunctionInfo> currentFunctions = nullToEmpty(current == null ? null : current.getFunctions())
|
||||||
|
.stream()
|
||||||
|
.filter(function -> function != null && StringUtils.isNotBlank(function.getPluginId()))
|
||||||
|
.collect(Collectors.toMap(AgentUpdateDTO.FunctionInfo::getPluginId, Function.identity(),
|
||||||
|
(left, right) -> left));
|
||||||
|
if (copy.getFunctions() != null) {
|
||||||
|
copy.getFunctions().forEach(function -> {
|
||||||
|
if (function == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AgentUpdateDTO.FunctionInfo currentFunction = currentFunctions.get(function.getPluginId());
|
||||||
|
function.setParamInfo(preserveCurrentSensitiveMap(function.getParamInfo(),
|
||||||
|
currentFunction == null ? null : currentFunction.getParamInfo()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, xiaozhi.modules.agent.dto.ContextProviderDTO> currentProviders = nullToEmpty(
|
||||||
|
current == null ? null : current.getContextProviders())
|
||||||
|
.stream()
|
||||||
|
.filter(provider -> provider != null && StringUtils.isNotBlank(provider.getUrl()))
|
||||||
|
.collect(Collectors.toMap(xiaozhi.modules.agent.dto.ContextProviderDTO::getUrl, Function.identity(),
|
||||||
|
(left, right) -> left));
|
||||||
|
if (copy.getContextProviders() != null) {
|
||||||
|
copy.getContextProviders().forEach(provider -> {
|
||||||
|
if (provider == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xiaozhi.modules.agent.dto.ContextProviderDTO currentProvider = currentProviders.get(provider.getUrl());
|
||||||
|
provider.setHeaders(preserveCurrentSensitiveMap(provider.getHeaders(),
|
||||||
|
currentProvider == null ? null : currentProvider.getHeaders()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<String, Object> redactSensitiveMap(Map<?, ?> map) {
|
||||||
|
HashMap<String, Object> result = new HashMap<>();
|
||||||
|
if (map == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
map.forEach((key, value) -> {
|
||||||
|
if (key != null) {
|
||||||
|
String keyText = String.valueOf(key);
|
||||||
|
result.put(keyText, isSensitiveKey(keyText) ? SECRET_PLACEHOLDER : redactSensitiveValue(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object redactSensitiveValue(Object value) {
|
||||||
|
if (value instanceof Map<?, ?> map) {
|
||||||
|
return redactSensitiveMap(map);
|
||||||
|
}
|
||||||
|
if (value instanceof List<?> list) {
|
||||||
|
return list.stream().map(this::redactSensitiveValue).toList();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<String, Object> preserveCurrentSensitiveMap(Map<?, ?> target, Map<?, ?> current) {
|
||||||
|
HashMap<String, Object> result = new HashMap<>();
|
||||||
|
if (target == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
target.forEach((key, value) -> {
|
||||||
|
if (key == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String keyText = String.valueOf(key);
|
||||||
|
Object currentValue = getMapValue(current, keyText);
|
||||||
|
if (isSensitiveKey(keyText)) {
|
||||||
|
result.put(keyText, currentValue == null || SECRET_PLACEHOLDER.equals(currentValue) ? "" : currentValue);
|
||||||
|
} else {
|
||||||
|
result.put(keyText, preserveCurrentSensitiveValue(value, currentValue));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object preserveCurrentSensitiveValue(Object target, Object current) {
|
||||||
|
if (target instanceof Map<?, ?> targetMap) {
|
||||||
|
return preserveCurrentSensitiveMap(targetMap, current instanceof Map<?, ?> currentMap ? currentMap : null);
|
||||||
|
}
|
||||||
|
if (target instanceof List<?> targetList) {
|
||||||
|
List<?> currentList = current instanceof List<?> list ? list : Collections.emptyList();
|
||||||
|
List<Object> result = new ArrayList<>();
|
||||||
|
for (int i = 0; i < targetList.size(); i++) {
|
||||||
|
Object currentItem = i < currentList.size() ? currentList.get(i) : null;
|
||||||
|
result.add(preserveCurrentSensitiveValue(targetList.get(i), currentItem));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getMapValue(Map<?, ?> map, String key) {
|
||||||
|
if (map == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (map.containsKey(key)) {
|
||||||
|
return map.get(key);
|
||||||
|
}
|
||||||
|
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||||
|
if (entry.getKey() != null && Objects.equals(key, String.valueOf(entry.getKey()))) {
|
||||||
|
return entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSensitiveKey(String key) {
|
||||||
|
String normalized = StringUtils.defaultString(key).toLowerCase().replaceAll("[^a-z0-9]", "");
|
||||||
|
return normalized.equals("authorization")
|
||||||
|
|| normalized.equals("token")
|
||||||
|
|| normalized.endsWith("token")
|
||||||
|
|| normalized.contains("apikey")
|
||||||
|
|| normalized.contains("appkey")
|
||||||
|
|| normalized.contains("accesskey")
|
||||||
|
|| normalized.contains("privatekey")
|
||||||
|
|| normalized.contains("password")
|
||||||
|
|| normalized.contains("passwd")
|
||||||
|
|| normalized.contains("secret")
|
||||||
|
|| normalized.contains("credential");
|
||||||
|
}
|
||||||
|
|
||||||
private <T> List<T> nullToEmpty(List<T> list) {
|
private <T> List<T> nullToEmpty(List<T> list) {
|
||||||
return list == null ? Collections.emptyList() : list;
|
return list == null ? Collections.emptyList() : list;
|
||||||
}
|
}
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package xiaozhi.modules.agent.typehandler;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
import xiaozhi.modules.agent.dto.ContextProviderDTO;
|
||||||
|
|
||||||
|
public class ContextProviderListTypeHandler extends AbstractJsonTypeHandler<List<ContextProviderDTO>> {
|
||||||
|
private static final TypeReference<List<ContextProviderDTO>> CONTEXT_PROVIDER_LIST_TYPE = new TypeReference<>() {
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<ContextProviderDTO> parse(String json) {
|
||||||
|
if (StringUtils.isBlank(json)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<ContextProviderDTO> providers = JsonUtils.parseObject(json, CONTEXT_PROVIDER_LIST_TYPE);
|
||||||
|
return providers == null ? Collections.emptyList() : providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String toJson(List<ContextProviderDTO> obj) {
|
||||||
|
return JsonUtils.toJsonString(obj == null ? Collections.emptyList() : obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,4 +25,7 @@ public class AgentInfoVO extends AgentEntity
|
|||||||
|
|
||||||
@Schema(description = "替换词文件ID列表")
|
@Schema(description = "替换词文件ID列表")
|
||||||
private List<String> correctWordFileIds;
|
private List<String> correctWordFileIds;
|
||||||
|
|
||||||
|
@Schema(description = "当前配置版本号")
|
||||||
|
private Integer currentVersionNo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,17 @@ import xiaozhi.modules.agent.dto.AgentSnapshotDataDTO;
|
|||||||
public class AgentSnapshotVO {
|
public class AgentSnapshotVO {
|
||||||
private String id;
|
private String id;
|
||||||
private String agentId;
|
private String agentId;
|
||||||
|
@Schema(description = "所属用户ID,表示该快照归属的智能体所有者")
|
||||||
private Long userId;
|
private Long userId;
|
||||||
private Integer versionNo;
|
private Integer versionNo;
|
||||||
private List<String> changedFields;
|
private List<String> changedFields;
|
||||||
|
private List<String> fieldOrder;
|
||||||
private String source;
|
private String source;
|
||||||
|
@Schema(description = "恢复来源快照ID,仅恢复前自动备份版本有值")
|
||||||
|
private String restoreFromSnapshotId;
|
||||||
|
@Schema(description = "恢复来源版本号,仅恢复前自动备份版本有值")
|
||||||
|
private Integer restoreFromVersionNo;
|
||||||
|
@Schema(description = "创建者,表示触发本次快照写入的操作人")
|
||||||
private Long creator;
|
private Long creator;
|
||||||
private Date createdAt;
|
private Date createdAt;
|
||||||
private AgentSnapshotDataDTO snapshotData;
|
private AgentSnapshotDataDTO snapshotData;
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- 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`);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- liquibase formatted sql
|
||||||
|
|
||||||
|
-- changeset codex:202607081230
|
||||||
|
ALTER TABLE `ai_agent_snapshot`
|
||||||
|
ALTER COLUMN `snapshot_data` SET DEFAULT (JSON_OBJECT());
|
||||||
@@ -704,3 +704,17 @@ databaseChangeLog:
|
|||||||
- 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
|
||||||
|
|||||||
@@ -36,9 +36,12 @@
|
|||||||
<result column="updater" property="updater"/>
|
<result column="updater" property="updater"/>
|
||||||
<result column="updatedAt" property="updatedAt"/>
|
<result column="updatedAt" property="updatedAt"/>
|
||||||
<collection property="functions"
|
<collection property="functions"
|
||||||
ofType="xiaozhi.modules.agent.entity.AgentPluginMapping"
|
ofType="xiaozhi.modules.agent.entity.AgentPluginMapping">
|
||||||
column="{agentId=id}"
|
<id column="functionId" property="id"/>
|
||||||
select="selectAgentFunctionsByAgentId"/>
|
<result column="functionAgentId" property="agentId"/>
|
||||||
|
<result column="pluginId" property="pluginId"/>
|
||||||
|
<result column="paramInfo" property="paramInfo"/>
|
||||||
|
</collection>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="selectAgentInfoById" resultMap="AgentInfoMap">
|
<select id="selectAgentInfoById" resultMap="AgentInfoMap">
|
||||||
@@ -68,17 +71,22 @@
|
|||||||
a.creator,
|
a.creator,
|
||||||
a.created_at AS createdAt,
|
a.created_at AS createdAt,
|
||||||
a.updater,
|
a.updater,
|
||||||
a.updated_at AS updatedAt
|
a.updated_at AS updatedAt,
|
||||||
|
f.id AS functionId,
|
||||||
|
f.agent_id AS functionAgentId,
|
||||||
|
f.plugin_id AS pluginId,
|
||||||
|
f.param_info AS paramInfo
|
||||||
FROM ai_agent a
|
FROM ai_agent a
|
||||||
|
LEFT JOIN ai_agent_plugin_mapping f ON f.agent_id = a.id
|
||||||
WHERE a.id = #{agentId}
|
WHERE a.id = #{agentId}
|
||||||
|
ORDER BY f.id ASC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectAgentFunctionsByAgentId" resultType="xiaozhi.modules.agent.entity.AgentPluginMapping">
|
<select id="selectByIdForUpdate" resultType="xiaozhi.modules.agent.entity.AgentEntity">
|
||||||
SELECT id,
|
SELECT *
|
||||||
agent_id AS agentId,
|
FROM ai_agent
|
||||||
plugin_id AS pluginId,
|
WHERE id = #{agentId}
|
||||||
param_info AS paramInfo
|
FOR UPDATE
|
||||||
FROM ai_agent_plugin_mapping
|
|
||||||
WHERE agent_id = #{agentId}
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
snapshot_data AS snapshotData,
|
snapshot_data AS snapshotData,
|
||||||
changed_fields AS changedFields,
|
changed_fields AS changedFields,
|
||||||
source,
|
source,
|
||||||
|
restore_from_snapshot_id AS restoreFromSnapshotId,
|
||||||
|
restore_from_version_no AS restoreFromVersionNo,
|
||||||
creator,
|
creator,
|
||||||
created_at AS createdAt
|
created_at AS createdAt
|
||||||
FROM ai_agent_snapshot
|
FROM ai_agent_snapshot
|
||||||
@@ -25,4 +27,19 @@
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<delete id="deleteOlderThanKeepLimit">
|
||||||
|
DELETE FROM ai_agent_snapshot
|
||||||
|
WHERE agent_id = #{agentId}
|
||||||
|
AND id NOT IN (
|
||||||
|
SELECT id
|
||||||
|
FROM (
|
||||||
|
SELECT id
|
||||||
|
FROM ai_agent_snapshot
|
||||||
|
WHERE agent_id = #{agentId}
|
||||||
|
ORDER BY version_no DESC
|
||||||
|
LIMIT #{keepLimit}
|
||||||
|
) retained_snapshots
|
||||||
|
)
|
||||||
|
</delete>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class AgentUpdateDTOTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void functionInfoAcceptsJsonStringParamInfo() {
|
||||||
|
AgentUpdateDTO.FunctionInfo info = new AgentUpdateDTO.FunctionInfo();
|
||||||
|
|
||||||
|
info.setParamInfo("{\"api_key\":\"abc\",\"max_results\":5}");
|
||||||
|
|
||||||
|
assertEquals("abc", info.getParamInfo().get("api_key"));
|
||||||
|
assertEquals(5, info.getParamInfo().get("max_results"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void functionInfoNormalizesMapKeysToStrings() {
|
||||||
|
AgentUpdateDTO.FunctionInfo info = new AgentUpdateDTO.FunctionInfo();
|
||||||
|
Map<Object, Object> params = new LinkedHashMap<>();
|
||||||
|
params.put("api_key", "abc");
|
||||||
|
params.put(42, true);
|
||||||
|
|
||||||
|
info.setParamInfo(params);
|
||||||
|
|
||||||
|
assertEquals("abc", info.getParamInfo().get("api_key"));
|
||||||
|
assertEquals(true, info.getParamInfo().get("42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void functionInfoFallsBackToEmptyMapForBlankParamInfo() {
|
||||||
|
AgentUpdateDTO.FunctionInfo info = new AgentUpdateDTO.FunctionInfo();
|
||||||
|
|
||||||
|
info.setParamInfo(" ");
|
||||||
|
|
||||||
|
assertTrue(info.getParamInfo().isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
+380
@@ -0,0 +1,380 @@
|
|||||||
|
package xiaozhi.modules.agent.service.impl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
import xiaozhi.modules.agent.Enums.AgentSnapshotField;
|
||||||
|
import xiaozhi.modules.agent.dao.AgentDao;
|
||||||
|
import xiaozhi.modules.agent.dao.AgentSnapshotDao;
|
||||||
|
import xiaozhi.modules.agent.dao.AgentTagDao;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotDataDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotPageDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentSnapshotTagDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.ContextProviderDTO;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTagEntity;
|
||||||
|
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
||||||
|
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
||||||
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
|
import xiaozhi.modules.correctword.service.CorrectWordFileService;
|
||||||
|
|
||||||
|
class AgentSnapshotServiceImplTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void normalizeSortedJsonListTreatsDtoAndMapShapesEqually() throws Exception {
|
||||||
|
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(null, null, null, null, null, null, null, null,
|
||||||
|
null, null);
|
||||||
|
Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("normalizeSortedJsonList", List.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
|
||||||
|
ContextProviderDTO dto = new ContextProviderDTO();
|
||||||
|
dto.setUrl("https://example.com/context");
|
||||||
|
dto.setHeaders(Map.of("Authorization", "Bearer token"));
|
||||||
|
|
||||||
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
|
map.put("headers", Map.of("Authorization", "Bearer token"));
|
||||||
|
map.put("url", "https://example.com/context");
|
||||||
|
|
||||||
|
assertEquals(method.invoke(service, List.of(dto)), method.invoke(service, List.of(map)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void redactSnapshotDataMasksSensitiveFunctionAndHeaderValues() throws Exception {
|
||||||
|
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(null, null, null, null, null, null, null, null,
|
||||||
|
null, null);
|
||||||
|
Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("redactSnapshotData",
|
||||||
|
AgentSnapshotDataDTO.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
|
||||||
|
AgentSnapshotDataDTO data = new AgentSnapshotDataDTO();
|
||||||
|
AgentUpdateDTO.FunctionInfo function = new AgentUpdateDTO.FunctionInfo();
|
||||||
|
function.setPluginId("rag");
|
||||||
|
function.setParamInfo(Map.of("api_key", "secret-key", "max_tokens", 32));
|
||||||
|
ContextProviderDTO provider = new ContextProviderDTO();
|
||||||
|
provider.setUrl("https://example.com/context");
|
||||||
|
provider.setHeaders(Map.of("Authorization", "Bearer secret-token", "Accept", "application/json"));
|
||||||
|
data.setFunctions(List.of(function));
|
||||||
|
data.setContextProviders(List.of(provider));
|
||||||
|
|
||||||
|
AgentSnapshotDataDTO redacted = (AgentSnapshotDataDTO) method.invoke(service, data);
|
||||||
|
String json = JsonUtils.toJsonString(redacted);
|
||||||
|
|
||||||
|
assertFalse(json.contains("secret-key"));
|
||||||
|
assertFalse(json.contains("secret-token"));
|
||||||
|
assertTrue(json.contains("__SNAPSHOT_SECRET_REDACTED__"));
|
||||||
|
assertTrue(json.contains("max_tokens"));
|
||||||
|
assertTrue(json.contains("application/json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void preserveCurrentSensitiveValuesKeepsCurrentSecretsWhenRestoringSnapshot() throws Exception {
|
||||||
|
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(null, null, null, null, null, null, null, null,
|
||||||
|
null, null);
|
||||||
|
Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("preserveCurrentSensitiveValues",
|
||||||
|
AgentSnapshotDataDTO.class, AgentSnapshotDataDTO.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
|
||||||
|
AgentSnapshotDataDTO snapshot = buildSnapshot("old-key", "old-token", "old-value");
|
||||||
|
AgentSnapshotDataDTO current = buildSnapshot("current-key", "current-token", "current-value");
|
||||||
|
|
||||||
|
AgentSnapshotDataDTO restored = (AgentSnapshotDataDTO) method.invoke(service, snapshot, current);
|
||||||
|
|
||||||
|
assertEquals("current-key", restored.getFunctions().get(0).getParamInfo().get("api_key"));
|
||||||
|
assertEquals("old-value", restored.getFunctions().get(0).getParamInfo().get("description"));
|
||||||
|
assertEquals("current-token", restored.getContextProviders().get(0).getHeaders().get("Authorization"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void getChangedFieldsFollowsCentralFieldOrder() throws Exception {
|
||||||
|
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(null, null, null, null, null, null, null, null,
|
||||||
|
null, null);
|
||||||
|
Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("getChangedFields",
|
||||||
|
AgentSnapshotDataDTO.class, AgentUpdateDTO.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
|
||||||
|
AgentSnapshotDataDTO current = new AgentSnapshotDataDTO();
|
||||||
|
current.setAgentName("old-name");
|
||||||
|
current.setCorrectWordFileIds(List.of("b", "a"));
|
||||||
|
|
||||||
|
AgentUpdateDTO pendingUpdate = new AgentUpdateDTO();
|
||||||
|
pendingUpdate.setAgentName("new-name");
|
||||||
|
pendingUpdate.setCorrectWordFileIds(List.of("a", "b"));
|
||||||
|
AgentUpdateDTO.FunctionInfo function = new AgentUpdateDTO.FunctionInfo();
|
||||||
|
function.setPluginId("rag");
|
||||||
|
function.setParamInfo(Map.of("api_key", "secret-key"));
|
||||||
|
pendingUpdate.setFunctions(List.of(function));
|
||||||
|
|
||||||
|
List<String> changedFields = (List<String>) method.invoke(service, current, pendingUpdate);
|
||||||
|
|
||||||
|
assertEquals(List.of(AgentSnapshotField.AGENT_NAME.getFieldName(),
|
||||||
|
AgentSnapshotField.FUNCTIONS.getFieldName()), changedFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void tagIdsAreNotExposedAsIndependentSnapshotFields() throws Exception {
|
||||||
|
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(null, null, null, null, null, null, null, null,
|
||||||
|
null, null);
|
||||||
|
Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("getChangedFields",
|
||||||
|
AgentSnapshotDataDTO.class, AgentUpdateDTO.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
|
||||||
|
AgentSnapshotDataDTO current = new AgentSnapshotDataDTO();
|
||||||
|
current.setTagNames(List.of("alpha", "beta"));
|
||||||
|
|
||||||
|
AgentUpdateDTO pendingUpdate = new AgentUpdateDTO();
|
||||||
|
pendingUpdate.setTagNames(List.of("beta", "alpha"));
|
||||||
|
pendingUpdate.setTagIds(List.of("new-id"));
|
||||||
|
|
||||||
|
List<String> changedFields = (List<String>) method.invoke(service, current, pendingUpdate);
|
||||||
|
|
||||||
|
assertFalse(AgentSnapshotField.names().contains("tagIds"));
|
||||||
|
assertFalse(changedFields.contains("tagIds"));
|
||||||
|
assertTrue(changedFields.isEmpty());
|
||||||
|
|
||||||
|
pendingUpdate.setTagNames(List.of("alpha", "gamma"));
|
||||||
|
changedFields = (List<String>) method.invoke(service, current, pendingUpdate);
|
||||||
|
|
||||||
|
assertEquals(List.of(AgentSnapshotField.TAG_NAMES.getFieldName()), changedFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tagOnlySavePathCreatesSnapshot() throws Exception {
|
||||||
|
String controller = Files.readString(Path.of("src/main/java/xiaozhi/modules/agent/controller/AgentController.java"));
|
||||||
|
String roleConfig = Files.readString(Path.of("../manager-web/src/views/roleConfig.vue"));
|
||||||
|
|
||||||
|
assertTrue(controller.contains("agentService.updateAgentById(id, dto);"));
|
||||||
|
assertFalse(controller.contains("agentService.updateAgentById(id, dto, false);"));
|
||||||
|
assertTrue(roleConfig.contains("configData.tagNames = tagNames;"));
|
||||||
|
assertFalse(roleConfig.contains("this.handleSaveAgentTags(agentId, tagNames)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void agentSnapshotFieldAppliesRestorableAgentFields() {
|
||||||
|
AgentSnapshotDataDTO data = new AgentSnapshotDataDTO();
|
||||||
|
data.setAgentName("restored-name");
|
||||||
|
data.setLlmModelId("llm-new");
|
||||||
|
data.setSystemPrompt("restored prompt");
|
||||||
|
data.setSort(7);
|
||||||
|
|
||||||
|
AgentEntity agent = new AgentEntity();
|
||||||
|
for (AgentSnapshotField field : AgentSnapshotField.values()) {
|
||||||
|
field.applyTo(agent, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("restored-name", agent.getAgentName());
|
||||||
|
assertEquals("llm-new", agent.getLlmModelId());
|
||||||
|
assertEquals("restored prompt", agent.getSystemPrompt());
|
||||||
|
assertEquals(7, agent.getSort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void restoreSnapshotRollsBackForAnyException() throws Exception {
|
||||||
|
Method method = AgentSnapshotServiceImpl.class.getMethod("restoreSnapshot", String.class, String.class);
|
||||||
|
|
||||||
|
Transactional transactional = method.getAnnotation(Transactional.class);
|
||||||
|
|
||||||
|
assertNotNull(transactional);
|
||||||
|
assertTrue(List.of(transactional.rollbackFor()).contains(Exception.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deleteSnapshotRollsBackForAnyException() throws Exception {
|
||||||
|
Method method = AgentSnapshotServiceImpl.class.getMethod("deleteSnapshot", String.class, String.class);
|
||||||
|
|
||||||
|
Transactional transactional = method.getAnnotation(Transactional.class);
|
||||||
|
|
||||||
|
assertNotNull(transactional);
|
||||||
|
assertTrue(List.of(transactional.rollbackFor()).contains(Exception.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void versionAllocationDoesNotDependOnSequenceTable() throws Exception {
|
||||||
|
String xml = 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 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"));
|
||||||
|
assertFalse(xml.contains("LAST_INSERT_ID"));
|
||||||
|
assertFalse(dao.contains("allocateVersionNo"));
|
||||||
|
assertFalse(dao.contains("selectLastInsertId"));
|
||||||
|
assertFalse(createMigration.contains("ai_agent_snapshot_sequence"));
|
||||||
|
assertFalse(master.contains("202607081430.sql"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void snapshotMigrationAddsRestoreTraceAndUserIndex() throws Exception {
|
||||||
|
String sql = Files.readString(Path.of("src/main/resources/db/changelog/202607081150.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"));
|
||||||
|
|
||||||
|
assertTrue(sql.contains("restore_from_snapshot_id"));
|
||||||
|
assertTrue(sql.contains("restore_from_version_no"));
|
||||||
|
assertTrue(sql.contains("idx_snapshot_user_created_at"));
|
||||||
|
assertTrue(master.contains("202607081150.sql"));
|
||||||
|
assertTrue(defaultSql.contains("DEFAULT (JSON_OBJECT())"));
|
||||||
|
assertTrue(master.contains("202607081230.sql"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void selectNextSnapshotLoadsRestoreTraceColumns() throws Exception {
|
||||||
|
String xml = 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_version_no AS restoreFromVersionNo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void retentionPruneSqlKeepsLatestVersions() throws Exception {
|
||||||
|
String xml = Files.readString(Path.of("src/main/resources/mapper/agent/AgentSnapshotDao.xml"));
|
||||||
|
|
||||||
|
assertTrue(xml.contains("deleteOlderThanKeepLimit"));
|
||||||
|
assertTrue(xml.contains("ORDER BY version_no DESC"));
|
||||||
|
assertTrue(xml.contains("LIMIT #{keepLimit}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void agentInfoMapperLoadsFunctionsWithJoinInsteadOfNestedSelect() throws Exception {
|
||||||
|
String xml = Files.readString(Path.of("src/main/resources/mapper/agent/AgentDao.xml"));
|
||||||
|
|
||||||
|
assertTrue(xml.contains("LEFT JOIN ai_agent_plugin_mapping f ON f.agent_id = a.id"));
|
||||||
|
assertFalse(xml.contains("selectAgentFunctionsByAgentId"));
|
||||||
|
assertFalse(xml.contains("select=\"selectAgentFunctions"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toVOIncludesRestoreTraceFields() throws Exception {
|
||||||
|
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(null, null, null, null, null, null, null, null,
|
||||||
|
null, null);
|
||||||
|
Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("toVO",
|
||||||
|
AgentSnapshotEntity.class, boolean.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
|
||||||
|
AgentSnapshotEntity entity = new AgentSnapshotEntity();
|
||||||
|
entity.setId("snapshot-id");
|
||||||
|
entity.setAgentId("agent-id");
|
||||||
|
entity.setVersionNo(3);
|
||||||
|
entity.setChangedFields("[]");
|
||||||
|
entity.setSource("restore");
|
||||||
|
entity.setRestoreFromSnapshotId("target-id");
|
||||||
|
entity.setRestoreFromVersionNo(1);
|
||||||
|
|
||||||
|
AgentSnapshotVO vo = (AgentSnapshotVO) method.invoke(service, entity, false);
|
||||||
|
|
||||||
|
assertEquals("target-id", vo.getRestoreFromSnapshotId());
|
||||||
|
assertEquals(1, vo.getRestoreFromVersionNo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void pageCreatesInitialSnapshotWhenAgentHasNoHistory() {
|
||||||
|
AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class);
|
||||||
|
AgentDao agentDao = mock(AgentDao.class);
|
||||||
|
AgentTagDao tagDao = mock(AgentTagDao.class);
|
||||||
|
AgentContextProviderService contextProviderService = mock(AgentContextProviderService.class);
|
||||||
|
CorrectWordFileService correctWordFileService = mock(CorrectWordFileService.class);
|
||||||
|
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(snapshotDao, agentDao, tagDao, null, null,
|
||||||
|
contextProviderService, null, null, null, correctWordFileService);
|
||||||
|
|
||||||
|
AgentEntity lockedAgent = new AgentEntity();
|
||||||
|
lockedAgent.setId("agent-id");
|
||||||
|
AgentInfoVO agentInfo = new AgentInfoVO();
|
||||||
|
agentInfo.setId("agent-id");
|
||||||
|
agentInfo.setUserId(7L);
|
||||||
|
agentInfo.setAgentName("agent");
|
||||||
|
when(snapshotDao.selectMaxVersionNo("agent-id")).thenReturn(0, 0, 0);
|
||||||
|
when(agentDao.selectByIdForUpdate("agent-id")).thenReturn(lockedAgent);
|
||||||
|
when(agentDao.selectAgentInfoById("agent-id")).thenReturn(agentInfo);
|
||||||
|
when(correctWordFileService.getAgentCorrectWordFileIds("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());
|
||||||
|
|
||||||
|
verify(snapshotDao, times(3)).selectMaxVersionNo("agent-id");
|
||||||
|
verify(snapshotDao).insert(argThat(snapshot -> Integer.valueOf(1).equals(snapshot.getVersionNo())));
|
||||||
|
verify(snapshotDao).deleteOlderThanKeepLimit("agent-id", 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void restoreTagDoesNotReviveSoftDeletedSnapshotTagId() throws Exception {
|
||||||
|
AgentTagDao tagDao = mock(AgentTagDao.class);
|
||||||
|
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(null, null, tagDao, null, null, null, null, null,
|
||||||
|
null, null);
|
||||||
|
Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("restoreTag",
|
||||||
|
AgentSnapshotTagDTO.class, Date.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
|
||||||
|
AgentTagEntity deletedTag = new AgentTagEntity();
|
||||||
|
deletedTag.setId("deleted-tag-id");
|
||||||
|
deletedTag.setTagName("archived");
|
||||||
|
deletedTag.setDeleted(1);
|
||||||
|
when(tagDao.selectById("deleted-tag-id")).thenReturn(deletedTag);
|
||||||
|
when(tagDao.selectOne(any())).thenReturn(null);
|
||||||
|
AtomicReference<AgentTagEntity> insertedTag = new AtomicReference<>();
|
||||||
|
when(tagDao.insert(any())).thenAnswer(invocation -> {
|
||||||
|
insertedTag.set(invocation.getArgument(0));
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
AgentSnapshotTagDTO snapshotTag = new AgentSnapshotTagDTO();
|
||||||
|
snapshotTag.setId("deleted-tag-id");
|
||||||
|
snapshotTag.setTagName("archived");
|
||||||
|
|
||||||
|
String restoredTagId = (String) method.invoke(service, snapshotTag, new Date());
|
||||||
|
|
||||||
|
verify(tagDao, never()).updateById(any());
|
||||||
|
assertNotEquals("deleted-tag-id", restoredTagId);
|
||||||
|
assertNotNull(insertedTag.get());
|
||||||
|
assertEquals(restoredTagId, insertedTag.get().getId());
|
||||||
|
assertEquals(0, insertedTag.get().getDeleted());
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentSnapshotDataDTO buildSnapshot(String apiKey, String authToken, String description) {
|
||||||
|
AgentSnapshotDataDTO data = new AgentSnapshotDataDTO();
|
||||||
|
|
||||||
|
AgentUpdateDTO.FunctionInfo function = new AgentUpdateDTO.FunctionInfo();
|
||||||
|
function.setPluginId("rag");
|
||||||
|
HashMap<String, Object> params = new HashMap<>();
|
||||||
|
params.put("api_key", apiKey);
|
||||||
|
params.put("description", description);
|
||||||
|
function.setParamInfo(params);
|
||||||
|
|
||||||
|
ContextProviderDTO provider = new ContextProviderDTO();
|
||||||
|
provider.setUrl("https://example.com/context");
|
||||||
|
provider.setHeaders(Map.of("Authorization", authToken));
|
||||||
|
|
||||||
|
data.setFunctions(List.of(function));
|
||||||
|
data.setContextProviders(List.of(provider));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package xiaozhi.modules.agent.typehandler;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.ibatis.type.TypeHandler;
|
||||||
|
import org.apache.ibatis.type.TypeHandlerRegistry;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import xiaozhi.modules.agent.dto.ContextProviderDTO;
|
||||||
|
|
||||||
|
class ContextProviderListTypeHandlerTest {
|
||||||
|
|
||||||
|
private final ContextProviderListTypeHandler handler = new ContextProviderListTypeHandler();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseKeepsContextProviderDtoElementType() {
|
||||||
|
List<ContextProviderDTO> providers = handler
|
||||||
|
.parse("[{\"url\":\"https://example.com/context\",\"headers\":{\"Authorization\":\"Bearer token\"}}]");
|
||||||
|
|
||||||
|
assertEquals(1, providers.size());
|
||||||
|
assertInstanceOf(ContextProviderDTO.class, providers.get(0));
|
||||||
|
assertEquals("https://example.com/context", providers.get(0).getUrl());
|
||||||
|
assertEquals("Bearer token", providers.get(0).getHeaders().get("Authorization"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseBlankJsonAsEmptyList() {
|
||||||
|
assertTrue(handler.parse(" ").isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void myBatisCanInstantiateHandlerForListField() {
|
||||||
|
TypeHandler<?> typeHandler = new TypeHandlerRegistry().getInstance(List.class,
|
||||||
|
ContextProviderListTypeHandler.class);
|
||||||
|
|
||||||
|
assertInstanceOf(ContextProviderListTypeHandler.class, typeHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -169,6 +169,28 @@ function handleInputConfirm() {
|
|||||||
// 是否禁用历史记忆输入框
|
// 是否禁用历史记忆输入框
|
||||||
const isMemoryDisabled = computed(() => formData.value.memModelId !== 'Memory_mem_local_short')
|
const isMemoryDisabled = computed(() => formData.value.memModelId !== 'Memory_mem_local_short')
|
||||||
|
|
||||||
|
function normalizeFunctionParams(paramInfo: any) {
|
||||||
|
if (!paramInfo)
|
||||||
|
return {}
|
||||||
|
if (typeof paramInfo === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(paramInfo)
|
||||||
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return typeof paramInfo === 'object' && !Array.isArray(paramInfo) ? { ...paramInfo } : {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAgentFunctions(functions: any[] = []) {
|
||||||
|
return functions.map(item => ({
|
||||||
|
...item,
|
||||||
|
paramInfo: normalizeFunctionParams(item.paramInfo),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
// 打开上下文源编辑弹窗
|
// 打开上下文源编辑弹窗
|
||||||
function openContextProviderDialog() {
|
function openContextProviderDialog() {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
@@ -191,11 +213,12 @@ async function loadAgentDetail() {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
tempSummaryMemory.value = ''
|
tempSummaryMemory.value = ''
|
||||||
const detail = await getAgentDetail(agentId.value)
|
const detail = await getAgentDetail(agentId.value)
|
||||||
formData.value = { ...detail }
|
const normalizedFunctions = normalizeAgentFunctions(detail.functions || [])
|
||||||
|
formData.value = { ...detail, functions: normalizedFunctions }
|
||||||
|
|
||||||
// 更新插件store
|
// 更新插件store
|
||||||
pluginStore.setCurrentAgentId(agentId.value)
|
pluginStore.setCurrentAgentId(agentId.value)
|
||||||
pluginStore.setCurrentFunctions(detail.functions || [])
|
pluginStore.setCurrentFunctions(normalizedFunctions)
|
||||||
|
|
||||||
// 更新语速音调
|
// 更新语速音调
|
||||||
speedPitchStore.updateSpeedPitch({
|
speedPitchStore.updateSpeedPitch({
|
||||||
@@ -626,6 +649,7 @@ async function saveAgent() {
|
|||||||
...speedPitchStore.speedPitch,
|
...speedPitchStore.speedPitch,
|
||||||
ttsLanguage: formData.value.language,
|
ttsLanguage: formData.value.language,
|
||||||
contextProviders: providerStore.providers,
|
contextProviders: providerStore.providers,
|
||||||
|
functions: normalizeAgentFunctions(formData.value.functions || []),
|
||||||
}
|
}
|
||||||
await updateAgent(agentId.value, saveData)
|
await updateAgent(agentId.value, saveData)
|
||||||
loadAgentDetail()
|
loadAgentDetail()
|
||||||
@@ -663,7 +687,7 @@ function handleTools() {
|
|||||||
|
|
||||||
// 确保store中有最新数据
|
// 确保store中有最新数据
|
||||||
pluginStore.setCurrentAgentId(agentId.value)
|
pluginStore.setCurrentAgentId(agentId.value)
|
||||||
pluginStore.setCurrentFunctions(formData.value.functions || [])
|
pluginStore.setCurrentFunctions(normalizeAgentFunctions(formData.value.functions || []))
|
||||||
pluginStore.setAllFunctions(allFunctions.value)
|
pluginStore.setAllFunctions(allFunctions.value)
|
||||||
|
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
@@ -688,7 +712,7 @@ async function handleUpdateAgentTags() {
|
|||||||
|
|
||||||
// 监听store中的插件配置变化
|
// 监听store中的插件配置变化
|
||||||
watch(() => pluginStore.currentFunctions, (newFunctions) => {
|
watch(() => pluginStore.currentFunctions, (newFunctions) => {
|
||||||
formData.value.functions = newFunctions
|
formData.value.functions = normalizeAgentFunctions(newFunctions || [])
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -41,17 +41,32 @@ const tempParams = ref<Record<string, any>>({})
|
|||||||
const arrayTextCache = ref<Record<string, string>>({})
|
const arrayTextCache = ref<Record<string, string>>({})
|
||||||
const jsonTextCache = ref<Record<string, string>>({})
|
const jsonTextCache = ref<Record<string, string>>({})
|
||||||
|
|
||||||
|
function normalizeFunctionParams(paramInfo: any, fallback: Record<string, any> = {}) {
|
||||||
|
if (!paramInfo)
|
||||||
|
return { ...fallback }
|
||||||
|
if (typeof paramInfo === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(paramInfo)
|
||||||
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : { ...fallback }
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return { ...fallback }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return typeof paramInfo === 'object' && !Array.isArray(paramInfo) ? { ...fallback, ...paramInfo } : { ...fallback }
|
||||||
|
}
|
||||||
|
|
||||||
async function mergeFunctions() {
|
async function mergeFunctions() {
|
||||||
selectedList.value = functions.value.map((mapping) => {
|
selectedList.value = functions.value.map((mapping) => {
|
||||||
const meta = allFunctions.value.find(f => f.id === mapping.pluginId)
|
const meta = allFunctions.value.find(f => f.id === mapping.pluginId)
|
||||||
if (!meta) {
|
if (!meta) {
|
||||||
return { id: mapping.pluginId, name: mapping.pluginId, params: {} }
|
return { id: mapping.pluginId, name: mapping.pluginId, params: normalizeFunctionParams(mapping.paramInfo) }
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: mapping.pluginId,
|
id: mapping.pluginId,
|
||||||
name: meta.name,
|
name: meta.name,
|
||||||
params: mapping.paramInfo || { ...meta.params },
|
params: normalizeFunctionParams(mapping.paramInfo, meta.params),
|
||||||
fieldsMeta: meta.fieldsMeta,
|
fieldsMeta: meta.fieldsMeta,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -91,7 +106,7 @@ function selectFunction(func: any) {
|
|||||||
selectedList.value = [...selectedList.value, {
|
selectedList.value = [...selectedList.value, {
|
||||||
id: func.id,
|
id: func.id,
|
||||||
name: func.name,
|
name: func.name,
|
||||||
params: { ...func.params },
|
params: normalizeFunctionParams(func.params),
|
||||||
fieldsMeta: func.fieldsMeta,
|
fieldsMeta: func.fieldsMeta,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@@ -118,7 +133,7 @@ function editFunction(func: any) {
|
|||||||
currentFunction.value = func
|
currentFunction.value = func
|
||||||
|
|
||||||
// 直接使用当前函数的参数
|
// 直接使用当前函数的参数
|
||||||
tempParams.value = { ...func.params }
|
tempParams.value = normalizeFunctionParams(func.params)
|
||||||
|
|
||||||
// 初始化文本缓存
|
// 初始化文本缓存
|
||||||
if (func.fieldsMeta) {
|
if (func.fieldsMeta) {
|
||||||
@@ -252,7 +267,7 @@ function getFieldRemark(field: any) {
|
|||||||
watch(() => selectedList.value, (newSelectedList) => {
|
watch(() => selectedList.value, (newSelectedList) => {
|
||||||
const finalFunctions = newSelectedList.map(f => ({
|
const finalFunctions = newSelectedList.map(f => ({
|
||||||
pluginId: f.id,
|
pluginId: f.id,
|
||||||
paramInfo: f.params,
|
paramInfo: normalizeFunctionParams(f.params),
|
||||||
}))
|
}))
|
||||||
pluginStore.updateFunctions(finalFunctions)
|
pluginStore.updateFunctions(finalFunctions)
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+10
@@ -11096,6 +11096,16 @@
|
|||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||||
|
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/validate-npm-package-license": {
|
"node_modules/validate-npm-package-license": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||||
|
|||||||
@@ -127,6 +127,21 @@ export default {
|
|||||||
});
|
});
|
||||||
}).send();
|
}).send();
|
||||||
},
|
},
|
||||||
|
// 删除智能体配置快照
|
||||||
|
deleteAgentSnapshot(agentId, snapshotId, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/agent/${agentId}/snapshots/${snapshotId}`)
|
||||||
|
.method('DELETE')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime();
|
||||||
|
callback(res);
|
||||||
|
})
|
||||||
|
.networkFail(() => {
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.deleteAgentSnapshot(agentId, snapshotId, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
},
|
||||||
// 新增方法:获取智能体模板
|
// 新增方法:获取智能体模板
|
||||||
getAgentTemplate(callback) { // 移除templateName参数
|
getAgentTemplate(callback) { // 移除templateName参数
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:visible="visible"
|
:visible="visible"
|
||||||
width="760px"
|
width="760px"
|
||||||
class="agent-snapshot-dialog"
|
class="agent-snapshot-dialog"
|
||||||
@open="fetchSnapshots"
|
@open="open"
|
||||||
@close="close"
|
@close="close"
|
||||||
>
|
>
|
||||||
<el-table
|
<el-table
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
<span v-else class="field-tags-empty">—</span>
|
<span v-else class="field-tags-empty">—</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('agentSnapshot.actions')" width="150" fixed="right">
|
<el-table-column :label="$t('agentSnapshot.actions')" width="190" fixed="right">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canViewSnapshot(scope.row)"
|
v-if="canViewSnapshot(scope.row)"
|
||||||
@@ -84,6 +84,16 @@
|
|||||||
>
|
>
|
||||||
{{ $t('agentSnapshot.restore') }}
|
{{ $t('agentSnapshot.restore') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="canDeleteSnapshot(scope.row)"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
class="snapshot-delete-button"
|
||||||
|
:loading="deletingSnapshotId === scope.row.id"
|
||||||
|
@click="deleteSnapshot(scope.row)"
|
||||||
|
>
|
||||||
|
{{ $t('agentSnapshot.delete') }}
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -185,7 +195,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="value-empty">功能配置无变化</div>
|
<div v-else class="value-empty">{{ $t('agentSnapshot.noFunctionChange') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="diff-values" :class="{ 'is-complex': item.complex }">
|
<div v-else class="diff-values" :class="{ 'is-complex': item.complex }">
|
||||||
<div class="diff-pane diff-before">
|
<div class="diff-pane diff-before">
|
||||||
@@ -306,7 +316,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="value-empty">功能配置无变化</div>
|
<div v-else class="value-empty">{{ $t('agentSnapshot.noFunctionChange') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="diff-values" :class="{ 'is-complex': item.complex }">
|
<div v-else class="diff-values" :class="{ 'is-complex': item.complex }">
|
||||||
<div class="diff-pane diff-before">
|
<div class="diff-pane diff-before">
|
||||||
@@ -339,6 +349,22 @@
|
|||||||
:description="$t('agentSnapshot.noChangedContent')"
|
:description="$t('agentSnapshot.noChangedContent')"
|
||||||
:image-size="80"
|
:image-size="80"
|
||||||
/>
|
/>
|
||||||
|
<el-alert
|
||||||
|
v-if="restorePreviewSnapshot"
|
||||||
|
class="restore-risk-alert"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
:title="$t('agentSnapshot.restoreConfirm', { version: restoreTargetVersion })"
|
||||||
|
/>
|
||||||
|
<el-alert
|
||||||
|
v-if="restoreWillClearChatHistory"
|
||||||
|
class="restore-risk-alert"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
:title="$t('agentSnapshot.restoreMemoryWarning')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span slot="footer" class="dialog-footer">
|
<span slot="footer" class="dialog-footer">
|
||||||
<el-button @click="restorePreviewVisible = false">
|
<el-button @click="restorePreviewVisible = false">
|
||||||
@@ -361,33 +387,33 @@
|
|||||||
import Api from "@/apis/api";
|
import Api from "@/apis/api";
|
||||||
import { formatDate } from "@/utils/date";
|
import { formatDate } from "@/utils/date";
|
||||||
|
|
||||||
const FALLBACK_PLUGIN_NAMES = {
|
const FALLBACK_PLUGIN_NAME_KEYS = {
|
||||||
SYSTEM_PLUGIN_WEATHER: "天气查询",
|
SYSTEM_PLUGIN_WEATHER: "agentSnapshot.plugin.SYSTEM_PLUGIN_WEATHER",
|
||||||
SYSTEM_PLUGIN_MUSIC: "服务器音乐播放",
|
SYSTEM_PLUGIN_MUSIC: "agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC",
|
||||||
SYSTEM_PLUGIN_NEWS_CHINANEWS: "中新网新闻",
|
SYSTEM_PLUGIN_NEWS_CHINANEWS: "agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS",
|
||||||
SYSTEM_PLUGIN_NEWS_NEWSNOW: "newsnow新闻聚合",
|
SYSTEM_PLUGIN_NEWS_NEWSNOW: "agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW",
|
||||||
SYSTEM_PLUGIN_HA_GET_STATE: "HomeAssistant设备状态查询",
|
SYSTEM_PLUGIN_HA_GET_STATE: "agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE",
|
||||||
SYSTEM_PLUGIN_HA_SET_STATE: "HomeAssistant设备状态修改",
|
SYSTEM_PLUGIN_HA_SET_STATE: "agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE",
|
||||||
SYSTEM_PLUGIN_HA_PLAY_MUSIC: "HomeAssistant音乐播放",
|
SYSTEM_PLUGIN_HA_PLAY_MUSIC: "agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC",
|
||||||
SYSTEM_PLUGIN_WEB_SEARCH: "联网搜索",
|
SYSTEM_PLUGIN_WEB_SEARCH: "agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH",
|
||||||
SYSTEM_PLUGIN_CALL_DEVICE: "设备呼叫设备"
|
SYSTEM_PLUGIN_CALL_DEVICE: "agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE"
|
||||||
};
|
};
|
||||||
|
|
||||||
const FALLBACK_FIELD_LABELS = {
|
const FALLBACK_FIELD_LABEL_KEYS = {
|
||||||
url: "接口地址",
|
url: "agentSnapshot.pluginField.url",
|
||||||
news_sources: "新闻源配置",
|
news_sources: "agentSnapshot.pluginField.news_sources",
|
||||||
default_rss_url: "默认 RSS 源",
|
default_rss_url: "agentSnapshot.pluginField.default_rss_url",
|
||||||
society_rss_url: "社会新闻 RSS 地址",
|
society_rss_url: "agentSnapshot.pluginField.society_rss_url",
|
||||||
world_rss_url: "国际新闻 RSS 地址",
|
world_rss_url: "agentSnapshot.pluginField.world_rss_url",
|
||||||
finance_rss_url: "财经新闻 RSS 地址",
|
finance_rss_url: "agentSnapshot.pluginField.finance_rss_url",
|
||||||
api_key: "API 密钥",
|
api_key: "agentSnapshot.pluginField.api_key",
|
||||||
default_location: "默认查询城市",
|
default_location: "agentSnapshot.pluginField.default_location",
|
||||||
api_host: "开发者 API Host",
|
api_host: "agentSnapshot.pluginField.api_host",
|
||||||
base_url: "服务器地址",
|
base_url: "agentSnapshot.pluginField.base_url",
|
||||||
devices: "设备列表",
|
devices: "agentSnapshot.pluginField.devices",
|
||||||
provider: "搜索源",
|
provider: "agentSnapshot.pluginField.provider",
|
||||||
description: "工具描述",
|
description: "agentSnapshot.pluginField.description",
|
||||||
max_results: "返回数量"
|
max_results: "agentSnapshot.pluginField.max_results"
|
||||||
};
|
};
|
||||||
|
|
||||||
const MODEL_FIELD_TYPES = {
|
const MODEL_FIELD_TYPES = {
|
||||||
@@ -403,29 +429,29 @@ const MODEL_FIELD_TYPES = {
|
|||||||
|
|
||||||
const MODEL_TYPES = ["ASR", "VAD", "LLM", "SLM", "VLLM", "TTS", "Memory", "Intent"];
|
const MODEL_TYPES = ["ASR", "VAD", "LLM", "SLM", "VLLM", "TTS", "Memory", "Intent"];
|
||||||
|
|
||||||
const FALLBACK_MODEL_NAMES = {
|
const FALLBACK_MODEL_NAME_KEYS = {
|
||||||
Memory_nomem: "无记忆",
|
Memory_nomem: "agentSnapshot.model.Memory_nomem",
|
||||||
Memory_mem_local_short: "本地短记忆",
|
Memory_mem_local_short: "agentSnapshot.model.Memory_mem_local_short",
|
||||||
Memory_mem0ai: "Mem0AI记忆",
|
Memory_mem0ai: "agentSnapshot.model.Memory_mem0ai",
|
||||||
Memory_mem_report_only: "仅上报记忆",
|
Memory_mem_report_only: "agentSnapshot.model.Memory_mem_report_only",
|
||||||
Intent_nointent: "无意图识别",
|
Intent_nointent: "agentSnapshot.model.Intent_nointent",
|
||||||
Intent_intent_llm: "外挂的大模型意图识别",
|
Intent_intent_llm: "agentSnapshot.model.Intent_intent_llm",
|
||||||
Intent_function_call: "大模型自主函数调用"
|
Intent_function_call: "agentSnapshot.model.Intent_function_call"
|
||||||
};
|
};
|
||||||
|
|
||||||
const FALLBACK_VOICE_NAMES = {
|
const FALLBACK_VOICE_NAME_KEYS = {
|
||||||
TTS_EdgeTTS0001: "EdgeTTS女声-晓晓",
|
TTS_EdgeTTS0001: "agentSnapshot.voice.TTS_EdgeTTS0001",
|
||||||
TTS_EdgeTTS0002: "EdgeTTS男声-云扬",
|
TTS_EdgeTTS0002: "agentSnapshot.voice.TTS_EdgeTTS0002",
|
||||||
TTS_EdgeTTS0003: "EdgeTTS女声-晓伊",
|
TTS_EdgeTTS0003: "agentSnapshot.voice.TTS_EdgeTTS0003",
|
||||||
TTS_EdgeTTS0004: "EdgeTTS男声-云健",
|
TTS_EdgeTTS0004: "agentSnapshot.voice.TTS_EdgeTTS0004",
|
||||||
TTS_EdgeTTS0005: "EdgeTTS男声-云希",
|
TTS_EdgeTTS0005: "agentSnapshot.voice.TTS_EdgeTTS0005",
|
||||||
TTS_EdgeTTS0006: "EdgeTTS男声-云夏"
|
TTS_EdgeTTS0006: "agentSnapshot.voice.TTS_EdgeTTS0006"
|
||||||
};
|
};
|
||||||
|
|
||||||
const CHAT_HISTORY_CONF_LABELS = {
|
const CHAT_HISTORY_CONF_LABEL_KEYS = {
|
||||||
0: "不记录聊天记录",
|
0: "agentSnapshot.chatHistoryConf.none",
|
||||||
1: "上报文字",
|
1: "agentSnapshot.chatHistoryConf.text",
|
||||||
2: "上报文字+语音"
|
2: "agentSnapshot.chatHistoryConf.textVoice"
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -456,16 +482,22 @@ export default {
|
|||||||
currentSnapshot: null,
|
currentSnapshot: null,
|
||||||
restorePreviewSnapshot: null,
|
restorePreviewSnapshot: null,
|
||||||
restorePreviewRow: null,
|
restorePreviewRow: null,
|
||||||
|
deletingSnapshotId: null,
|
||||||
pluginMetadata: {},
|
pluginMetadata: {},
|
||||||
pluginMetadataLoaded: false,
|
pluginMetadataLoaded: false,
|
||||||
pluginMetadataLoading: null,
|
pluginMetadataLoading: null,
|
||||||
modelNameMap: {},
|
modelNameMap: {},
|
||||||
modelMetadataLoaded: false,
|
modelMetadataLoaded: false,
|
||||||
modelMetadataLoading: null,
|
modelMetadataLoading: null,
|
||||||
voiceNameMap: { ...FALLBACK_VOICE_NAMES },
|
voiceNameMap: {},
|
||||||
voiceMetadataLoading: {},
|
voiceMetadataLoading: {},
|
||||||
loadedVoiceModelIds: {},
|
loadedVoiceModelIds: {},
|
||||||
snapshotFetchSeq: 0,
|
snapshotFetchSeq: 0,
|
||||||
|
detailFetchSeq: 0,
|
||||||
|
restorePreviewFetchSeq: 0,
|
||||||
|
restoreActionSeq: 0,
|
||||||
|
deleteActionSeq: 0,
|
||||||
|
historyAnchorVersionNo: null,
|
||||||
historyTotal: 0,
|
historyTotal: 0,
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
@@ -503,10 +535,11 @@ export default {
|
|||||||
const beforeData = this.currentSnapshot.snapshotData || {};
|
const beforeData = this.currentSnapshot.snapshotData || {};
|
||||||
const afterData = this.currentSnapshot.afterSnapshotData || {};
|
const afterData = this.currentSnapshot.afterSnapshotData || {};
|
||||||
return this.buildDiffs(beforeData, afterData, this.currentSnapshot.changedFields || [], {
|
return this.buildDiffs(beforeData, afterData, this.currentSnapshot.changedFields || [], {
|
||||||
beforeLabel: "变化前",
|
beforeLabel: this.$t("agentSnapshot.beforeChange"),
|
||||||
afterLabel: "变化后",
|
afterLabel: this.$t("agentSnapshot.afterChange"),
|
||||||
beforeVersionNo: this.currentSnapshot.beforeVersionNo,
|
beforeVersionNo: this.currentSnapshot.beforeVersionNo,
|
||||||
afterVersionNo: this.currentSnapshot.afterVersionNo || this.currentSnapshot.versionNo
|
afterVersionNo: this.currentSnapshot.afterVersionNo || this.currentSnapshot.versionNo,
|
||||||
|
fieldOrder: this.currentSnapshot.fieldOrder
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
restorePreviewDiffs() {
|
restorePreviewDiffs() {
|
||||||
@@ -519,17 +552,35 @@ export default {
|
|||||||
this.restorePreviewSnapshot.afterSnapshotData || {},
|
this.restorePreviewSnapshot.afterSnapshotData || {},
|
||||||
[],
|
[],
|
||||||
{
|
{
|
||||||
beforeLabel: "恢复前",
|
beforeLabel: this.$t("agentSnapshot.beforeRestore"),
|
||||||
afterLabel: "恢复后",
|
afterLabel: this.$t("agentSnapshot.afterRestore"),
|
||||||
beforeVersionNo: this.restorePreviewSnapshot.beforeVersionNo || this.resolveCurrentVersionNo(),
|
beforeVersionNo: this.restorePreviewSnapshot.beforeVersionNo || this.resolveCurrentVersionNo(),
|
||||||
afterVersionNo: this.restorePreviewSnapshot.afterVersionNo || this.restorePreviewSnapshot.versionNo
|
afterVersionNo: this.restorePreviewSnapshot.afterVersionNo || this.restorePreviewSnapshot.versionNo,
|
||||||
|
fieldOrder: this.restorePreviewSnapshot.fieldOrder
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
restoreWillClearChatHistory() {
|
||||||
|
return this.restorePreviewSnapshot?.afterSnapshotData?.memModelId === "Memory_nomem";
|
||||||
|
},
|
||||||
|
restoreTargetVersion() {
|
||||||
|
return this.restorePreviewSnapshot?.versionNo || this.restorePreviewRow?.versionNo || "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
currentVersionNo(newValue, oldValue) {
|
||||||
|
if (this.visible && newValue !== oldValue) {
|
||||||
|
this.historyAnchorVersionNo = null;
|
||||||
|
this.fetchSnapshots({ clearTable: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.cancelPendingSnapshotRequests();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
buildDiffs(beforeData, afterData, changedFields, options = {}) {
|
buildDiffs(beforeData, afterData, changedFields, options = {}) {
|
||||||
const fields = this.resolveDiffFields(beforeData, afterData, changedFields);
|
const fields = this.resolveDiffFields(beforeData, afterData, changedFields, options.fieldOrder);
|
||||||
return fields.map((field) => {
|
return fields.map((field) => {
|
||||||
const beforeValue = this.getFieldValue(beforeData, field);
|
const beforeValue = this.getFieldValue(beforeData, field);
|
||||||
const afterValue = this.getFieldValue(afterData, field);
|
const afterValue = this.getFieldValue(afterData, field);
|
||||||
@@ -552,10 +603,27 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.snapshotFetchSeq += 1;
|
this.cancelPendingSnapshotRequests();
|
||||||
this.loading = false;
|
this.historyAnchorVersionNo = null;
|
||||||
this.$emit("update:visible", false);
|
this.$emit("update:visible", false);
|
||||||
},
|
},
|
||||||
|
open() {
|
||||||
|
this.historyAnchorVersionNo = null;
|
||||||
|
this.page = 1;
|
||||||
|
this.fetchSnapshots();
|
||||||
|
},
|
||||||
|
cancelPendingSnapshotRequests() {
|
||||||
|
this.snapshotFetchSeq += 1;
|
||||||
|
this.detailFetchSeq += 1;
|
||||||
|
this.restorePreviewFetchSeq += 1;
|
||||||
|
this.restoreActionSeq += 1;
|
||||||
|
this.deleteActionSeq += 1;
|
||||||
|
this.loading = false;
|
||||||
|
this.detailLoading = false;
|
||||||
|
this.restorePreviewLoading = false;
|
||||||
|
this.restoring = false;
|
||||||
|
this.deletingSnapshotId = null;
|
||||||
|
},
|
||||||
snapshotRowKey(row) {
|
snapshotRowKey(row) {
|
||||||
if (row?.isCurrentVersion) {
|
if (row?.isCurrentVersion) {
|
||||||
return "current-version";
|
return "current-version";
|
||||||
@@ -566,11 +634,14 @@ export default {
|
|||||||
return row?.isCurrentVersion ? "current-version-row" : "";
|
return row?.isCurrentVersion ? "current-version-row" : "";
|
||||||
},
|
},
|
||||||
canViewSnapshot(row) {
|
canViewSnapshot(row) {
|
||||||
return Number(row?.versionNo) > 1;
|
return !!row && (row.isCurrentVersion || row.previousSnapshotId || row.previousVersionNo);
|
||||||
},
|
},
|
||||||
canRestoreSnapshot(row) {
|
canRestoreSnapshot(row) {
|
||||||
return !!row && !row.isCurrentVersion;
|
return !!row && !row.isCurrentVersion;
|
||||||
},
|
},
|
||||||
|
canDeleteSnapshot(row) {
|
||||||
|
return !!row && !row.isCurrentVersion && !row.isLatestSnapshot;
|
||||||
|
},
|
||||||
fetchSnapshots(options = {}) {
|
fetchSnapshots(options = {}) {
|
||||||
if (!this.agentId) {
|
if (!this.agentId) {
|
||||||
this.snapshots = [];
|
this.snapshots = [];
|
||||||
@@ -582,7 +653,7 @@ export default {
|
|||||||
const requestSeq = ++this.snapshotFetchSeq;
|
const requestSeq = ++this.snapshotFetchSeq;
|
||||||
const displayStart = (this.page - 1) * this.limit;
|
const displayStart = (this.page - 1) * this.limit;
|
||||||
const displayEnd = displayStart + this.limit;
|
const displayEnd = displayStart + this.limit;
|
||||||
const historyFetchLimit = Math.max(displayEnd, this.limit, 1);
|
const historyFetchLimit = Math.max(displayEnd + 1, this.limit, 1);
|
||||||
this.ensurePluginMetadata();
|
this.ensurePluginMetadata();
|
||||||
this.ensureModelMetadata();
|
this.ensureModelMetadata();
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -591,24 +662,32 @@ export default {
|
|||||||
}
|
}
|
||||||
Api.agent.getAgentSnapshots(
|
Api.agent.getAgentSnapshots(
|
||||||
this.agentId,
|
this.agentId,
|
||||||
{ page: "1", limit: String(historyFetchLimit) },
|
this.snapshotQueryParams({ page: "1", limit: String(historyFetchLimit) }),
|
||||||
({ data }) => {
|
({ data }) => {
|
||||||
if (requestSeq !== this.snapshotFetchSeq) {
|
if (requestSeq !== this.snapshotFetchSeq) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
const historyRows = data.data?.list || [];
|
const historyRows = this.decorateSnapshotRows(data.data?.list || []);
|
||||||
|
if (!this.historyAnchorVersionNo && 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 + 1;
|
this.total = this.historyTotal + historyOffset;
|
||||||
if (this.page === 1) {
|
if (this.page === 1) {
|
||||||
|
const firstPageRows = historyRows.slice(0, currentRow ? this.limit - 1 : this.limit);
|
||||||
this.snapshots = [
|
this.snapshots = [
|
||||||
this.buildCurrentVersionRow(historyRows[0]),
|
...(currentRow ? [currentRow] : []),
|
||||||
...this.applyPreviousChangedFields(historyRows.slice(0, this.limit - 1), historyRows)
|
...this.applyPreviousChangedFields(firstPageRows, historyRows)
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
const historyStart = Math.max(displayStart - historyOffset, 0);
|
||||||
|
const historyEnd = Math.max(displayEnd - historyOffset, 0);
|
||||||
this.snapshots = this.applyPreviousChangedFields(
|
this.snapshots = this.applyPreviousChangedFields(
|
||||||
historyRows.slice(displayStart - 1, displayEnd - 1),
|
historyRows.slice(historyStart, historyEnd),
|
||||||
historyRows
|
historyRows
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -629,18 +708,27 @@ export default {
|
|||||||
this.detailVisible = true;
|
this.detailVisible = true;
|
||||||
this.detailLoading = true;
|
this.detailLoading = true;
|
||||||
this.currentSnapshot = null;
|
this.currentSnapshot = null;
|
||||||
|
const requestSeq = ++this.detailFetchSeq;
|
||||||
this.ensurePluginMetadata();
|
this.ensurePluginMetadata();
|
||||||
this.ensureModelMetadata();
|
this.ensureModelMetadata();
|
||||||
this.buildVersionDiffSnapshot(row)
|
this.buildVersionDiffSnapshot(row)
|
||||||
.then((snapshot) => this.ensureDisplayMetadata(snapshot).then(() => snapshot))
|
.then((snapshot) => this.ensureDisplayMetadata(snapshot).then(() => snapshot))
|
||||||
.then((snapshot) => {
|
.then((snapshot) => {
|
||||||
|
if (requestSeq !== this.detailFetchSeq) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.currentSnapshot = snapshot;
|
this.currentSnapshot = snapshot;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
if (requestSeq !== this.detailFetchSeq) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$message.error(this.$t("agentSnapshot.detailFailed"));
|
this.$message.error(this.$t("agentSnapshot.detailFailed"));
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.detailLoading = false;
|
if (requestSeq === this.detailFetchSeq) {
|
||||||
|
this.detailLoading = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
restoreSnapshot(row) {
|
restoreSnapshot(row) {
|
||||||
@@ -651,6 +739,7 @@ export default {
|
|||||||
this.restorePreviewLoading = true;
|
this.restorePreviewLoading = true;
|
||||||
this.restorePreviewRow = row;
|
this.restorePreviewRow = row;
|
||||||
this.restorePreviewSnapshot = null;
|
this.restorePreviewSnapshot = null;
|
||||||
|
const requestSeq = ++this.restorePreviewFetchSeq;
|
||||||
this.ensurePluginMetadata();
|
this.ensurePluginMetadata();
|
||||||
this.ensureModelMetadata();
|
this.ensureModelMetadata();
|
||||||
|
|
||||||
@@ -658,41 +747,97 @@ export default {
|
|||||||
this.fetchSnapshotDetail(row.id),
|
this.fetchSnapshotDetail(row.id),
|
||||||
this.fetchCurrentAgentData()
|
this.fetchCurrentAgentData()
|
||||||
]).then(([targetSnapshot, currentData]) => {
|
]).then(([targetSnapshot, currentData]) => {
|
||||||
|
if (requestSeq !== this.restorePreviewFetchSeq) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
const previewSnapshot = {
|
const previewSnapshot = {
|
||||||
...targetSnapshot,
|
...targetSnapshot,
|
||||||
beforeSnapshotData: currentData,
|
beforeSnapshotData: currentData,
|
||||||
afterSnapshotData: targetSnapshot.snapshotData || {},
|
afterSnapshotData: targetSnapshot.snapshotData || {},
|
||||||
beforeVersionNo: this.resolveCurrentVersionNo(),
|
beforeVersionNo: this.resolveCurrentVersionNo(),
|
||||||
afterVersionNo: targetSnapshot.versionNo
|
afterVersionNo: targetSnapshot.versionNo,
|
||||||
|
fieldOrder: targetSnapshot.fieldOrder || []
|
||||||
};
|
};
|
||||||
return this.ensureDisplayMetadata(previewSnapshot).then(() => {
|
return this.ensureDisplayMetadata(previewSnapshot).then(() => {
|
||||||
|
if (requestSeq !== this.restorePreviewFetchSeq) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.restorePreviewSnapshot = previewSnapshot;
|
this.restorePreviewSnapshot = previewSnapshot;
|
||||||
});
|
});
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
if (requestSeq !== this.restorePreviewFetchSeq) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.restorePreviewVisible = false;
|
this.restorePreviewVisible = false;
|
||||||
this.$message.error(this.$t("agentSnapshot.detailFailed"));
|
this.$message.error(this.$t("agentSnapshot.detailFailed"));
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.restorePreviewLoading = false;
|
if (requestSeq === this.restorePreviewFetchSeq) {
|
||||||
|
this.restorePreviewLoading = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
decorateSnapshotRows(rows) {
|
||||||
|
return (rows || []).map((row, index) => ({
|
||||||
|
...row,
|
||||||
|
isLatestSnapshot: index === 0
|
||||||
|
}));
|
||||||
|
},
|
||||||
confirmRestoreSnapshot() {
|
confirmRestoreSnapshot() {
|
||||||
if (!this.restorePreviewRow) {
|
if (!this.restorePreviewRow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.restoring = true;
|
this.restoring = true;
|
||||||
|
const requestSeq = ++this.restoreActionSeq;
|
||||||
Api.agent.restoreAgentSnapshot(this.agentId, this.restorePreviewRow.id, ({ data }) => {
|
Api.agent.restoreAgentSnapshot(this.agentId, this.restorePreviewRow.id, ({ data }) => {
|
||||||
|
if (requestSeq !== this.restoreActionSeq) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.restoring = false;
|
this.restoring = false;
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
this.$message.success(this.$t("agentSnapshot.restoreSuccess"));
|
this.$message.success(this.$t("agentSnapshot.restoreSuccess"));
|
||||||
this.restorePreviewVisible = false;
|
this.restorePreviewVisible = false;
|
||||||
this.detailVisible = false;
|
this.detailVisible = false;
|
||||||
this.$emit("restored");
|
this.$emit("restored");
|
||||||
|
this.historyAnchorVersionNo = null;
|
||||||
this.fetchSnapshots();
|
this.fetchSnapshots();
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(data.msg || this.$t("agentSnapshot.restoreFailed"));
|
this.$message.error(this.restoreFailedMessage(data));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
deleteSnapshot(row) {
|
||||||
|
if (!this.canDeleteSnapshot(row)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$confirm(
|
||||||
|
this.$t("agentSnapshot.deleteConfirm", { version: row.versionNo }),
|
||||||
|
this.$t("common.warning"),
|
||||||
|
{
|
||||||
|
confirmButtonText: this.$t("common.confirm"),
|
||||||
|
cancelButtonText: this.$t("common.cancel"),
|
||||||
|
type: "warning"
|
||||||
|
}
|
||||||
|
).then(() => {
|
||||||
|
const requestSeq = ++this.deleteActionSeq;
|
||||||
|
this.deletingSnapshotId = row.id;
|
||||||
|
Api.agent.deleteAgentSnapshot(this.agentId, row.id, ({ data }) => {
|
||||||
|
if (requestSeq !== this.deleteActionSeq) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.deletingSnapshotId = null;
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success(this.$t("agentSnapshot.deleteSuccess"));
|
||||||
|
const savedRowsOnPage = this.snapshots.filter((item) => !item.isCurrentVersion);
|
||||||
|
if (savedRowsOnPage.length <= 1 && this.page > 1) {
|
||||||
|
this.page -= 1;
|
||||||
|
}
|
||||||
|
this.fetchSnapshots();
|
||||||
|
} else {
|
||||||
|
this.$message.error(data.msg || this.$t("agentSnapshot.deleteFailed"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(() => {});
|
||||||
|
},
|
||||||
hasAfterSnapshotData(snapshot) {
|
hasAfterSnapshotData(snapshot) {
|
||||||
return !!(
|
return !!(
|
||||||
snapshot &&
|
snapshot &&
|
||||||
@@ -701,8 +846,9 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
resolveCurrentVersionNo() {
|
resolveCurrentVersionNo() {
|
||||||
if (this.currentVersionNo) {
|
const propVersionNo = Number(this.currentVersionNo);
|
||||||
return this.currentVersionNo;
|
if (Number.isFinite(propVersionNo) && propVersionNo > 0) {
|
||||||
|
return propVersionNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentRow = this.snapshots.find((item) => item.isCurrentVersion);
|
const currentRow = this.snapshots.find((item) => item.isCurrentVersion);
|
||||||
@@ -710,12 +856,7 @@ export default {
|
|||||||
return currentRow.versionNo;
|
return currentRow.versionNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxVersionNo = Math.max(
|
return null;
|
||||||
...this.snapshots
|
|
||||||
.filter((item) => !item.isCurrentVersion)
|
|
||||||
.map((item) => Number(item.versionNo) || 0)
|
|
||||||
);
|
|
||||||
return maxVersionNo > 0 ? maxVersionNo + 1 : 1;
|
|
||||||
},
|
},
|
||||||
formatPaneTitle(label, versionNo) {
|
formatPaneTitle(label, versionNo) {
|
||||||
return versionNo ? `${label} #${versionNo}` : label;
|
return versionNo ? `${label} #${versionNo}` : label;
|
||||||
@@ -745,7 +886,7 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchSnapshotRows(params) {
|
fetchSnapshotRows(params) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Api.agent.getAgentSnapshots(this.agentId, params, ({ data }) => {
|
Api.agent.getAgentSnapshots(this.agentId, this.snapshotQueryParams(params), ({ data }) => {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
resolve(data.data || { list: [], total: 0 });
|
resolve(data.data || { list: [], total: 0 });
|
||||||
} else {
|
} else {
|
||||||
@@ -754,6 +895,15 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
snapshotQueryParams(params = {}) {
|
||||||
|
if (!this.historyAnchorVersionNo) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...params,
|
||||||
|
maxVersionNo: String(this.historyAnchorVersionNo)
|
||||||
|
};
|
||||||
|
},
|
||||||
applyPreviousChangedFields(rows, sourceRows) {
|
applyPreviousChangedFields(rows, sourceRows) {
|
||||||
return rows.map((row) => {
|
return rows.map((row) => {
|
||||||
const versionNo = Number(row.versionNo);
|
const versionNo = Number(row.versionNo);
|
||||||
@@ -763,27 +913,38 @@ export default {
|
|||||||
changedFields: []
|
changedFields: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const previousRow = sourceRows.find((item) => Number(item.versionNo) === versionNo - 1);
|
const previousRow = this.findPreviousSnapshotRow(row, sourceRows);
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
|
previousSnapshotId: previousRow?.id || null,
|
||||||
|
previousVersionNo: previousRow?.versionNo || null,
|
||||||
changedFields: previousRow?.changedFields || []
|
changedFields: previousRow?.changedFields || []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
findPreviousSnapshotRow(row, sourceRows) {
|
||||||
|
const versionNo = Number(row?.versionNo);
|
||||||
|
if (!Number.isFinite(versionNo)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (sourceRows || []).find((item) => {
|
||||||
|
return !item.isCurrentVersion && Number(item.versionNo) < versionNo;
|
||||||
|
}) || null;
|
||||||
|
},
|
||||||
buildCurrentVersionRow(latestSnapshot) {
|
buildCurrentVersionRow(latestSnapshot) {
|
||||||
const latestVersionNo = Number(latestSnapshot?.versionNo) || 0;
|
const latestVersionNo = Number(latestSnapshot?.versionNo) || 0;
|
||||||
const propVersionNo = Number(this.currentVersionNo);
|
const propVersionNo = Number(this.currentVersionNo);
|
||||||
const inferredVersionNo = latestVersionNo + 1 || 1;
|
if (!Number.isFinite(propVersionNo) || propVersionNo <= latestVersionNo) {
|
||||||
const currentVersionNo = Number.isFinite(propVersionNo) && propVersionNo > latestVersionNo
|
return null;
|
||||||
? propVersionNo
|
}
|
||||||
: inferredVersionNo;
|
|
||||||
return {
|
return {
|
||||||
id: "__current__",
|
id: "__current__",
|
||||||
agentId: this.agentId,
|
agentId: this.agentId,
|
||||||
versionNo: currentVersionNo,
|
versionNo: propVersionNo,
|
||||||
source: "current",
|
source: "current",
|
||||||
createdAt: latestSnapshot?.createdAt || null,
|
createdAt: latestSnapshot?.createdAt || null,
|
||||||
changedFields: latestSnapshot?.changedFields || [],
|
changedFields: latestSnapshot?.changedFields || [],
|
||||||
|
fieldOrder: latestSnapshot?.fieldOrder || [],
|
||||||
isCurrentVersion: true,
|
isCurrentVersion: true,
|
||||||
previousVersionNo: latestVersionNo || null,
|
previousVersionNo: latestVersionNo || null,
|
||||||
previousSnapshotId: latestSnapshot?.id || null
|
previousSnapshotId: latestSnapshot?.id || null
|
||||||
@@ -797,9 +958,8 @@ export default {
|
|||||||
},
|
},
|
||||||
buildCurrentVersionDiffSnapshot(row) {
|
buildCurrentVersionDiffSnapshot(row) {
|
||||||
const currentVersionNo = Number(row.versionNo);
|
const currentVersionNo = Number(row.versionNo);
|
||||||
const previousVersionNo = currentVersionNo - 1;
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
this.fetchSnapshotDetailByVersion(previousVersionNo),
|
this.fetchPreviousSnapshotDetail(row),
|
||||||
this.fetchCurrentAgentData()
|
this.fetchCurrentAgentData()
|
||||||
]).then(([previousSnapshot, currentData]) => ({
|
]).then(([previousSnapshot, currentData]) => ({
|
||||||
...row,
|
...row,
|
||||||
@@ -807,26 +967,41 @@ export default {
|
|||||||
changedFields: previousSnapshot.changedFields || [],
|
changedFields: previousSnapshot.changedFields || [],
|
||||||
snapshotData: previousSnapshot.snapshotData || {},
|
snapshotData: previousSnapshot.snapshotData || {},
|
||||||
afterSnapshotData: currentData,
|
afterSnapshotData: currentData,
|
||||||
beforeVersionNo: previousVersionNo,
|
beforeVersionNo: previousSnapshot.versionNo,
|
||||||
afterVersionNo: currentVersionNo
|
afterVersionNo: currentVersionNo,
|
||||||
|
fieldOrder: previousSnapshot.fieldOrder || row.fieldOrder || []
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
buildSavedVersionDiffSnapshot(row) {
|
buildSavedVersionDiffSnapshot(row) {
|
||||||
const versionNo = Number(row.versionNo);
|
const versionNo = Number(row.versionNo);
|
||||||
const previousVersionNo = versionNo - 1;
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
this.fetchSnapshotDetail(row.id),
|
this.fetchSnapshotDetail(row.id),
|
||||||
this.fetchSnapshotDetailByVersion(previousVersionNo)
|
this.fetchPreviousSnapshotDetail(row)
|
||||||
]).then(([selectedSnapshot, previousSnapshot]) => ({
|
]).then(([selectedSnapshot, previousSnapshot]) => ({
|
||||||
...selectedSnapshot,
|
...selectedSnapshot,
|
||||||
versionNo,
|
versionNo,
|
||||||
changedFields: previousSnapshot.changedFields || [],
|
changedFields: previousSnapshot.changedFields || [],
|
||||||
snapshotData: previousSnapshot.snapshotData || {},
|
snapshotData: previousSnapshot.snapshotData || {},
|
||||||
afterSnapshotData: selectedSnapshot.snapshotData || {},
|
afterSnapshotData: selectedSnapshot.snapshotData || {},
|
||||||
beforeVersionNo: previousVersionNo,
|
beforeVersionNo: previousSnapshot.versionNo,
|
||||||
afterVersionNo: versionNo
|
afterVersionNo: versionNo,
|
||||||
|
fieldOrder: selectedSnapshot.fieldOrder || previousSnapshot.fieldOrder || row.fieldOrder || []
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
fetchPreviousSnapshotDetail(row) {
|
||||||
|
if (row?.previousSnapshotId) {
|
||||||
|
return this.fetchSnapshotDetail(row.previousSnapshotId);
|
||||||
|
}
|
||||||
|
if (row?.previousVersionNo) {
|
||||||
|
return this.fetchSnapshotDetailByVersion(row.previousVersionNo);
|
||||||
|
}
|
||||||
|
return this.fetchSnapshotRowBeforeVersion(row?.versionNo).then((previousRow) => {
|
||||||
|
if (!previousRow?.id) {
|
||||||
|
return Promise.reject(new Error("previous snapshot not found"));
|
||||||
|
}
|
||||||
|
return this.fetchSnapshotDetail(previousRow.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
fetchSnapshotDetailByVersion(versionNo) {
|
fetchSnapshotDetailByVersion(versionNo) {
|
||||||
return this.fetchSnapshotRowByVersion(versionNo).then((row) => {
|
return this.fetchSnapshotRowByVersion(versionNo).then((row) => {
|
||||||
if (!row?.id) {
|
if (!row?.id) {
|
||||||
@@ -867,6 +1042,17 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
fetchSnapshotRowBeforeVersion(versionNo) {
|
||||||
|
const cached = this.findPreviousSnapshotRow({ versionNo }, this.snapshots);
|
||||||
|
if (cached) {
|
||||||
|
return Promise.resolve(cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.fetchSnapshotRows({
|
||||||
|
page: "1",
|
||||||
|
limit: String(Math.max(this.historyTotal, this.limit, 1))
|
||||||
|
}).then((data) => this.findPreviousSnapshotRow({ versionNo }, data.list || []));
|
||||||
|
},
|
||||||
ensurePluginMetadata() {
|
ensurePluginMetadata() {
|
||||||
if (this.pluginMetadataLoaded) {
|
if (this.pluginMetadataLoaded) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -927,7 +1113,7 @@ export default {
|
|||||||
|
|
||||||
this.modelMetadataLoading = Promise.all(fetchers)
|
this.modelMetadataLoading = Promise.all(fetchers)
|
||||||
.then((groups) => {
|
.then((groups) => {
|
||||||
const metadata = { ...FALLBACK_MODEL_NAMES };
|
const metadata = {};
|
||||||
groups.flat().forEach((item) => {
|
groups.flat().forEach((item) => {
|
||||||
if (item.id) {
|
if (item.id) {
|
||||||
metadata[item.id] = item.name || item.id;
|
metadata[item.id] = item.name || item.id;
|
||||||
@@ -1166,13 +1352,13 @@ export default {
|
|||||||
const name = this.functionDisplayName(pluginId);
|
const name = this.functionDisplayName(pluginId);
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
added: {
|
added: {
|
||||||
badge: "已开启"
|
badge: this.$t("agentSnapshot.function.added")
|
||||||
},
|
},
|
||||||
removed: {
|
removed: {
|
||||||
badge: "已关闭"
|
badge: this.$t("agentSnapshot.function.removed")
|
||||||
},
|
},
|
||||||
updated: {
|
updated: {
|
||||||
badge: "参数变更"
|
badge: this.$t("agentSnapshot.function.updated")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
@@ -1188,18 +1374,18 @@ export default {
|
|||||||
if (!func) {
|
if (!func) {
|
||||||
return {
|
return {
|
||||||
active: false,
|
active: false,
|
||||||
status: "未开启",
|
status: this.$t("agentSnapshot.function.disabled"),
|
||||||
params: [],
|
params: [],
|
||||||
note: "未开启此功能"
|
note: this.$t("agentSnapshot.function.disabledNote")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = this.functionParamRows(pluginId, func.params, changedParamKeys);
|
const params = this.functionParamRows(pluginId, func.params, changedParamKeys);
|
||||||
return {
|
return {
|
||||||
active: true,
|
active: true,
|
||||||
status: "已开启",
|
status: this.$t("agentSnapshot.function.enabled"),
|
||||||
params,
|
params,
|
||||||
note: params.length ? "" : "无需配置参数"
|
note: params.length ? "" : this.$t("agentSnapshot.function.noParams")
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
changedFunctionParamKeys(pluginId, beforeParams, afterParams) {
|
changedFunctionParamKeys(pluginId, beforeParams, afterParams) {
|
||||||
@@ -1235,11 +1421,12 @@ export default {
|
|||||||
},
|
},
|
||||||
functionDisplayName(pluginId) {
|
functionDisplayName(pluginId) {
|
||||||
const meta = this.resolveFunctionMeta(pluginId);
|
const meta = this.resolveFunctionMeta(pluginId);
|
||||||
return meta?.name || FALLBACK_PLUGIN_NAMES[pluginId] || pluginId || this.$t("agentSnapshot.emptyValue");
|
return meta?.name || this.translateKey(FALLBACK_PLUGIN_NAME_KEYS[pluginId], pluginId)
|
||||||
|
|| pluginId || this.$t("agentSnapshot.emptyValue");
|
||||||
},
|
},
|
||||||
functionParamLabel(pluginId, key) {
|
functionParamLabel(pluginId, key) {
|
||||||
const field = this.functionFields(pluginId).find((item) => item.key === key);
|
const field = this.functionFields(pluginId).find((item) => item.key === key);
|
||||||
return field?.label || FALLBACK_FIELD_LABELS[key] || key;
|
return field?.label || this.translateKey(FALLBACK_FIELD_LABEL_KEYS[key], key) || key;
|
||||||
},
|
},
|
||||||
functionFields(pluginId) {
|
functionFields(pluginId) {
|
||||||
const meta = this.resolveFunctionMeta(pluginId);
|
const meta = this.resolveFunctionMeta(pluginId);
|
||||||
@@ -1273,7 +1460,7 @@ export default {
|
|||||||
const lines = String(value).replace(/\r\n/g, "\n").split("\n");
|
const lines = String(value).replace(/\r\n/g, "\n").split("\n");
|
||||||
const html = [];
|
const html = [];
|
||||||
let paragraph = [];
|
let paragraph = [];
|
||||||
let listType = null;
|
const listStack = [];
|
||||||
|
|
||||||
const closeParagraph = () => {
|
const closeParagraph = () => {
|
||||||
if (paragraph.length) {
|
if (paragraph.length) {
|
||||||
@@ -1281,59 +1468,89 @@ export default {
|
|||||||
paragraph = [];
|
paragraph = [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const closeList = () => {
|
const closeOneList = () => {
|
||||||
if (listType) {
|
const list = listStack.pop();
|
||||||
html.push(`</${listType}>`);
|
if (!list) {
|
||||||
listType = null;
|
return;
|
||||||
|
}
|
||||||
|
if (list.liOpen) {
|
||||||
|
html.push("</li>");
|
||||||
|
}
|
||||||
|
html.push(`</${list.type}>`);
|
||||||
|
};
|
||||||
|
const closeLists = () => {
|
||||||
|
while (listStack.length) {
|
||||||
|
closeOneList();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const openList = (type) => {
|
const openList = (type, indent) => {
|
||||||
if (listType !== type) {
|
html.push(`<${type}>`);
|
||||||
closeList();
|
listStack.push({ type, indent, liOpen: false });
|
||||||
html.push(`<${type}>`);
|
};
|
||||||
listType = type;
|
const alignList = (type, indent) => {
|
||||||
|
while (listStack.length && indent < listStack[listStack.length - 1].indent) {
|
||||||
|
closeOneList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let current = listStack[listStack.length - 1];
|
||||||
|
if (current && indent === current.indent && type !== current.type) {
|
||||||
|
closeOneList();
|
||||||
|
}
|
||||||
|
|
||||||
|
current = listStack[listStack.length - 1];
|
||||||
|
if (!current || indent > current.indent || indent !== current.indent) {
|
||||||
|
openList(type, indent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const addListItem = (type, indent, text) => {
|
||||||
|
closeParagraph();
|
||||||
|
alignList(type, indent);
|
||||||
|
const current = listStack[listStack.length - 1];
|
||||||
|
if (current.liOpen) {
|
||||||
|
html.push("</li>");
|
||||||
|
}
|
||||||
|
html.push(`<li>${this.renderInlineMarkdown(text)}`);
|
||||||
|
current.liOpen = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
lines.forEach((rawLine) => {
|
lines.forEach((rawLine) => {
|
||||||
const line = rawLine.trim();
|
const normalizedLine = rawLine.replace(/\t/g, " ");
|
||||||
|
const line = normalizedLine.trim();
|
||||||
if (!line) {
|
if (!line) {
|
||||||
closeParagraph();
|
closeParagraph();
|
||||||
closeList();
|
closeLists();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
||||||
if (heading) {
|
if (heading) {
|
||||||
closeParagraph();
|
closeParagraph();
|
||||||
closeList();
|
closeLists();
|
||||||
const level = Math.min(heading[1].length, 4);
|
const level = Math.min(heading[1].length, 4);
|
||||||
html.push(`<h${level}>${this.renderInlineMarkdown(heading[2])}</h${level}>`);
|
html.push(`<h${level}>${this.renderInlineMarkdown(heading[2])}</h${level}>`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unordered = line.match(/^[-*+]\s+(.+)$/);
|
const indent = normalizedLine.match(/^ */)[0].length;
|
||||||
|
const listContent = normalizedLine.slice(indent);
|
||||||
|
const unordered = listContent.match(/^[-*+]\s+(.+)$/);
|
||||||
if (unordered) {
|
if (unordered) {
|
||||||
closeParagraph();
|
addListItem("ul", indent, unordered[1]);
|
||||||
openList("ul");
|
|
||||||
html.push(`<li>${this.renderInlineMarkdown(unordered[1])}</li>`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ordered = line.match(/^\d+\.\s+(.+)$/);
|
const ordered = listContent.match(/^\d+\.\s+(.+)$/);
|
||||||
if (ordered) {
|
if (ordered) {
|
||||||
closeParagraph();
|
addListItem("ol", indent, ordered[1]);
|
||||||
openList("ol");
|
|
||||||
html.push(`<li>${this.renderInlineMarkdown(ordered[1])}</li>`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
closeList();
|
closeLists();
|
||||||
paragraph.push(line);
|
paragraph.push(line);
|
||||||
});
|
});
|
||||||
|
|
||||||
closeParagraph();
|
closeParagraph();
|
||||||
closeList();
|
closeLists();
|
||||||
return html.join("");
|
return html.join("");
|
||||||
},
|
},
|
||||||
renderInlineMarkdown(value) {
|
renderInlineMarkdown(value) {
|
||||||
@@ -1351,13 +1568,19 @@ export default {
|
|||||||
.replace(/"/g, """)
|
.replace(/"/g, """)
|
||||||
.replace(/'/g, "'");
|
.replace(/'/g, "'");
|
||||||
},
|
},
|
||||||
resolveDiffFields(beforeData, afterData, changedFields) {
|
restoreFailedMessage(data) {
|
||||||
const directFields = changedFields
|
return data?.msg
|
||||||
|
? `${this.$t("agentSnapshot.restoreFailedRollback")}: ${data.msg}`
|
||||||
|
: this.$t("agentSnapshot.restoreFailedRollback");
|
||||||
|
},
|
||||||
|
resolveDiffFields(beforeData, afterData, changedFields = [], fieldOrder = []) {
|
||||||
|
const safeChangedFields = Array.isArray(changedFields) ? changedFields : [];
|
||||||
|
const directFields = safeChangedFields
|
||||||
.filter((field) => field && field !== "restore")
|
.filter((field) => field && field !== "restore")
|
||||||
.map((field) => this.canonicalField(field));
|
.map((field) => this.canonicalField(field));
|
||||||
const candidates = directFields.length > 0 && !changedFields.includes("restore")
|
const candidates = directFields.length > 0 && !safeChangedFields.includes("restore")
|
||||||
? directFields
|
? directFields
|
||||||
: this.snapshotFieldOrder();
|
: this.snapshotFieldOrder(fieldOrder, beforeData, afterData);
|
||||||
|
|
||||||
return Array.from(new Set(candidates)).filter((field) => {
|
return Array.from(new Set(candidates)).filter((field) => {
|
||||||
return !this.isSameValue(
|
return !this.isSameValue(
|
||||||
@@ -1366,38 +1589,19 @@ export default {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
snapshotFieldOrder() {
|
snapshotFieldOrder(fieldOrder = [], beforeData = {}, afterData = {}) {
|
||||||
return [
|
const backendOrder = Array.isArray(fieldOrder) ? fieldOrder : [];
|
||||||
"agentCode",
|
if (backendOrder.length) {
|
||||||
"agentName",
|
return backendOrder.map((field) => this.canonicalField(field));
|
||||||
"asrModelId",
|
}
|
||||||
"vadModelId",
|
const dataFields = [
|
||||||
"llmModelId",
|
...Object.keys(beforeData || {}),
|
||||||
"slmModelId",
|
...Object.keys(afterData || {})
|
||||||
"vllmModelId",
|
].map((field) => this.canonicalField(field));
|
||||||
"ttsModelId",
|
return Array.from(new Set(dataFields)).filter((field) => field !== "tags");
|
||||||
"ttsVoiceId",
|
|
||||||
"ttsLanguage",
|
|
||||||
"ttsVolume",
|
|
||||||
"ttsRate",
|
|
||||||
"ttsPitch",
|
|
||||||
"memModelId",
|
|
||||||
"intentModelId",
|
|
||||||
"chatHistoryConf",
|
|
||||||
"systemPrompt",
|
|
||||||
"summaryMemory",
|
|
||||||
"langCode",
|
|
||||||
"language",
|
|
||||||
"sort",
|
|
||||||
"functions",
|
|
||||||
"contextProviders",
|
|
||||||
"correctWordFileIds",
|
|
||||||
"tagNames",
|
|
||||||
"tagIds"
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
canonicalField(field) {
|
canonicalField(field) {
|
||||||
return field === "tags" ? "tagNames" : field;
|
return field === "tags" || field === "tagIds" ? "tagNames" : field;
|
||||||
},
|
},
|
||||||
getFieldValue(data, field) {
|
getFieldValue(data, field) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -1434,7 +1638,8 @@ export default {
|
|||||||
return this.voiceDisplayName(data.ttsModelId, value);
|
return this.voiceDisplayName(data.ttsModelId, value);
|
||||||
}
|
}
|
||||||
if (field === "chatHistoryConf") {
|
if (field === "chatHistoryConf") {
|
||||||
return CHAT_HISTORY_CONF_LABELS[value] || CHAT_HISTORY_CONF_LABELS[Number(value)] || this.formatValue(value);
|
return this.translateKey(CHAT_HISTORY_CONF_LABEL_KEYS[value] || CHAT_HISTORY_CONF_LABEL_KEYS[Number(value)])
|
||||||
|
|| this.formatValue(value);
|
||||||
}
|
}
|
||||||
return this.formatValue(value);
|
return this.formatValue(value);
|
||||||
},
|
},
|
||||||
@@ -1442,13 +1647,14 @@ export default {
|
|||||||
if (value === null || value === undefined || value === "") {
|
if (value === null || value === undefined || value === "") {
|
||||||
return this.$t("agentSnapshot.emptyValue");
|
return this.$t("agentSnapshot.emptyValue");
|
||||||
}
|
}
|
||||||
return this.modelNameMap[value] || FALLBACK_MODEL_NAMES[value] || String(value);
|
return this.modelNameMap[value] || this.translateKey(FALLBACK_MODEL_NAME_KEYS[value], String(value)) || String(value);
|
||||||
},
|
},
|
||||||
voiceDisplayName(modelId, value) {
|
voiceDisplayName(modelId, value) {
|
||||||
if (value === null || value === undefined || value === "") {
|
if (value === null || value === undefined || value === "") {
|
||||||
return this.$t("agentSnapshot.emptyValue");
|
return this.$t("agentSnapshot.emptyValue");
|
||||||
}
|
}
|
||||||
return this.voiceNameMap[`${modelId}:${value}`] || this.voiceNameMap[value] || String(value);
|
return this.voiceNameMap[`${modelId}:${value}`] || this.voiceNameMap[value]
|
||||||
|
|| this.translateKey(FALLBACK_VOICE_NAME_KEYS[value], String(value)) || String(value);
|
||||||
},
|
},
|
||||||
formatValue(value) {
|
formatValue(value) {
|
||||||
if (value === null || value === undefined || value === "") {
|
if (value === null || value === undefined || value === "") {
|
||||||
@@ -1481,6 +1687,9 @@ export default {
|
|||||||
fieldLabel(field) {
|
fieldLabel(field) {
|
||||||
const key = `agentSnapshot.field.${field}`;
|
const key = `agentSnapshot.field.${field}`;
|
||||||
return this.$te && this.$te(key) ? this.$t(key) : field;
|
return this.$te && this.$te(key) ? this.$t(key) : field;
|
||||||
|
},
|
||||||
|
translateKey(key, fallback = "") {
|
||||||
|
return key && this.$te && this.$te(key) ? this.$t(key) : fallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1540,6 +1749,10 @@ export default {
|
|||||||
background: #fbfffa;
|
background: #fbfffa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.snapshot-delete-button {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
.field-tags {
|
.field-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -1564,6 +1777,10 @@ export default {
|
|||||||
@include scrollbar-style;
|
@include scrollbar-style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.restore-risk-alert {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.detail-summary {
|
.detail-summary {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -806,12 +806,126 @@ export default {
|
|||||||
'roleConfig.addTag': 'Neues Label hinzufügen',
|
'roleConfig.addTag': 'Neues Label hinzufügen',
|
||||||
'roleConfig.restartNotice': 'Neustart nach Konfigurationsspeichern zur Aktivierung.',
|
'roleConfig.restartNotice': 'Neustart nach Konfigurationsspeichern zur Aktivierung.',
|
||||||
'roleConfig.saveConfig': 'Konfiguration speichern',
|
'roleConfig.saveConfig': 'Konfiguration speichern',
|
||||||
|
'roleConfig.snapshotHistory': 'Versionsverlauf',
|
||||||
|
'roleConfig.currentVersion': 'Aktuelle Version {version}',
|
||||||
|
'roleConfig.functionName': 'Funktionsname',
|
||||||
'roleConfig.reset': 'Zurücksetzen',
|
'roleConfig.reset': 'Zurücksetzen',
|
||||||
'roleConfig.agentName': 'Spitzname',
|
'roleConfig.agentName': 'Spitzname',
|
||||||
'roleConfig.roleTemplate': 'Vorlage',
|
'roleConfig.roleTemplate': 'Vorlage',
|
||||||
'roleConfig.roleIntroduction': 'Einführung',
|
'roleConfig.roleIntroduction': 'Einführung',
|
||||||
'roleConfig.languageCode': 'Sprachcode',
|
'roleConfig.languageCode': 'Sprachcode',
|
||||||
'roleConfig.interactionLanguage': 'Interaktionssprache',
|
'roleConfig.interactionLanguage': 'Interaktionssprache',
|
||||||
|
'agentSnapshot.title': 'Versionsverlauf',
|
||||||
|
'agentSnapshot.empty': 'Noch keine Versionen',
|
||||||
|
'agentSnapshot.version': 'Version',
|
||||||
|
'agentSnapshot.createdAt': 'Gespeichert am',
|
||||||
|
'agentSnapshot.source': 'Quelle',
|
||||||
|
'agentSnapshot.changedFields': 'Änderungen',
|
||||||
|
'agentSnapshot.actions': 'Aktionen',
|
||||||
|
'agentSnapshot.view': 'Anzeigen',
|
||||||
|
'agentSnapshot.restore': 'Wiederherstellen',
|
||||||
|
'agentSnapshot.delete': 'Löschen',
|
||||||
|
'agentSnapshot.detailTitle': 'Änderungsdetails',
|
||||||
|
'agentSnapshot.restorePreviewTitle': 'Wiederherstellungsvorschau',
|
||||||
|
'agentSnapshot.confirmRestore': 'Wiederherstellung bestätigen',
|
||||||
|
'agentSnapshot.before': 'Vorher',
|
||||||
|
'agentSnapshot.after': 'Nachher',
|
||||||
|
'agentSnapshot.emptyValue': 'Keine',
|
||||||
|
'agentSnapshot.noChangedContent': 'Keine anzeigbaren Änderungen',
|
||||||
|
'agentSnapshot.noFunctionChange': 'Keine Funktionsänderungen',
|
||||||
|
'agentSnapshot.beforeChange': 'Vor der Änderung',
|
||||||
|
'agentSnapshot.afterChange': 'Nach der Änderung',
|
||||||
|
'agentSnapshot.beforeRestore': 'Vor der Wiederherstellung',
|
||||||
|
'agentSnapshot.afterRestore': 'Nach der Wiederherstellung',
|
||||||
|
'agentSnapshot.restoreMemoryWarning': 'Beim Wiederherstellen einer Version ohne Speicher wird der Chatverlauf dieses Agenten gelöscht. Fahren Sie nur fort, wenn Sie dieses Risiko bestätigt haben.',
|
||||||
|
'agentSnapshot.restoreConfirm': 'Version {version} wiederherstellen? Die aktuelle Konfiguration wird zuerst als neue Version gespeichert.',
|
||||||
|
'agentSnapshot.restoreSuccess': 'Version wurde wiederhergestellt',
|
||||||
|
'agentSnapshot.restoreFailed': 'Version konnte nicht wiederhergestellt werden',
|
||||||
|
'agentSnapshot.restoreFailedRollback': 'Version konnte nicht wiederhergestellt werden. Die Transaktion wurde zurueckgerollt und die aktuelle Konfiguration wurde nicht geaendert',
|
||||||
|
'agentSnapshot.deleteConfirm': 'Version {version} löschen? Dies kann nicht rückgängig gemacht werden.',
|
||||||
|
'agentSnapshot.deleteSuccess': 'Verlaufsversion gelöscht',
|
||||||
|
'agentSnapshot.deleteFailed': 'Verlaufsversion konnte nicht gelöscht werden',
|
||||||
|
'agentSnapshot.fetchFailed': 'Versionsverlauf konnte nicht abgerufen werden',
|
||||||
|
'agentSnapshot.detailFailed': 'Snapshot-Details konnten nicht abgerufen werden',
|
||||||
|
'agentSnapshot.currentVersion': 'Aktuelle Version',
|
||||||
|
'agentSnapshot.source.config': 'Konfiguration gespeichert',
|
||||||
|
'agentSnapshot.source.current': 'Aktuelle Konfiguration',
|
||||||
|
'agentSnapshot.source.restore': 'Vor der Wiederherstellung',
|
||||||
|
'agentSnapshot.source.initial': 'Initialversion',
|
||||||
|
'agentSnapshot.field.restore': 'Wiederherstellung',
|
||||||
|
'agentSnapshot.field.initial': 'Initialer Snapshot',
|
||||||
|
'agentSnapshot.field.agentCode': 'Agent-Code',
|
||||||
|
'agentSnapshot.field.agentName': 'Spitzname',
|
||||||
|
'agentSnapshot.field.asrModelId': 'Spracherkennung',
|
||||||
|
'agentSnapshot.field.vadModelId': 'Sprachaktivitätserkennung',
|
||||||
|
'agentSnapshot.field.llmModelId': 'Hauptsprachmodell',
|
||||||
|
'agentSnapshot.field.slmModelId': 'Kleines Sprachmodell',
|
||||||
|
'agentSnapshot.field.vllmModelId': 'Vision-Modell',
|
||||||
|
'agentSnapshot.field.ttsModelId': 'Text-zu-Sprache',
|
||||||
|
'agentSnapshot.field.ttsVoiceId': 'Stimme',
|
||||||
|
'agentSnapshot.field.ttsLanguage': 'Sprache',
|
||||||
|
'agentSnapshot.field.ttsVolume': 'Lautstärke',
|
||||||
|
'agentSnapshot.field.ttsRate': 'Geschwindigkeit',
|
||||||
|
'agentSnapshot.field.ttsPitch': 'Tonhöhe',
|
||||||
|
'agentSnapshot.field.memModelId': 'Speichermodell',
|
||||||
|
'agentSnapshot.field.intentModelId': 'Absichtserkennung',
|
||||||
|
'agentSnapshot.field.chatHistoryConf': 'Chatverlauf',
|
||||||
|
'agentSnapshot.field.systemPrompt': 'Einführung',
|
||||||
|
'agentSnapshot.field.summaryMemory': 'Speicher',
|
||||||
|
'agentSnapshot.field.langCode': 'Sprachcode',
|
||||||
|
'agentSnapshot.field.language': 'Interaktionssprache',
|
||||||
|
'agentSnapshot.field.sort': 'Sortierung',
|
||||||
|
'agentSnapshot.field.functions': 'Funktionen',
|
||||||
|
'agentSnapshot.field.contextProviders': 'Kontextquellen',
|
||||||
|
'agentSnapshot.field.correctWordFileIds': 'Ersatzwörter',
|
||||||
|
'agentSnapshot.field.tagNames': 'Labels',
|
||||||
|
'agentSnapshot.field.tagIds': 'Label-IDs',
|
||||||
|
'agentSnapshot.function.added': 'Aktiviert',
|
||||||
|
'agentSnapshot.function.removed': 'Deaktiviert',
|
||||||
|
'agentSnapshot.function.updated': 'Parameter geändert',
|
||||||
|
'agentSnapshot.function.enabled': 'Aktiviert',
|
||||||
|
'agentSnapshot.function.disabled': 'Deaktiviert',
|
||||||
|
'agentSnapshot.function.disabledNote': 'Diese Funktion ist deaktiviert',
|
||||||
|
'agentSnapshot.function.noParams': 'Keine Parameter erforderlich',
|
||||||
|
'agentSnapshot.chatHistoryConf.none': 'Chatverlauf nicht aufzeichnen',
|
||||||
|
'agentSnapshot.chatHistoryConf.text': 'Text melden',
|
||||||
|
'agentSnapshot.chatHistoryConf.textVoice': 'Text und Sprache melden',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEATHER': 'Wetter',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': 'Server-Musikplayer',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': 'China News',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'NewsNow-Aggregation',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'HomeAssistant-Statusabfrage',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'HomeAssistant-Statusaktualisierung',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'HomeAssistant-Musikplayer',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': 'Websuche',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': 'Geräteanruf',
|
||||||
|
'agentSnapshot.pluginField.url': 'Endpunkt-URL',
|
||||||
|
'agentSnapshot.pluginField.news_sources': 'Nachrichtenquellen',
|
||||||
|
'agentSnapshot.pluginField.default_rss_url': 'Standard-RSS-URL',
|
||||||
|
'agentSnapshot.pluginField.society_rss_url': 'RSS-URL für Gesellschaft',
|
||||||
|
'agentSnapshot.pluginField.world_rss_url': 'RSS-URL für Welt',
|
||||||
|
'agentSnapshot.pluginField.finance_rss_url': 'RSS-URL für Finanzen',
|
||||||
|
'agentSnapshot.pluginField.api_key': 'API-Schlüssel',
|
||||||
|
'agentSnapshot.pluginField.default_location': 'Standardstadt',
|
||||||
|
'agentSnapshot.pluginField.api_host': 'Entwickler-API-Host',
|
||||||
|
'agentSnapshot.pluginField.base_url': 'Server-URL',
|
||||||
|
'agentSnapshot.pluginField.devices': 'Geräte',
|
||||||
|
'agentSnapshot.pluginField.provider': 'Suchanbieter',
|
||||||
|
'agentSnapshot.pluginField.description': 'Werkzeugbeschreibung',
|
||||||
|
'agentSnapshot.pluginField.max_results': 'Ergebnisanzahl',
|
||||||
|
'agentSnapshot.model.Memory_nomem': 'Kein Speicher',
|
||||||
|
'agentSnapshot.model.Memory_mem_local_short': 'Lokaler Kurzzeitspeicher',
|
||||||
|
'agentSnapshot.model.Memory_mem0ai': 'Mem0AI-Speicher',
|
||||||
|
'agentSnapshot.model.Memory_mem_report_only': 'Nur melden',
|
||||||
|
'agentSnapshot.model.Intent_nointent': 'Keine Absichtserkennung',
|
||||||
|
'agentSnapshot.model.Intent_intent_llm': 'Externe LLM-Absichtserkennung',
|
||||||
|
'agentSnapshot.model.Intent_function_call': 'LLM-Funktionsaufruf',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0001': 'EdgeTTS weiblich - Xiaoxiao',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0002': 'EdgeTTS männlich - Yunyang',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0003': 'EdgeTTS weiblich - Xiaoyi',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0004': 'EdgeTTS männlich - Yunjian',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0005': 'EdgeTTS männlich - Yunxi',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0006': 'EdgeTTS männlich - Yunxia',
|
||||||
'roleConfig.vad': 'VAD',
|
'roleConfig.vad': 'VAD',
|
||||||
'roleConfig.asr': 'ASR',
|
'roleConfig.asr': 'ASR',
|
||||||
'roleConfig.llm': 'Hauptsprachmodell (LLM)',
|
'roleConfig.llm': 'Hauptsprachmodell (LLM)',
|
||||||
@@ -1554,4 +1668,4 @@ export default {
|
|||||||
// Header navigation
|
// Header navigation
|
||||||
'header.addressBook': 'Adressbuch',
|
'header.addressBook': 'Adressbuch',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -807,6 +807,8 @@ export default {
|
|||||||
'roleConfig.restartNotice': 'After saving the configuration, you need to restart the device for the new configuration to take effect.',
|
'roleConfig.restartNotice': 'After saving the configuration, you need to restart the device for the new configuration to take effect.',
|
||||||
'roleConfig.saveConfig': 'Save Configuration',
|
'roleConfig.saveConfig': 'Save Configuration',
|
||||||
'roleConfig.snapshotHistory': 'History',
|
'roleConfig.snapshotHistory': 'History',
|
||||||
|
'roleConfig.currentVersion': 'Current Version {version}',
|
||||||
|
'roleConfig.functionName': 'Function Name',
|
||||||
'roleConfig.reset': 'Reset',
|
'roleConfig.reset': 'Reset',
|
||||||
'roleConfig.agentName': 'Nickname',
|
'roleConfig.agentName': 'Nickname',
|
||||||
'roleConfig.roleTemplate': 'Template',
|
'roleConfig.roleTemplate': 'Template',
|
||||||
@@ -873,6 +875,7 @@ export default {
|
|||||||
'agentSnapshot.actions': 'Actions',
|
'agentSnapshot.actions': 'Actions',
|
||||||
'agentSnapshot.view': 'View',
|
'agentSnapshot.view': 'View',
|
||||||
'agentSnapshot.restore': 'Restore',
|
'agentSnapshot.restore': 'Restore',
|
||||||
|
'agentSnapshot.delete': 'Delete',
|
||||||
'agentSnapshot.detailTitle': 'Change Details',
|
'agentSnapshot.detailTitle': 'Change Details',
|
||||||
'agentSnapshot.restorePreviewTitle': 'Restore Preview',
|
'agentSnapshot.restorePreviewTitle': 'Restore Preview',
|
||||||
'agentSnapshot.confirmRestore': 'Confirm Restore',
|
'agentSnapshot.confirmRestore': 'Confirm Restore',
|
||||||
@@ -880,16 +883,28 @@ export default {
|
|||||||
'agentSnapshot.after': 'After',
|
'agentSnapshot.after': 'After',
|
||||||
'agentSnapshot.emptyValue': 'None',
|
'agentSnapshot.emptyValue': 'None',
|
||||||
'agentSnapshot.noChangedContent': 'No changes to display',
|
'agentSnapshot.noChangedContent': 'No changes to display',
|
||||||
'agentSnapshot.restoreConfirm': 'Restore to version #{version}? The current configuration will be saved as a new version first.',
|
'agentSnapshot.noFunctionChange': 'No function changes',
|
||||||
|
'agentSnapshot.beforeChange': 'Before Change',
|
||||||
|
'agentSnapshot.afterChange': 'After Change',
|
||||||
|
'agentSnapshot.beforeRestore': 'Before Restore',
|
||||||
|
'agentSnapshot.afterRestore': 'After Restore',
|
||||||
|
'agentSnapshot.restoreMemoryWarning': 'Restoring to a no-memory version will clear this agent\'s chat history. Continue only after confirming this risk.',
|
||||||
|
'agentSnapshot.restoreConfirm': 'Restore to version {version}? The current configuration will be saved as a new version first.',
|
||||||
'agentSnapshot.restoreSuccess': 'Version restored',
|
'agentSnapshot.restoreSuccess': 'Version restored',
|
||||||
'agentSnapshot.restoreFailed': 'Failed to restore version',
|
'agentSnapshot.restoreFailed': 'Failed to restore version',
|
||||||
|
'agentSnapshot.restoreFailedRollback': 'Failed to restore version. The transaction was rolled back and the current configuration was not changed',
|
||||||
|
'agentSnapshot.deleteConfirm': 'Delete version {version}? This cannot be undone.',
|
||||||
|
'agentSnapshot.deleteSuccess': 'History version deleted',
|
||||||
|
'agentSnapshot.deleteFailed': 'Failed to delete history version',
|
||||||
'agentSnapshot.fetchFailed': 'Failed to fetch versions',
|
'agentSnapshot.fetchFailed': 'Failed to fetch versions',
|
||||||
'agentSnapshot.detailFailed': 'Failed to fetch snapshot details',
|
'agentSnapshot.detailFailed': 'Failed to fetch snapshot details',
|
||||||
'agentSnapshot.currentVersion': 'Current Version',
|
'agentSnapshot.currentVersion': 'Current Version',
|
||||||
'agentSnapshot.source.config': 'Config Save',
|
'agentSnapshot.source.config': 'Config Save',
|
||||||
'agentSnapshot.source.current': 'Current Config',
|
'agentSnapshot.source.current': 'Current Config',
|
||||||
'agentSnapshot.source.restore': 'Before Restore',
|
'agentSnapshot.source.restore': 'Before Restore',
|
||||||
|
'agentSnapshot.source.initial': 'Initial Version',
|
||||||
'agentSnapshot.field.restore': 'Restore',
|
'agentSnapshot.field.restore': 'Restore',
|
||||||
|
'agentSnapshot.field.initial': 'Initial Snapshot',
|
||||||
'agentSnapshot.field.agentCode': 'Agent Code',
|
'agentSnapshot.field.agentCode': 'Agent Code',
|
||||||
'agentSnapshot.field.agentName': 'Nickname',
|
'agentSnapshot.field.agentName': 'Nickname',
|
||||||
'agentSnapshot.field.asrModelId': 'Speech Recognition',
|
'agentSnapshot.field.asrModelId': 'Speech Recognition',
|
||||||
@@ -916,6 +931,52 @@ export default {
|
|||||||
'agentSnapshot.field.correctWordFileIds': 'Replacement Words',
|
'agentSnapshot.field.correctWordFileIds': 'Replacement Words',
|
||||||
'agentSnapshot.field.tagNames': 'Tags',
|
'agentSnapshot.field.tagNames': 'Tags',
|
||||||
'agentSnapshot.field.tagIds': 'Tag IDs',
|
'agentSnapshot.field.tagIds': 'Tag IDs',
|
||||||
|
'agentSnapshot.function.added': 'Enabled',
|
||||||
|
'agentSnapshot.function.removed': 'Disabled',
|
||||||
|
'agentSnapshot.function.updated': 'Parameters changed',
|
||||||
|
'agentSnapshot.function.enabled': 'Enabled',
|
||||||
|
'agentSnapshot.function.disabled': 'Disabled',
|
||||||
|
'agentSnapshot.function.disabledNote': 'This function is disabled',
|
||||||
|
'agentSnapshot.function.noParams': 'No parameters required',
|
||||||
|
'agentSnapshot.chatHistoryConf.none': 'Do not record chat history',
|
||||||
|
'agentSnapshot.chatHistoryConf.text': 'Report text',
|
||||||
|
'agentSnapshot.chatHistoryConf.textVoice': 'Report text and voice',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEATHER': 'Weather',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': 'Server music player',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': 'China News',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'NewsNow aggregation',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'HomeAssistant state query',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'HomeAssistant state update',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'HomeAssistant music player',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': 'Web search',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': 'Device-to-device call',
|
||||||
|
'agentSnapshot.pluginField.url': 'Endpoint URL',
|
||||||
|
'agentSnapshot.pluginField.news_sources': 'News sources',
|
||||||
|
'agentSnapshot.pluginField.default_rss_url': 'Default RSS URL',
|
||||||
|
'agentSnapshot.pluginField.society_rss_url': 'Society RSS URL',
|
||||||
|
'agentSnapshot.pluginField.world_rss_url': 'World RSS URL',
|
||||||
|
'agentSnapshot.pluginField.finance_rss_url': 'Finance RSS URL',
|
||||||
|
'agentSnapshot.pluginField.api_key': 'API key',
|
||||||
|
'agentSnapshot.pluginField.default_location': 'Default city',
|
||||||
|
'agentSnapshot.pluginField.api_host': 'Developer API host',
|
||||||
|
'agentSnapshot.pluginField.base_url': 'Server URL',
|
||||||
|
'agentSnapshot.pluginField.devices': 'Devices',
|
||||||
|
'agentSnapshot.pluginField.provider': 'Search provider',
|
||||||
|
'agentSnapshot.pluginField.description': 'Tool description',
|
||||||
|
'agentSnapshot.pluginField.max_results': 'Result count',
|
||||||
|
'agentSnapshot.model.Memory_nomem': 'No memory',
|
||||||
|
'agentSnapshot.model.Memory_mem_local_short': 'Local short memory',
|
||||||
|
'agentSnapshot.model.Memory_mem0ai': 'Mem0AI memory',
|
||||||
|
'agentSnapshot.model.Memory_mem_report_only': 'Report only',
|
||||||
|
'agentSnapshot.model.Intent_nointent': 'No intent recognition',
|
||||||
|
'agentSnapshot.model.Intent_intent_llm': 'External LLM intent recognition',
|
||||||
|
'agentSnapshot.model.Intent_function_call': 'LLM function calling',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0001': 'EdgeTTS female - Xiaoxiao',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0002': 'EdgeTTS male - Yunyang',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0003': 'EdgeTTS female - Xiaoyi',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0004': 'EdgeTTS male - Yunjian',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0005': 'EdgeTTS male - Yunxi',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0006': 'EdgeTTS male - Yunxia',
|
||||||
|
|
||||||
// Form field Tooltip descriptions
|
// Form field Tooltip descriptions
|
||||||
'roleConfig.tooltip.agentName': 'Set the name of your AI agent for identification and recognition',
|
'roleConfig.tooltip.agentName': 'Set the name of your AI agent for identification and recognition',
|
||||||
|
|||||||
@@ -806,12 +806,126 @@ export default {
|
|||||||
'roleConfig.addTag': 'Adicionar Novo Rótulo',
|
'roleConfig.addTag': 'Adicionar Novo Rótulo',
|
||||||
'roleConfig.restartNotice': 'Reinicie após salvar a configuração para ativação.',
|
'roleConfig.restartNotice': 'Reinicie após salvar a configuração para ativação.',
|
||||||
'roleConfig.saveConfig': 'Salvar Configuração',
|
'roleConfig.saveConfig': 'Salvar Configuração',
|
||||||
|
'roleConfig.snapshotHistory': 'Histórico de Versões',
|
||||||
|
'roleConfig.currentVersion': 'Versão Atual {version}',
|
||||||
|
'roleConfig.functionName': 'Nome da Função',
|
||||||
'roleConfig.reset': 'Redefinir',
|
'roleConfig.reset': 'Redefinir',
|
||||||
'roleConfig.agentName': 'Apelido',
|
'roleConfig.agentName': 'Apelido',
|
||||||
'roleConfig.roleTemplate': 'Modelo',
|
'roleConfig.roleTemplate': 'Modelo',
|
||||||
'roleConfig.roleIntroduction': 'Introdução',
|
'roleConfig.roleIntroduction': 'Introdução',
|
||||||
'roleConfig.languageCode': 'Código do Idioma',
|
'roleConfig.languageCode': 'Código do Idioma',
|
||||||
'roleConfig.interactionLanguage': 'Idioma de Interação',
|
'roleConfig.interactionLanguage': 'Idioma de Interação',
|
||||||
|
'agentSnapshot.title': 'Histórico de Versões',
|
||||||
|
'agentSnapshot.empty': 'Ainda sem versões',
|
||||||
|
'agentSnapshot.version': 'Versão',
|
||||||
|
'agentSnapshot.createdAt': 'Salvo em',
|
||||||
|
'agentSnapshot.source': 'Origem',
|
||||||
|
'agentSnapshot.changedFields': 'Alterações',
|
||||||
|
'agentSnapshot.actions': 'Ações',
|
||||||
|
'agentSnapshot.view': 'Ver',
|
||||||
|
'agentSnapshot.restore': 'Restaurar',
|
||||||
|
'agentSnapshot.delete': 'Excluir',
|
||||||
|
'agentSnapshot.detailTitle': 'Detalhes da Alteração',
|
||||||
|
'agentSnapshot.restorePreviewTitle': 'Prévia da Restauração',
|
||||||
|
'agentSnapshot.confirmRestore': 'Confirmar Restauração',
|
||||||
|
'agentSnapshot.before': 'Antes',
|
||||||
|
'agentSnapshot.after': 'Depois',
|
||||||
|
'agentSnapshot.emptyValue': 'Nenhum',
|
||||||
|
'agentSnapshot.noChangedContent': 'Nenhuma alteração para exibir',
|
||||||
|
'agentSnapshot.noFunctionChange': 'Sem alterações de função',
|
||||||
|
'agentSnapshot.beforeChange': 'Antes da Alteração',
|
||||||
|
'agentSnapshot.afterChange': 'Depois da Alteração',
|
||||||
|
'agentSnapshot.beforeRestore': 'Antes da Restauração',
|
||||||
|
'agentSnapshot.afterRestore': 'Depois da Restauração',
|
||||||
|
'agentSnapshot.restoreMemoryWarning': 'Restaurar para uma versão sem memória apagará o histórico de conversa deste agente. Continue somente após confirmar esse risco.',
|
||||||
|
'agentSnapshot.restoreConfirm': 'Restaurar para a versão {version}? A configuração atual será salva primeiro como uma nova versão.',
|
||||||
|
'agentSnapshot.restoreSuccess': 'Versão restaurada',
|
||||||
|
'agentSnapshot.restoreFailed': 'Falha ao restaurar versão',
|
||||||
|
'agentSnapshot.restoreFailedRollback': 'Falha ao restaurar versão. A transação foi revertida e a configuração atual não foi alterada',
|
||||||
|
'agentSnapshot.deleteConfirm': 'Excluir a versão {version}? Esta ação não pode ser desfeita.',
|
||||||
|
'agentSnapshot.deleteSuccess': 'Versão do histórico excluída',
|
||||||
|
'agentSnapshot.deleteFailed': 'Falha ao excluir a versão do histórico',
|
||||||
|
'agentSnapshot.fetchFailed': 'Falha ao buscar versões',
|
||||||
|
'agentSnapshot.detailFailed': 'Falha ao buscar detalhes do snapshot',
|
||||||
|
'agentSnapshot.currentVersion': 'Versão Atual',
|
||||||
|
'agentSnapshot.source.config': 'Configuração Salva',
|
||||||
|
'agentSnapshot.source.current': 'Configuração Atual',
|
||||||
|
'agentSnapshot.source.restore': 'Antes da Restauração',
|
||||||
|
'agentSnapshot.source.initial': 'Versão inicial',
|
||||||
|
'agentSnapshot.field.restore': 'Restauração',
|
||||||
|
'agentSnapshot.field.initial': 'Snapshot inicial',
|
||||||
|
'agentSnapshot.field.agentCode': 'Código do Agente',
|
||||||
|
'agentSnapshot.field.agentName': 'Apelido',
|
||||||
|
'agentSnapshot.field.asrModelId': 'Reconhecimento de Fala',
|
||||||
|
'agentSnapshot.field.vadModelId': 'Detecção de Atividade de Voz',
|
||||||
|
'agentSnapshot.field.llmModelId': 'Modelo de Linguagem Principal',
|
||||||
|
'agentSnapshot.field.slmModelId': 'Modelo de Linguagem Pequeno',
|
||||||
|
'agentSnapshot.field.vllmModelId': 'Modelo de Visão',
|
||||||
|
'agentSnapshot.field.ttsModelId': 'Texto para Fala',
|
||||||
|
'agentSnapshot.field.ttsVoiceId': 'Voz',
|
||||||
|
'agentSnapshot.field.ttsLanguage': 'Idioma',
|
||||||
|
'agentSnapshot.field.ttsVolume': 'Volume',
|
||||||
|
'agentSnapshot.field.ttsRate': 'Velocidade',
|
||||||
|
'agentSnapshot.field.ttsPitch': 'Tom',
|
||||||
|
'agentSnapshot.field.memModelId': 'Modelo de Memória',
|
||||||
|
'agentSnapshot.field.intentModelId': 'Reconhecimento de Intenção',
|
||||||
|
'agentSnapshot.field.chatHistoryConf': 'Histórico de Conversa',
|
||||||
|
'agentSnapshot.field.systemPrompt': 'Introdução',
|
||||||
|
'agentSnapshot.field.summaryMemory': 'Memória',
|
||||||
|
'agentSnapshot.field.langCode': 'Código do Idioma',
|
||||||
|
'agentSnapshot.field.language': 'Idioma de Interação',
|
||||||
|
'agentSnapshot.field.sort': 'Ordenação',
|
||||||
|
'agentSnapshot.field.functions': 'Funções',
|
||||||
|
'agentSnapshot.field.contextProviders': 'Provedores de Contexto',
|
||||||
|
'agentSnapshot.field.correctWordFileIds': 'Palavras de Substituição',
|
||||||
|
'agentSnapshot.field.tagNames': 'Rótulos',
|
||||||
|
'agentSnapshot.field.tagIds': 'IDs dos Rótulos',
|
||||||
|
'agentSnapshot.function.added': 'Ativado',
|
||||||
|
'agentSnapshot.function.removed': 'Desativado',
|
||||||
|
'agentSnapshot.function.updated': 'Parâmetros alterados',
|
||||||
|
'agentSnapshot.function.enabled': 'Ativado',
|
||||||
|
'agentSnapshot.function.disabled': 'Desativado',
|
||||||
|
'agentSnapshot.function.disabledNote': 'Esta função está desativada',
|
||||||
|
'agentSnapshot.function.noParams': 'Nenhum parâmetro necessário',
|
||||||
|
'agentSnapshot.chatHistoryConf.none': 'Não registrar histórico de conversa',
|
||||||
|
'agentSnapshot.chatHistoryConf.text': 'Enviar texto',
|
||||||
|
'agentSnapshot.chatHistoryConf.textVoice': 'Enviar texto e voz',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEATHER': 'Clima',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': 'Reprodutor de música do servidor',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': 'China News',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'Agregação NewsNow',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'Consulta de estado do HomeAssistant',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'Atualização de estado do HomeAssistant',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'Reprodutor de música do HomeAssistant',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': 'Pesquisa na web',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': 'Chamada entre dispositivos',
|
||||||
|
'agentSnapshot.pluginField.url': 'URL do endpoint',
|
||||||
|
'agentSnapshot.pluginField.news_sources': 'Fontes de notícias',
|
||||||
|
'agentSnapshot.pluginField.default_rss_url': 'URL RSS padrão',
|
||||||
|
'agentSnapshot.pluginField.society_rss_url': 'URL RSS de sociedade',
|
||||||
|
'agentSnapshot.pluginField.world_rss_url': 'URL RSS mundial',
|
||||||
|
'agentSnapshot.pluginField.finance_rss_url': 'URL RSS financeiro',
|
||||||
|
'agentSnapshot.pluginField.api_key': 'Chave de API',
|
||||||
|
'agentSnapshot.pluginField.default_location': 'Cidade padrão',
|
||||||
|
'agentSnapshot.pluginField.api_host': 'Host da API de desenvolvedor',
|
||||||
|
'agentSnapshot.pluginField.base_url': 'URL do servidor',
|
||||||
|
'agentSnapshot.pluginField.devices': 'Dispositivos',
|
||||||
|
'agentSnapshot.pluginField.provider': 'Provedor de pesquisa',
|
||||||
|
'agentSnapshot.pluginField.description': 'Descrição da ferramenta',
|
||||||
|
'agentSnapshot.pluginField.max_results': 'Quantidade de resultados',
|
||||||
|
'agentSnapshot.model.Memory_nomem': 'Sem memória',
|
||||||
|
'agentSnapshot.model.Memory_mem_local_short': 'Memória curta local',
|
||||||
|
'agentSnapshot.model.Memory_mem0ai': 'Memória Mem0AI',
|
||||||
|
'agentSnapshot.model.Memory_mem_report_only': 'Somente relatório',
|
||||||
|
'agentSnapshot.model.Intent_nointent': 'Sem reconhecimento de intenção',
|
||||||
|
'agentSnapshot.model.Intent_intent_llm': 'Reconhecimento de intenção por LLM externo',
|
||||||
|
'agentSnapshot.model.Intent_function_call': 'Chamada de função por LLM',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0001': 'EdgeTTS feminino - Xiaoxiao',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0002': 'EdgeTTS masculino - Yunyang',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0003': 'EdgeTTS feminino - Xiaoyi',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0004': 'EdgeTTS masculino - Yunjian',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0005': 'EdgeTTS masculino - Yunxi',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0006': 'EdgeTTS masculino - Yunxia',
|
||||||
'roleConfig.vad': 'Detecção de Voz',
|
'roleConfig.vad': 'Detecção de Voz',
|
||||||
'roleConfig.asr': 'Reconhecimento de Fala',
|
'roleConfig.asr': 'Reconhecimento de Fala',
|
||||||
'roleConfig.llm': 'Modelo de Linguagem Principal',
|
'roleConfig.llm': 'Modelo de Linguagem Principal',
|
||||||
@@ -1554,4 +1668,4 @@ export default {
|
|||||||
// Header navigation
|
// Header navigation
|
||||||
'header.addressBook': 'Lista de Contatos',
|
'header.addressBook': 'Lista de Contatos',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -806,12 +806,126 @@ export default {
|
|||||||
'roleConfig.addTag': 'Thêm mới nhãn',
|
'roleConfig.addTag': 'Thêm mới nhãn',
|
||||||
'roleConfig.restartNotice': 'Sau khi lưu cấu hình, bạn cần khởi động lại thiết bị để cấu hình mới có hiệu lực.',
|
'roleConfig.restartNotice': 'Sau khi lưu cấu hình, bạn cần khởi động lại thiết bị để cấu hình mới có hiệu lực.',
|
||||||
'roleConfig.saveConfig': 'Lưu cấu hình',
|
'roleConfig.saveConfig': 'Lưu cấu hình',
|
||||||
|
'roleConfig.snapshotHistory': 'Lịch sử phiên bản',
|
||||||
|
'roleConfig.currentVersion': 'Phiên bản hiện tại {version}',
|
||||||
|
'roleConfig.functionName': 'Tên chức năng',
|
||||||
'roleConfig.reset': 'Đặt lại',
|
'roleConfig.reset': 'Đặt lại',
|
||||||
'roleConfig.agentName': 'Biệt danh',
|
'roleConfig.agentName': 'Biệt danh',
|
||||||
'roleConfig.roleTemplate': 'Mẫu',
|
'roleConfig.roleTemplate': 'Mẫu',
|
||||||
'roleConfig.roleIntroduction': 'Giới thiệu',
|
'roleConfig.roleIntroduction': 'Giới thiệu',
|
||||||
'roleConfig.languageCode': 'Mã ngôn ngữ',
|
'roleConfig.languageCode': 'Mã ngôn ngữ',
|
||||||
'roleConfig.interactionLanguage': 'Ngôn ngữ tương tác',
|
'roleConfig.interactionLanguage': 'Ngôn ngữ tương tác',
|
||||||
|
'agentSnapshot.title': 'Lịch sử phiên bản',
|
||||||
|
'agentSnapshot.empty': 'Chưa có phiên bản',
|
||||||
|
'agentSnapshot.version': 'Phiên bản',
|
||||||
|
'agentSnapshot.createdAt': 'Lưu lúc',
|
||||||
|
'agentSnapshot.source': 'Nguồn',
|
||||||
|
'agentSnapshot.changedFields': 'Thay đổi',
|
||||||
|
'agentSnapshot.actions': 'Thao tác',
|
||||||
|
'agentSnapshot.view': 'Xem',
|
||||||
|
'agentSnapshot.restore': 'Khôi phục',
|
||||||
|
'agentSnapshot.delete': 'Xóa',
|
||||||
|
'agentSnapshot.detailTitle': 'Chi tiết thay đổi',
|
||||||
|
'agentSnapshot.restorePreviewTitle': 'Xem trước khôi phục',
|
||||||
|
'agentSnapshot.confirmRestore': 'Xác nhận khôi phục',
|
||||||
|
'agentSnapshot.before': 'Trước',
|
||||||
|
'agentSnapshot.after': 'Sau',
|
||||||
|
'agentSnapshot.emptyValue': 'Không có',
|
||||||
|
'agentSnapshot.noChangedContent': 'Không có thay đổi để hiển thị',
|
||||||
|
'agentSnapshot.noFunctionChange': 'Không có thay đổi chức năng',
|
||||||
|
'agentSnapshot.beforeChange': 'Trước thay đổi',
|
||||||
|
'agentSnapshot.afterChange': 'Sau thay đổi',
|
||||||
|
'agentSnapshot.beforeRestore': 'Trước khi khôi phục',
|
||||||
|
'agentSnapshot.afterRestore': 'Sau khi khôi phục',
|
||||||
|
'agentSnapshot.restoreMemoryWarning': 'Khôi phục về phiên bản không có bộ nhớ sẽ xóa lịch sử trò chuyện của tác nhân này. Chỉ tiếp tục sau khi xác nhận rủi ro này.',
|
||||||
|
'agentSnapshot.restoreConfirm': 'Khôi phục về phiên bản {version}? Cấu hình hiện tại sẽ được lưu thành phiên bản mới trước.',
|
||||||
|
'agentSnapshot.restoreSuccess': 'Đã khôi phục phiên bản',
|
||||||
|
'agentSnapshot.restoreFailed': 'Khôi phục phiên bản thất bại',
|
||||||
|
'agentSnapshot.restoreFailedRollback': 'Khôi phục phiên bản thất bại. Giao dịch đã được hoàn tác và cấu hình hiện tại chưa thay đổi',
|
||||||
|
'agentSnapshot.deleteConfirm': 'Xóa phiên bản {version}? Không thể hoàn tác thao tác này.',
|
||||||
|
'agentSnapshot.deleteSuccess': 'Đã xóa phiên bản lịch sử',
|
||||||
|
'agentSnapshot.deleteFailed': 'Xóa phiên bản lịch sử thất bại',
|
||||||
|
'agentSnapshot.fetchFailed': 'Không thể tải lịch sử phiên bản',
|
||||||
|
'agentSnapshot.detailFailed': 'Không thể tải chi tiết snapshot',
|
||||||
|
'agentSnapshot.currentVersion': 'Phiên bản hiện tại',
|
||||||
|
'agentSnapshot.source.config': 'Lưu cấu hình',
|
||||||
|
'agentSnapshot.source.current': 'Cấu hình hiện tại',
|
||||||
|
'agentSnapshot.source.restore': 'Trước khi khôi phục',
|
||||||
|
'agentSnapshot.source.initial': 'Phiên bản ban đầu',
|
||||||
|
'agentSnapshot.field.restore': 'Khôi phục',
|
||||||
|
'agentSnapshot.field.initial': 'Snapshot ban đầu',
|
||||||
|
'agentSnapshot.field.agentCode': 'Mã tác nhân',
|
||||||
|
'agentSnapshot.field.agentName': 'Biệt danh',
|
||||||
|
'agentSnapshot.field.asrModelId': 'Nhận dạng giọng nói',
|
||||||
|
'agentSnapshot.field.vadModelId': 'Phát hiện hoạt động giọng nói',
|
||||||
|
'agentSnapshot.field.llmModelId': 'Mô hình ngôn ngữ chính',
|
||||||
|
'agentSnapshot.field.slmModelId': 'Mô hình ngôn ngữ nhỏ',
|
||||||
|
'agentSnapshot.field.vllmModelId': 'Mô hình thị giác',
|
||||||
|
'agentSnapshot.field.ttsModelId': 'Văn bản thành giọng nói',
|
||||||
|
'agentSnapshot.field.ttsVoiceId': 'Giọng nói',
|
||||||
|
'agentSnapshot.field.ttsLanguage': 'Ngôn ngữ',
|
||||||
|
'agentSnapshot.field.ttsVolume': 'Âm lượng',
|
||||||
|
'agentSnapshot.field.ttsRate': 'Tốc độ',
|
||||||
|
'agentSnapshot.field.ttsPitch': 'Cao độ',
|
||||||
|
'agentSnapshot.field.memModelId': 'Mô hình bộ nhớ',
|
||||||
|
'agentSnapshot.field.intentModelId': 'Nhận dạng ý định',
|
||||||
|
'agentSnapshot.field.chatHistoryConf': 'Lịch sử trò chuyện',
|
||||||
|
'agentSnapshot.field.systemPrompt': 'Giới thiệu',
|
||||||
|
'agentSnapshot.field.summaryMemory': 'Bộ nhớ',
|
||||||
|
'agentSnapshot.field.langCode': 'Mã ngôn ngữ',
|
||||||
|
'agentSnapshot.field.language': 'Ngôn ngữ tương tác',
|
||||||
|
'agentSnapshot.field.sort': 'Sắp xếp',
|
||||||
|
'agentSnapshot.field.functions': 'Chức năng',
|
||||||
|
'agentSnapshot.field.contextProviders': 'Nguồn ngữ cảnh',
|
||||||
|
'agentSnapshot.field.correctWordFileIds': 'Từ thay thế',
|
||||||
|
'agentSnapshot.field.tagNames': 'Nhãn',
|
||||||
|
'agentSnapshot.field.tagIds': 'ID nhãn',
|
||||||
|
'agentSnapshot.function.added': 'Đã bật',
|
||||||
|
'agentSnapshot.function.removed': 'Đã tắt',
|
||||||
|
'agentSnapshot.function.updated': 'Tham số đã thay đổi',
|
||||||
|
'agentSnapshot.function.enabled': 'Đã bật',
|
||||||
|
'agentSnapshot.function.disabled': 'Đã tắt',
|
||||||
|
'agentSnapshot.function.disabledNote': 'Chức năng này đang tắt',
|
||||||
|
'agentSnapshot.function.noParams': 'Không cần cấu hình tham số',
|
||||||
|
'agentSnapshot.chatHistoryConf.none': 'Không ghi lịch sử trò chuyện',
|
||||||
|
'agentSnapshot.chatHistoryConf.text': 'Báo cáo văn bản',
|
||||||
|
'agentSnapshot.chatHistoryConf.textVoice': 'Báo cáo văn bản và giọng nói',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEATHER': 'Thời tiết',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': 'Trình phát nhạc máy chủ',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': 'Tin tức China News',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'Tổng hợp NewsNow',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'Truy vấn trạng thái HomeAssistant',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'Cập nhật trạng thái HomeAssistant',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'Trình phát nhạc HomeAssistant',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': 'Tìm kiếm web',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': 'Gọi thiết bị',
|
||||||
|
'agentSnapshot.pluginField.url': 'URL điểm cuối',
|
||||||
|
'agentSnapshot.pluginField.news_sources': 'Nguồn tin',
|
||||||
|
'agentSnapshot.pluginField.default_rss_url': 'URL RSS mặc định',
|
||||||
|
'agentSnapshot.pluginField.society_rss_url': 'URL RSS xã hội',
|
||||||
|
'agentSnapshot.pluginField.world_rss_url': 'URL RSS thế giới',
|
||||||
|
'agentSnapshot.pluginField.finance_rss_url': 'URL RSS tài chính',
|
||||||
|
'agentSnapshot.pluginField.api_key': 'Khóa API',
|
||||||
|
'agentSnapshot.pluginField.default_location': 'Thành phố mặc định',
|
||||||
|
'agentSnapshot.pluginField.api_host': 'Máy chủ API nhà phát triển',
|
||||||
|
'agentSnapshot.pluginField.base_url': 'URL máy chủ',
|
||||||
|
'agentSnapshot.pluginField.devices': 'Thiết bị',
|
||||||
|
'agentSnapshot.pluginField.provider': 'Nhà cung cấp tìm kiếm',
|
||||||
|
'agentSnapshot.pluginField.description': 'Mô tả công cụ',
|
||||||
|
'agentSnapshot.pluginField.max_results': 'Số lượng kết quả',
|
||||||
|
'agentSnapshot.model.Memory_nomem': 'Không có bộ nhớ',
|
||||||
|
'agentSnapshot.model.Memory_mem_local_short': 'Bộ nhớ ngắn cục bộ',
|
||||||
|
'agentSnapshot.model.Memory_mem0ai': 'Bộ nhớ Mem0AI',
|
||||||
|
'agentSnapshot.model.Memory_mem_report_only': 'Chỉ báo cáo bộ nhớ',
|
||||||
|
'agentSnapshot.model.Intent_nointent': 'Không nhận dạng ý định',
|
||||||
|
'agentSnapshot.model.Intent_intent_llm': 'Nhận dạng ý định bằng LLM bên ngoài',
|
||||||
|
'agentSnapshot.model.Intent_function_call': 'LLM gọi hàm',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0001': 'EdgeTTS nữ - Xiaoxiao',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0002': 'EdgeTTS nam - Yunyang',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0003': 'EdgeTTS nữ - Xiaoyi',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0004': 'EdgeTTS nam - Yunjian',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0005': 'EdgeTTS nam - Yunxi',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0006': 'EdgeTTS nam - Yunxia',
|
||||||
'roleConfig.vad': 'Phát hiện giọng nói',
|
'roleConfig.vad': 'Phát hiện giọng nói',
|
||||||
'roleConfig.asr': 'Nhận dạng giọng nói',
|
'roleConfig.asr': 'Nhận dạng giọng nói',
|
||||||
'roleConfig.llm': 'Mô hình ngôn ngữ chính',
|
'roleConfig.llm': 'Mô hình ngôn ngữ chính',
|
||||||
@@ -1554,4 +1668,4 @@ export default {
|
|||||||
// Header navigation
|
// Header navigation
|
||||||
'header.addressBook': 'Danh bạ',
|
'header.addressBook': 'Danh bạ',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -807,6 +807,8 @@ export default {
|
|||||||
'roleConfig.restartNotice': '保存配置后,需要重启设备,新的配置才会生效。',
|
'roleConfig.restartNotice': '保存配置后,需要重启设备,新的配置才会生效。',
|
||||||
'roleConfig.saveConfig': '保存配置',
|
'roleConfig.saveConfig': '保存配置',
|
||||||
'roleConfig.snapshotHistory': '历史版本',
|
'roleConfig.snapshotHistory': '历史版本',
|
||||||
|
'roleConfig.currentVersion': '当前版本 {version}',
|
||||||
|
'roleConfig.functionName': '功能名称',
|
||||||
'roleConfig.reset': '重置',
|
'roleConfig.reset': '重置',
|
||||||
'roleConfig.agentName': '助手昵称',
|
'roleConfig.agentName': '助手昵称',
|
||||||
'roleConfig.roleTemplate': '角色模版',
|
'roleConfig.roleTemplate': '角色模版',
|
||||||
@@ -816,7 +818,7 @@ export default {
|
|||||||
'roleConfig.vad': '语音活动检测(VAD)',
|
'roleConfig.vad': '语音活动检测(VAD)',
|
||||||
'roleConfig.asr': '语音识别(ASR)',
|
'roleConfig.asr': '语音识别(ASR)',
|
||||||
'roleConfig.llm': '主语言模型(LLM)',
|
'roleConfig.llm': '主语言模型(LLM)',
|
||||||
"roleConfig.slm": "小参数模型(SLM)",
|
'roleConfig.slm': '小参数模型(SLM)',
|
||||||
'roleConfig.vllm': '视觉大模型(VLLM)',
|
'roleConfig.vllm': '视觉大模型(VLLM)',
|
||||||
'roleConfig.intent': '意图识别(Intent)',
|
'roleConfig.intent': '意图识别(Intent)',
|
||||||
'roleConfig.memoryHis': '记忆',
|
'roleConfig.memoryHis': '记忆',
|
||||||
@@ -873,6 +875,7 @@ export default {
|
|||||||
'agentSnapshot.actions': '操作',
|
'agentSnapshot.actions': '操作',
|
||||||
'agentSnapshot.view': '查看',
|
'agentSnapshot.view': '查看',
|
||||||
'agentSnapshot.restore': '恢复',
|
'agentSnapshot.restore': '恢复',
|
||||||
|
'agentSnapshot.delete': '删除',
|
||||||
'agentSnapshot.detailTitle': '变更详情',
|
'agentSnapshot.detailTitle': '变更详情',
|
||||||
'agentSnapshot.restorePreviewTitle': '恢复确认',
|
'agentSnapshot.restorePreviewTitle': '恢复确认',
|
||||||
'agentSnapshot.confirmRestore': '确认恢复',
|
'agentSnapshot.confirmRestore': '确认恢复',
|
||||||
@@ -880,16 +883,28 @@ export default {
|
|||||||
'agentSnapshot.after': '变化后',
|
'agentSnapshot.after': '变化后',
|
||||||
'agentSnapshot.emptyValue': '无',
|
'agentSnapshot.emptyValue': '无',
|
||||||
'agentSnapshot.noChangedContent': '暂无可展示的变更内容',
|
'agentSnapshot.noChangedContent': '暂无可展示的变更内容',
|
||||||
'agentSnapshot.restoreConfirm': '确定恢复到版本 #{version} 吗?当前配置会先自动保存为新的历史版本。',
|
'agentSnapshot.noFunctionChange': '功能配置无变化',
|
||||||
|
'agentSnapshot.beforeChange': '变化前',
|
||||||
|
'agentSnapshot.afterChange': '变化后',
|
||||||
|
'agentSnapshot.beforeRestore': '恢复前',
|
||||||
|
'agentSnapshot.afterRestore': '恢复后',
|
||||||
|
'agentSnapshot.restoreMemoryWarning': '恢复到无记忆版本会清空该智能体的聊天记录,请确认后再继续。',
|
||||||
|
'agentSnapshot.restoreConfirm': '确定恢复到版本 {version} 吗?当前配置会先自动保存为新的历史版本。',
|
||||||
'agentSnapshot.restoreSuccess': '已恢复历史版本',
|
'agentSnapshot.restoreSuccess': '已恢复历史版本',
|
||||||
'agentSnapshot.restoreFailed': '恢复历史版本失败',
|
'agentSnapshot.restoreFailed': '恢复历史版本失败',
|
||||||
|
'agentSnapshot.restoreFailedRollback': '恢复历史版本失败,事务已回滚,当前配置未改变',
|
||||||
|
'agentSnapshot.deleteConfirm': '确定删除版本 {version} 吗?删除后不可恢复。',
|
||||||
|
'agentSnapshot.deleteSuccess': '已删除历史版本',
|
||||||
|
'agentSnapshot.deleteFailed': '删除历史版本失败',
|
||||||
'agentSnapshot.fetchFailed': '获取历史版本失败',
|
'agentSnapshot.fetchFailed': '获取历史版本失败',
|
||||||
'agentSnapshot.detailFailed': '获取快照详情失败',
|
'agentSnapshot.detailFailed': '获取快照详情失败',
|
||||||
'agentSnapshot.currentVersion': '当前版本',
|
'agentSnapshot.currentVersion': '当前版本',
|
||||||
'agentSnapshot.source.config': '配置保存',
|
'agentSnapshot.source.config': '配置保存',
|
||||||
'agentSnapshot.source.current': '当前配置',
|
'agentSnapshot.source.current': '当前配置',
|
||||||
'agentSnapshot.source.restore': '恢复前',
|
'agentSnapshot.source.restore': '恢复前',
|
||||||
|
'agentSnapshot.source.initial': '初始版本',
|
||||||
'agentSnapshot.field.restore': '恢复操作',
|
'agentSnapshot.field.restore': '恢复操作',
|
||||||
|
'agentSnapshot.field.initial': '初始快照',
|
||||||
'agentSnapshot.field.agentCode': '助手编码',
|
'agentSnapshot.field.agentCode': '助手编码',
|
||||||
'agentSnapshot.field.agentName': '助手昵称',
|
'agentSnapshot.field.agentName': '助手昵称',
|
||||||
'agentSnapshot.field.asrModelId': '语音识别',
|
'agentSnapshot.field.asrModelId': '语音识别',
|
||||||
@@ -916,6 +931,52 @@ export default {
|
|||||||
'agentSnapshot.field.correctWordFileIds': '替换词',
|
'agentSnapshot.field.correctWordFileIds': '替换词',
|
||||||
'agentSnapshot.field.tagNames': '标签',
|
'agentSnapshot.field.tagNames': '标签',
|
||||||
'agentSnapshot.field.tagIds': '标签ID',
|
'agentSnapshot.field.tagIds': '标签ID',
|
||||||
|
'agentSnapshot.function.added': '已开启',
|
||||||
|
'agentSnapshot.function.removed': '已关闭',
|
||||||
|
'agentSnapshot.function.updated': '参数变更',
|
||||||
|
'agentSnapshot.function.enabled': '已开启',
|
||||||
|
'agentSnapshot.function.disabled': '未开启',
|
||||||
|
'agentSnapshot.function.disabledNote': '未开启此功能',
|
||||||
|
'agentSnapshot.function.noParams': '无需配置参数',
|
||||||
|
'agentSnapshot.chatHistoryConf.none': '不记录聊天记录',
|
||||||
|
'agentSnapshot.chatHistoryConf.text': '上报文字',
|
||||||
|
'agentSnapshot.chatHistoryConf.textVoice': '上报文字+语音',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEATHER': '天气查询',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': '服务器音乐播放',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': '中新网新闻',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'newsnow新闻聚合',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'HomeAssistant设备状态查询',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'HomeAssistant设备状态修改',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'HomeAssistant音乐播放',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': '联网搜索',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': '设备呼叫设备',
|
||||||
|
'agentSnapshot.pluginField.url': '接口地址',
|
||||||
|
'agentSnapshot.pluginField.news_sources': '新闻源配置',
|
||||||
|
'agentSnapshot.pluginField.default_rss_url': '默认 RSS 源',
|
||||||
|
'agentSnapshot.pluginField.society_rss_url': '社会新闻 RSS 地址',
|
||||||
|
'agentSnapshot.pluginField.world_rss_url': '国际新闻 RSS 地址',
|
||||||
|
'agentSnapshot.pluginField.finance_rss_url': '财经新闻 RSS 地址',
|
||||||
|
'agentSnapshot.pluginField.api_key': 'API 密钥',
|
||||||
|
'agentSnapshot.pluginField.default_location': '默认查询城市',
|
||||||
|
'agentSnapshot.pluginField.api_host': '开发者 API Host',
|
||||||
|
'agentSnapshot.pluginField.base_url': '服务器地址',
|
||||||
|
'agentSnapshot.pluginField.devices': '设备列表',
|
||||||
|
'agentSnapshot.pluginField.provider': '搜索源',
|
||||||
|
'agentSnapshot.pluginField.description': '工具描述',
|
||||||
|
'agentSnapshot.pluginField.max_results': '返回数量',
|
||||||
|
'agentSnapshot.model.Memory_nomem': '无记忆',
|
||||||
|
'agentSnapshot.model.Memory_mem_local_short': '本地短记忆',
|
||||||
|
'agentSnapshot.model.Memory_mem0ai': 'Mem0AI记忆',
|
||||||
|
'agentSnapshot.model.Memory_mem_report_only': '仅上报记忆',
|
||||||
|
'agentSnapshot.model.Intent_nointent': '无意图识别',
|
||||||
|
'agentSnapshot.model.Intent_intent_llm': '外挂的大模型意图识别',
|
||||||
|
'agentSnapshot.model.Intent_function_call': '大模型自主函数调用',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0001': 'EdgeTTS女声-晓晓',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0002': 'EdgeTTS男声-云扬',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0003': 'EdgeTTS女声-晓伊',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0004': 'EdgeTTS男声-云健',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0005': 'EdgeTTS男声-云希',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0006': 'EdgeTTS男声-云夏',
|
||||||
|
|
||||||
// 表单字段 Tooltip 提示说明
|
// 表单字段 Tooltip 提示说明
|
||||||
'roleConfig.tooltip.agentName': '设置智能体的名称,用于标识和识别您的AI助手',
|
'roleConfig.tooltip.agentName': '设置智能体的名称,用于标识和识别您的AI助手',
|
||||||
@@ -928,7 +989,7 @@ export default {
|
|||||||
'roleConfig.tooltip.vad': '语音活动检测(Voice Activity Detection):检测用户何时开始或结束说话,用于判断对话的开始和结束,实现打断功能',
|
'roleConfig.tooltip.vad': '语音活动检测(Voice Activity Detection):检测用户何时开始或结束说话,用于判断对话的开始和结束,实现打断功能',
|
||||||
'roleConfig.tooltip.asr': '自动语音识别(Automatic Speech Recognition):将用户的语音转换为文字,是人机对话的第一步,支持多语言识别',
|
'roleConfig.tooltip.asr': '自动语音识别(Automatic Speech Recognition):将用户的语音转换为文字,是人机对话的第一步,支持多语言识别',
|
||||||
'roleConfig.tooltip.llm': '主语言模型(Large Language Model):AI助手的"大脑",负责理解用户意图、生成回答和执行各种任务',
|
'roleConfig.tooltip.llm': '主语言模型(Large Language Model):AI助手的"大脑",负责理解用户意图、生成回答和执行各种任务',
|
||||||
"roleConfig.tooltip.slm": '小参数模型(Small Language Model):用于智能体唤醒,生成记忆总结标题',
|
'roleConfig.tooltip.slm': '小参数模型(Small Language Model):用于智能体唤醒,生成记忆总结标题',
|
||||||
'roleConfig.tooltip.vllm': '视觉大语言模型(Visual LLM):处理图像和视频理解,使AI助手能够分析和描述摄像头捕获的画面内容',
|
'roleConfig.tooltip.vllm': '视觉大语言模型(Visual LLM):处理图像和视频理解,使AI助手能够分析和描述摄像头捕获的画面内容',
|
||||||
'roleConfig.tooltip.intent': '意图识别(Intent Detection):分析用户语音或文本,判断用户的真实意图,如查询、聊天、控制设备等',
|
'roleConfig.tooltip.intent': '意图识别(Intent Detection):分析用户语音或文本,判断用户的真实意图,如查询、聊天、控制设备等',
|
||||||
'roleConfig.tooltip.memory': '记忆模型(Memory Model):管理对话历史的存储和摘要,决定AI能否记住之前的对话内容,实现长期记忆功能',
|
'roleConfig.tooltip.memory': '记忆模型(Memory Model):管理对话历史的存储和摘要,决定AI能否记住之前的对话内容,实现长期记忆功能',
|
||||||
|
|||||||
@@ -806,12 +806,126 @@ export default {
|
|||||||
'roleConfig.addTag': '添加新標籤',
|
'roleConfig.addTag': '添加新標籤',
|
||||||
'roleConfig.restartNotice': '保存配置後,需要重啟設備,新的配置才會生效。',
|
'roleConfig.restartNotice': '保存配置後,需要重啟設備,新的配置才會生效。',
|
||||||
'roleConfig.saveConfig': '保存配置',
|
'roleConfig.saveConfig': '保存配置',
|
||||||
|
'roleConfig.snapshotHistory': '歷史版本',
|
||||||
|
'roleConfig.currentVersion': '目前版本 {version}',
|
||||||
|
'roleConfig.functionName': '功能名稱',
|
||||||
'roleConfig.reset': '重置',
|
'roleConfig.reset': '重置',
|
||||||
'roleConfig.agentName': '助手暱稱',
|
'roleConfig.agentName': '助手暱稱',
|
||||||
'roleConfig.roleTemplate': '角色模版',
|
'roleConfig.roleTemplate': '角色模版',
|
||||||
'roleConfig.roleIntroduction': '角色介紹',
|
'roleConfig.roleIntroduction': '角色介紹',
|
||||||
'roleConfig.languageCode': '語言編碼',
|
'roleConfig.languageCode': '語言編碼',
|
||||||
'roleConfig.interactionLanguage': '交互語種',
|
'roleConfig.interactionLanguage': '交互語種',
|
||||||
|
'agentSnapshot.title': '歷史版本',
|
||||||
|
'agentSnapshot.empty': '暫無歷史版本',
|
||||||
|
'agentSnapshot.version': '版本',
|
||||||
|
'agentSnapshot.createdAt': '保存時間',
|
||||||
|
'agentSnapshot.source': '來源',
|
||||||
|
'agentSnapshot.changedFields': '變更內容',
|
||||||
|
'agentSnapshot.actions': '操作',
|
||||||
|
'agentSnapshot.view': '查看',
|
||||||
|
'agentSnapshot.restore': '恢復',
|
||||||
|
'agentSnapshot.delete': '刪除',
|
||||||
|
'agentSnapshot.detailTitle': '變更詳情',
|
||||||
|
'agentSnapshot.restorePreviewTitle': '恢復確認',
|
||||||
|
'agentSnapshot.confirmRestore': '確認恢復',
|
||||||
|
'agentSnapshot.before': '之前',
|
||||||
|
'agentSnapshot.after': '之後',
|
||||||
|
'agentSnapshot.emptyValue': '無',
|
||||||
|
'agentSnapshot.noChangedContent': '暫無可展示的變更內容',
|
||||||
|
'agentSnapshot.noFunctionChange': '功能配置無變化',
|
||||||
|
'agentSnapshot.beforeChange': '變化前',
|
||||||
|
'agentSnapshot.afterChange': '變化後',
|
||||||
|
'agentSnapshot.beforeRestore': '恢復前',
|
||||||
|
'agentSnapshot.afterRestore': '恢復後',
|
||||||
|
'agentSnapshot.restoreMemoryWarning': '恢復到無記憶版本會清空該智能體的聊天記錄,請確認後再繼續。',
|
||||||
|
'agentSnapshot.restoreConfirm': '確定恢復到版本 {version} 嗎?目前配置會先自動保存為新的歷史版本。',
|
||||||
|
'agentSnapshot.restoreSuccess': '已恢復歷史版本',
|
||||||
|
'agentSnapshot.restoreFailed': '恢復歷史版本失敗',
|
||||||
|
'agentSnapshot.restoreFailedRollback': '恢復歷史版本失敗,交易已回滾,目前配置未改變',
|
||||||
|
'agentSnapshot.deleteConfirm': '確定刪除版本 {version} 嗎?刪除後無法復原。',
|
||||||
|
'agentSnapshot.deleteSuccess': '已刪除歷史版本',
|
||||||
|
'agentSnapshot.deleteFailed': '刪除歷史版本失敗',
|
||||||
|
'agentSnapshot.fetchFailed': '獲取歷史版本失敗',
|
||||||
|
'agentSnapshot.detailFailed': '獲取快照詳情失敗',
|
||||||
|
'agentSnapshot.currentVersion': '目前版本',
|
||||||
|
'agentSnapshot.source.config': '配置保存',
|
||||||
|
'agentSnapshot.source.current': '目前配置',
|
||||||
|
'agentSnapshot.source.restore': '恢復前',
|
||||||
|
'agentSnapshot.source.initial': '初始版本',
|
||||||
|
'agentSnapshot.field.restore': '恢復操作',
|
||||||
|
'agentSnapshot.field.initial': '初始快照',
|
||||||
|
'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',
|
||||||
|
'agentSnapshot.function.added': '已開啟',
|
||||||
|
'agentSnapshot.function.removed': '已關閉',
|
||||||
|
'agentSnapshot.function.updated': '參數變更',
|
||||||
|
'agentSnapshot.function.enabled': '已開啟',
|
||||||
|
'agentSnapshot.function.disabled': '未開啟',
|
||||||
|
'agentSnapshot.function.disabledNote': '未開啟此功能',
|
||||||
|
'agentSnapshot.function.noParams': '無需配置參數',
|
||||||
|
'agentSnapshot.chatHistoryConf.none': '不記錄聊天記錄',
|
||||||
|
'agentSnapshot.chatHistoryConf.text': '上報文字',
|
||||||
|
'agentSnapshot.chatHistoryConf.textVoice': '上報文字+語音',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEATHER': '天氣查詢',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_MUSIC': '伺服器音樂播放',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_CHINANEWS': '中新網新聞',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_NEWS_NEWSNOW': 'newsnow新聞聚合',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_GET_STATE': 'HomeAssistant設備狀態查詢',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_SET_STATE': 'HomeAssistant設備狀態修改',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_HA_PLAY_MUSIC': 'HomeAssistant音樂播放',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_WEB_SEARCH': '聯網搜尋',
|
||||||
|
'agentSnapshot.plugin.SYSTEM_PLUGIN_CALL_DEVICE': '設備呼叫設備',
|
||||||
|
'agentSnapshot.pluginField.url': '接口地址',
|
||||||
|
'agentSnapshot.pluginField.news_sources': '新聞源配置',
|
||||||
|
'agentSnapshot.pluginField.default_rss_url': '預設 RSS 源',
|
||||||
|
'agentSnapshot.pluginField.society_rss_url': '社會新聞 RSS 地址',
|
||||||
|
'agentSnapshot.pluginField.world_rss_url': '國際新聞 RSS 地址',
|
||||||
|
'agentSnapshot.pluginField.finance_rss_url': '財經新聞 RSS 地址',
|
||||||
|
'agentSnapshot.pluginField.api_key': 'API 密鑰',
|
||||||
|
'agentSnapshot.pluginField.default_location': '預設查詢城市',
|
||||||
|
'agentSnapshot.pluginField.api_host': '開發者 API Host',
|
||||||
|
'agentSnapshot.pluginField.base_url': '伺服器地址',
|
||||||
|
'agentSnapshot.pluginField.devices': '設備列表',
|
||||||
|
'agentSnapshot.pluginField.provider': '搜尋源',
|
||||||
|
'agentSnapshot.pluginField.description': '工具描述',
|
||||||
|
'agentSnapshot.pluginField.max_results': '返回數量',
|
||||||
|
'agentSnapshot.model.Memory_nomem': '無記憶',
|
||||||
|
'agentSnapshot.model.Memory_mem_local_short': '本地短記憶',
|
||||||
|
'agentSnapshot.model.Memory_mem0ai': 'Mem0AI記憶',
|
||||||
|
'agentSnapshot.model.Memory_mem_report_only': '僅上報記憶',
|
||||||
|
'agentSnapshot.model.Intent_nointent': '無意圖識別',
|
||||||
|
'agentSnapshot.model.Intent_intent_llm': '外掛的大模型意圖識別',
|
||||||
|
'agentSnapshot.model.Intent_function_call': '大模型自主函式呼叫',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0001': 'EdgeTTS女聲-曉曉',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0002': 'EdgeTTS男聲-雲揚',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0003': 'EdgeTTS女聲-曉伊',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0004': 'EdgeTTS男聲-雲健',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0005': 'EdgeTTS男聲-雲希',
|
||||||
|
'agentSnapshot.voice.TTS_EdgeTTS0006': 'EdgeTTS男聲-雲夏',
|
||||||
'roleConfig.vad': '語音活動檢測(VAD)',
|
'roleConfig.vad': '語音活動檢測(VAD)',
|
||||||
'roleConfig.asr': '語音識別(ASR)',
|
'roleConfig.asr': '語音識別(ASR)',
|
||||||
'roleConfig.llm': '主語言模型(LLM)',
|
'roleConfig.llm': '主語言模型(LLM)',
|
||||||
@@ -1553,4 +1667,4 @@ export default {
|
|||||||
'addressBookManagement.monthsAgo': '{months}個月前',
|
'addressBookManagement.monthsAgo': '{months}個月前',
|
||||||
'addressBookManagement.yearsAgo': '{years}年前',
|
'addressBookManagement.yearsAgo': '{years}年前',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="header-title">{{ form.agentName }}</span>
|
<span class="header-title">{{ form.agentName }}</span>
|
||||||
<span v-if="currentVersionNo" class="current-version-tag">
|
<span v-if="currentVersionNo" class="current-version-tag">
|
||||||
当前版本 #{{ currentVersionNo }}
|
{{ $t("roleConfig.currentVersion", { version: currentVersionNo }) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-tags">
|
<div class="header-tags">
|
||||||
@@ -319,7 +319,7 @@
|
|||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<div slot="content">
|
<div slot="content">
|
||||||
<div><strong>功能名称:</strong> {{ func.name }}</div>
|
<div><strong>{{ $t("roleConfig.functionName") }}:</strong> {{ func.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="icon-dot">
|
<div class="icon-dot">
|
||||||
{{ getFunctionDisplayChar(func.name) }}
|
{{ getFunctionDisplayChar(func.name) }}
|
||||||
@@ -562,6 +562,7 @@ export default {
|
|||||||
asr: false, // 语音识别功能状态
|
asr: false, // 语音识别功能状态
|
||||||
},
|
},
|
||||||
dynamicTags: [],
|
dynamicTags: [],
|
||||||
|
originalTagNames: [],
|
||||||
inputVisible: false,
|
inputVisible: false,
|
||||||
inputValue: '',
|
inputValue: '',
|
||||||
checkedReplacementWordIds: []
|
checkedReplacementWordIds: []
|
||||||
@@ -618,8 +619,12 @@ export default {
|
|||||||
}),
|
}),
|
||||||
contextProviders: this.currentContextProviders,
|
contextProviders: this.currentContextProviders,
|
||||||
correctWordFileIds: this.checkedReplacementWordIds,
|
correctWordFileIds: this.checkedReplacementWordIds,
|
||||||
tagNames: this.dynamicTags.map(tag => tag.tagName),
|
|
||||||
};
|
};
|
||||||
|
const tagNames = this.dynamicTags.map(tag => tag.tagName);
|
||||||
|
const tagsChanged = !this.isSameStringList(tagNames, this.originalTagNames);
|
||||||
|
if (tagsChanged) {
|
||||||
|
configData.tagNames = tagNames;
|
||||||
|
}
|
||||||
|
|
||||||
// 只在用户设置了TTS参数时才传递(不为null/undefined)
|
// 只在用户设置了TTS参数时才传递(不为null/undefined)
|
||||||
if (this.form.ttsVolume !== null && this.form.ttsVolume !== undefined) {
|
if (this.form.ttsVolume !== null && this.form.ttsVolume !== undefined) {
|
||||||
@@ -631,13 +636,20 @@ export default {
|
|||||||
if (this.form.ttsPitch !== null && this.form.ttsPitch !== undefined) {
|
if (this.form.ttsPitch !== null && this.form.ttsPitch !== undefined) {
|
||||||
configData.ttsPitch = this.form.ttsPitch;
|
configData.ttsPitch = this.form.ttsPitch;
|
||||||
}
|
}
|
||||||
Api.agent.updateAgentConfig(this.$route.query.agentId, configData, ({ data }) => {
|
const agentId = this.$route.query.agentId;
|
||||||
|
Api.agent.updateAgentConfig(agentId, configData, ({ data }) => {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
this.$message.success({
|
const afterSave = () => {
|
||||||
message: i18n.t("roleConfig.saveSuccess"),
|
if (tagsChanged) {
|
||||||
showClose: true,
|
this.originalTagNames = [...tagNames];
|
||||||
});
|
}
|
||||||
this.fetchCurrentVersion(this.$route.query.agentId);
|
this.$message.success({
|
||||||
|
message: i18n.t("roleConfig.saveSuccess"),
|
||||||
|
showClose: true,
|
||||||
|
});
|
||||||
|
this.fetchCurrentVersion(agentId);
|
||||||
|
};
|
||||||
|
afterSave();
|
||||||
} else {
|
} else {
|
||||||
this.$message.error({
|
this.$message.error({
|
||||||
message: data.msg || i18n.t("roleConfig.saveFailed"),
|
message: data.msg || i18n.t("roleConfig.saveFailed"),
|
||||||
@@ -661,17 +673,11 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Api.agent.getAgentSnapshots(
|
Api.agent.getDeviceConfig(agentId, ({ data }) => {
|
||||||
agentId,
|
if (data.code === 0) {
|
||||||
{ page: "1", limit: "1" },
|
this.currentVersionNo = data.data?.currentVersionNo || null;
|
||||||
({ data }) => {
|
|
||||||
if (data.code === 0) {
|
|
||||||
const latest = data.data?.list?.[0];
|
|
||||||
const latestVersionNo = Number(latest?.versionNo) || 0;
|
|
||||||
this.currentVersionNo = latestVersionNo + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
},
|
},
|
||||||
resetConfig() {
|
resetConfig() {
|
||||||
this.$confirm(i18n.t("roleConfig.confirmReset"), i18n.t("message.info"), {
|
this.$confirm(i18n.t("roleConfig.confirmReset"), i18n.t("message.info"), {
|
||||||
@@ -862,7 +868,7 @@ export default {
|
|||||||
});
|
});
|
||||||
this.$set(this.modelOptions, model.type, LLMdata);
|
this.$set(this.modelOptions, model.type, LLMdata);
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(data.msg || "获取LLM模型列表失败");
|
this.$message.error(data.msg || i18n.t("roleConfig.fetchModelsFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1385,7 +1391,7 @@ export default {
|
|||||||
handleInputConfirm() {
|
handleInputConfirm() {
|
||||||
let inputValue = this.inputValue;
|
let inputValue = this.inputValue;
|
||||||
if (inputValue) {
|
if (inputValue) {
|
||||||
const tag = { id: new Date().getTime(), tagName: inputValue };
|
const tag = { id: `tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, tagName: inputValue };
|
||||||
this.dynamicTags.push(tag);
|
this.dynamicTags.push(tag);
|
||||||
}
|
}
|
||||||
this.inputVisible = false;
|
this.inputVisible = false;
|
||||||
@@ -1395,14 +1401,21 @@ export default {
|
|||||||
Api.agent.getAgentTags(agentId, ({ data }) => {
|
Api.agent.getAgentTags(agentId, ({ data }) => {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
this.dynamicTags = data.data || [];
|
this.dynamicTags = data.data || [];
|
||||||
|
this.originalTagNames = this.dynamicTags.map(tag => tag.tagName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleSaveAgentTags(agentId) {
|
isSameStringList(left, right) {
|
||||||
|
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return left.every((value, index) => value === right[index]);
|
||||||
|
},
|
||||||
|
handleSaveAgentTags(agentId, tagNames = this.dynamicTags.map(tag => tag.tagName)) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tagNames = this.dynamicTags.map(tag => tag.tagName);
|
|
||||||
Api.agent.saveAgentTags(agentId, { tagNames }, ({ data }) => {
|
Api.agent.saveAgentTags(agentId, { tagNames }, ({ data }) => {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
|
this.originalTagNames = [...tagNames];
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
reject(data.msg);
|
reject(data.msg);
|
||||||
|
|||||||
Binary file not shown.
@@ -73,6 +73,7 @@ class ManageApiClient:
|
|||||||
},
|
},
|
||||||
timeout=cls.config.get("timeout", 30),
|
timeout=cls.config.get("timeout", 30),
|
||||||
limits=limits, # 使用限制
|
limits=limits, # 使用限制
|
||||||
|
trust_env=False,
|
||||||
)
|
)
|
||||||
return cls._async_clients[loop_id]
|
return cls._async_clients[loop_id]
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
|||||||
@@ -43,9 +43,13 @@ class ASRProvider(ASRProviderBase):
|
|||||||
|
|
||||||
# 内存检测,要求大于2G
|
# 内存检测,要求大于2G
|
||||||
min_mem_bytes = 2 * 1024 * 1024 * 1024
|
min_mem_bytes = 2 * 1024 * 1024 * 1024
|
||||||
total_mem = psutil.virtual_memory().total
|
try:
|
||||||
if total_mem < min_mem_bytes:
|
total_mem = psutil.virtual_memory().total
|
||||||
logger.bind(tag=TAG).error(f"可用内存不足2G,当前仅有 {total_mem / (1024*1024):.2f} MB,可能无法启动FunASR")
|
except RuntimeError as e:
|
||||||
|
logger.bind(tag=TAG).warning(f"获取系统内存信息失败,跳过FunASR内存检测: {e}")
|
||||||
|
else:
|
||||||
|
if total_mem < min_mem_bytes:
|
||||||
|
logger.bind(tag=TAG).error(f"可用内存不足2G,当前仅有 {total_mem / (1024*1024):.2f} MB,可能无法启动FunASR")
|
||||||
|
|
||||||
self.interface_type = InterfaceType.LOCAL
|
self.interface_type = InterfaceType.LOCAL
|
||||||
self.model_dir = config.get("model_dir")
|
self.model_dir = config.get("model_dir")
|
||||||
|
|||||||
Reference in New Issue
Block a user