From fa01a02a6df35de7969f1ce80925f44c6a1c3a3d Mon Sep 17 00:00:00 2001 From: Michael Han <107991372+shimmyshimmer@users.noreply.github.com> Date: Wed, 10 Jun 2026 07:28:28 -0700 Subject: [PATCH] Studio UI polish: search dialog shadow, model picker pills, sidebar spacing, white Hub background (#6147) * Studio UI polish: search dialog shadow, picker pills, sidebar spacing, white Hub - Chat search dialog: keep a page-bg shadow in dark mode so the dialog does not merge into same-color content behind it - Model selector: hover and selected rows are fully rounded pills - Sidebar: nudge nav items, recents and section labels 2px right and widen the hover pill 2px left so its side gaps match - Hub: pure white page background in light mode and drop the ambient card glow that tinted the page gray * Lighten sidebar border, widen model picker rows - Sidebar border is #f2f2f2 in light mode - Model picker rows extend 4px further left and right, lining up with the search bar * Plus menu: restore the intended 18px corner radius The global 14px !important dropdown radius overrode the menu's own 18px, so the plus menu and its submenus rendered squarer than designed. Mark the menu radius !important so it wins. * Projects: borderless import/export button; picker search border #f2f2f2 - The import/export button on the Projects page drops its outline border and matches the Sort by pill next to it - The model picker search inputs use a #f2f2f2 border in light mode, dark mode keeps the default * Model selector: align the popup with the trigger label The popover lined up with the trigger button edge, 14px left of the label text. A 10px alignOffset starts it just before the label. * Model picker: shorten search placeholder to Search models * Pill-shaped model trigger and panel controls; chat template edit button - Select model trigger: rounded pill with slightly tighter horizontal padding - KV cache, speculative decoding, draft N and preset controls in the chat settings panel are rounded pills - Chat Template: replace the three-line preview box with an edit icon button next to the label; it opens the existing editor dialog * Training overlay: borderless console, sloth attached to the box - Drop the terminal border for the training start console - Remove the gap between the sloth image and the console so they touch * Settings panel: more left padding in pill controls, smaller template edit icon - f16, Auto and draft N pills get 12px left padding, widths bumped 4px to keep the values from truncating - Chat template edit icon shrinks to 14px and the Chat Template label also opens the editor * Model picker: more padding inside row pills, hugeicons delete icon - Row pills get 12px horizontal padding so text is not pressed against the highlight boundary - The cached model delete button uses the hugeicons Delete02 icon instead of the lucide trash icon * Address review: hub bg layering, dead shadow utilities, merged paddings - Remove bg-background from the Hub root: the white rule lives in the base layer and the utility was winning the cascade, so the white background never applied - Drop the inline shadow utilities on the chat search dialog: the chat-search-surface rules sit later in the utilities layer and already win, making the inline ones dead - Merge symmetric pl/pr pairs into px on the sidebar group wrappers --- .../frontend/src/components/app-sidebar.tsx | 6 +-- .../assistant-ui/model-selector.tsx | 13 ++--- .../model-selector/model-delete-action.tsx | 9 +++- .../assistant-ui/model-selector/pickers.tsx | 14 ++--- .../src/features/chat/chat-settings-sheet.tsx | 54 ++++++++++++------- .../chat/components/chat-search-dialog.tsx | 2 +- .../src/features/chat/projects-page.tsx | 7 ++- studio/frontend/src/features/hub/hub-page.tsx | 2 +- studio/frontend/src/features/hub/hub.css | 7 ++- .../studio/training-start-overlay.tsx | 4 +- studio/frontend/src/index.css | 19 ++++--- 11 files changed, 84 insertions(+), 53 deletions(-) diff --git a/studio/frontend/src/components/app-sidebar.tsx b/studio/frontend/src/components/app-sidebar.tsx index 14131c3c1c..575fd20a06 100644 --- a/studio/frontend/src/components/app-sidebar.tsx +++ b/studio/frontend/src/components/app-sidebar.tsx @@ -217,7 +217,7 @@ function NavItem({ onClick={onClick} isActive={active} data-tour={dataTour} - className="sidebar-nav-btn h-[33px] rounded-full gap-[8.5px] px-2.5 font-medium group-data-[collapsible=icon]:!w-[32px] group-data-[collapsible=icon]:!rounded-full group-data-[collapsible=icon]:mx-auto" + className="sidebar-nav-btn h-[33px] rounded-full gap-[8.5px] pl-3 pr-2.5 font-medium group-data-[collapsible=icon]:px-2.5 group-data-[collapsible=icon]:!w-[32px] group-data-[collapsible=icon]:!rounded-full group-data-[collapsible=icon]:mx-auto" > {label} @@ -558,7 +558,7 @@ export function AppSidebar() { : "sidebar-row-action group-hover/recent-item:opacity-100 group-hover/recent-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto"; const buttonClass = cn( "sidebar-nav-btn h-[33px] cursor-pointer rounded-full pr-4 text-[14.5px] leading-[19px] tracking-nav font-medium", - variant === "project" ? "pl-[37px]" : "pl-2.5", + variant === "project" ? "pl-[39px]" : "pl-3", variant === "project" ? "group-hover/project-chat-item:pr-8 group-has-[.sidebar-row-action[data-state=open]]/project-chat-item:pr-8" : "group-hover/recent-item:pr-8 group-has-[.sidebar-row-action[data-state=open]]/recent-item:pr-8", @@ -966,7 +966,7 @@ export function AppSidebar() { > { setSelectedHistoryRunId(run.id); closeMobileIfOpen(); diff --git a/studio/frontend/src/components/assistant-ui/model-selector.tsx b/studio/frontend/src/components/assistant-ui/model-selector.tsx index f8a3ce4e20..b8d1bc0f00 100644 --- a/studio/frontend/src/components/assistant-ui/model-selector.tsx +++ b/studio/frontend/src/components/assistant-ui/model-selector.tsx @@ -148,12 +148,12 @@ function ModelSelectorTrigger({ className={cn( "unsloth-model-selector-trigger flex min-w-0 items-center gap-2 transition-colors", variant === "outline" && - "rounded-[10px] border border-border/60 hover:bg-[#ececec] dark:hover:bg-[#2d2e32]", - variant === "ghost" && "rounded-[10px] hover:bg-[#ececec] dark:hover:bg-[#2d2e32]", - variant === "muted" && "rounded-[10px] bg-muted hover:bg-muted/80", - size === "sm" && "h-8 px-3 text-xs", - size === "default" && "h-9 px-3.5 text-sm", - size === "lg" && "h-10 px-4 text-sm", + "rounded-full border border-border/60 hover:bg-[#ececec] dark:hover:bg-[#2d2e32]", + variant === "ghost" && "rounded-full hover:bg-[#ececec] dark:hover:bg-[#2d2e32]", + variant === "muted" && "rounded-full bg-muted hover:bg-muted/80", + size === "sm" && "h-8 px-2.5 text-xs", + size === "default" && "h-9 px-3 text-sm", + size === "lg" && "h-10 px-3.5 text-sm", className, )} > @@ -244,6 +244,7 @@ function ModelSelectorContent({ return ( - + @@ -386,7 +386,7 @@ function GgufVariantExpander({ handleVariantClick(v.quant, v.downloaded, v.size_bytes) } className={cn( - "flex min-w-0 flex-1 items-center justify-between gap-2 rounded-[8px] px-2.5 py-1 text-left text-sm transition-colors hover:bg-[#ececec] dark:hover:bg-[#3a3d44]", + "flex min-w-0 flex-1 items-center justify-between gap-2 rounded-full px-3 py-1 text-left text-sm transition-colors hover:bg-[#ececec] dark:hover:bg-[#3a3d44]", )} > @@ -887,8 +887,8 @@ export function HubModelPicker({ setQuery(event.target.value)} - placeholder="Search Hugging Face models" - className="h-9 pl-8 pr-8" + placeholder="Search models" + className="h-9 border-[#f2f2f2] dark:border-input pl-8 pr-8" /> {isLoading && ( @@ -896,7 +896,7 @@ export function HubModelPicker({
-
+
{!cachedReady && !showHfSection ? (
@@ -1478,12 +1478,12 @@ export function LoraModelPicker({ value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Search trained models" - className="h-9 pl-8" + className="h-9 border-[#f2f2f2] dark:border-input pl-8" />
-
+
{grouped.length === 0 ? (
No trained models found. diff --git a/studio/frontend/src/features/chat/chat-settings-sheet.tsx b/studio/frontend/src/features/chat/chat-settings-sheet.tsx index 776bad5244..96415c4da0 100644 --- a/studio/frontend/src/features/chat/chat-settings-sheet.tsx +++ b/studio/frontend/src/features/chat/chat-settings-sheet.tsx @@ -54,6 +54,7 @@ import { cn } from "@/lib/utils"; import { ArrowDown01Icon, ArrowTurnBackwardIcon, + Edit03Icon, InformationCircleIcon, LayoutAlignRightIcon, } from "@hugeicons/core-free-icons"; @@ -841,7 +842,7 @@ export function ChatSettingsPanel({ animateRadius={false} icon={ArrowDown01Icon} iconClassName="size-3.5" - className="grid h-7 w-[60px] min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-1 rounded-[10px] border-transparent bg-black/[0.04] dark:bg-white/[0.05] hover:bg-black/[0.06] dark:hover:bg-white/[0.1] px-2 py-0 text-[13px]! font-medium text-nav-fg focus-visible:ring-0 focus-visible:border-transparent [&_[data-slot=select-value]]:min-w-0 [&_[data-slot=select-value]]:truncate [&>svg]:shrink-0" + className="grid h-7 w-[64px] min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-1 rounded-full border-transparent bg-black/[0.04] dark:bg-white/[0.05] hover:bg-black/[0.06] dark:hover:bg-white/[0.1] pl-3 pr-2 py-0 text-[13px]! font-medium text-nav-fg focus-visible:ring-0 focus-visible:border-transparent [&_[data-slot=select-value]]:min-w-0 [&_[data-slot=select-value]]:truncate [&>svg]:shrink-0" > @@ -881,7 +882,7 @@ export function ChatSettingsPanel({ animateRadius={false} icon={ArrowDown01Icon} iconClassName="size-3.5" - className="grid h-7 w-[120px] min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-1 rounded-[10px] border-transparent bg-black/[0.04] dark:bg-white/[0.05] hover:bg-black/[0.06] dark:hover:bg-white/[0.1] px-2 py-0 text-[13px]! font-medium text-nav-fg focus-visible:ring-0 focus-visible:border-transparent [&_[data-slot=select-value]]:min-w-0 [&_[data-slot=select-value]]:truncate [&>svg]:shrink-0" + className="grid h-7 w-[124px] min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-1 rounded-full border-transparent bg-black/[0.04] dark:bg-white/[0.05] hover:bg-black/[0.06] dark:hover:bg-white/[0.1] pl-3 pr-2 py-0 text-[13px]! font-medium text-nav-fg focus-visible:ring-0 focus-visible:border-transparent [&_[data-slot=select-value]]:min-w-0 [&_[data-slot=select-value]]:truncate [&>svg]:shrink-0" data-test-id="speculative-type-select" > @@ -932,7 +933,7 @@ export function ChatSettingsPanel({ }} data-test-id="spec-draft-n-max-input" aria-label="Speculative decoding draft tokens" - className="h-7 w-[72px] rounded-[10px] border-transparent bg-black/[0.04] dark:bg-white/[0.05] hover:bg-black/[0.06] dark:hover:bg-white/[0.1] px-2 py-0 text-[13px] font-medium text-nav-fg outline-none focus-visible:ring-0" + className="h-7 w-[76px] rounded-full border-transparent bg-black/[0.04] dark:bg-white/[0.05] hover:bg-black/[0.06] dark:hover:bg-white/[0.1] pl-3 pr-2 py-0 text-[13px] font-medium text-nav-fg outline-none focus-visible:ring-0" />
)} @@ -1563,11 +1564,15 @@ function ChatTemplateFields() { return ( <> -
-
- - Chat Template - +
+ +
{isModified && ( @@ -1593,17 +1598,30 @@ function ChatTemplateFields() { )} + + + + + + Edit template + +
-
diff --git a/studio/frontend/src/features/chat/projects-page.tsx b/studio/frontend/src/features/chat/projects-page.tsx index a9496858c0..a9ce03fa47 100644 --- a/studio/frontend/src/features/chat/projects-page.tsx +++ b/studio/frontend/src/features/chat/projects-page.tsx @@ -269,7 +269,12 @@ export function ProjectsPage() {
- diff --git a/studio/frontend/src/features/hub/hub-page.tsx b/studio/frontend/src/features/hub/hub-page.tsx index f606a9941a..5b7fc4ad77 100644 --- a/studio/frontend/src/features/hub/hub-page.tsx +++ b/studio/frontend/src/features/hub/hub-page.tsx @@ -675,7 +675,7 @@ export function ModelsPage() { ); return ( -
+
div > section .border-b, diff --git a/studio/frontend/src/features/studio/training-start-overlay.tsx b/studio/frontend/src/features/studio/training-start-overlay.tsx index ba80432896..b9fb34fedb 100644 --- a/studio/frontend/src/features/studio/training-start-overlay.tsx +++ b/studio/frontend/src/features/studio/training-start-overlay.tsx @@ -311,7 +311,7 @@ export function TrainingStartOverlay({ return (
-
+
Unsloth Studio diff --git a/studio/frontend/src/index.css b/studio/frontend/src/index.css index 48e587953f..34e4241ae2 100644 --- a/studio/frontend/src/index.css +++ b/studio/frontend/src/index.css @@ -146,7 +146,7 @@ --sidebar-primary-foreground: oklch(1 0 0); --sidebar-accent: oklch(0.96 0.0279 166.55); --sidebar-accent-foreground: oklch(0.2868 0.0649 159.9823); - --sidebar-border: oklch(0.92 0 0); + --sidebar-border: #f2f2f2; --sidebar-ring: #17b88b; --destructive-foreground: oklch(1 0 0); --font-sans: "Inter Variable", ui-sans-serif, sans-serif, system-ui; @@ -599,7 +599,7 @@ } .sidebar-sticky-label { - @apply rounded-none bg-sidebar pt-0 pb-[8px] pl-[18px] pr-4 text-[14px]! leading-[17px] font-medium normal-case focus-visible:ring-0! focus-visible:outline-none transition-shadow duration-150; + @apply rounded-none bg-sidebar pt-0 pb-[8px] pl-[20px] pr-4 text-[14px]! leading-[17px] font-medium normal-case focus-visible:ring-0! focus-visible:outline-none transition-shadow duration-150; /* Muted section-header gray, matching Gemini's "Notebooks"/"Recents". Lightened from #5f6368 so the label reads as a header, clearly lighter than the near-black nav items. */ @@ -621,7 +621,7 @@ prompt button, and the chat-template textarea so all three read as one quiet gray family, matching the focused-edit-number tint. */ .panel-input-group { - @apply !h-9 min-h-9 min-w-0 items-stretch gap-0 rounded-[14px] pr-0 transition-colors focus-within:ring-0 focus-within:shadow-none; + @apply !h-9 min-h-9 min-w-0 items-stretch gap-0 rounded-full pr-0 transition-colors focus-within:ring-0 focus-within:shadow-none; border: 0 !important; background-color: var(--panel-input-surface); } @@ -904,12 +904,14 @@ /* Pin to the dark --radius so the corners are the same (less round) in both themes; rounded-4xl here and on the inner Command follow this. */ --radius: 0.625rem; - /* Match the chat box: soft elevation in light, none in dark. */ + /* Match the chat box: soft elevation. */ box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.16); } + /* Dark: page-bg shadow so the dialog does not merge into same-color + content behind it (same convention as the dropdowns). */ .dark .chat-search-surface { - box-shadow: none; + box-shadow: 0 4px 14px var(--background); } .menu-soft-surface, @@ -1255,8 +1257,9 @@ /* Soft elevation; [data-slot] outranks the component ring-1, dropping the border. */ .unsloth-plus-menu[data-slot] { - /* Pin radius so dark matches light (rounded-lg resolves smaller in dark). */ - border-radius: 18px; + /* Pin radius so dark matches light (rounded-lg resolves smaller in dark). + !important beats the global 14px dropdown radius. */ + border-radius: 18px !important; padding-top: 0.5rem; padding-bottom: 0.5rem; box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.16); @@ -1265,7 +1268,7 @@ /* MCP menu is wider (232px), so nudge the radius up so it reads as round as the narrower + menu (18 * 232/212 ~= 20px). */ .unsloth-plus-menu.mcp-menu[data-slot] { - border-radius: 20px; + border-radius: 20px !important; } .dark .unsloth-plus-menu[data-slot] {