From e219382a51efc72e3cc2609a72b11912d47c05eb Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 31 Oct 2026 14:56:03 +0100 Subject: [PATCH] feat(diagnostic): Suche im Modell-Katalog und in den Karten-Dropdowns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modell-Katalog: Suchfeld (erscheint, sobald die Liste geladen ist), Teilsuche ueber Name, Beschreibung, Repo und Quantisierung; mehrere Woerter muessen alle vorkommen. - LLM-Karten: Suchfeld ueber jedem Modell-Dropdown blendet nicht passende Modelle aus; "β€” leer β€”" und das geladene Modell bleiben sichtbar. - Fix: Ein offenes Karten-Dropdown klappte nach ein paar Sekunden zu, weil jedes worker_update die Liste neu zeichnete. Solange ein Dropdown oder Suchfeld darin den Fokus hat, wird das Neuzeichnen aufgeschoben und beim Verlassen nachgeholt (gilt auch fuer die Ziel-Karte im Katalog). Co-Authored-By: Claude Opus 5.5 --- diagnostic/index.html | 65 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/diagnostic/index.html b/diagnostic/index.html index 8e50a1e..c050e73 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -956,6 +956,8 @@ den Fortschritt. Danach ist es im Modell-Dropdown oben waehlbar. Achte auf den VRAM der Box (Spalte VRAM).

