Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion ergon-dashboard/RUN_VISIBILITY_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"; ... }
Expand Down
9 changes: 0 additions & 9 deletions ergon-dashboard/scripts/generate-event-contracts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
7 changes: 7 additions & 0 deletions ergon-dashboard/scripts/generate-rest-contracts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 5 additions & 13 deletions ergon-dashboard/src/app/api/samples/[sampleId]/events/route.ts
Original file line number Diff line number Diff line change
@@ -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<{
Expand All @@ -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 });
}
21 changes: 0 additions & 21 deletions ergon-dashboard/src/app/samples/[sampleId]/detail/page.tsx

This file was deleted.

13 changes: 11 additions & 2 deletions ergon-dashboard/src/app/samples/[sampleId]/page.tsx
Original file line number Diff line number Diff line change
@@ -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<{
Expand All @@ -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 <SampleWorkspacePage sampleId={sampleId} ssrError={`API returned ${result.status}`} />;
}

return <SampleWorkspacePage sampleId={sampleId} initialRunState={result.data} />;
}
22 changes: 13 additions & 9 deletions ergon-dashboard/src/app/samples/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<main className="mx-auto w-full max-w-7xl px-6 py-8">
<div className="mb-5">
<p className="text-xs font-semibold uppercase tracking-[0.12em] text-[var(--faint)]">
Run Index
Sample Index
</p>
<h1 className="mt-2 text-3xl font-semibold text-[var(--ink)]">Runs</h1>
<h1 className="mt-2 text-3xl font-semibold text-[var(--ink)]">Samples</h1>
</div>

{error ? (
Expand All @@ -47,7 +51,7 @@ export default async function RunsPage() {
</div>
</div>

<SampleIndexTable runs={runs} />
<SampleIndexTable runs={samples} />
</main>
);
}
2 changes: 1 addition & 1 deletion ergon-dashboard/src/components/experiments/SampleTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function SampleTable({ samples }: { samples: ExperimentSampleSummaryView[
>
<td>
<Link
href={`/samples/${sample.sampleId}/detail`}
href={`/samples/${sample.sampleId}`}
className="block px-3 py-2 font-mono text-xs text-[var(--ink)] underline-offset-2 group-hover:underline"
>
{sample.sampleKey}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]> = {}): ExperimentRunRow["metrics"] {
Expand All @@ -28,7 +28,7 @@ function metrics(overrides: Partial<ExperimentRunRow["metrics"]> = {}): Experime
function runRow(overrides: Partial<ExperimentRunRow> = {}): ExperimentRunRow {
return {
sample_id: defaultSampleId,
definition_id: definitionId,
experiment_id: experimentId,
benchmark_type: "minif2f",
instance_key: "sample-a",
status: "completed",
Expand Down
13 changes: 6 additions & 7 deletions ergon-dashboard/src/components/indexes/SampleIndexTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -45,7 +44,7 @@ export function SampleIndexTable({ runs }: { runs: SampleSummary[] }) {
<input
value={query}
onChange={(event) => 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)]"
/>
<select
Expand All @@ -67,7 +66,7 @@ export function SampleIndexTable({ runs }: { runs: SampleSummary[] }) {
<table className="w-full min-w-[1180px] text-left text-[13px]">
<thead className="border-b border-[var(--line)] bg-[var(--paper-2)] text-[11px] uppercase tracking-[0.08em] text-[var(--faint)]">
<tr>
<th className="px-3 py-2 font-semibold">Run</th>
<th className="px-3 py-2 font-semibold">Sample</th>
<th className="px-3 py-2 font-semibold">Experiment</th>
<th className="px-3 py-2 font-semibold">Benchmark / Sample</th>
<th className="px-3 py-2 font-semibold">Status</th>
Expand Down Expand Up @@ -96,13 +95,13 @@ export function SampleIndexTable({ runs }: { runs: SampleSummary[] }) {
</td>
<td className="px-3 py-2.5">
<Link
href={`/experiments/${run.definition_id}`}
href={run.experiment_id ? `/experiments/${run.experiment_id}` : "/experiments"}
className="text-[var(--ink-2)] underline-offset-2 hover:underline"
>
{run.definition_name ?? run.experiment ?? "Experiment"}
{run.experiment ?? "Experiment"}
</Link>
<div className="mt-0.5 text-xs text-[var(--faint)]">
{run.experiment ?? run.definition_id}
{run.experiment_id ?? "-"}
</div>
</td>
<td className="px-3 py-2.5 font-mono text-xs text-[var(--ink-2)]">
Expand Down Expand Up @@ -144,7 +143,7 @@ export function SampleIndexTable({ runs }: { runs: SampleSummary[] }) {
{filtered.length === 0 ? (
<tr>
<td colSpan={10} className="px-3 py-8 text-center text-sm text-[var(--muted)]">
No runs match the current filters.
No samples match the current filters.
</td>
</tr>
) : null}
Expand Down
4 changes: 2 additions & 2 deletions ergon-dashboard/src/components/panels/CommunicationPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ export function CommunicationPanel({
<div className="mt-1 flex flex-wrap gap-2 break-all font-mono text-[10px] text-[var(--faint)]">
<span>#{message.sequenceNum}</span>
{message.taskId && <span className="break-all">task {message.taskId}</span>}
{message.taskExecutionId && (
<span className="break-all">exec {message.taskExecutionId}</span>
{message.taskAttemptId && (
<span className="break-all">exec {message.taskAttemptId}</span>
)}
</div>
</div>
Expand Down
Loading
Loading