feat(training): add intro banner and parameter tooltips to ML Training page

This commit is contained in:
KoalaTrapLord committed 2026-03-27 00:18:21 -04:00
1 parent 4e90337f54
commit e6f18d4a61
3 files changed
+297 -13

No files matched your search

+179
View File
@@ -358,3 +358,182 @@
grid-template-columns: 1fr;
}
}
/* ══════════════════════════════════════════════════════════════
Intro / Getting Started Banner
══════════════════════════════════════════════════════════════ */
.intro-banner {
background: color-mix(in srgb, var(--accent) 8%, var(--surface-1));
border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border));
border-radius: var(--radius-lg);
padding: var(--sp-4) var(--sp-5);
margin-bottom: var(--sp-5);
}
.intro-banner.hidden {
display: none;
}
.intro-banner-header {
display: flex;
align-items: center;
gap: var(--sp-2);
margin-bottom: var(--sp-3);
font-size: 15px;
color: var(--text);
}
.intro-banner-icon {
font-size: 18px;
}
.intro-banner-header strong {
flex: 1;
color: var(--accent);
}
.intro-banner-close {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 14px;
padding: 2px 6px;
border-radius: var(--radius-sm);
line-height: 1;
transition: color 0.15s, background 0.15s;
}
.intro-banner-close:hover {
color: var(--text);
background: var(--surface-3);
}
.intro-banner-lead {
font-size: 13px;
color: var(--text-secondary);
margin: 0 0 var(--sp-3) 0;
}
.intro-banner-steps {
margin: 0 0 var(--sp-4) 0;
padding-left: 20px;
display: flex;
flex-direction: column;
gap: var(--sp-2);
}
.intro-banner-steps li {
font-size: 13px;
color: var(--text);
line-height: 1.5;
}
.intro-banner-steps li strong {
color: var(--text);
}
.intro-banner-steps li em {
color: var(--text-secondary);
}
.intro-banner-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: var(--sp-3);
border-top: 1px solid var(--border);
}
/* ══════════════════════════════════════════════════════════════
Tooltip System (.tip-icon + data-tip)
══════════════════════════════════════════════════════════════ */
.tip-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 15px;
height: 15px;
border-radius: 50%;
background: var(--surface-3);
color: var(--text-secondary);
font-size: 10px;
font-weight: 700;
font-style: normal;
cursor: default;
position: relative;
vertical-align: middle;
margin-left: 4px;
flex-shrink: 0;
transition: background 0.15s, color 0.15s;
/* prevent tooltip from triggering form-label hover states */
user-select: none;
}
.tip-icon:hover {
background: var(--accent-dim);
color: var(--accent);
}
/* The tooltip bubble itself */
.tip-icon::after {
content: attr(data-tip);
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: var(--surface-0, #1a1a2e);
color: var(--text, #e0e0e0);
border: 1px solid var(--border, #333);
border-radius: var(--radius-md, 6px);
padding: 8px 12px;
font-size: 12px;
font-weight: 400;
line-height: 1.5;
white-space: normal;
width: 240px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
pointer-events: none;
opacity: 0;
transition: opacity 0.15s ease;
z-index: 1000;
text-align: left;
/* reset inherited label styles */
text-transform: none;
letter-spacing: 0;
}
/* Arrow pointing down */
.tip-icon::before {
content: '';
position: absolute;
bottom: calc(100% + 3px);
left: 50%;
transform: translateX(-50%);
border: 5px solid transparent;
border-top-color: var(--border, #333);
pointer-events: none;
opacity: 0;
transition: opacity 0.15s ease;
z-index: 1001;
}
.tip-icon:hover::after,
.tip-icon:hover::before {
opacity: 1;
}
/* Flip tooltip below when near the top of the viewport */
.tip-icon.tip-below::after {
bottom: auto;
top: calc(100% + 8px);
}
.tip-icon.tip-below::before {
bottom: auto;
top: calc(100% + 3px);
border-top-color: transparent;
border-bottom-color: var(--border, #333);
}
+71 -13
View File
@@ -18,9 +18,35 @@
<h1>ML Training Dashboard</h1>
<p>Train, evaluate, and deploy the AI opponent policy network</p>
</div>
<button id="btn-refresh-data" class="btn btn-secondary" onclick="refreshAll()" title="Refresh training data status">
<span class="btn-icon" id="refresh-icon">&#8635;</span> Refresh
</button>
<div style="display:flex;gap:8px;align-items:center;">
<button class="btn btn-ghost btn-sm" onclick="showIntroBanner()" title="Show getting started guide">&#10067; Help</button>
<button id="btn-refresh-data" class="btn btn-secondary" onclick="refreshAll()" title="Refresh training data status">
<span class="btn-icon" id="refresh-icon">&#8635;</span> Refresh
</button>
</div>
</div>
<!-- Intro / Getting Started Banner -->
<div id="training-intro-banner" class="intro-banner hidden">
<div class="intro-banner-header">
<span class="intro-banner-icon">&#127891;</span>
<strong>Getting Started with ML Training</strong>
<button class="intro-banner-close" onclick="dismissIntroBanner()" title="Dismiss">&#10005;</button>
</div>
<p class="intro-banner-lead">This page trains a neural network to play Commander decisions. Follow these steps in order on your first run:</p>
<ol class="intro-banner-steps">
<li><strong>Run batch simulations</strong> with ML logging enabled (from the Simulator page) to generate decision log files.</li>
<li><strong>Start Training</strong> below &mdash; leave &ldquo;Rebuild dataset&rdquo; checked so it parses your logs into a dataset automatically.</li>
<li>Check <strong>Evaluation Results</strong> after training. Aim for &gt;60% test accuracy before moving on.</li>
<li><em>(Optional)</em> Run <strong>PPO reinforcement learning</strong> to fine-tune the policy via self-play. Only recommended after a solid supervised baseline.</li>
<li>Run a <strong>Tournament</strong> to benchmark your model against heuristic and random agents and get a win-rate score.</li>
</ol>
<div class="intro-banner-footer">
<label class="form-checkbox" style="font-size:12px;">
<input type="checkbox" id="intro-dont-show" /> Don&rsquo;t show this again
</label>
<button class="btn btn-primary btn-sm" onclick="dismissIntroBanner()">Got it</button>
</div>
</div>
<!-- Status Overview Cards -->
@@ -59,19 +85,31 @@
<div class="train-controls">
<div class="param-grid form-row">
<div class="form-group">
<label class="form-label" for="param-epochs">Epochs</label>
<label class="form-label" for="param-epochs">
Epochs
<span class="tip-icon" data-tip="Number of full passes over the training dataset. More epochs improve fit but risk overfitting. 50 is a safe starting point.">?</span>
</label>
<input type="number" id="param-epochs" class="form-input" value="50" min="1" max="500" />
</div>
<div class="form-group">
<label class="form-label" for="param-lr">Learning Rate</label>
<label class="form-label" for="param-lr">
Learning Rate
<span class="tip-icon" data-tip="Controls how large each weight update is. Too high causes instability; too low is very slow. 0.001 is a reliable default.">?</span>
</label>
<input type="number" id="param-lr" class="form-input" value="0.001" step="0.0001" min="0.00001" max="0.1" />
</div>
<div class="form-group">
<label class="form-label" for="param-batch">Batch Size</label>
<label class="form-label" for="param-batch">
Batch Size
<span class="tip-icon" data-tip="Samples processed per gradient update. Larger batches are faster on GPU but use more VRAM. 256 is recommended for most setups.">?</span>
</label>
<input type="number" id="param-batch" class="form-input" value="256" min="16" max="2048" step="16" />
</div>
<div class="form-group">
<label class="form-label" for="param-patience">Early Stop Patience</label>
<label class="form-label" for="param-patience">
Early Stop Patience
<span class="tip-icon" data-tip="Stops training if validation loss doesn't improve for this many epochs in a row. Prevents wasted compute. 10 is a sensible default.">?</span>
</label>
<input type="number" id="param-patience" class="form-input" value="10" min="3" max="100" />
</div>
</div>
@@ -79,6 +117,7 @@
<label class="form-checkbox checkbox-label">
<input type="checkbox" id="param-rebuild" checked />
Rebuild dataset from decision logs
<span class="tip-icon" data-tip="Re-parses all .jsonl decision log files into a fresh dataset. Uncheck to reuse the last built dataset and save time on re-runs.">?</span>
</label>
</div>
<div class="train-actions">
@@ -132,19 +171,31 @@
<div class="train-controls">
<div class="param-grid form-row">
<div class="form-group">
<label class="form-label" for="ppo-iterations">Iterations</label>
<label class="form-label" for="ppo-iterations">
Iterations
<span class="tip-icon" data-tip="Each iteration runs N episodes, computes returns, and updates the policy. 100 iterations is a typical starting point.">?</span>
</label>
<input type="number" id="ppo-iterations" class="form-input" value="100" min="1" max="1000" />
</div>
<div class="form-group">
<label class="form-label" for="ppo-episodes">Episodes/Iter</label>
<label class="form-label" for="ppo-episodes">
Episodes/Iter
<span class="tip-icon" data-tip="Number of full game simulations per PPO update step. More episodes produce more stable gradients but slow down each iteration.">?</span>
</label>
<input type="number" id="ppo-episodes" class="form-input" value="64" min="8" max="512" step="8" />
</div>
<div class="form-group">
<label class="form-label" for="ppo-lr">Learning Rate</label>
<label class="form-label" for="ppo-lr">
Learning Rate
<span class="tip-icon" data-tip="PPO learning rate. Lower than supervised training is usually better — 0.0003 is a standard RL starting value.">?</span>
</label>
<input type="number" id="ppo-lr" class="form-input" value="0.0003" step="0.0001" />
</div>
<div class="form-group">
<label class="form-label" for="ppo-opponent">Opponent</label>
<label class="form-label" for="ppo-opponent">
Opponent
<span class="tip-icon" data-tip="Who the agent plays against during training. Heuristic is the best first opponent. Self-Play is more advanced and can be unstable early on.">?</span>
</label>
<select id="ppo-opponent" class="form-select">
<option value="heuristic">Heuristic</option>
<option value="random">Random</option>
@@ -156,6 +207,7 @@
<label class="form-checkbox checkbox-label">
<input type="checkbox" id="ppo-load-supervised" />
Initialize from supervised checkpoint (warm start)
<span class="tip-icon" data-tip="Strongly recommended. Starts PPO from your best supervised model instead of random weights. PPO from scratch is very slow and unstable.">?</span>
</label>
</div>
<div class="train-actions">
@@ -184,11 +236,17 @@
<div class="train-controls">
<div class="param-grid form-row tourney-param-grid">
<div class="form-group">
<label class="form-label" for="tourney-episodes">Games per Matchup</label>
<label class="form-label" for="tourney-episodes">
Games per Matchup
<span class="tip-icon" data-tip="Number of games played per policy pair. Use at least 50–100 games for a statistically reliable win rate.">?</span>
</label>
<input type="number" id="tourney-episodes" class="form-input" value="50" min="10" max="500" />
</div>
<div class="form-group">
<label class="form-label" for="tourney-style">Playstyle</label>
<label class="form-label" for="tourney-style">
Playstyle
<span class="tip-icon" data-tip="The deck archetype used during tournament games. Try Midrange first — it's the most balanced and representative of typical Commander gameplay.">?</span>
</label>
<select id="tourney-style" class="form-select">
<option value="midrange">Midrange</option>
<option value="aggro">Aggro</option>
+47
View File
@@ -19,6 +19,8 @@ let isTraining = false;
document.addEventListener('DOMContentLoaded', () => {
refreshDataStatus();
refreshTrainingStatus();
initIntroBanner();
initTooltipFlip();
});
async function refreshAll() {
@@ -36,6 +38,51 @@ async function refreshAll() {
}
}
// ═══════════════════════════════════════════
// Intro Banner
// ═══════════════════════════════════════════
const INTRO_STORAGE_KEY = 'trainingIntroSeen';
function initIntroBanner() {
const seen = localStorage.getItem(INTRO_STORAGE_KEY);
if (!seen) {
showIntroBanner();
}
}
function showIntroBanner() {
const banner = document.getElementById('training-intro-banner');
if (banner) banner.classList.remove('hidden');
}
function dismissIntroBanner() {
const banner = document.getElementById('training-intro-banner');
const dontShow = document.getElementById('intro-dont-show');
if (banner) banner.classList.add('hidden');
if (dontShow && dontShow.checked) {
localStorage.setItem(INTRO_STORAGE_KEY, '1');
}
}
// ═══════════════════════════════════════════
// Tooltip flip — prevent overflow at top
// ═══════════════════════════════════════════
function initTooltipFlip() {
document.querySelectorAll('.tip-icon').forEach(el => {
el.addEventListener('mouseenter', () => {
const rect = el.getBoundingClientRect();
// If less than 120px above the element, flip tooltip below
if (rect.top < 120) {
el.classList.add('tip-below');
} else {
el.classList.remove('tip-below');
}
});
});
}
// ═══════════════════════════════════════════
// Data Status
// ═══════════════════════════════════════════