import type { ReactNode } from 'react';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Chip from '@mui/material/Chip';
import Typography from '@mui/material/Typography';
import Alert from '@mui/material/Alert';
import CircularProgress from '@mui/material/CircularProgress';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import { CheckCircle2, XCircle, MinusCircle } from 'lucide-react';
import type { GateLine } from '../../types/labeleval';
import { GATE_GLOSSARY, glossaryKeyForStatus } from '../../content/glossary';
import { ExplainTip, InfoDot } from '../help/InfoTip';
// ---------------------------------------------------------------- formatting
export function fmtInt(n: number | null | undefined): string {
if (n === null || n === undefined || Number.isNaN(n)) return '—';
return Math.round(n).toLocaleString();
}
export function fmtNum(n: number | null | undefined, digits = 3): string {
if (n === null || n === undefined || Number.isNaN(n)) return '—';
return n.toLocaleString(undefined, { maximumFractionDigits: digits });
}
/**
* Formats a rate as a percentage with 1 decimal. Values <= 1 are treated as
* fractions (0..1); values > 1 are assumed to already be percentages.
*/
export function fmtPct(v: number | null | undefined): string {
if (v === null || v === undefined || Number.isNaN(v)) return '—';
const pct = v <= 1 ? v * 100 : v;
return `${pct.toFixed(1)}%`;
}
export function pctFraction(v: number | null | undefined): number {
if (v === null || v === undefined || Number.isNaN(v)) return 0;
return v <= 1 ? v : v / 100;
}
// ---------------------------------------------------------------- MetricCard
export function MetricCard({
label,
value,
sub,
accent,
term,
info,
}: {
label: string;
value: ReactNode;
sub?: ReactNode;
accent?: string;
/** Glossary key: renders an info icon whose tooltip explains this metric. */
term?: string;
/** Ad-hoc explanation when no glossary key fits. */
info?: string;
}) {
return (
{label}
{term || info ? : null}
{value}
{sub ? (
{sub}
) : null}
);
}
// ---------------------------------------------------------------- StatusChip
const STATUS_COLORS: Record = {
// triage statuses
AUTO_GRADED: { bg: '#1b5e20', fg: '#a5d6a7' },
FLAGGED: { bg: '#e65100', fg: '#ffe0b2' },
VERIFIED: { bg: '#2e7d32', fg: '#c8e6c9' },
REJECTED: { bg: '#b71c1c', fg: '#ffcdd2' },
PENDING: { bg: '#37474f', fg: '#cfd8dc' },
// service states
HEALTHY: { bg: '#1b5e20', fg: '#a5d6a7' },
RUNNING: { bg: '#0d47a1', fg: '#90caf9' },
DEGRADED: { bg: '#f9a825', fg: '#212121' },
BLOCKED: { bg: '#e65100', fg: '#ffe0b2' },
FAILED: { bg: '#b71c1c', fg: '#ffcdd2' },
IDLE: { bg: '#37474f', fg: '#b0bec5' },
// severities
critical: { bg: '#b71c1c', fg: '#ffcdd2' },
high: { bg: '#e65100', fg: '#ffe0b2' },
medium: { bg: '#f9a825', fg: '#212121' },
low: { bg: '#37474f', fg: '#cfd8dc' },
info: { bg: '#0d47a1', fg: '#90caf9' },
warning: { bg: '#e65100', fg: '#ffe0b2' },
// model regression status
improved: { bg: '#1b5e20', fg: '#a5d6a7' },
regressed: { bg: '#b71c1c', fg: '#ffcdd2' },
baseline: { bg: '#37474f', fg: '#cfd8dc' },
unknown: { bg: '#37474f', fg: '#b0bec5' },
// review task status
open: { bg: '#e65100', fg: '#ffe0b2' },
in_review: { bg: '#0d47a1', fg: '#90caf9' },
resolved: { bg: '#1b5e20', fg: '#a5d6a7' },
// job status
queued: { bg: '#37474f', fg: '#cfd8dc' },
running: { bg: '#0d47a1', fg: '#90caf9' },
completed: { bg: '#1b5e20', fg: '#a5d6a7' },
failed: { bg: '#b71c1c', fg: '#ffcdd2' },
stopped: { bg: '#37474f', fg: '#b0bec5' },
};
export function StatusChip({ status, size = 'small' }: { status: string; size?: 'small' | 'medium' }) {
const colors = STATUS_COLORS[status] ?? { bg: '#37474f', fg: '#cfd8dc' };
const chip = (
);
// Auto-explain known statuses / failure reasons from the glossary.
const key = glossaryKeyForStatus(status);
if (!key) return chip;
return {chip};
}
// ---------------------------------------------------------------- GateLineList
function fmtGateValue(v: number | string | boolean): string {
if (typeof v === 'number') return v.toLocaleString(undefined, { maximumFractionDigits: 3 });
return String(v);
}
export function GateLineList({ checks }: { checks: GateLine[] }) {
if (!checks.length) {
return (
No gate results available.
);
}
return (
Gate
Actual
Threshold
Result
{checks.map((c, i) => (
{GATE_GLOSSARY[c.gate] ? (
{c.gate}
) : (
c.gate
)}
{fmtGateValue(c.actual)}
{fmtGateValue(c.threshold)}
{!c.applicable ? (
) : c.passed ? (
) : (
)}
))}
);
}
// ---------------------------------------------------------------- layout helpers
export function SectionCard({
title,
action,
children,
sx,
help,
helpTerm,
}: {
title: ReactNode;
action?: ReactNode;
children: ReactNode;
sx?: object;
/** Ad-hoc "what is this panel" explanation shown behind an info icon. */
help?: string;
/** Glossary key alternative to `help`. */
helpTerm?: string;
}) {
return (
{title}
{help || helpTerm ? (
) : null}
{action}
{children}
);
}
export function LoadingBox({ label = 'Loading…' }: { label?: string }) {
return (
{label}
);
}
export function ErrorNote({ error }: { error: string }) {
return (
{error.startsWith('API') ? error : `Backend unavailable: ${error}`}
);
}
export function EmptyState({
title,
message,
action,
}: {
title: string;
message: string;
action?: ReactNode;
}) {
return (
{title}
{message}
{action}
);
}
// ---------------------------------------------------------------- horizontal bar
export function HBar({
label,
value,
max,
color = '#4fc3f7',
valueLabel,
term,
info,
}: {
label: string;
value: number;
max: number;
color?: string;
valueLabel?: string;
/** Glossary key explaining this bar's meaning on hover. */
term?: string;
/** Ad-hoc explanation when no glossary key fits. */
info?: string;
}) {
const width = max > 0 ? Math.max(1.5, (value / max) * 100) : 0;
const labelNode =
term || info ? (
{label}
) : (
label
);
return (
{labelNode}
{valueLabel ?? value.toLocaleString()}
);
}