mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-21 22:53:56 +08:00
feat: improve agent snapshot history
This commit is contained in:
@@ -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
|
||||
|
||||
+11
@@ -531,10 +531,16 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -595,9 +601,14 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
}
|
||||
// 保存默认插件
|
||||
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<ModelConfigEntity> llmConfigs = modelConfigService.getEnabledModelsByType("LLM");
|
||||
|
||||
+28
@@ -358,10 +358,38 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
||||
case CONTEXT_PROVIDERS -> normalizeSortedJsonList((List<?>) value);
|
||||
case CORRECT_WORD_FILE_IDS -> normalizeStringList((List<String>) value);
|
||||
case TAG_NAMES -> normalizeStringList((List<String>) 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<String, Object> normalizeFunctions(List<AgentUpdateDTO.FunctionInfo> functions) {
|
||||
Map<String, Object> result = new TreeMap<>();
|
||||
if (functions == null) {
|
||||
|
||||
@@ -57,6 +57,14 @@ public interface TimbreService extends BaseService<TimbreEntity> {
|
||||
|
||||
List<VoiceDTO> getVoiceNames(String ttsModelId, String voiceName);
|
||||
|
||||
/**
|
||||
* 获取普通音色或克隆音色配置的首个有效语言。
|
||||
*
|
||||
* @param id 音色ID
|
||||
* @return 默认语言;音色不存在或未配置有效语言时返回null
|
||||
*/
|
||||
String getDefaultLanguageById(String id);
|
||||
|
||||
/**
|
||||
* 根据ID获取音色名称
|
||||
*
|
||||
|
||||
+29
@@ -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<TimbreDao, TimbreEntity> 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<TimbreDao, TimbreEntity>
|
||||
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
|
||||
*/
|
||||
|
||||
+159
-1
@@ -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<String> changedFields = (List<String>) 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<String> changedFields = (List<String>) 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<String> changedFields = (List<String>) 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<AgentSnapshotEntity> result = new Page<>(1, 10);
|
||||
result.setRecords(List.of(versionFive, versionThree));
|
||||
result.setTotal(2);
|
||||
when(snapshotDao.selectPage(any(), any())).thenReturn(result);
|
||||
|
||||
List<AgentSnapshotVO> 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);
|
||||
|
||||
+57
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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<AgentDetail>) {
|
||||
export function updateAgent(id: string, data: Partial<AgentDetail> & { 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<PageData<AgentSnapshot>>(`/agent/${agentId}/snapshots`, {
|
||||
params,
|
||||
meta: {
|
||||
ignoreAuth: false,
|
||||
toast: false,
|
||||
},
|
||||
cacheFor: {
|
||||
expire: 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 获取智能体历史版本详情
|
||||
export function getAgentSnapshot(agentId: string, snapshotId: string) {
|
||||
return http.Get<AgentSnapshot>(`/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<CorrectWordFile[]>('/correct-word/file/select', {
|
||||
meta: {
|
||||
ignoreAuth: false,
|
||||
toast: false,
|
||||
},
|
||||
cacheFor: {
|
||||
expire: 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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<string, string | number | boolean> | null
|
||||
}
|
||||
|
||||
export interface PageData<T> {
|
||||
list: T[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface AgentSnapshotData extends Partial<AgentDetail> {
|
||||
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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AgentDetail, ModelOption, PluginDefinition, RoleTemplate } from '@/api/agent/types'
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { getAgentDetail, getAgentTags, getAllLanguage, getModelOptions, getPluginFunctions, getRoleTemplates, updateAgent, updateAgentTags } from '@/api/agent/agent'
|
||||
import { getAgentDetail, getAgentTags, getAllLanguage, getModelOptions, getPluginFunctions, getRoleTemplates, updateAgent } from '@/api/agent/agent'
|
||||
import { t } from '@/i18n'
|
||||
import { usePluginStore, useProvider, useSpeedPitch } from '@/store'
|
||||
import { toast } from '@/utils/toast'
|
||||
import AgentSnapshotPanel from './components/AgentSnapshotPanel.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'AgentEdit',
|
||||
@@ -64,6 +65,8 @@ const selectedTemplateId = ref('')
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const showSnapshotPanel = ref(false)
|
||||
const currentVersionNo = ref<number | null>(null)
|
||||
|
||||
// 模型选项数据
|
||||
const modelOptions = ref<{
|
||||
@@ -79,9 +82,9 @@ const modelOptions = ref<{
|
||||
})
|
||||
|
||||
// 音色选项数据
|
||||
const voiceOptions = ref([])
|
||||
const voiceOptions = ref<any[]>([])
|
||||
// 保存完整的音色信息
|
||||
const voiceDetails = ref({})
|
||||
const voiceDetails = ref<Record<string, any>>({})
|
||||
|
||||
// 上报模式选项数据
|
||||
const reportOptions = [
|
||||
@@ -110,9 +113,15 @@ const allFunctions = ref<PluginDefinition[]>([])
|
||||
const dynamicTags = ref([])
|
||||
const inputValue = ref('')
|
||||
const inputVisible = ref(false)
|
||||
const languageOptions = ref([])
|
||||
const languageOptions = ref<any[]>([])
|
||||
const isVisibleReport = ref(false)
|
||||
const tempSummaryMemory = ref('')
|
||||
const selectedTtsLanguage = ref('')
|
||||
const ttsLanguageTouched = ref(false)
|
||||
const ttsVoiceTouched = ref(false)
|
||||
const ttsOptionsLoading = ref(false)
|
||||
const originalTagNames = ref<string[]>([])
|
||||
let ttsOptionsRequestSequence = 0
|
||||
|
||||
// 音频播放相关
|
||||
const audioRef = ref<UniApp.InnerAudioContext | null>(null)
|
||||
@@ -212,9 +221,14 @@ async function loadAgentDetail() {
|
||||
try {
|
||||
loading.value = true
|
||||
tempSummaryMemory.value = ''
|
||||
ttsLanguageTouched.value = false
|
||||
ttsVoiceTouched.value = false
|
||||
ttsOptionsRequestSequence += 1
|
||||
ttsOptionsLoading.value = false
|
||||
const detail = await getAgentDetail(agentId.value)
|
||||
const normalizedFunctions = normalizeAgentFunctions(detail.functions || [])
|
||||
formData.value = { ...detail, functions: normalizedFunctions }
|
||||
currentVersionNo.value = detail.currentVersionNo || null
|
||||
|
||||
// 更新插件store
|
||||
pluginStore.setCurrentAgentId(agentId.value)
|
||||
@@ -222,17 +236,27 @@ async function loadAgentDetail() {
|
||||
|
||||
// 更新语速音调
|
||||
speedPitchStore.updateSpeedPitch({
|
||||
ttsVolume: detail.ttsVolume || 0,
|
||||
ttsRate: detail.ttsRate || 0,
|
||||
ttsPitch: detail.ttsPitch || 0,
|
||||
ttsVolume: detail.ttsVolume ?? 0,
|
||||
ttsRate: detail.ttsRate ?? 0,
|
||||
ttsPitch: detail.ttsPitch ?? 0,
|
||||
})
|
||||
speedPitchStore.resetChangedFields()
|
||||
|
||||
// 加载上下文配置
|
||||
providerStore.updateProviders(detail.contextProviders || [])
|
||||
|
||||
// 如果有TTS模型,加载对应的音色选项
|
||||
if (detail.ttsModelId) {
|
||||
await fetchAllLanguag(detail.ttsModelId)
|
||||
await fetchAllLanguag(detail.ttsModelId, {
|
||||
preferredLanguage: detail.ttsLanguage,
|
||||
preferredVoiceId: detail.ttsVoiceId,
|
||||
})
|
||||
}
|
||||
else {
|
||||
voiceOptions.value = []
|
||||
voiceDetails.value = {}
|
||||
languageOptions.value = []
|
||||
selectedTtsLanguage.value = ''
|
||||
}
|
||||
|
||||
// 等待模型选项加载完成后再更新显示名称
|
||||
@@ -344,7 +368,13 @@ async function loadModelOptions() {
|
||||
}
|
||||
|
||||
// 根据语言筛选音色
|
||||
function filterVoicesByLanguage() {
|
||||
interface VoiceSelectionOptions {
|
||||
autoSelectVoice?: boolean
|
||||
preferredLanguage?: string | null
|
||||
preferredVoiceId?: string | null
|
||||
}
|
||||
|
||||
function filterVoicesByLanguage(options: VoiceSelectionOptions = {}) {
|
||||
if (!voiceDetails.value || Object.keys(voiceDetails.value).length === 0) {
|
||||
voiceOptions.value = []
|
||||
return
|
||||
@@ -358,7 +388,7 @@ function filterVoicesByLanguage() {
|
||||
return false
|
||||
}
|
||||
const languagesArray = voice.languages.split(/[、;;,,]/).map(lang => lang.trim()).filter(lang => lang)
|
||||
return languagesArray.includes(formData.value.language)
|
||||
return languagesArray.includes(selectedTtsLanguage.value)
|
||||
})
|
||||
|
||||
voiceOptions.value = filteredVoices.map(voice => ({
|
||||
@@ -374,26 +404,37 @@ function filterVoicesByLanguage() {
|
||||
const currentVoiceSupportsLanguage = formData.value.ttsVoiceId
|
||||
&& filteredVoices.some(voice => voice.id === formData.value.ttsVoiceId)
|
||||
|
||||
if (!currentVoiceSupportsLanguage) {
|
||||
if (!currentVoiceSupportsLanguage && options.autoSelectVoice) {
|
||||
formData.value.ttsVoiceId = filteredVoices.length > 0 ? filteredVoices[0].id : ''
|
||||
displayNames.value.voiceprint = filteredVoices.length > 0 ? filteredVoices[0].name : ''
|
||||
ttsVoiceTouched.value = true
|
||||
}
|
||||
else {
|
||||
displayNames.value.voiceprint = filteredVoices.find(item => item.id === formData.value.ttsVoiceId)?.name
|
||||
|| getVoiceDisplayName(formData.value.ttsVoiceId)
|
||||
}
|
||||
}
|
||||
|
||||
// 同步到ttsSettings(如果值为null,使用0作为显示默认值,但不修改form中的值)
|
||||
speedPitchStore.updateSpeedPitch({
|
||||
ttsVolume: formData.value.ttsVolume !== null && formData.value.ttsVolume !== undefined ? formData.value.ttsVolume : 0,
|
||||
ttsRate: formData.value.ttsRate !== null && formData.value.ttsRate !== undefined ? formData.value.ttsRate : 0,
|
||||
ttsPitch: formData.value.ttsPitch !== null && formData.value.ttsPitch !== undefined ? formData.value.ttsPitch : 0,
|
||||
})
|
||||
function getVoiceDefaultLanguage(ttsVoiceId: string) {
|
||||
if (!ttsVoiceId || !voiceDetails.value?.[ttsVoiceId]?.languages) {
|
||||
return ''
|
||||
}
|
||||
const languages = voiceDetails.value[ttsVoiceId].languages
|
||||
.split(/[、;;,,]/)
|
||||
.map(lang => lang.trim())
|
||||
.filter(Boolean)
|
||||
return languages[0] || ''
|
||||
}
|
||||
|
||||
// 根据语音合成模型加载语言
|
||||
async function fetchAllLanguag(ttsModelId: string) {
|
||||
async function fetchAllLanguag(ttsModelId: string, options: VoiceSelectionOptions = {}) {
|
||||
const requestId = ++ttsOptionsRequestSequence
|
||||
ttsOptionsLoading.value = true
|
||||
try {
|
||||
const res = await getAllLanguage(ttsModelId)
|
||||
if (requestId !== ttsOptionsRequestSequence) {
|
||||
return
|
||||
}
|
||||
// 保存完整的音色信息
|
||||
voiceDetails.value = res.reduce((acc, voice) => {
|
||||
acc[voice.id] = voice
|
||||
@@ -412,23 +453,48 @@ async function fetchAllLanguag(ttsModelId: string) {
|
||||
name: lang,
|
||||
}))
|
||||
|
||||
// 使用后端返回的用户选择的语言,如果没有则使用第一个语言选项
|
||||
if (formData.value.ttsLanguage && languageOptions.value.some(option => option.value === formData.value.ttsLanguage)) {
|
||||
formData.value.language = formData.value.ttsLanguage
|
||||
const requestedLanguage = options.preferredLanguage
|
||||
const preferredVoiceLanguage = options.preferredVoiceId
|
||||
? getVoiceDefaultLanguage(options.preferredVoiceId)
|
||||
: ''
|
||||
// 优先使用调用方指定的语言或音色默认语言,再回退到智能体当前配置
|
||||
if (requestedLanguage && languageOptions.value.some(option => option.value === requestedLanguage)) {
|
||||
selectedTtsLanguage.value = requestedLanguage
|
||||
displayNames.value.language = requestedLanguage
|
||||
}
|
||||
else if (preferredVoiceLanguage && languageOptions.value.some(option => option.value === preferredVoiceLanguage)) {
|
||||
selectedTtsLanguage.value = preferredVoiceLanguage
|
||||
displayNames.value.language = preferredVoiceLanguage
|
||||
}
|
||||
else if (formData.value.ttsLanguage && languageOptions.value.some(option => option.value === formData.value.ttsLanguage)) {
|
||||
selectedTtsLanguage.value = formData.value.ttsLanguage
|
||||
displayNames.value.language = formData.value.ttsLanguage
|
||||
}
|
||||
else if (getVoiceDefaultLanguage(formData.value.ttsVoiceId)) {
|
||||
selectedTtsLanguage.value = getVoiceDefaultLanguage(formData.value.ttsVoiceId)
|
||||
displayNames.value.language = selectedTtsLanguage.value
|
||||
}
|
||||
else if (languageOptions.value.length > 0) {
|
||||
formData.value.language = languageOptions.value[0].value
|
||||
selectedTtsLanguage.value = languageOptions.value[0].value
|
||||
displayNames.value.language = languageOptions.value[0].value
|
||||
}
|
||||
|
||||
// 根据选中的语言筛选音色
|
||||
filterVoicesByLanguage()
|
||||
filterVoicesByLanguage(options)
|
||||
}
|
||||
catch {
|
||||
if (requestId === ttsOptionsRequestSequence) {
|
||||
voiceOptions.value = []
|
||||
voiceDetails.value = {}
|
||||
languageOptions.value = []
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (requestId === ttsOptionsRequestSequence) {
|
||||
ttsOptionsLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载TTS音色选项
|
||||
// async function loadVoiceOptions(ttsModelId?: string) {
|
||||
@@ -457,6 +523,8 @@ function selectRoleTemplate(templateId: string) {
|
||||
selectedTemplateId.value = templateId
|
||||
const template = roleTemplates.value.find(t => t.id === templateId)
|
||||
if (template) {
|
||||
const templateTtsLanguage = template.ttsLanguage?.trim() || ''
|
||||
const hasTemplateTtsLanguage = Boolean(templateTtsLanguage)
|
||||
formData.value = {
|
||||
...formData.value,
|
||||
systemPrompt: template.systemPrompt || formData.value.systemPrompt,
|
||||
@@ -469,18 +537,34 @@ function selectRoleTemplate(templateId: string) {
|
||||
memModelId: template.memModelId || formData.value.memModelId,
|
||||
ttsModelId: template.ttsModelId || formData.value.ttsModelId,
|
||||
ttsVoiceId: template.ttsVoiceId || formData.value.ttsVoiceId,
|
||||
ttsLanguage: hasTemplateTtsLanguage ? templateTtsLanguage : formData.value.ttsLanguage,
|
||||
agentName: template.agentName || formData.value.agentName,
|
||||
chatHistoryConf: template.chatHistoryConf || formData.value.chatHistoryConf,
|
||||
summaryMemory: template.summaryMemory || formData.value.summaryMemory,
|
||||
langCode: template.langCode || formData.value.langCode,
|
||||
}
|
||||
fetchAllLanguag(template.ttsModelId || formData.value.ttsModelId)
|
||||
if (hasTemplateTtsLanguage) {
|
||||
selectedTtsLanguage.value = templateTtsLanguage
|
||||
displayNames.value.language = templateTtsLanguage
|
||||
}
|
||||
if (template.ttsModelId || template.ttsVoiceId || hasTemplateTtsLanguage) {
|
||||
ttsLanguageTouched.value = true
|
||||
ttsVoiceTouched.value = true
|
||||
}
|
||||
fetchAllLanguag(template.ttsModelId || formData.value.ttsModelId, {
|
||||
autoSelectVoice: true,
|
||||
preferredLanguage: hasTemplateTtsLanguage ? templateTtsLanguage : '',
|
||||
preferredVoiceId: template.ttsVoiceId,
|
||||
})
|
||||
updateDisplayNames()
|
||||
}
|
||||
}
|
||||
|
||||
// 打开选择器
|
||||
function openPicker(type: string) {
|
||||
if (ttsOptionsLoading.value && (type === 'tts' || type === 'language' || type === 'voiceprint')) {
|
||||
return
|
||||
}
|
||||
pickerShow.value[type] = true
|
||||
}
|
||||
|
||||
@@ -526,16 +610,28 @@ async function onPickerConfirm(type: string, value: any, name: string) {
|
||||
tempSummaryMemory.value = ''
|
||||
}
|
||||
break
|
||||
case 'tts':
|
||||
case 'tts': {
|
||||
const preferredLanguage = selectedTtsLanguage.value
|
||||
formData.value.ttsModelId = value
|
||||
await fetchAllLanguag(value)
|
||||
ttsLanguageTouched.value = true
|
||||
ttsVoiceTouched.value = true
|
||||
formData.value.ttsVoiceId = ''
|
||||
await fetchAllLanguag(value, { autoSelectVoice: true, preferredLanguage })
|
||||
break
|
||||
}
|
||||
case 'language':
|
||||
formData.value.language = value
|
||||
filterVoicesByLanguage()
|
||||
selectedTtsLanguage.value = value
|
||||
formData.value.ttsLanguage = value
|
||||
ttsLanguageTouched.value = true
|
||||
filterVoicesByLanguage({ autoSelectVoice: true })
|
||||
break
|
||||
case 'voiceprint':
|
||||
formData.value.ttsVoiceId = value
|
||||
ttsVoiceTouched.value = true
|
||||
if (selectedTtsLanguage.value) {
|
||||
formData.value.ttsLanguage = selectedTtsLanguage.value
|
||||
ttsLanguageTouched.value = true
|
||||
}
|
||||
displayNames.value.voiceprint = name // 确保显示名称正确更新
|
||||
break
|
||||
case 'report':
|
||||
@@ -623,6 +719,9 @@ function getModelDisplayName(modelType: string, modelId: string) {
|
||||
|
||||
// 保存智能体
|
||||
async function saveAgent() {
|
||||
if (ttsOptionsLoading.value) {
|
||||
return
|
||||
}
|
||||
if (!formData.value.agentName?.trim()) {
|
||||
toast.warning(t('agent.pleaseInputAgentName'))
|
||||
return
|
||||
@@ -633,26 +732,46 @@ async function saveAgent() {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await handleUpdateAgentTags()
|
||||
}
|
||||
catch (err) {
|
||||
toast.error(err)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
saving.value = true
|
||||
const tagNames = dynamicTags.value.map(tag => tag.tagName)
|
||||
const tagsChanged = !isSameStringList(tagNames, originalTagNames.value)
|
||||
// 构建保存数据,包含上下文配置和语音设置
|
||||
const saveData = {
|
||||
const saveData: Record<string, any> = {
|
||||
...formData.value,
|
||||
...speedPitchStore.speedPitch,
|
||||
ttsLanguage: formData.value.language,
|
||||
contextProviders: providerStore.providers,
|
||||
functions: normalizeAgentFunctions(formData.value.functions || []),
|
||||
}
|
||||
delete saveData.ttsVolume
|
||||
delete saveData.ttsRate
|
||||
delete saveData.ttsPitch
|
||||
delete saveData.ttsLanguage
|
||||
delete saveData.ttsVoiceId
|
||||
if (ttsLanguageTouched.value) {
|
||||
saveData.ttsLanguage = selectedTtsLanguage.value
|
||||
}
|
||||
if (ttsVoiceTouched.value) {
|
||||
saveData.ttsVoiceId = formData.value.ttsVoiceId
|
||||
}
|
||||
const changedTtsFields = new Set(speedPitchStore.changedFields)
|
||||
if (changedTtsFields.has('ttsVolume')) {
|
||||
saveData.ttsVolume = speedPitchStore.speedPitch.ttsVolume
|
||||
}
|
||||
if (changedTtsFields.has('ttsRate')) {
|
||||
saveData.ttsRate = speedPitchStore.speedPitch.ttsRate
|
||||
}
|
||||
if (changedTtsFields.has('ttsPitch')) {
|
||||
saveData.ttsPitch = speedPitchStore.speedPitch.ttsPitch
|
||||
}
|
||||
if (tagsChanged) {
|
||||
saveData.tagNames = tagNames
|
||||
}
|
||||
await updateAgent(agentId.value, saveData)
|
||||
loadAgentDetail()
|
||||
if (tagsChanged) {
|
||||
originalTagNames.value = [...tagNames]
|
||||
}
|
||||
speedPitchStore.resetChangedFields()
|
||||
await loadAgentDetail()
|
||||
|
||||
toast.success(t('agent.saveSuccess'))
|
||||
}
|
||||
@@ -700,14 +819,23 @@ async function loadAgentTags() {
|
||||
try {
|
||||
const res = await getAgentTags(agentId.value)
|
||||
dynamicTags.value = res || []
|
||||
originalTagNames.value = dynamicTags.value.map(tag => tag.tagName)
|
||||
}
|
||||
catch (error) {}
|
||||
}
|
||||
|
||||
// 更新智能体标签
|
||||
async function handleUpdateAgentTags() {
|
||||
const tagNames = dynamicTags.value.map(tag => tag.tagName)
|
||||
await updateAgentTags(agentId.value, { tagNames })
|
||||
async function handleSnapshotRestored() {
|
||||
await Promise.all([
|
||||
loadAgentDetail(),
|
||||
loadAgentTags(),
|
||||
])
|
||||
}
|
||||
|
||||
function isSameStringList(left: string[], right: string[]) {
|
||||
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
||||
return false
|
||||
}
|
||||
return left.every((value, index) => value === right[index])
|
||||
}
|
||||
|
||||
// 监听store中的插件配置变化
|
||||
@@ -734,6 +862,25 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<view class="bg-[#f5f7fb] px-[20rpx]">
|
||||
<view class="mb-[24rpx] flex items-center justify-between border border-[#eeeeee] rounded-[20rpx] bg-white p-[24rpx]" style="box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);">
|
||||
<view>
|
||||
<text class="block text-[32rpx] text-[#232338] font-bold">
|
||||
{{ t('agent.editTitle') }}
|
||||
</text>
|
||||
<text v-if="currentVersionNo" class="mt-[8rpx] block text-[24rpx] text-[#65686f]">
|
||||
{{ t('agentSnapshot.currentVersion') }} #{{ currentVersionNo }}
|
||||
</text>
|
||||
</view>
|
||||
<wd-button
|
||||
size="small"
|
||||
type="primary"
|
||||
custom-class="!bg-[#336cff] !h-[64rpx] !rounded-[32rpx]"
|
||||
@click="showSnapshotPanel = true"
|
||||
>
|
||||
{{ t('agentSnapshot.title') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
|
||||
<!-- 基础信息标题
|
||||
<view class="pb-[20rpx] first:pt-[20rpx]">
|
||||
<text class="text-[32rpx] text-[#232338] font-bold">
|
||||
@@ -1008,7 +1155,7 @@ onMounted(async () => {
|
||||
<wd-button
|
||||
type="primary"
|
||||
:loading="saving"
|
||||
:disabled="saving"
|
||||
:disabled="saving || ttsOptionsLoading"
|
||||
custom-class="w-full h-[80rpx] rounded-[16rpx] text-[30rpx] font-semibold bg-[#336cff] active:bg-[#2d5bd1]"
|
||||
@click="saveAgent"
|
||||
>
|
||||
@@ -1109,6 +1256,12 @@ onMounted(async () => {
|
||||
@close="onPickerCancel('report')"
|
||||
@select="({ item }) => onPickerConfirm('report', item.value, item.name)"
|
||||
/>
|
||||
<AgentSnapshotPanel
|
||||
v-model:visible="showSnapshotPanel"
|
||||
:agent-id="agentId"
|
||||
:current-version-no="currentVersionNo"
|
||||
@restored="handleSnapshotRestored"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ defineOptions({
|
||||
})
|
||||
|
||||
const speedPitchStore = useSpeedPitch()
|
||||
const SPEED_PITCH_FIELDS = ['ttsVolume', 'ttsRate', 'ttsPitch'] as const
|
||||
|
||||
const localSettings = ref({
|
||||
ttsVolume: 0,
|
||||
@@ -25,7 +26,10 @@ const localSettings = ref({
|
||||
})
|
||||
|
||||
function handleConfirm() {
|
||||
speedPitchStore.updateSpeedPitch(localSettings.value)
|
||||
const changedFields = SPEED_PITCH_FIELDS.filter((field) => {
|
||||
return localSettings.value[field] !== speedPitchStore.speedPitch[field]
|
||||
})
|
||||
speedPitchStore.updateSpeedPitch(localSettings.value, { changedFields })
|
||||
goBack()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
const SPEED_PITCH_FIELDS = ['ttsVolume', 'ttsRate', 'ttsPitch'] as const
|
||||
|
||||
type SpeedPitchField = typeof SPEED_PITCH_FIELDS[number]
|
||||
type SpeedPitchSettings = Record<SpeedPitchField, number>
|
||||
|
||||
export const useSpeedPitch = defineStore('speedPitch', () => {
|
||||
const speedPitch = ref({
|
||||
const speedPitch = ref<SpeedPitchSettings>({
|
||||
ttsVolume: 0,
|
||||
ttsRate: 0,
|
||||
ttsPitch: 0,
|
||||
})
|
||||
const changedFields = ref<SpeedPitchField[]>([])
|
||||
|
||||
const updateSpeedPitch = (val: typeof speedPitch.value) => {
|
||||
const updateSpeedPitch = (val: SpeedPitchSettings, options: { changedFields?: SpeedPitchField[] } = {}) => {
|
||||
speedPitch.value = val
|
||||
if (options.changedFields) {
|
||||
changedFields.value = Array.from(new Set([
|
||||
...changedFields.value,
|
||||
...options.changedFields,
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
const resetChangedFields = () => {
|
||||
changedFields.value = []
|
||||
}
|
||||
|
||||
return {
|
||||
speedPitch,
|
||||
changedFields,
|
||||
updateSpeedPitch,
|
||||
resetChangedFields,
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
@open="open"
|
||||
@close="close"
|
||||
>
|
||||
<template slot="title">
|
||||
<div class="snapshot-dialog-title">
|
||||
<i class="el-icon-time snapshot-title-icon" aria-hidden="true"></i>
|
||||
<span>{{ $t('agentSnapshot.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="snapshots"
|
||||
@@ -116,6 +123,13 @@
|
||||
width="860px"
|
||||
class="snapshot-detail-dialog"
|
||||
>
|
||||
<template slot="title">
|
||||
<div class="snapshot-dialog-title">
|
||||
<i class="el-icon-time snapshot-title-icon" aria-hidden="true"></i>
|
||||
<span>{{ detailDialogTitle }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-loading="detailLoading" class="snapshot-diff">
|
||||
<template v-if="currentSnapshot && snapshotDiffs.length">
|
||||
<div class="detail-summary">
|
||||
@@ -289,6 +303,13 @@
|
||||
width="860px"
|
||||
class="snapshot-detail-dialog"
|
||||
>
|
||||
<template slot="title">
|
||||
<div class="snapshot-dialog-title">
|
||||
<i class="el-icon-time snapshot-title-icon" aria-hidden="true"></i>
|
||||
<span>{{ restorePreviewTitle }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-loading="restorePreviewLoading" class="snapshot-diff">
|
||||
<template v-if="restorePreviewSnapshot && restorePreviewDiffs.length">
|
||||
<div class="detail-summary">
|
||||
@@ -418,17 +439,23 @@
|
||||
:title="$t('agentSnapshot.restoreMemoryWarning')"
|
||||
/>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="restorePreviewVisible = false">
|
||||
<span slot="footer" class="snapshot-dialog-footer">
|
||||
<el-button
|
||||
class="snapshot-footer-button snapshot-footer-cancel"
|
||||
@click="restorePreviewVisible = false"
|
||||
>
|
||||
{{ $t('button.cancel') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="snapshot-footer-button snapshot-footer-confirm"
|
||||
:loading="restoring"
|
||||
:disabled="restorePreviewLoading || !restorePreviewSnapshot || restorePreviewDiffs.length === 0"
|
||||
@click="confirmRestoreSnapshot"
|
||||
>
|
||||
<span class="confirm-inner">
|
||||
<i class="el-icon-refresh-left confirm-icon" aria-hidden="true"></i>
|
||||
{{ $t('agentSnapshot.confirmRestore') }}
|
||||
</span>
|
||||
</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
@@ -437,6 +464,7 @@
|
||||
|
||||
<script>
|
||||
import Api from "@/apis/api";
|
||||
import correctWord from "@/apis/module/correctWord";
|
||||
import { formatDate } from "@/utils/date";
|
||||
|
||||
const FALLBACK_PLUGIN_NAME_KEYS = {
|
||||
@@ -545,6 +573,9 @@ export default {
|
||||
voiceNameMap: {},
|
||||
voiceMetadataLoading: {},
|
||||
loadedVoiceModelIds: {},
|
||||
correctWordNameMap: {},
|
||||
correctWordMetadataLoaded: false,
|
||||
correctWordMetadataLoading: null,
|
||||
snapshotFetchSeq: 0,
|
||||
detailFetchSeq: 0,
|
||||
restorePreviewFetchSeq: 0,
|
||||
@@ -598,12 +629,13 @@ export default {
|
||||
|
||||
const beforeData = this.currentSnapshot.snapshotData || {};
|
||||
const afterData = this.currentSnapshot.afterSnapshotData || {};
|
||||
return this.buildDiffs(beforeData, afterData, this.currentSnapshot.changedFields || [], {
|
||||
return this.buildDiffs(beforeData, afterData, this.currentSnapshot.changedFields, {
|
||||
beforeLabel: this.$t("agentSnapshot.beforeChange"),
|
||||
afterLabel: this.$t("agentSnapshot.afterChange"),
|
||||
beforeVersionNo: this.currentSnapshot.beforeVersionNo,
|
||||
afterVersionNo: this.currentSnapshot.afterVersionNo || this.currentSnapshot.versionNo,
|
||||
fieldOrder: this.currentSnapshot.fieldOrder
|
||||
fieldOrder: this.currentSnapshot.fieldOrder,
|
||||
forceCompare: Boolean(this.currentSnapshot.forceCompare)
|
||||
});
|
||||
},
|
||||
restorePreviewDiffs() {
|
||||
@@ -620,7 +652,8 @@ export default {
|
||||
afterLabel: this.$t("agentSnapshot.afterRestore"),
|
||||
beforeVersionNo: this.restorePreviewSnapshot.beforeVersionNo || this.resolveCurrentVersionNo(),
|
||||
afterVersionNo: this.restorePreviewSnapshot.afterVersionNo || this.restorePreviewSnapshot.versionNo,
|
||||
fieldOrder: this.restorePreviewSnapshot.fieldOrder
|
||||
fieldOrder: this.restorePreviewSnapshot.fieldOrder,
|
||||
forceCompare: true
|
||||
}
|
||||
);
|
||||
},
|
||||
@@ -668,7 +701,13 @@ export default {
|
||||
});
|
||||
},
|
||||
buildDiffs(beforeData, afterData, changedFields, options = {}) {
|
||||
const fields = this.resolveDiffFields(beforeData, afterData, changedFields, options.fieldOrder);
|
||||
const fields = this.resolveDiffFields(
|
||||
beforeData,
|
||||
afterData,
|
||||
changedFields,
|
||||
options.fieldOrder,
|
||||
options.forceCompare
|
||||
);
|
||||
return fields.map((field) => {
|
||||
const beforeValue = this.getFieldValue(beforeData, field);
|
||||
const afterValue = this.getFieldValue(afterData, field);
|
||||
@@ -1023,6 +1062,7 @@ export default {
|
||||
...selectedSnapshot,
|
||||
versionNo: displayVersionNo,
|
||||
changedFields: adjacentVersion ? (selectedSnapshot.changedFields || row.changedFields || []) : [],
|
||||
forceCompare: !adjacentVersion,
|
||||
snapshotData: previousSnapshot.snapshotData || {},
|
||||
afterSnapshotData: selectedSnapshot.snapshotData || {},
|
||||
beforeVersionNo: previousSnapshot.versionNo,
|
||||
@@ -1210,9 +1250,44 @@ export default {
|
||||
const afterData = snapshot.afterSnapshotData || {};
|
||||
return Promise.all([
|
||||
this.ensureModelMetadata(),
|
||||
this.ensureVoiceMetadataForData(beforeData, afterData)
|
||||
this.ensureVoiceMetadataForData(beforeData, afterData),
|
||||
this.ensureCorrectWordMetadataForData(beforeData, afterData)
|
||||
]);
|
||||
},
|
||||
ensureCorrectWordMetadataForData(...dataList) {
|
||||
const hasCorrectWordIds = dataList.some((data) => {
|
||||
return Array.isArray(data?.correctWordFileIds) && data.correctWordFileIds.length > 0;
|
||||
});
|
||||
return hasCorrectWordIds ? this.ensureCorrectWordMetadata() : Promise.resolve();
|
||||
},
|
||||
ensureCorrectWordMetadata() {
|
||||
if (this.correctWordMetadataLoaded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (this.correctWordMetadataLoading) {
|
||||
return this.correctWordMetadataLoading;
|
||||
}
|
||||
|
||||
this.correctWordMetadataLoading = new Promise((resolve) => {
|
||||
correctWord.selectAll(({ data }) => {
|
||||
if (data.code === 0) {
|
||||
const metadata = {};
|
||||
(data.data || []).forEach((item) => {
|
||||
if (item.id) {
|
||||
metadata[item.id] = item.fileName || item.id;
|
||||
}
|
||||
});
|
||||
this.correctWordNameMap = metadata;
|
||||
this.correctWordMetadataLoaded = true;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}).finally(() => {
|
||||
this.correctWordMetadataLoading = null;
|
||||
});
|
||||
|
||||
return this.correctWordMetadataLoading;
|
||||
},
|
||||
ensureVoiceMetadataForData(...dataList) {
|
||||
const modelIds = Array.from(new Set(
|
||||
dataList
|
||||
@@ -1661,17 +1736,21 @@ export default {
|
||||
? `${this.$t("agentSnapshot.restoreFailedRollback")}: ${data.msg}`
|
||||
: this.$t("agentSnapshot.restoreFailedRollback");
|
||||
},
|
||||
resolveDiffFields(beforeData, afterData, changedFields = [], fieldOrder = []) {
|
||||
const safeChangedFields = Array.isArray(changedFields) ? changedFields : [];
|
||||
resolveDiffFields(beforeData, afterData, changedFields = null, fieldOrder = [], forceCompare = false) {
|
||||
const hasBackendChangedFields = Array.isArray(changedFields);
|
||||
const safeChangedFields = hasBackendChangedFields ? changedFields : [];
|
||||
const directFields = safeChangedFields
|
||||
.filter((field) => field && field !== "restore")
|
||||
.map((field) => this.canonicalField(field));
|
||||
const candidates = directFields.length > 0 && !safeChangedFields.includes("restore")
|
||||
? directFields
|
||||
: this.snapshotFieldOrder(fieldOrder, beforeData, afterData);
|
||||
if (!forceCompare && hasBackendChangedFields) {
|
||||
return Array.from(new Set(directFields));
|
||||
}
|
||||
|
||||
const candidates = this.snapshotFieldOrder(fieldOrder, beforeData, afterData);
|
||||
|
||||
return Array.from(new Set(candidates)).filter((field) => {
|
||||
return !this.isSameValue(
|
||||
return !this.isSameFieldValue(
|
||||
field,
|
||||
this.getFieldValue(beforeData, field),
|
||||
this.getFieldValue(afterData, field)
|
||||
);
|
||||
@@ -1706,6 +1785,52 @@ export default {
|
||||
isSameValue(left, right) {
|
||||
return this.isEquivalentValue(this.normalizeValue(left), this.normalizeValue(right));
|
||||
},
|
||||
isSameFieldValue(field, left, right) {
|
||||
return this.isEquivalentValue(
|
||||
this.normalizeValueForField(field, left),
|
||||
this.normalizeValueForField(field, right)
|
||||
);
|
||||
},
|
||||
normalizeValueForField(field, value) {
|
||||
if (field === "functions") {
|
||||
return this.normalizeFunctionMap(value);
|
||||
}
|
||||
if (field === "contextProviders") {
|
||||
return this.normalizeSortedJsonList(value);
|
||||
}
|
||||
if (["ttsVolume", "ttsRate", "ttsPitch"].includes(field)) {
|
||||
return this.normalizeDefaultTtsNumber(value);
|
||||
}
|
||||
if (field === "summaryMemory") {
|
||||
return value === null || value === undefined || String(value).trim() === "" ? "" : String(value);
|
||||
}
|
||||
if (field === "correctWordFileIds" || field === "tagNames") {
|
||||
return Array.isArray(value)
|
||||
? value.filter((item) => item !== null && item !== undefined && String(item).trim() !== "")
|
||||
.map((item) => String(item))
|
||||
.sort()
|
||||
: [];
|
||||
}
|
||||
return this.normalizeValue(value);
|
||||
},
|
||||
normalizeSortedJsonList(value) {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value
|
||||
.map((item) => this.normalizeValue(item))
|
||||
.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
||||
},
|
||||
normalizeDefaultTtsNumber(value) {
|
||||
if (value === null || value === undefined || String(value).trim() === "") {
|
||||
return 0;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return Math.trunc(value);
|
||||
}
|
||||
const text = String(value).trim();
|
||||
return /^[+-]?\d+$/.test(text) ? Number.parseInt(text, 10) : text;
|
||||
},
|
||||
isEquivalentValue(left, right) {
|
||||
if (left === SNAPSHOT_SECRET_REDACTED || right === SNAPSHOT_SECRET_REDACTED) {
|
||||
return true;
|
||||
@@ -1754,6 +1879,9 @@ export default {
|
||||
return this.translateKey(CHAT_HISTORY_CONF_LABEL_KEYS[value] || CHAT_HISTORY_CONF_LABEL_KEYS[Number(value)])
|
||||
|| this.formatValue(value);
|
||||
}
|
||||
if (field === "correctWordFileIds") {
|
||||
return this.correctWordDisplayNames(value);
|
||||
}
|
||||
return this.formatValue(value);
|
||||
},
|
||||
modelDisplayName(value) {
|
||||
@@ -1769,6 +1897,14 @@ export default {
|
||||
return this.voiceNameMap[`${modelId}:${value}`] || this.voiceNameMap[value]
|
||||
|| this.translateKey(FALLBACK_VOICE_NAME_KEYS[value], String(value)) || String(value);
|
||||
},
|
||||
correctWordDisplayNames(value) {
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
return this.$t("agentSnapshot.emptyValue");
|
||||
}
|
||||
return value
|
||||
.map((id) => this.correctWordNameMap[id] || id)
|
||||
.join(", ");
|
||||
},
|
||||
formatValue(value) {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return this.$t("agentSnapshot.emptyValue");
|
||||
@@ -1846,6 +1982,59 @@ export default {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.snapshot-dialog-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
|
||||
> span {
|
||||
line-height: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.snapshot-title-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 8px;
|
||||
color: #4a7cfd;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__headerbtn {
|
||||
top: 12px;
|
||||
right: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
|
||||
|
||||
.el-dialog__close {
|
||||
position: static;
|
||||
color: #666;
|
||||
font-size: 18px;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
|
||||
|
||||
.el-dialog__close {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__body {
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -1854,6 +2043,81 @@ export default {
|
||||
padding: 12px 20px 16px;
|
||||
}
|
||||
|
||||
.snapshot-dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.snapshot-footer-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 96px;
|
||||
height: 36px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
& + .snapshot-footer-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.snapshot-footer-cancel {
|
||||
border: 1px solid #d7e1ff;
|
||||
background: #f7f9ff;
|
||||
color: #4a7cfd;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: #4a7cfd;
|
||||
background: #eef4ff;
|
||||
color: #4a7cfd;
|
||||
box-shadow: 0 2px 8px rgba(74, 124, 253, 0.14);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
.snapshot-footer-confirm {
|
||||
border: none;
|
||||
background: linear-gradient(to right, #4a7cfd, #8154fc);
|
||||
color: #fff;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: transparent;
|
||||
background: linear-gradient(to right, #4a7cfd, #8154fc);
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 8px rgba(74, 124, 253, 0.3);
|
||||
transform: translateY(-2px);
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
&.is-disabled,
|
||||
&.is-disabled:hover,
|
||||
&.is-disabled:focus {
|
||||
border-color: transparent;
|
||||
background: linear-gradient(to right, #a0b4fd, #b8a4fd);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
cursor: not-allowed;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-inner {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.confirm-icon {
|
||||
margin-right: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.snapshot-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
:step="1"
|
||||
:format-tooltip="formatTooltip"
|
||||
class="tts-slider"
|
||||
@change="markTtsSettingChanged('volume')"
|
||||
/>
|
||||
<span class="slider-hint">{{ $t('roleConfig.volumeHint') }}</span>
|
||||
</div>
|
||||
@@ -40,6 +41,7 @@
|
||||
:step="1"
|
||||
:format-tooltip="formatTooltip"
|
||||
class="tts-slider"
|
||||
@change="markTtsSettingChanged('speed')"
|
||||
/>
|
||||
<span class="slider-hint">{{ $t('roleConfig.speedHint') }}</span>
|
||||
</div>
|
||||
@@ -55,6 +57,7 @@
|
||||
:step="1"
|
||||
:format-tooltip="formatTooltip"
|
||||
class="tts-slider"
|
||||
@change="markTtsSettingChanged('pitch')"
|
||||
/>
|
||||
<span class="slider-hint">{{ $t('roleConfig.pitchHint') }}</span>
|
||||
</div>
|
||||
@@ -122,6 +125,11 @@ export default {
|
||||
speed: 0,
|
||||
pitch: 0,
|
||||
},
|
||||
changedTtsFields: {
|
||||
volume: false,
|
||||
speed: false,
|
||||
pitch: false,
|
||||
},
|
||||
replacementWordIds: [],
|
||||
replacementWordList: []
|
||||
};
|
||||
@@ -141,6 +149,11 @@ export default {
|
||||
if (newVal) {
|
||||
// 当抽屉打开时,复制当前设置到本地
|
||||
this.localSettings = { ...this.settings };
|
||||
this.changedTtsFields = {
|
||||
volume: false,
|
||||
speed: false,
|
||||
pitch: false,
|
||||
};
|
||||
this.replacementWordIds = [...this.checkedReplacementWordIds];
|
||||
this.fetchReplacementWordList();
|
||||
}
|
||||
@@ -156,9 +169,18 @@ export default {
|
||||
},
|
||||
handleSave() {
|
||||
// 保存设置并关闭
|
||||
this.$emit('save', { ...this.localSettings, replacementWordIds: this.replacementWordIds });
|
||||
const changedTtsFields = Object.keys(this.changedTtsFields)
|
||||
.filter((field) => this.changedTtsFields[field]);
|
||||
this.$emit('save', {
|
||||
...this.localSettings,
|
||||
changedTtsFields,
|
||||
replacementWordIds: this.replacementWordIds
|
||||
});
|
||||
this.handleClose();
|
||||
},
|
||||
markTtsSettingChanged(field) {
|
||||
this.$set(this.changedTtsFields, field, true);
|
||||
},
|
||||
formatTooltip(val) {
|
||||
return `${val}%`;
|
||||
},
|
||||
|
||||
@@ -51,7 +51,12 @@
|
||||
<el-button class="history-btn" @click="showSnapshotDialog = true">
|
||||
{{ $t("roleConfig.snapshotHistory") }}
|
||||
</el-button>
|
||||
<el-button type="primary" class="save-btn" @click="saveConfig">
|
||||
<el-button
|
||||
type="primary"
|
||||
class="save-btn"
|
||||
:disabled="voiceOptionsLoading"
|
||||
@click="saveConfig"
|
||||
>
|
||||
{{ $t("roleConfig.saveConfig") }}
|
||||
</el-button>
|
||||
<el-button class="reset-btn" @click="resetConfig">{{
|
||||
@@ -367,7 +372,7 @@
|
||||
v-model="selectedLanguage"
|
||||
:placeholder="$t('roleConfig.selectLanguage')"
|
||||
class="form-select language-select"
|
||||
@change="filterVoicesByLanguage"
|
||||
@change="handleLanguageChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="(lang, index) in languageOptions"
|
||||
@@ -392,6 +397,7 @@
|
||||
filterable
|
||||
:placeholder="$t('roleConfig.pleaseSelect')"
|
||||
class="form-select"
|
||||
@change="handleVoiceChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in voiceOptions"
|
||||
@@ -556,6 +562,10 @@ export default {
|
||||
// 语言筛选相关状态
|
||||
languageOptions: [], // 语言选项列表
|
||||
selectedLanguage: '', // 当前选中的语言
|
||||
ttsLanguageTouched: false,
|
||||
ttsVoiceTouched: false,
|
||||
voiceFetchSeq: 0,
|
||||
voiceOptionsLoading: false,
|
||||
// 功能状态
|
||||
featureStatus: {
|
||||
vad: false, // 语言检测活动功能状态
|
||||
@@ -592,6 +602,9 @@ export default {
|
||||
return { ...fallback };
|
||||
},
|
||||
async saveConfig() {
|
||||
if (this.voiceOptionsLoading) {
|
||||
return;
|
||||
}
|
||||
const configData = {
|
||||
agentCode: this.form.agentCode,
|
||||
agentName: this.form.agentName,
|
||||
@@ -601,8 +614,6 @@ export default {
|
||||
slmModelId: this.form.model.slmModelId,
|
||||
vllmModelId: this.form.model.vllmModelId,
|
||||
ttsModelId: this.form.model.ttsModelId,
|
||||
ttsVoiceId: this.form.ttsVoiceId,
|
||||
ttsLanguage: this.selectedLanguage,
|
||||
chatHistoryConf: this.form.chatHistoryConf,
|
||||
memModelId: this.form.model.memModelId,
|
||||
intentModelId: this.form.model.intentModelId,
|
||||
@@ -625,6 +636,17 @@ export default {
|
||||
if (tagsChanged) {
|
||||
configData.tagNames = tagNames;
|
||||
}
|
||||
if (this.shouldSubmitTtsLanguage()) {
|
||||
configData.ttsLanguage = this.selectedLanguage;
|
||||
}
|
||||
if (this.ttsVoiceTouched && this.form.ttsVoiceId !== null && this.form.ttsVoiceId !== undefined) {
|
||||
configData.ttsVoiceId = this.form.ttsVoiceId;
|
||||
}
|
||||
const submittedTtsLanguageTouched = this.ttsLanguageTouched;
|
||||
const submittedTtsVoiceTouched = this.ttsVoiceTouched;
|
||||
const submittedTtsLanguage = configData.ttsLanguage;
|
||||
const submittedTtsVoiceId = configData.ttsVoiceId;
|
||||
const submittedVoiceFetchSeq = this.voiceFetchSeq;
|
||||
|
||||
// 只在用户设置了TTS参数时才传递(不为null/undefined)
|
||||
if (this.form.ttsVolume !== null && this.form.ttsVolume !== undefined) {
|
||||
@@ -643,6 +665,17 @@ export default {
|
||||
if (tagsChanged) {
|
||||
this.originalTagNames = [...tagNames];
|
||||
}
|
||||
if (submittedVoiceFetchSeq === this.voiceFetchSeq
|
||||
&& submittedTtsLanguageTouched
|
||||
&& this.selectedLanguage === submittedTtsLanguage) {
|
||||
this.form.ttsLanguage = submittedTtsLanguage;
|
||||
this.ttsLanguageTouched = false;
|
||||
}
|
||||
if (submittedVoiceFetchSeq === this.voiceFetchSeq
|
||||
&& submittedTtsVoiceTouched
|
||||
&& this.form.ttsVoiceId === submittedTtsVoiceId) {
|
||||
this.ttsVoiceTouched = false;
|
||||
}
|
||||
this.$message.success({
|
||||
message: i18n.t("roleConfig.saveSuccess"),
|
||||
showClose: true,
|
||||
@@ -686,10 +719,14 @@ export default {
|
||||
type: "warning",
|
||||
})
|
||||
.then(() => {
|
||||
this.selectedLanguage = "";
|
||||
this.ttsLanguageTouched = true;
|
||||
this.ttsVoiceTouched = true;
|
||||
this.form = {
|
||||
agentCode: "",
|
||||
agentName: "",
|
||||
ttsVoiceId: "",
|
||||
ttsLanguage: "",
|
||||
chatHistoryConf: 0,
|
||||
systemPrompt: "",
|
||||
summaryMemory: "",
|
||||
@@ -707,6 +744,7 @@ export default {
|
||||
intentModelId: "",
|
||||
},
|
||||
};
|
||||
this.fetchVoiceOptions("");
|
||||
this.dynamicTags = [];
|
||||
this.currentFunctions = [];
|
||||
this.$message.success({
|
||||
@@ -745,6 +783,7 @@ export default {
|
||||
}
|
||||
},
|
||||
applyTemplateData(templateData) {
|
||||
const currentLanguage = this.selectedLanguage;
|
||||
this.form = {
|
||||
...this.form,
|
||||
agentName: templateData.agentName || this.form.agentName,
|
||||
@@ -764,11 +803,28 @@ export default {
|
||||
intentModelId: templateData.intentModelId || this.form.model.intentModelId,
|
||||
},
|
||||
};
|
||||
if (templateData.ttsLanguage) {
|
||||
this.selectedLanguage = templateData.ttsLanguage;
|
||||
this.ttsLanguageTouched = true;
|
||||
}
|
||||
if (templateData.ttsModelId || templateData.ttsVoiceId || templateData.ttsLanguage) {
|
||||
if (templateData.ttsModelId || templateData.ttsVoiceId) {
|
||||
this.ttsVoiceTouched = true;
|
||||
}
|
||||
this.ttsLanguageTouched = true;
|
||||
this.fetchVoiceOptions(this.form.model.ttsModelId, {
|
||||
autoSelectVoice: true,
|
||||
preferredLanguage: templateData.ttsLanguage || (templateData.ttsVoiceId ? "" : currentLanguage),
|
||||
preferredVoiceId: templateData.ttsVoiceId || ""
|
||||
});
|
||||
}
|
||||
},
|
||||
fetchAgentConfig(agentId) {
|
||||
Api.agent.getDeviceConfig(agentId, ({ data }) => {
|
||||
if (data.code === 0) {
|
||||
this.tempSummaryMemory = "";
|
||||
this.ttsLanguageTouched = false;
|
||||
this.ttsVoiceTouched = false;
|
||||
this.form = {
|
||||
...this.form,
|
||||
...data.data,
|
||||
@@ -783,6 +839,10 @@ export default {
|
||||
intentModelId: data.data.intentModelId,
|
||||
},
|
||||
};
|
||||
this.fetchVoiceOptions(data.data.ttsModelId, {
|
||||
preferredLanguage: data.data.ttsLanguage,
|
||||
preferredVoiceId: data.data.ttsVoiceId
|
||||
});
|
||||
|
||||
// 同步TTS设置到ttsSettings
|
||||
this.ttsSettings = {
|
||||
@@ -874,15 +934,22 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
fetchVoiceOptions(modelId) {
|
||||
fetchVoiceOptions(modelId, options = {}) {
|
||||
const requestSeq = ++this.voiceFetchSeq;
|
||||
if (!modelId) {
|
||||
this.voiceOptionsLoading = false;
|
||||
this.voiceOptions = [];
|
||||
this.voiceDetails = {};
|
||||
this.languageOptions = [];
|
||||
this.selectedLanguage = '';
|
||||
return;
|
||||
}
|
||||
this.voiceOptionsLoading = true;
|
||||
Api.model.getModelVoices(modelId, "", ({ data }) => {
|
||||
if (requestSeq !== this.voiceFetchSeq) {
|
||||
return;
|
||||
}
|
||||
this.voiceOptionsLoading = false;
|
||||
if (data.code === 0 && data.data) {
|
||||
// 保存完整的音色信息
|
||||
this.voiceDetails = data.data.reduce((acc, voice) => {
|
||||
@@ -904,15 +971,27 @@ export default {
|
||||
label: lang
|
||||
}));
|
||||
|
||||
// 使用后端返回的用户选择的语言,如果没有则使用第一个语言选项
|
||||
if (this.form.ttsLanguage && this.languageOptions.some(option => option.value === this.form.ttsLanguage)) {
|
||||
// 优先保留调用方指定或后端保存的语言,其次使用当前音色的默认语言
|
||||
const requestedLanguage = options.preferredLanguage;
|
||||
const preferredVoiceLanguage = options.preferredVoiceId
|
||||
? this.getVoiceDefaultLanguage(options.preferredVoiceId)
|
||||
: "";
|
||||
if (requestedLanguage && this.languageOptions.some(option => option.value === requestedLanguage)) {
|
||||
this.selectedLanguage = requestedLanguage;
|
||||
} else if (preferredVoiceLanguage && this.languageOptions.some(option => option.value === preferredVoiceLanguage)) {
|
||||
this.selectedLanguage = preferredVoiceLanguage;
|
||||
} else if (this.form.ttsLanguage && this.languageOptions.some(option => option.value === this.form.ttsLanguage)) {
|
||||
this.selectedLanguage = this.form.ttsLanguage;
|
||||
} else if (this.getVoiceDefaultLanguage(this.form.ttsVoiceId)) {
|
||||
this.selectedLanguage = this.getVoiceDefaultLanguage(this.form.ttsVoiceId);
|
||||
} else if (this.languageOptions.length > 0) {
|
||||
this.selectedLanguage = this.languageOptions[0].value;
|
||||
} else {
|
||||
this.selectedLanguage = "";
|
||||
}
|
||||
|
||||
// 根据选中的语言筛选音色
|
||||
this.filterVoicesByLanguage();
|
||||
this.filterVoicesByLanguage(options);
|
||||
} else {
|
||||
this.voiceOptions = [];
|
||||
this.voiceDetails = {};
|
||||
@@ -921,9 +1000,19 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
getVoiceDefaultLanguage(voiceId) {
|
||||
if (!voiceId || !this.voiceDetails || !this.voiceDetails[voiceId]?.languages) {
|
||||
return "";
|
||||
}
|
||||
const languages = this.voiceDetails[voiceId].languages
|
||||
.split(/[、;;,,]/)
|
||||
.map(lang => lang.trim())
|
||||
.filter(Boolean);
|
||||
return languages[0] || "";
|
||||
},
|
||||
|
||||
// 根据语言筛选音色
|
||||
filterVoicesByLanguage() {
|
||||
filterVoicesByLanguage(options = {}) {
|
||||
if (!this.voiceDetails || Object.keys(this.voiceDetails).length === 0) {
|
||||
this.voiceOptions = [];
|
||||
return;
|
||||
@@ -954,8 +1043,9 @@ export default {
|
||||
const currentVoiceSupportsLanguage = this.form.ttsVoiceId &&
|
||||
filteredVoices.some(voice => voice.id === this.form.ttsVoiceId);
|
||||
|
||||
if (!currentVoiceSupportsLanguage) {
|
||||
if (!currentVoiceSupportsLanguage && options.autoSelectVoice) {
|
||||
this.form.ttsVoiceId = filteredVoices.length > 0 ? filteredVoices[0].id : '';
|
||||
this.ttsVoiceTouched = true;
|
||||
}
|
||||
|
||||
// 同步到ttsSettings(如果值为null,使用0作为显示默认值,但不修改form中的值)
|
||||
@@ -965,6 +1055,21 @@ export default {
|
||||
pitch: this.form.ttsPitch !== null && this.form.ttsPitch !== undefined ? this.form.ttsPitch : 0
|
||||
};
|
||||
},
|
||||
handleLanguageChange() {
|
||||
this.ttsLanguageTouched = true;
|
||||
this.form.ttsLanguage = this.selectedLanguage;
|
||||
this.filterVoicesByLanguage({ autoSelectVoice: true });
|
||||
},
|
||||
handleVoiceChange() {
|
||||
this.ttsVoiceTouched = true;
|
||||
if (this.selectedLanguage) {
|
||||
this.form.ttsLanguage = this.selectedLanguage;
|
||||
this.ttsLanguageTouched = true;
|
||||
}
|
||||
},
|
||||
shouldSubmitTtsLanguage() {
|
||||
return this.ttsLanguageTouched;
|
||||
},
|
||||
|
||||
getFunctionDisplayChar(name) {
|
||||
if (!name || name.length === 0) return "";
|
||||
@@ -1006,6 +1111,17 @@ export default {
|
||||
// 当LLM类型改变时,更新意图识别选项的可见性
|
||||
this.updateIntentOptionsVisibility();
|
||||
}
|
||||
if (type === "TTS") {
|
||||
const preferredLanguage = this.selectedLanguage;
|
||||
this.ttsLanguageTouched = true;
|
||||
this.ttsVoiceTouched = true;
|
||||
this.form.ttsVoiceId = "";
|
||||
this.form.ttsLanguage = "";
|
||||
this.fetchVoiceOptions(value, {
|
||||
autoSelectVoice: true,
|
||||
preferredLanguage
|
||||
});
|
||||
}
|
||||
},
|
||||
fetchAllFunctions() {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -1042,13 +1158,20 @@ export default {
|
||||
this.showTtsAdvancedDialog = true;
|
||||
},
|
||||
handleTtsSettingsSave(settings) {
|
||||
const { replacementWordIds, ...ttsSettings } = settings;
|
||||
const { replacementWordIds, changedTtsFields = [], ...ttsSettings } = settings;
|
||||
this.checkedReplacementWordIds = replacementWordIds;
|
||||
// 保存TTS设置
|
||||
this.ttsSettings = ttsSettings;
|
||||
const changedFields = new Set(changedTtsFields);
|
||||
if (changedFields.has("volume")) {
|
||||
this.form.ttsVolume = ttsSettings.volume;
|
||||
}
|
||||
if (changedFields.has("speed")) {
|
||||
this.form.ttsRate = ttsSettings.speed;
|
||||
}
|
||||
if (changedFields.has("pitch")) {
|
||||
this.form.ttsPitch = ttsSettings.pitch;
|
||||
}
|
||||
},
|
||||
handleUpdateContext(providers) {
|
||||
this.currentContextProviders = providers;
|
||||
@@ -1424,27 +1547,6 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"form.model.ttsModelId": {
|
||||
handler(newVal, oldVal) {
|
||||
if (oldVal && newVal !== oldVal) {
|
||||
this.form.ttsVoiceId = "";
|
||||
this.fetchVoiceOptions(newVal);
|
||||
} else {
|
||||
this.fetchVoiceOptions(newVal);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
voiceOptions: {
|
||||
handler(newVal) {
|
||||
if (newVal && newVal.length > 0 && !this.form.ttsVoiceId) {
|
||||
this.form.ttsVoiceId = newVal[0].value;
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
const agentId = this.$route.query.agentId;
|
||||
if (agentId) {
|
||||
|
||||
Reference in New Issue
Block a user