import { useState, useEffect, useCallback } from "react"; import { useParams, useNavigate, Link } from "react-router-dom"; import { experiments, runs as runsApi, ApiError, } from "../api/client"; import type { ExperimentResponse, RunResponse, } from "../api/client"; import { useExperimentWS } from "../hooks/useExperimentWS"; import type { WsEvent, WsEventType, ConnectionStatus } from "../hooks/useExperimentWS"; import Leaderboard from "../components/Leaderboard"; import type { LeaderboardRow } from "../components/Leaderboard"; import Timeline from "../components/Timeline"; import type { TimelineEntry } from "../components/Timeline"; import SteeringControls from "../components/SteeringControls"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- let _timelineIdCounter = 0; function nextTimelineId(): string { _timelineIdCounter += 1; return `tl-${_timelineIdCounter}`; } function configSummary(config?: Record): string { if (!config) return "—"; const model = config.model ?? config.model_used ?? ""; const temp = config.temperature != null ? `t=${config.temperature}` : ""; const parts = [model, temp].filter(Boolean); return parts.length > 0 ? parts.join(" ") : JSON.stringify(config).slice(0, 60); } // --------------------------------------------------------------------------- // Connection Status Indicator // --------------------------------------------------------------------------- function ConnectionIndicator({ status }: { status: ConnectionStatus }) { const colors: Record = { connected: "bg-green-500", connecting: "bg-amber-500 animate-pulse", disconnected: "bg-red-500", }; return ( {status === "connected" ? "Live" : status === "connecting" ? "Connecting…" : "Disconnected"} ); } // --------------------------------------------------------------------------- // Live Page // --------------------------------------------------------------------------- export default function LivePage() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); // Experiment state const [experiment, setExperiment] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [expStatus, setExpStatus] = useState("idle"); // Timeline const [timeline, setTimeline] = useState([]); const [autoScroll, setAutoScroll] = useState(true); const [eventFilter, setEventFilter] = useState("all"); // Leaderboard const [leaderboard, setLeaderboard] = useState([]); const [bestRunId, setBestRunId] = useState(null); // Progress const [progress, setProgress] = useState({ completed: 0, total: 0, cache_hits: 0, tokens_total: 0, cost_total: 0, }); // ------------------------------------------------------------------------- // Load experiment // ------------------------------------------------------------------------- const loadExperiment = useCallback(async () => { if (!id) return; setLoading(true); setError(null); try { const exp = await experiments.get(id); setExperiment(exp); setExpStatus(exp.status); } catch (err: unknown) { if (err instanceof ApiError) { setError(`Failed to load experiment (${err.status}).`); } else { setError("Network error. Is the server running?"); } } finally { setLoading(false); } }, [id]); // ------------------------------------------------------------------------- // Load initial leaderboard // ------------------------------------------------------------------------- const loadLeaderboard = useCallback(async () => { if (!id) return; try { const resp = await runsApi.leaderboard(id); const rows: LeaderboardRow[] = resp.items.map((r: RunResponse) => ({ run_id: r.id, config_summary: configSummary(r.config), scores: {}, weighted_total: 0, status: r.status, cached: false, })); setLeaderboard(rows); if (rows.length > 0) { setBestRunId(rows[0].run_id); } } catch { // Non-critical — leaderboard will populate via WebSocket } }, [id]); useEffect(() => { loadExperiment(); loadLeaderboard(); }, [loadExperiment, loadLeaderboard]); // ------------------------------------------------------------------------- // Process incoming WS events // ------------------------------------------------------------------------- const processEvent = useCallback( (evt: WsEvent) => { const now = new Date(evt.timestamp ?? Date.now()); // Build timeline entry let message = ""; let detail: string | undefined; switch (evt.type) { case "run.started": message = `Run started: ${configSummary(evt.config)}`; break; case "run.completed": message = `Run completed: ${configSummary(evt.config)}`; detail = evt.weighted_total != null ? `Score: ${evt.weighted_total.toFixed(3)}` : undefined; break; case "new_best_found": message = `New best config found!`; detail = evt.weighted_total != null ? `Score: ${evt.weighted_total.toFixed(3)}` : undefined; break; case "cache_hit": message = `Cache hit: ${configSummary(evt.config)}`; break; case "run.failed": message = `Run failed: ${evt.error ?? "unknown error"}`; break; case "sweep.progress": message = `Progress: ${evt.progress?.completed ?? 0}/${evt.progress?.total ?? 0} runs`; break; case "sweep.completed": message = "Sweep completed!"; setExpStatus("completed"); break; default: message = evt.type; } setTimeline((prev) => [ ...prev, { id: nextTimelineId(), type: evt.type, run_id: evt.run_id, message, detail, timestamp: now, }, ]); // Update leaderboard if ( (evt.type === "run.completed" || evt.type === "new_best_found") && evt.run_id ) { const row: LeaderboardRow = { run_id: evt.run_id, config_summary: configSummary(evt.config), scores: evt.scores ?? {}, weighted_total: evt.weighted_total ?? 0, status: "completed", cached: evt.cached ?? false, }; setLeaderboard((prev) => { const existing = prev.findIndex((r) => r.run_id === row.run_id); if (existing >= 0) { const next = [...prev]; next[existing] = row; return next; } return [...prev, row]; }); if (evt.type === "new_best_found") { setBestRunId(evt.run_id); } } if (evt.type === "cache_hit" && evt.run_id) { const row: LeaderboardRow = { run_id: evt.run_id, config_summary: configSummary(evt.config), scores: evt.scores ?? {}, weighted_total: evt.weighted_total ?? 0, status: "completed", cached: true, }; setLeaderboard((prev) => { const existing = prev.findIndex((r) => r.run_id === row.run_id); if (existing >= 0) return prev; return [...prev, row]; }); } // Update progress if (evt.progress) { setProgress(evt.progress); } }, [], ); // ------------------------------------------------------------------------- // WebSocket connection via hook // ------------------------------------------------------------------------- const { connectionStatus } = useExperimentWS(id, { onEvent: processEvent, enabled: !loading && !error, }); // ------------------------------------------------------------------------- // Render // ------------------------------------------------------------------------- if (loading) { return (

Loading experiment…

); } if (error) { return (

{error}

); } return (
{/* Header */}
← Experiment

{experiment?.name ?? "Live Dashboard"}

{experiment?.description && (

{experiment.description}

)}
{/* Main layout: 60/40 split */}
{/* Left column — 60% */}
setEventFilter(f as WsEventType | "all")} autoScroll={autoScroll} onAutoScrollToggle={() => setAutoScroll(!autoScroll)} />
{/* Right column — 40% */}
{/* Steering Controls */}

Controls

{/* Leaderboard */}

Leaderboard

); }