import { useQuery } from "@tanstack/react-query" import type { HistoryPoint } from "@/client" import { MAX_SERIES, UplotChart } from "@/components/Common/UplotChart" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { compareQueryOptions, runMetricsQueryOptions, shortId } from "./queries" /** * Why a finished run can have nothing to draw. * * A cache hit restores what a node returned, not the values it emitted along * the way, so a run whose training node was reused has a result and no curve. * Said here rather than left as an empty chart, which reads as a fault. */ export const NO_CURVE = "No curve was recorded. A node restored from the cache replays no emissions, so a run that reused an earlier one draws nothing here — its outputs are still on the result." /** The metrics one run recorded, in the order they are worth offering. */ export function useMetricNames(runId: string | undefined) { const { data } = useQuery({ ...runMetricsQueryOptions(runId ?? "", ""), enabled: Boolean(runId), }) const names = new Set() for (const point of data ?? []) if (point.name) names.add(point.name) return [...names] } /** * One metric across one or more runs, drawn on a step axis. * * The endpoint answers in the chart widget's own series shape, so comparing * three curves and showing one are the same call with a different id list. */ export function RunMetricChart({ ids, metric, refreshMs, }: { ids: string[] metric: string refreshMs?: number }) { const { data, isPending } = useQuery( compareQueryOptions(ids, metric, refreshMs), ) const lines = (data?.lines ?? []).slice(0, MAX_SERIES) const labels = lines.map((line) => // The endpoint labels a line with the whole run id, which is too long to // read in a legend beside four others. line.label.replace(/^\S+/, (id) => shortId(id)), ) const plots: HistoryPoint[][] = lines.map((line) => (line.points ?? []).map(([step, value]) => ({ ts: step, value })), ) const drawn = plots.reduce((total, plot) => total + plot.length, 0) return (
{drawn > 0 && (data?.lines?.length ?? 0) > MAX_SERIES && (

Showing {MAX_SERIES} of {data?.lines?.length} runs.

)}
) } /** The metric picker both the detail and the comparison sit under. */ export function MetricPicker({ names, value, onChange, }: { names: string[] value: string onChange: (name: string) => void }) { if (names.length === 0) return null return ( ) }