Merge remote-tracking branch 'upstream/py_test_Memory_powermem' into add-powermem

# Conflicts:
#	main/xiaozhi-server/core/providers/memory/powermem/powermem.py
This commit is contained in:
渠磊
2026-01-09 12:32:59 +08:00
17 changed files with 193 additions and 104 deletions
@@ -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;
}
}
@@ -136,6 +136,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
.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<TimbreDao, TimbreEntity>
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<TimbreDao, TimbreEntity>
TimbreEntity entity = list.get(0);
VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName());
dto.setVoiceDemo(entity.getVoiceDemo());
dto.setIsClone(false); // 设置为普通音色
return dto;
}
}
+14
View File
@@ -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',
+14
View File
@@ -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',
+14
View File
@@ -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',
+14
View File
@@ -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': '固件管理',
+14
View File
@@ -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': '操作已關閉',
+28 -57
View File
@@ -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;
}
},
@@ -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)
@@ -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
@@ -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({})
@@ -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:
@@ -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:
@@ -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)}")
@@ -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:
+1 -1
View File
@@ -2,7 +2,7 @@ import json
TAG = __name__
EMOJI_MAP = {
"😂": "laughing",
"😂": "funny",
"😭": "crying",
"😠": "angry",
"😔": "sad",
+7 -1
View File
@@ -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()