From ecb80670e563d0fe2a84bfadb6ead5cd774d2706 Mon Sep 17 00:00:00 2001 From: Unsloth Date: Wed, 29 Jul 2026 00:12:37 -0700 Subject: [PATCH] Studio: size the GPU row to its pane and fix the docs link name - The device row keyed its layout off the viewport, but the Settings dialog caps the tab at roughly 650px no matter how wide the window is, so on every desktop the row stayed horizontal and squeezed the device name down to a fragment. It now uses a container query against the pane and only goes to one row when there is genuinely space. - The docs link carried an aria-label that replaced its visible "unsloth start" text as the accessible name, so it could not be targeted by the label users can see. The title keeps the description. - Dropped a stale agent from the swap hint so it matches the picker. --- .../src/features/settings/tabs/agents-tab.tsx | 5 +- .../features/settings/tabs/resources-tab.tsx | 169 +++++++++--------- studio/frontend/src/i18n/locales/en.ts | 3 +- 3 files changed, 92 insertions(+), 85 deletions(-) diff --git a/studio/frontend/src/features/settings/tabs/agents-tab.tsx b/studio/frontend/src/features/settings/tabs/agents-tab.tsx index 63dc31faf8..d810048a86 100644 --- a/studio/frontend/src/features/settings/tabs/agents-tab.tsx +++ b/studio/frontend/src/features/settings/tabs/agents-tab.tsx @@ -1094,12 +1094,13 @@ export function AgentsTab() { data-settings-label={t("settings.agents.intro")} className="text-sm text-muted-foreground leading-relaxed" > - {/* The chip is the docs entry point, so no separate link is needed. */} + {/* The chip is the docs entry point, so no separate link is needed. + No aria-label: it would replace the visible "unsloth start" as the + accessible name, leaving voice control unable to target it. */} diff --git a/studio/frontend/src/features/settings/tabs/resources-tab.tsx b/studio/frontend/src/features/settings/tabs/resources-tab.tsx index 496432c7a4..8ac51f3e38 100644 --- a/studio/frontend/src/features/settings/tabs/resources-tab.tsx +++ b/studio/frontend/src/features/settings/tabs/resources-tab.tsx @@ -456,92 +456,99 @@ export function ResourcesTab() { )} - {hasGpu ? ( - metrics.devices.map((device, index) => { - const ordinal = deviceOrdinal(device); - // Preserve null (unknown, e.g. Windows ROCm perf counter); coercing - // to 0 would render a fabricated 0 used / full free. - const total = device.memory_total_gb ?? null; - const used = device.vram_used_gb ?? null; - const free = - device.vram_free_gb ?? - (isFiniteNumber(total) && isFiniteNumber(used) - ? Math.max(0, total - used) - : null); - const percent = - device.vram_utilization_pct ?? - (isFiniteNumber(total) && total > 0 && isFiniteNumber(used) - ? (used / total) * 100 - : null); - const safePercent = clampPercent(percent); - const usedText = isFiniteNumber(used) - ? formatGiB(used) - : unknownLabel; - const freeText = isFiniteNumber(free) - ? formatGiB(free) - : unknownLabel; - const totalText = isFiniteNumber(total) - ? formatGiB(total) - : unknownLabel; - const percentText = isFiniteNumber(percent) - ? formatPercent(safePercent) - : unknownLabel; - return ( - // One row per device: a full-width bar under a single GPU read - // as a page-wide rule, so the bar is sized to the row instead. -
-
-
- {device.name ?? t("settings.resources.gpu.unknownDevice")} + {/* Sized against this pane, not the viewport: the Settings dialog caps + the tab at roughly 650px however wide the window is. */} +
+ {hasGpu ? ( + metrics.devices.map((device, index) => { + const ordinal = deviceOrdinal(device); + // Preserve null (unknown, e.g. Windows ROCm perf counter); coercing + // to 0 would render a fabricated 0 used / full free. + const total = device.memory_total_gb ?? null; + const used = device.vram_used_gb ?? null; + const free = + device.vram_free_gb ?? + (isFiniteNumber(total) && isFiniteNumber(used) + ? Math.max(0, total - used) + : null); + const percent = + device.vram_utilization_pct ?? + (isFiniteNumber(total) && total > 0 && isFiniteNumber(used) + ? (used / total) * 100 + : null); + const safePercent = clampPercent(percent); + const usedText = isFiniteNumber(used) + ? formatGiB(used) + : unknownLabel; + const freeText = isFiniteNumber(free) + ? formatGiB(free) + : unknownLabel; + const totalText = isFiniteNumber(total) + ? formatGiB(total) + : unknownLabel; + const percentText = isFiniteNumber(percent) + ? formatPercent(safePercent) + : unknownLabel; + return ( + // One row per device: a full-width bar under a single GPU read + // as a page-wide rule, so the bar is sized to the row instead. +
+
+
+ {device.name ?? t("settings.resources.gpu.unknownDevice")} +
+
+ {ordinal === undefined + ? backendLabel + : `${t("settings.resources.gpu.deviceWithIndex", { + index: ordinal, + })}, ${backendLabel}`} +
-
- {ordinal === undefined - ? backendLabel - : `${t("settings.resources.gpu.deviceWithIndex", { - index: ordinal, - })}, ${backendLabel}`} -
-
- {/* Narrow screens stack instead of hiding: the used, free and - total figures stay reachable on tablets and small laptops. */} -
-
- - {t("settings.resources.gpu.used", { value: usedText })} - - - {t("settings.resources.gpu.free", { value: freeText })} - - - {t("settings.resources.gpu.total", { value: totalText })} - -
-
- {/* Fills the space left by the percentage rather than + {/* Stacked until the pane is genuinely wide enough: the + figures, meter and percentage need about 50rem before the + name has room left to read. */} +
+
+ + {t("settings.resources.gpu.used", { value: usedText })} + + + {t("settings.resources.gpu.free", { value: freeText })} + + + {t("settings.resources.gpu.total", { + value: totalText, + })} + +
+
+ {/* Fills the space left by the percentage rather than claiming the full row, which overflowed once stacked. */} - -
- {percentText}{" "} - {t("settings.resources.gpu.vramUtilization")} + +
+ {percentText}{" "} + {t("settings.resources.gpu.vramUtilization")} +
-
- ); - }) - ) : ( -
- {t("settings.resources.gpu.noGpu")} -
- )} + ); + }) + ) : ( +
+ {t("settings.resources.gpu.noGpu")} +
+ )} +
diff --git a/studio/frontend/src/i18n/locales/en.ts b/studio/frontend/src/i18n/locales/en.ts index 2b5bf7f508..642b224d2a 100644 --- a/studio/frontend/src/i18n/locales/en.ts +++ b/studio/frontend/src/i18n/locales/en.ts @@ -843,8 +843,7 @@ export const en = { codingAgents: "Coding agents", codingAgentsHint: "Launch a coding agent against this server. It uses the loaded model; a local server mints an API key automatically, a remote one includes it in the command.", - codingAgentsSwap: - "Swap claude for codex, openclaw, opencode, hermes, or pi.", + codingAgentsSwap: "Swap claude for codex, openclaw, opencode, or hermes.", codingAgentDetected: "Installed on this machine", codingAgentsDetectedHint: "Detected on this machine: {agents}.", relativeNever: "never",