Commit graph

12 commits

Author SHA1 Message Date
Daniel Han
53e8de601d
studio: persist personalization (profile, avatar, theme) server-side (#6516)
* studio: persist personalization (profile + theme) server-side

Profile name/nickname/avatar and appearance (theme) were stored only in the
browser's localStorage, so every browser or device that connected to the same
Studio started from defaults and forgot the user's personalization.

Persist them server-side (single-account, stored as one JSON blob in
app_settings) so they follow the account:
- utils/personalization_settings.py + GET/PUT /api/settings/personalization,
  with validation (theme/shape enums, avatar must be an image data URL capped at
  512 KB) and a 'saved' flag.
- Frontend usePersonalizationSync (mounted in the root layout when signed in)
  hydrates the profile + theme stores from the server when a blob exists, and
  otherwise migrates the existing local settings up once so nothing is lost;
  later changes are written through, debounced. Writers keep using the local
  stores unchanged.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix/adjust personalization sync for PR #6516

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix Studio personalization sync edge cases

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: wasimysaid <wasimysdev@gmail.com>
2026-06-22 04:09:48 -07:00
Eyera
435cdbc372
feat(hub): full-page redesign with trending feed, search, and persisted state (#6349)
* fix(hub): stop demoting cached gguf variants on mmproj or filename mismatch

- bug: a quant with its bytes on disk was marked not fully downloaded when the API-preferred main filenames did not match or the mmproj adapter was absent
- fix: fall back to the on-disk quant byte signal, the same one inventory uses for on-device, so a present quant is no longer demoted
- broaden mmproj detection to accept any mmproj-looking cached file, not only the API-preferred name

* feat(hub): full-page redesign with trending feed, search, and persisted state

- convert the hub to a full-page view with a new layout, model cards, and a sortable models table
- add a trending/latest/finetune feed with model and section deep-link params validated on the hub route
- add recent searches and rework model and dataset search with pagination and infinite scroll
- persist feed and token state through a dedicated store and persist-storage layer

* fix(hub): browse sorting, deep-link presets, dataset URLs, and persistence

- sort dropdown: drive HF-wide browse across all repos by the chosen sort, respecting format and capability filters
- section deep-link: apply the section preset (format and sort) on refresh and deep-link, not only on click
- dataset detail URL: persist the resource kind so refresh and share resolve datasets correctly
- gguf card: show a Loading state for hub-cache dir-path repos via repo-id match
- active-model CTA: New Chat now actually opens a fresh chat
- persist-storage: fix throttle keying with a Map and dedupe a duplicated util
- transport-toggle: drop redundant controlled-tooltip state

* Studio: polish hub redesign UI and unify segmented tabs

Refinements on top of the hub full-page redesign:

- Unify every segmented control (Discover/On Device, models/datasets,
  Unsloth/All, recent/name/size, settings tabs, train dataset source,
  profile shape, theme, OS toggle) on one filled-pill design.
- Hub list now loads in larger batches with a shorter fetch interval so
  results fill in fast instead of dripping one row at a time.
- Disable remote avatar fetches in list rows and brighten the colored
  initial fallbacks so they read clearly without network calls.
- Add a split master-detail view for model lists and make it the default.
- Left-align the split "Showing GGUF models" header with the rows below.
- Round the "Load more" footer box and tidy On Device stats layout.
- Show recent trainings on Recipes and Export, falling back to recent
  chats when there is no training history.

* Studio: address hub review comments (filter warning + scrollMargin)

- DiscoverFetchMoreFooter only shows the "results may be hidden by your
  filters" note when a filter is actually active, instead of always.
- Use the destructured scrollMargin prop directly in the row transform
  rather than reaching into virtualizer.options.scrollMargin.

* Fix/adjust Hub metadata and deep links for PR #6349

* Studio: drop avatar ring in hub split view

The split master-pane rows (discover + on device) added a ring-1 around
the owner avatar that read as a shadow. Remove it so split-view avatars
match the flat avatars elsewhere; grid cards and the full list keep theirs.

* Studio: hub sort + scope as dropdown pills beside view tabs

Recent/Name/Size and Unsloth/All were segmented controls that dropped to
their own row in the narrow split pane. Make each a compact dropdown pill
(HubOptionMenu) that sits in the header actions slot next to the view-mode
tabs in every layout, so split view no longer needs a separate row.

* Studio: align hub list header with the view tabs and rows

- Vertically center the "On device" / "Showing GGUF models" title with the
  dropdown pill and view-mode tabs (items-center instead of items-end), so a
  short title no longer sits low against the taller tab row.
- Nudge the back chevron 2px further left (-ml-2) so its glyph edge lines up
  with the start of the row hover below it.

* Studio: align back chevron tip with the row hover edge

The arrow glyph is inset ~6px inside its centered icon box, so an
edge-aligned button left the visible chevron sitting in from the column.
Pull the button out (-ml-3.5) so the chevron tip lands on the row hover's
left edge instead of floating to its right.

* Studio: unify every bare tick on the shared check mark

Point all plain checkmarks at the canonical @/lib/tick-icon tick (the one
already used in the chat composer and menus), so there is a single tick
across the app:

- Hub: model-inspector, hub-option-menu, path-info-button were importing
  the stock hugeicons Tick02Icon; switch them to the shared icon.
- Chat / assistant-ui: artifact-surface, prompt-storage-dialog, reasoning,
  tool-ui-python, tool-ui-terminal, tool-ui-code-execution, and the
  tool-fallback status map used lucide CheckIcon; render the shared tick
  via HugeiconsIcon instead (tool-fallback wraps it to fit its icon map).

The circular CheckmarkCircle success badges are intentionally left as-is.
No bare CheckIcon/stock Tick02Icon references remain; verified the tick
renders in every converted spot via typecheck + build.

* Studio: nudge back chevron 2px right

-ml-3.5 pushed the chevron a touch too far left; -ml-3 sits it just
inside the row hover edge, aligned with the avatars below.

* Studio: search base-model chips across all publishers

Clicking a Base model chip searches the Hub for the upstream repo, which
lives under another publisher (google, meta, etc.). It left ownerScope at
the default "unsloth", so the search hard-restricted to the Unsloth org and
could never surface the base model. Switch the scope to "all" for this action.

* Studio: label the safetensors list header "Safetensors"

The focused list heading showed "Showing Checkpoint ... models" while the
format dropdown labels the same checkpoint filter value "Safetensors". Match
the dropdown so the header reads "Showing Safetensors ... models".

* Studio: simplify the focused list heading to "Models"

Drop the format/capability composition (e.g. "Showing Safetensors Reasoning
models") so the focused list heading just reads "Models" (or "Datasets").
Search keeps its "Results for ..." label.

* Studio: drop the header refresh button to the text baseline

The refresh button sat at the heading's vertical centre. Nudge it down so
it lines up with the bottom of the title text instead.

* Studio: hide redundant "Back to Hub" in the split detail pane

In split view on large screens the master list sits beside the detail, so
the back button is redundant. Hide it there (lg) and reclaim the top space.
It stays on the small-screen overlay and the full-page detail, where the
list is hidden and back is the only way out.

* Studio: match the readme scroll fade to the left column

The detail pane relied on the sticky back-bar's fade, which is now hidden in
split view. Add the same hub-scroll-fade overlay the master list uses so the
readme fades consistently at the top when scrolled. The back-bar, when shown,
sits above and covers it.

* Studio: align Hub refresh button to the heading text bottom

* Studio: nudge Hub refresh button up to the heading text

* Studio: optically centre the HF token shield in its circle

* Studio: preview the first visible on-device row in split view

* Studio: calm the on-device row colour and fix size tooltip contrast

* Studio: fix Hub reset tab and clear search when opening a section

* Studio: fix Hub feed defaults, filter sync, and GGUF vision download state

* Studio: condense Hub redesign code comments

---------

Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: shimmyshimmer <info@unsloth.ai>
Co-authored-by: Michael Han <107991372+shimmyshimmer@users.noreply.github.com>
Co-authored-by: wasimysaid <wasimysdev@gmail.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: danielhanchen <michaelhan2050@gmail.com>
2026-06-17 04:50:34 -07:00
Michael Han
2262e02b1a
Studio: UI polish for sidebar, menus, hub and toasts (#6288)
* Studio: UI polish for sidebar, menus, hub and toasts

- Pinned chats: add a Pinned section above Recents with a Pin/Unpin menu
  action and a hover unpin button on each pinned row
- Sidebar: align the Recents and Pinned section labels with Train, and
  switch the chat Archive icon to archive-03
- Dropdown menus: pull separators in to match item width, and even out the
  projects menu padding and radius to match the composer menu
- Account menu: match the divider width to the row hover width
- Hub toolbar: equalize the format, capability and sort filter widths so
  the search field has more room
- Model run bar: make the quant selector hover a full pill
- Tooltips: slightly larger corner radius
- Auth: reduce horizontal padding on the submit button
- Toasts: center the model load content so top and bottom padding match

* Studio: shrink pinned unpin icon, match projects menu to composer menu

* Studio: inset dropdown separators slightly from item width

* Studio: move archived chats from the sidebar into chat settings

Archive now shows a toast pointing to Settings. Archived chats are listed
and managed (open, unarchive, delete) under Settings, Chat, Data.

* Studio: make the archive toast open the archived chats list

* Studio: keep chats archived when opened from the archived list

* Chat settings: optional delete confirmation and menu cleanup

Add a "Confirm before deleting" toggle in Settings > Chat > Data. When
turned off, deleting a chat from the sidebar skips the confirm dialog
and removes it instantly. The preference is stored client-side.

Add a separator above Archive in the chat row menu so Archive and
Delete read as a group separate from Export.

Drop the duplicate divider above "Clear all chats". The section already
draws a divide-y separator between rows, so the destructive row no
longer adds its own top border.

* Hub, dialog and page polish

Hub catalog:
- Drop "inference" from the Hub subtitle.
- Make the HF token shield a circle in the header.
- Equal, narrower filter triggers that stay a fixed width; long labels
  truncate with an ellipsis instead of clipping.
- Tighter gap between the label and the dropdown chevron so one more
  character fits.
- Keep the toolbar on one row at desktop widths.
- Align the model list and row hover with the Discover tab, add right
  side breathing room.
- Make the dark-mode scrollbar lighter so it is easier to see.
- Filled size and quant chips no longer draw a border.
- Column divider uses the sidebar border color.
- Hub card extends into the bottom space.

Filters and channels:
- Add an MLX format option; detect MLX repos by library or tag.
- "Fine-tune ready" now includes 16bit safetensors, not only bnb-4bit.
- Shorten the curated channel descriptions.

Shared UI:
- New standard chevron icons (down and submenu right) shared across the
  Hub, dropdown menu, menubar, project switcher and model selector.
- Dialogs get more top and bottom padding and a larger heading.
- Guided Tour hides itself on pages with no tour.

Other pages:
- Export and Data Recipes use the same page padding as the Hub.

* Soften popup overlays and refine hub filters

Dialogs, alert dialogs and sheets now use a light blur with a slight
darken instead of a heavy black scrim, so popups stay legible without
dimming the whole screen. Edit System Prompt and Edit Chat Template
drop their custom overlays and share the same look.

Also limit the Fine-tune ready channel to checkpoints Unsloth can
actually fine-tune (drops fp8, nvfp4, w4a16 and similar), revert chip
borders so filled chips stay visible, nudge the Run icon to center it,
and shrink the submenu chevron a touch.

* Inline chat rename and review-feedback fixes

Rename a chat inline as a rounded pill on the row instead of opening a
dialog. Enter saves, Escape cancels, clicking away saves. Projects and
training runs keep the existing dialog.

Address review feedback:
- Route the no-confirm chat delete through the shared cleanup helper so
  it gets the same error toast and pin removal as the confirmed path.
- Archived chats now confirm before deleting (respecting the setting)
  and pass the active thread, so deleting the open chat resets to a new
  chat instead of leaving a stale thread URL.
- Only offer the MLX format filter on Discover, since downloaded
  inventory rows are never tagged mlx and would filter to empty.
- Tighten the non-finetunable name match so a dot-delimited token such
  as .tflite is also excluded from the Fine-tune ready channel.

* Sidebar: borderless inline rename, no name flash, align titles

Rename now edits the title as plain highlighted text with no pill or
box. While the debounced sidebar refresh catches up after a rename the
row shows the new name optimistically, so the old name no longer flashes
back in. Recent chat titles now line up flush under the Recents label.

* Profile: preferred name and avatar shape options

Add a "What should Unsloth call you?" preferred name that drives the
new-chat greeting, falling back to the first name then the login id.
Add a toggle to show the avatar as a full circle or a rounded
rectangle, applied everywhere the avatar appears.

* Settings: show version block at the top of General

Surface the Unsloth and package versions at the top of General, not
just in About. Both tabs share one StudioVersionSection so the version
fetch and markup live in a single place.

* Profile: pick a sloth sticker as your avatar

Add a curated set of sloth stickers users can choose as a profile
picture, shown below the picture shape toggle in Settings > Profile.

- Only squarish, low-whitespace stickers are listed so each one fills
  the avatar frame cleanly.
- Selecting one reuses the existing avatar persistence (localStorage),
  so it sits alongside the photo upload with no backend changes.
- Image avatars now get a neutral background so transparent stickers
  read cleanly in both shapes.

* Profile: keep transparent avatars transparent

A transparent upload is now kept as WebP and shrunk to fit instead of
falling back to JPEG, which has no alpha and was painting a background
behind the image. The image avatar is also explicitly transparent.

* Profile: fall back to PNG for transparent avatars on Safari

Safari cannot encode WebP via canvas, so a transparent upload was
throwing instead of saving. Fall back to PNG, which keeps alpha and
works in every browser, before giving up. Still never uses JPEG.

* Archived chats: reset nav when deleting the open compare too

Compare panes do not write the active thread to the store, so the pair
id only lives in the route search. Derive the open chat id from the
route (thread or compare) like the sidebar does, so deleting the open
archived compare resets to a new chat instead of a dead URL.

* Profile: trim three sloth stickers from the picker

Drop the duplicate computer sloth without text on the screen (keeping
the Local one), the nature-screen sloth, and the gift sloth.

* Chat: tighten model selector right padding

Use less right padding than left so the trailing chevron does not leave
a heavy gap on the right of the trigger.

* Address review feedback on profile, reset and rename

- Give each sloth avatar button an accessible name so screen readers
  can tell the options apart.
- Reset all local preferences now clears the delete-confirmation pref,
  so it returns to the safe confirm default.
- A no-op Enter in inline rename now closes the editor instead of
  leaving the row stuck as an input with its blur suppressed.

* Chat: rebalance model selector padding and chevron

More left padding than right and pull the chevron close to the label so
the trigger reads balanced around the text instead of heavy on the right.

* Hub: shrink the external link dialog icon

Set the icon to size-6 so it sits proportionally in the media circle
instead of filling it.

* Hub: shrink the external link dialog media circle

Override the media circle to size-12 with a size-5 icon on this dialog
only, so it is more compact without changing the shared component.
2026-06-13 03:50:36 -07:00
Michael Han
f542ffb023
Studio: unify shadows, backgrounds and dark mode consistency in chat UI (#6116)
* Studio: refine chat UI shadow, background and sidebar divider

- Give both chat composers a Gemini style drop shadow with a short
  transition, and add a visible shadow in dark mode.
- Set the main chat surface to a soft off white (#fbfbfc) in light mode
  so the white composer reads as a card; dark mode is unchanged.
- Remove the divider line between the side menu and the page.

* Studio: lighten chat background to #fcfcfd

* Studio: make dark mode composer shadow visible

* Studio: drop composer shadow in dark mode

* Studio: unify all light mode shadows on the chatbox shadow

* Studio: unify dark mode surface colors and make hover states visible

* Studio: fix barely visible dark mode hover in account and plus menus

* Studio: lift settings dialog off the page background in dark mode

* Studio: drop greeting periods and restyle projects page to match the chatbox

* Studio: chatbox shadow on search dialog, grey sort pill on projects

* Studio: slimmer sidebar profile row, borderless login card

* Studio: match select popups to their trigger, taller profile hover target

* Studio: bigger projects search pill with the original chatbox glow

* Studio: keep select trigger shape while open

* Studio: center and narrow the projects search pill like Gemini

* Studio: borderless export card with the chatbox shadow

* Studio: chatbox shadow for selectable pills, warmer composer, search pill tweaks

* Studio: one warm background token for every page, softer search glow

* Studio: keep off white depth when warming the page background

* Studio: distinct sidebar surface from the page background

* Studio: white sidebar on the warm page background

* Studio: soften projects search shadow

* Studio: nudge the chat zero state up 5px

* Studio: darker dark mode page background, 28.5vh welcome offset

* Studio: dark mode shadows match the chatbox geometry, borderless recipes empty state

* Studio: revert dark mode shadows, desaturate dark recipe cards

* Studio: lift dark recipe cards, no hover shadow in dark

* Studio: drop the pale hover halo on recipe cards

* Studio: lighter dark recipe text, flat dark menus, flush select popups

* Studio: page-bg shadow on model selector, flush popovers and dropdowns

* Studio: zero menu offsets so dropdowns sit flush against triggers

* Studio: one 14px radius for list menus and sidebar buttons

* Studio: 14px buttons, pill hover shapes, narrower slider inputs

* Studio: pill buttons, keep profile row rectangular

* Studio: pill Save and Delete, small gap under account menu

* Studio: skinnier dropdown popups, larger account menu gap

* Studio: dropdown popups slightly wider than their trigger

* Restore light mode sidebar separator line

* Size panel number input pills to their content

* Lighten light mode page background

* Pure white composer, nudge page background whiter

* Use inline ch width for panel number pills

* Fix panel number pills at uniform 4ch width

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-06-10 03:09:37 -07:00
Daniel Han
fcfbf166ff
studio(ui): use the --primary brand token for the avatar fallback color (#5987)
* studio(ui): use the --primary brand token for the avatar fallback color

The fallback profile avatar hardcoded #14b789, a slightly different green
from the app's general brand color (--primary = #17b88b, used by the send
button and every other primary-colored control). Next to primary-colored UI
-- e.g. the artifact preview/code panel -- the avatar's off-brand shade looked
inconsistent ("changes color weirdly"). Point avatarBgStyle() at
var(--primary) so the avatar always renders the general brand green and
follows the theme token.

Verified live in Studio: the avatar was rgb(20,183,137) (#14b789) while
--primary resolves to rgb(23,184,139) (#17b88b); the fix unifies them. This
is the only hardcoded brand-green left in the frontend -- every other
brand-green element already uses --primary / bg-primary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* studio(ui): add literal fallback to the avatar --primary token

UserAvatar is a reusable component; if it is ever rendered outside the theme
root (where --primary is undefined), var(--primary) alone would compute to
transparent. Use var(--primary, #17b88b) so the avatar stays branded in that
edge case. When --primary is defined (the normal case, app-wide) it always
wins, so this changes nothing in practice -- verified in a browser:
var(--primary)=rgb(23,184,139), and an undefined var correctly falls back to
the literal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:44:24 -07:00
Daniel Han
85314ed162
Studio frontend: reduce and tighten code comments (#6099)
Trim and tighten code comments across studio/frontend TS/JS. Comment-only: every changed file verified code-identical to main via the TypeScript printer signature comparison.
2026-06-08 23:10:35 -07:00
Michael Han
37fd76a02f
studio: redesign chat composer (#5891)
* studio: redesign chat composer

Reworks the new-chat composer and the compare composer into a single
rounded pill surface with a softer, lighter look.

- New welcome screen with a time-of-day sloth mascot and a lighter
  heading.
- One rounded composer surface with a soft drop shadow. The input grows
  inline as you type and collapses back to a single row when cleared.
- Tools and attachments live in a single plus menu; the thinking control
  is a compact pill with a reasoning-effort submenu.
- Inlined glyphs for the thinking, send, and dictate controls, kept in
  sync across the main and compare composers.
- Toast notifications match the composer surface: no border line, the
  same drop shadow, and the same dark surface color, with a ring-less
  close button.
- Dark mode: the side-menu shadow blends into the background, hovered
  menu rows read clearly, and their roundness matches light mode.
- Composer styles use dedicated unsloth- prefixed classes so compare
  mode keeps its own stacked layout.

* studio: sync compare-composer reasoning state and harden compare id

- Compare composer: keep "Preserve thinking" consistent with reasoning,
  matching the main composer. Enabling it now turns reasoning on, and
  disabling reasoning (the None option or the Thinking toggle) turns it
  off, so the invalid "preserve on while thinking off" state can't occur.
- Guard crypto.randomUUID in the Compare action. It is undefined in
  non-secure contexts (HTTP over a LAN IP) and would throw; fall back to
  a timestamped random id, matching createNavigationNonce.

* studio: reflect pre-selected Search/Code tools when no model is loaded

The Search and Code pills only lit up when the tool was usable right now
(a model loaded and capable), so a tool turned on from the + menu showed
as off in the pill while the menu showed it on. toolsEnabled is persisted
and takes effect once a capable model loads, so the pill should reflect it.
The pills now disable only when a loaded model lacks the capability, and
otherwise reflect the selected state. Applied to the main and compare
composers.

* Studio: link MCP Servers heading to its PR and fix composer pill cursors

Make the "MCP Servers" heading in the chat Configuration sheet link to the
MCP PR, keeping the chevron as the toggle. The label and chevron are rendered
as siblings so we don't nest an <a> inside a <button>.

Also add cursor-pointer to the composer pills and the thinking pill so hovering
a clickable pill shows the hand cursor instead of the default arrow.

* Studio: refine chat composer and add compare-mode parity

- Composer expands to two rows only once the input wraps to a second line,
  not on the first keystroke. Re-measure the autosize textarea on the width
  swap so expanding no longer leaves a stray blank row.
- Light-mode composer shadow now matches Gemini's soft elevation.
- Plus menu: replace Canvas with a More submenu (Canvas, Compare chat, RAG)
  and add Code above MCP. Active Web search/Code items use medium weight.
- Compare mode: the plus side menu, Search/Code toggles, and a Compare exit
  pill now match single chat, with the thinking control on the right.
- Projects menu entries link to their tracking PR (#5725).
- Add cursor-pointer to the composer plus button.

* studio: refine composer controls and chat search shadow

- Active tool pills show an x on hover to signal click-to-disable
- Plus button rotates into an x when the tools menu opens
- Composer surface uses a 32px radius and a taller single-line height
- Even, ChatGPT-style spacing between the plus and tool pills in both the single and compare composers
- Send and mic circles resized and spaced, with the arrow centered in the circle
- Chat search box gets a borderless, soft Gemini-style shadow

* studio: size the pill hover x to match the icon it replaces

Cross-engine checks (Chromium, Firefox, WebKit) flagged the active-pill
hover x as a fixed 14px, so it popped smaller than the 19px Code icon.
Fill the glyph slot instead so the x tracks whatever icon it covers.

* studio: do not persist Kimi search/thinking mutual-exclusion in single composer

The single-chat composer flipped the other control off when toggling
search or thinking on Kimi, but without { persist: false }, so it
overwrote the user's saved preference. Match shared-composer and keep
the side effect session-only.

* studio: pointer cursor on model selector trigger and menu items

Add scoped marker classes so the model picker trigger and every
clickable element in its menu (tabs, model rows, delete, eject) show a
pointer cursor; disabled items stay not-allowed.

* studio: pass baseUrl when resolving reasoning caps in single composer

The docked composer omitted baseUrl, so a custom Gemini OpenAI-compat
gateway still advertised the native thinking ladder the backend cannot
honor. Pass selectedExternalProvider.baseUrl like the compare composer
so the resolver hides it.

* studio: grey side-menu hover, green pill hover, thinking hover x

- Plus side-menu items hover grey in light mode, not the green accent
- Thinking pill hovers green like the Search and Code pills
- The plain Thinking toggle shows an x on hover when active, matching
  Search and Code; the effort dropdown trigger keeps its bulb

* studio: make the pill hover x a uniform size

The x filled the icon slot, so the wider Code chevron gave a bigger x
than Search and Compare. Pin it to a fixed 15px, centered, so every
pill's x matches.

* studio: broaden chat attachments, fix active hover color, gemini shadow

- Accept svg, source code and many text/config files as drag-and-drop
  or picked attachments, matched by extension since their MIME is
  unreliable; html keeps its own adapter
- Active (green) side-menu items keep their text and icon color on
  hover instead of switching to the accent color
- Composer surface uses Gemini's soft centered shadow 0 0 20px rgba(0,0,0,0.04)

* studio: keep the thinking pill full height when icon-only

The inactive thinking pill has no label, so its flex row collapsed to
the icon height and the hover box looked short. Reserve one text line
(min-height: 1lh + padding) so it matches the Search and Code pills.

* studio: refine composer menu, drop overlay and greetings

- Open the MCP servers dialog directly from the composer plus menu
- Redesign the drag-and-drop affordance Gemini style, drop the badge and border, make the whole chat page a drop target
- Swap in Hugeicons for the RAG, attachment chip and new project icons
- Add time-based randomized welcome greetings, each matched to a fitting sloth

* studio: rename artifacts toggle to Canvas and make it opt-in

- Label the toggle Canvas everywhere, matching the plus menu
- Stop greying out the Canvas menu item; it toggles like the other items
- Only show the Canvas pill in the composer row once it is turned on, since it is less central than Search and Code

* studio: wire Canvas and MCP composer toggles, even out the pill row

- Open the MCP servers dialog from the menu, or toggle MCP on/off once a server is enabled
- Force MCP off when no server is enabled, so the toggle stays honest
- Show Canvas and MCP as opt-in pills that appear in the order they were toggled on
- Expand the composer and light up the pill when Canvas or MCP is on, like Search and Code
- Keep Compare directly after Code in the compare composer
- Use the same Code icon on both composers and give every pill an even icon slot

* studio: tidy composer toggle row and fix MCP enable/disable lifecycle

- Enable MCP automatically after a server is configured via the toggle flow
- Force MCP off everywhere once the last enabled server is removed
- Collapse the pill labels to icons only when more than 4 pills show, keeping Compare labelled
- Order Compare first in compare mode, before Search and Code
- Use the same Code icon and an even 19px icon slot across both composers
- Match the compare composer surface padding and send button inset to normal chat

* studio: revert compare composer padding change that cramped the input

Matching the surface padding to normal chat clipped the textarea text and
left a white strip on top. Restore the compare composer's own padding, which
gives proper top spacing. The send button inset fix stays.

* studio: center welcome greeting and soften composer scrollbar

Center the sloth and title together over the composer instead of
shifting the row left, which left the greeting sitting off to the side.

Keep the composer textarea scroll thumb faint by default and only darken
it when the thumb is hovered or dragged, so a tall draft no longer shows
a heavy dark rail.

* studio: match composer plus-menu tool gating to the pills

The new plus-menu tool entries did not carry the gating the visible pills
already enforce, so the menu and pills could disagree about a loaded
model's capabilities.

- Web search and Code menu items now disable when a loaded model lacks
  the capability, while still allowing preselection with no model loaded.
- Enabling Web search from the menu on a Kimi model now flips thinking
  off as a session-only change, since Kimi forbids search and thinking
  together. This matches the Search pill.
- Added an Images menu item, shown only for image-generation models and
  disabled until a model loads, so a short prompt has an entry point.

Applied to both the single-chat and compare composers.

* studio: round the active-pill hover x and even out pill padding

The hover x sat bare and the trailing label was tighter to the pill edge
than the leading icon, so the pill looked lopsided.

- Give the hover x a soft circular background that fills the icon slot,
  matching the ChatGPT-style toggle and the icon it replaces.
- Add a little more trailing padding so the label and the leading icon
  have even breathing room, and keep icon-only compact pills symmetric.

* studio: nudge the thinking bulb icon up by 0.5px

Bump the thinking lightbulb from 15px to 15.5px in the single-chat and
compare composers so it sits a touch larger next to the other controls.

* studio: drop the hover x circle on icon-only pills

When pills collapse to icon-only, the circle around the hover x is too
cramped in the small chip, so show a bare x there and keep the circle
only on the full-width labelled pills.

* studio: space the compare send button like normal chat

In compare mode the Thinking control sat right against the send button.
Match the normal composer's control spacing (gap-1.5 plus a send margin)
so Thinking has the same breathing room before send. The send button
keeps its 14px inset, so its position is unchanged.

* studio: make collapsed pill hover a circle, not a wide pill

Icon-only pills were wider than tall, so their rounded-full hover
highlight read as a fat rounded rectangle. Make the compact button a
square and center the glyph so the hover (and the x it reveals) sits in
a clean circle.

* studio: fix compare pane drops and audio picker lifetime

- Skip the page-level drop handler when the composer is hidden, so files
  dropped on a compare pane are not swallowed by a hidden composer; the
  shared compare composer keeps handling drops through its own dropzone.
- Build the audio file input on document.body instead of inside the plus
  menu, so the menu closing on select no longer unmounts the input before
  the OS picker returns and drops the file.

* studio/chat: stop projects list from white-screening on older backends

The projects list API returned data.projects directly, so a backend that
omits the field handed back undefined. useChatProjects cached that value,
then the next mount read undefined.length and crashed the whole chat page.

Default the projects and threads list APIs to an empty array and keep the
hook null-safe so a bad response can never poison the cache.

* studio/chat: align MCP dropdown with the + menu and add a chevron

Reuse the + menu surface (unsloth-plus-menu) for the MCP dropdown: rounded
corners, narrower width, neutral grey hover, and enabled rows shown as green
text with a right-aligned check instead of the emerald underlay. Add a
chevron to the MCP pill so it reads as openable, matching the Thinking pill.

* studio/chat: make MCP an opt-in pill and fix its dropdown placement

- MCP is back in the + menu as a toggle. The pill now only shows in the
  composer when MCP is on, matching Canvas, instead of always sitting there.
- The dropdown follows the composer side like the + menu (opens down in the
  welcome composer, up when docked) rather than always opening upward.
- Drop the dropdown caret when pills collapse so the icon is not squished.
- Stop force-syncing mcpEnabledForChat to the server count; the + menu owns it.

* studio/chat: MCP expands the composer, drop sidebar Compare, tidy scrollbars

- Toggling MCP now expands the composer and shows the tool pills, the same as
  Canvas, instead of leaving the row collapsed.
- Remove the Compare item from the sidebar now that it lives in the + menu, and
  point the compare tour step at the side-by-side view instead of the old button.
- Both sidebars only show their scrollbar on hover, and run settings reserves
  the scrollbar gutter so the close button no longer shifts when it appears.

* studio/chat: tighten toggle gap, fix run-settings close button, collapsed Train

- Reduce the composer toggle gap by 2px (gap-1 to gap-0.5) in both composers.
- Move the run settings header out of the scroll area so the close button keeps
  its position whether or not the scrollbar shows, and sits flush with the
  topbar open button again instead of shifting left.
- Surface Train as an icon in the collapsed sidebar (it already has a labelled
  section when expanded).

* studio/chat: tighten Thinking pill X padding, create projects inline

- The Thinking pill used px-2.5, so the hover X sat further in than the left
  pills. Match their pl-2 so the X lines up.
- The + menu New project now opens a create dialog and jumps straight to the
  new project, instead of routing to the projects list. Shared by both
  composers via a small NewProjectDialog.

* studio/chat: soften account menu, hover scrollbars, show collapsed chevrons

- Account menu drops its border ring for the composer's soft shadow and opens
  centered over its trigger.
- Settings and search reuse the hover-only scrollbar via a shared
  hover-scrollbar class, matching the sidebars.
- Train and Recents keep their chevron visible while collapsed so it is clear
  they can be expanded.

* studio/chat: roomier, more rounded account menu

Widen the account menu, add more left and right padding on the rows, bump the
row height and text a touch, and round the corners more, closer to the GPT
account menu.

* studio/chat: trim account menu width and nudge it up 2px

Pull the account menu in slightly on the left and right (narrower box, a touch
less row padding) and lift it 2px higher above the trigger.

* studio/settings: drop outline ring, circular close hover, pointer cursors

- Remove the settings dialog outline ring, keeping just the soft shadow.
- The close button hover is now a circle instead of a rounded rectangle.
- Every clickable control in the settings dialog uses a pointer cursor.

* studio/chat: bump MCP pill icon to 14.5px

Nudge the MCP icon up 0.5px so it sits even with the other pill glyphs.

* studio/chat: bump MCP pill icon to 15px

Nudge the MCP icon up another 0.5px.

* studio/settings: add a Settings title above the tabs

Put a Settings heading at the top of the sidebar so the tabs sit below it,
matching the Claude settings layout. Hidden on mobile where the nav is a row.

* studio/settings: rounder tab hover, bigger title, less-round search dialog

* studio/sidebar: round nav row hover boxes 2px more (10px to 12px)

* studio: drop settings dark shadow + divider, add tab left padding, tune hover roundness

* studio/model-selector: roomier padding, borderless box, rounder hover rows; settings divider light-only

* studio/search: match chat box shadow (soft light, none dark)

* studio/sidebar: borderless chat context menus, rename submenu to Projects with folder-export icon

* studio/model-selector: match light corner radius in dark, drop dark shadow, more visible dark hover

* studio/sidebar: chat context menu matches + side menu styling; relabel submenu Move to project

* studio: borderless message export menu (no dark shadow), match dark corner radius to light on export menu and settings

* studio/sidebar: open chat options menu GPT-style (down-right) and widen so Move to project fits one line

* studio/chat: message export menu uses the chatbox shadow in light mode

* studio/sidebar: narrow chat options menu slightly (w-60 to w-56)

* studio: unify all download icons to Hugeicons download-01; round profile button hover 1px more

* studio/run-settings: bump header to 16px

* studio/sidebar: trim chat options menu width slightly (w-56 to 216px)

* studio/sidebar: trim chat options menu width to w-52

* studio/profile: camera-01 Hugeicons glyph and chatbox shadow on avatar button

* studio: match dark-mode corner radius to light globally (single --radius token)

* studio/recipes: borderless New Recipe menu with chatbox shadow in light, none in dark

* studio: borderless dropdowns globally, chatbox shadow in light, none in dark

* studio: extend borderless + chatbox/none shadow to select, combobox and popover overlays

* studio/mcp: nudge MCP dropdown radius to 20px so its wider box reads as round as the + menu

* studio: restore dark dropdown shadow to avoid same-color merge; greet name ~1/3 of lines; bigger sloth + more gap

* studio/train: active tab is a borderless pill (no underline), roomier padding, more tab gap and bottom spacing

* studio/chat: nudge welcome up ~5px (still vh-based) and trim sloth image to 44px

* studio/train: active tab pill is white with chatbox shadow in light, taller padding

* studio/chat: welcome offset to calc(30vh - 10px)

* studio/chat: welcome offset to 28vh (drop the -10px)

* studio/chat: tighten sloth-to-text gap by 1px (16px to 15px)

* studio/train: revert light active pill to grey fill, drop white bg + shadow

* studio: app-wide hand cursor on every clickable control (disabled excluded)

* studio/chat: welcome offset to 26vh

* studio/chat: welcome offset to 28vh

* studio/chat: harden project and thread list guards against non-array payloads

* studio/sidebar: give the profile row more height and breathing room

* studio/sidebar: trim the profile row top and bottom padding slightly

* studio/sidebar: reduce Train and Recents section label size slightly

* studio/sidebar: trim the profile row top and bottom padding a touch more

* studio/sidebar: enlarge the profile hover area top and bottom

* studio/sidebar: increase profile hover roundness by 1px

* studio/sidebar: trim the profile row top and bottom padding slightly

* studio/sidebar: trim the profile row top and bottom padding slightly

* studio/chat: cache composer line metrics so wrap detection runs once, not per keystroke

* studio/chat: restore the prior view when exiting compare opened from the + menu

* studio/tests: drive Compare from the composer + menu after it moved out of the sidebar

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio/tests: open Compare from the composer + menu in the extra UI suite too

* studio: fix chat dictation microphone access

* studio: snappier plus-to-x spin and steady composer expand gap

Speed up the composer plus icon morph from 480ms to 300ms.

Add row-gap on the expanded composer line so the space between the text
and the controls row stays the same whether the box expanded from
wrapped text or from a toggle being on. The gap sits on the line, not the
input, so the placeholder max-height clamp never crops it.

* studio: only show composer tool pills once a model is loaded

Persisted Search/Code/Canvas/MCP toggles were surfacing the composer pill row on a fresh page load before any model was selected, so an empty composer looked different from the clean just-ejected state. Gate the composerExpanded tool checks on modelLoaded so a model-less composer stays collapsed, while saved preferences still apply the moment a model loads.

* studio: hide RAG composer menu item temporarily

Hide the placeholder RAG entry from the composer plus menu in both single chat and compare until the feature is ready, and drop the now-unused DatabaseIcon import.

* studio: let composer tools pre-select before a model loads

Selecting Web search, Code, Canvas or MCP from the + menu with no model
loaded did nothing visible: the toggle turned on but the composer never
expanded, so the pill stayed hidden. Drop the model-loaded gate from the
expand check so an active tool always surfaces its pill.

Align MCP with the Search/Code pattern too: grey it out only when a loaded
model lacks tool support, so MCP stays toggleable and the pill stays
clickable before a model is loaded instead of looking disabled.

---------

Co-authored-by: Unsloth <michaelhan@Michaels-MacBook-Pro.local>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: wasimysaid <wasimysdev@gmail.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: Daniel Han <23090290+danielhanchen@users.noreply.github.com>
2026-06-03 06:07:30 -07:00
Lee Jackson
e0ff6a1404
Studio: manage chat history with projects (#5725)
* feat: align project sidebar UX with ChatGPT

* feat: align project sidebar UX with ChatGPT

* feat(chat): load stored project list

* feat(chat): add project sidebar workflows

* fix: stabilize project page navigation

* fix: projects chat loading

* fix: show project chat thread

* style: sidebar project spacing and hover clipping

* style: add expandable project chat history and move-to-project submenu

* feat: polish project sidebar

* feat: persist project sandbox paths

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: only create sandbox project workspace dir

* feat: add optional project workspace deletion from delete dialog

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: stabilize chat projects CI failures

* fix: polish project chat navigation

* Studio: manage chat history with projects

Group chats into projects with a dedicated projects page and route.
Sidebar shows recents with per-row actions and a vertical more-vertical
menu, and the sidebar scrollbar stays hidden so rows never shift on
hover. Includes chat settings and composer refinements.

* Studio: projects sidebar and breadcrumb polish

Sidebar:
- Remove the Compare nav item.
- Widen the sidebar to match the projects layout.
- Replace the scroll-gated bottom fade with a static fade pinned above
  the profile box, so it no longer attaches to Recents or lags the
  collapse and expand animation.

Topbar breadcrumb (chat-page):
- On a project landing show "Projects" linking to the projects list.
- Inside a project chat show the project name and chat title, with the
  project name linking back to that specific project page.
- Drop the divider between the model selector and the breadcrumb.

* Studio: make project workspace delete test cross-platform

test_chat_project_delete_files_removes_workspace rooted the project under
pytest tmp_path, which resolves to /private/tmp on macOS. The workspace
delete guard refuses paths under the system denylist by design, so the
test passed on Linux CI but failed on macOS.

Add a workspace_projects_home fixture that keeps tmp_path on Linux and
Windows (CI unchanged) and falls back to a home subdir only when the temp
root is on the platform denylist. Derive the workspace path from the
created project so it tracks the projects home.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio: satisfy import-hoist check for new path re-exports

documents_root and project_workspaces_root are re-exported from
utils.paths but only referenced as __all__ string literals, which the
import-hoist safety net does not count as a use. It flagged the two newly
added re-exports as unused imports and failed Source lint.

Name-load both via a module-level _REEXPORTED tuple so the check sees
them used. No behaviour change; consumers still import them from
utils.paths.

* fix: avoid projects empty-state flash

* fix: batch chat search indexing

* Studio: polish chat sidebar, run settings, and search

- Use the native OS scrollbar for the chat sidebar, Run settings panel, and chat search list instead of a custom scrollbar
- Highlight the active run in the sidebar and keep chat search available during training
- Stop the training log view from replaying when navigating back to a run
- Rename the chat settings panel to Run settings and align its toggle icon and position
- Tighten heading and sidebar letter spacing and lighten the Train and Recents labels
- Match the search dialog corner style across light and dark and drop the stray border
- Make the MCP Servers section header plain text instead of a link
- Remove a stray .orig backup file

* studio/frontend: restore Compare entry point in the sidebar

The chat-projects sidebar redesign dropped the Compare nav item and moved
it to thread-sidebar.tsx, which is not imported or rendered anywhere. That
left no way for a user to start a new model comparison (enterCompare only
fired from the guided tour and the training handoff), and broke the
Compare/Recipes/Export UI smoke test that clicks [data-tour="chat-compare"].

Re-add the Compare NavItem to the New Chat / Search group, carrying
data-tour="chat-compare" and the same new-comparison navigation as before.

* studio/frontend: use Unsloth green for the fallback profile avatar

Switch the initials-avatar background from blue to #14b789 so the sidebar
and edit-profile avatar match the Unsloth brand colour.

* studio/frontend: turn project breadcrumb into a project switcher dropdown

* studio/frontend: stop project card kebab clicks from opening the project

* studio/frontend: hide project switcher outside projects

* studio/frontend: stabilize project switcher loading

* style: project switcher alignment

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: shimmyshimmer <107991372+shimmyshimmer@users.noreply.github.com>
Co-authored-by: Unsloth <michaelhan@Michaels-MacBook-Pro.local>
Co-authored-by: Roland Tannous <rolandtannous@gravityq.ai>
Co-authored-by: Roland Tannous <115670425+rolandtannous@users.noreply.github.com>
2026-06-01 22:09:16 +04:00
wuwuwu
4891118b5e
Studio: add frontend i18n support (#5765)
* Studio: add frontend i18n support

* Studio i18n: guard storage events, restore plurals, fill zh-CN, add parity check

- locale-store.ts: wrap window.localStorage access in handleStorageEvent
  with try/catch. readStoredLocale and writeStoredLocale already guard the
  same API; the storage-event path can throw the same way in privacy/
  restricted contexts and was the only unguarded localStorage call. Refactor
  the storageArea + key match into isLocaleStorageEvent for clarity.

- chat-tab.tsx + en.ts/zh-CN.ts: restore singular handling for chat-clear
  copy that the i18n migration dropped. Pre-PR code rendered "1 chat" but
  the new template strings always said "chats", so a user with exactly one
  chat saw "Cleared 1 chats", "Clear 1 chats?", and "1 chats cleared;
  1 chats remain". Add clearOneChat*, clearedOneChat, oneChatClearedRemain*,
  chatsClearedRemainOne, and storageClearFailedOne keys and pick them in
  chat-tab.tsx when count === 1.

- zh-CN.ts: fill ~50 previously English-fallback keys across studio.configure,
  studio.model VRAM helpers, studio.dataset (source, browsing, tooltips,
  preview/split/subset), studio.params tooltips and learningRateDescription,
  studio.training (audio/vision incompatible), studio.trainingStart.terminalStart,
  studio.tour.guidedTour, settings.chat.clear*, settings.connections,
  settings.apiKeys.newBadge. shell.{beta,brand,product} kept as brand strings.

- src/i18n/check-parity.ts + npm i18n:check: small script that verifies every
  locale overlay against the English baseline. Catches placeholder mismatches,
  shape mismatches, and unintended extra keys; runs via node --experimental-
  strip-types with no new devDependencies.

Verified locally:
  npm run typecheck, lint, build, biome:check, i18n:check all pass.
  24 vitest unit tests cover locale resolution, persistence failures,
  storage-event sync (including window.localStorage throwing), interpolation,
  and fallback.
  33 Playwright e2e tests pass across Chromium, Firefox, and WebKit covering
  default load, switch + reload persistence, unsupported/garbage locale
  fallback, storage-event cross-tab sync, and storage clear.

* Studio i18n: use translated API-key error copy instead of raw err.message

The API helpers in src/features/settings/api/api-keys.ts throw generic
English Error objects ("Failed to load API access", "Failed to create
access token", "Failed to revoke access token"). ApiKeysTab and
CreateKeyForm caught those and preferred err.message over the translated
"settings.apiKeys.loadError" / .createError / .revokeError keys, so in
zh-CN mode failed load/create/revoke requests still surfaced the English
strings instead of the translated copy.

Switched the four call-sites to always render the translated message and
left the helper throws unchanged (they are still useful for diagnostics
but should not be treated as user-facing localized copy).

* Studio i18n: polish two zh-CN embedding LR tooltips

Translation-pass review surfaced two awkward phrasings I introduced earlier:

  "常用区间是主学习率的 2 至 10 倍小"
    -> "常用区间是比主学习率小 2 至 10 倍"

Both versions are grammatical, but the new "比 X 小 N 倍" phrasing is the
standard idiomatic comparative for "N times smaller than X" in technical
Chinese writing. The earlier "X 的 N 倍小" reads as a non-native construction.

Applies to:
  studio.params.embeddingLearningRateTooltip
  studio.params.embeddingLearningRateDescription

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-05-27 04:52:16 -07:00
Lee Jackson
00b607267a
Studio: Polish spacing and profile input radius (#5222)
* UI: polish studio spacing and profile input radius

* chore: use standard rounded radius for profile input

---------

Co-authored-by: Roland Tannous <115670425+rolandtannous@users.noreply.github.com>
2026-05-04 00:38:19 +04:00
Lee Jackson
230d58872d
Studio: Preserve transparency in uploaded profile avatars (#5200)
* fix: preserve transparency in uploaded profile avatars

* fix: guard unsupported canvas mime fallback
2026-04-28 02:39:48 -07:00
Lee Jackson
9c8a079d97
Studio: Local profile customization in settings and sync sidebar identity (#5088)
* studio: add local profile customization in settings

* studio: add local profile settings and sync sidebar identity

* fix: adjust profile card margin

* fix: move helper modules to utils and use single-letter avatar fallback

* fix: keep profile icon visible on sidebar collapse

* fix: sidebar account trigger labeling and profile reset prefs
2026-04-20 22:28:02 +04:00