Cookbook: clearer tooltips on saved-config badge and GPU chip (#850)
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 <model> — click ▾ to load or delete"
(and "No saved launch configs for <model> 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.
This commit is contained in:
@@ -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')
|
||||
? ''
|
||||
|
||||
@@ -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 = `<div class="cookbook-serve-slots cookbook-saved-split">`
|
||||
+ `<button type="button" class="cookbook-slot-btn cookbook-saved-save" title="Save current config"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>Save</button>`
|
||||
+ `<button type="button" class="cookbook-slot-btn cookbook-saved-arrow" title="Saved launch configs">${_arrowLabel}</button>`
|
||||
+ `<button type="button" class="cookbook-slot-btn cookbook-saved-arrow" title="${esc(_arrowTitle)}">${_arrowLabel}</button>`
|
||||
+ `</div>`;
|
||||
|
||||
let panelHtml = `<div class="hwfit-serve-panel">${_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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user