2026-07-10 09:14:06 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from typing import Any
|
2026-07-03 08:53:56 +08:00
|
|
|
|
|
|
|
|
|
|
from nonebot_plugin_alconna import UniMessage
|
|
|
|
|
|
|
|
|
|
|
|
from zhenxun.services.ai.core.stream_events import ToolStreamChunkEvent, UserCustomEvent
|
2026-07-10 09:14:06 +08:00
|
|
|
|
from zhenxun.utils.message import MessageUtils
|
|
|
|
|
|
from zhenxun.utils.platform import PlatformUtils
|
2026-07-03 08:53:56 +08:00
|
|
|
|
|
2026-07-10 09:14:06 +08:00
|
|
|
|
from .context import RunContext
|
2026-07-03 08:53:56 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UIController:
|
|
|
|
|
|
"""前端 UI 富交互流式控制器 (按需生成模式)"""
|
|
|
|
|
|
|
2026-07-10 09:14:06 +08:00
|
|
|
|
def __init__(self, context: RunContext):
|
2026-07-03 08:53:56 +08:00
|
|
|
|
self.context = context
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def tool_name(self) -> str:
|
|
|
|
|
|
"""从上下文中动态获取当前调用的工具名"""
|
|
|
|
|
|
return getattr(self.context.call, "tool_name", "UnknownTool")
|
|
|
|
|
|
|
|
|
|
|
|
async def send_text(self, text: str, status: str = "running") -> None:
|
|
|
|
|
|
"""向前端流式反馈执行进度文本"""
|
2026-07-10 09:14:06 +08:00
|
|
|
|
await self.context.run.emit(
|
|
|
|
|
|
ToolStreamChunkEvent(
|
|
|
|
|
|
tool_name=self.tool_name, content=text, metadata={"status": status}
|
2026-07-03 08:53:56 +08:00
|
|
|
|
)
|
2026-07-10 09:14:06 +08:00
|
|
|
|
)
|
2026-07-03 08:53:56 +08:00
|
|
|
|
|
|
|
|
|
|
async def send_image(self, image: bytes | str) -> None:
|
|
|
|
|
|
"""向前端发送富文本图片气泡(字节流或 URL)"""
|
2026-07-10 09:14:06 +08:00
|
|
|
|
display_msg = UniMessage()
|
|
|
|
|
|
if isinstance(image, bytes):
|
|
|
|
|
|
display_msg = display_msg.image(raw=image)
|
|
|
|
|
|
else:
|
|
|
|
|
|
display_msg = display_msg.image(url=image)
|
|
|
|
|
|
await self.context.run.emit(UserCustomEvent(display=display_msg))
|
2026-07-03 08:53:56 +08:00
|
|
|
|
|
|
|
|
|
|
async def send_display(self, display: Any) -> None:
|
|
|
|
|
|
"""向前端发送任意展示载体"""
|
2026-07-10 09:14:06 +08:00
|
|
|
|
if display is not None:
|
|
|
|
|
|
await self.context.run.emit(UserCustomEvent(display=display))
|
2026-07-03 08:53:56 +08:00
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
async def handle_control_flow_exit_display(
|
2026-07-10 09:14:06 +08:00
|
|
|
|
e: BaseException, context: RunContext | None, reply_to: bool = False
|
2026-07-03 08:53:56 +08:00
|
|
|
|
) -> None:
|
|
|
|
|
|
"""统一处理 ControlFlowExit 异常带来的 UI 反馈逻辑"""
|
|
|
|
|
|
from zhenxun.services.ai.core.exceptions import ControlFlowExit
|
|
|
|
|
|
|
|
|
|
|
|
if not isinstance(e, ControlFlowExit):
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
display_msg = getattr(e, "display", None) or getattr(e, "display_content", None)
|
|
|
|
|
|
if not display_msg:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
bot = context.get_bot() if context else None
|
|
|
|
|
|
event = context.get_event() if context else None
|
|
|
|
|
|
|
2026-07-10 09:14:06 +08:00
|
|
|
|
if bot:
|
|
|
|
|
|
msg_to_send = (
|
|
|
|
|
|
display_msg
|
|
|
|
|
|
if isinstance(display_msg, UniMessage)
|
|
|
|
|
|
else MessageUtils.build_message(str(display_msg))
|
|
|
|
|
|
)
|
|
|
|
|
|
if event:
|
|
|
|
|
|
await msg_to_send.send(event, bot=bot, reply_to=reply_to)
|
|
|
|
|
|
elif context:
|
|
|
|
|
|
target = PlatformUtils.get_target(
|
|
|
|
|
|
user_id=context.get_user_id(), group_id=context.get_group_id()
|
|
|
|
|
|
)
|
|
|
|
|
|
if target:
|
|
|
|
|
|
await msg_to_send.send(target=target, bot=bot)
|
2026-07-03 08:53:56 +08:00
|
|
|
|
else:
|
|
|
|
|
|
await MessageUtils.build_message(str(display_msg)).send(
|
|
|
|
|
|
reply_to=reply_to
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|