diff --git a/image/app/static/app.js b/image/app/static/app.js
index 6fa0af5..4d5c25d 100644
--- a/image/app/static/app.js
+++ b/image/app/static/app.js
@@ -1,7 +1,4 @@
// spark-control front-end
-// - polls /api/status every 5s for current model + health
-// - lists models from /api/models as cards
-// - POST /api/swap to start a swap, then opens SSE /api/swap/{id}/stream
const state = {
models: {},
@@ -9,116 +6,222 @@ const state = {
current_model_key: null,
swap_job_id: null,
swap_eventsource: null,
+ swap_started_at: null,
+ swap_lines: [], // local accumulator for phase detection
+ swap_phase: 'Starting…',
+ swap_phase_detail: '',
+ swap_progress: 0, // 0–1
configured: true,
+ timer_handle: null,
};
-function el(sel) { return document.querySelector(sel); }
-function $(sel) { return document.querySelectorAll(sel); }
+const el = (sel) => document.querySelector(sel);
+const $$ = (sel) => document.querySelectorAll(sel);
async function fetchJSON(url, opts) {
const r = await fetch(url, opts);
if (!r.ok) {
- const text = await r.text().catch(() => "");
+ const text = await r.text().catch(() => '');
throw new Error(`${r.status} ${r.statusText}: ${text}`);
}
return r.json();
}
+// ===================== rendering =====================
+
function renderCards() {
- const root = el("#cards");
- root.innerHTML = "";
- const keys = Object.keys(state.models);
- for (const key of keys) {
+ const root = el('#cards');
+ root.innerHTML = '';
+ const isSwapping = !!state.swap_job_id;
+ for (const key of Object.keys(state.models)) {
const m = state.models[key];
const isActive = key === state.current_model_key;
- const isSwapping = !!state.swap_job_id;
- const card = document.createElement("div");
- card.className = "card" + (isActive ? " active" : "");
+ const card = document.createElement('div');
+ card.className = 'card' + (isActive ? ' active' : '');
card.innerHTML = `
${m.display_name}
${m.mode}
${m.size_gb} GB
- ${(m.capabilities || []).map(c => `${c}`).join("")}
+ ${(m.capabilities || []).map(c => `${c}`).join('')}
- ${m.repo}
+ ${m.repo}
-