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 '
';
+ 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; }