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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion apps/web/app/benchmarks/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import {
loadBenchmarkById,
loadBenchmarkArtifacts,
loadBenchmarkPairsById,
loadAnalysisIndex,
} from "../../../lib/data";
Expand Down Expand Up @@ -38,6 +39,7 @@ import {
import { DownloadArtifactLink } from "../../../components/DownloadArtifactLink";
import { CopyPageLink } from "../../../components/CopyPageLink";
import { RecentViewsTracker } from "../../../components/RecentViewsTracker";
import { buildBenchmarkCompareSuggestions } from "../../../lib/benchmarkCompareSuggestions";

export async function generateMetadata({
params,
Expand All @@ -58,8 +60,9 @@ export default async function BenchmarkDetailPage({
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const [benchmark, index] = await Promise.all([
const [benchmark, allBenchmarks, index] = await Promise.all([
loadBenchmarkById(id),
loadBenchmarkArtifacts(),
loadAnalysisIndex(),
]);

Expand Down Expand Up @@ -114,6 +117,9 @@ export default async function BenchmarkDetailPage({
const summaryDisplay = extractBenchmarkSummaryDisplay(benchmark);
const claimCentroidDisplay = extractClaimCentroidDisplay(benchmark);
const benchmarkTitle = `${benchmark.question.slice(0, 80)}${benchmark.question.length > 80 ? "…" : ""}`;
const compareSuggestions = buildBenchmarkCompareSuggestions(allBenchmarks, {
left: benchmark.id,
});

return (
<section className="stack">
Expand Down Expand Up @@ -213,6 +219,35 @@ export default async function BenchmarkDetailPage({
) : null}
</div>

{compareSuggestions.length > 0 ? (
<div className="card">
<h2 style={{ marginTop: 0 }}>Suggested comparisons</h2>
<p className="small muted" style={{ marginBottom: "1rem" }}>
Pair this benchmark with others on the same question or
model — one click to open the compare view.
</p>
<ul className="compare-suggestions-list">
{compareSuggestions.map((suggestion) => (
<li key={suggestion.id}>
<Link
href={suggestion.href}
className="button secondary"
>
Compare with {suggestion.id.slice(-16)}
</Link>
<span className="small muted">
{suggestion.reason} · {suggestion.model} ·{" "}
{suggestion.pipelinePreset} ·{" "}
{new Date(
suggestion.createdAt,
).toLocaleString()}
</span>
</li>
))}
</ul>
</div>
) : null}

{summaryDisplay.hasAny ? (
<div className="card">
<h2 style={{ marginTop: 0 }}>
Expand Down
59 changes: 42 additions & 17 deletions apps/web/app/catalog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -377,14 +377,28 @@ export default async function CatalogPage({
preset: string;
};
return (
<Link
href={filterHref("/runs", {
model: r.model,
preset: r.preset,
})}
>
Filter runs
</Link>
<span className="cell-compare-links">
<Link
href={filterHref("/runs", {
model: r.model,
preset: r.preset,
})}
>
Runs
</Link>
{" · "}
<Link
href={filterHref(
"/benchmarks",
{
model: r.model,
preset: r.preset,
},
)}
>
Benchmarks
</Link>
</span>
);
},
},
Expand All @@ -403,15 +417,26 @@ export default async function CatalogPage({
preset: string;
};
return (
<Link
href={filterHref("/runs", {
model: r.model,
preset: r.preset,
})}
className="button"
>
View runs
</Link>
<>
<Link
href={filterHref("/runs", {
model: r.model,
preset: r.preset,
})}
className="button"
>
View runs
</Link>
<Link
href={filterHref("/benchmarks", {
model: r.model,
preset: r.preset,
})}
className="button secondary"
>
Benchmarks
</Link>
</>
);
}}
/>
Expand Down
29 changes: 29 additions & 0 deletions apps/web/app/questions/view/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
summarizeQuestionHubBenchmarkMetrics,
summarizeQuestionHubMetrics,
} from "../../../lib/questionHubMetrics";
import { RecentViewsTracker } from "../../../components/RecentViewsTracker";

export const metadata: Metadata = {
title: "Question hub",
Expand All @@ -42,6 +43,8 @@ type QuestionViewSearchParams = {
model?: string;
preset?: string;
fast?: string;
from?: string;
to?: string;
};

export default async function QuestionHubPage({
Expand Down Expand Up @@ -78,6 +81,8 @@ export default async function QuestionHubPage({
model: params.model,
preset: params.preset,
fast: params.fast,
from: params.from,
to: params.to,
};
const runs = filterRunArtifacts(allRuns, filterParams);
const benchmarks = filterBenchmarkArtifacts(allBenchmarks, filterParams);
Expand Down Expand Up @@ -129,8 +134,16 @@ export default async function QuestionHubPage({
const benchmarkCompareRight = benchmarks[1]?.id;
const questionQuery: QuestionViewSearchParams = { question };

const questionTitle = `${question.slice(0, 80)}${question.length > 80 ? "…" : ""}`;

return (
<section className="stack">
<RecentViewsTracker
id={question}
kind="question"
href={questionHubHref(question)}
title={questionTitle}
/>
<div>
<h1 className="title">Question hub</h1>
<p className="subtitle">
Expand Down Expand Up @@ -222,6 +235,20 @@ export default async function QuestionHubPage({
<option value="true">Fast only</option>
<option value="false">Non-fast only</option>
</select>
<input
type="datetime-local"
name="from"
defaultValue={params.from ?? ""}
className="input"
title="Created at or after"
/>
<input
type="datetime-local"
name="to"
defaultValue={params.to ?? ""}
className="input"
title="Created at or before"
/>
</div>
<div className="filter-actions">
<button type="submit" className="button">
Expand Down Expand Up @@ -270,6 +297,8 @@ export default async function QuestionHubPage({
model: params.model,
preset: params.preset,
fast: params.fast,
from: params.from,
to: params.to,
},
)}`}
label="Copy URL"
Expand Down
6 changes: 3 additions & 3 deletions apps/web/components/RecentViewsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ export function RecentViewsMenu() {
id={menuId}
className="nav-recent-menu"
role="menu"
aria-label="Recently viewed artifacts"
aria-label="Recently viewed pages"
>
{entries.length === 0 ? (
<p className="nav-recent-empty">
Open a run or benchmark trace to build your recent
list.
Open a run, benchmark, or question hub to build your
recent list.
</p>
) : (
<ul className="nav-recent-list">
Expand Down
14 changes: 14 additions & 0 deletions apps/web/lib/recentViews.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ describe("recentViews", () => {
expect(entries[1].id).toBe("run_b");
});

it("records question hub views", () => {
recordRecentView({
id: "Is AI an existential threat?",
kind: "question",
href: "/questions/view?question=Is+AI+an+existential+threat%3F",
title: "Is AI an existential threat?",
});

const entries = readRecentViews();
expect(entries).toHaveLength(1);
expect(entries[0].kind).toBe("question");
expect(entries[0].title).toBe("Is AI an existential threat?");
});

it("clears stored recent views", () => {
recordRecentView({
id: "benchmark_1",
Expand Down
6 changes: 4 additions & 2 deletions apps/web/lib/recentViews.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type RecentViewKind = "run" | "benchmark";
export type RecentViewKind = "run" | "benchmark" | "question";

export type RecentViewEntry = {
id: string;
Expand All @@ -24,7 +24,9 @@ export function readRecentViews(): RecentViewEntry[] {
typeof entry.id === "string" &&
typeof entry.href === "string" &&
typeof entry.title === "string" &&
(entry.kind === "run" || entry.kind === "benchmark"),
(entry.kind === "run" ||
entry.kind === "benchmark" ||
entry.kind === "question"),
);
} catch {
return [];
Expand Down
Loading