Handoff: HoloDeck Evaluation Dashboard¶
Overview¶
A dashboard for viewing agent-evaluation test runs — the sort of UI you'd scaffold after running a holodeck test command against an agent config. It shows pass/fail trends, per-metric breakdowns, individual test-case detail with full tool-call traces, and a 3-run comparison view with delta analysis.
There are three primary views:
- Summary — headline KPIs, trend charts, metric breakdowns, filterable run table
- Explorer — three-column drilldown (runs → cases → detail) with conversation thread + tool calls
- Compare — pick up to 3 runs; see them side-by-side, baseline+deltas, or matrix-first
The user wants this reproduced in Streamlit (see "Streamlit notes" section below).
About the Design Files¶
The files in this bundle are design references created in HTML/React — prototypes showing intended look and behavior, not production code to copy directly. The task is to recreate these designs in Streamlit, using Streamlit's native components (st.metric, st.dataframe, st.plotly_chart, st.tabs, st.columns, etc.) and CSS overrides where needed to approximate the visual style.
The HTML prototypes are fully interactive. Open Evaluation Dashboard.html in a browser to explore every interaction before you start.
Fidelity¶
High-fidelity. The HTML prototype has final colors, typography, spacing, and a coherent dark-mode "terminal-green" aesthetic. Recreate in Streamlit as closely as Streamlit allows — Streamlit is opinionated about layout, so some compromises are expected. Aim for pixel-adjacent, not pixel-perfect.
Design Tokens¶
Colors¶
| Token | Hex | Usage |
|---|---|---|
--hd-bg-body |
#050b09 |
Page background |
--hd-bg-alt |
#0c1412 |
Surface |
--hd-card |
#0a110f |
Raised card |
--hd-card-2 |
#070c0a |
Card gradient end |
--hd-border |
#1c2b25 |
Borders, dividers |
--hd-fg |
#e8f5ec |
Primary foreground |
--hd-muted |
#9bb3a5 |
Secondary text |
--hd-accent |
#7bff5a |
Primary accent (terminal green) |
--hd-accent-2 |
#53ff9c |
Gradient stop |
--hd-accent-soft |
#5ae0a6 |
Softer accent (labels) |
--hd-accent-glow |
rgba(123,255,90,.18) |
Hover/glow |
| (fail) | #ff9d7e |
Regressions, fails (coral) |
| (warn) | #ffcf5a |
Warn / changed-config |
Compare-palette (per slot)¶
['#7bff5a', '#5ae0a6', '#ffcf5a'] // baseline, run-1, run-2
Page background radial¶
background:
radial-gradient(circle at 22% 18%, rgba(123,255,90,.09), transparent 26%),
radial-gradient(circle at 78% 12%, rgba(123,255,90,.06), transparent 22%),
#050b09;
Typography¶
- Sans:
"Inter", -apple-system, system-ui, sans-serif— weights 400/500/600/700 - Mono:
"JetBrains Mono", ui-monospace, Menlo, monospace— weights 400/500 - Scale: h3 18px · body 16px · small 15px · code 13px · tag 12px · label 11px
- Eyebrow labels: 10px,
letter-spacing: .15em, uppercase, color--hd-accent-soft - Numeric values: always in mono
Radius / Spacing¶
--radius-sm6px ·--radius-md10px ·--radius-lg16px ·--radius-pill999px- Cards: 14–18px padding, 1px solid
--hd-border, linear-gradient bgrgba(10,17,15,.95)→#070c0a60% - Gap scale: 2/4/6/8/10/12/14/16/20/24px
Motion¶
--dur-quick140ms ·--dur-base220ms ·--ease-standardcubic-bezier(.2,.7,.3,1)
Data Model¶
See data.js for the full synthetic dataset. The shape each real run should match:
type Run = {
id: string; // "run_20251103_104300_a1b2"
created_at: string; // ISO 8601
git_commit: string; // short SHA
summary: {
total: number;
passed: number;
failed: number;
pass_rate: number; // 0..1
duration_ms: number;
};
metadata: {
agent_config: {
model: { name: string; temperature: number; max_tokens: number; };
claude: { extended_thinking: boolean; /*...*/ };
api_key: string; // always redacted to "***" in UI
/* other agent config */
};
prompt_version: {
version: string; // "v0.4.2"
tags: string[]; // ["triage", "cautious"]
};
};
test_results: TestCase[];
};
type TestCase = {
name: string;
passed: boolean;
input: string; // user turn that started the case
conversation: Message[]; // chat-style thread, includes tool calls
expected_tools: string[]; // names of tools the agent SHOULD call
actual_tools: string[]; // names actually called
metric_results: MetricResult[];
};
type Message =
| { role: 'user' | 'assistant'; content: string; }
| { role: 'tool_call'; name: string; arguments: any; }
| { role: 'tool_result'; name: string; result: any; bytes?: number; };
type MetricResult = {
kind: 'standard' | 'rag' | 'geval';
name: string; // e.g. "faithfulness", "answer_relevancy"
score: number; // 0..1
threshold: number; // 0.7 typical
passed: boolean;
reasoning?: string; // for geval, judge's explanation
};
Views¶
1. Summary¶
Purpose: Dashboard landing. Answer "is the agent regressing?" at a glance.
Layout — 2 columns: - Left rail (240px fixed): filter controls (collapsible via Tweak) - Main (flex-1): stacked content
Main content order:
1. KPI strip — 4 cards in a row: Pass rate (big number + delta vs. prev run), Run count, Avg G-Eval, Avg duration
2. Pass-rate-over-time chart (area chart) — x: time, y: pass rate. Coral dots on regressions (drop >10pp from previous run). Dashed vertical lines at prompt-version boundaries, labeled with version.
3. Per-metric trend (line chart) — one line per metric, with segmented control (standard | rag | geval) to filter. Horizontal dashed line at threshold 0.7.
4. Three breakdown panels side-by-side (grid of 3 cols): Standard / RAG / G-Eval. Each shows per-metric averages as horizontal bars with threshold markers.
5. Runs table — sortable columns: [+] | Timestamp | Pass rate (pill + inline bar) | Tests (passed/total) | Prompt version | Model | Duration | Commit. Click row → opens in Explorer. [+] adds to compare queue.
Filter rail contents: - Date range - Prompt version chips (multi-select) - Model chips (multi-select) - Min pass-rate slider (0–100%) - Tag chips (multi-select) - Shareable URL field (encodes filters in querystring)
Pill colors: pass ≥ 85% → green, 65–85% → yellow, <65% → coral.
2. Explorer¶
Purpose: Deep-dive on a specific test case.
Layout — 3 columns:
- Runs list (340px, collapsible to 48px via arrow): vertical list, newest first. Each item shows: timestamp, version, pass-rate pill, passed/total, model suffix, [+] button.
- Cases list (340px): vertical list of cases for selected run. Each shows pass/fail icon, case name, G-Eval score, RAG avg.
- Detail panel (flex-1): everything about the selected case.
Detail panel sections (scrollable):
1. Case header: name, pass/fail pill, expected-tools coverage (check/cross per tool name)
2. Agent config snapshot: collapsible card showing agent_config as pretty-printed JSON with api_key redacted to ***
3. Conversation thread: chat-style bubbles
- user → right-aligned light bubble
- assistant → left-aligned fg bubble
- tool_call → distinct amber-tinted panel showing tool name, args as JSON
- tool_result → distinct green-tinted panel. If bytes > 500, collapse by default with "show result (1.2 KB)" toggle.
4. Metric evaluations: grouped by kind (standard/rag/geval). Each metric: name · score (mono, big) · threshold · pass/fail pill · (for geval) judge's reasoning as expandable text.
3. Compare¶
Purpose: Pick 2–3 runs, see what changed. Baseline = first run added.
Empty state: friendly CTA with "Compare latest 2 runs" / "Compare latest 3 runs" shortcuts.
3 layout variants (segmented control at top):
Variant 1 — Side-by-side (default):
- Column headers row (one per run, color-coded dot)
- Summary block: rows pass rate / passed / avg geval / avg rag / duration / est. cost, with a delta pill next to non-baseline values (green for improvement, coral for regression; duration + cost invert polarity)
- Config diff block: rows for prompt version / model / temp / tags / commit / extended thinking. Cells where the value differs from baseline get an amber left-border and a "changed" badge.
- Case matrix: one row per case, one column per run. Each cell shows ✓/✕ + per-case score. Regression cells (baseline passed, this run failed) get a coral outer ring; improvement cells the inverse with green.
Variant 2 — Baseline + deltas:
- Big baseline card on the left (~1.4fr), takes full stats including big pass-rate number
- Right: compact delta cards (1fr each) showing label · value · delta-pill rows
- Case matrix below
Variant 3 — Matrix-first:
- Compact strip of run cards at top (equal-width)
- Callouts block: per non-baseline run, a card listing first 3 regressions and first 3 improvements with +N overflow
- Full case matrix below
Delta polarity: positive values green, negative coral. For duration and cost, invert (lower = better).
Compare tray (floating, fixed bottom-center, shown across all tabs when queue > 0): slot pills for 3 items, "base" badge on first, remove ×, "Open Compare →" CTA. Enabled when ≥ 2 slots filled.
Interactions & Behavior¶
- Tab persistence: selected tab stored in localStorage
- Compare queue: stored in localStorage (array of run IDs, max 3)
- Run row click (Summary): navigate to Explorer with that run selected
[+]button (Summary table + Explorer runs list): add/remove from queue. Shows slot number (1/2/3) when in queue.- Variant switch: segmented control at top of Compare view
- Hover states: rows brighten; cards get subtle glow
- Filter changes: live-update chart + table
State (Streamlit mapping)¶
Use st.session_state for:
- tab: "summary" | "explorer" | "compare"
- filters: { versions: [], models: [], tags: [], min_pass: 0, metric_kind: "rag" }
- explorer_run_id, explorer_case_name
- compare_queue: list[str] (max 3)
- compare_variant: 1 | 2 | 3
Persist across reruns with streamlit_local_storage or a sidecar JSON file if cross-session persistence is needed.
Streamlit notes¶
Streamlit-specific implementation hints:
- Tabs:
st.tabs(["Summary", "Explorer", "Compare"])— simple but doesn't support badges; considerst.radio(horizontal=True)with custom CSS if you want the count badges from the HTML design. - Charts: use Plotly (
st.plotly_chart), not Altair — you need fine control over the dashed prompt-version lines, coral regression dots, and threshold markers. Setplot_bgcolor="#070c0a",paper_bgcolor="rgba(0,0,0,0)", font color#e8f5ec, gridrgba(28,43,37,.5). - Tables:
st.dataframesupports clickable rows viaon_select="rerun"andselection_mode="single-row". Use it for the runs table. For the per-metric breakdown, a smallst.dataframeper group works. - 3-column Explorer layout:
st.columns([1, 1, 2]). Put the lists in scrollable containers:with st.container(height=600):. - Case matrix in Compare: easiest approach is a Plotly heatmap — rows=cases, cols=runs, z=score, colorscale matching the HTML's green/coral gradient. Overlay regression/improvement markers with
add_annotation. - Compare tray: Streamlit has no fixed-position floaters natively — use
st.sidebarOR inject a small CSS-positionedst.empty()container. Or just render it at the top of the page whenlen(compare_queue) > 0. - Global CSS: inject once at the top:
st.markdown("""<style> :root { --hd-accent: #7bff5a; ... } .stApp { background: radial-gradient(...) #050b09; color: #e8f5ec; } /* mono font for metrics, etc. */ </style>""", unsafe_allow_html=True) - Tool-call bubbles in the conversation thread: render as
st.json(expanded=False)inside anst.container(border=True). Forbytes > 500, wrap inst.expander("show result (1.2 KB)"). - Redact
api_key: do it in the data layer before rendering, never in UI. - Tweaks: Streamlit doesn't need a tweaks panel — put equivalent controls in
st.sidebar: density, default tab, compare layout.
Files in this handoff¶
Evaluation Dashboard.html— entry HTML; load this in a browser to explore the prototypedata.js— synthetic dataset (24 runs, 7 prompt versions). Use this as your seed data for Streamlit development. Parse it with a small JS-to-JSON shim or manually port the structure.styles.js— all CSS (injected as a<style>tag at runtime). Good source for exact color/spacing/shadow values.summary.js— Summary view React components (KPIs, charts, breakdowns, runs table, filter rail)explorer.js— Explorer view (run list, case list, case detail with tool-call rendering)compare.js— Compare view (3 variants, tray,[+]button, empty state)app.jsx— root component wiring, state management, Tweaks panel
Assets¶
No bespoke image assets — everything is SVG inline or CSS. The dataset's run IDs and commit SHAs are synthetic. Swap data.js for your real eval pipeline's output and the UI should just work.