mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
refactor(messaging): 拆分用户交互模块到 application/messaging 层
- 新增 application/messaging 交互层:router.py 统一会话优先级与回调分发, site/subscribe/skill/media/plugin 各交互状态与视图从 Chain 迁出 - MessageChain 改为通过 InteractionRouter 派发文本会话与按钮回调, 新增结构化 callback_data 通道(兼容 CALLBACK: 文本前缀) - Transfer 失败重试/AI 接管回调归入 TransferChain - MediaInteractionChain 拆出为 app/chain/interaction.py(旧路径保留兼容别名) - WebAgent Endpoint 去重,统一使用 agent.py 回调协议函数 - 删除 app/chain/skills.py(交互逻辑并入 SkillInteractionHandler) - 同步更新架构文档与测试,全量 4476 通过
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
交互路由层:统一选择活动文本会话,并按固定顺序派发按钮回调。
|
||||
|
||||
文本会话候选覆盖 Site、Subscribe、Skill、Media 四类,
|
||||
按会话创建时间选择最近激活的一条,避免旧会话抢占新会话的输入。
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Optional, Sequence, Union
|
||||
|
||||
from app.application.messaging.interaction import (
|
||||
InteractionContext,
|
||||
InteractionDispatch,
|
||||
)
|
||||
from app.application.messaging.media import media_interaction_manager
|
||||
from app.application.messaging.site import site_interaction_manager
|
||||
from app.application.messaging.skill import skill_interaction_manager
|
||||
from app.application.messaging.subscribe import subscribe_interaction_manager
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SessionRoute:
|
||||
"""描述一种可继续接收文本的交互会话。"""
|
||||
|
||||
# 会话名称,与对应 Slash 命令一致
|
||||
name: str
|
||||
# 返回用户当前待处理会话对象(含 created_at),无会话返回 None
|
||||
get_pending: Callable[[Union[str, int]], Optional[Any]]
|
||||
# 将一条文本派发给该会话,返回是否已消费
|
||||
handle_text: Callable[[InteractionContext, str], bool]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CallbackRoute:
|
||||
"""描述一种按钮回调的匹配和处理方式。"""
|
||||
|
||||
# 路由名称,用于日志和排查
|
||||
name: str
|
||||
# 按回调内容判断是否归本路由处理
|
||||
matches: Callable[[str], bool]
|
||||
# 执行回调处理并返回派发结果
|
||||
dispatch: Callable[[str, InteractionContext], InteractionDispatch]
|
||||
|
||||
|
||||
class InteractionRouter:
|
||||
"""统一选择活动文本会话并按顺序派发按钮回调。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session_routes: Sequence[SessionRoute],
|
||||
callback_routes: Sequence[CallbackRoute],
|
||||
):
|
||||
"""按注册顺序保存会话路由和回调路由,回调顺序即优先级。"""
|
||||
self._session_routes = list(session_routes)
|
||||
self._callback_routes = list(callback_routes)
|
||||
|
||||
def latest_session(self, user_id: Union[str, int]) -> Optional[SessionRoute]:
|
||||
"""返回最近创建的待处理文本会话路由,无会话返回 None。"""
|
||||
best_route: Optional[SessionRoute] = None
|
||||
best_created_at = None
|
||||
for route in self._session_routes:
|
||||
pending = route.get_pending(user_id)
|
||||
if pending is None:
|
||||
continue
|
||||
created_at = getattr(pending, "created_at", None)
|
||||
# 缺少时间戳的会话视为最早,保证有时间戳的新会话优先
|
||||
if best_route is None or (
|
||||
created_at is not None
|
||||
and (best_created_at is None or created_at > best_created_at)
|
||||
):
|
||||
best_route = route
|
||||
best_created_at = created_at
|
||||
return best_route
|
||||
|
||||
def has_pending(self, user_id: Union[str, int]) -> bool:
|
||||
"""判断用户是否存在任意待处理文本会话。"""
|
||||
return any(
|
||||
route.get_pending(user_id) is not None
|
||||
for route in self._session_routes
|
||||
)
|
||||
|
||||
def dispatch_active_text(
|
||||
self,
|
||||
context: InteractionContext,
|
||||
text: str,
|
||||
) -> bool:
|
||||
"""把文本派发给最近活动的会话,返回是否被消费。"""
|
||||
route = self.latest_session(context.user_id)
|
||||
if route is None:
|
||||
return False
|
||||
return route.handle_text(context, text)
|
||||
|
||||
def dispatch_callback(
|
||||
self,
|
||||
context: InteractionContext,
|
||||
callback_data: str,
|
||||
) -> InteractionDispatch:
|
||||
"""按注册顺序匹配并派发按钮回调,均不匹配时返回未处理。
|
||||
|
||||
匹配到的路由未消费回调(handled=False)时继续尝试后续路由,
|
||||
与既有顺序式派发的行为保持一致。
|
||||
"""
|
||||
for route in self._callback_routes:
|
||||
if route.matches(callback_data):
|
||||
result = route.dispatch(callback_data, context)
|
||||
if result.handled:
|
||||
return result
|
||||
return InteractionDispatch(handled=False)
|
||||
|
||||
|
||||
def has_pending_interaction(user_id: Union[str, int]) -> bool:
|
||||
"""供 WebAgent 判断用户是否处于传统交互会话。"""
|
||||
return any(
|
||||
manager.get_by_user(user_id) is not None
|
||||
for manager in (
|
||||
site_interaction_manager,
|
||||
subscribe_interaction_manager,
|
||||
skill_interaction_manager,
|
||||
media_interaction_manager,
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user