feat: Enhance Reply chain handling for Record components (#8527)

* Enhance Reply chain handling for Record components

Added processing for Record components within Reply chains, including WAV conversion and STT functionality.

* Refactor STT processing for Record components

* Add STT record function for voice-to-text processing

* Update stage.py

* Update stage.py

* Update stage.py
This commit is contained in:
tjc66666666
2026-06-04 09:00:57 +08:00
committed by GitHub
parent 9a648eb426
commit 85ec7a969f

View File

@@ -4,7 +4,7 @@ import traceback
from collections.abc import AsyncGenerator
from astrbot.core import logger
from astrbot.core.message.components import Image, Plain, Record
from astrbot.core.message.components import Image, Plain, Record, Reply
from astrbot.core.platform.astr_message_event import AstrMessageEvent
from astrbot.core.utils.media_utils import ensure_wav
@@ -80,6 +80,24 @@ class PreProcessStage(Stage):
except Exception as e:
logger.warning(f"Voice processing failed: {e}")
# Also process Record components inside Reply chains (wav conversion)
for component in event.get_messages():
if isinstance(component, Reply) and component.chain:
for idx, reply_comp in enumerate(component.chain):
if isinstance(reply_comp, Record):
try:
original_path = await reply_comp.convert_to_file_path()
record_path = await ensure_wav(original_path)
if record_path != original_path:
event.track_temporary_local_file(record_path)
reply_comp.file = record_path
reply_comp.path = record_path
component.chain[idx] = reply_comp
except Exception as e:
logger.warning(
f"Voice processing in reply chain failed: {e}"
)
# STT
if self.stt_settings.get("enable", False):
# TODO: 独立
@@ -90,27 +108,53 @@ class PreProcessStage(Stage):
f"会话 {event.unified_msg_origin} 未配置语音转文本模型。",
)
return
async def _stt_record(record_comp: Record, is_reply: bool = False):
"""对单个 Record 组件执行语音转文本,成功返回 Plain失败返回 None。"""
prefix = "引用消息" if is_reply else ""
try:
path = await record_comp.convert_to_file_path()
except Exception as e:
logger.warning(f"获取{prefix}语音路径失败: {e}")
return None
retry = 5
for i in range(retry):
try:
result = await stt_provider.get_text(audio_url=path)
if result:
suffix = "(引用消息)" if is_reply else ""
logger.info(f"语音转文本{suffix}结果: " + result)
return Plain(result)
break
except FileNotFoundError:
# napcat workaround: file may not be ready immediately
logger.debug(f"文件尚未就绪 ({path}),重试 {i + 1}/{retry}")
await asyncio.sleep(0.5)
continue
except BaseException as e:
logger.error(traceback.format_exc())
suffix = "(引用消息)" if is_reply else ""
logger.error(f"语音转文本{suffix}失败: {e}")
break
return None
message_chain = event.get_messages()
for idx, component in enumerate(message_chain):
if isinstance(component, Record):
path = await component.convert_to_file_path()
retry = 5
for i in range(retry):
try:
result = await stt_provider.get_text(audio_url=path)
if result:
logger.info("语音转文本结果: " + result)
message_chain[idx] = Plain(result)
event.message_str += result
event.message_obj.message_str += result
break
except FileNotFoundError as e:
# napcat workaround
logger.warning(e)
logger.warning(f"重试中: {i + 1}/{retry}")
await asyncio.sleep(0.5)
continue
except BaseException as e:
logger.error(traceback.format_exc())
logger.error(f"语音转文本失败: {e}")
break
plain_comp = await _stt_record(component)
if plain_comp:
message_chain[idx] = plain_comp
event.message_str += plain_comp.text
event.message_obj.message_str += plain_comp.text
# Also STT for Record components inside Reply chains
for component in event.get_messages():
if isinstance(component, Reply) and component.chain:
for idx, reply_comp in enumerate(component.chain):
if isinstance(reply_comp, Record):
plain_comp = await _stt_record(reply_comp, is_reply=True)
if plain_comp:
component.chain[idx] = plain_comp
event.message_str += plain_comp.text
event.message_obj.message_str += plain_comp.text