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; } }, diff --git a/main/xiaozhi-server/core/handle/helloHandle.py b/main/xiaozhi-server/core/handle/helloHandle.py index a4220d5a..129b0aa7 100644 --- a/main/xiaozhi-server/core/handle/helloHandle.py +++ b/main/xiaozhi-server/core/handle/helloHandle.py @@ -1,5 +1,6 @@ import time import json +import uuid import random import asyncio from core.utils.dialogue import Message @@ -105,6 +106,9 @@ async def checkWakeupWords(conn, text): # 播放唤醒词回复 conn.client_abort = False + # 将唤醒词回复视为新会话,生成新的 sentence_id,确保流控器重置 + conn.sentence_id = str(uuid.uuid4().hex) + conn.logger.bind(tag=TAG).info(f"播放唤醒词回复: {response.get('text')}") await sendAudioMessage(conn, SentenceType.FIRST, opus_packets, response.get("text")) await sendAudioMessage(conn, SentenceType.LAST, [], None) diff --git a/main/xiaozhi-server/core/handle/sendAudioHandle.py b/main/xiaozhi-server/core/handle/sendAudioHandle.py index 8198dfb0..323056d1 100644 --- a/main/xiaozhi-server/core/handle/sendAudioHandle.py +++ b/main/xiaozhi-server/core/handle/sendAudioHandle.py @@ -133,12 +133,27 @@ def _get_or_create_rate_controller(conn, frame_duration, is_single_packet): Returns: (rate_controller, flow_control) """ - # 判断是否需要重置:单包模式且 sentence_id 变化,或者控制器不存在 - need_reset = ( - is_single_packet - and getattr(conn, "audio_flow_control", {}).get("sentence_id") - != conn.sentence_id - ) or not hasattr(conn, "audio_rate_controller") + # 检查是否需要重置控制器 + need_reset = False + + if not hasattr(conn, "audio_rate_controller"): + # 控制器不存在,需要创建 + need_reset = True + else: + rate_controller = conn.audio_rate_controller + + # 后台发送任务已停止, 则需要重置 + if ( + not rate_controller.pending_send_task + or rate_controller.pending_send_task.done() + ): + need_reset = True + # 当sentence_id 变化,需要重置 + elif ( + getattr(conn, "audio_flow_control", {}).get("sentence_id") + != conn.sentence_id + ): + need_reset = True if need_reset: # 创建或获取 rate_controller diff --git a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py index 76f84bb7..96387ec4 100644 --- a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py +++ b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py @@ -46,7 +46,7 @@ class MemoryProvider(MemoryProviderBase): try: # Check if user profile mode is enabled self.enable_user_profile = config.get("enable_user_profile", False) - + # Get configuration parameters database_provider = config.get("database_provider", "sqlite") llm_provider = config.get("llm_provider", "qwen") @@ -134,12 +134,12 @@ class MemoryProvider(MemoryProviderBase): memory_mode = "AsyncMemory (普通记忆模式)" self.use_powermem = True - + logger.bind(tag=TAG).info( f"PowerMem initialized successfully: mode={memory_mode}, " f"database={database_provider}, llm={llm_provider}, embedding={embedding_provider}" ) - + except ImportError as e: logger.bind(tag=TAG).error( f"PowerMem not installed. Please install with: pip install powermem. Error: {e}" @@ -150,13 +150,15 @@ class MemoryProvider(MemoryProviderBase): logger.bind(tag=TAG).debug(f"Detailed error: {traceback.format_exc()}") self.use_powermem = False - async def save_memory(self, msgs): + async def save_memory(self, msgs, session_id=None): """ Save conversation messages to PowerMem. Args: msgs: List of message objects with 'role' and 'content' attributes + session_id: Session identifier (optional, for compatibility) + Returns: Result from PowerMem API or None if failed """ @@ -186,13 +188,13 @@ class MemoryProvider(MemoryProviderBase): result = await result logger.bind(tag=TAG).debug(f"Save memory result: {result}") - + # Cache user profile if UserMemory mode and profile was extracted if self.enable_user_profile and result: if result.get('profile_extracted'): self.last_profile_content = result.get('profile_content', '') logger.bind(tag=TAG).debug(f"User profile extracted: {self.last_profile_content}") - + return result except Exception as e: @@ -306,3 +308,6 @@ class MemoryProvider(MemoryProviderBase): return "" + +# Register the memory provider instance +powermem = MemoryProvider({}) diff --git a/main/xiaozhi-server/core/providers/tts/alibl_stream.py b/main/xiaozhi-server/core/providers/tts/alibl_stream.py index 3130cb57..42658008 100644 --- a/main/xiaozhi-server/core/providers/tts/alibl_stream.py +++ b/main/xiaozhi-server/core/providers/tts/alibl_stream.py @@ -185,20 +185,21 @@ class TTSProvider(TTSProviderBase): # 过滤Markdown filtered_text = MarkdownCleaner.clean_markdown(text) - # 发送continue-task消息 - continue_task_message = { - "header": { - "action": "continue-task", - "task_id": self.conn.sentence_id, - "streaming": "duplex", - }, - "payload": {"input": {"text": filtered_text}}, - } - - await self.ws.send(json.dumps(continue_task_message)) - self.last_active_time = time.time() - logger.bind(tag=TAG).debug(f"已发送文本: {filtered_text}") + if filtered_text: + # 发送continue-task消息 + continue_task_message = { + "header": { + "action": "continue-task", + "task_id": self.conn.sentence_id, + "streaming": "duplex", + }, + "payload": {"input": {"text": filtered_text}}, + } + await self.ws.send(json.dumps(continue_task_message)) + self.last_active_time = time.time() + logger.bind(tag=TAG).debug(f"已发送文本: {filtered_text}") + return except Exception as e: logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}") if self.ws: diff --git a/main/xiaozhi-server/core/providers/tts/aliyun_stream.py b/main/xiaozhi-server/core/providers/tts/aliyun_stream.py index bcbf2b40..52284466 100644 --- a/main/xiaozhi-server/core/providers/tts/aliyun_stream.py +++ b/main/xiaozhi-server/core/providers/tts/aliyun_stream.py @@ -288,18 +288,19 @@ class TTSProvider(TTSProviderBase): logger.bind(tag=TAG).warning(f"WebSocket连接不存在,终止发送文本") return filtered_text = MarkdownCleaner.clean_markdown(text) - run_request = { - "header": { - "message_id": uuid.uuid4().hex, - "task_id": self.task_id, - "namespace": "FlowingSpeechSynthesizer", - "name": "RunSynthesis", - "appkey": self.appkey, - }, - "payload": {"text": filtered_text}, - } - await self.ws.send(json.dumps(run_request)) - self.last_active_time = time.time() + if filtered_text: + run_request = { + "header": { + "message_id": uuid.uuid4().hex, + "task_id": self.task_id, + "namespace": "FlowingSpeechSynthesizer", + "name": "RunSynthesis", + "appkey": self.appkey, + }, + "payload": {"text": filtered_text}, + } + await self.ws.send(json.dumps(run_request)) + self.last_active_time = time.time() return except Exception as e: diff --git a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py index 38be1560..d7829e35 100644 --- a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py +++ b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py @@ -4,15 +4,15 @@ import json import queue import asyncio import traceback -from typing import Callable, Any import websockets + +from typing import Callable, Any from core.utils.tts import MarkdownCleaner from config.logger import setup_logging from core.utils import opus_encoder_utils from core.utils.util import check_model_key from core.providers.tts.base import TTSProviderBase from core.providers.tts.dto.dto import SentenceType, ContentType, InterfaceType -from asyncio import Task TAG = __name__ @@ -340,8 +340,9 @@ class TTSProvider(TTSProviderBase): # 过滤Markdown filtered_text = MarkdownCleaner.clean_markdown(text) - # 发送文本 - await self.send_text(self.voice, filtered_text, self.conn.sentence_id) + if filtered_text: + # 发送文本 + await self.send_text(self.voice, filtered_text, self.conn.sentence_id) return except Exception as e: logger.bind(tag=TAG).error(f"发送TTS文本失败: {str(e)}") diff --git a/main/xiaozhi-server/core/providers/tts/xunfei_stream.py b/main/xiaozhi-server/core/providers/tts/xunfei_stream.py index d06c9330..dc8d833f 100644 --- a/main/xiaozhi-server/core/providers/tts/xunfei_stream.py +++ b/main/xiaozhi-server/core/providers/tts/xunfei_stream.py @@ -237,10 +237,10 @@ class TTSProvider(TTSProviderBase): return filtered_text = MarkdownCleaner.clean_markdown(text) - - # 发送文本合成请求 - run_request = self._build_base_request(status=1,text=filtered_text) - await self.ws.send(json.dumps(run_request)) + if filtered_text: + # 发送文本合成请求 + run_request = self._build_base_request(status=1,text=filtered_text) + await self.ws.send(json.dumps(run_request)) return except Exception as e: diff --git a/main/xiaozhi-server/core/utils/textUtils.py b/main/xiaozhi-server/core/utils/textUtils.py index 47677b99..2f6afa8c 100644 --- a/main/xiaozhi-server/core/utils/textUtils.py +++ b/main/xiaozhi-server/core/utils/textUtils.py @@ -2,7 +2,7 @@ import json TAG = __name__ EMOJI_MAP = { - "😂": "laughing", + "😂": "funny", "😭": "crying", "😠": "angry", "😔": "sad", diff --git a/main/xiaozhi-server/core/utils/tts.py b/main/xiaozhi-server/core/utils/tts.py index 8b68d195..6727f6de 100644 --- a/main/xiaozhi-server/core/utils/tts.py +++ b/main/xiaozhi-server/core/utils/tts.py @@ -1,9 +1,11 @@ import os import re import sys -from config.logger import setup_logging import importlib +from config.logger import setup_logging +from core.utils.textUtils import check_emoji + logger = setup_logging() punctuation_set = { @@ -135,4 +137,8 @@ class MarkdownCleaner: for regex, replacement in MarkdownCleaner.REGEXES: text = regex.sub(replacement, text) + + # 去除emoji表情 + text = check_emoji(text) + return text.strip() \ No newline at end of file