diff --git a/astrbot/core/pipeline/respond/stage.py b/astrbot/core/pipeline/respond/stage.py index 888fb65a4b..adb7dde0cc 100644 --- a/astrbot/core/pipeline/respond/stage.py +++ b/astrbot/core/pipeline/respond/stage.py @@ -10,6 +10,7 @@ from astrbot.core.platform.astr_message_event import AstrMessageEvent from astrbot.core.star.star_handler import EventType from astrbot.core.utils.path_util import path_Mapping +from astrbot.core.utils.text_utils import calculate_word_count from ..context import PipelineContext, call_event_hook from ..stage import Stage, register_stage @@ -88,12 +89,7 @@ async def initialize(self, ctx: PipelineContext) -> None: logger.info(f"分段回复间隔时间:{self.interval}") async def _word_cnt(self, text: str) -> int: - """分段回复 统计字数""" - if all(ord(c) < 128 for c in text): - word_count = len(text.split()) - else: - word_count = len([c for c in text if c.isalnum()]) - return word_count + return calculate_word_count(text) async def _calc_comp_interval(self, comp: BaseMessageComponent) -> float: """分段回复 计算间隔时间""" diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index 5956c8b5ef..527eee84e3 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -13,6 +13,7 @@ from astrbot.core.star.session_llm_manager import SessionServiceManager from astrbot.core.star.star import star_map from astrbot.core.star.star_handler import EventType, star_handlers_registry +from astrbot.core.utils.text_utils import calculate_word_count from ..context import PipelineContext from ..stage import Stage, register_stage, registered_stages @@ -213,7 +214,8 @@ async def process( new_chain = [] for comp in result.chain: if isinstance(comp, Plain): - if len(comp.text) > self.words_count_threshold: + word_count = calculate_word_count(comp.text) + if word_count > self.words_count_threshold: # 不分段回复 new_chain.append(comp) continue @@ -296,7 +298,7 @@ async def process( if should_tts and tts_provider: new_chain = [] for comp in result.chain: - if isinstance(comp, Plain) and len(comp.text) > 1: + if isinstance(comp, Plain) and calculate_word_count(comp.text) > 1: try: logger.info(f"TTS 请求: {comp.text}") audio_path = await tts_provider.get_audio(comp.text) @@ -356,7 +358,8 @@ async def process( else: break plain_str = "".join(parts) - if plain_str and len(plain_str) > self.t2i_word_threshold: + word_count = calculate_word_count(plain_str) + if plain_str and word_count > self.t2i_word_threshold: render_start = time.time() try: url = await html_renderer.render_t2i( @@ -391,7 +394,7 @@ async def process( word_cnt = 0 for comp in result.chain: if isinstance(comp, Plain): - word_cnt += len(comp.text) + word_cnt += calculate_word_count(comp.text) if word_cnt > self.forward_threshold: node = Node( uin=event.get_self_id(), diff --git a/astrbot/core/utils/text_utils.py b/astrbot/core/utils/text_utils.py new file mode 100644 index 0000000000..8920a59232 --- /dev/null +++ b/astrbot/core/utils/text_utils.py @@ -0,0 +1,21 @@ +import re + + +def calculate_word_count(text: str) -> int: + """ + 将不同语言分开计算 + - 中文/日语: 按字数计算 + - 其他语言: 按空格分割计算 + """ + # \u4e00-\u9fff : CJK Unified Ideographs (Chinese Hanzi & Japanese Kanji) + # \u3040-\u309f : Japanese Hiragana + # \u30a0-\u30ff : Japanese Katakana + no_space_pattern = r"[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]" + + char_count = len(re.findall(no_space_pattern, text)) + + text_remaining = re.sub(no_space_pattern, " ", text) + + spaced_words = len(text_remaining.split()) + + return char_count + spaced_words