diff --git a/frontend/app.js b/frontend/app.js index 9eee482..cb6aad3 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -14,8 +14,9 @@ const svPrompt = document.getElementById('sv-prompt'); const svSpeed = document.getElementById('sv-speed'); const svTokens = document.getElementById('sv-tokens'); - const svLoadStage = document.getElementById('sv-load-stage'); - const svReqs = document.getElementById('sv-reqs'); + const svLoadStage = document.getElementById('sv-load-stage'); + const svSleepTimer = document.getElementById('sv-sleep-timer'); + const svReqs = document.getElementById('sv-reqs'); // ── State class management ──────────────────────────────────────────────── const ALL_STATES = [ @@ -47,7 +48,7 @@ } function update(msg) { - const { state, model, loading, metrics, request_count } = msg; + const { state, model, loading, metrics, request_count, sleeping_since } = msg; applyState(state); @@ -92,6 +93,13 @@ svPrompt.textContent = svSpeed.textContent = svTokens.textContent = '—'; } + // Sleep timer + if (state === 'sleeping' && sleeping_since) { + startSleepTimer(sleeping_since); + } else { + stopSleepTimer(); + } + // Pulse dot on message wsDot.classList.remove('pulse'); void wsDot.offsetWidth; // force reflow to restart animation @@ -130,6 +138,40 @@ document.addEventListener('touchmove', e => { if (dragging) { e.preventDefault(); onDrag(e.touches[0].clientX); } }, { passive: false }); document.addEventListener('touchend', endDrag); + // ── Sleep timer ─────────────────────────────────────────────────────────── + let sleepingSince = null; + let sleepInterval = null; + + function formatDuration(sec) { + const h = Math.floor(sec / 3600); + const m = Math.floor((sec % 3600) / 60); + const s = sec % 60; + if (h > 0) return h + 'h ' + m + 'm'; + if (m > 0) return m + 'm ' + s + 's'; + return s + 's'; + } + + function tickSleepTimer() { + if (!sleepingSince) return; + const elapsed = Math.floor((Date.now() - new Date(sleepingSince).getTime()) / 1000); + svSleepTimer.textContent = formatDuration(elapsed); + } + + function startSleepTimer(since) { + if (sleepingSince === since) return; + sleepingSince = since; + clearInterval(sleepInterval); + tickSleepTimer(); + sleepInterval = setInterval(tickSleepTimer, 1000); + } + + function stopSleepTimer() { + sleepingSince = null; + clearInterval(sleepInterval); + sleepInterval = null; + svSleepTimer.textContent = '—'; + } + // ── HTTP polling fallback ───────────────────────────────────────────────── let pollTimer = null; const POLL_MS = 2000; diff --git a/frontend/index.html b/frontend/index.html index 3c179fb..8902875 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -147,6 +147,10 @@ STAGE — +