"use client" import { useState, useEffect, useMemo, useRef, useCallback } from "react" import Link from "next/link" import { useRouter } from "next/navigation" import { getRuns, deleteRun, stopRun, startRun, addToLeaderboard, type RunSummary } from "@/lib/api" import { formatDate, getStatusColor, cn } from "@/lib/utils" import { FilterBar } from "@/components/filter-bar" import { DataTable, type Column } from "@/components/data-table" import { RunActionsMenu } from "@/components/run-actions-menu" import { CircularProgress } from "@/components/circular-progress" import { EmptyState, ListIcon } from "@/components/empty-state" const POLL_INTERVAL = 2000 // 2 seconds export default function RunsPage() { const router = useRouter() const [runs, setRuns] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const pollIntervalRef = useRef(null) // Filters const [search, setSearch] = useState("") const [selectedProviders, setSelectedProviders] = useState([]) const [selectedBenchmarks, setSelectedBenchmarks] = useState([]) const [selectedStatuses, setSelectedStatuses] = useState([]) // Check if any run is in progress const hasRunningRuns = useMemo(() => { return runs.some( (r) => r.status === "running" || r.status === "pending" || r.status === "initializing" ) }, [runs]) // Silent refresh (no loading state) const refreshRuns = useCallback(async () => { try { const data = await getRuns() setRuns(data) setError(null) } catch (e) { // Silent fail on poll } }, []) // Initial load useEffect(() => { loadRuns() }, []) // Polling when runs are in progress useEffect(() => { if (hasRunningRuns) { pollIntervalRef.current = setInterval(refreshRuns, POLL_INTERVAL) } else { if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current) pollIntervalRef.current = null } } return () => { if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current) } } }, [hasRunningRuns, refreshRuns]) async function loadRuns() { try { setLoading(true) const data = await getRuns() setRuns(data) setError(null) } catch (e) { setError(e instanceof Error ? e.message : "Failed to load runs") } finally { setLoading(false) } } async function handleDelete(runId: string) { if (!confirm(`Delete run "${runId}"? This cannot be undone.`)) return try { await deleteRun(runId) setRuns((prev) => prev.filter((r) => r.runId !== runId)) } catch (e) { alert(e instanceof Error ? e.message : "Failed to delete run") } } async function handleTerminate(runId: string) { try { await stopRun(runId) await refreshRuns() } catch (e) { alert(e instanceof Error ? e.message : "Failed to terminate run") } } async function handleAddToLeaderboard(runId: string, data: { version?: string; notes?: string }) { await addToLeaderboard(runId, data) router.push("/leaderboard") } async function handleContinue(run: RunSummary) { try { await startRun({ provider: run.provider, benchmark: run.benchmark, runId: run.runId, judgeModel: run.judge, answeringModel: run.answeringModel, }) await refreshRuns() } catch (e) { alert(e instanceof Error ? e.message : "Failed to continue run") } } // Get unique values for filter options const providers = useMemo(() => { const counts: Record = {} runs.forEach((r) => { counts[r.provider] = (counts[r.provider] || 0) + 1 }) return Object.entries(counts).map(([value, count]) => ({ value, label: value, count, })) }, [runs]) const benchmarks = useMemo(() => { const counts: Record = {} runs.forEach((r) => { counts[r.benchmark] = (counts[r.benchmark] || 0) + 1 }) return Object.entries(counts).map(([value, count]) => ({ value, label: value, count, })) }, [runs]) const statuses = useMemo(() => { const counts: Record = {} runs.forEach((r) => { counts[r.status] = (counts[r.status] || 0) + 1 }) return Object.entries(counts).map(([value, count]) => ({ value, label: value, count, })) }, [runs]) // Filter runs const filteredRuns = useMemo(() => { return runs.filter((run) => { // Search filter if (search) { const searchLower = search.toLowerCase() const matchesSearch = run.runId.toLowerCase().includes(searchLower) || run.provider.toLowerCase().includes(searchLower) || run.benchmark.toLowerCase().includes(searchLower) if (!matchesSearch) return false } // Provider filter if (selectedProviders.length > 0 && !selectedProviders.includes(run.provider)) { return false } // Benchmark filter if (selectedBenchmarks.length > 0 && !selectedBenchmarks.includes(run.benchmark)) { return false } // Status filter if (selectedStatuses.length > 0 && !selectedStatuses.includes(run.status)) { return false } return true }) }, [runs, search, selectedProviders, selectedBenchmarks, selectedStatuses]) // Build columns const columns: Column[] = useMemo( () => [ { key: "runId", header: "Run ID", render: (run) => ( {run.runId} ), }, { key: "provider", header: "Provider", render: (run) => {run.provider}, }, { key: "benchmark", header: "Benchmark", render: (run) => {run.benchmark}, }, { key: "status", header: "Status", render: (run) => { const isRunning = run.status === "running" || run.status === "pending" || run.status === "initializing" || run.status === "stopping" const s = run.summary const phasesCompleted = s.ingested + s.indexed + s.searched + s.answered + s.evaluated const totalPhases = 5 * s.total const progress = totalPhases > 0 ? phasesCompleted / totalPhases : 0 let phasesFullyComplete = 0 if (s.ingested === s.total) phasesFullyComplete++ if (s.indexed === s.total) phasesFullyComplete++ if (s.searched === s.total) phasesFullyComplete++ if (s.answered === s.total) phasesFullyComplete++ if (s.evaluated === s.total) phasesFullyComplete++ return (
{isRunning && } {run.status} {isRunning && s.total > 0 && ( {phasesFullyComplete}/5 )}
) }, }, { key: "accuracy", header: "Accuracy", align: "right", render: (run) => { const accuracyPct = run.accuracy !== null && run.accuracy !== undefined ? (run.accuracy * 100).toFixed(0) : null return accuracyPct ? ( {accuracyPct}% ) : ( ) }, }, { key: "date", header: "Date", render: (run) => ( {formatDate(run.createdAt)} ), }, { key: "actions", header: "", width: "40px", align: "right", render: (run) => ( handleAddToLeaderboard(run.runId, data)} onDelete={() => handleDelete(run.runId)} onTerminate={() => handleTerminate(run.runId)} onContinue={() => handleContinue(run)} /> ), }, ], [] ) const clearFilters = () => { setSearch("") setSelectedProviders([]) setSelectedBenchmarks([]) setSelectedStatuses([]) } return (
{/* Header */}

Runs

{/* Filter Bar */} {!loading && runs.length > 0 && (
)} {/* Content */} {loading ? (

Loading runs...

) : error ? (

{error}

) : runs.length === 0 ? ( } title="No runs yet" action={ + New Run } /> ) : ( run.runId} /> )}
) }