From 1e5afe7f656286054bf7ec8f004ef522e4c204f4 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 31 Oct 2026 15:03:09 +0100 Subject: [PATCH] feat(diagnostic): Karten-Modellwahl als Liste mit Suchfeld oben drin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Statt Suchfeld neben einem normalen Dropdown (Tippen → Liste oeffnen → passt nicht → zurueck ins Feld) oeffnet ein Klick jetzt eine Liste mit dem Suchfeld oben. Tippen filtert sofort, Auswahl per Klick oder Pfeiltasten + Enter, Esc / Klick daneben / Scrollen schliesst. Das versteckte ${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)
' : ''} -
- +
+ onchange="onLlmCardSelect(this)" style="display:none;">${opts.join('')} +
${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 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 ────────────────────────────────────────