from __future__ import annotations import asyncio from typing import Any from yuxi.channels.models import DeliveryResult _MAX_CHUNK_COUNT = 5 _LINE_MAX_CHARS = 5000 class ReplyChunker: """LINE 回复分块策略:基于 5 条消息上限实现智能分块。""" def __init__(self, adapter): self._adapter = adapter self._accumulated: str = "" self._sent_count: int = 0 def reset(self) -> None: self._accumulated = "" self._sent_count = 0 async def add_chunk(self, chat_id: str, msg_id: str, chunk: str, finished: bool) -> DeliveryResult: self._accumulated += chunk if finished or len(self._accumulated) >= _LINE_MAX_CHARS or self._sent_count >= _MAX_CHUNK_COUNT: return await self._flush(chat_id) return DeliveryResult(success=True) async def _flush(self, chat_id: str) -> DeliveryResult: if not self._accumulated: return DeliveryResult(success=True) if self._sent_count >= _MAX_CHUNK_COUNT: self._accumulated = "" return DeliveryResult(success=True) result = await self._adapter.send_stream_chunk( chat_id, f"chunk_{self._sent_count}", self._accumulated, finished=self._sent_count >= _MAX_CHUNK_COUNT - 1, ) self._accumulated = "" self._sent_count += 1 return result async def reply_with_chunks( adapter, chat_id: str, content: str, reply_token: str | None = None, loading_seconds: int = 0, ) -> DeliveryResult: """主分块回复入口:如果内容超过 5000 字符或需要多条消息,则分块发送。""" from yuxi.channels.models import ChannelIdentity, ChannelResponse if loading_seconds > 0: await adapter.send_loading_animation(chat_id, loading_seconds) await asyncio.sleep(min(loading_seconds, 2)) if len(content) <= _LINE_MAX_CHARS: identity = ChannelIdentity( channel_id=adapter.channel_id, channel_type=adapter.channel_type, channel_user_id=chat_id, channel_chat_id=chat_id, ) metadata: dict[str, Any] = {} if reply_token: metadata["reply_token"] = reply_token response = ChannelResponse(identity=identity, content=content, metadata=metadata) return await adapter.send(response) last_result = DeliveryResult(success=True) chunk_idx = 0 remaining = content while remaining and chunk_idx < _MAX_CHUNK_COUNT: chunk_end = min(len(remaining), _LINE_MAX_CHARS) chunk = remaining[:chunk_end] remaining = remaining[chunk_end:] finished = not remaining or chunk_idx >= _MAX_CHUNK_COUNT - 1 msg_content = chunk if chunk_idx == 0 and reply_token: from yuxi.channels.models import ChannelIdentity, ChannelResponse identity = ChannelIdentity( channel_id=adapter.channel_id, channel_type=adapter.channel_type, channel_user_id=chat_id, channel_chat_id=chat_id, ) response = ChannelResponse(identity=identity, content=msg_content, metadata={"reply_token": reply_token}) last_result = await adapter.send(response) else: last_result = await adapter.send_stream_chunk(chat_id, f"chunk_{chunk_idx}", msg_content, finished) chunk_idx += 1 if chunk_idx < _MAX_CHUNK_COUNT and remaining: await asyncio.sleep(0.5) return last_result