"""审批展示模板 — 可自定义的审批信息展示格式""" from __future__ import annotations import html as _html import logging import re from dataclasses import dataclass from enum import StrEnum from yuxi.channel.protocols import ApprovalAction, ApprovalDecision, ApprovalRequest, ApprovalResult logger = logging.getLogger(__name__) _MD_ESCAPE_RE = re.compile(r"([\\`*_{}\[\]()#+\-.!])") class TemplateStyle(StrEnum): PLAIN = "plain" MARKDOWN = "markdown" HTML = "html" @dataclass class ApprovalTemplateConfig: title: str = "审批请求" show_request_id: bool = True show_action: bool = True show_description: bool = True show_initiator: bool = True show_expiry: bool = True style: TemplateStyle = TemplateStyle.PLAIN approve_label: str = "批准" deny_label: str = "拒绝" timeout_minutes: int = 5 _DEFAULT_CONFIG = ApprovalTemplateConfig() class ApprovalTemplateRenderer: def __init__(self, config: ApprovalTemplateConfig | None = None): self._config = config or _DEFAULT_CONFIG @property def config(self) -> ApprovalTemplateConfig: return self._config def render_request(self, request: ApprovalRequest) -> str: cfg = self._config lines: list[str] = [f"🔐 {self._bold(cfg.title)}"] if cfg.show_request_id: lines.append(f"请求 ID: {self._code(request.id)}") if cfg.show_action: lines.append(f"操作类型: {_action_label(request.action)}") if cfg.show_description: lines.append(f"描述: {self._escape(request.description)}") if cfg.show_initiator: lines.append(f"发起人: {self._escape(request.initiator_peer_id)}") if cfg.show_expiry and cfg.timeout_minutes > 0: lines.append(f"请在 {cfg.timeout_minutes} 分钟内回复") lines.append("") lines.append(f"回复「{self._bold(cfg.approve_label)}」或「{self._bold(cfg.deny_label)}」做出决定") return "\n".join(lines) def render_result(self, result: ApprovalResult, request: ApprovalRequest | None = None) -> str: if result.decision == ApprovalDecision.APPROVED: icon = "✅" text = "审批已通过" elif result.decision == ApprovalDecision.DENIED: icon = "❌" text = "审批已拒绝" elif result.decision == ApprovalDecision.CANCELLED: icon = "🚫" text = "审批已取消" elif result.decision == ApprovalDecision.EXPIRED: icon = "⏰" text = "审批已过期" else: icon = "❓" text = "审批状态未知" lines = [f"{icon} {self._bold(text)}"] lines.append(f"请求 ID: {self._code(result.request_id)}") if result.reason: lines.append(f"原因: {self._escape(result.reason)}") if result.decided_by: lines.append(f"决定人: {self._escape(result.decided_by)}") return "\n".join(lines) def render_expiry_warning(self, request: ApprovalRequest, remaining_seconds: int) -> str: request_id = self._code(request.id) if remaining_seconds <= 0: return f"⏳ 审批请求 {request_id} 已过期。" if remaining_seconds < 60: return f"⏳ 审批请求 {request_id} 将在 {remaining_seconds} 秒后过期,请尽快处理。" minutes = remaining_seconds // 60 return f"⏳ 审批请求 {request_id} 将在 {minutes} 分钟后过期,请尽快处理。" def _bold(self, text: str) -> str: style = self._config.style if style == TemplateStyle.MARKDOWN: return f"**{text}**" elif style == TemplateStyle.HTML: return f"{_html.escape(text)}" return text def _code(self, text: str) -> str: style = self._config.style if style == TemplateStyle.MARKDOWN: return f"`{text}`" elif style == TemplateStyle.HTML: return f"{_html.escape(text)}" return f"'{text}'" def _escape(self, text: str) -> str: style = self._config.style if style == TemplateStyle.HTML: return _html.escape(text) elif style == TemplateStyle.MARKDOWN: return _MD_ESCAPE_RE.sub(r"\\\1", text) return text def _action_label(action: ApprovalAction) -> str: labels: dict[ApprovalAction, str] = { ApprovalAction.APPROVE_EXEC: "执行操作", ApprovalAction.APPROVE_READ: "读取操作", } return labels.get(action, action.value) _renderer_cache: dict[str, ApprovalTemplateRenderer] = {} def get_template_renderer(style: str = "default") -> ApprovalTemplateRenderer: if style not in _renderer_cache: if style == "default": _renderer_cache[style] = ApprovalTemplateRenderer() elif style == "compact": _renderer_cache[style] = ApprovalTemplateRenderer( ApprovalTemplateConfig( title="审批", show_request_id=False, show_action=False, show_initiator=False, show_expiry=False, ) ) elif style == "detailed": _renderer_cache[style] = ApprovalTemplateRenderer( ApprovalTemplateConfig( title="操作审批请求", style=TemplateStyle.MARKDOWN, timeout_minutes=10, ) ) else: logger.warning("未知模板风格 '%s',使用默认配置", style) _renderer_cache[style] = ApprovalTemplateRenderer() return _renderer_cache[style]