165 lines
5.6 KiB
Python
165 lines
5.6 KiB
Python
"""审批展示模板 — 可自定义的审批信息展示格式"""
|
|
|
|
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"<b>{_html.escape(text)}</b>"
|
|
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"<code>{_html.escape(text)}</code>"
|
|
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]
|