import { useEffect, useRef } from "react"; import { fmtParams, latticeAxisOf } from "../spec.js"; // One column tried to hold the model summary, the sweep directions, the run // metrics and every layer at once, so everything was cramped and the layer // list — the part that changes as you watch — sat below the fold. Tabs put one // concern on screen at a time; the run metrics moved out entirely, to the dock // under the scene where a training curve has room to be a curve. const S = { panel: { width: 320, height: "100%", display: "flex", flexDirection: "column", background: "linear-gradient(180deg, #131926 0%, #10141d 42%, #0e121a 100%)", borderRight: "1px solid #1e2635", flexShrink: 0, }, head: { padding: "16px 18px 0" }, body: { flex: 1, overflowY: "auto", padding: "4px 18px 18px" }, h1: { fontSize: 15, fontWeight: 650, letterSpacing: 0.2, marginBottom: 2 }, sub: { color: "#8b95a8", fontSize: 12, marginBottom: 12 }, tabs: { display: "flex", gap: 2, padding: "0 12px", borderBottom: "1px solid #1e2635", }, tab: (active) => ({ flex: 1, background: "none", border: "none", borderBottom: active ? "2px solid #ffc061" : "2px solid transparent", color: active ? "#e8eef8" : "#7d8aa3", fontSize: 12.5, fontWeight: active ? 650 : 500, padding: "9px 4px", cursor: "pointer", letterSpacing: 0.3, }), section: { color: "#5d6b84", fontSize: 10.5, fontWeight: 700, letterSpacing: 1.4, textTransform: "uppercase", margin: "16px 2px 6px", }, card: { background: "#151b28", border: "1px solid #232d40", borderRadius: 8, padding: "10px 12px", }, row: { display: "flex", justifyContent: "space-between", padding: "2px 0", fontSize: 13, }, key: { color: "#8b95a8" }, warn: { background: "#2a1418", border: "1px solid #7f1d1d", color: "#fca5a5", borderRadius: 8, padding: "8px 12px", marginTop: 10, fontSize: 13, }, layer: (active) => ({ position: "relative", overflow: "hidden", display: "flex", alignItems: "center", gap: 8, padding: "6px 10px", borderRadius: 6, cursor: "pointer", fontSize: 13, background: active ? "#22304c" : "transparent", border: active ? "1px solid #41568a" : "1px solid transparent", boxShadow: active ? "inset 3px 0 0 #ffc061" : "none", }), // The sweep's progress *through the active layer*, drawn behind its row. layerFill: { position: "absolute", inset: 0, transformOrigin: "left center", background: "linear-gradient(90deg, rgba(255,192,97,0.20), rgba(255,192,97,0.05))", pointerEvents: "none", }, chip: { fontSize: 11, padding: "1px 7px", borderRadius: 99, background: "#233049", color: "#9fb3d9", }, controls: { display: "flex", gap: 8, marginTop: 10 }, btn: { background: "#1c2536", color: "#d6dbe4", border: "1px solid #2b3850", borderRadius: 6, padding: "7px 12px", cursor: "pointer", fontSize: 13, flex: 1, }, select: { background: "#1c2536", color: "#d6dbe4", border: "1px solid #2b3850", borderRadius: 6, padding: "6px 8px", fontSize: 13, width: "100%", }, switch: (on) => ({ display: "flex", alignItems: "center", gap: 10, width: "100%", background: on ? "#1d2b21" : "#1c2536", color: on ? "#9ece6a" : "#d6dbe4", border: `1px solid ${on ? "#2f6e42" : "#2b3850"}`, borderRadius: 8, padding: "10px 12px", cursor: "pointer", fontSize: 13.5, fontWeight: 600, textAlign: "left", }), knob: (on) => ({ width: 30, height: 17, borderRadius: 99, background: on ? "#2f6e42" : "#2b3850", position: "relative", flexShrink: 0, transition: "background 160ms ease", }), dot: (on) => ({ position: "absolute", top: 2, left: on ? 15 : 2, width: 13, height: 13, borderRadius: 99, background: on ? "#9ece6a" : "#7d8aa3", transition: "left 160ms ease", }), note: { color: "#5d6b84", fontSize: 11.5, marginTop: 8, lineHeight: 1.5 }, }; // Progress through the active layer, written straight to the DOM node. Going // through React state here would re-render the whole panel sixty times a // second to move one bar. function LayerFill({ anim }) { const ref = useRef(); useEffect(() => { let raf; const tick = () => { const el = ref.current; if (el) el.style.transform = `scaleX(${anim.current.progress})`; raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [anim]); return
; } function ModelTab({ parsed }) { const { spec } = parsed; const m = spec.model; const cells = spec.lattice.cells; return ( <>
model
model {m.kind}
d_model {m.d_model}
layers {m.n_layers}
parameters {fmtParams(m.n_params)}
method {spec.nd_method.name}
lattice {spec.lattice.shape.join(" × ") || "—"} {spec.lattice.time ? " + time" : ""}
cells {cells.present} / {cells.total} {cells.dense ? "" : " (sparse)"}
{spec.sweeps.unswept_axes.length > 0 && (
never swept: {spec.sweeps.unswept_axes.join(", ")} — these axes get no mixing.
)}
directions
{Object.entries(spec.sweeps.directions).map(([axis, dir]) => (
{axis} {dir === "both" ? "→ and ←" : dir === "forward" ? "→ only" : "← only"}
))} {(spec.sweeps.joint_axes ?? []).map((axis) => (
{axis} ⊕ joint
))} {(spec.sweeps.contracted_axes ?? []).map((axis) => (
{axis} {/* A contraction has no direction: the whole axis is mixed at once, every layer. Listing it as "→ only" would invent a property the model does not have. */} ⊗ contracted
))}
); } function LayersTab({ parsed, layerIndex, playing, onTogglePlay, onSelectLayer, anim, }) { const { spec } = parsed; const dirGlyph = (r) => (r ? "←" : "→"); return ( <>
sweep animation
{spec.layers.map((l, i) => { // A kernel-family layer contracts every spatial axis at once and, in // the hybrid form, sweeps time. Rendering it with the scan family's // "one axis, one arrow" was drawing sweeps that never happen — the // spec used to claim them (DEBUG.md #26). const kernel = l.kind === "kernel"; const joint = l.kind === "flatten"; const isTime = !kernel && !joint && latticeAxisOf(l, spec) === null; return (
onSelectLayer(i)} > {i === layerIndex && anim && } L{i} {joint ? ( <> {(l.axes ?? []).join(" ⊕ ")} {l.tokens ? `${l.tokens} tokens` : ""} ) : kernel ? ( <> {(l.contracted ?? []).join(" ⊗ ")} {l.axis ? `+ ${l.axis} →` : ""} ) : ( <> {l.axis} {isTime ? "→ (causal)" : dirGlyph(l.reverse)} )} {l.mixer && {l.mixer}}
); })} ); } function DataTab({ parsed, dataShow, onToggleData }) { const { spec } = parsed; const cells = spec.lattice.cells; const names = (spec.lattice.names ?? []).slice(spec.lattice.time ? 1 : 0); return ( <>
cell data
Each cell is labelled with its axis names and indices, and tinted by which side of the wavefront it is on. Only the cells nearest the camera get one: a label per cell across a rank-4 lattice is thousands of text meshes and an unreadable thicket besides. Zoom in to name more of them.
axes
{parsed.shape.map((size, i) => (
{names[i] ?? `dim${i}`} {size}
))} {spec.lattice.time && (
time dynamic
)}
presence
present {cells.present}
absent {cells.total - cells.present}
total {cells.total}
{!cells.dense && (
Absent cells are not drawn at all, and their values can never reach an output — that is tested bitwise, not asserted.
)} ); } const TABS = ["model", "layers", "data"]; export default function Sidebar({ parsed, layerIndex, playing, onTogglePlay, onSelectLayer, samples, sampleKey, onSample, onFile, anim, tab, onTab, dataShow, onToggleData, }) { const { spec } = parsed; return (
torch-dimensions
architecture viewer · spec v{spec.version}
{TABS.map((t) => ( ))}
{tab === "model" && } {tab === "layers" && ( )} {tab === "data" && ( )}
); }