Spaces:
Sleeping
Sleeping
File size: 4,252 Bytes
824b65d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | """Renders a job's trace events into an interactive HTML timeline.
Events are simple dicts (see local_runner.py for the exact shapes emitted):
{"ts": "...", "kind": "tool_call" | "tool_result" | "note" | "task_update" | "status",
"label": "short one-liner", "detail": "optional longer text", "ok": true/false/None}
Rendered with plain <details>/<summary> so expand/collapse works with no JS,
which keeps it safe to drop straight into a Gradio gr.HTML component.
"""
from __future__ import annotations
import html as _html
_ICONS = {
"tool_call": "\U0001F527", # wrench
"tool_result_ok": "✅",
"tool_result_fail": "❌",
"note": "\U0001F4AC",
"task_update": "\U0001F4CB",
"status_start": "▶️",
"status_end": "\U0001F3C1",
}
_STYLE = """
<style>
.pr-trace { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
.pr-trace .pr-empty {
color: var(--body-text-color-subdued, #888); font-style: italic; padding: 22px;
text-align: center; border: 1px dashed var(--border-color-primary, #ddd); border-radius: 12px;
}
.pr-trace ol { list-style: none; margin: 0; padding: 4px 0 0; border-left: 2px solid var(--border-color-primary, #d0d5dd); max-height: 480px; overflow-y: auto; }
.pr-trace li { position: relative; padding: 6px 0 6px 18px; margin-left: 4px; }
.pr-trace li::before {
content: ""; position: absolute; left: -7px; top: 14px;
width: 10px; height: 10px; border-radius: 50%;
background: #9aa4b2; border: 2px solid var(--background-fill-secondary, #fff);
}
.pr-trace li.ok::before { background: #0d9488; }
.pr-trace li.fail::before { background: #b91c1c; }
.pr-trace summary { cursor: pointer; display: flex; gap: 8px; align-items: baseline; padding: 2px 4px; border-radius: 6px; }
.pr-trace summary:hover { background: rgba(13,148,136,0.08); }
.pr-trace summary .pr-icon { flex: none; }
.pr-trace summary .pr-label { font-weight: 600; }
.pr-trace summary .pr-ts { margin-left: auto; font-size: 0.78em; color: var(--body-text-color-subdued, #888); white-space: nowrap; }
.pr-trace .pr-detail {
margin: 6px 0 0 22px; padding: 8px 10px; border-radius: 6px;
background: rgba(127,127,127,0.08); font-size: 0.85em; white-space: pre-wrap;
font-family: ui-monospace, 'Fira Code', monospace; max-height: 260px; overflow: auto;
}
.pr-trace .pr-live {
color: #0d9488; font-weight: 600; margin-bottom: 8px; padding: 6px 10px;
background: rgba(13,148,136,0.1); border-radius: 8px; display: inline-block;
}
</style>
"""
def _icon_for(event: dict) -> str:
kind = event.get("kind")
if kind == "tool_result":
return _ICONS["tool_result_ok"] if event.get("ok") else _ICONS["tool_result_fail"]
if kind == "status":
return _ICONS["status_end"] if event.get("ok") is not None else _ICONS["status_start"]
return _ICONS.get(kind, "•")
def _row_class(event: dict) -> str:
if event.get("ok") is True:
return "ok"
if event.get("ok") is False:
return "fail"
return ""
def render_trace(events: list[dict], running: bool = False) -> str:
if not events:
empty_msg = "Waiting for the local runner to pick this job up..." if running else "No trace recorded yet."
return f'{_STYLE}<div class="pr-trace"><div class="pr-empty">{_html.escape(empty_msg)}</div></div>'
items = []
for event in events:
icon = _icon_for(event)
label = _html.escape(str(event.get("label", "")))
ts = _html.escape(str(event.get("ts", "")))
detail = event.get("detail")
row_class = _row_class(event)
detail_html = ""
if detail:
detail_html = f'<div class="pr-detail">{_html.escape(str(detail))}</div>'
items.append(
f'<li class="{row_class}"><details{" open" if detail and row_class == "fail" else ""}>'
f'<summary><span class="pr-icon">{icon}</span>'
f'<span class="pr-label">{label}</span>'
f'<span class="pr-ts">{ts}</span></summary>'
f"{detail_html}</details></li>"
)
live_banner = '<div class="pr-live">⏳ Run in progress — refreshing automatically...</div>' if running else ""
return f'{_STYLE}<div class="pr-trace">{live_banner}<ol>{"".join(items)}</ol></div>'
|