mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-25 16:43:55 +08:00
update:替换词功能
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import os
|
||||
import asyncio
|
||||
import yaml
|
||||
from collections.abc import Mapping
|
||||
from config.manage_api_client import init_service, get_server_config, get_agent_models
|
||||
from config.manage_api_client import init_service, get_server_config, get_agent_models, get_correct_words
|
||||
|
||||
|
||||
def get_project_dir():
|
||||
@@ -87,7 +88,16 @@ async def get_config_from_api_async(config):
|
||||
|
||||
async def get_private_config_from_api(config, device_id, client_id):
|
||||
"""从Java API获取私有配置"""
|
||||
return await get_agent_models(device_id, client_id, config["selected_module"])
|
||||
results = await asyncio.gather(
|
||||
get_agent_models(device_id, client_id, config["selected_module"]),
|
||||
get_correct_words(device_id),
|
||||
return_exceptions=True
|
||||
)
|
||||
private_config = results[0] if not isinstance(results[0], Exception) else {}
|
||||
correct_words = results[1] if not isinstance(results[1], Exception) else None
|
||||
if correct_words:
|
||||
private_config["correct_words"] = correct_words
|
||||
return private_config
|
||||
|
||||
|
||||
def ensure_directories(config):
|
||||
|
||||
@@ -183,6 +183,18 @@ async def get_agent_models(
|
||||
)
|
||||
|
||||
|
||||
async def get_correct_words(mac_address: str) -> Optional[Dict]:
|
||||
"""获取智能体替换词"""
|
||||
try:
|
||||
return await ManageApiClient._instance._execute_async_request(
|
||||
"POST", "/config/correct-words",
|
||||
json={"macAddress": mac_address}
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"获取替换词失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
async def generate_and_save_chat_summary(session_id: str) -> Optional[Dict]:
|
||||
"""生成并保存聊天记录总结"""
|
||||
try:
|
||||
|
||||
@@ -726,6 +726,13 @@ class ConnectionHandler:
|
||||
if private_config.get("context_providers", None) is not None:
|
||||
self.config["context_providers"] = private_config["context_providers"]
|
||||
|
||||
# 注入替换词到 TTS 模块配置
|
||||
if private_config.get("correct_words", None) is not None:
|
||||
select_tts_module = self.config["selected_module"]["TTS"]
|
||||
self.config["TTS"][select_tts_module]["correct_words"] = private_config[
|
||||
"correct_words"
|
||||
]
|
||||
|
||||
# 使用 run_in_executor 在线程池中执行 initialize_modules,避免阻塞主循环
|
||||
try:
|
||||
modules = await self.loop.run_in_executor(
|
||||
|
||||
@@ -194,6 +194,8 @@ class TTSProvider(TTSProviderBase):
|
||||
|
||||
# 过滤Markdown
|
||||
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
filtered_text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], filtered_text)
|
||||
|
||||
if filtered_text:
|
||||
# 发送continue-task消息
|
||||
@@ -480,6 +482,8 @@ class TTSProvider(TTSProviderBase):
|
||||
|
||||
# 发送文本
|
||||
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
filtered_text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], filtered_text)
|
||||
# 发送continue-task消息
|
||||
continue_task_message = {
|
||||
"header": {
|
||||
|
||||
@@ -295,6 +295,8 @@ class TTSProvider(TTSProviderBase):
|
||||
logger.bind(tag=TAG).warning(f"WebSocket连接不存在,终止发送文本")
|
||||
return
|
||||
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
filtered_text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], filtered_text)
|
||||
if filtered_text:
|
||||
run_request = {
|
||||
"header": {
|
||||
@@ -564,6 +566,8 @@ class TTSProvider(TTSProviderBase):
|
||||
|
||||
# 发送文本合成请求
|
||||
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
filtered_text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], filtered_text)
|
||||
run_request = {
|
||||
"header": {
|
||||
"message_id": uuid.uuid4().hex,
|
||||
|
||||
@@ -45,6 +45,21 @@ class TTSProviderBase(ABC):
|
||||
self.report_on_last = False
|
||||
# sentence_id 到文本的映射,用于流式TTS获取正确的字幕文本
|
||||
self._sentence_text_map = {}
|
||||
# 加载替换词,用于一次性正则替换
|
||||
raw_words = config.get("correct_words", [])
|
||||
self.correct_words = {}
|
||||
for item in raw_words:
|
||||
parts = item.split("|", 1)
|
||||
if len(parts) == 2:
|
||||
self.correct_words[parts[0]] = parts[1]
|
||||
# 构建正则表达式,使用最长匹配优先(排序后转义拼接)
|
||||
if self.correct_words:
|
||||
# 按key长度降序排列,长的先匹配,避免短词部分干扰
|
||||
sorted_keys = sorted(self.correct_words.keys(), key=len, reverse=True)
|
||||
pattern_str = '|'.join(re.escape(k) for k in sorted_keys)
|
||||
self._correct_words_pattern = re.compile(pattern_str)
|
||||
else:
|
||||
self._correct_words_pattern = None
|
||||
|
||||
self.tts_text_buff = []
|
||||
self.punctuations = (
|
||||
@@ -89,7 +104,12 @@ class TTSProviderBase(ABC):
|
||||
self.before_stop_play_files.append((file_audio, text))
|
||||
|
||||
def to_tts_stream(self, text, opus_handler: Callable[[bytes], None] = None) -> None:
|
||||
# 保留原始文本用于显示/上报
|
||||
original_text = text
|
||||
text = MarkdownCleaner.clean_markdown(text)
|
||||
# 使用正则一次性替换,避免重复遍历和部分匹配问题
|
||||
if self._correct_words_pattern:
|
||||
text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], text)
|
||||
max_repeat_time = 5
|
||||
if self.delete_audio_file:
|
||||
# 需要删除文件的直接转为音频数据
|
||||
@@ -97,7 +117,8 @@ class TTSProviderBase(ABC):
|
||||
try:
|
||||
audio_bytes = asyncio.run(self.text_to_speak(text, None))
|
||||
if audio_bytes:
|
||||
self.tts_audio_queue.put((SentenceType.FIRST, None, text, getattr(self, 'current_sentence_id', None)))
|
||||
# 使用原始文本用于显示/上报
|
||||
self.tts_audio_queue.put((SentenceType.FIRST, None, original_text, getattr(self, 'current_sentence_id', None)))
|
||||
audio_bytes_to_data_stream(
|
||||
audio_bytes,
|
||||
file_type=self.audio_file_type,
|
||||
@@ -111,16 +132,16 @@ class TTSProviderBase(ABC):
|
||||
max_repeat_time -= 1
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).warning(
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {text},错误: {e}"
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {original_text},错误: {e}"
|
||||
)
|
||||
max_repeat_time -= 1
|
||||
if max_repeat_time > 0:
|
||||
logger.bind(tag=TAG).info(
|
||||
f"语音生成成功: {text},重试{5 - max_repeat_time}次"
|
||||
f"语音生成成功: {original_text},重试{5 - max_repeat_time}次"
|
||||
)
|
||||
else:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"语音生成失败: {text},请检查网络或服务是否正常"
|
||||
f"语音生成失败: {original_text},请检查网络或服务是否正常"
|
||||
)
|
||||
return None
|
||||
else:
|
||||
@@ -131,7 +152,7 @@ class TTSProviderBase(ABC):
|
||||
asyncio.run(self.text_to_speak(text, tmp_file))
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).warning(
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {text},错误: {e}"
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {original_text},错误: {e}"
|
||||
)
|
||||
# 未执行成功,删除文件
|
||||
if os.path.exists(tmp_file):
|
||||
@@ -140,20 +161,24 @@ class TTSProviderBase(ABC):
|
||||
|
||||
if max_repeat_time > 0:
|
||||
logger.bind(tag=TAG).info(
|
||||
f"语音生成成功: {text}:{tmp_file},重试{5 - max_repeat_time}次"
|
||||
f"语音生成成功: {original_text}:{tmp_file},重试{5 - max_repeat_time}次"
|
||||
)
|
||||
else:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"语音生成失败: {text},请检查网络或服务是否正常"
|
||||
f"语音生成失败: {original_text},请检查网络或服务是否正常"
|
||||
)
|
||||
self.tts_audio_queue.put((SentenceType.FIRST, None, text, getattr(self, 'current_sentence_id', None)))
|
||||
self.tts_audio_queue.put((SentenceType.FIRST, None, original_text, getattr(self, 'current_sentence_id', None)))
|
||||
self._process_audio_file_stream(tmp_file, callback=opus_handler)
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Failed to generate TTS file: {e}")
|
||||
return None
|
||||
|
||||
def to_tts(self, text):
|
||||
# 保留原始文本用于日志/显示
|
||||
original_text = text
|
||||
text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], text)
|
||||
max_repeat_time = 5
|
||||
if self.delete_audio_file:
|
||||
# 需要删除文件的直接转为音频数据
|
||||
@@ -174,16 +199,16 @@ class TTSProviderBase(ABC):
|
||||
max_repeat_time -= 1
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).warning(
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {text},错误: {e}"
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {original_text},错误: {e}"
|
||||
)
|
||||
max_repeat_time -= 1
|
||||
if max_repeat_time > 0:
|
||||
logger.bind(tag=TAG).info(
|
||||
f"语音生成成功: {text},重试{5 - max_repeat_time}次"
|
||||
f"语音生成成功: {original_text},重试{5 - max_repeat_time}次"
|
||||
)
|
||||
else:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"语音生成失败: {text},请检查网络或服务是否正常"
|
||||
f"语音生成失败: {original_text},请检查网络或服务是否正常"
|
||||
)
|
||||
return None
|
||||
else:
|
||||
@@ -194,7 +219,7 @@ class TTSProviderBase(ABC):
|
||||
asyncio.run(self.text_to_speak(text, tmp_file))
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).warning(
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {text},错误: {e}"
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {original_text},错误: {e}"
|
||||
)
|
||||
# 未执行成功,删除文件
|
||||
if os.path.exists(tmp_file):
|
||||
@@ -203,11 +228,11 @@ class TTSProviderBase(ABC):
|
||||
|
||||
if max_repeat_time > 0:
|
||||
logger.bind(tag=TAG).info(
|
||||
f"语音生成成功: {text}:{tmp_file},重试{5 - max_repeat_time}次"
|
||||
f"语音生成成功: {original_text}:{tmp_file},重试{5 - max_repeat_time}次"
|
||||
)
|
||||
else:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"语音生成失败: {text},请检查网络或服务是否正常"
|
||||
f"语音生成失败: {original_text},请检查网络或服务是否正常"
|
||||
)
|
||||
|
||||
return tmp_file
|
||||
|
||||
@@ -370,6 +370,8 @@ class TTSProvider(TTSProviderBase):
|
||||
|
||||
# 过滤Markdown
|
||||
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
filtered_text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], filtered_text)
|
||||
|
||||
if filtered_text:
|
||||
# 发送文本
|
||||
|
||||
@@ -92,22 +92,25 @@ class TTSProvider(TTSProviderBase):
|
||||
def to_tts_single_stream(self, text, is_last=False):
|
||||
try:
|
||||
max_repeat_time = 5
|
||||
original_text = text
|
||||
text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], text)
|
||||
try:
|
||||
asyncio.run(self.text_to_speak(text, is_last))
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).warning(
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {text},错误: {e}"
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {original_text},错误: {e}"
|
||||
)
|
||||
max_repeat_time -= 1
|
||||
|
||||
if max_repeat_time > 0:
|
||||
logger.bind(tag=TAG).info(
|
||||
f"语音生成成功: {text},重试{5 - max_repeat_time}次"
|
||||
f"语音生成成功: {original_text},重试{5 - max_repeat_time}次"
|
||||
)
|
||||
else:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"语音生成失败: {text},请检查网络或服务是否正常"
|
||||
f"语音生成失败: {original_text},请检查网络或服务是否正常"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Failed to generate TTS file: {e}")
|
||||
@@ -203,6 +206,8 @@ class TTSProvider(TTSProviderBase):
|
||||
"""
|
||||
start_time = time.time()
|
||||
text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], text)
|
||||
|
||||
payload = {"text": text, "character": self.voice}
|
||||
|
||||
|
||||
@@ -84,22 +84,25 @@ class TTSProvider(TTSProviderBase):
|
||||
def to_tts_single_stream(self, text, is_last=False):
|
||||
try:
|
||||
max_repeat_time = 5
|
||||
original_text = text
|
||||
text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], text)
|
||||
try:
|
||||
asyncio.run(self.text_to_speak(text, is_last))
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).warning(
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {text},错误: {e}"
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {original_text},错误: {e}"
|
||||
)
|
||||
max_repeat_time -= 1
|
||||
|
||||
if max_repeat_time > 0:
|
||||
logger.bind(tag=TAG).info(
|
||||
f"语音生成成功: {text},重试{5 - max_repeat_time}次"
|
||||
f"语音生成成功: {original_text},重试{5 - max_repeat_time}次"
|
||||
)
|
||||
else:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"语音生成失败: {text},请检查网络或服务是否正常"
|
||||
f"语音生成失败: {original_text},请检查网络或服务是否正常"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Failed to generate TTS file: {e}")
|
||||
@@ -202,6 +205,8 @@ class TTSProvider(TTSProviderBase):
|
||||
"""
|
||||
start_time = time.time()
|
||||
text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], text)
|
||||
|
||||
params = {
|
||||
"tts_text": text,
|
||||
|
||||
@@ -148,22 +148,25 @@ class TTSProvider(TTSProviderBase):
|
||||
def to_tts_single_stream(self, text, is_last=False):
|
||||
try:
|
||||
max_repeat_time = 5
|
||||
original_text = text
|
||||
text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], text)
|
||||
try:
|
||||
asyncio.run(self.text_to_speak(text, is_last))
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).warning(
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {text},错误: {e}"
|
||||
f"语音生成失败{5 - max_repeat_time + 1}次: {original_text},错误: {e}"
|
||||
)
|
||||
max_repeat_time -= 1
|
||||
|
||||
if max_repeat_time > 0:
|
||||
logger.bind(tag=TAG).info(
|
||||
f"语音生成成功: {text},重试{5 - max_repeat_time}次"
|
||||
f"语音生成成功: {original_text},重试{5 - max_repeat_time}次"
|
||||
)
|
||||
else:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"语音生成失败: {text},请检查网络或服务是否正常"
|
||||
f"语音生成失败: {original_text},请检查网络或服务是否正常"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Failed to generate TTS file: {e}")
|
||||
@@ -298,6 +301,8 @@ class TTSProvider(TTSProviderBase):
|
||||
"""
|
||||
start_time = time.time()
|
||||
text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], text)
|
||||
|
||||
payload = {
|
||||
"model": self.model,
|
||||
|
||||
@@ -245,6 +245,8 @@ class TTSProvider(TTSProviderBase):
|
||||
return
|
||||
|
||||
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
filtered_text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], filtered_text)
|
||||
if filtered_text:
|
||||
# 发送文本合成请求
|
||||
run_request = self._build_base_request(status=1,text=filtered_text)
|
||||
@@ -441,6 +443,8 @@ class TTSProvider(TTSProviderBase):
|
||||
|
||||
try:
|
||||
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||
if self._correct_words_pattern:
|
||||
filtered_text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], filtered_text)
|
||||
|
||||
text_request = self._build_base_request(status=2,text=filtered_text)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user