From 14d3e12ec8b24c1cb15840c921261810020157ed Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Tue, 30 Jun 2026 20:21:18 +0200 Subject: [PATCH] Fix engines local container refresh race --- CHANGELOG.md | 8 ++++++++ VERSION | 2 +- static/index.html | 6 +++--- static/js/engines.js | 48 +++++++++++++++++++++++++++++++++++++------- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e2e2b5..736b205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi --- +## [1.12.56] — 2026-06-30 + +### Fixed +- **Engines local-card fetch race** — overlapping Docker container refreshes can no longer let an older failed request overwrite a newer successful render with `Could not reach server: Failed to fetch`. +- **Engines refresh resilience** — local engine cards retry once, keep the last good container list on transient network changes, and show an inline Retry button only when no cached data exists. + +--- + ## [1.12.55] — 2026-06-30 ### Added diff --git a/VERSION b/VERSION index a32117e..85badc6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.12.55 +1.12.56 diff --git a/static/index.html b/static/index.html index ca0340d..dabd3a4 100644 --- a/static/index.html +++ b/static/index.html @@ -10,7 +10,7 @@ - + @@ -27,7 +27,7 @@ - + @@ -362,7 +362,7 @@ window.toggleNavTree = function(treeId, chevronId) { - + diff --git a/static/js/engines.js b/static/js/engines.js index 17ec1a0..aa80f3d 100644 --- a/static/js/engines.js +++ b/static/js/engines.js @@ -307,6 +307,9 @@ function editCustomEngineCard(card) { // ── Local Docker container management ───────────────────────────────────── +let _localContainersLoadSeq = 0; +let _lastLocalContainers = null; + function cardType(card) { const page = card?.closest('[data-page]'); return page ? page.dataset.page : ''; @@ -325,27 +328,58 @@ function _dcSkel(n = 2) { return Array.from({ length: n }, card).join(''); } +async function fetchLocalContainersWithRetry() { + let lastErr = null; + for (let attempt = 0; attempt < 2; attempt++) { + try { + const r = await fetch('/api/local-containers', { cache: 'no-store' }); + if (!r.ok) { + const e = await r.json().catch(() => ({})); + throw new Error(e.detail || r.statusText || `HTTP ${r.status}`); + } + return await r.json(); + } catch (e) { + lastErr = e; + if (attempt === 0) await new Promise(resolve => setTimeout(resolve, 450)); + } + } + throw lastErr || new Error('Failed to fetch'); +} + async function loadLocalContainers() { + const seq = ++_localContainersLoadSeq; const gridTts = $('dc-grid-tts'); const gridStt = $('dc-grid-stt'); const gridLlm = $('dc-grid-llm'); if (!gridTts && !gridStt && !gridLlm) return; const skel = _dcSkel(2); - if (gridTts) gridTts.innerHTML = skel; - if (gridStt) gridStt.innerHTML = skel; - if (gridLlm) gridLlm.innerHTML = skel; + if (gridTts && !gridTts.querySelector('.llm-local-card')) gridTts.innerHTML = skel; + if (gridStt && !gridStt.querySelector('.llm-local-card')) gridStt.innerHTML = skel; + if (gridLlm && !gridLlm.querySelector('.llm-local-card')) gridLlm.innerHTML = skel; const custom = loadCustomEngineCards(); try { - const r = await fetch('/api/local-containers'); - const d = await r.json(); + const d = await fetchLocalContainersWithRetry(); + if (seq !== _localContainersLoadSeq) return; + _lastLocalContainers = d; renderLocalContainers([...(d.containers || []), ...custom]); } catch (e) { - const err = `
Could not reach server: ${escHtml(e.message)}
`; + if (seq !== _localContainersLoadSeq) return; + if (_lastLocalContainers) { + renderLocalContainers([...(_lastLocalContainers.containers || []), ...custom]); + return; + } + if (custom.length) { + renderLocalContainers(custom); + return; + } + const err = `
Could not reach server: ${escHtml(e.message)}
`; if (gridTts) gridTts.innerHTML = err; if (gridStt) gridStt.innerHTML = err; - if (custom.length) renderLocalContainers(custom); + if (gridLlm) gridLlm.innerHTML = err; + document.querySelectorAll('.dc-retry-inline').forEach(btn => btn.addEventListener('click', loadLocalContainers)); } } +window.loadLocalContainers = loadLocalContainers; function renderLocalContainers(containers) { const gridTts = $('dc-grid-tts');