general-eval-card / components /embed-eval-source.tsx
j-chim's picture
Merged-page review fixes: exclude flagged rows, wire comparability, thread card
c5c4016
Raw
History Blame Contribute Delete
6.23 kB
"use client"
// Shared data hook + source picker for the /embed/eval/* pages.
//
// Per-source (two-segment) embed ids behave exactly as before: one
// fetch, no picker. Merged (single-segment) ids default to the MERGED
// all-sources data and expose a compact "Source" selector so a viewer —
// or an embedder, via ?source=<composite_slug> — can pin one source's
// instantiation of the benchmark instead. Selecting a source swaps the
// embed's data to that source's per-source summary without navigating.
import { useEffect, useMemo, useState } from "react"
import { fetchEvalSummary, fetchMergedBenchmarkSummary } from "@/lib/dashboard-data-client"
import { isMergedBenchmarkSummary, mergedSummaryToEvalSummary } from "@/lib/merged-adapter"
import { isMergedEvalId } from "@/lib/utils"
import type { BenchmarkEvalSummary, MergedBenchmarkSummary } from "@/lib/eval-processing"
export interface EmbedSourceOption {
/** Composite slug — the ?source= value. */
slug: string
label: string
/** Two-segment per-source evaluation_id to fetch when selected. */
evaluationId: string
}
export function useEmbedEvalSummary(
evalId: string,
opts: { metricParam?: string | null; sourceParam?: string | null } = {},
): {
summary: BenchmarkEvalSummary | null
error: string | null
/** Non-null only for merged ids with at least one linkable source. */
sources: EmbedSourceOption[] | null
/** "" = merged (default). */
activeSource: string
setActiveSource: (slug: string) => void
} {
const isMerged = isMergedEvalId(evalId)
const metricId = opts.metricParam?.trim() || undefined
// `base` = the embed's default data: the adapted merged summary for
// merged ids, or the plain per-source summary otherwise.
const [base, setBase] = useState<BenchmarkEvalSummary | null>(null)
const [mergedRaw, setMergedRaw] = useState<MergedBenchmarkSummary | null>(null)
const [error, setError] = useState<string | null>(null)
const [activeSource, setActiveSource] = useState<string>(
() => opts.sourceParam?.trim() ?? "",
)
const [sourceSummary, setSourceSummary] = useState<BenchmarkEvalSummary | null>(null)
useEffect(() => {
let cancelled = false
if (!isMerged) {
fetchEvalSummary(evalId)
.then((s) => {
if (cancelled) return
setBase(s)
setError(null)
})
.catch((err) => {
if (!cancelled) setError(err instanceof Error ? err.message : String(err))
})
return () => {
cancelled = true
}
}
fetchMergedBenchmarkSummary(evalId, { metricId })
.then((payload) => {
if (cancelled) return
if (isMergedBenchmarkSummary(payload)) {
setMergedRaw(payload)
setBase(mergedSummaryToEvalSummary(payload))
setError(null)
} else if (payload && !("error" in payload)) {
// Pre-merged-view snapshot: the API answered with a plain summary.
setBase(payload)
setError(null)
} else {
setError("Evaluation not found")
}
})
.catch((err) => {
if (!cancelled) setError(err instanceof Error ? err.message : String(err))
})
return () => {
cancelled = true
}
}, [evalId, isMerged, metricId])
const sources: EmbedSourceOption[] | null = useMemo(() => {
if (!mergedRaw) return null
// Every source with a per-source page is pinnable — a pinned source
// shows ITS OWN per-source data, so sources that only report other
// metrics (reports_preferred=false, e.g. HELM Capabilities on gpqa)
// belong in the list too. Slice-only sources have no top-level page
// to pin, so they stay out.
const options = mergedRaw.aggregate_sources
.filter((s) => s.evaluation_id && !s.slice_only)
.map((s) => ({
slug: s.composite_slug,
label: s.composite_display_name || s.composite_slug,
evaluationId: s.evaluation_id!,
}))
return options.length > 0 ? options : null
}, [mergedRaw])
// Ignore a ?source= that names no linkable source (typo / stale link):
// fall back to merged rather than an empty embed.
const effectiveSource =
activeSource && sources?.some((s) => s.slug === activeSource) ? activeSource : ""
useEffect(() => {
if (!effectiveSource || !sources) {
setSourceSummary(null)
return
}
const target = sources.find((s) => s.slug === effectiveSource)
if (!target) return
let cancelled = false
setSourceSummary(null)
fetchEvalSummary(target.evaluationId)
.then((s) => {
if (cancelled) return
setSourceSummary(s)
setError(null)
})
.catch((err) => {
if (!cancelled) setError(err instanceof Error ? err.message : String(err))
})
return () => {
cancelled = true
}
}, [effectiveSource, sources])
// Clear any stale error when the viewer changes selection, so a failed
// pinned-source fetch doesn't leave the embed stuck on the error state
// after switching back to Merged (or to another source).
const selectSource = (slug: string) => {
setError(null)
setActiveSource(slug)
}
return {
summary: effectiveSource ? sourceSummary : base,
error,
sources,
activeSource: effectiveSource,
setActiveSource: selectSource,
}
}
/** Compact merged-vs-source selector, styled like the embeds' Split dropdown. */
export function EmbedSourcePicker({
sources,
value,
onChange,
}: {
sources: EmbedSourceOption[] | null
value: string
onChange: (slug: string) => void
}) {
if (!sources || sources.length === 0) return null
return (
<div className="mb-3 flex items-center gap-3">
<span
className="font-mono uppercase shrink-0"
style={{ fontSize: 10, letterSpacing: "0.14em", color: "var(--fg-subtle)" }}
>
Source
</span>
<select
className="ec-select"
value={value}
onChange={(e) => onChange(e.target.value)}
>
<option value="">Merged (all sources)</option>
{sources.map((s) => (
<option key={s.slug} value={s.slug}>
{s.label}
</option>
))}
</select>
</div>
)
}