fix: wss support, polling fallback, cancellation, offline labels, screen sizing
- frontend/app.js: use wss:// when page is served over HTTPS; add HTTP polling
fallback (/state every 2s) when WebSocket is down, amber dot = polling mode;
stop polling on WS reconnect; clearer state labels (LOG OFFLINE, LOAD ERROR,
DISCONNECTED); fix replace('_',' ') to use regex for multi-underscore states
- frontend/style.css: add #ws-dot.polling (amber); min-height 280px on screen;
shrink llama-wrap/SVG; tighten screen padding and gap
- log_parser.py: detect cancelled requests (stop: cancel task / Connection
handling canceled) → idle; fix: switch to generating at progress=1.00
- server.py: add GET /state endpoint for polling fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
const wsDot = document.getElementById('ws-dot');
|
||||
const statsPanel = document.getElementById('stats-panel');
|
||||
const resizeHandle = document.getElementById('resize-handle');
|
||||
const stateLabel = document.getElementById('state-label');
|
||||
const stateLabelEl = document.getElementById('state-label');
|
||||
const progressFill = document.getElementById('progress-fill');
|
||||
const progressLabel = document.getElementById('progress-label');
|
||||
const svModel = document.getElementById('sv-model');
|
||||
@@ -23,6 +23,15 @@
|
||||
'sleeping','waiting','processing_prompt','generating','unloading','error'
|
||||
];
|
||||
|
||||
const STATE_LABELS = {
|
||||
offline: 'LOG OFFLINE', // llamagochi can't read the log file
|
||||
error: 'LOAD ERROR',
|
||||
};
|
||||
|
||||
function stateLabel(stateName) {
|
||||
return STATE_LABELS[stateName] ?? stateName.replace(/_/g, ' ').toUpperCase();
|
||||
}
|
||||
|
||||
function applyState(stateName) {
|
||||
ALL_STATES.forEach(s => body.classList.remove('state-' + s));
|
||||
body.classList.add('state-' + (ALL_STATES.includes(stateName) ? stateName : 'offline'));
|
||||
@@ -42,8 +51,8 @@
|
||||
|
||||
applyState(state);
|
||||
|
||||
stateLabel.textContent = state.replace('_', ' ').toUpperCase();
|
||||
svState.textContent = state.replace('_', ' ');
|
||||
stateLabelEl.textContent = stateLabel(state);
|
||||
svState.textContent = stateLabel(state).toLowerCase();
|
||||
svModel.textContent = model || '—';
|
||||
svReqs.textContent = request_count ?? 0;
|
||||
|
||||
@@ -111,16 +120,43 @@
|
||||
document.addEventListener('touchmove', e => { if (dragging) { e.preventDefault(); onDrag(e.touches[0].clientX); } }, { passive: false });
|
||||
document.addEventListener('touchend', endDrag);
|
||||
|
||||
// ── HTTP polling fallback ─────────────────────────────────────────────────
|
||||
let pollTimer = null;
|
||||
const POLL_MS = 2000;
|
||||
|
||||
async function pollOnce() {
|
||||
try {
|
||||
const r = await fetch('/state');
|
||||
if (!r.ok) throw new Error(r.status);
|
||||
update(await r.json());
|
||||
wsDot.className = 'polling';
|
||||
} catch {
|
||||
wsDot.className = ''; // red: llamagochi itself unreachable
|
||||
}
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (pollTimer) return;
|
||||
pollOnce();
|
||||
pollTimer = setInterval(pollOnce, POLL_MS);
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
clearInterval(pollTimer);
|
||||
pollTimer = null;
|
||||
}
|
||||
|
||||
// ── WebSocket with auto-reconnect ─────────────────────────────────────────
|
||||
let ws = null;
|
||||
let retryDelay = 1000;
|
||||
const MAX_DELAY = 30000;
|
||||
|
||||
function connect() {
|
||||
const url = 'ws://' + location.host + '/ws';
|
||||
const url = (location.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + location.host + '/ws';
|
||||
ws = new WebSocket(url);
|
||||
|
||||
ws.addEventListener('open', () => {
|
||||
stopPolling();
|
||||
wsDot.className = 'connected';
|
||||
retryDelay = 1000;
|
||||
});
|
||||
@@ -134,9 +170,7 @@
|
||||
});
|
||||
|
||||
ws.addEventListener('close', () => {
|
||||
wsDot.className = '';
|
||||
applyState('offline');
|
||||
stateLabel.textContent = 'DISCONNECTED';
|
||||
startPolling();
|
||||
setTimeout(connect, retryDelay);
|
||||
retryDelay = Math.min(retryDelay * 2, MAX_DELAY);
|
||||
});
|
||||
|
||||
@@ -46,15 +46,15 @@ html, body {
|
||||
#screen {
|
||||
position: relative;
|
||||
width: 280px;
|
||||
min-height: 380px;
|
||||
min-height: 280px;
|
||||
background: #0d0d06;
|
||||
border: 3px solid var(--amber-dim);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem 1rem 1rem;
|
||||
padding: 1.2rem 1rem 0.8rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
gap: 0.5rem;
|
||||
/* CRT scanline overlay */
|
||||
background-image: repeating-linear-gradient(
|
||||
0deg,
|
||||
@@ -78,6 +78,7 @@ html, body {
|
||||
transition: background 0.3s;
|
||||
}
|
||||
#ws-dot.connected { background: var(--green); }
|
||||
#ws-dot.polling { background: var(--amber-dim); }
|
||||
#ws-dot.pulse { animation: pulse-dot 0.3s ease-out; }
|
||||
|
||||
@keyframes pulse-dot {
|
||||
@@ -88,16 +89,16 @@ html, body {
|
||||
/* ── Llama container ─────────────────────────────────────────────────────── */
|
||||
#llama-wrap {
|
||||
position: relative;
|
||||
width: 160px;
|
||||
height: 200px;
|
||||
width: 150px;
|
||||
height: 175px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#llama {
|
||||
width: 140px;
|
||||
height: 180px;
|
||||
width: 135px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
/* SVG part colors */
|
||||
|
||||
Reference in New Issue
Block a user