CoolFace
Apppublic

build-small-hackathon/Hackathon-IA-VisualNovel

sourceHugging Faceapache-2.0updated 3mo agoView on Hugging Face
5likes
debug.html518 linesDownload Raw Back to frontend
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>VN Debug Dashboard</title>7  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.6/dist/chart.umd.min.js"></script>8  <style>9    :root {10      --bg:      #0f1117;11      --surface: #1a1d27;12      --border:  #252836;13      --text:    #dde1f0;14      --muted:   #6e7891;15      --blue:    #4c9be8;16      --red:     #e05c5c;17      --green:   #56b87a;18      --yellow:  #e0a840;19      --orange:  #e09440;20      --purple:  #b07de8;21      --gray:    #4e5568;22    }23    * { box-sizing: border-box; margin: 0; padding: 0; }24    body {25      background: var(--bg);26      color: var(--text);27      font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;28      font-size: 13px;29      padding: 16px;30      min-height: 100vh;31    }32 33    /* ── Header ─────────────────────────────────────────────────── */34    header {35      display: flex;36      align-items: center;37      justify-content: space-between;38      margin-bottom: 16px;39    }40    header h1 {41      font-size: 17px;42      font-weight: 700;43      letter-spacing: -.02em;44    }45    header h1 span { color: var(--blue); }46    .status {47      display: flex;48      align-items: center;49      gap: 7px;50      font-size: 12px;51      color: var(--muted);52    }53    .dot {54      width: 8px; height: 8px;55      border-radius: 50%;56      background: var(--gray);57      flex-shrink: 0;58      transition: background 0.3s;59    }60    .dot.live  { background: var(--green); box-shadow: 0 0 6px var(--green); }61    .dot.error { background: var(--red); }62 63    /* ── KPI cards ──────────────────────────────────────────────── */64    .kpi-grid {65      display: grid;66      grid-template-columns: repeat(4, 1fr);67      gap: 10px;68      margin-bottom: 12px;69    }70    @media (max-width: 800px) { .kpi-grid { grid-template-columns: repeat(2, 1fr); } }71    .kpi {72      background: var(--surface);73      border: 1px solid var(--border);74      border-radius: 8px;75      padding: 12px 14px;76    }77    .kpi-label {78      font-size: 10px;79      font-weight: 600;80      text-transform: uppercase;81      letter-spacing: .07em;82      color: var(--muted);83      margin-bottom: 5px;84    }85    .kpi-value {86      font-size: 28px;87      font-weight: 800;88      line-height: 1;89      font-variant-numeric: tabular-nums;90      color: var(--text);91    }92    .kpi-sub {93      font-size: 11px;94      color: var(--muted);95      margin-top: 5px;96    }97    .c-blue   { color: var(--blue) !important; }98    .c-green  { color: var(--green) !important; }99    .c-yellow { color: var(--yellow) !important; }100    .c-red    { color: var(--red) !important; }101 102    /* ── Charts ──────────────────────────────────────────────────── */103    .charts-grid {104      display: grid;105      grid-template-columns: 1fr 1fr;106      gap: 10px;107      margin-bottom: 12px;108    }109    @media (max-width: 900px) { .charts-grid { grid-template-columns: 1fr; } }110    .card {111      background: var(--surface);112      border: 1px solid var(--border);113      border-radius: 8px;114      padding: 14px;115    }116    .card h2 {117      font-size: 10px;118      font-weight: 700;119      text-transform: uppercase;120      letter-spacing: .07em;121      color: var(--muted);122      margin-bottom: 10px;123    }124 125    /* ── Operations table ────────────────────────────────────────── */126    table {127      width: 100%;128      border-collapse: collapse;129      font-size: 12px;130    }131    th {132      text-align: right;133      font-size: 10px;134      font-weight: 700;135      text-transform: uppercase;136      letter-spacing: .05em;137      color: var(--muted);138      padding: 0 8px 8px;139      border-bottom: 1px solid var(--border);140    }141    th:first-child { text-align: left; }142    td {143      padding: 6px 8px;144      border-bottom: 1px solid var(--border);145      text-align: right;146      font-variant-numeric: tabular-nums;147      color: var(--text);148    }149    td:first-child { text-align: left; color: var(--muted); }150    tr:last-child td { border-bottom: none; }151    tr:hover td { background: rgba(255,255,255,.025); }152    .total-row td { font-weight: 700; color: var(--text) !important; }153 154    /* ── Actions ─────────────────────────────────────────────────── */155    .actions {156      display: flex;157      align-items: center;158      gap: 10px;159      margin-top: 12px;160    }161    button {162      background: var(--blue);163      color: #fff;164      border: none;165      border-radius: 6px;166      padding: 7px 14px;167      font-size: 12px;168      font-weight: 700;169      cursor: pointer;170      opacity: 1;171      transition: opacity .15s;172    }173    button:hover:not(:disabled) { opacity: .82; }174    button:disabled { opacity: .35; cursor: default; }175    #report-msg { font-size: 12px; color: var(--green); }176  </style>177</head>178<body>179 180<!-- ── Header ─────────────────────────────────────────────────────── -->181<header>182  <h1>VN <span>Debug</span> Dashboard</h1>183  <div class="status">184    <div class="dot" id="dot"></div>185    <span id="status-text">Connecting…</span>186  </div>187</header>188 189<!-- ── KPI cards ──────────────────────────────────────────────────── -->190<div class="kpi-grid">191  <div class="kpi">192    <div class="kpi-label">Turns</div>193    <div class="kpi-value c-blue" id="kpi-turns">—</div>194    <div class="kpi-sub">completed</div>195  </div>196  <div class="kpi">197    <div class="kpi-label">LLM avg</div>198    <div class="kpi-value" id="kpi-llm">—</div>199    <div class="kpi-sub" id="kpi-llm-sub">p95 —</div>200  </div>201  <div class="kpi">202    <div class="kpi-label">Image cache</div>203    <div class="kpi-value" id="kpi-cache">—</div>204    <div class="kpi-sub" id="kpi-cache-sub">hits / total</div>205  </div>206  <div class="kpi">207    <div class="kpi-label">Session</div>208    <div class="kpi-value c-blue" id="kpi-elapsed">—</div>209    <div class="kpi-sub" id="kpi-gpu">—</div>210  </div>211</div>212 213<!-- ── Charts ─────────────────────────────────────────────────────── -->214<div class="charts-grid">215  <div class="card">216    <h2>System Resources — last 2 min</h2>217    <canvas id="res-chart" height="170"></canvas>218  </div>219  <div class="card">220    <h2>Latency per Turn (ms, stacked)</h2>221    <canvas id="lat-chart" height="170"></canvas>222  </div>223</div>224 225<!-- ── Operations table ───────────────────────────────────────────── -->226<div class="card">227  <h2>Operations</h2>228  <table>229    <thead>230      <tr>231        <th>Operation</th>232        <th>n</th>233        <th>avg ms</th>234        <th>p95 ms</th>235        <th>max ms</th>236      </tr>237    </thead>238    <tbody id="ops-body"></tbody>239  </table>240</div>241 242<!-- ── Actions ────────────────────────────────────────────────────── -->243<div class="actions">244  <button id="save-btn" onclick="saveReport()">Save report now</button>245  <span id="report-msg"></span>246</div>247 248<script>249// ── Chart defaults ───────────────────────────────────────────────────────250Chart.defaults.color = '#6e7891';251Chart.defaults.borderColor = '#252836';252Chart.defaults.font.size = 10;253 254const GRID_COLOR = 'rgba(255,255,255,.04)';255const BASE_SCALES = {256  x: { ticks: { color: '#555', maxTicksLimit: 8 }, grid: { color: GRID_COLOR } },257  y: { ticks: { color: '#555' }, grid: { color: GRID_COLOR } },258};259 260// ── Resource chart (line) ────────────────────────────────────────────────261const resCtx = document.getElementById('res-chart').getContext('2d');262const resChart = new Chart(resCtx, {263  type: 'line',264  data: {265    labels: [],266    datasets: [267      {268        label: 'RAM %', yAxisID: 'yPct', data: [],269        borderColor: '#4c9be8', backgroundColor: 'rgba(76,155,232,.06)',270        borderWidth: 1.5, pointRadius: 0, fill: true, tension: 0.3,271      },272      {273        label: 'CPU %', yAxisID: 'yPct', data: [],274        borderColor: '#e05c5c', backgroundColor: 'rgba(224,92,92,.05)',275        borderWidth: 1.5, pointRadius: 0, fill: true, tension: 0.3,276      },277      {278        label: 'VRAM', yAxisID: 'yVram', data: [],279        borderColor: '#e09440', backgroundColor: 'rgba(224,148,64,.05)',280        borderWidth: 1.5, pointRadius: 0, fill: false, tension: 0.3,281      },282    ]283  },284  options: {285    animation: false,286    responsive: true,287    interaction: { mode: 'index', intersect: false },288    plugins: {289      legend: { labels: { color: '#888', boxWidth: 10, font: { size: 10 } } },290    },291    scales: {292      x: BASE_SCALES.x,293      yPct: {294        type: 'linear', position: 'left', min: 0, suggestedMax: 100,295        ticks: { color: '#555', callback: v => v + '%' },296        grid: { color: GRID_COLOR },297      },298      yVram: {299        type: 'linear', position: 'right', min: 0,300        ticks: { color: '#7a6040' },301        grid: { drawOnChartArea: false },302        display: false, // shown only when VRAM data present303      },304    },305  },306});307 308// ── Latency stacked bar ──────────────────────────────────────────────────309const latCtx = document.getElementById('lat-chart').getContext('2d');310const latChart = new Chart(latCtx, {311  type: 'bar',312  data: {313    labels: [],314    datasets: [315      { label: 'LLM',     data: [], backgroundColor: '#4c9be8', stack: 's' },316      { label: 'Painter', data: [], backgroundColor: '#56b87a', stack: 's' },317      { label: 'STT',     data: [], backgroundColor: '#b07de8', stack: 's' },318      { label: 'Compact', data: [], backgroundColor: '#e09440', stack: 's' },319      { label: 'Other',   data: [], backgroundColor: '#4e5568', stack: 's' },320    ]321  },322  options: {323    animation: false,324    responsive: true,325    plugins: {326      legend: { labels: { color: '#888', boxWidth: 10, font: { size: 10 } } },327      tooltip: { callbacks: { label: ctx => ` ${ctx.dataset.label}: ${Math.round(ctx.raw)} ms` } },328    },329    scales: {330      x: { ...BASE_SCALES.x, stacked: true },331      y: {332        ...BASE_SCALES.y, stacked: true,333        title: { display: true, text: 'ms', color: '#555', font: { size: 10 } },334      },335    },336  },337});338 339// ── Helpers ──────────────────────────────────────────────────────────────340function fmtMs(ms) {341  if (ms == null) return '—';342  return ms >= 1000 ? (ms / 1000).toFixed(1) + 's' : Math.round(ms) + 'ms';343}344function fmtElapsed(s) {345  if (s == null) return '—';346  const m = Math.floor(s / 60), sec = Math.floor(s % 60);347  return m > 0 ? `${m}m ${sec}s` : `${sec}s`;348}349function fmtTime(ts) {350  const d = new Date(ts * 1000);351  return d.getMinutes().toString().padStart(2, '0') + ':' + d.getSeconds().toString().padStart(2, '0');352}353function latencyClass(ms) {354  if (ms == null) return '';355  if (ms < 2000)  return 'c-green';356  if (ms < 6000)  return 'c-yellow';357  return 'c-red';358}359function cacheClass(rate) {360  if (rate == null) return '';361  if (rate >= 0.7) return 'c-green';362  if (rate >= 0.4) return 'c-yellow';363  return 'c-red';364}365 366const GPU_LABELS = { nvml: 'NVIDIA GPU', rocm: 'AMD ROCm', mps: 'Apple MPS (unified)' };367 368const OP_ORDER = [369  'total_turn', 'llm_direct', 'compact_memory', 'context_assembly',370  'apply_directives', 'save_memory', 'painter_backdrop', 'painter_sprite', 'stt',371];372const OP_NAMES = {373  total_turn:       'Turn total',374  llm_direct:       'LLM direct_turn',375  compact_memory:   'Compact memory (LLM)',376  context_assembly: 'Context assembly',377  apply_directives: 'apply_directives',378  save_memory:      'save_memory',379  painter_backdrop: 'Painter — backdrop',380  painter_sprite:   'Painter — sprite',381  stt:              'STT transcribe',382};383 384// ── Main update ──────────────────────────────────────────────────────────385function update(data) {386  // KPI: turns387  document.getElementById('kpi-turns').textContent = data.turns_completed ?? '—';388 389  // KPI: LLM390  const llm = data.ops?.llm_direct;391  const kpiLlm = document.getElementById('kpi-llm');392  kpiLlm.textContent = fmtMs(llm?.avg_ms);393  kpiLlm.className = 'kpi-value ' + latencyClass(llm?.avg_ms);394  document.getElementById('kpi-llm-sub').textContent =395    llm ? `p95 ${fmtMs(llm.p95_ms)}  ·  max ${fmtMs(llm.max_ms)}` : 'no data yet';396 397  // KPI: cache398  const rate = data.cache_hit_rate;399  const kpiCache = document.getElementById('kpi-cache');400  kpiCache.textContent = rate != null ? Math.round(rate * 100) + '%' : '—';401  kpiCache.className = 'kpi-value ' + cacheClass(rate);402  const tot = (data.cache_hits ?? 0) + (data.cache_misses ?? 0);403  document.getElementById('kpi-cache-sub').textContent =404    tot > 0 ? `${data.cache_hits} hits / ${tot} total` : 'no renders yet';405 406  // KPI: elapsed + GPU backend407  document.getElementById('kpi-elapsed').textContent = fmtElapsed(data.session_elapsed_s);408  document.getElementById('kpi-gpu').textContent =409    data.gpu_backend ? (GPU_LABELS[data.gpu_backend] ?? data.gpu_backend) : 'No GPU detected';410 411  // Resource chart412  const samples = data.samples ?? [];413  resChart.data.labels = samples.map(s => fmtTime(s.ts));414  resChart.data.datasets[0].data = samples.map(s => s.ram_pct ?? null);415  resChart.data.datasets[1].data = samples.map(s => s.cpu_pct ?? null);416 417  const hasVram = samples.some(s => s.vram_mb != null);418  if (hasVram) {419    // For nvml/rocm: use vram_pct on left axis if available.420    // For MPS (unified): express process RSS as % of total system RAM (ram_mb / ram_pct * 100).421    resChart.data.datasets[2].data = samples.map(s => {422      if (s.vram_mb == null) return null;423      if (s.vram_pct != null) return s.vram_pct;  // nvml / rocm424      // MPS: derive total RAM from ram_mb + ram_pct, express RSS as %425      if (s.ram_pct > 0) return (s.vram_mb / (s.ram_mb / (s.ram_pct / 100))) * 100;426      return null;427    });428    const vramLabel = data.gpu_backend === 'mps' ? 'RSS % (MPS)' : 'VRAM %';429    resChart.data.datasets[2].label = vramLabel;430    resChart.options.scales.yVram.display = false; // keep right axis hidden, same % scale431  } else {432    resChart.data.datasets[2].data = samples.map(() => null);433  }434  resChart.update('none');435 436  // Latency stacked bar437  const turns = data.turn_breakdown ?? [];438  latChart.data.labels = turns.map(t => `T${t.turn}`);439  latChart.data.datasets[0].data = turns.map(t => t.llm_direct ?? 0);440  latChart.data.datasets[1].data = turns.map(t => (t.painter_backdrop ?? 0) + (t.painter_sprite ?? 0));441  latChart.data.datasets[2].data = turns.map(t => t.stt ?? 0);442  latChart.data.datasets[3].data = turns.map(t => t.compact_memory ?? 0);443  latChart.data.datasets[4].data = turns.map(t => {444    const acc = (t.llm_direct ?? 0) + (t.painter_backdrop ?? 0) + (t.painter_sprite ?? 0)445              + (t.stt ?? 0) + (t.compact_memory ?? 0)446              + (t.apply_directives ?? 0) + (t.save_memory ?? 0);447    return Math.max(0, (t.total_turn ?? 0) - acc);448  });449  latChart.update('none');450 451  // Ops table452  const ops = data.ops ?? {};453  const keys = [...OP_ORDER.filter(k => k in ops), ...Object.keys(ops).filter(k => !OP_ORDER.includes(k))];454  const tbody = document.getElementById('ops-body');455  tbody.innerHTML = keys.map(k => {456    const o = ops[k];457    if (!o) return '';458    const isTot = k === 'total_turn';459    return `<tr class="${isTot ? 'total-row' : ''}">460      <td>${OP_NAMES[k] ?? k}</td>461      <td>${o.count}</td>462      <td class="${latencyClass(o.avg_ms)}">${Math.round(o.avg_ms)}</td>463      <td>${Math.round(o.p95_ms)}</td>464      <td>${Math.round(o.max_ms)}</td>465    </tr>`;466  }).join('');467}468 469// ── SSE connection with auto-reconnect ───────────────────────────────────470const dot = document.getElementById('dot');471const statusTxt = document.getElementById('status-text');472let src = null;473 474function connect() {475  if (src) { src.close(); src = null; }476  dot.className = 'dot';477  statusTxt.textContent = 'Connecting…';478 479  src = new EventSource('/debug/stream');480  src.onopen = () => {481    dot.className = 'dot live';482    statusTxt.textContent = 'Live';483  };484  src.onmessage = e => {485    try { update(JSON.parse(e.data)); } catch { /* ignore parse errors */ }486  };487  src.onerror = () => {488    dot.className = 'dot error';489    statusTxt.textContent = 'Reconnecting in 3s…';490    src.close(); src = null;491    setTimeout(connect, 3000);492  };493}494 495// ── Save report ──────────────────────────────────────────────────────────496async function saveReport() {497  const btn = document.getElementById('save-btn');498  const msg = document.getElementById('report-msg');499  btn.disabled = true;500  msg.style.color = 'var(--muted)';501  msg.textContent = 'Saving…';502  try {503    const r = await fetch('/debug/report');504    const d = await r.json();505    msg.style.color = 'var(--green)';506    msg.textContent = d.status === 'ok' ? '✓ Saved to runs/debug_*/' : '✗ Save failed';507  } catch {508    msg.style.color = 'var(--red)';509    msg.textContent = '✗ Request failed';510  }511  setTimeout(() => { msg.textContent = ''; btn.disabled = false; }, 4000);512}513 514connect();515</script>516</body>517</html>518