Spaces:
Paused
Paused
File size: 4,800 Bytes
f6a5256 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | /** Shared building blocks for the Closed-Loop Lab page. */
import Box from '@mui/material/Box';
import Chip from '@mui/material/Chip';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import type { DataLabel } from '../../types/nextgen';
export const PANEL_SX = {
p: 1.5,
bgcolor: '#161c22',
border: '1px solid #232a31',
borderRadius: 1.5,
} as const;
const LABEL_COLORS: Record<DataLabel, string> = {
REAL: '#2e7d32',
REPLAYED: '#0277bd',
SIMULATED: '#6a1b9a',
GENERATED: '#ef6c00',
COUNTERFACTUAL: '#c62828',
};
export function DataLabelChip({ label }: { label: DataLabel | string }) {
const color = LABEL_COLORS[label as DataLabel] ?? '#455a64';
return (
<Chip
size="small"
label={label}
sx={{ height: 18, fontSize: 10, fontWeight: 700, bgcolor: color, color: '#fff' }}
/>
);
}
export function ScoreChip({ name, value }: { name: string; value: number }) {
const color = value >= 0.8 ? '#2e7d32' : value >= 0.5 ? '#e65100' : '#b71c1c';
return (
<Chip
size="small"
label={`${name} ${(value * 100).toFixed(0)}%`}
sx={{ height: 20, fontSize: 10.5, fontWeight: 700, bgcolor: '#12171d', color, border: `1px solid ${color}` }}
/>
);
}
export function MetricCard({
label,
value,
unit,
tone,
}: {
label: string;
value: string | number | null | undefined;
unit?: string;
tone?: 'good' | 'bad' | 'neutral';
}) {
const color = tone === 'good' ? '#81c784' : tone === 'bad' ? '#ef9a9a' : '#e0e3e7';
return (
<Paper sx={{ ...PANEL_SX, minWidth: 130, flex: '1 1 130px' }}>
<Typography variant="caption" sx={{ color: '#8a949e', display: 'block', fontSize: 10.5 }}>
{label}
</Typography>
<Typography variant="h6" sx={{ fontWeight: 800, fontSize: 18, color }}>
{value === null || value === undefined ? '—' : value}
{unit && value !== null && value !== undefined ? (
<Typography component="span" variant="caption" sx={{ color: '#8a949e', ml: 0.5 }}>
{unit}
</Typography>
) : null}
</Typography>
</Paper>
);
}
export interface Series {
name: string;
color: string;
points: { x: number; y: number }[];
}
/** Dependency-free SVG line chart (time series comparison). */
export function LineChart({
series,
height = 160,
xLabel,
yLabel,
}: {
series: Series[];
height?: number;
xLabel?: string;
yLabel?: string;
}) {
const all = series.flatMap((s) => s.points);
if (!all.length) return null;
const w = 560;
const pad = { l: 42, r: 10, t: 10, b: 24 };
const xs = all.map((p) => p.x);
const ys = all.map((p) => p.y);
const xMin = Math.min(...xs);
const xMax = Math.max(...xs) || 1;
const yMin = Math.min(0, ...ys);
const yMax = Math.max(...ys) * 1.08 || 1;
const sx = (x: number) => pad.l + ((x - xMin) / (xMax - xMin || 1)) * (w - pad.l - pad.r);
const sy = (y: number) => height - pad.b - ((y - yMin) / (yMax - yMin || 1)) * (height - pad.t - pad.b);
const yTicks = [yMin, (yMin + yMax) / 2, yMax];
const xTicks = [xMin, (xMin + xMax) / 2, xMax];
return (
<Box>
<svg viewBox={`0 0 ${w} ${height}`} style={{ width: '100%', maxWidth: w }}>
{yTicks.map((t, i) => (
<g key={`y${i}`}>
<line x1={pad.l} x2={w - pad.r} y1={sy(t)} y2={sy(t)} stroke="#232a31" strokeDasharray="3 3" />
<text x={pad.l - 6} y={sy(t) + 3} fill="#8a949e" fontSize={9} textAnchor="end">
{t.toFixed(1)}
</text>
</g>
))}
{xTicks.map((t, i) => (
<text key={`x${i}`} x={sx(t)} y={height - 8} fill="#8a949e" fontSize={9} textAnchor="middle">
{t.toFixed(1)}
</text>
))}
{series.map((s) => (
<polyline
key={s.name}
fill="none"
stroke={s.color}
strokeWidth={1.8}
points={s.points.map((p) => `${sx(p.x)},${sy(p.y)}`).join(' ')}
/>
))}
{yLabel ? (
<text x={10} y={pad.t + 4} fill="#8a949e" fontSize={9}>
{yLabel}
</text>
) : null}
{xLabel ? (
<text x={w - pad.r} y={height - 8} fill="#8a949e" fontSize={9} textAnchor="end">
{xLabel}
</text>
) : null}
</svg>
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap' }}>
{series.map((s) => (
<Box key={s.name} sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Box sx={{ width: 14, height: 3, bgcolor: s.color, borderRadius: 1 }} />
<Typography variant="caption" sx={{ color: '#c7ccd1', fontSize: 10.5 }}>
{s.name}
</Typography>
</Box>
))}
</Box>
</Box>
);
}
|