diff --git a/diagnostic/index.html b/diagnostic/index.html
index c050e73..d117148 100644
--- a/diagnostic/index.html
+++ b/diagnostic/index.html
@@ -9437,7 +9437,6 @@
// 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;
@@ -9460,6 +9459,7 @@
const vramGB = sl.vramMB ? sl.vramMB / 1024 : 0;
const ids = new Set(_llmCatalog.map(m => m.id));
const opts = ['— leer — '];
+ let curLabel = '— leer —';
const list = _llmCatalog.filter(m => !m.unusable);
if (sl.model && !ids.has(sl.model)) list.unshift({ id: sl.model });
for (const m of list) {
@@ -9469,6 +9469,7 @@
const tooBig = vramGB && m.sizeGB && m.sizeGB > vramGB * 0.92;
const label = m.id + (m.sizeGB ? ` · ~${m.sizeGB} GB` : '') + (m.quant ? ` · ${m.quant}` : '')
+ (tooBig ? ' · passt nicht' : '');
+ if (m.id === sl.model) curLabel = label;
opts.push(`${escapeHtml(label)} `);
}
let [c, st] = sl.model ? (SLOT_STATE[sl.state] || ['#8888AA', escapeHtml(sl.state || '?')]) : ['#555570', 'leer'];
@@ -9512,13 +9513,12 @@
alle im Arbeitsspeicher
${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)
' : ''}
-
-
+
${opts.join('')}
+ onchange="onLlmCardSelect(this)" style="display:none;">${opts.join('')}
+
+ ${escapeHtml(curLabel)} ▾
${ctxCell}
${jobTxt || st}
@@ -9526,15 +9526,72 @@
}
}
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);
+ // Modell-Auswahl mit Suchfeld oben in der Liste (ein normales
+ // kann das nicht). Das versteckte haelt weiter Wert + onchange.
+ function openLlmPicker(btn) {
+ closeLlmPicker();
+ const sel = btn.parentElement.querySelector('select');
+ const r = btn.getBoundingClientRect();
+ const pop = document.createElement('div');
+ pop.id = 'llm-picker';
+ const below = window.innerHeight - r.bottom > 260;
+ pop.style.cssText = `position:fixed;left:${r.left}px;${below ? 'top:' + (r.bottom + 2) : 'bottom:' + (window.innerHeight - r.top + 2)}px;`
+ + `width:${Math.max(r.width, 320)}px;z-index:9999;background:#12121E;border:1px solid #444466;border-radius:6px;`
+ + 'box-shadow:0 6px 20px rgba(0,0,0,.5);padding:6px;';
+ pop.innerHTML = ' '
+ + '
';
+ btn.parentElement.appendChild(pop);
+ const inp = pop.querySelector('input'), list = pop.querySelector('div');
+ let items = [], active = 0;
+ const draw = () => {
+ items = [...sel.options].filter(o => !o.value || _llmMatches(o.textContent, inp.value));
+ active = Math.max(0, Math.min(active, items.length - 1));
+ list.innerHTML = items.map((o, i) => ``
+ + (o.value === sel.value ? '✓ ' : '') + escapeHtml(o.textContent) + '
').join('')
+ + (items.length > 1 || !inp.value ? '' : 'Kein Modell passt zur Suche.
');
+ const a = list.querySelector(`[data-i="${active}"]`);
+ if (a) a.scrollIntoView({ block: 'nearest' });
+ };
+ const pick = o => {
+ closeLlmPicker();
+ if (!o || o.value === sel.value) return;
+ sel.value = o.value;
+ onLlmCardSelect(sel);
+ };
+ list.addEventListener('mousedown', e => {
+ const d = e.target.closest('[data-i]');
+ if (d) { e.preventDefault(); pick(items[+d.dataset.i]); }
+ });
+ list.addEventListener('mousemove', e => {
+ const d = e.target.closest('[data-i]');
+ if (d && +d.dataset.i !== active) { active = +d.dataset.i; draw(); }
+ });
+ inp.addEventListener('input', () => { active = 0; draw(); });
+ inp.addEventListener('keydown', e => {
+ if (e.key === 'ArrowDown') { e.preventDefault(); active++; draw(); }
+ else if (e.key === 'ArrowUp') { e.preventDefault(); active--; draw(); }
+ else if (e.key === 'Enter') { e.preventDefault(); pick(items[active]); }
+ else if (e.key === 'Escape') { e.preventDefault(); closeLlmPicker(); }
+ });
+ active = Math.max(0, [...sel.options].findIndex(o => o.value === sel.value));
+ draw();
+ inp.focus();
+ setTimeout(() => document.addEventListener('mousedown', _llmPickerOutside), 0);
+ window.addEventListener('scroll', _llmPickerScroll, true);
+ }
+ function _llmPickerOutside(e) { const p = document.getElementById('llm-picker'); if (p && !p.contains(e.target)) closeLlmPicker(); }
+ function _llmPickerScroll(e) { const p = document.getElementById('llm-picker'); if (p && !p.contains(e.target)) closeLlmPicker(); }
+ function closeLlmPicker() {
+ document.removeEventListener('mousedown', _llmPickerOutside);
+ window.removeEventListener('scroll', _llmPickerScroll, true);
+ const p = document.getElementById('llm-picker');
+ if (!p) return;
+ const inp = p.querySelector('input');
+ if (inp) inp.blur(); // loest das aufgeschobene Neuzeichnen der Karten aus
+ p.remove();
}
// ── Bestenlisten ────────────────────────────────────────