Skip to content
Merged
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
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { ContextEventState } from "@/lib/types";

export async function POST(request: Request) {
const payload = (await request.json()) as {
runId: string;
sampleId: string;
taskId: string;
event: ContextEventState;
};
emitHarnessContextEvent(payload.runId, payload.taskId, payload.event);
emitHarnessContextEvent(payload.sampleId, payload.taskId, payload.event);
return NextResponse.json({ ok: true });
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { emitHarnessRunCompleted } from "@/lib/testing/dashboardHarness";

export async function POST(request: Request) {
const payload = (await request.json()) as {
runId: string;
sampleId: string;
status: "completed" | "failed";
durationSeconds: number;
finalScore: number | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { TaskEvaluationState } from "@/lib/types";

export async function POST(request: Request) {
const payload = (await request.json()) as {
runId: string;
sampleId: string;
taskId: string | null;
evaluation: TaskEvaluationState;
};
emitHarnessTaskEvaluation(payload.runId, payload.taskId, payload.evaluation);
emitHarnessTaskEvaluation(payload.sampleId, payload.taskId, payload.evaluation);
return NextResponse.json({ ok: true });
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { CommunicationThreadState } from "@/lib/types";

export async function POST(request: Request) {
const payload = (await request.json()) as {
runId: string;
sampleId: string;
thread: CommunicationThreadState;
};
emitHarnessThreadMessage(payload.runId, payload.thread);
emitHarnessThreadMessage(payload.sampleId, payload.thread);
return NextResponse.json({ ok: true });
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ import { getHarnessRunMutations } from "@/lib/testing/dashboardHarness";

interface RouteContext {
params: Promise<{
runId: string;
sampleId: string;
}>;
}

export async function GET(_request: Request, context: RouteContext) {
const { runId } = await context.params;
const { sampleId } = await context.params;

try {
if (config.enableTestHarness) {
const harnessMutations = getHarnessRunMutations(runId);
const harnessMutations = getHarnessRunMutations(sampleId);
if (harnessMutations) {
return NextResponse.json(harnessMutations);
}
}
const response = await fetchErgonApi(`/runs/${runId}/mutations`);
const response = await fetchErgonApi(`/samples/${sampleId}/mutations`);
const body = await response.json();
if (response.ok) {
return NextResponse.json(body, { status: response.status });
Expand All @@ -29,7 +29,7 @@ export async function GET(_request: Request, context: RouteContext) {
} catch (error) {
return NextResponse.json(
{
detail: `Ergon API is unavailable while loading mutations for run ${runId}.`,
detail: `Ergon API is unavailable while loading mutations for run ${sampleId}.`,
error: error instanceof Error ? error.message : "Unknown backend fetch failure",
},
{ status: 503 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ import { fetchErgonApi } from "@/lib/serverApi";

interface RouteContext {
params: Promise<{
runId: string;
sampleId: string;
resourceId: string;
}>;
}

/**
* Proxy the binary-or-text body of a RunResource blob from the Ergon API back
* Proxy the binary-or-text body of a SampleResource blob from the Ergon API back
* to the dashboard. No parsing — we preserve Content-Type and
* Content-Disposition so viewers (markdown, PDF via iframe, image, CSV) can
* interpret the stream themselves.
*/
export async function GET(_request: Request, context: RouteContext) {
const { runId, resourceId } = await context.params;
const { sampleId, resourceId } = await context.params;

try {
const upstream = await fetchErgonApi(`/runs/${runId}/resources/${resourceId}/content`, {
const upstream = await fetchErgonApi(`/samples/${sampleId}/resources/${resourceId}/content`, {
timeoutMs: 30_000,
headers: {
Accept: "*/*",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { NextResponse } from "next/server";

import { loadRunSnapshot } from "@/lib/server-data/runs";
import { loadRunSnapshot } from "@/lib/server-data/samples";

interface RouteContext {
params: Promise<{
runId: string;
sampleId: string;
}>;
}

export async function GET(_request: Request, context: RouteContext) {
const { runId } = await context.params;
const result = await loadRunSnapshot(runId);
const { sampleId } = await context.params;
const result = await loadRunSnapshot(sampleId);

if (result.ok) {
return NextResponse.json(result.data, { status: result.status });
Expand Down
30 changes: 15 additions & 15 deletions ergon-dashboard/src/app/experiments/[definitionId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import Link from "next/link";
import { notFound } from "next/navigation";

import { StatusBadge } from "@/components/common/StatusBadge";
import { RunMetricExplorer } from "@/components/experiments/RunMetricExplorer";
import { formatRunMetricValue, metricDescriptor } from "@/components/experiments/runMetricExplorerModel";
import { SampleRunMetricExplorer } from "@/components/experiments/SampleRunMetricExplorer";
import { formatRunMetricValue, metricDescriptor } from "@/components/experiments/sampleRunMetricExplorerModel";
import { formatDurationMs } from "@/lib/formatDuration";
import { loadExperimentDetail, type ExperimentDetailWithRunMetrics } from "@/lib/server-data/experiments";

Expand All @@ -27,8 +27,8 @@ function workerTeamLabel(workerTeam: Record<string, unknown>) {
return entries.map(([key, value]) => `${key}: ${String(value)}`).join(", ");
}

function runHref(runId: string) {
return `/run/${runId}`;
function runHref(sampleId: string) {
return `/samples/${sampleId}`;
}

export default async function ExperimentPage({ params }: ExperimentPageProps) {
Expand Down Expand Up @@ -143,7 +143,7 @@ export default async function ExperimentPage({ params }: ExperimentPageProps) {
</section>

<div className="mb-6" data-testid="experiment-run-distribution">
<RunMetricExplorer points={detail.runMetricPoints} runHrefBase="/run" />
<SampleRunMetricExplorer points={detail.runMetricPoints} runHrefBase="/samples" />
</div>

<div className="overflow-hidden rounded-[var(--radius)] border border-[var(--line)] bg-[var(--card)] shadow-card">
Expand All @@ -163,51 +163,51 @@ export default async function ExperimentPage({ params }: ExperimentPageProps) {
<tbody>
{detail.runMetricPoints.map((point) => (
<tr
key={point.runId}
data-testid={`experiment-run-row-${point.runId}`}
key={point.sampleId}
data-testid={`experiment-run-row-${point.sampleId}`}
className="group cursor-pointer border-b border-[var(--line)] last:border-0 hover:bg-[var(--paper)]"
>
<td>
<Link
href={runHref(point.runId)}
href={runHref(point.sampleId)}
className="block px-3 py-2 font-mono text-xs text-[var(--ink)] underline-offset-2 group-hover:underline"
>
{point.runName}
</Link>
</td>
<td>
<Link href={runHref(point.runId)} className="block px-3 py-2 text-[var(--muted)]">
<Link href={runHref(point.sampleId)} className="block px-3 py-2 text-[var(--muted)]">
{point.sampleLabel}
</Link>
</td>
<td>
<Link href={runHref(point.runId)} className="block px-3 py-2 text-[var(--muted)]">
<Link href={runHref(point.sampleId)} className="block px-3 py-2 text-[var(--muted)]">
<StatusBadge status={point.status} size="sm" />
{point.errorSummary ? <div className="mt-1 max-w-56 truncate text-xs text-red-500">{point.errorSummary}</div> : null}
</Link>
</td>
<td>
<Link href={runHref(point.runId)} className="block px-3 py-2 font-mono text-xs text-[var(--ink)]">
<Link href={runHref(point.sampleId)} className="block px-3 py-2 font-mono text-xs text-[var(--ink)]">
{formatRunMetricValue(scoreDescriptor, point.metrics.score)}
</Link>
</td>
<td>
<Link href={runHref(point.runId)} className="block px-3 py-2 font-mono text-xs text-[var(--muted)]">
<Link href={runHref(point.sampleId)} className="block px-3 py-2 font-mono text-xs text-[var(--muted)]">
{formatRunMetricValue(durationDescriptor, point.metrics.duration_ms)}
</Link>
</td>
<td>
<Link href={runHref(point.runId)} className="block px-3 py-2 font-mono text-xs text-[var(--muted)]">
<Link href={runHref(point.sampleId)} className="block px-3 py-2 font-mono text-xs text-[var(--muted)]">
{formatRunMetricValue(tasksDescriptor, point.metrics.total_tasks)}
</Link>
</td>
<td>
<Link href={runHref(point.runId)} className="block px-3 py-2 text-xs text-[var(--muted)]">
<Link href={runHref(point.sampleId)} className="block px-3 py-2 text-xs text-[var(--muted)]">
{formatRunMetricValue(costDescriptor, point.metrics.total_cost_usd)}
</Link>
</td>
<td>
<Link href={runHref(point.runId)} className="block px-3 py-2 text-[var(--muted)]">
<Link href={runHref(point.sampleId)} className="block px-3 py-2 text-[var(--muted)]">
{point.modelTarget ?? "—"}
</Link>
</td>
Expand Down
25 changes: 0 additions & 25 deletions ergon-dashboard/src/app/run/[runId]/page.tsx

This file was deleted.

25 changes: 25 additions & 0 deletions ergon-dashboard/src/app/samples/[sampleId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { SampleWorkspacePage } from "@/components/sample/SampleWorkspacePage";
import { loadRunSnapshot } from "@/lib/server-data/samples";
import type { SerializedSampleWorkspaceState } from "@/lib/types";

interface LegacyRunPageProps {
params: Promise<{
sampleId: string;
}>;
}

export default async function RunPage({ params }: LegacyRunPageProps) {
const { sampleId } = await params;
let initialRunState: SerializedSampleWorkspaceState | null = null;
let ssrError: string | null = null;

const result = await loadRunSnapshot(sampleId);
if (result.ok) {
initialRunState = result.data;
} else {
const detail = (result.body as { detail?: string })?.detail;
ssrError = detail ?? `Run API returned ${result.status}`;
}

return <SampleWorkspacePage sampleId={sampleId} initialRunState={initialRunState} ssrError={ssrError} />;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RunIndexTable } from "@/components/indexes/RunIndexTable";
import { loadRunList, type RunSummary } from "@/lib/server-data/runs";
import { SampleIndexTable } from "@/components/indexes/SampleIndexTable";
import { loadRunList, type RunSummary } from "@/lib/server-data/samples";

export default async function RunsPage() {
let runs: RunSummary[] = [];
Expand Down Expand Up @@ -47,7 +47,7 @@ export default async function RunsPage() {
</div>
</div>

<RunIndexTable runs={runs} />
<SampleIndexTable runs={runs} />
</main>
);
}
14 changes: 7 additions & 7 deletions ergon-dashboard/src/components/dag/DAGCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* - Hierarchical dagre layout with nested container rendering
* - Depth-based expansion control via floating controls
* - Search/filter tasks by name
* - Live updates via useRunState hook
* - Live updates via useSampleWorkspaceState hook
* - Zoom/pan controls
*/

Expand All @@ -25,7 +25,7 @@ import {
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";

import { TaskStatus, type WorkflowRunState } from "@/lib/types";
import { TaskStatus, type SampleWorkspaceState } from "@/lib/types";
import { nodeTypes, type TaskNodeType } from "./TaskNode";
import { GraphDependencyEdge } from "./edges/GraphDependencyEdge";
import { buildContainerEvaluationRollup } from "@/features/evaluation/selectors";
Expand All @@ -36,8 +36,8 @@ import type { ContainerDimensions } from "@/features/graph/layout/layoutTypes";
import { SearchInput } from "@/components/common/SearchInput";

interface DAGCanvasProps {
runId: string;
runState: WorkflowRunState | null;
sampleId: string;
runState: SampleWorkspaceState | null;
isLoading?: boolean;
error?: string | null;
isSubscribed?: boolean;
Expand Down Expand Up @@ -235,7 +235,7 @@ function LegendCard() {
/* ─── Main canvas ───────────────────────────────────────────────── */

function DAGCanvasInner({
runId,
sampleId,
runState,
isLoading = false,
error = null,
Expand Down Expand Up @@ -444,7 +444,7 @@ function DAGCanvasInner({
</h3>
<p style={{ color: "var(--muted)" }}>{error}</p>
<p className="text-xs mt-2 font-mono" style={{ color: "var(--faint)" }}>
Run ID: {runId}
Sample ID: {sampleId}
</p>
</div>
</div>
Expand Down Expand Up @@ -474,7 +474,7 @@ function DAGCanvasInner({
: "Connecting to server..."}
</p>
<p className="text-xs mt-2 font-mono" style={{ color: "var(--faint)" }}>
Run ID: {runId}
Sample ID: {sampleId}
</p>
</div>
</div>
Expand Down
Loading
Loading