From c6da6b0784beefbbaa530fd8a3b6093cb7be491a Mon Sep 17 00:00:00 2001 From: Grant Date: Tue, 12 May 2026 11:45:55 -0500 Subject: [PATCH] v0.2.4 - Hotfix: Unknown status + copy UX + update banner context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug fix: - config.py: empty PARAKEET_CONTAINER / MAGPIE_CONTAINER env vars (from migrating to v0.2.0+ where the field is optional and saved as '') now fall back to 'parakeet-asr' / 'magpie-tts' via the 'or' idiom. Confirmed live: services classify as 'running' instead of 'unknown'. UX: - Replaced text 'Copy' buttons with compact icon buttons (clipboard SVG) - Endpoint Base URL + Model ID + curl snippet are now click-to-copy themselves (the value AND a separate icon button) - Service cards: host, base URL, and model are now three separate copyable rows - Update banner: leading explanatory line — 'Updates to eugr/spark-vllm-docker — the upstream project that orchestrates vLLM on your Sparks. These are not firmware, OS, or model updates.' with a link to the repo. --- image/app/config.py | 4 +- image/app/static/app.js | 71 ++++++++++++++++++++---------- image/app/static/index.html | 22 ++++++--- image/app/static/style.css | 39 +++++++++++++--- package/startos/versions/v0_1_0.ts | 4 +- 5 files changed, 102 insertions(+), 38 deletions(-) diff --git a/image/app/config.py b/image/app/config.py index 1e92871..f5cc57e 100644 --- a/image/app/config.py +++ b/image/app/config.py @@ -55,10 +55,10 @@ class Settings: spark2_user=spark2_user, parakeet_host=_env("PARAKEET_HOST") or spark2_host, parakeet_user=_env("PARAKEET_USER") or spark2_user, - parakeet_container=_env("PARAKEET_CONTAINER", "parakeet-asr"), + parakeet_container=_env("PARAKEET_CONTAINER") or "parakeet-asr", magpie_host=_env("MAGPIE_HOST") or spark2_host, magpie_user=_env("MAGPIE_USER") or spark2_user, - magpie_container=_env("MAGPIE_CONTAINER", "magpie-tts"), + magpie_container=_env("MAGPIE_CONTAINER") or "magpie-tts", ssh_key_path=_env("SSH_KEY_PATH"), ssh_known_hosts=_env("SSH_KNOWN_HOSTS"), models_yaml=_resolve_models_yaml(), diff --git a/image/app/static/app.js b/image/app/static/app.js index 125a8a1..ca32807 100644 --- a/image/app/static/app.js +++ b/image/app/static/app.js @@ -143,11 +143,16 @@ async function renderServices() { if (action === 'stop' && cls !== 'running' && cls !== 'starting' && cls !== 'unhealthy') return true; return false; }; + const copyIcon = ``; + const hostStr = s.host ? `${s.host}:${s.port}` : ''; const hostRow = s.host - ? `
Host${escapeHtml(s.host)}:${s.port}
` + ? `
Host${escapeHtml(hostStr)}
` : `
Hostnot configured
`; + const urlRow = s.base_url + ? `
URL${escapeHtml(s.base_url)}
` + : ''; const modelRow = s.model - ? `
Model${escapeHtml(s.model)}
` + ? `
Model${escapeHtml(s.model)}
` : ''; const restartsRow = s.restart_count != null && s.restart_count > 1 ? `
Restarts${s.restart_count}
` @@ -159,6 +164,7 @@ async function renderServices() { ${statusLabel(cls)} ${hostRow} + ${urlRow} ${modelRow} ${restartsRow}
@@ -212,31 +218,50 @@ function renderEndpoint(status) { el('#ep-curl-snippet').textContent = snippet; } -function setupCopyButtons() { - document.body.addEventListener('click', async (e) => { - const btn = e.target.closest('.copy-btn'); - if (!btn) return; - const targetSel = btn.dataset.copy; - if (!targetSel) return; - const target = el(targetSel); - if (!target) return; - const text = target.textContent; - try { - await navigator.clipboard.writeText(text); - const original = btn.textContent; - btn.classList.add('copied'); - btn.textContent = 'Copied'; - setTimeout(() => { - btn.classList.remove('copied'); - btn.textContent = original; - }, 1400); - } catch { - // Clipboard API may fail over plain HTTP; fall back to selection +async function copyText(text, indicatorEl) { + try { + await navigator.clipboard.writeText(text); + if (indicatorEl) { + indicatorEl.classList.add('copied'); + setTimeout(() => indicatorEl.classList.remove('copied'), 1200); + } + return true; + } catch { + // Plain HTTP fallback: select the text so the user can ⌘C + if (indicatorEl) { const range = document.createRange(); - range.selectNode(target); + range.selectNode(indicatorEl); window.getSelection().removeAllRanges(); window.getSelection().addRange(range); } + return false; + } +} + +function setupCopyButtons() { + document.body.addEventListener('click', async (e) => { + // Inline icon copy with literal text (used for dynamically-rendered service rows) + const litBtn = e.target.closest('[data-copy-text]'); + if (litBtn) { + await copyText(litBtn.dataset.copyText, litBtn); + return; + } + // Copy buttons (with svg icon) referenced by data-copy="selector" + const btn = e.target.closest('[data-copy]'); + if (btn) { + const target = el(btn.dataset.copy); + if (target) { + await copyText(target.textContent, btn); + target.classList.add('copied'); + setTimeout(() => target.classList.remove('copied'), 1200); + } + return; + } + // Self-copy: clicking the text itself + const selfCopy = e.target.closest('[data-copy-self]'); + if (selfCopy) { + await copyText(selfCopy.textContent, selfCopy); + } }); } diff --git a/image/app/static/index.html b/image/app/static/index.html index ae07bfd..df3266e 100644 --- a/image/app/static/index.html +++ b/image/app/static/index.html @@ -28,18 +28,24 @@
OpenAI-compatible endpoint
Base URL - - + +
Model ID - - + +
curl example -

-        
+        

+        
       
@@ -165,6 +171,10 @@