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()} ); }