+
(lade Katalog…)
@@ -9254,10 +9256,41 @@ // online LLM-Boxen (fuer die Ziel-Auswahl + Verfuegbarkeit) function onlineLlmBoxes() { return workers.filter(w => w.service === 'llm' && w.online); } function boxesServing(id) { return onlineLlmBoxes().filter(w => (w.models || []).includes(id) || w.model === id); } + // Neu zeichnen schliesst ein offenes Dropdown und loescht Eingaben. Solange + // darin etwas den Fokus hat, warten β€” beim Verlassen wird nachgeholt. + function _llmRenderDeferred(box, fn) { + const a = document.activeElement; + if (!a || !box.contains(a) || !['SELECT', 'INPUT'].includes(a.tagName)) return false; + if (!box._llmDeferred) { + box._llmDeferred = true; + box.addEventListener('focusout', () => setTimeout(() => { + box._llmDeferred = false; + if (_llmRenderDeferred(box, fn)) return; // Fokus nur innerhalb gewechselt β†’ weiter warten + fn(); + }, 0), { once: true }); + } + return true; + } + // Teilsuche: jedes Wort muss irgendwo in Name/Beschreibung/Repo vorkommen. + function _llmMatches(text, query) { + const t = String(text || '').toLowerCase(); + return String(query || '').toLowerCase().split(/\s+/).filter(Boolean).every(w => t.includes(w)); + } + function filterLlmCatalog() { + const q = (document.getElementById('llm-catalog-search') || {}).value || ''; + const rows = document.querySelectorAll('#llm-catalog-list tbody tr[data-search]'); + let shown = 0; + rows.forEach(tr => { const ok = _llmMatches(tr.dataset.search, q); tr.style.display = ok ? '' : 'none'; if (ok) shown++; }); + const none = document.getElementById('llm-catalog-none'); + if (none) none.style.display = rows.length && !shown ? '' : 'none'; + } function renderLlmCatalog() { const box = document.getElementById('llm-catalog-list'); if (!box) return; renderLlmCardSettings(); + if (_llmRenderDeferred(box, renderLlmCatalog)) return; + const search = document.getElementById('llm-catalog-search'); + if (search) search.style.display = _llmCatalog.length ? '' : 'none'; if (!_llmCatalog.length) { box.innerHTML = 'Katalog leer.'; return; } const boxes = onlineLlmBoxes(); const th = 'style="padding:6px 8px;text-align:left;position:sticky;top:0;background:#12122A;"'; @@ -9277,7 +9310,8 @@ const opts = targets.length ? targets.map(t => ``).join('') : ''; - return '' + + const searchText = [m.id, m.description, m.hfRepo, m.quant].filter(Boolean).join(' '); + return '' + '' + escapeHtml(m.id) + '' + '' + escapeHtml(m.description || m.hfRepo || '') + (m.quant ? ' Β· ' + escapeHtml(m.quant) + '' : '') @@ -9293,7 +9327,9 @@ '' + 'ModellKann gut / BeschreibungVRAM' + 'StatusZiel-Karte' + - '' + rows + ''; + '' + rows + '' + + ''; + filterLlmCatalog(); } // ── Download-Fenster ────────────────────────────────────────────── // Zustand kommt vom Diagnostic-Server (llm_provision_progress / llm_jobs); @@ -9401,6 +9437,7 @@ // Optionen = Modell-Katalog + was gerade geladen ist (falls nicht im // Katalog). Zu gross fuer die Karte β†’ markiert, aber waehlbar (Groesse ist // nur eine Schaetzung, der Kontext braucht zusaetzlich Platz). + const _llmCardSearch = {}; // Suchtext je Karte (box|gpu), ueberlebt das Neuzeichnen function renderLlmCardSettings() { const box = document.getElementById('llm-card-settings'); if (!box) return; @@ -9408,6 +9445,7 @@ window._llmCatalogRequested = true; loadLlmCatalog(); // ruft renderLlmCatalog β†’ renderLlmCardSettings } + if (_llmRenderDeferred(box, renderLlmCardSettings)) return; const boxes = onlineLlmBoxes().filter(b => Array.isArray(b.slots)); if (!boxes.length) { const old = onlineLlmBoxes().length; @@ -9474,15 +9512,29 @@ ${sl.ramMB ? ' Β· ' + (sl.ramMB / 1024).toFixed(1).replace('.', ',') + ' GB im RAM' : ''}${sl.cpuMoe > 0 ? ' (' + sl.cpuMoe + ' Schichten)' : ''}` : ''} ${sl.onCpu ? '
⚠ Modell lΓ€uft auf der CPU, Karte leer β€” Treiber zu alt? (bootstrap.sh --upgrade-driver)
' : ''} - +
+ + +
${ctxCell} ${jobTxt || st} `); } } box.innerHTML = rows.join(''); + box.querySelectorAll('input[data-key]').forEach(onLlmCardSearch); + } + // Suchfeld ueber dem Karten-Dropdown: blendet nicht passende Modelle aus. + // "β€” leer β€”" und das geladene Modell bleiben immer sichtbar. + function onLlmCardSearch(inp) { + _llmCardSearch[inp.dataset.key] = inp.value; + const sel = inp.parentElement.querySelector('select'); + if (!sel) return; + for (const o of sel.options) o.hidden = !!o.value && o.value !== sel.dataset.current && !_llmMatches(o.textContent, inp.value); } // ── Bestenlisten ──────────────────────────────────────── @@ -9616,6 +9668,7 @@ // die Karte auf den alten Stand zurueck. function onLlmCardCtx(sel) { const target = sel.dataset.box, gpu = sel.dataset.gpu, model = sel.dataset.model; + sel.blur(); // Fokus weg β†’ aufgeschobenes Neuzeichnen holt den Stand nach const cur = Number(sel.dataset.current || 0), next = Number(sel.value); if (next === cur) return; const m = _llmCatalog.find(x => x.id === model); @@ -9632,6 +9685,7 @@ function onLlmCardSelect(sel) { const target = sel.dataset.box, gpu = sel.dataset.gpu, current = sel.dataset.current, next = sel.value; + sel.blur(); // Fokus weg β†’ aufgeschobenes Neuzeichnen holt den Stand nach const node = _boxName(target); if (next === current) return; if (!next) { @@ -9656,6 +9710,7 @@ // MoE: Experten zwischen Karte und Arbeitsspeicher β€” gleiches Modell neu laden function onLlmCardMoe(sel) { const target = sel.dataset.box, gpu = sel.dataset.gpu, model = sel.dataset.model; + sel.blur(); // Fokus weg β†’ aufgeschobenes Neuzeichnen holt den Stand nach const cur = Number(sel.dataset.current || 0), next = Number(sel.value); const m = _llmCatalog.find(x => x.id === model); if (!m || !m.hfRepo) { alert(`${model} steht nicht im Modell-Katalog.`); sel.value = String(cur); return; }