feat: improve agent snapshot history

This commit is contained in:
Tyke Chen
2026-07-10 11:44:35 +08:00
parent 8ec5851027
commit a5aee109fe
23 changed files with 2833 additions and 130 deletions
+5
View File
@@ -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
@@ -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");
@@ -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获取音色名称
*
@@ -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
*/
@@ -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 &gt; #{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);
@@ -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"));
}
}
+65 -1
View File
@@ -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,
},
})
}
+50 -4
View File
@@ -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
+75
View File
@@ -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',
+75
View File
@@ -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',
+75
View File
@@ -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',
+75
View File
@@ -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',
+75
View File
@@ -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',
+75
View File
@@ -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',
+55 -1
View File
@@ -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
+198 -45
View File
@@ -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()
}
+20 -2
View File
@@ -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}%`;
},
+134 -32
View File
@@ -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) {