CoolFace
Apppublic

Rahmath1/self_improving_agent

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
dashboard.html858 linesDownload Raw Back to root
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="UTF-8">5<meta name="viewport" content="width=device-width, initial-scale=1.0">6<title>Self Improvement Agents โ€” Training Dashboard</title>7<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>8<style>9  @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syne:wght@400;600;800&display=swap');10 11  :root {12    --bg:       #050810;13    --surface:  #0d1220;14    --surface2: #111827;15    --border:   #1e2d45;16    --accent:   #00d4ff;17    --accent2:  #7c3aed;18    --green:    #10b981;19    --orange:   #f59e0b;20    --red:      #ef4444;21    --text:     #e2e8f0;22    --muted:    #64748b;23    --glow:     0 0 20px rgba(0,212,255,0.15);24  }25 26  * { box-sizing: border-box; margin: 0; padding: 0; }27 28  body {29    font-family: 'Syne', sans-serif;30    background: var(--bg);31    color: var(--text);32    min-height: 100vh;33    padding: 24px;34    background-image:35      radial-gradient(ellipse 80% 50% at 20% 10%, rgba(0,212,255,0.04) 0%, transparent 60%),36      radial-gradient(ellipse 60% 40% at 80% 80%, rgba(124,58,237,0.05) 0%, transparent 60%);37  }38 39  header {40    display: flex;41    align-items: center;42    justify-content: space-between;43    margin-bottom: 32px;44    padding-bottom: 20px;45    border-bottom: 1px solid var(--border);46  }47 48  .logo {49    font-family: 'Space Mono', monospace;50    font-size: 13px;51    color: var(--accent);52    letter-spacing: 2px;53    text-transform: uppercase;54  }55 56  h1 {57    font-size: 28px;58    font-weight: 800;59    background: linear-gradient(135deg, #fff 30%, var(--accent));60    -webkit-background-clip: text;61    -webkit-text-fill-color: transparent;62    margin-top: 4px;63  }64 65  .subtitle {66    font-size: 13px;67    color: var(--muted);68    margin-top: 4px;69    font-family: 'Space Mono', monospace;70  }71 72  .status-pill {73    display: flex;74    align-items: center;75    gap: 8px;76    background: rgba(16,185,129,0.1);77    border: 1px solid rgba(16,185,129,0.3);78    padding: 8px 16px;79    border-radius: 20px;80    font-size: 12px;81    color: var(--green);82    font-family: 'Space Mono', monospace;83  }84 85  .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green);86         animation: blink 1.5s infinite; }87  @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.3} }88 89  /* Controls */90  .controls {91    display: flex;92    gap: 12px;93    margin-bottom: 28px;94    flex-wrap: wrap;95    align-items: center;96  }97 98  .btn {99    font-family: 'Space Mono', monospace;100    font-size: 12px;101    padding: 10px 20px;102    border-radius: 8px;103    border: none;104    cursor: pointer;105    transition: all 0.2s;106    letter-spacing: 1px;107    text-transform: uppercase;108  }109 110  .btn-primary {111    background: var(--accent);112    color: #000;113    font-weight: 700;114  }115  .btn-primary:hover { box-shadow: 0 0 20px rgba(0,212,255,0.4); transform: translateY(-1px); }116 117  .btn-secondary {118    background: transparent;119    color: var(--text);120    border: 1px solid var(--border);121  }122  .btn-secondary:hover { border-color: var(--accent); color: var(--accent); }123 124  .btn-danger {125    background: transparent;126    color: var(--red);127    border: 1px solid rgba(239,68,68,0.3);128  }129  .btn-danger:hover { background: rgba(239,68,68,0.1); }130 131  select {132    font-family: 'Space Mono', monospace;133    font-size: 12px;134    background: var(--surface);135    color: var(--text);136    border: 1px solid var(--border);137    padding: 10px 16px;138    border-radius: 8px;139    cursor: pointer;140  }141 142  /* Metric cards */143  .metrics-row {144    display: grid;145    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));146    gap: 16px;147    margin-bottom: 28px;148  }149 150  .metric-card {151    background: var(--surface);152    border: 1px solid var(--border);153    border-radius: 12px;154    padding: 20px;155    position: relative;156    overflow: hidden;157    transition: border-color 0.3s;158  }159  .metric-card:hover { border-color: var(--accent); box-shadow: var(--glow); }160  .metric-card::before {161    content: '';162    position: absolute;163    top: 0; left: 0; right: 0;164    height: 2px;165    background: linear-gradient(90deg, var(--accent), var(--accent2));166  }167 168  .metric-label {169    font-size: 11px;170    color: var(--muted);171    text-transform: uppercase;172    letter-spacing: 1.5px;173    font-family: 'Space Mono', monospace;174    margin-bottom: 10px;175  }176 177  .metric-val {178    font-size: 32px;179    font-weight: 800;180    line-height: 1;181  }182 183  .metric-val.good  { color: var(--green); }184  .metric-val.warn  { color: var(--orange); }185  .metric-val.bad   { color: var(--red); }186  .metric-val.blue  { color: var(--accent); }187 188  .metric-delta {189    font-size: 12px;190    margin-top: 6px;191    font-family: 'Space Mono', monospace;192  }193  .metric-delta.up   { color: var(--green); }194  .metric-delta.down { color: var(--red); }195 196  /* Chart grid */197  .charts-grid {198    display: grid;199    grid-template-columns: 2fr 1fr;200    gap: 20px;201    margin-bottom: 20px;202  }203 204  .charts-bottom {205    display: grid;206    grid-template-columns: 1fr 1fr 1fr;207    gap: 20px;208    margin-bottom: 20px;209  }210 211  .card {212    background: var(--surface);213    border: 1px solid var(--border);214    border-radius: 12px;215    padding: 20px;216  }217 218  .card-title {219    font-size: 12px;220    color: var(--muted);221    text-transform: uppercase;222    letter-spacing: 1.5px;223    font-family: 'Space Mono', monospace;224    margin-bottom: 16px;225    display: flex;226    align-items: center;227    justify-content: space-between;228  }229 230  .card-badge {231    font-size: 10px;232    padding: 2px 8px;233    border-radius: 10px;234    background: rgba(0,212,255,0.1);235    color: var(--accent);236    border: 1px solid rgba(0,212,255,0.2);237  }238 239  canvas { max-height: 260px; }240 241  /* Comparison table */242  .comparison-table {243    width: 100%;244    border-collapse: collapse;245    font-family: 'Space Mono', monospace;246    font-size: 13px;247  }248 249  .comparison-table th {250    padding: 12px 16px;251    text-align: left;252    font-size: 11px;253    color: var(--muted);254    text-transform: uppercase;255    letter-spacing: 1px;256    border-bottom: 1px solid var(--border);257  }258 259  .comparison-table td {260    padding: 14px 16px;261    border-bottom: 1px solid rgba(30,45,69,0.5);262  }263 264  .comparison-table tr:last-child td { border-bottom: none; }265 266  .comparison-table tr:hover td { background: rgba(255,255,255,0.02); }267 268  .val-base    { color: var(--orange); }269  .val-ft      { color: var(--green); }270  .val-up      { color: var(--green); }271  .val-down    { color: var(--red); }272 273  /* Curriculum level bar */274  .level-bar {275    display: flex;276    gap: 6px;277    margin-top: 12px;278  }279 280  .level-dot {281    flex: 1;282    height: 6px;283    border-radius: 3px;284    background: var(--border);285    transition: background 0.5s;286  }287  .level-dot.active { background: var(--accent); }288  .level-dot.done   { background: var(--green); }289 290  /* Episode log */291  .episode-log {292    max-height: 200px;293    overflow-y: auto;294    font-family: 'Space Mono', monospace;295    font-size: 11px;296  }297 298  .log-entry {299    display: flex;300    justify-content: space-between;301    align-items: center;302    padding: 6px 0;303    border-bottom: 1px solid rgba(30,45,69,0.4);304  }305 306  .log-entry:last-child { border-bottom: none; }307 308  .log-task { color: var(--muted); }309  .log-score { font-weight: 700; }310  .log-badge {311    font-size: 10px;312    padding: 2px 6px;313    border-radius: 8px;314  }315  .log-badge.promoted { background: rgba(16,185,129,0.15); color: var(--green); border: 1px solid rgba(16,185,129,0.3); }316  .log-badge.normal   { background: rgba(100,116,139,0.15); color: var(--muted); }317  .log-badge.penalty  { background: rgba(239,68,68,0.15); color: var(--red); }318 319  /* Scrollbar */320  ::-webkit-scrollbar { width: 4px; }321  ::-webkit-scrollbar-track { background: transparent; }322  ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }323 324  .phase-toggle {325    display: flex;326    gap: 0;327    border: 1px solid var(--border);328    border-radius: 8px;329    overflow: hidden;330  }331  .phase-btn {332    padding: 8px 16px;333    font-family: 'Space Mono', monospace;334    font-size: 11px;335    cursor: pointer;336    border: none;337    background: transparent;338    color: var(--muted);339    transition: all 0.2s;340  }341  .phase-btn.active { background: var(--accent); color: #000; font-weight: 700; }342</style>343</head>344<body>345 346<header>347  <div>348    <div class="logo">EDA OpenEnv ยท Self-Improvement System</div>349    <h1>Training Dashboard</h1>350    <div class="subtitle">// Base vs Fine-tuned ยท Curriculum Progression ยท Injection Detection</div>351  </div>352  <div class="status-pill">353    <div class="dot"></div>354    Live Training355  </div>356</header>357 358<!-- Controls -->359<div class="controls">360  <button class="btn btn-primary" onclick="runSimulation()">โ–ถ Simulate Training</button>361  <button class="btn btn-secondary" onclick="resetAll()">โ†บ Reset</button>362  <select id="speedSelect">363    <option value="300">Speed: Fast</option>364    <option value="600" selected>Speed: Normal</option>365    <option value="1200">Speed: Slow</option>366  </select>367  <div class="phase-toggle">368    <button class="phase-btn active" onclick="setView('both')" id="btn-both">Both Phases</button>369    <button class="phase-btn" onclick="setView('base')" id="btn-base">Base Only</button>370    <button class="phase-btn" onclick="setView('ft')" id="btn-ft">Fine-tuned Only</button>371  </div>372</div>373 374<!-- Metrics Row -->375<div class="metrics-row">376  <div class="metric-card">377    <div class="metric-label">Base Avg Reward</div>378    <div class="metric-val warn" id="baseReward">โ€”</div>379    <div class="metric-delta" id="baseEps">0 episodes</div>380  </div>381  <div class="metric-card">382    <div class="metric-label">Fine-tuned Avg Reward</div>383    <div class="metric-val good" id="ftReward">โ€”</div>384    <div class="metric-delta up" id="ftDelta">โ€”</div>385  </div>386  <div class="metric-card">387    <div class="metric-label">Injection Catch Rate</div>388    <div class="metric-val blue" id="catchRate">โ€”</div>389    <div class="metric-delta" id="catchDelta">โ€”</div>390  </div>391  <div class="metric-card">392    <div class="metric-label">Curriculum Level</div>393    <div class="metric-val blue" id="currLevel">1</div>394    <div class="level-bar">395      <div class="level-dot active" id="l1"></div>396      <div class="level-dot" id="l2"></div>397      <div class="level-dot" id="l3"></div>398      <div class="level-dot" id="l4"></div>399      <div class="level-dot" id="l5"></div>400    </div>401  </div>402  <div class="metric-card">403    <div class="metric-label">Total Episodes</div>404    <div class="metric-val blue" id="totalEps">0</div>405    <div class="metric-delta" id="epsBreakdown">base: 0 ยท ft: 0</div>406  </div>407  <div class="metric-card">408    <div class="metric-label">Improvement</div>409    <div class="metric-val good" id="improvement">โ€”</div>410    <div class="metric-delta up" id="improvementNote">โ€”</div>411  </div>412</div>413 414<!-- Main Charts -->415<div class="charts-grid">416  <div class="card">417    <div class="card-title">418      Reward Progression419      <span class="card-badge">per episode</span>420    </div>421    <canvas id="rewardChart"></canvas>422  </div>423  <div class="card">424    <div class="card-title">425      Curriculum Levels426      <span class="card-badge">fine-tuned</span>427    </div>428    <canvas id="levelChart"></canvas>429  </div>430</div>431 432<!-- Bottom Charts -->433<div class="charts-bottom">434  <div class="card">435    <div class="card-title">Injection Detection Rate</div>436    <canvas id="injectionChart"></canvas>437  </div>438  <div class="card">439    <div class="card-title">Penalties per Episode</div>440    <canvas id="penaltyChart"></canvas>441  </div>442  <div class="card">443    <div class="card-title">Task Score by Type</div>444    <canvas id="taskChart"></canvas>445  </div>446</div>447 448<!-- Comparison Table + Episode Log -->449<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:20px">450  <div class="card">451    <div class="card-title">BASE vs FINE-TUNED Comparison</div>452    <table class="comparison-table" id="compTable">453      <tr>454        <th>Metric</th>455        <th>Base</th>456        <th>Fine-tuned</th>457        <th>Delta</th>458      </tr>459      <tr>460        <td>Avg Reward</td>461        <td class="val-base" id="ct-base-reward">โ€”</td>462        <td class="val-ft" id="ct-ft-reward">โ€”</td>463        <td class="val-up" id="ct-delta-reward">โ€”</td>464      </tr>465      <tr>466        <td>Inj Catch Rate</td>467        <td class="val-base" id="ct-base-catch">โ€”</td>468        <td class="val-ft" id="ct-ft-catch">โ€”</td>469        <td class="val-up" id="ct-delta-catch">โ€”</td>470      </tr>471      <tr>472        <td>Inj Executed</td>473        <td class="val-base" id="ct-base-inj">โ€”</td>474        <td class="val-ft" id="ct-ft-inj">โ€”</td>475        <td id="ct-delta-inj">โ€”</td>476      </tr>477      <tr>478        <td>False Positives</td>479        <td class="val-base" id="ct-base-fp">โ€”</td>480        <td class="val-ft" id="ct-ft-fp">โ€”</td>481        <td id="ct-delta-fp">โ€”</td>482      </tr>483      <tr>484        <td>Penalties</td>485        <td class="val-base" id="ct-base-pen">โ€”</td>486        <td class="val-ft" id="ct-ft-pen">โ€”</td>487        <td class="val-up" id="ct-delta-pen">โ€”</td>488      </tr>489    </table>490  </div>491  <div class="card">492    <div class="card-title">Episode Log</div>493    <div class="episode-log" id="episodeLog">494      <div style="color:var(--muted);text-align:center;padding:20px;font-size:11px;font-family:Space Mono">495        Click "Simulate Training" to begin496      </div>497    </div>498  </div>499</div>500 501<script>502// โ”€โ”€โ”€ Chart setup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€503const CHART_DEFAULTS = {504  responsive: true,505  maintainAspectRatio: true,506  plugins: { legend: { labels: { color: '#64748b', font: { family: 'Space Mono', size: 11 } } } },507  scales: {508    x: { ticks: { color: '#64748b', font: { family: 'Space Mono', size: 10 } }, grid: { color: 'rgba(30,45,69,0.5)' } },509    y: { ticks: { color: '#64748b', font: { family: 'Space Mono', size: 10 } }, grid: { color: 'rgba(30,45,69,0.5)' } },510  }511};512 513const rewardChart = new Chart(document.getElementById('rewardChart'), {514  type: 'line',515  data: {516    labels: [],517    datasets: [518      { label: 'Base Model', data: [], borderColor: '#f59e0b', backgroundColor: 'rgba(245,158,11,0.08)', tension: 0.4, pointRadius: 4, pointBackgroundColor: '#f59e0b' },519      { label: 'Fine-tuned', data: [], borderColor: '#00d4ff', backgroundColor: 'rgba(0,212,255,0.08)', tension: 0.4, pointRadius: 4, pointBackgroundColor: '#00d4ff' },520      { label: 'FT Rolling Avg', data: [], borderColor: '#10b981', borderDash: [5,3], backgroundColor: 'transparent', tension: 0.4, pointRadius: 0 },521    ]522  },523  options: { ...CHART_DEFAULTS, scales: { ...CHART_DEFAULTS.scales, y: { ...CHART_DEFAULTS.scales.y, min: 0, max: 1 } } }524});525 526const levelChart = new Chart(document.getElementById('levelChart'), {527  type: 'bar',528  data: { labels: ['๐ŸŒฑ Novice','๐Ÿ“š Learner','๐Ÿ”ง Practitioner','๐Ÿš€ Expert','๐Ÿ† Master'],529          datasets: [{ label: 'Episodes at Level', data: [0,0,0,0,0], backgroundColor: ['#1e3a5f','#1a4a6b','#0e6ea3','#0090cc','#00d4ff'], borderRadius: 6 }] },530  options: { ...CHART_DEFAULTS, plugins: { legend: { display: false } } }531});532 533const injectionChart = new Chart(document.getElementById('injectionChart'), {534  type: 'line',535  data: {536    labels: [],537    datasets: [538      { label: 'Base Catch %', data: [], borderColor: '#f59e0b', tension: 0.4, pointRadius: 3 },539      { label: 'FT Catch %',   data: [], borderColor: '#10b981', tension: 0.4, pointRadius: 3 },540    ]541  },542  options: { ...CHART_DEFAULTS, scales: { ...CHART_DEFAULTS.scales, y: { ...CHART_DEFAULTS.scales.y, min: 0, max: 100 } } }543});544 545const penaltyChart = new Chart(document.getElementById('penaltyChart'), {546  type: 'bar',547  data: {548    labels: [],549    datasets: [550      { label: 'Base', data: [], backgroundColor: 'rgba(245,158,11,0.5)', borderRadius: 4 },551      { label: 'Fine-tuned', data: [], backgroundColor: 'rgba(0,212,255,0.5)', borderRadius: 4 },552    ]553  },554  options: CHART_DEFAULTS555});556 557const taskChart = new Chart(document.getElementById('taskChart'), {558  type: 'radar',559  data: {560    labels: ['detect_missing','find_correlation','generate_insight'],561    datasets: [562      { label: 'Base',        data: [0,0,0], borderColor: '#f59e0b', backgroundColor: 'rgba(245,158,11,0.1)', pointBackgroundColor: '#f59e0b' },563      { label: 'Fine-tuned',  data: [0,0,0], borderColor: '#00d4ff', backgroundColor: 'rgba(0,212,255,0.1)', pointBackgroundColor: '#00d4ff' },564    ]565  },566  options: {567    responsive: true,568    plugins: { legend: { labels: { color: '#64748b', font: { family: 'Space Mono', size: 11 } } } },569    scales: { r: { min: 0, max: 1, ticks: { color: '#64748b', stepSize: 0.2, font: { size: 9 } }, grid: { color: 'rgba(30,45,69,0.5)' }, pointLabels: { color: '#94a3b8', font: { family: 'Space Mono', size: 10 } } } }570  }571});572 573// โ”€โ”€โ”€ Simulation State โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€574let simTimer = null;575let state = {576  episode: 0,577  baseScores: [], ftScores: [],578  basePenalties: [], ftPenalties: [],579  baseCatch: [], ftCatch: [],580  taskScores: { base: {dm:0,fc:0,gi:0,n:0}, ft: {dm:0,fc:0,gi:0,n:0} },581  levelCounts: [0,0,0,0,0],582  currLevel: 1,583  baseInjExecuted: 0, ftInjExecuted: 0,584  baseFP: 0, ftFP: 0,585  currentView: 'both',586  phase: 'base',587  phaseEps: 0,588};589 590const TASKS = ['detect_missing','find_correlation','generate_insight'];591const LEVEL_NAMES = ['๐ŸŒฑ Novice','๐Ÿ“š Learner','๐Ÿ”ง Practitioner','๐Ÿš€ Expert','๐Ÿ† Master'];592 593// Simulate realistic score curve for base vs fine-tuned594function genBaseScore(ep) {595  const base = 0.28 + Math.random() * 0.15;596  return Math.max(0.05, Math.min(0.98, base + (Math.random()-0.5)*0.08));597}598 599function genFTScore(ep, level) {600  // Fine-tuned model improves over episodes with curriculum601  const base = 0.35 + (ep / 40) * 0.45 + (level-1) * 0.06;602  return Math.max(0.10, Math.min(0.98, base + (Math.random()-0.5)*0.06));603}604 605function rollingAvg(arr, n=5) {606  if (!arr.length) return 0;607  const slice = arr.slice(-n);608  return slice.reduce((a,b)=>a+b,0)/slice.length;609}610 611function runSimulation() {612  if (simTimer) return;613  const speed = parseInt(document.getElementById('speedSelect').value);614  simTimer = setInterval(simulateStep, speed);615}616 617function simulateStep() {618  state.episode++;619  state.phaseEps++;620  const task = TASKS[(state.episode-1) % 3];621 622  if (state.episode <= 12) {623    // Phase 1: Base model624    state.phase = 'base';625    const score    = genBaseScore(state.phaseEps);626    const penalty  = Math.floor(Math.random() * 3);627    const catch_r  = 60 + Math.random() * 20;628    const injExec  = Math.random() < 0.15 ? 1 : 0;629 630    state.baseScores.push(score);631    state.basePenalties.push(penalty);632    state.baseCatch.push(catch_r);633    state.baseInjExecuted += injExec;634    if (Math.random() < 0.05) state.baseFP++;635 636    // Task scores637    const tk = task === 'detect_missing' ? 'dm' : task === 'find_correlation' ? 'fc' : 'gi';638    state.taskScores.base[tk] = (state.taskScores.base[tk] * state.taskScores.base.n + score) / (state.taskScores.base.n + 1);639    state.taskScores.base.n++;640 641    addLogEntry(state.episode, 'BASE', task, score, penalty, false);642 643  } else {644    // Phase 2: Fine-tuned model with curriculum645    state.phase = 'ft';646    const ftEp = state.phaseEps - 12;647    const score   = genFTScore(ftEp, state.currLevel);648    const penalty = Math.max(0, Math.floor(Math.random() * 2) - (state.currLevel > 2 ? 1 : 0));649    const catch_r = Math.min(98, 70 + ftEp * 1.5 + Math.random() * 10);650    const injExec = Math.random() < Math.max(0.02, 0.12 - ftEp*0.008) ? 1 : 0;651 652    state.ftScores.push(score);653    state.ftPenalties.push(penalty);654    state.ftCatch.push(catch_r);655    state.ftInjExecuted += injExec;656    if (Math.random() < 0.03) state.ftFP++;657 658    // Task scores659    const tk = task === 'detect_missing' ? 'dm' : task === 'find_correlation' ? 'fc' : 'gi';660    state.taskScores.ft[tk] = (state.taskScores.ft[tk] * state.taskScores.ft.n + score) / (state.taskScores.ft.n + 1);661    state.taskScores.ft.n++;662 663    // Curriculum progression664    state.levelCounts[state.currLevel-1]++;665    const ftAvg = rollingAvg(state.ftScores, 5);666    const thresholds = [0, 0.45, 0.60, 0.75, 0.88];667    if (state.currLevel < 5 && ftAvg >= thresholds[state.currLevel]) {668      state.currLevel++;669      addLogEntry(state.episode, 'FT', task, score, penalty, true);670    } else {671      addLogEntry(state.episode, 'FT', task, score, penalty, false);672    }673  }674 675  updateCharts();676  updateMetrics();677 678  // Stop after 24 episodes679  if (state.episode >= 24) {680    clearInterval(simTimer);681    simTimer = null;682    document.querySelector('.dot').style.background = '#10b981';683  }684}685 686function addLogEntry(ep, phase, task, score, penalties, promoted) {687  const log = document.getElementById('episodeLog');688  const p = log.querySelector('p');689  if (p) p.remove();690 691  const entry = document.createElement('div');692  entry.className = 'log-entry';693  const badgeClass = promoted ? 'promoted' : (penalties > 0 ? 'penalty' : 'normal');694  const badgeText  = promoted ? '๐Ÿš€ PROMOTED' : (penalties > 0 ? `${penalties} pen` : 'ok');695  const scoreColor = score > 0.6 ? 'var(--green)' : score > 0.35 ? 'var(--orange)' : 'var(--red)';696  entry.innerHTML = `697    <span class="log-task">Ep${ep} [${phase}] ${task.replace('_',' ')}</span>698    <span class="log-score" style="color:${scoreColor}">${score.toFixed(3)}</span>699    <span class="log-badge ${badgeClass}">${badgeText}</span>700  `;701  log.insertBefore(entry, log.firstChild);702}703 704function updateCharts() {705  const epLabels = Array.from({length: state.episode}, (_,i) => `Ep${i+1}`);706 707  // Reward chart โ€” pad shorter array708  const baseData = [...state.baseScores, ...new Array(Math.max(0, state.ftScores.length)).fill(null)];709  const ftData   = [...new Array(state.baseScores.length).fill(null), ...state.ftScores];710 711  // Rolling avg for FT712  const ftRolling = state.ftScores.map((_, i) => {713    const slice = state.ftScores.slice(Math.max(0,i-4), i+1);714    return slice.reduce((a,b)=>a+b,0)/slice.length;715  });716  const ftRollingPadded = [...new Array(state.baseScores.length).fill(null), ...ftRolling];717 718  rewardChart.data.labels   = epLabels;719  rewardChart.data.datasets[0].data = baseData;720  rewardChart.data.datasets[1].data = ftData;721  rewardChart.data.datasets[2].data = ftRollingPadded;722  rewardChart.update('none');723 724  // Level chart725  levelChart.data.datasets[0].data = [...state.levelCounts];726  levelChart.update('none');727 728  // Injection chart729  const injLabels = Array.from({length: state.episode}, (_,i) => `Ep${i+1}`);730  const baseCatchPadded = [...state.baseCatch, ...new Array(state.ftCatch.length).fill(null)];731  const ftCatchPadded   = [...new Array(state.baseCatch.length).fill(null), ...state.ftCatch];732  injectionChart.data.labels = injLabels;733  injectionChart.data.datasets[0].data = baseCatchPadded;734  injectionChart.data.datasets[1].data = ftCatchPadded;735  injectionChart.update('none');736 737  // Penalty chart738  const allPenLabels = Array.from({length: state.episode}, (_,i) => `Ep${i+1}`);739  const basePenPadded = [...state.basePenalties, ...new Array(state.ftPenalties.length).fill(0)];740  const ftPenPadded   = [...new Array(state.basePenalties.length).fill(0), ...state.ftPenalties];741  penaltyChart.data.labels = allPenLabels;742  penaltyChart.data.datasets[0].data = basePenPadded;743  penaltyChart.data.datasets[1].data = ftPenPadded;744  penaltyChart.update('none');745 746  // Task radar chart747  const ts = state.taskScores;748  taskChart.data.datasets[0].data = [ts.base.dm||0, ts.base.fc||0, ts.base.gi||0];749  taskChart.data.datasets[1].data = [ts.ft.dm||0,   ts.ft.fc||0,   ts.ft.gi||0];750  taskChart.update('none');751}752 753function updateMetrics() {754  const baseAvg = state.baseScores.length ? rollingAvg(state.baseScores, state.baseScores.length) : 0;755  const ftAvg   = state.ftScores.length   ? rollingAvg(state.ftScores, state.ftScores.length)     : 0;756  const delta   = ftAvg - baseAvg;757 758  // Cards759  document.getElementById('baseReward').textContent  = state.baseScores.length ? baseAvg.toFixed(3) : 'โ€”';760  document.getElementById('baseEps').textContent     = `${state.baseScores.length} episodes`;761  document.getElementById('ftReward').textContent    = state.ftScores.length ? ftAvg.toFixed(3) : 'โ€”';762  document.getElementById('ftDelta').textContent     = state.ftScores.length ? `${delta > 0 ? 'โ†‘' : 'โ†“'} ${Math.abs(delta).toFixed(3)} vs base` : 'โ€”';763  document.getElementById('currLevel').textContent   = state.currLevel;764  document.getElementById('totalEps').textContent    = state.episode;765  document.getElementById('epsBreakdown').textContent = `base: ${state.baseScores.length} ยท ft: ${state.ftScores.length}`;766  document.getElementById('improvement').textContent  = state.ftScores.length ? `${delta >= 0 ? '+' : ''}${delta.toFixed(3)}` : 'โ€”';767  document.getElementById('improvementNote').textContent = state.ftScores.length ? `${((delta/Math.max(baseAvg,0.01))*100).toFixed(0)}% improvement` : 'โ€”';768 769  // Injection metrics770  const baseCatchAvg = state.baseCatch.length ? state.baseCatch.reduce((a,b)=>a+b,0)/state.baseCatch.length : 0;771  const ftCatchAvg   = state.ftCatch.length   ? state.ftCatch.reduce((a,b)=>a+b,0)/state.ftCatch.length   : 0;772  document.getElementById('catchRate').textContent = state.ftCatch.length ? `${ftCatchAvg.toFixed(0)}%` : 'โ€”';773  document.getElementById('catchDelta').textContent = state.ftCatch.length ? `base: ${baseCatchAvg.toFixed(0)}% ยท ft: ${ftCatchAvg.toFixed(0)}%` : 'โ€”';774 775  // Level dots776  for (let i = 1; i <= 5; i++) {777    const dot = document.getElementById(`l${i}`);778    dot.className = 'level-dot' + (i < state.currLevel ? ' done' : i === state.currLevel ? ' active' : '');779  }780 781  // Comparison table782  if (state.baseScores.length) {783    const basePenTotal = state.basePenalties.reduce((a,b)=>a+b,0);784    const ftPenTotal   = state.ftPenalties.reduce((a,b)=>a+b,0);785 786    document.getElementById('ct-base-reward').textContent = `+${baseAvg.toFixed(2)}`;787    document.getElementById('ct-ft-reward').textContent   = `+${ftAvg.toFixed(2)}`;788    document.getElementById('ct-delta-reward').textContent = `${delta >= 0 ? '+' : ''}${delta.toFixed(2)}`;789    document.getElementById('ct-delta-reward').className  = delta >= 0 ? 'val-up' : 'val-down';790 791    document.getElementById('ct-base-catch').textContent = `${baseCatchAvg.toFixed(0)}%`;792    document.getElementById('ct-ft-catch').textContent   = `${ftCatchAvg.toFixed(0)}%`;793    const catchDelta = ftCatchAvg - baseCatchAvg;794    document.getElementById('ct-delta-catch').textContent = `${catchDelta >= 0 ? '+' : ''}${catchDelta.toFixed(0)}%`;795    document.getElementById('ct-delta-catch').className  = catchDelta >= 0 ? 'val-up' : 'val-down';796 797    document.getElementById('ct-base-inj').textContent = state.baseInjExecuted;798    document.getElementById('ct-ft-inj').textContent   = state.ftInjExecuted;799    const injDelta = state.ftInjExecuted - state.baseInjExecuted;800    document.getElementById('ct-delta-inj').textContent = `${injDelta >= 0 ? '+' : ''}${injDelta}`;801    document.getElementById('ct-delta-inj').className  = injDelta <= 0 ? 'val-up' : 'val-down';802 803    document.getElementById('ct-base-fp').textContent = state.baseFP;804    document.getElementById('ct-ft-fp').textContent   = state.ftFP;805    const fpDelta = state.ftFP - state.baseFP;806    document.getElementById('ct-delta-fp').textContent = `${fpDelta >= 0 ? '+' : ''}${fpDelta}`;807    document.getElementById('ct-delta-fp').className  = fpDelta <= 0 ? 'val-up' : 'val-down';808 809    document.getElementById('ct-base-pen').textContent = basePenTotal;810    document.getElementById('ct-ft-pen').textContent   = ftPenTotal;811    const penDelta = ftPenTotal - basePenTotal;812    document.getElementById('ct-delta-pen').textContent = `${penDelta >= 0 ? '+' : ''}${penDelta}`;813    document.getElementById('ct-delta-pen').className  = penDelta <= 0 ? 'val-up' : 'val-down';814  }815}816 817function resetAll() {818  clearInterval(simTimer); simTimer = null;819  state = {820    episode:0, baseScores:[], ftScores:[],821    basePenalties:[], ftPenalties:[],822    baseCatch:[], ftCatch:[],823    taskScores:{base:{dm:0,fc:0,gi:0,n:0},ft:{dm:0,fc:0,gi:0,n:0}},824    levelCounts:[0,0,0,0,0], currLevel:1,825    baseInjExecuted:0, ftInjExecuted:0, baseFP:0, ftFP:0,826    currentView:'both', phase:'base', phaseEps:0,827  };828  [rewardChart, levelChart, injectionChart, penaltyChart, taskChart].forEach(c => {829    c.data.labels = [];830    c.data.datasets.forEach(d => d.data = []);831    c.update();832  });833  levelChart.data.datasets[0].data = [0,0,0,0,0];834  levelChart.update();835  taskChart.data.datasets[0].data = [0,0,0];836  taskChart.data.datasets[1].data = [0,0,0];837  taskChart.update();838 839  ['baseReward','ftReward','catchRate','improvement'].forEach(id => document.getElementById(id).textContent = 'โ€”');840  document.getElementById('currLevel').textContent = '1';841  document.getElementById('totalEps').textContent  = '0';842  for(let i=1;i<=5;i++){843    const d=document.getElementById(`l${i}`);844    d.className='level-dot'+(i===1?' active':'');845  }846  document.getElementById('episodeLog').innerHTML =847    '<div style="color:var(--muted);text-align:center;padding:20px;font-size:11px;font-family:Space Mono">Click "Simulate Training" to begin</div>';848}849 850function setView(v) {851  state.currentView = v;852  ['both','base','ft'].forEach(b => {853    document.getElementById(`btn-${b}`).classList.toggle('active', b===v);854  });855}856</script>857</body>858</html>