Files
2026-07-01 17:56:31 +08:00

127 lines
3.9 KiB
Python

"""Shared render helpers for the CLI board widgets."""
from __future__ import annotations
from datetime import datetime
from typing import Any
from rich.text import Text
STATUS_STYLES = {
"pending": "bold black on #64748b",
"running": "bold black on #22c55e",
"idle": "bold black on #38bdf8",
"blocked": "bold black on #f59e0b",
"awaiting_peer": "bold black on #f59e0b",
"awaiting_review": "bold black on #f59e0b",
"done": "bold black on #10b981",
"failed": "bold white on #ef4444",
"cancelled": "bold white on #9333ea",
"reflecting": "bold black on #a78bfa",
"tool_active": "bold black on #fb7185",
"info": "bold black on #38bdf8",
"warn": "bold black on #f59e0b",
"error": "bold white on #ef4444",
}
PRIORITY_STYLES = {
"urgent": "bold white on #dc2626",
"high": "bold black on #fb7185",
"medium": "bold black on #fbbf24",
"low": "bold black on #60a5fa",
}
ROLE_STYLES = {
"user": "bold #38bdf8",
"assistant": "bold #22c55e",
"system": "bold #f59e0b",
"subagent": "bold #c084fc",
}
def truncate_text(value: str | None, limit: int) -> str:
text = " ".join(str(value or "").split())
if len(text) <= limit:
return text
return f"{text[: max(0, limit - 1)].rstrip()}…"
def humanize_age(timestamp: float | None, *, now: float | None = None) -> str:
if not timestamp:
return "n/a"
current = float(now if now is not None else datetime.now().timestamp())
seconds = max(0, int(current - float(timestamp)))
if seconds < 60:
return f"{seconds}s"
if seconds < 3600:
return f"{seconds // 60}m"
if seconds < 86400:
return f"{seconds // 3600}h"
return f"{seconds // 86400}d"
def format_clock(timestamp: float | None) -> str:
if not timestamp:
return "--:--"
return datetime.fromtimestamp(float(timestamp)).strftime("%H:%M")
def badge(label: str, style: str, *, prefix: str = "") -> Text:
text = Text()
if prefix:
text.append(prefix, style="dim")
text.append(f" {label} ", style=style)
return text
def status_style(status: str | None) -> str:
return STATUS_STYLES.get(str(status or "").strip().lower(), "bold black on #475569")
def priority_style(priority: str | None) -> str:
return PRIORITY_STYLES.get(str(priority or "").strip().lower(), "bold black on #475569")
def role_style(role: str | None) -> str:
return ROLE_STYLES.get(str(role or "").strip().lower(), "bold white")
def adaptive_summary(metadata: dict[str, Any] | None) -> dict[str, Any]:
meta = dict(metadata or {})
adaptive = dict(meta.get("adaptive", {}) or {})
if not adaptive:
return {
"state": "",
"blocked_reason": "",
"gate_owner": "",
"missing_signals": [],
"confidence_label": "",
"invalidated": False,
}
work_item_profile = dict(adaptive.get("work_item_profile", {}) or {})
missing_signals = [
str(item.get("name", "") or "").strip()
for item in list(adaptive.get("signals", []) or [])
if isinstance(item, dict)
and bool(item.get("required", True))
and not bool(item.get("satisfied", False))
and str(item.get("name", "") or "").strip()
]
confidence = adaptive.get("confidence")
try:
confidence_value = float(confidence)
except (TypeError, ValueError):
confidence_value = None
return {
"state": str(adaptive.get("normalized_state", "") or "").strip(),
"blocked_reason": str(adaptive.get("blocked_reason", "") or "").strip(),
"gate_owner": str(work_item_profile.get("gate_owner_role_id", "") or "").strip(),
"missing_signals": missing_signals,
"confidence_label": (
f"{round(confidence_value * 100)}%"
if confidence_value is not None
else ""
),
"invalidated": str(adaptive.get("normalized_state", "") or "").strip().lower() == "invalidated",
}