Add icons to cookbook engine filter

This commit is contained in:
pewdiepie-archdaemon
2026-06-27 22:01:23 +00:00
parent 36c8a0a3c3
commit ef7e5f90ec
4 changed files with 179 additions and 6 deletions
+79 -1
View File
@@ -468,7 +468,7 @@ function _hwfitShowError(list, host, detail) {
if (rb) rb.addEventListener('click', () => { _resetGpuToggleState(); _hwfitFetch(true); });
}
// Client-side "Engine" filter (llama.cpp / vLLM / SGLang / Ollama). Empty =
// Client-side "Engine" filter (llama.cpp / vLLM / SGLang / Ollama / Diffusers). Empty =
// show all. Uses the same _detectBackend() the serve commands use, so what you
// filter to is exactly what would be launched. Pure view filter — no refetch
// needed. Ollama rows are merged into the main list (see _ensureOllamaLib +
@@ -1804,6 +1804,83 @@ export function _expandModelRow(row, modelData) {
}
const _HWFIT_ENGINE_GLYPHS = {
'': '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" y1="6" x2="20" y2="6"></line><line x1="4" y1="12" x2="20" y2="12"></line><line x1="4" y1="18" x2="20" y2="18"></line><circle cx="8" cy="6" r="2" fill="currentColor" stroke="none"></circle><circle cx="16" cy="12" r="2" fill="currentColor" stroke="none"></circle><circle cx="10" cy="18" r="2" fill="currentColor" stroke="none"></circle></svg>',
vllm: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4l7 16 7-16"></path><path d="M14 4l4 9 3-9"></path></svg>',
sglang: '<span aria-hidden="true" style="display:block;width:14px;height:14px;background:currentColor;-webkit-mask:url(/static/icons/sglang-mark.png) center/contain no-repeat;mask:url(/static/icons/sglang-mark.png) center/contain no-repeat;"></span>',
llamacpp: '<svg width="14" height="14" viewBox="0 0 600 600" fill="none" aria-hidden="true"><path d="M600 392L504.249 558L504.137 557.929C487.252 584.069 458.193 600 426.864 600H120L240 392H600Z" fill="currentColor"></path><path d="M240 392H0L199.602 46.0254C216.032 17.5463 246.411 0 279.29 0H466.154L240 392Z" fill="currentColor"></path></svg>',
ollama: '<span aria-hidden="true" style="display:block;width:14px;height:14px;background:currentColor;-webkit-mask:url(/static/icons/ollama-mark-crop.png) center/contain no-repeat;mask:url(/static/icons/ollama-mark-crop.png) center/contain no-repeat;"></span>',
diffusers: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M5 19l2-2M17 7l2-2"></path></svg>',
};
function _hwfitEngineGlyph(value) {
return _HWFIT_ENGINE_GLYPHS[value] || _HWFIT_ENGINE_GLYPHS[''];
}
function _bindHwfitEnginePicker(engine) {
const wrap = engine?.closest('.hwfit-engine-wrap');
const btn = wrap?.querySelector('[data-hwfit-engine-btn]');
const menu = wrap?.querySelector('[data-hwfit-engine-menu]');
const icon = wrap?.querySelector('[data-hwfit-engine-icon]');
const label = wrap?.querySelector('[data-hwfit-engine-label]');
if (!engine || !wrap || !btn || !menu || wrap.dataset.enginePickerBound) return;
wrap.dataset.enginePickerBound = '1';
const setOpen = (open) => {
menu.hidden = !open;
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
};
const currentLabel = () => {
const opt = Array.from(engine.options).find((o) => o.value === engine.value);
return opt?.textContent || 'Engine';
};
const syncButton = () => {
if (label) label.textContent = currentLabel();
if (icon) icon.innerHTML = _hwfitEngineGlyph(engine.value);
menu.querySelectorAll('[data-hwfit-engine-value]').forEach((item) => {
const active = item.dataset.hwfitEngineValue === engine.value;
item.classList.toggle('active', active);
item.setAttribute('aria-selected', active ? 'true' : 'false');
});
};
const renderMenu = () => {
menu.innerHTML = Array.from(engine.options).map((opt) => (
`<button type="button" role="option" class="hwfit-engine-item" data-hwfit-engine-value="${opt.value}">`
+ `<span class="hwfit-engine-item-icon" aria-hidden="true">${_hwfitEngineGlyph(opt.value)}</span>`
+ `<span class="hwfit-engine-item-label">${opt.textContent}</span>`
+ '</button>'
)).join('');
menu.querySelectorAll('[data-hwfit-engine-value]').forEach((item) => {
item.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
const next = item.dataset.hwfitEngineValue || '';
if (engine.value !== next) {
engine.value = next;
engine.dispatchEvent(new Event('change', { bubbles: true }));
}
syncButton();
setOpen(false);
});
});
syncButton();
};
btn.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
setOpen(menu.hidden);
});
engine.addEventListener('change', syncButton);
document.addEventListener('click', (ev) => {
if (!wrap.contains(ev.target)) setOpen(false);
});
document.addEventListener('keydown', (ev) => {
if (ev.key === 'Escape') setOpen(false);
});
renderMenu();
}
export function _hwfitInit() {
const uc = document.getElementById('hwfit-usecase');
const sort = document.getElementById('hwfit-sort');
@@ -1819,6 +1896,7 @@ export function _hwfitInit() {
// Engine filter is a pure client-side view filter over the already-fetched
// list (HF + Ollama merged), so just re-render from cache.
const engine = document.getElementById('hwfit-engine');
if (engine) _bindHwfitEnginePicker(engine);
if (engine) engine.addEventListener('change', () => {
const list = document.getElementById('hwfit-list');
if (list && _hwfitCache && Array.isArray(_hwfitCache.models)) {
+8 -1
View File
@@ -2692,13 +2692,20 @@ function _renderRecipes() {
// levers (Engine / Quant / Context) live to the right.
html += '<input type="text" class="cookbook-field-input hwfit-search" id="hwfit-search" placeholder="Search models..." style="flex:1;" />';
html += '<span class="hwfit-engine-wrap">';
html += '<select class="cookbook-field-input hwfit-engine" id="hwfit-engine" style="height:28px;" title="Filter by serving engine">';
html += '<select class="cookbook-field-input hwfit-engine" id="hwfit-engine" style="display:none;" title="Filter by serving engine">';
html += '<option value="">Engine</option>';
html += '<option value="llamacpp">llama.cpp</option>';
html += '<option value="ollama">Ollama</option>';
html += '<option value="vllm">vLLM</option>';
html += '<option value="sglang">SGLang</option>';
html += '<option value="diffusers">Diffusers</option>';
html += '</select>';
html += '<button type="button" class="cookbook-field-input hwfit-engine-btn" data-hwfit-engine-btn aria-haspopup="listbox" aria-expanded="false" title="Filter by serving engine">';
html += '<span class="hwfit-engine-btn-icon" data-hwfit-engine-icon aria-hidden="true"></span>';
html += '<span class="hwfit-engine-btn-label" data-hwfit-engine-label>Engine</span>';
html += '<svg class="hwfit-engine-caret" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>';
html += '</button>';
html += '<div class="hwfit-engine-menu" data-hwfit-engine-menu role="listbox" hidden></div>';
html += '<span class="hwfit-help-chip hwfit-help-chip-inline hwfit-engine-help" title="Rule of thumb: GGUF on single GPU / CPU+RAM → llama.cpp (or Ollama). Safetensors on multi-GPU NVIDIA → vLLM. SGLang is a vLLM-class alternative, sometimes faster on big-MoE / long-context.">?</span>';
html += '</span>';
// Quant (Q4/Q8/…). Default is "All" so the list shows the best-scoring
+3 -3
View File
@@ -1390,7 +1390,7 @@ function _rerenderCachedModels() {
// Row 2b: Diffusers settings
const diffDtypeOpts = ['bfloat16','float16','float32'].map(d => `<option value="${d}"${sv('diff_dtype','bfloat16')===d?' selected':''}>${d}</option>`).join('');
const deviceMapOpts = ['balanced','auto','sequential'].map(d => `<option value="${d}"${sv('diff_device_map','balanced')===d?' selected':''}>${d}</option>`).join('');
panelHtml += `<div class="hwfit-serve-row hwfit-backend-diffusers">`;
panelHtml += `<div class="hwfit-serve-row hwfit-backend-diffusers hwfit-diff-settings-row">`;
panelHtml += `<label>Dtype${_h('Precision. bfloat16 recommended for Flux, float16 for SD')} <select class="hwfit-sf" data-field="diff_dtype">${diffDtypeOpts}</select></label>`;
panelHtml += `<label>Device Map${_h('How to place model on GPUs. balanced = split evenly')} <select class="hwfit-sf" data-field="diff_device_map">${deviceMapOpts}</select></label>`;
panelHtml += `<label>Steps${_h('Default inference steps. More = better quality, slower')} <input type="text" class="hwfit-sf" data-field="diff_steps" value="${esc(sv('diff_steps', ''))}" placeholder="auto" /></label>`;
@@ -1495,11 +1495,11 @@ function _rerenderCachedModels() {
panelHtml += `<label class="hwfit-sf-cb hwfit-spec-group"><input type="checkbox" class="hwfit-sf" data-field="llama_speculative_mtp"${sv('llama_speculative_mtp',false)?' checked':''} /> MTP Spec${_h('llama.cpp native MTP speculative decoding: --spec-type draft-mtp. Requires a GGUF with MTP heads.')} <input type="number" class="hwfit-sf hwfit-spec-tokens hwfit-spec-tokens-bare" data-field="llama_spec_tokens" value="${esc(sv('llama_spec_tokens', '3'))}" min="1" max="10" title="--spec-draft-n-max" /></label>`;
panelHtml += `</div>`;
// Row 3b: Checkboxes (diffusers)
panelHtml += `<div class="hwfit-serve-checks hwfit-backend-diffusers">`;
panelHtml += `<div class="hwfit-serve-checks hwfit-backend-diffusers hwfit-diff-checks-row">`;
panelHtml += `<label class="hwfit-sf-cb"><input type="checkbox" class="hwfit-sf" data-field="diff_offload"${sv('diff_offload',false)?' checked':''} /> CPU Offload${_h('Offload parts of model to CPU RAM to save VRAM. Slower but fits larger models')}</label>`;
panelHtml += `<label class="hwfit-sf-cb"><input type="checkbox" class="hwfit-sf" data-field="diff_attention_slicing"${sv('diff_attention_slicing',false)?' checked':''} /> Attention Slicing${_h('Slice attention computation to reduce peak VRAM. Slower')}</label>`;
panelHtml += `<label class="hwfit-sf-cb"><input type="checkbox" class="hwfit-sf" data-field="diff_vae_slicing"${sv('diff_vae_slicing',false)?' checked':''} /> VAE Slicing${_h('Process VAE in slices. Reduces VRAM for high-res images')}</label>`;
panelHtml += `</div><div class="hwfit-serve-row hwfit-backend-diffusers">`;
panelHtml += `</div><div class="hwfit-serve-row hwfit-backend-diffusers hwfit-diff-harmonize-row">`;
panelHtml += `<label>Harmonize GPU${_h('Separate GPU for img2img/harmonize. Leave empty to use same GPU')}<input type="text" class="hwfit-sf" data-field="diff_harmonize_gpu" value="${esc(sv('diff_harmonize_gpu', ''))}" placeholder="auto" style="width:50px;" /></label>`;
panelHtml += `</div>`;
// Model-specific optimizations. The checks row always renders for the
+89 -1
View File
@@ -20479,7 +20479,7 @@ body.gallery-selecting .gallery-dl-btn,
width: 100%;
}
.hwfit-serve-cmd-details {
margin: 6px 0 0;
margin: 4px 0 0;
}
.hwfit-serve-cmd-summary {
display: flex;
@@ -22201,6 +22201,7 @@ body.gallery-selecting .gallery-dl-btn,
.hwfit-toolbar .hwfit-usecase { min-width: 70px; flex-shrink: 0; }
.hwfit-toolbar .hwfit-quant { min-width: 0; width: 78px; flex-shrink: 0; font-size: 10px; }
.hwfit-toolbar .hwfit-engine { min-width: 0; width: 86px; flex-shrink: 0; font-size: 10px; }
.hwfit-toolbar .hwfit-engine-btn { min-width: 0; width: 86px; flex-shrink: 0; font-size: 10px; }
.hwfit-toolbar .hwfit-search { flex: 1; min-width: 80px; }
/* Lower-opacity "Search models..." placeholder so it reads as a hint, not
a label matches the muted form-field feel of the inline filters. */
@@ -22271,6 +22272,83 @@ body.gallery-selecting .gallery-dl-btn,
display: inline-flex;
align-items: center;
}
.hwfit-engine-btn {
height: 28px;
padding: 0 30px 0 7px;
display: inline-flex;
align-items: center;
gap: 5px;
color: var(--fg);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 4px;
font: inherit;
cursor: pointer;
text-align: left;
}
.hwfit-engine-btn-icon,
.hwfit-engine-item-icon {
width: 14px;
height: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--accent, var(--red));
flex-shrink: 0;
}
.hwfit-engine-btn-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hwfit-engine-caret {
position: absolute;
right: 6px;
opacity: 0.55;
pointer-events: none;
}
.hwfit-engine-menu {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
z-index: 120;
padding: 4px;
background: var(--panel, var(--bg));
border: 1px solid var(--border);
border-radius: 6px;
box-shadow: 0 6px 20px rgba(0,0,0,0.22);
}
.hwfit-engine-item {
width: 100%;
min-height: 26px;
padding: 5px 7px;
display: flex;
align-items: center;
gap: 7px;
color: var(--fg);
background: transparent;
border: 0;
border-radius: 5px;
font: inherit;
font-size: 11px;
text-align: left;
cursor: pointer;
box-sizing: border-box;
}
.hwfit-engine-item:hover,
.hwfit-engine-item.active {
background: color-mix(in srgb, var(--fg) 8%, transparent);
}
.hwfit-engine-item-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hwfit-quant-wrap .hwfit-quant,
.hwfit-engine-wrap .hwfit-engine {
/* Make room for the ? on the right edge, in addition to the native chevron. */
@@ -24056,6 +24134,16 @@ details.hwfit-serve-advanced > .hwfit-llama-monitor-row,
details.hwfit-serve-advanced > .hwfit-llama-checks-row {
margin-top: -18px !important;
}
details.hwfit-serve-advanced > .hwfit-diff-settings-row {
row-gap: 2px;
}
details.hwfit-serve-advanced > .hwfit-diff-checks-row,
details.hwfit-serve-advanced > .hwfit-diff-harmonize-row {
margin-top: -10px !important;
}
details.hwfit-serve-advanced > .hwfit-diff-checks-row {
row-gap: 2px;
}
details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-vram-monitor {
gap: 4px !important;
grid-template-columns: max-content minmax(0, 1fr);