From a5aee109fe44c8e5660af6a36bbcce4d1825b85a Mon Sep 17 00:00:00 2001 From: Tyke Chen <190473011+chentyke@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:07:54 +0800 Subject: [PATCH] feat: improve agent snapshot history --- .gitignore | 5 + .../agent/service/impl/AgentServiceImpl.java | 51 +- .../impl/AgentSnapshotServiceImpl.java | 28 + .../modules/timbre/service/TimbreService.java | 10 +- .../service/impl/TimbreServiceImpl.java | 31 +- .../impl/AgentSnapshotServiceImplTest.java | 160 ++- .../service/impl/TimbreServiceImplTest.java | 57 + main/manager-mobile/src/api/agent/agent.ts | 66 +- main/manager-mobile/src/api/agent/types.ts | 54 +- main/manager-mobile/src/i18n/de.ts | 75 + main/manager-mobile/src/i18n/en.ts | 75 + main/manager-mobile/src/i18n/pt_BR.ts | 75 + main/manager-mobile/src/i18n/vi.ts | 77 +- main/manager-mobile/src/i18n/zh_CN.ts | 75 + main/manager-mobile/src/i18n/zh_TW.ts | 75 + main/manager-mobile/src/pages.json | 56 +- .../agent/components/AgentSnapshotPanel.vue | 1230 +++++++++++++++++ main/manager-mobile/src/pages/agent/edit.vue | 245 +++- .../src/pages/agent/speedPitch.vue | 6 +- main/manager-mobile/src/store/speedPitch.ts | 22 +- .../src/components/AgentSnapshotDialog.vue | 294 +++- .../src/components/TtsAdvancedSettings.vue | 24 +- main/manager-web/src/views/roleConfig.vue | 172 ++- 23 files changed, 2833 insertions(+), 130 deletions(-) create mode 100644 main/manager-api/src/test/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImplTest.java create mode 100644 main/manager-mobile/src/pages/agent/components/AgentSnapshotPanel.vue diff --git a/.gitignore b/.gitignore index b5711fbe..bbab869d 100644 --- a/.gitignore +++ b/.gitignore @@ -112,12 +112,17 @@ celerybeat.pid # Environments .env .venv +/.venv-*/ env/ venv/ ENV/ env.bak/ venv.bak/ +# Repository-local runtimes and package-manager caches +/.runtime/ +/main/manager-web/.npm-cache/ + # Spyder project settings .spyderproject .spyproject diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java index 3d6da990..7562c287 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java @@ -56,9 +56,9 @@ import xiaozhi.modules.model.dto.VoiceDTO; import xiaozhi.modules.model.entity.ModelConfigEntity; import xiaozhi.modules.model.service.ModelConfigService; import xiaozhi.modules.model.service.ModelProviderService; -import xiaozhi.modules.security.user.SecurityUser; -import xiaozhi.modules.sys.enums.SuperAdminEnum; -import xiaozhi.modules.timbre.service.TimbreService; +import xiaozhi.modules.security.user.SecurityUser; +import xiaozhi.modules.sys.enums.SuperAdminEnum; +import xiaozhi.modules.timbre.service.TimbreService; @Service @AllArgsConstructor @@ -528,16 +528,22 @@ public class AgentServiceImpl extends BaseServiceImpl imp template.setTtsVoiceId(timbre.getId()); } } - } - - entity.setTtsVoiceId(template.getTtsVoiceId()); - entity.setMemModelId(template.getMemModelId()); - entity.setIntentModelId(template.getIntentModelId()); - entity.setSystemPrompt(template.getSystemPrompt()); - entity.setSummaryMemory(template.getSummaryMemory()); - - // 根据记忆模型类型设置默认的chatHistoryConf值 - if (template.getMemModelId() != null) { + } + + entity.setTtsVoiceId(template.getTtsVoiceId()); + entity.setTtsLanguage(defaultIfBlank(template.getTtsLanguage(), + timbreModelService.getDefaultLanguageById(entity.getTtsVoiceId()))); + entity.setMemModelId(template.getMemModelId()); + entity.setIntentModelId(template.getIntentModelId()); + entity.setSystemPrompt(template.getSystemPrompt()); + entity.setSummaryMemory(template.getSummaryMemory()); + if (Constant.MEMORY_NO_MEM.equals(entity.getMemModelId()) + || Constant.MEMORY_MEM_REPORT_ONLY.equals(entity.getMemModelId())) { + entity.setSummaryMemory(""); + } + + // 根据记忆模型类型设置默认的chatHistoryConf值 + if (template.getMemModelId() != null) { if (template.getMemModelId().equals("Memory_nomem")) { // 无记忆功能的模型,默认不记录聊天记录 entity.setChatHistoryConf(0); @@ -592,13 +598,18 @@ public class AgentServiceImpl extends BaseServiceImpl imp mapping.setParamInfo(JsonUtils.toJsonString(paramInfo)); mapping.setAgentId(entity.getId()); toInsert.add(mapping); - } - // 保存默认插件 - agentPluginMappingService.saveBatch(toInsert); - return entity.getId(); - } - - private String getDefaultLLMModelId() { + } + // 保存默认插件 + agentPluginMappingService.saveBatch(toInsert); + agentSnapshotService.createSnapshot(entity.getId(), "initial"); + return entity.getId(); + } + + private String defaultIfBlank(String value, String defaultValue) { + return StringUtils.isBlank(value) ? defaultValue : value; + } + + private String getDefaultLLMModelId() { try { List llmConfigs = modelConfigService.getEnabledModelsByType("LLM"); if (llmConfigs == null || llmConfigs.isEmpty()) { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImpl.java index 3919b917..3ad50e63 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImpl.java @@ -358,10 +358,38 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl normalizeSortedJsonList((List) value); case CORRECT_WORD_FILE_IDS -> normalizeStringList((List) value); case TAG_NAMES -> normalizeStringList((List) value); + case SUMMARY_MEMORY -> normalizeBlankText(value); + case TTS_VOLUME, TTS_RATE, TTS_PITCH -> normalizeDefaultTtsNumber(value); default -> value; }; } + private String normalizeBlankText(Object value) { + if (value == null) { + return ""; + } + String text = String.valueOf(value); + return StringUtils.isBlank(text) ? "" : text; + } + + private Object normalizeDefaultTtsNumber(Object value) { + if (value == null) { + return 0; + } + if (value instanceof Number number) { + return number.intValue(); + } + String text = String.valueOf(value); + if (StringUtils.isBlank(text)) { + return 0; + } + try { + return Integer.valueOf(text.trim()); + } catch (NumberFormatException ignored) { + return text; + } + } + private Map normalizeFunctions(List functions) { Map result = new TreeMap<>(); if (functions == null) { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java index d8025b3c..3ba32f50 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java @@ -57,6 +57,14 @@ public interface TimbreService extends BaseService { List getVoiceNames(String ttsModelId, String voiceName); + /** + * 获取普通音色或克隆音色配置的首个有效语言。 + * + * @param id 音色ID + * @return 默认语言;音色不存在或未配置有效语言时返回null + */ + String getDefaultLanguageById(String id); + /** * 根据ID获取音色名称 * @@ -73,4 +81,4 @@ public interface TimbreService extends BaseService { * @return 音色信息 */ VoiceDTO getByVoiceCode(String ttsModelId, String voiceCode); -} \ No newline at end of file +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java index 165824f6..f86035f8 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java @@ -1,6 +1,7 @@ package xiaozhi.modules.timbre.service.impl; import java.util.*; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -41,6 +42,8 @@ import xiaozhi.modules.voiceclone.entity.VoiceCloneEntity; @Service public class TimbreServiceImpl extends BaseServiceImpl implements TimbreService { + private static final Pattern LANGUAGE_SEPARATOR = Pattern.compile("[、;;,,]"); + private final TimbreDao timbreDao; private final VoiceCloneDao voiceCloneDao; private final RedisUtils redisUtils; @@ -158,6 +161,32 @@ public class TimbreServiceImpl extends BaseServiceImpl return CollectionUtil.isEmpty(voiceDTOs) ? null : voiceDTOs; } + @Override + public String getDefaultLanguageById(String id) { + if (StringUtils.isBlank(id)) { + return null; + } + + TimbreEntity timbre = timbreDao.selectById(id); + if (timbre != null) { + return firstNonBlankLanguage(timbre.getLanguages()); + } + + VoiceCloneEntity voiceClone = voiceCloneDao.selectById(id); + return voiceClone == null ? null : firstNonBlankLanguage(voiceClone.getLanguages()); + } + + private String firstNonBlankLanguage(String languages) { + if (StringUtils.isBlank(languages)) { + return null; + } + return LANGUAGE_SEPARATOR.splitAsStream(languages) + .map(StringUtils::trimToNull) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + /** * 处理是不是tts模型的id */ @@ -214,4 +243,4 @@ public class TimbreServiceImpl extends BaseServiceImpl dto.setIsClone(false); // 设置为普通音色 return dto; } -} \ No newline at end of file +} diff --git a/main/manager-api/src/test/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImplTest.java b/main/manager-api/src/test/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImplTest.java index e3aad1b1..9efe336c 100644 --- a/main/manager-api/src/test/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImplTest.java +++ b/main/manager-api/src/test/java/xiaozhi/modules/agent/service/impl/AgentSnapshotServiceImplTest.java @@ -12,6 +12,8 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -35,19 +37,25 @@ 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.AgentCreateDTO; 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.AgentTemplateEntity; import xiaozhi.modules.agent.entity.AgentSnapshotEntity; import xiaozhi.modules.agent.entity.AgentTagEntity; +import xiaozhi.modules.agent.service.AgentPluginMappingService; import xiaozhi.modules.agent.service.AgentContextProviderService; import xiaozhi.modules.agent.service.AgentSnapshotService; +import xiaozhi.modules.agent.service.AgentTemplateService; import xiaozhi.modules.agent.vo.AgentSnapshotVO; import xiaozhi.modules.agent.vo.AgentInfoVO; import xiaozhi.modules.correctword.service.CorrectWordFileService; +import xiaozhi.modules.model.service.ModelProviderService; +import xiaozhi.modules.timbre.service.TimbreService; class AgentSnapshotServiceImplTest { @@ -171,6 +179,70 @@ class AgentSnapshotServiceImplTest { assertEquals(List.of(AgentSnapshotField.TAG_NAMES.getFieldName()), changedFields); } + @Test + @SuppressWarnings("unchecked") + void blankSummaryMemoryValuesAreNotSnapshotChanges() throws Exception { + AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(null, null, null, null, null, null, null, null, + null, null); + Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("getChangedFields", + AgentSnapshotDataDTO.class, AgentSnapshotDataDTO.class); + method.setAccessible(true); + + AgentSnapshotDataDTO current = new AgentSnapshotDataDTO(); + current.setSummaryMemory(null); + AgentSnapshotDataDTO next = new AgentSnapshotDataDTO(); + next.setSummaryMemory(""); + + List changedFields = (List) method.invoke(service, current, next); + + assertFalse(changedFields.contains(AgentSnapshotField.SUMMARY_MEMORY.getFieldName())); + assertTrue(changedFields.isEmpty()); + } + + @Test + @SuppressWarnings("unchecked") + void emptyTtsAdvancedDefaultsAreNotSnapshotChanges() throws Exception { + AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(null, null, null, null, null, null, null, null, + null, null); + Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("getChangedFields", + AgentSnapshotDataDTO.class, AgentSnapshotDataDTO.class); + method.setAccessible(true); + + AgentSnapshotDataDTO current = new AgentSnapshotDataDTO(); + current.setTtsVolume(null); + current.setTtsRate(null); + current.setTtsPitch(null); + AgentSnapshotDataDTO next = new AgentSnapshotDataDTO(); + next.setTtsVolume(0); + next.setTtsRate(0); + next.setTtsPitch(0); + + List changedFields = (List) method.invoke(service, current, next); + + assertTrue(changedFields.isEmpty()); + } + + @Test + @SuppressWarnings("unchecked") + void explicitTtsLanguageIsStoredAsSnapshotChangeEvenWhenItMatchesVoiceDefault() throws Exception { + AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(null, null, null, null, null, null, null, null, + null, null); + Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("getChangedFields", + AgentSnapshotDataDTO.class, AgentSnapshotDataDTO.class); + method.setAccessible(true); + + AgentSnapshotDataDTO current = new AgentSnapshotDataDTO(); + current.setTtsVoiceId("voice-id"); + current.setTtsLanguage(null); + AgentSnapshotDataDTO next = new AgentSnapshotDataDTO(); + next.setTtsVoiceId("voice-id"); + next.setTtsLanguage("普通话"); + + List changedFields = (List) method.invoke(service, current, next); + + assertEquals(List.of(AgentSnapshotField.TTS_LANGUAGE.getFieldName()), changedFields); + } + @Test void tagOnlySavePathCreatesSnapshot() throws Exception { String controller = Files.readString(Path.of("src/main/java/xiaozhi/modules/agent/controller/AgentController.java")); @@ -216,6 +288,42 @@ class AgentSnapshotServiceImplTest { inOrder.verify(snapshotService).createSnapshot(agentId, "config"); } + @Test + void createAgentPersistsDisplayDefaultsBeforeInitialSnapshot() { + AgentDao agentDao = mock(AgentDao.class); + TimbreService timbreService = mock(TimbreService.class); + AgentPluginMappingService pluginMappingService = mock(AgentPluginMappingService.class); + AgentTemplateService templateService = mock(AgentTemplateService.class); + ModelProviderService providerService = mock(ModelProviderService.class); + AgentSnapshotService snapshotService = mock(AgentSnapshotService.class); + AgentServiceImpl service = new AgentServiceImpl(agentDao, null, timbreService, null, null, null, + pluginMappingService, null, templateService, providerService, null, null, null, snapshotService); + ReflectionTestUtils.setField(service, "baseDao", agentDao); + + AgentTemplateEntity template = new AgentTemplateEntity(); + template.setTtsModelId("TTS_EdgeTTS"); + template.setTtsVoiceId("TTS_EdgeTTS0001"); + template.setMemModelId("Memory_nomem"); + template.setSummaryMemory(null); + when(templateService.getDefaultTemplate()).thenReturn(template); + + when(timbreService.getDefaultLanguageById("TTS_EdgeTTS0001")).thenReturn("普通话"); + when(agentDao.insert(any())).thenReturn(1); + + AgentCreateDTO dto = new AgentCreateDTO(); + dto.setAgentName("test123"); + + String agentId = service.createAgent(dto); + + InOrder inOrder = inOrder(agentDao, pluginMappingService, snapshotService); + inOrder.verify(agentDao).insert(argThat(agent -> "test123".equals(agent.getAgentName()) + && "普通话".equals(agent.getTtsLanguage()) + && "".equals(agent.getSummaryMemory()) + && Integer.valueOf(0).equals(agent.getChatHistoryConf()))); + inOrder.verify(pluginMappingService).saveBatch(any()); + inOrder.verify(snapshotService).createSnapshot(agentId, "initial"); + } + @Test void agentSnapshotFieldAppliesRestorableAgentFields() { AgentSnapshotDataDTO data = new AgentSnapshotDataDTO(); @@ -283,11 +391,15 @@ class AgentSnapshotServiceImplTest { } @Test - void selectNextSnapshotLoadsRestoreTraceColumns() throws Exception { + void selectNextSnapshotLoadsRestoreTraceColumnsWithoutPreviousVersionQuery() throws Exception { String xml = normalizeWhitespace(Files.readString(Path.of("src/main/resources/mapper/agent/AgentSnapshotDao.xml"))); + String dao = Files.readString(Path.of("src/main/java/xiaozhi/modules/agent/dao/AgentSnapshotDao.java")); assertTrue(xml.contains("restore_from_snapshot_id AS restoreFromSnapshotId")); assertTrue(xml.contains("restore_from_version_no AS restoreFromVersionNo")); + assertTrue(xml.contains("version_no > #{versionNo}")); + assertFalse(xml.contains("selectPreviousSnapshot")); + assertFalse(dao.contains("selectPreviousSnapshot")); } @Test @@ -331,6 +443,52 @@ class AgentSnapshotServiceImplTest { assertEquals(1, vo.getRestoreFromVersionNo()); } + @Test + void toVOKeepsStoredEventFieldsStableAcrossDeletedVersionGapAndMetadataChanges() throws Exception { + AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class); + AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(snapshotDao, null, null, null, null, null, null, + null, null, null); + Method method = AgentSnapshotServiceImpl.class.getDeclaredMethod("toVO", + AgentSnapshotEntity.class, boolean.class); + method.setAccessible(true); + + AgentSnapshotEntity current = new AgentSnapshotEntity(); + current.setId("snapshot-id"); + current.setAgentId("agent-id"); + current.setVersionNo(3); + current.setChangedFields(JsonUtils.toJsonString(List.of("ttsLanguage", "ttsVolume"))); + + AgentSnapshotVO vo = (AgentSnapshotVO) method.invoke(service, current, false); + + assertEquals(List.of("ttsLanguage", "ttsVolume"), vo.getChangedFields()); + verifyNoInteractions(snapshotDao); + } + + @Test + void pageUsesPersistedChangedFieldsWithoutPerRecordQueries() { + AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class); + AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(snapshotDao, null, null, null, null, null, null, + null, null, null); + + AgentSnapshotEntity versionFive = new AgentSnapshotEntity(); + versionFive.setVersionNo(5); + versionFive.setChangedFields(JsonUtils.toJsonString(List.of("agentName"))); + AgentSnapshotEntity versionThree = new AgentSnapshotEntity(); + versionThree.setVersionNo(3); + versionThree.setChangedFields(JsonUtils.toJsonString(List.of("ttsLanguage"))); + Page result = new Page<>(1, 10); + result.setRecords(List.of(versionFive, versionThree)); + result.setTotal(2); + when(snapshotDao.selectPage(any(), any())).thenReturn(result); + + List records = service.page("agent-id", new AgentSnapshotPageDTO()).getList(); + + assertEquals(List.of("agentName"), records.get(0).getChangedFields()); + assertEquals(List.of("ttsLanguage"), records.get(1).getChangedFields()); + verify(snapshotDao).selectPage(any(), any()); + verifyNoMoreInteractions(snapshotDao); + } + @Test void pageDoesNotCreateInitialSnapshotWhenAgentHasNoHistory() { AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class); diff --git a/main/manager-api/src/test/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImplTest.java b/main/manager-api/src/test/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImplTest.java new file mode 100644 index 00000000..3e5eaa81 --- /dev/null +++ b/main/manager-api/src/test/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImplTest.java @@ -0,0 +1,57 @@ +package xiaozhi.modules.timbre.service.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; + +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.modules.timbre.dao.TimbreDao; +import xiaozhi.modules.timbre.entity.TimbreEntity; +import xiaozhi.modules.voiceclone.dao.VoiceCloneDao; +import xiaozhi.modules.voiceclone.entity.VoiceCloneEntity; + +class TimbreServiceImplTest { + + @Test + void defaultLanguageUsesFirstValidRegularTimbreLanguageWithoutCloneQuery() { + TimbreDao timbreDao = mock(TimbreDao.class); + VoiceCloneDao voiceCloneDao = mock(VoiceCloneDao.class); + TimbreServiceImpl service = new TimbreServiceImpl(timbreDao, voiceCloneDao, mock(RedisUtils.class)); + TimbreEntity timbre = new TimbreEntity(); + timbre.setLanguages(",, ; 普通话;粤语"); + when(timbreDao.selectById("voice-id")).thenReturn(timbre); + + assertEquals("普通话", service.getDefaultLanguageById("voice-id")); + + verify(voiceCloneDao, never()).selectById("voice-id"); + } + + @Test + void defaultLanguageFallsBackToCloneTimbre() { + TimbreDao timbreDao = mock(TimbreDao.class); + VoiceCloneDao voiceCloneDao = mock(VoiceCloneDao.class); + TimbreServiceImpl service = new TimbreServiceImpl(timbreDao, voiceCloneDao, mock(RedisUtils.class)); + VoiceCloneEntity voiceClone = new VoiceCloneEntity(); + voiceClone.setLanguages("、, English,中文"); + when(voiceCloneDao.selectById("clone-id")).thenReturn(voiceClone); + + assertEquals("English", service.getDefaultLanguageById("clone-id")); + } + + @Test + void delimiterOnlyLanguageConfigurationReturnsNull() { + TimbreDao timbreDao = mock(TimbreDao.class); + VoiceCloneDao voiceCloneDao = mock(VoiceCloneDao.class); + TimbreServiceImpl service = new TimbreServiceImpl(timbreDao, voiceCloneDao, mock(RedisUtils.class)); + TimbreEntity timbre = new TimbreEntity(); + timbre.setLanguages(",,、;;;,,"); + when(timbreDao.selectById("voice-id")).thenReturn(timbre); + + assertNull(service.getDefaultLanguageById("voice-id")); + } +} diff --git a/main/manager-mobile/src/api/agent/agent.ts b/main/manager-mobile/src/api/agent/agent.ts index 32b72ad7..0732900c 100644 --- a/main/manager-mobile/src/api/agent/agent.ts +++ b/main/manager-mobile/src/api/agent/agent.ts @@ -2,7 +2,11 @@ import type { Agent, AgentCreateData, AgentDetail, + AgentSnapshot, + AgentSnapshotPageParams, + CorrectWordFile, ModelOption, + PageData, RoleTemplate, } from './types' import { http } from '@/http/request/alova' @@ -100,7 +104,7 @@ export function getTTSVoices(ttsModelId: string, voiceName: string = '') { } // 更新智能体 -export function updateAgent(id: string, data: Partial) { +export function updateAgent(id: string, data: Partial & { tagNames?: string[] }) { return http.Put(`/agent/${id}`, data, { meta: { ignoreAuth: false, @@ -220,3 +224,63 @@ export function getAllLanguage(modelId: string) { }, }) } + +// 获取智能体历史版本列表 +export function getAgentSnapshots(agentId: string, params: AgentSnapshotPageParams) { + return http.Get>(`/agent/${agentId}/snapshots`, { + params, + meta: { + ignoreAuth: false, + toast: false, + }, + cacheFor: { + expire: 0, + }, + }) +} + +// 获取智能体历史版本详情 +export function getAgentSnapshot(agentId: string, snapshotId: string) { + return http.Get(`/agent/${agentId}/snapshots/${snapshotId}`, { + meta: { + ignoreAuth: false, + toast: false, + }, + cacheFor: { + expire: 0, + }, + }) +} + +// 恢复智能体历史版本 +export function restoreAgentSnapshot(agentId: string, snapshotId: string) { + return http.Post(`/agent/${agentId}/snapshots/${snapshotId}/restore`, {}, { + meta: { + ignoreAuth: false, + toast: true, + }, + }) +} + +// 删除智能体历史版本 +export function deleteAgentSnapshot(agentId: string, snapshotId: string) { + return http.Delete(`/agent/${agentId}/snapshots/${snapshotId}`, { + meta: { + ignoreAuth: false, + toast: true, + }, + }) +} + +// 获取所有替换词文件 +export function getCorrectWordFiles() { + return http.Get('/correct-word/file/select', { + meta: { + ignoreAuth: false, + toast: false, + }, + cacheFor: { + expire: 0, + }, + }) +} diff --git a/main/manager-mobile/src/api/agent/types.ts b/main/manager-mobile/src/api/agent/types.ts index f04ef3d1..9d29ad4b 100644 --- a/main/manager-mobile/src/api/agent/types.ts +++ b/main/manager-mobile/src/api/agent/types.ts @@ -44,10 +44,12 @@ export interface AgentDetail { createdAt: string updater: string updatedAt: string - ttsLanguage: string - ttsVolume: number - ttsRate: number - ttsPitch: number + ttsLanguage: string | null + ttsVolume: number | null + ttsRate: number | null + ttsPitch: number | null + currentVersionNo?: number | null + tagNames?: string[] functions: AgentFunction[] contextProviders: Providers[] } @@ -67,6 +69,49 @@ export interface AgentFunction { paramInfo: Record | null } +export interface PageData { + list: T[] + total: number +} + +export interface AgentSnapshotData extends Partial { + correctWordFileIds?: string[] + tagNames?: string[] + tags?: Array<{ + tagName?: string + [key: string]: any + }> + [key: string]: any +} + +export interface AgentSnapshot { + id: string + agentId: string + userId?: number + versionNo: number + changedFields?: string[] + fieldOrder?: string[] + source?: string + restoreFromSnapshotId?: string | null + restoreFromVersionNo?: number | null + creator?: number + createdAt?: string + snapshotData?: AgentSnapshotData + afterSnapshotData?: AgentSnapshotData +} + +export interface AgentSnapshotPageParams { + page?: number + limit?: number + maxVersionNo?: number +} + +export interface CorrectWordFile { + id: string + fileName: string + wordCount?: number +} + // 角色模板数据类型 export interface RoleTemplate { id: string @@ -78,6 +123,7 @@ export interface RoleTemplate { vllmModelId: string ttsModelId: string ttsVoiceId: string + ttsLanguage?: string | null memModelId: string intentModelId: string chatHistoryConf: number diff --git a/main/manager-mobile/src/i18n/de.ts b/main/manager-mobile/src/i18n/de.ts index dca4bb4f..4cccde4b 100644 --- a/main/manager-mobile/src/i18n/de.ts +++ b/main/manager-mobile/src/i18n/de.ts @@ -146,6 +146,81 @@ export default { 'agent.speedHint': '-100=langsamst, 0=Standard, 100=schnellst', 'agent.pitchHint': '-100=tiefst, 0=Standard, 100=höchst', + // Agent-Versionsverlauf + 'agentSnapshot.title': 'Versionsverlauf', + 'agentSnapshot.empty': 'Noch keine Versionen', + 'agentSnapshot.emptyTip': 'Gespeicherte Konfigurationen erscheinen hier', + 'agentSnapshot.version': 'Version', + 'agentSnapshot.createdAt': 'Gespeichert am', + 'agentSnapshot.source': 'Quelle', + 'agentSnapshot.changedFields': 'Änderungen', + 'agentSnapshot.view': 'Ansehen', + 'agentSnapshot.restore': 'Wiederherstellen', + 'agentSnapshot.delete': 'Löschen', + 'agentSnapshot.loadMore': 'Mehr laden', + 'agentSnapshot.detailTitle': 'Änderungsdetails', + 'agentSnapshot.restorePreviewTitle': 'Wiederherstellungsvorschau', + 'agentSnapshot.confirmRestore': 'Wiederherstellung bestätigen', + 'agentSnapshot.currentVersion': 'Aktuelle Version', + 'agentSnapshot.beforeChange': 'Vorher', + 'agentSnapshot.afterChange': 'Nachher', + 'agentSnapshot.beforeRestore': 'Vor der Wiederherstellung', + 'agentSnapshot.afterRestore': 'Nach der Wiederherstellung', + 'agentSnapshot.configValue': 'Konfigurationswert', + 'agentSnapshot.emptyValue': 'Keine', + 'agentSnapshot.secretRedacted': 'Geheimnis verborgen', + 'agentSnapshot.noChangedContent': 'Keine anzeigbaren Änderungen', + 'agentSnapshot.restoreConfirm': 'Version #{version} wiederherstellen? Die aktuelle Konfiguration wird zuerst als neue Version gespeichert.', + 'agentSnapshot.restoreMemoryWarning': 'Beim Wiederherstellen einer Version ohne Speicher wird der Chatverlauf dieses Agenten gelöscht. Bitte Risiko bestätigen.', + 'agentSnapshot.restoreSuccess': 'Version wurde wiederhergestellt', + 'agentSnapshot.restoreFailed': 'Version konnte nicht wiederhergestellt werden', + 'agentSnapshot.deleteConfirm': 'Version #{version} löschen? Dies kann nicht rückgängig gemacht werden.', + 'agentSnapshot.deleteSuccess': 'Version gelöscht', + 'agentSnapshot.deleteFailed': 'Version konnte nicht gelöscht werden', + 'agentSnapshot.fetchFailed': 'Versionsverlauf konnte nicht abgerufen werden', + 'agentSnapshot.detailFailed': 'Versionsdetails konnten nicht abgerufen werden', + 'agentSnapshot.correctWordCount': '{count} Ersatzwörter', + 'agentSnapshot.source.config': 'Konfiguration gespeichert', + 'agentSnapshot.source.current': 'Aktuelle Konfiguration', + 'agentSnapshot.source.restore': 'Vor der Wiederherstellung', + 'agentSnapshot.source.initial': 'Initialversion', + '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': 'Speichermodus', + 'agentSnapshot.field.intentModelId': 'Absichtserkennung', + 'agentSnapshot.field.chatHistoryConf': 'Chatverlauf-Konfiguration', + 'agentSnapshot.field.systemPrompt': 'Rollenbeschreibung', + 'agentSnapshot.field.summaryMemory': 'Speicher', + 'agentSnapshot.field.langCode': 'Sprachcode', + 'agentSnapshot.field.language': 'Interaktionssprache', + 'agentSnapshot.field.sort': 'Sortierung', + 'agentSnapshot.field.functions': 'Plugins', + 'agentSnapshot.field.contextProviders': 'Kontextquellen', + 'agentSnapshot.field.correctWordFileIds': 'Ersatzwörter', + 'agentSnapshot.field.tagNames': 'Agent-Labels', + 'agentSnapshot.chatHistoryConf.none': 'Chatverlauf nicht aufzeichnen', + 'agentSnapshot.chatHistoryConf.text': 'Text melden', + 'agentSnapshot.chatHistoryConf.textVoice': 'Text und Sprache melden', + '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', + // Context provider dialog related 'contextProviderDialog.title': 'Quelle bearbeiten', 'contextProviderDialog.noContextApi': 'Keine Kontext-API', diff --git a/main/manager-mobile/src/i18n/en.ts b/main/manager-mobile/src/i18n/en.ts index 719e5875..23eefbb7 100644 --- a/main/manager-mobile/src/i18n/en.ts +++ b/main/manager-mobile/src/i18n/en.ts @@ -146,6 +146,81 @@ export default { 'agent.speedHint': '-100=Slower, 0=Standard, 100=Faster', 'agent.pitchHint': '-100=Lowest, 0=Standard, 100=Highest', + // Agent snapshots + 'agentSnapshot.title': 'Version History', + 'agentSnapshot.empty': 'No versions yet', + 'agentSnapshot.emptyTip': 'Saved configs will appear here', + 'agentSnapshot.version': 'Version', + 'agentSnapshot.createdAt': 'Saved At', + 'agentSnapshot.source': 'Source', + 'agentSnapshot.changedFields': 'Changes', + 'agentSnapshot.view': 'View', + 'agentSnapshot.restore': 'Restore', + 'agentSnapshot.delete': 'Delete', + 'agentSnapshot.loadMore': 'Load More', + 'agentSnapshot.detailTitle': 'Change Details', + 'agentSnapshot.restorePreviewTitle': 'Restore Preview', + 'agentSnapshot.confirmRestore': 'Confirm Restore', + 'agentSnapshot.currentVersion': 'Current Version', + 'agentSnapshot.beforeChange': 'Before', + 'agentSnapshot.afterChange': 'After', + 'agentSnapshot.beforeRestore': 'Before Restore', + 'agentSnapshot.afterRestore': 'After Restore', + 'agentSnapshot.configValue': 'Config Value', + 'agentSnapshot.emptyValue': 'None', + 'agentSnapshot.secretRedacted': 'Secret hidden', + 'agentSnapshot.noChangedContent': 'No displayable changes', + 'agentSnapshot.restoreConfirm': 'Restore to version #{version}? The current config will be saved as a new version first.', + 'agentSnapshot.restoreMemoryWarning': 'Restoring to a no-memory version will clear this agent\'s chat history. Please confirm the risk.', + 'agentSnapshot.restoreSuccess': 'Version restored', + 'agentSnapshot.restoreFailed': 'Failed to restore version', + 'agentSnapshot.deleteConfirm': 'Delete version #{version}? This cannot be undone.', + 'agentSnapshot.deleteSuccess': 'Version deleted', + 'agentSnapshot.deleteFailed': 'Failed to delete version', + 'agentSnapshot.fetchFailed': 'Failed to fetch versions', + 'agentSnapshot.detailFailed': 'Failed to fetch version details', + 'agentSnapshot.correctWordCount': '{count} replacement words', + 'agentSnapshot.source.config': 'Config Save', + 'agentSnapshot.source.current': 'Current Config', + 'agentSnapshot.source.restore': 'Before Restore', + 'agentSnapshot.source.initial': 'Initial Version', + 'agentSnapshot.field.initial': 'Initial Snapshot', + 'agentSnapshot.field.agentCode': 'Agent Code', + 'agentSnapshot.field.agentName': 'Nickname', + 'agentSnapshot.field.asrModelId': 'Speech Recognition', + 'agentSnapshot.field.vadModelId': 'Voice Activity Detection', + 'agentSnapshot.field.llmModelId': 'Main Language Model', + 'agentSnapshot.field.slmModelId': 'Small Language Model', + 'agentSnapshot.field.vllmModelId': 'Vision Model', + 'agentSnapshot.field.ttsModelId': 'Text-to-Speech', + 'agentSnapshot.field.ttsVoiceId': 'Voice', + 'agentSnapshot.field.ttsLanguage': 'Language', + 'agentSnapshot.field.ttsVolume': 'Volume', + 'agentSnapshot.field.ttsRate': 'Speed', + 'agentSnapshot.field.ttsPitch': 'Pitch', + 'agentSnapshot.field.memModelId': 'Memory Mode', + 'agentSnapshot.field.intentModelId': 'Intent Recognition', + 'agentSnapshot.field.chatHistoryConf': 'Chat History Config', + 'agentSnapshot.field.systemPrompt': 'Role Description', + 'agentSnapshot.field.summaryMemory': 'Memory', + 'agentSnapshot.field.langCode': 'Language Code', + 'agentSnapshot.field.language': 'Interaction Language', + 'agentSnapshot.field.sort': 'Sort', + 'agentSnapshot.field.functions': 'Plugins', + 'agentSnapshot.field.contextProviders': 'Context Sources', + 'agentSnapshot.field.correctWordFileIds': 'Replacement Words', + 'agentSnapshot.field.tagNames': 'Agent Tags', + 'agentSnapshot.chatHistoryConf.none': 'Do not record chat history', + 'agentSnapshot.chatHistoryConf.text': 'Report text', + 'agentSnapshot.chatHistoryConf.textVoice': 'Report text and voice', + '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', + // Context provider dialog related 'contextProviderDialog.title': 'Edit Source', 'contextProviderDialog.noContextApi': 'No Context API', diff --git a/main/manager-mobile/src/i18n/pt_BR.ts b/main/manager-mobile/src/i18n/pt_BR.ts index 98f7ab97..c00fd330 100644 --- a/main/manager-mobile/src/i18n/pt_BR.ts +++ b/main/manager-mobile/src/i18n/pt_BR.ts @@ -146,6 +146,81 @@ export default { 'agent.speedHint': '-100=Slower, 0=Standard, 100=Faster', 'agent.pitchHint': '-100=Lowest, 0=Standard, 100=Highest', + // Histórico de versões do agente + 'agentSnapshot.title': 'Histórico de Versões', + 'agentSnapshot.empty': 'Ainda sem versões', + 'agentSnapshot.emptyTip': 'Configurações salvas aparecerão aqui', + 'agentSnapshot.version': 'Versão', + 'agentSnapshot.createdAt': 'Salvo em', + 'agentSnapshot.source': 'Origem', + 'agentSnapshot.changedFields': 'Alterações', + 'agentSnapshot.view': 'Ver', + 'agentSnapshot.restore': 'Restaurar', + 'agentSnapshot.delete': 'Excluir', + 'agentSnapshot.loadMore': 'Carregar Mais', + 'agentSnapshot.detailTitle': 'Detalhes da Alteração', + 'agentSnapshot.restorePreviewTitle': 'Prévia da Restauração', + 'agentSnapshot.confirmRestore': 'Confirmar Restauração', + 'agentSnapshot.currentVersion': 'Versão Atual', + 'agentSnapshot.beforeChange': 'Antes', + 'agentSnapshot.afterChange': 'Depois', + 'agentSnapshot.beforeRestore': 'Antes da Restauração', + 'agentSnapshot.afterRestore': 'Depois da Restauração', + 'agentSnapshot.configValue': 'Valor da Configuração', + 'agentSnapshot.emptyValue': 'Nenhum', + 'agentSnapshot.secretRedacted': 'Segredo oculto', + 'agentSnapshot.noChangedContent': 'Nenhuma alteração exibível', + 'agentSnapshot.restoreConfirm': 'Restaurar para a versão #{version}? A configuração atual será salva como uma nova versão primeiro.', + 'agentSnapshot.restoreMemoryWarning': 'Restaurar para uma versão sem memória limpará o histórico de chat deste agente. Confirme o risco.', + 'agentSnapshot.restoreSuccess': 'Versão restaurada', + 'agentSnapshot.restoreFailed': 'Falha ao restaurar versão', + 'agentSnapshot.deleteConfirm': 'Excluir versão #{version}? Esta ação não pode ser desfeita.', + 'agentSnapshot.deleteSuccess': 'Versão excluída', + 'agentSnapshot.deleteFailed': 'Falha ao excluir versão', + 'agentSnapshot.fetchFailed': 'Falha ao buscar versões', + 'agentSnapshot.detailFailed': 'Falha ao buscar detalhes da versão', + 'agentSnapshot.correctWordCount': '{count} palavras de substituição', + '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.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 Voz', + 'agentSnapshot.field.llmModelId': 'Modelo Principal', + 'agentSnapshot.field.slmModelId': 'Modelo Pequeno', + 'agentSnapshot.field.vllmModelId': 'Modelo Visual', + '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': 'Tonalidade', + 'agentSnapshot.field.memModelId': 'Modo de Memória', + 'agentSnapshot.field.intentModelId': 'Reconhecimento de Intenção', + 'agentSnapshot.field.chatHistoryConf': 'Configuração do Histórico', + 'agentSnapshot.field.systemPrompt': 'Descrição do Papel', + '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': 'Plugins', + 'agentSnapshot.field.contextProviders': 'Fontes de Contexto', + 'agentSnapshot.field.correctWordFileIds': 'Palavras de Substituição', + 'agentSnapshot.field.tagNames': 'Tags do Agente', + 'agentSnapshot.chatHistoryConf.none': 'Não registrar histórico de chat', + 'agentSnapshot.chatHistoryConf.text': 'Reportar texto', + 'agentSnapshot.chatHistoryConf.textVoice': 'Reportar texto e voz', + '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': 'Apenas reportar', + 'agentSnapshot.model.Intent_nointent': 'Sem reconhecimento de intenção', + 'agentSnapshot.model.Intent_intent_llm': 'Reconhecimento por LLM externo', + 'agentSnapshot.model.Intent_function_call': 'Chamada de função por LLM', + // Diálogo de provedor de contexto 'contextProviderDialog.title': 'Editar Fonte', 'contextProviderDialog.noContextApi': 'Sem API de Contexto', diff --git a/main/manager-mobile/src/i18n/vi.ts b/main/manager-mobile/src/i18n/vi.ts index fe9ad21e..82e951c2 100644 --- a/main/manager-mobile/src/i18n/vi.ts +++ b/main/manager-mobile/src/i18n/vi.ts @@ -146,6 +146,81 @@ export default { 'agent.speedHint': '-100=Slowest, 0=Standard, 100=Fastest', 'agent.pitchHint': '-100=Lowest, 0=Standard, 100=Highest', + // Lịch sử phiên bản đại lý + 'agentSnapshot.title': 'Lịch sử phiên bản', + 'agentSnapshot.empty': 'Chưa có phiên bản', + 'agentSnapshot.emptyTip': 'Cấu hình đã lưu sẽ xuất hiện ở đây', + 'agentSnapshot.version': 'Phiên bản', + 'agentSnapshot.createdAt': 'Thời gian lưu', + 'agentSnapshot.source': 'Nguồn', + 'agentSnapshot.changedFields': 'Thay đổi', + 'agentSnapshot.view': 'Xem', + 'agentSnapshot.restore': 'Khôi phục', + 'agentSnapshot.delete': 'Xóa', + 'agentSnapshot.loadMore': 'Tải thêm', + '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.currentVersion': 'Phiên bản hiện tại', + 'agentSnapshot.beforeChange': 'Trước', + 'agentSnapshot.afterChange': 'Sau', + 'agentSnapshot.beforeRestore': 'Trước khôi phục', + 'agentSnapshot.afterRestore': 'Sau khôi phục', + 'agentSnapshot.configValue': 'Giá trị cấu hình', + 'agentSnapshot.emptyValue': 'Không có', + 'agentSnapshot.secretRedacted': 'Đã ẩn khóa bí mật', + 'agentSnapshot.noChangedContent': 'Không có thay đổi để hiển thị', + '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.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 đại lý này. Vui lòng xác nhận rủi ro.', + '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.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', + 'agentSnapshot.deleteFailed': 'Xóa phiên bản thất bại', + 'agentSnapshot.fetchFailed': 'Lấy lịch sử phiên bản thất bại', + 'agentSnapshot.detailFailed': 'Lấy chi tiết phiên bản thất bại', + 'agentSnapshot.correctWordCount': '{count} từ thay thế', + '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 khôi phục', + 'agentSnapshot.source.initial': 'Phiên bản khởi tạo', + 'agentSnapshot.field.initial': 'Ảnh chụp ban đầu', + 'agentSnapshot.field.agentCode': 'Mã đại lý', + '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': 'Tổng hợp giọng nói', + 'agentSnapshot.field.ttsVoiceId': 'Giọng', + 'agentSnapshot.field.ttsLanguage': 'Ngôn ngữ', + 'agentSnapshot.field.ttsVolume': 'Âm lượng', + 'agentSnapshot.field.ttsRate': 'Tốc độ', + 'agentSnapshot.field.ttsPitch': 'Tone', + 'agentSnapshot.field.memModelId': 'Chế độ bộ nhớ', + 'agentSnapshot.field.intentModelId': 'Nhận dạng ý định', + 'agentSnapshot.field.chatHistoryConf': 'Cấu hình lịch sử chat', + 'agentSnapshot.field.systemPrompt': 'Mô tả vai trò', + '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': 'Plugin', + 'agentSnapshot.field.contextProviders': 'Nguồn ngữ cảnh', + 'agentSnapshot.field.correctWordFileIds': 'Từ thay thế', + 'agentSnapshot.field.tagNames': 'Nhãn đại lý', + 'agentSnapshot.chatHistoryConf.none': 'Không ghi lịch sử chat', + 'agentSnapshot.chatHistoryConf.text': 'Gửi văn bản', + 'agentSnapshot.chatHistoryConf.textVoice': 'Gửi văn bản và giọng nói', + 'agentSnapshot.model.Memory_nomem': 'Không có bộ nhớ', + 'agentSnapshot.model.Memory_mem_local_short': 'Bộ nhớ ngắn hạn cục bộ', + 'agentSnapshot.model.Memory_mem0ai': 'Bộ nhớ Mem0AI', + 'agentSnapshot.model.Memory_mem_report_only': 'Chỉ gửi báo cáo', + 'agentSnapshot.model.Intent_nointent': 'Không nhận dạng ý định', + 'agentSnapshot.model.Intent_intent_llm': 'Nhận dạng ý định LLM ngoài', + 'agentSnapshot.model.Intent_function_call': 'Gọi hàm bằng LLM', + // Context provider dialog related 'contextProviderDialog.title': 'Chỉnh sửa nguồn', 'contextProviderDialog.noContextApi': 'Không có API ngữ cảnh', @@ -507,4 +582,4 @@ export default { 'voiceprint.audioNotExist': 'Âm thanh không tồn tại', 'voiceprint.getAudioFailed': 'Không thể lấy âm thanh', 'voiceprint.audioPlayFailed': 'Phát âm thanh thất bại', -} \ No newline at end of file +} diff --git a/main/manager-mobile/src/i18n/zh_CN.ts b/main/manager-mobile/src/i18n/zh_CN.ts index ecea39a6..49485b2d 100644 --- a/main/manager-mobile/src/i18n/zh_CN.ts +++ b/main/manager-mobile/src/i18n/zh_CN.ts @@ -146,6 +146,81 @@ export default { 'agent.speedHint': '-100=最慢, 0=标准, 100=最快', 'agent.pitchHint': '-100=最低, 0=标准, 100=最高', + // 智能体历史版本 + 'agentSnapshot.title': '历史版本', + 'agentSnapshot.empty': '暂无历史版本', + 'agentSnapshot.emptyTip': '保存配置后会生成历史版本', + 'agentSnapshot.version': '版本', + 'agentSnapshot.createdAt': '保存时间', + 'agentSnapshot.source': '来源', + 'agentSnapshot.changedFields': '变更内容', + 'agentSnapshot.view': '查看', + 'agentSnapshot.restore': '恢复', + 'agentSnapshot.delete': '删除', + 'agentSnapshot.loadMore': '加载更多', + 'agentSnapshot.detailTitle': '变更详情', + 'agentSnapshot.restorePreviewTitle': '恢复预览', + 'agentSnapshot.confirmRestore': '确认恢复', + 'agentSnapshot.currentVersion': '当前版本', + 'agentSnapshot.beforeChange': '变化前', + 'agentSnapshot.afterChange': '变化后', + 'agentSnapshot.beforeRestore': '恢复前', + 'agentSnapshot.afterRestore': '恢复后', + 'agentSnapshot.configValue': '配置值', + 'agentSnapshot.emptyValue': '无', + 'agentSnapshot.secretRedacted': '密钥已隐藏', + 'agentSnapshot.noChangedContent': '无可显示变更', + 'agentSnapshot.restoreConfirm': '确定恢复到版本 #{version}?当前配置会先保存为新的历史版本。', + 'agentSnapshot.restoreMemoryWarning': '恢复到无记忆版本会清空该智能体聊天记录,请确认风险。', + 'agentSnapshot.restoreSuccess': '版本已恢复', + 'agentSnapshot.restoreFailed': '版本恢复失败', + 'agentSnapshot.deleteConfirm': '确定删除版本 #{version}?此操作不可撤销。', + 'agentSnapshot.deleteSuccess': '历史版本已删除', + 'agentSnapshot.deleteFailed': '历史版本删除失败', + 'agentSnapshot.fetchFailed': '获取历史版本失败', + 'agentSnapshot.detailFailed': '获取版本详情失败', + 'agentSnapshot.correctWordCount': '共 {count} 个替换词', + 'agentSnapshot.source.config': '配置保存', + 'agentSnapshot.source.current': '当前配置', + 'agentSnapshot.source.restore': '恢复前备份', + 'agentSnapshot.source.initial': '初始版本', + '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.chatHistoryConf.none': '不记录聊天记录', + 'agentSnapshot.chatHistoryConf.text': '上报文字', + 'agentSnapshot.chatHistoryConf.textVoice': '上报文字+语音', + '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': '大模型函数调用', + // 上下文源对话框相关 'contextProviderDialog.title': '编辑源', 'contextProviderDialog.noContextApi': '暂无上下文API', diff --git a/main/manager-mobile/src/i18n/zh_TW.ts b/main/manager-mobile/src/i18n/zh_TW.ts index 0acde90d..2dd28410 100644 --- a/main/manager-mobile/src/i18n/zh_TW.ts +++ b/main/manager-mobile/src/i18n/zh_TW.ts @@ -167,6 +167,81 @@ export default { 'agent.speedHint': '-100=最慢, 0=標準, 100=最快', 'agent.pitchHint': '-100=最低, 0=標準, 100=最高', + // 智能體歷史版本 + 'agentSnapshot.title': '歷史版本', + 'agentSnapshot.empty': '暫無歷史版本', + 'agentSnapshot.emptyTip': '保存配置後會生成歷史版本', + 'agentSnapshot.version': '版本', + 'agentSnapshot.createdAt': '保存時間', + 'agentSnapshot.source': '來源', + 'agentSnapshot.changedFields': '變更內容', + 'agentSnapshot.view': '查看', + 'agentSnapshot.restore': '恢復', + 'agentSnapshot.delete': '刪除', + 'agentSnapshot.loadMore': '載入更多', + 'agentSnapshot.detailTitle': '變更詳情', + 'agentSnapshot.restorePreviewTitle': '恢復預覽', + 'agentSnapshot.confirmRestore': '確認恢復', + 'agentSnapshot.currentVersion': '當前版本', + 'agentSnapshot.beforeChange': '變化前', + 'agentSnapshot.afterChange': '變化後', + 'agentSnapshot.beforeRestore': '恢復前', + 'agentSnapshot.afterRestore': '恢復後', + 'agentSnapshot.configValue': '配置值', + 'agentSnapshot.emptyValue': '無', + 'agentSnapshot.secretRedacted': '密鑰已隱藏', + 'agentSnapshot.noChangedContent': '無可顯示變更', + 'agentSnapshot.restoreConfirm': '確定恢復到版本 #{version}?當前配置會先保存為新的歷史版本。', + 'agentSnapshot.restoreMemoryWarning': '恢復到無記憶版本會清空該智能體聊天記錄,請確認風險。', + 'agentSnapshot.restoreSuccess': '版本已恢復', + 'agentSnapshot.restoreFailed': '版本恢復失敗', + 'agentSnapshot.deleteConfirm': '確定刪除版本 #{version}?此操作不可復原。', + 'agentSnapshot.deleteSuccess': '歷史版本已刪除', + 'agentSnapshot.deleteFailed': '歷史版本刪除失敗', + 'agentSnapshot.fetchFailed': '獲取歷史版本失敗', + 'agentSnapshot.detailFailed': '獲取版本詳情失敗', + 'agentSnapshot.correctWordCount': '共 {count} 個替換詞', + 'agentSnapshot.source.config': '配置保存', + 'agentSnapshot.source.current': '當前配置', + 'agentSnapshot.source.restore': '恢復前備份', + 'agentSnapshot.source.initial': '初始版本', + '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.chatHistoryConf.none': '不記錄聊天記錄', + 'agentSnapshot.chatHistoryConf.text': '上報文字', + 'agentSnapshot.chatHistoryConf.textVoice': '上報文字+語音', + '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': '大模型函數調用', + // 上下文源对话框相关 'contextProviderDialog.title': '編輯源', 'contextProviderDialog.noContextApi': '暫無上下文API', diff --git a/main/manager-mobile/src/pages.json b/main/manager-mobile/src/pages.json index 554a0a20..9799c40f 100644 --- a/main/manager-mobile/src/pages.json +++ b/main/manager-mobile/src/pages.json @@ -1,7 +1,7 @@ { "globalStyle": { "navigationStyle": "default", - "navigationBarTitleText": "智控台", + "navigationBarTitleText": "小智", "navigationBarBackgroundColor": "#f8f8f8", "navigationBarTextStyle": "black", "backgroundColor": "#FFFFFF" @@ -74,6 +74,24 @@ "navigationStyle": "custom" } }, + { + "path": "pages/agent/provider", + "type": "page", + "layout": "default", + "style": { + "navigationBarTitleText": "编辑源", + "navigationStyle": "custom" + } + }, + { + "path": "pages/agent/speedPitch", + "type": "page", + "layout": "default", + "style": { + "navigationBarTitleText": "语音设置", + "navigationStyle": "custom" + } + }, { "path": "pages/agent/tools", "type": "page", @@ -126,6 +144,42 @@ "navigationBarTitleText": "Login" } }, + { + "path": "pages/login/privacy-policy-en", + "type": "page", + "layout": "default", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "Privacy Policy" + } + }, + { + "path": "pages/login/privacy-policy-zh", + "type": "page", + "layout": "default", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "隐私政策" + } + }, + { + "path": "pages/login/user-agreement-en", + "type": "page", + "layout": "default", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "User Agreement" + } + }, + { + "path": "pages/login/user-agreement-zh", + "type": "page", + "layout": "default", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "用户协议" + } + }, { "path": "pages/register/index", "type": "page", diff --git a/main/manager-mobile/src/pages/agent/components/AgentSnapshotPanel.vue b/main/manager-mobile/src/pages/agent/components/AgentSnapshotPanel.vue new file mode 100644 index 00000000..b48bbf97 --- /dev/null +++ b/main/manager-mobile/src/pages/agent/components/AgentSnapshotPanel.vue @@ -0,0 +1,1230 @@ + + + + + diff --git a/main/manager-mobile/src/pages/agent/edit.vue b/main/manager-mobile/src/pages/agent/edit.vue index e33c0211..6d77256c 100644 --- a/main/manager-mobile/src/pages/agent/edit.vue +++ b/main/manager-mobile/src/pages/agent/edit.vue @@ -1,10 +1,11 @@