From 517aa593e03fe829ac303af9d393da963b21db1b Mon Sep 17 00:00:00 2001 From: Sirsyorrz Date: Tue, 2 Jun 2026 13:30:24 +1000 Subject: [PATCH] Cookbook: clearer tooltips on saved-config badge and GPU chip (#850) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two small polish items in the Cookbook Serve panel. Saved-config badge The little count badge next to the Save button ("3 ▾" etc.) had a generic "Saved launch configs" tooltip, so the number reads like a notification dot. Make it spell out what it is and what clicking does: "3 saved launch configs for — click ▾ to load or delete" (and "No saved launch configs for yet — click Save to add one" when empty). Tooltip stays in sync via _updateSavedToggleLabel so save/delete updates both the count and the hint. GPU chip on mixed-GPU boxes (#711) The chip label was `${gpuCount}x ${gpu_name}`, where gpu_name is just gpus[0].name — so a 4090 + 3060 reads as "2x RTX 4090". The backend already emits gpu_groups (identical cards grouped, used by the serve flow to pin CUDA_VISIBLE_DEVICES) and a per-card gpus[] array, so use them: - Label renders each homogeneous pool: "1× RTX 4090 + 1× RTX 3060". Homogeneous setups keep the existing "2× RTX 4090" form. - Tooltip lists each GPU with its index + VRAM, useful for picking the right device when launching. Refs #711. --- static/js/cookbook-hwfit.js | 32 ++++++++++++++++++++++++++++++-- static/js/cookbookServe.js | 16 +++++++++++++--- static/style.css | 13 +++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/static/js/cookbook-hwfit.js b/static/js/cookbook-hwfit.js index 7a46666..6ed895d 100644 --- a/static/js/cookbook-hwfit.js +++ b/static/js/cookbook-hwfit.js @@ -576,8 +576,36 @@ export function _hwfitRenderHw(el, sys) { }; let gpuChip; if (sys.gpu_name) { - const label = gpuCount > 1 ? `${gpuCount}x ${esc(sys.gpu_name)}` : esc(sys.gpu_name); - gpuChip = chip('gpu', label); + // Mixed-GPU boxes (#711): `${gpuCount}x ${gpu_name}` uses gpus[0].name for + // every card, so a 4090+3060 reads as "2x RTX 4090". Use gpu_groups (the + // backend already groups identical cards) to render each pool separately + // and put the per-card index+VRAM into the tooltip so it's actually + // useful for picking CUDA_VISIBLE_DEVICES. + const groups = Array.isArray(sys.gpu_groups) ? sys.gpu_groups : []; + // Shorten vendor prefixes so a mixed-GPU label fits in the chip row + // without overflowing. Single-GPU label still shows the full name + // (that's what users are used to seeing). Tooltip carries the full + // unmodified names regardless, so no information is lost. + const _shortGpuName = (n) => String(n || '') + .replace(/^NVIDIA\s+GeForce\s+/i, '') + .replace(/^NVIDIA\s+/i, '') + .replace(/^AMD\s+Radeon\s+/i, '') + .replace(/^AMD\s+/i, '') + .replace(/^Intel\s+/i, ''); + let label; + if (groups.length > 1) { + // Heterogeneous: "1× RTX 4090 + 1× RTX 3060" + label = groups.map(g => `${g.count}× ${esc(_shortGpuName(g.name))}`).join(' + '); + } else if (gpuCount > 1) { + label = `${gpuCount}× ${esc(sys.gpu_name)}`; + } else { + label = esc(sys.gpu_name); + } + const gpus = Array.isArray(sys.gpus) ? sys.gpus : []; + const tip = gpus.length + ? gpus.map(g => `GPU ${g.index}: ${g.name} · ${(+g.vram_gb).toFixed(1)} GB`).join('\n') + : 'Click to toggle off (X to hide)'; + gpuChip = chip('gpu', label, tip); } else if (sys.gpu_error) { gpuChip = _removedHwChips.has('gpu') ? '' diff --git a/static/js/cookbookServe.js b/static/js/cookbookServe.js index a50ab2a..32dbaa1 100644 --- a/static/js/cookbookServe.js +++ b/static/js/cookbookServe.js @@ -413,10 +413,16 @@ function _rerenderCachedModels() { // load, × to delete) plus a "Save current config" row — see _showSavedConfigMenu. // Split button: "Save" saves the current config directly; the arrow opens // the dropdown of saved configs (load / delete). Arrow shows the count. + // The arrow button shows just the saved-config count next to a "▾". + // Spell out what the number means in the tooltip so users don't have + // to click it to find out the badge isn't a notification dot. const _arrowLabel = _modelPresets.length > 0 ? `${_modelPresets.length} ▾` : '▾'; + const _arrowTitle = _modelPresets.length > 0 + ? `${_modelPresets.length} saved launch config${_modelPresets.length === 1 ? '' : 's'} for ${_repoShort} — click ▾ to load or delete` + : `No saved launch configs for ${_repoShort} yet — click Save to add one`; let _slotsHtml = `
` + `` - + `` + + `` + `
`; let panelHtml = `
${_slotsHtml}`; @@ -663,11 +669,15 @@ function _rerenderCachedModels() { panel.querySelector(`.cookbook-slot-btn[data-slot="${slotIdx}"]`)?.classList.add('active'); } - // Keep the arrow button's count in sync with the stored presets. + // Keep the arrow button's count + tooltip in sync with stored presets. function _updateSavedToggleLabel() { const n = _presetsForModel(_loadPresets(), repo).length; const t = panel.querySelector('.cookbook-saved-arrow'); - if (t) t.textContent = n > 0 ? `${n} ▾` : '▾'; + if (!t) return; + t.textContent = n > 0 ? `${n} ▾` : '▾'; + t.title = n > 0 + ? `${n} saved launch config${n === 1 ? '' : 's'} for ${_repoShort} — click ▾ to load or delete` + : `No saved launch configs for ${_repoShort} yet — click Save to add one`; } // Save the current panel fields as a new named preset (shared by the menu's diff --git a/static/style.css b/static/style.css index 48589f9..bfd8b4c 100644 --- a/static/style.css +++ b/static/style.css @@ -20215,6 +20215,19 @@ body.gallery-selecting .gallery-dl-btn, display: inline-flex; align-items: center; gap: 3px; + /* Cap chip width so a long label (e.g. heterogeneous GPU group + "1× RTX 4090 + 1× RTX 3060") wraps to the next row instead of + overflowing the modal. Full text stays in the tooltip. */ + max-width: 100%; +} +.hwfit-hw-chip-toggle { + /* Allow the chip body to truncate with an ellipsis when the chip + itself is capped at its container's width. Without this, the + toggle button keeps its intrinsic width and pushes the × button + off-screen on narrow viewports. */ + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; } .hwfit-hw-chip button, .hwfit-hw-chip-dismiss,