diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/VoiceDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/VoiceDTO.java index 4db614ef..e69608a9 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/VoiceDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/VoiceDTO.java @@ -22,12 +22,24 @@ public class VoiceDTO implements Serializable { @Schema(description = "音频播放地址") private String voiceDemo; + + @Schema(description = "是否为克隆音色") + private Boolean isClone; // 添加双参数构造函数,保持向后兼容 public VoiceDTO(String id, String name) { this.id = id; this.name = name; this.voiceDemo = null; + this.isClone = false; // 默认不是克隆音色 + } + + // 添加三参数构造函数,用于普通音色 + public VoiceDTO(String id, String name, String voiceDemo) { + this.id = id; + this.name = name; + this.voiceDemo = voiceDemo; + this.isClone = false; } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java index feb6c449..9d6ca5c4 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java @@ -136,6 +136,7 @@ public class TimbreServiceImpl extends BaseServiceImpl .map(entity -> { VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName()); dto.setVoiceDemo(entity.getVoiceDemo()); + dto.setIsClone(false); // 设置为普通音色 return dto; }) .collect(Collectors.toList()); @@ -152,6 +153,7 @@ public class TimbreServiceImpl extends BaseServiceImpl voiceDTO.setName(MessageUtils.getMessage(ErrorCode.VOICE_CLONE_PREFIX) + entity.getName()); // 保留从数据库查询到的voiceDemo字段 voiceDTO.setVoiceDemo(entity.getVoiceDemo()); + voiceDTO.setIsClone(true); // 设置为克隆音色 redisUtils.set(RedisKeys.getTimbreNameById(voiceDTO.getId()), voiceDTO.getName(), RedisUtils.NOT_EXPIRE); voiceDTOs.add(0, voiceDTO); @@ -214,6 +216,7 @@ public class TimbreServiceImpl extends BaseServiceImpl TimbreEntity entity = list.get(0); VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName()); dto.setVoiceDemo(entity.getVoiceDemo()); + dto.setIsClone(false); // 设置为普通音色 return dto; } } \ No newline at end of file diff --git a/main/manager-web/src/i18n/de.js b/main/manager-web/src/i18n/de.js index 2ecfca59..005fd9db 100644 --- a/main/manager-web/src/i18n/de.js +++ b/main/manager-web/src/i18n/de.js @@ -779,6 +779,16 @@ export default { 'roleConfig.fetchConfigFailed': 'Konfiguration konnte nicht abgerufen werden', 'roleConfig.fetchModelsFailed': 'Modellliste konnte nicht abgerufen werden', 'roleConfig.fetchPluginsFailed': 'Plugin-Liste konnte nicht abgerufen werden', + 'roleConfig.cannotResumeAudio': 'Audio-Wiedergabe kann nicht fortgesetzt werden', + 'roleConfig.selectVoiceFirst': 'Bitte wählen Sie zuerst eine Stimme aus', + 'roleConfig.audioLoadTimeout': 'Audio-Laden dauert zu lange, bitte versuchen Sie es später erneut', + 'roleConfig.cloneAudioPlayFailed': 'Klon-Audio-Wiedergabe fehlgeschlagen', + 'roleConfig.cannotPlayCloneAudio': 'Klon-Audio kann nicht abgespielt werden', + 'roleConfig.getCloneAudioFailed': 'Klon-Audio konnte nicht abgerufen werden', + 'roleConfig.noPreviewAudio': 'Diese Stimme hat keine Vorschau-Audio-Datei', + 'roleConfig.audioPlayFailed': 'Audio-Wiedergabe fehlgeschlagen', + 'roleConfig.cannotPlayAudio': 'Audio kann nicht abgespielt werden', + 'roleConfig.audioPlayError': 'Fehler bei der Audio-Wiedergabe', // Function management dialog text 'functionDialog.title': 'Funktionsverwaltung', @@ -919,6 +929,10 @@ export default { 'ttsModel.selectVoiceToDelete': 'Bitte wählen Sie Stimmen zum Löschen aus', 'ttsModel.warning': 'Warnung', 'ttsModel.confirmDeleteVoice': 'Sind Sie sicher, dass Sie {count} Stimmen löschen möchten?', + 'ttsModel.voiceCodeNameLanguageRequired': 'Stimmcode, Stimmaname und Sprache können nicht leer sein', + 'ttsModel.deleteCancelled': 'Löschvorgang abgebrochen', + 'ttsModel.updateSuccess': 'Aktualisierung erfolgreich', + 'ttsModel.saveSuccess': 'Speicherung erfolgreich', // OTA Management Page Text 'otaManagement.firmwareManagement': 'Firmware-Verwaltung', diff --git a/main/manager-web/src/i18n/en.js b/main/manager-web/src/i18n/en.js index 76876b42..ecf7a539 100644 --- a/main/manager-web/src/i18n/en.js +++ b/main/manager-web/src/i18n/en.js @@ -779,6 +779,16 @@ export default { 'roleConfig.fetchConfigFailed': 'Failed to fetch configuration', 'roleConfig.fetchModelsFailed': 'Failed to fetch model list', 'roleConfig.fetchPluginsFailed': 'Failed to fetch plugin list', + 'roleConfig.cannotResumeAudio': 'Cannot resume audio playback', + 'roleConfig.selectVoiceFirst': 'Please select a voice first', + 'roleConfig.audioLoadTimeout': 'Audio loading takes too long, please try again later', + 'roleConfig.cloneAudioPlayFailed': 'Clone audio playback failed', + 'roleConfig.cannotPlayCloneAudio': 'Cannot play clone audio', + 'roleConfig.getCloneAudioFailed': 'Failed to get clone audio', + 'roleConfig.noPreviewAudio': 'This voice has no preview audio available', + 'roleConfig.audioPlayFailed': 'Audio playback failed', + 'roleConfig.cannotPlayAudio': 'Cannot play audio', + 'roleConfig.audioPlayError': 'Error occurred during audio playback', // Function management dialog text 'functionDialog.title': 'Function Management', @@ -919,6 +929,10 @@ export default { 'ttsModel.selectVoiceToDelete': 'Please select voices to delete', 'ttsModel.warning': 'Warning', 'ttsModel.confirmDeleteVoice': 'Are you sure to delete {count} voices?', + 'ttsModel.voiceCodeNameLanguageRequired': 'Voice code, voice name and language type cannot be empty', + 'ttsModel.deleteCancelled': 'Deletion cancelled', + 'ttsModel.updateSuccess': 'Update successful', + 'ttsModel.saveSuccess': 'Save successful', // OTA Management Page Text 'otaManagement.firmwareManagement': 'Firmware Management', diff --git a/main/manager-web/src/i18n/vi.js b/main/manager-web/src/i18n/vi.js index 9e6cb17d..18e13033 100644 --- a/main/manager-web/src/i18n/vi.js +++ b/main/manager-web/src/i18n/vi.js @@ -779,6 +779,16 @@ export default { 'roleConfig.fetchConfigFailed': 'Không thể lấy cấu hình', 'roleConfig.fetchModelsFailed': 'Không thể tải danh sách mô hình', 'roleConfig.fetchPluginsFailed': 'Không thể tải danh sách plugin', + 'roleConfig.cannotResumeAudio': 'Không thể tiếp tục phát âm thanh', + 'roleConfig.selectVoiceFirst': 'Vui lòng chọn giọng nói trước', + 'roleConfig.audioLoadTimeout': 'Tải âm thanh mất quá nhiều thời gian, vui lòng thử lại sau', + 'roleConfig.cloneAudioPlayFailed': 'Phát âm thanh sao chép thất bại', + 'roleConfig.cannotPlayCloneAudio': 'Không thể phát âm thanh sao chép', + 'roleConfig.getCloneAudioFailed': 'Không thể lấy âm thanh sao chép', + 'roleConfig.noPreviewAudio': 'Giọng nói này không có âm thanh xem trước', + 'roleConfig.audioPlayFailed': 'Phát âm thanh thất bại', + 'roleConfig.cannotPlayAudio': 'Không thể phát âm thanh', + 'roleConfig.audioPlayError': 'Lỗi trong quá trình phát âm thanh', // Function management dialog text 'functionDialog.title': 'Quản lý chức năng', @@ -919,6 +929,10 @@ export default { 'ttsModel.selectVoiceToDelete': 'Vui lòng chọn giọng nói để xóa', 'ttsModel.warning': 'Cảnh báo', 'ttsModel.confirmDeleteVoice': 'Bạn có chắc chắn muốn xóa {count} giọng nói?', + 'ttsModel.voiceCodeNameLanguageRequired': 'Mã giọng nói, tên giọng nói và loại ngôn ngữ không được để trống', + 'ttsModel.deleteCancelled': 'Xóa đã bị hủy', + 'ttsModel.updateSuccess': 'Cập nhật thành công', + 'ttsModel.saveSuccess': 'Lưu thành công', // OTA Management Page Text 'otaManagement.firmwareManagement': 'Quản lý firmware', diff --git a/main/manager-web/src/i18n/zh_CN.js b/main/manager-web/src/i18n/zh_CN.js index bc655d32..718040c4 100644 --- a/main/manager-web/src/i18n/zh_CN.js +++ b/main/manager-web/src/i18n/zh_CN.js @@ -779,6 +779,16 @@ export default { 'roleConfig.fetchConfigFailed': '获取配置失败', 'roleConfig.fetchModelsFailed': '获取模型列表失败', 'roleConfig.fetchPluginsFailed': '获取插件列表失败', + 'roleConfig.cannotResumeAudio': '无法恢复播放音频', + 'roleConfig.selectVoiceFirst': '请先选择一个音色', + 'roleConfig.audioLoadTimeout': '音频加载时间较长,请稍后重试', + 'roleConfig.cloneAudioPlayFailed': '克隆音频播放失败', + 'roleConfig.cannotPlayCloneAudio': '无法播放克隆音频', + 'roleConfig.getCloneAudioFailed': '获取克隆音频失败', + 'roleConfig.noPreviewAudio': '该音色暂无可预览的音频', + 'roleConfig.audioPlayFailed': '音频播放失败', + 'roleConfig.cannotPlayAudio': '无法播放音频', + 'roleConfig.audioPlayError': '播放音频过程出错', // 功能管理对话框文本 'functionDialog.title': '功能管理', @@ -919,6 +929,10 @@ export default { 'ttsModel.selectVoiceToDelete': '请选择要删除的音色', 'ttsModel.warning': '警告', 'ttsModel.confirmDeleteVoice': '确定要删除{count}个音色吗?', + 'ttsModel.voiceCodeNameLanguageRequired': '音色编码、音色名称和语言类型不能为空', + 'ttsModel.deleteCancelled': '删除已取消', + 'ttsModel.updateSuccess': '更新成功', + 'ttsModel.saveSuccess': '保存成功', // OTA管理页面文本 'otaManagement.firmwareManagement': '固件管理', diff --git a/main/manager-web/src/i18n/zh_TW.js b/main/manager-web/src/i18n/zh_TW.js index c5ea5b19..6be0f64f 100644 --- a/main/manager-web/src/i18n/zh_TW.js +++ b/main/manager-web/src/i18n/zh_TW.js @@ -779,6 +779,16 @@ export default { 'roleConfig.fetchConfigFailed': '獲取配置失敗', 'roleConfig.fetchModelsFailed': '獲取模型列表失敗', 'roleConfig.fetchPluginsFailed': '獲取插件列表失敗', + 'roleConfig.cannotResumeAudio': '無法恢復播放音訊', + 'roleConfig.selectVoiceFirst': '請先選擇一個音色', + 'roleConfig.audioLoadTimeout': '音訊載入時間較長,請稍後重試', + 'roleConfig.cloneAudioPlayFailed': '克隆音訊播放失敗', + 'roleConfig.cannotPlayCloneAudio': '無法播放克隆音訊', + 'roleConfig.getCloneAudioFailed': '獲取克隆音訊失敗', + 'roleConfig.noPreviewAudio': '該音色暫無可預覽的音訊', + 'roleConfig.audioPlayFailed': '音訊播放失敗', + 'roleConfig.cannotPlayAudio': '無法播放音訊', + 'roleConfig.audioPlayError': '播放音訊過程出錯', // 功能管理對話框文本 'functionDialog.title': '功能管理', @@ -917,6 +927,10 @@ export default { 'ttsModel.selectVoiceToDelete': '請選擇要刪除的音色', 'ttsModel.warning': '警告', 'ttsModel.confirmDeleteVoice': '確定要刪除{count}個音色嗎?', + 'ttsModel.voiceCodeNameLanguageRequired': '音色編碼、音色名稱和語言類型不能為空', + 'ttsModel.deleteCancelled': '刪除已取消', + 'ttsModel.updateSuccess': '更新成功', + 'ttsModel.saveSuccess': '保存成功', 'ttsModel.operationFailed': '操作失敗', 'ttsModel.operationClosed': '操作已關閉', diff --git a/main/manager-web/src/views/roleConfig.vue b/main/manager-web/src/views/roleConfig.vue index 4203ceb9..580b6989 100644 --- a/main/manager-web/src/views/roleConfig.vue +++ b/main/manager-web/src/views/roleConfig.vue @@ -599,21 +599,15 @@ export default { this.voiceOptions = data.data.map((voice) => ({ value: voice.id, label: voice.name, - // 复制音频相关字段,确保hasAudioPreview能检测到 + // 只保留后端实际返回的音频相关字段 voiceDemo: voice.voiceDemo, - demoUrl: voice.demoUrl, - audioUrl: voice.audioUrl, voice_demo: voice.voice_demo, - sample_voice: voice.sample_voice, - referenceAudio: voice.referenceAudio, - // 新增:添加克隆音频相关字段 - cloneAudioUrl: voice.cloneAudioUrl, - hasCloneAudio: voice.hasCloneAudio || false, - // 保存训练状态字段,用于判断是否为克隆音频 + // 使用后端实际返回的 isClone 字段 + isClone: Boolean(voice.isClone), + // 保存训练状态字段 train_status: voice.trainStatus, })); // 保存完整的音色信息,添加调试信息 - console.log("获取到的音色数据:", data.data); this.voiceDetails = data.data.reduce((acc, voice) => { acc[voice.id] = voice; return acc; @@ -746,32 +740,15 @@ export default { }, // 检查是否有音频预览 hasAudioPreview(item) { - // 检查item中是否包含有效的音频URL字段或克隆音频字段 - // 克隆音频通过hasCloneAudio标志或ID格式判断(非TTS开头的ID) - const isCloneAudio = - item.hasCloneAudio || (item.value && !item.value.startsWith("TTS")); - - const audioFields = [ - item.voiceDemo, - item.demoUrl, - item.audioUrl, - item.voice_demo, - item.sample_voice, - item.referenceAudio, - item.cloneAudioUrl, // 克隆音频的URL - ]; - - // 检查是否有任何音频字段是有效的URL - const hasUrlAudio = audioFields.some( - (field) => - field !== undefined && - field !== null && - typeof field === "string" && - field.trim() !== "" && - field.toLowerCase().startsWith("http") - ); - - return hasUrlAudio || isCloneAudio; + // 检查是否为克隆音频 + // 使用后端实际返回的 isClone 字段 + const isCloneAudio = Boolean(item.isClone); + + // 检查是否有有效的音频URL,只使用后端实际返回的字段 + const hasValidAudioUrl = !!((item.voice_demo || item.voiceDemo)?.trim()); + + // 克隆音频始终显示播放按钮,普通音频需要有有效URL才显示 + return isCloneAudio || hasValidAudioUrl; }, // 播放/暂停音频切换 @@ -782,7 +759,7 @@ export default { // 从暂停状态恢复播放 this.currentAudio.play().catch((error) => { console.error("恢复播放失败:", error); - this.$message.warning("无法恢复播放音频"); + this.$message.warning(this.$t('roleConfig.cannotResumeAudio')); }); this.isPaused = false; } else { @@ -803,7 +780,7 @@ export default { const targetVoiceId = voiceId || this.form.ttsVoiceId; if (!targetVoiceId) { - this.$message.warning("请先选择一个音色"); + this.$message.warning(this.$t('roleConfig.selectVoiceFirst')); return; } @@ -830,10 +807,8 @@ export default { let isCloneAudio = false; if (voiceDetail) { - // 首先检查是否是克隆音频(通过ID格式判断,非TTS开头的ID) - isCloneAudio = - voiceDetail.hasCloneAudio || - (voiceDetail.id && !voiceDetail.id.startsWith("TTS")); + // 使用后端实际返回的 isClone 字段判断是否为克隆音频 + isCloneAudio = Boolean(voiceDetail.isClone); console.log( "克隆音频判断结果:", isCloneAudio, @@ -889,7 +864,7 @@ export default { // 设置超时,防止加载过长时间 const timeoutId = setTimeout(() => { if (this.currentAudio && this.playingVoice) { - this.$message.warning("音频加载时间较长,请稍后重试"); + this.$message.warning(this.$t('roleConfig.audioLoadTimeout')); this.playingVoice = false; } }, 10000); // 10秒超时 @@ -898,7 +873,7 @@ export default { this.currentAudio.onerror = () => { clearTimeout(timeoutId); console.error("克隆音频播放错误"); - this.$message.warning("克隆音频播放失败"); + this.$message.warning(this.$t('roleConfig.cloneAudioPlayFailed')); this.playingVoice = false; }; @@ -920,12 +895,12 @@ export default { this.currentAudio.play().catch((error) => { clearTimeout(timeoutId); console.error("播放克隆音频失败:", error); - this.$message.warning("无法播放克隆音频"); + this.$message.warning(this.$t('roleConfig.cannotPlayCloneAudio')); this.playingVoice = false; }); } else { clearTimeout(timeoutId); - this.$message.warning("获取克隆音频失败"); + this.$message.warning(this.$t('roleConfig.getCloneAudioFailed')); this.playingVoice = false; } }); @@ -933,14 +908,10 @@ export default { // 返回,避免继续执行下面的普通音频播放逻辑 return; } else { - // 对于普通音频,尝试各种可能的URL字段 + // 对于普通音频,只使用后端实际返回的字段 audioUrl = voiceDetail.voiceDemo || - voiceDetail.demoUrl || - voiceDetail.audioUrl || - voiceDetail.voice_demo || - voiceDetail.sample_voice || - voiceDetail.cloneAudioUrl; // 克隆音频URL + voiceDetail.voice_demo; } // 如果没有找到,尝试检查是否有URL格式的字段 @@ -965,7 +936,7 @@ export default { if (!audioUrl) { // 如果没有音频URL,显示友好的提示 - this.$message.warning("该音色暂无可预览的音频"); + this.$message.warning(this.$t('roleConfig.noPreviewAudio')); return; } @@ -984,7 +955,7 @@ export default { // 设置超时,防止加载过长时间 const timeoutId = setTimeout(() => { if (this.currentAudio && this.playingVoice) { - this.$message.warning("音频加载时间较长,请稍后重试"); + this.$message.warning(this.$t('roleConfig.audioLoadTimeout')); this.playingVoice = false; } }, 10000); // 10秒超时 @@ -993,7 +964,7 @@ export default { this.currentAudio.onerror = () => { clearTimeout(timeoutId); console.error("音频播放错误"); - this.$message.warning("音频播放失败"); + this.$message.warning(this.$t('roleConfig.audioPlayFailed')); this.playingVoice = false; }; @@ -1011,13 +982,13 @@ export default { this.currentAudio.play().catch((error) => { clearTimeout(timeoutId); console.error("播放失败:", error); - this.$message.warning("无法播放音频"); + this.$message.warning(this.$t('roleConfig.cannotPlayAudio')); this.playingVoice = false; }); } } catch (error) { console.error("播放音频过程出错:", error); - this.$message.error("播放音频过程出错"); + this.$message.error(this.$t('roleConfig.audioPlayError')); this.playingVoice = false; } },