Spaces:
Sleeping
Sleeping
File size: 3,803 Bytes
f019486 | 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 | """Pure rendering of agent events to HTML, plus the session run-cap check."""
import html as _html
import json
from agent import config
TIMELINE_CSS = """
:root { --bg:#0b0f14; --card:#121821; --line:#1e2733; --fg:#cfd8e3;
--muted:#7d8a99; --cyan:#39d3ee; --green:#37e0a6; --amber:#f0b35e; }
.gradio-container { background: var(--bg) !important; }
#oa-timeline { font-family: ui-monospace, "JetBrains Mono", Menlo, Consolas, monospace;
display:flex; flex-direction:column; gap:10px; }
.oa-card { background:var(--card); border:1px solid var(--line); border-left:3px solid var(--line);
border-radius:8px; padding:10px 14px; color:var(--fg); }
.oa-card .oa-head { font-size:12px; letter-spacing:.04em; color:var(--muted); margin-bottom:4px; }
.oa-thought { border-left-color: var(--cyan); }
.oa-revision { border-left-color: var(--amber); background:#1a1710; }
.oa-tool_call { border-left-color: var(--green); }
.oa-observation { border-left-color:#2b3a4a; }
.oa-observation.oa-weak { border-left-color: var(--amber); opacity:.92; }
.oa-final { border-left-color: var(--green); background:#0f1a16; font-size:15px; }
.oa-status { color:var(--muted); font-style:italic; }
.oa-args, .oa-obs { white-space:pre-wrap; word-break:break-word; margin-top:4px; }
#oa-stats { color:var(--muted); font-family: ui-monospace, monospace; margin-top:6px; }
"""
_ICON = {"thought": "🧠", "tool_call": "🔧", "observation": "📄", "final": "✅", "limit": "⛔"}
def _esc(text) -> str:
return _html.escape(str(text))
def render_event(event) -> str:
kind = event["kind"]
step = event.get("step", "")
if kind == "thought":
revision = event.get("revision", False)
icon = "↻" if revision else _ICON["thought"]
label = "revision" if revision else "thought"
cls = "oa-thought oa-revision" if revision else "oa-thought"
return (f'<div class="oa-card {cls}"><div class="oa-head">{icon} {label} · step {step}</div>'
f'<div>{_esc(event["text"])}</div></div>')
if kind == "tool_call":
args = json.dumps(event.get("args", {}), ensure_ascii=False)
return (f'<div class="oa-card oa-tool_call"><div class="oa-head">🔧 tool call · step {step}</div>'
f'<div>{_esc(event["tool"])}</div><div class="oa-args">{_esc(args)}</div></div>')
if kind == "observation":
weak = " oa-weak" if event.get("weak") else ""
tag = " (weak — reconsidering)" if event.get("weak") else ""
return (f'<div class="oa-card oa-observation{weak}"><div class="oa-head">📄 observation · '
f'{_esc(event["tool"])}{tag}</div><div class="oa-obs">{_esc(event["text"])}</div></div>')
if kind == "final":
return (f'<div class="oa-card oa-final"><div class="oa-head">✅ final answer</div>'
f'<div>{_esc(event["text"])}</div></div>')
if kind == "limit":
return (f'<div class="oa-card"><div class="oa-head">⛔ stopped</div>'
f'<div>{_esc(event["text"])}</div></div>')
return ""
def render_timeline(events, status=None) -> str:
cards = [render_event(e) for e in events if e.get("kind") != "status"]
if status:
cards.append(f'<div class="oa-card oa-status">{_esc(status)}</div>')
return f'<div id="oa-timeline">{"".join(cards)}</div>'
def render_stats(*, steps: int, tool_calls: int, seconds: float) -> str:
return f"{steps} steps · {tool_calls} tool calls · {seconds:.1f}s"
def check_run_allowed(runs_used: int, cap: int = config.RUNS_PER_SESSION):
if runs_used >= cap:
return False, f"You've reached this session's run limit ({cap}). Refresh to start a new session."
return True, ""
|