update:替换词功能

This commit is contained in:
rainv123
2026-04-24 10:06:26 +08:00
parent 550397de98
commit 606a42cfb2
44 changed files with 1030 additions and 37 deletions
+12 -2
View File
@@ -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:
+7
View File
@@ -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,
+39 -14
View File
@@ -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)