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 { cn } from "@/lib/utils" import { compareQueryOptions, NO_CURVE, runMetricsQueryOptions, STEP_AXIS, shortenRunLabel, TIME_AXIS, } from "./queries" /** 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. * * 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. * * `x` is what the readings are plotted against: the step they were recorded * at, the seconds since the run's own first one, or another metric of the same * runs. Never a clock — even "time" is an elapsed count, which is what makes * two runs started hours apart comparable. */ export function RunMetricChart({ ids, metric, x = STEP_AXIS, refreshMs, className, }: { ids: string[] metric: string x?: string refreshMs?: number className?: string }) { const { data, isPending } = useQuery( compareQueryOptions(ids, metric, x, refreshMs), ) const lines = (data?.lines ?? []).slice(0, MAX_SERIES) const labels = lines.map((line) => shortenRunLabel(line.label)) const plots: HistoryPoint[][] = lines.map((line) => (line.points ?? []).map(([at, value]) => ({ ts: at, 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 axis a picker sets, written beside it rather than into every option. * * The dropdown then lists names — "step", "loss" — instead of repeating "vs" * once per row, and which axis is being set is said once, where it belongs. */ function AxisLabel({ children }: { children: string }) { return ( {children} ) } /** The metric picker both the detail and the comparison sit under. */ export function MetricPicker({ names, value, axis, onChange, }: { names: string[] value: string /** Named where the chart has an axis for it to be; bare on its own. */ axis?: string onChange: (name: string) => void }) { if (names.length === 0) return null return (
{axis && {axis}}
) } /** What the comparison is plotted against. */ export function AxisPicker({ names, metric, value, axis = "X", onChange, }: { names: string[] /** Excluded from the choices: a metric against itself is a straight line. */ metric: string value: string axis?: string onChange: (x: string) => void }) { const options = [ { value: STEP_AXIS, label: "step" }, { value: TIME_AXIS, label: "time (s)" }, ...names .filter((name) => name !== metric) .map((name) => ({ value: name, label: name })), ] return (
{axis}
) }