diff --git a/ergon-dashboard/RUN_VISIBILITY_PLAN.md b/ergon-dashboard/RUN_VISIBILITY_PLAN.md index 01a0c5afd..7bfc32e0a 100644 --- a/ergon-dashboard/RUN_VISIBILITY_PLAN.md +++ b/ergon-dashboard/RUN_VISIBILITY_PLAN.md @@ -34,7 +34,7 @@ These land first because every workstream depends on them. type RunEvent = | { kind: "workflow.started"; at: string; runId: string; ... } | { kind: "task.status_changed"; at: string; taskId: string; from: TaskStatus; to: TaskStatus; trigger: TaskTrigger; ... } - | { kind: "generation.turn"; at: string; taskExecutionId: string; turnIndex: number; ... } + | { kind: "generation.turn"; at: string; taskAttemptId: string; turnIndex: number; ... } | { kind: "sandbox.command"; ... } | { kind: "thread.message"; ... } | { kind: "task.evaluation"; ... } diff --git a/ergon-dashboard/scripts/generate-event-contracts.mjs b/ergon-dashboard/scripts/generate-event-contracts.mjs index 607f4fa4c..ec32502c0 100644 --- a/ergon-dashboard/scripts/generate-event-contracts.mjs +++ b/ergon-dashboard/scripts/generate-event-contracts.mjs @@ -41,15 +41,6 @@ function patchZod4Compat(source) { } function customEventContractSource(entry) { - if (entry.modelName === "DashboardGraphMutationEvent") { - return `import { z } from "zod"; -import { GraphMutationDtoSchema } from "@/features/graph/contracts/graphMutations"; - -export const DashboardGraphMutationEventSchema = z.object({ - mutation: GraphMutationDtoSchema, -}).catchall(z.any()); -`; - } if (entry.modelName === "DashboardSampleRuntimeEvent") { return `import { z } from "zod"; import { SampleRuntimeEventViewSchema } from "@/lib/contracts/rest"; diff --git a/ergon-dashboard/scripts/generate-rest-contracts.mjs b/ergon-dashboard/scripts/generate-rest-contracts.mjs index 3e27f52ea..6fb74ff38 100644 --- a/ergon-dashboard/scripts/generate-rest-contracts.mjs +++ b/ergon-dashboard/scripts/generate-rest-contracts.mjs @@ -28,6 +28,13 @@ let source = readFileSync(contractsPath, "utf8") ); const sampleRuntimeEventDiscriminators = { + // openapi-zod-client currently loses literal discriminators for this Pydantic + // discriminated union. The dashboard relies on eventType narrowing, so this + // post-process keeps SampleRuntimeEventView type-safe until the generator can + // emit the correct z.discriminatedUnion shape directly. + // + // When adding a new backend SampleRuntimeEventView union member, add the + // schema name and eventType here as well, then regenerate contracts. SampleStatusChangedEventView: "sample.status_changed", SampleTaskAddedEventView: "task.added", SampleTaskRemovedEventView: "task.removed", diff --git a/ergon-dashboard/src/app/api/samples/[sampleId]/events/route.ts b/ergon-dashboard/src/app/api/samples/[sampleId]/events/route.ts index 7ecf31d56..dd9d21d1c 100644 --- a/ergon-dashboard/src/app/api/samples/[sampleId]/events/route.ts +++ b/ergon-dashboard/src/app/api/samples/[sampleId]/events/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; -import { fetchErgonApi } from "@/lib/serverApi"; +import { loadSampleEvents } from "@/lib/server-data/samples"; interface RouteContext { params: Promise<{ @@ -10,18 +10,10 @@ interface RouteContext { export async function GET(_request: Request, context: RouteContext) { const { sampleId } = await context.params; + const result = await loadSampleEvents(sampleId); - try { - const response = await fetchErgonApi(`/samples/${sampleId}/events`); - const body = await response.json(); - return NextResponse.json(body, { status: response.status }); - } catch (error) { - return NextResponse.json( - { - detail: `Ergon API is unavailable while loading events for sample ${sampleId}.`, - error: error instanceof Error ? error.message : "Unknown backend fetch failure", - }, - { status: 503 }, - ); + if (result.ok) { + return NextResponse.json(result.data, { status: result.status }); } + return NextResponse.json(result.body, { status: result.status }); } diff --git a/ergon-dashboard/src/app/samples/[sampleId]/detail/page.tsx b/ergon-dashboard/src/app/samples/[sampleId]/detail/page.tsx deleted file mode 100644 index 13ca16277..000000000 --- a/ergon-dashboard/src/app/samples/[sampleId]/detail/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { notFound } from "next/navigation"; - -import { SampleDetail } from "@/components/samples/SampleDetail"; -import { loadSampleState } from "@/lib/server-data/samples"; - -interface SampleDetailPageProps { - params: Promise<{ - sampleId: string; - }>; -} - -export default async function SampleDetailPage({ params }: SampleDetailPageProps) { - const { sampleId } = await params; - const result = await loadSampleState(sampleId); - if (!result.ok) { - if (result.status === 404) notFound(); - throw new Error(`Failed to load sample ${sampleId}: ${result.status}`); - } - - return ; -} diff --git a/ergon-dashboard/src/app/samples/[sampleId]/page.tsx b/ergon-dashboard/src/app/samples/[sampleId]/page.tsx index 0b5e79c52..96769d641 100644 --- a/ergon-dashboard/src/app/samples/[sampleId]/page.tsx +++ b/ergon-dashboard/src/app/samples/[sampleId]/page.tsx @@ -1,4 +1,7 @@ -import { redirect } from "next/navigation"; +import { notFound } from "next/navigation"; + +import { SampleWorkspacePage } from "@/components/sample/SampleWorkspacePage"; +import { loadSampleSnapshot } from "@/lib/server-data/samples"; interface SamplePageProps { params: Promise<{ @@ -8,5 +11,11 @@ interface SamplePageProps { export default async function SamplePage({ params }: SamplePageProps) { const { sampleId } = await params; - redirect(`/samples/${sampleId}/detail`); + const result = await loadSampleSnapshot(sampleId); + if (!result.ok) { + if (result.status === 404) notFound(); + return ; + } + + return ; } diff --git a/ergon-dashboard/src/app/samples/page.tsx b/ergon-dashboard/src/app/samples/page.tsx index 42d8ce6ca..fac9d73f0 100644 --- a/ergon-dashboard/src/app/samples/page.tsx +++ b/ergon-dashboard/src/app/samples/page.tsx @@ -1,29 +1,33 @@ import { SampleIndexTable } from "@/components/indexes/SampleIndexTable"; import { loadSampleList, type SampleSummary } from "@/lib/server-data/samples"; -export default async function RunsPage() { - let runs: SampleSummary[] = []; +export default async function SamplesPage() { + let samples: SampleSummary[] = []; let error: string | null = null; const result = await loadSampleList({ limit: 100 }); if (result.ok) { - runs = result.data; + samples = result.data; } else { const detail = (result.body as { detail?: string })?.detail; error = detail ?? `API returned ${result.status}`; } - const runningCount = runs.filter((run) => ["executing", "evaluating"].includes(run.status)).length; - const failedCount = runs.filter((run) => run.status === "failed" || run.failed_tasks > 0).length; - const completedCount = runs.filter((run) => run.status === "completed").length; + const runningCount = samples.filter((sample) => + ["executing", "evaluating"].includes(sample.status), + ).length; + const failedCount = samples.filter( + (sample) => sample.status === "failed" || sample.failed_tasks > 0, + ).length; + const completedCount = samples.filter((sample) => sample.status === "completed").length; return (

- Run Index + Sample Index

-

Runs

+

Samples

{error ? ( @@ -47,7 +51,7 @@ export default async function RunsPage() { - +
); } diff --git a/ergon-dashboard/src/components/experiments/SampleTable.tsx b/ergon-dashboard/src/components/experiments/SampleTable.tsx index fb0ca5ad2..b72c6fb79 100644 --- a/ergon-dashboard/src/components/experiments/SampleTable.tsx +++ b/ergon-dashboard/src/components/experiments/SampleTable.tsx @@ -26,7 +26,7 @@ export function SampleTable({ samples }: { samples: ExperimentSampleSummaryView[ > {sample.sampleKey} diff --git a/ergon-dashboard/src/components/experiments/sampleRunMetricExplorerModel.test.ts b/ergon-dashboard/src/components/experiments/sampleRunMetricExplorerModel.test.ts index 4837b3f5f..1d7685e95 100644 --- a/ergon-dashboard/src/components/experiments/sampleRunMetricExplorerModel.test.ts +++ b/ergon-dashboard/src/components/experiments/sampleRunMetricExplorerModel.test.ts @@ -11,7 +11,7 @@ import { type RunMetricPoint, } from "./sampleRunMetricExplorerModel"; -const definitionId = "11111111-1111-4111-8111-111111111111"; +const experimentId = "11111111-1111-4111-8111-111111111111"; const defaultSampleId = "22222222-2222-4222-8222-222222222222"; function metrics(overrides: Partial = {}): ExperimentRunRow["metrics"] { @@ -28,7 +28,7 @@ function metrics(overrides: Partial = {}): Experime function runRow(overrides: Partial = {}): ExperimentRunRow { return { sample_id: defaultSampleId, - definition_id: definitionId, + experiment_id: experimentId, benchmark_type: "minif2f", instance_key: "sample-a", status: "completed", diff --git a/ergon-dashboard/src/components/indexes/SampleIndexTable.tsx b/ergon-dashboard/src/components/indexes/SampleIndexTable.tsx index ecd1fabe4..7d2ade3a1 100644 --- a/ergon-dashboard/src/components/indexes/SampleIndexTable.tsx +++ b/ergon-dashboard/src/components/indexes/SampleIndexTable.tsx @@ -23,7 +23,6 @@ export function SampleIndexTable({ runs }: { runs: SampleSummary[] }) { const matchesStatus = status === "all" || run.status === status; const text = [ run.name, - run.definition_name, run.experiment, run.benchmark_type, run.instance_key, @@ -45,7 +44,7 @@ export function SampleIndexTable({ runs }: { runs: SampleSummary[] }) { setQuery(event.target.value)} - placeholder="Search runs" + placeholder="Search samples" className="h-9 w-full max-w-sm rounded-md border border-[var(--line)] bg-[var(--card)] px-3 text-sm text-[var(--ink)] outline-none focus:border-[var(--ink-2)]" />