/* ── ZONE 06 — Models: registry of every training run ── Ported from the Hover design project. Every train() call persists a new model server-side (never overwritten); push only moves which version the board pulls, so any past model (even one never previously live) can be re-pushed -- e.g. reverting to a model that tested better in the field. */ function relTime(ts) { const s = Math.max(0, Math.round((Date.now() - ts) / 1000)); if (s < 45) return 'just now'; const m = Math.round(s / 60); if (m < 60) return m + ' min ago'; const h = Math.round(m / 60); return h + ' h ago'; } function ModelNote({ note, onSave }) { const [editing, setEditing] = React.useState(false); const [draft, setDraft] = React.useState(note || ''); const save = () => { onSave(draft.trim()); setEditing(false); }; if (editing) { return ( setDraft(e.target.value)} onBlur={save} onKeyDown={e => { if (e.key === 'Enter') save(); if (e.key === 'Escape') { setDraft(note || ''); setEditing(false); } }} style={{ width: '100%', background: 'var(--bg-0)', border: '1px solid var(--olive)', borderRadius: 4, padding: '4px 8px', color: 'var(--t-1)', fontSize: 'var(--text-sm)', outline: 'none', boxShadow: '0 0 0 3px rgba(184,176,106,0.10)', }} /> ); } return note ? ( ) : ( ); } function ModelRow({ m, isLive, onPush, onNote }) { const good = m.tone === 'good'; return (
{/* version + when */}
v{m.version}
{relTime(m.trainedAt)}
{/* val + separability */}
{m.val.toFixed(2)} VAL
sep {m.separability.toFixed(2)}
{/* tone chip */} {good ? 'good' : 'flagged'} {/* dataset + note */}
{m.dataset.spans} spans {SARG.classes.filter(c => ((m.dataset.counts || {})[c.label] || 0) > 0).map(c => ( · {m.dataset.counts[c.label]} {c.short} ))}
onNote(m.version, txt)} />
{/* action */} {isLive ? ( live on board ) : ( )}
); } function ModelsZone({ models, liveVersion, onPush, onNote }) { return (
Model registry {models.length > 0 && ( {models.length} trained )}
{models.length === 0 ? (
No models yet — train one above to start the registry.
) : (
{[...models].sort((a, b) => b.version - a.version).map(m => ( ))}
Every training run is kept. Push any of them — the board just re-points, so you can fall back to an older model if a new one disappoints.
)}
); } Object.assign(window, { relTime, ModelNote, ModelRow, ModelsZone });