* Studio: redesign Select model dropdown to match Hub design Make the chat Select model picker easier to scan by reusing the Hub on-device card's visual language. - Rows now split owner/name, add a param chip, a DotTag format pill, a tabular size, and a Loaded marker on the active model. - Hub models / Fine-tuned tabs reuse the Hub's exact .hub-tab-toggle styling (selectors extended in hub.css to the selector menu). - Add a Downloaded / Recommended / Custom section toggle on the Hub tab to filter the list. - Widen the popover and nudge the scrollbar toward the edge. * Studio: move section toggle below search, size tabs to label Put Downloaded / Recommended / Custom under the search bar in their own row so Hub models / Fine-tuned no longer wrap. The section toggle uses a smaller font and sizes each tab to its label instead of equal widths. * Studio: extract pure row-meta helpers into their own module Move splitRepoLabel, classifyMetaToken, and parseMetaTokens out of pickers.tsx into row-meta.ts. No behaviour change; keeps the presentation logic free of React/DOM deps so it is easy to test in isolation. * Studio: content-size the source tabs and add section icons Size the Hub models / Fine-tuned tabs to their labels (with side padding) like the section toggle, instead of stretching full width. Add a leading download, star, and folder icon to Downloaded, Recommended, and Custom. * Studio: stop source tabs stretching and hide empty Fine-tuned tab The popover is a flex column, so the fit toggle stretched full width; add w-fit/self-start so it sizes to its content. Also hide the Fine-tuned tab when there are no fine-tuned models, defaulting to Hub models. * Studio: keep only fine-tuned models in the Fine-tuned tab Local models (LM Studio, Ollama, custom folders) carry source "local" and already show in the Hub tab's Downloaded / Custom sections, so exclude them from the Fine-tuned tab and from its visibility count. Extract the tab rules into source-tabs.ts. * Studio: show local providers under Downloaded, Recommended first Show LM Studio and other local provider models in the Downloaded section in all modes (was chat-only). Put Recommended first and make it the default section. Add a little more space below the search bar. * Studio: make Recommended a sortable live Unsloth listing Replace the static Recommended list (and its collapse chevron) with a sort dropdown over Unsloth's own models: Recommended, Trending, Most likes, Downloads, Recently updated. Recommended shows recently uploaded GGUF/MLX models that fit the device (hidden if they do not); the other sorts list all Unsloth models, badged but never hidden. Adds a sort option to useHfModelSearch and a pure recommended-fit helper. * Studio: size Recommended models from the repo name when metadata is missing GGUF and MLX repos rarely expose safetensors metadata, so a large model with no size could pass the Recommended fit check because unknown size was treated as fitting. Parse the parameter count from the repo id, including the Gemma E series, and hide anything we still cannot size. * Studio: detect model capabilities and family from HF tags Thread tags and the pipeline tag through the model search results and add a pure helper that infers vision, reasoning and audio plus the architecture family, falling back to repo-name keywords when tags are absent. * Studio: add row details and inline section sorting to Select model Give each model row more detail and make the Hub sections easier to scan: - Show vision, reasoning and audio badges plus the architecture family tag on each row, alongside the params, format and size. - Drop the redundant unsloth/ prefix on the Recommended rows. - Rename the Recommended section tab to Unsloth and enlarge the section tabs. - Move the sort dropdown inline to the right of the tabs at a fixed width. - Add Recent, Size and Downloaded sorting to the Downloaded and Custom tabs. - Remove the header icons, pad the subheadings, and grow the list height. * Studio: tune the Select model sort dropdown and trim row badges - Recommended now lists the most recently created Unsloth repos. - Narrow the sort dropdown, remove its border, and truncate long labels. - Tighten the gap between the section tab icons and their labels. - Remove the architecture family tag from rows since it repeats the name. * Studio: extract the PillTabs toggle into a shared module Move the segmented pill toggle out of the model selector into its own file so the Hub picker can reuse it for a format filter without duplicating the markup. * Studio: fix Recommended infinite scroll and add a format filter - Re-attach the scroll observer on each loaded page so a filtered Recommended list keeps paging until the viewport fills instead of spinning forever with nothing new appearing. - Add an All / GGUF / MLX / Safetensors toggle on the Unsloth listing that filters every sort. * Studio: default Recommended to Trending, rename Downloaded to On Device, and fade the scroll edge Sort: default the Recommended view to Trending and add a Name option to the On Device / Custom sort. Recent now orders by last load time while Downloaded orders by file date, tracked in localStorage (model-usage.ts). Formats: show the format filter on all three tabs (Unsloth, On Device, Custom), exclude mobile GGUF builds from Recommended, and flag GGUF rows that exceed the device with the same OOM badge as safetensors. Polish: download-icon badge on already-downloaded Recommended rows, the hugeicons view stroke-rounded vision badge, Search all models placeholder, matched popover padding, and a top-edge mask fade once the list scrolls. * Studio: size GGUF repos from gguf metadata so large ones flag OOM Repos with no <n>B token in the name (Kimi, MiniMax) had no param count and so never showed an OOM badge. Request the gguf expand field from Hugging Face and read gguf.total, so those repos get a param chip and an OOM badge when they exceed the device budget. Keep the row name full contrast when over budget (the OOM badge already signals the fit), shorten the format and sort dropdowns, narrow the popover, and rename Recently updated to Recent and All formats to All. * Studio: address selector review feedback Add WAI-ARIA roving tabindex and Arrow Left/Right navigation to the pill toggle so only the active tab is in the tab order. Keep the chat-only GGUF/MLX filter for every Recommended sort, not just Recommended, so chat-only users do not see unrunnable checkpoints under Trending. Feed both listings' GGUF hints into repo detection so a tag-only GGUF in Recommended expands variants instead of loading as a checkpoint. * Studio: scope Select model search per tab and add an MLX tag Search is now per section. The Unsloth tab searches the Unsloth HF listing only, On Device filters downloaded and LM Studio models by name, and Custom filters custom-folder models, each with its own empty state. MLX repos get an MLX pill mirroring the GGUF tag. Downloaded quants in the Unsloth and search lists get the same delete action as On Device. Also: revert the model name to normal weight, narrow the popover to 558px so the format and sort dropdowns sit one gap-2 from the tabs, tighten the dropdown menus to match the Projects activity Select, and make the empty On Device state name the active format filter. * Studio: show local ./models on the On Device tab so they stay selectable Models under the local models directory (source models_dir) flow in as local models but were dropped from every list: filtered out of Fine-tuned and never re-added by the Hub picker, which kept only LM Studio and custom-folder sources. Capture them in the local refresh and render a Local models group on the On Device tab, with the same format, search, and chat-only GGUF rules as the other local groups. * Studio: add a Hub button beside the Select model search bar Adds a Hub button next to the search bar that opens the full Hub Discover page to browse more models. Styled like the section tabs (rounded, no border, soft shadow with a faint top layer) and darkens on hover. Also nudges the format and sort dropdown chevrons a touch toward the edge. * Studio: align Select model padding and tighten the format pills Sizes the popover to the tab cluster so the left and right padding match, and drops the top row below the rounded corner so the Hub button lines up with the Trending dropdown. Gives the Hub button a fixed width, lets the list scrollbar sit inside the box, and shrinks the format pill dot with a tighter dot-to-label gap. * Studio: label the Hub button Search Hub and match the dropdown width Renames the button to Search Hub, sets its width to the format and sort dropdown width so it lines up above them, and tightens the icon gap. * Studio: drop the vision and reasoning row badges to declutter Removes the vision and reasoning capability icons from the model rows so they read cleaner. Audio is kept. * Studio: add a safetensors pill, hide diffusion models, eye on Vision Gives safetensors rows a format pill and size so their meta matches GGUF and MLX, drops image and video diffusion models from the listing since they cannot run in chat, and shows an eye icon next to the Vision tag. Also removes the em dashes from the Projects export and import labels. * Studio: gate recommended folders on real weights and polish the selector Only show a Recommended chip once the well-known dir actually holds weights, so an empty LM Studio or Ollama scaffold no longer suggests itself. _dir_has_downloaded_model checks for a GGUF/safetensors file or a non-empty Ollama manifests store, with a bounded walk. Selector polish: round the popover and option menus a touch more, lighten the OOM badge in dark mode, soften the inner dropdown shadow, even out the padding, and lift the toggle track and field triggers so their edges read against the popover. Also catch CogVideoX in the diffusion name fallback. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: align the dark Select model panel with the sidebar Match the popover, fields, dropdowns, tab toggle and row states to the sidebar surface and accent so the dropdown reads as one piece in dark mode. The active tab pill and Search Hub button sit a touch lighter than the track, and the inner option menus drop their drop shadow for a flatter look. Light mode is unchanged. * Studio: re-derive the Select model tab on open The picker remounts each time the dropdown opens, but the source tab state did not, so a persisted fine-tuned or connected selection that only lands in its list after an async load would reopen on Hub. Reset the active tab to the selection-derived default on the open edge, while still letting the user switch tabs freely within a session. * Studio: fold Custom into On Device and polish the picker Merge the Custom tab into On Device so custom folders sit right below the downloaded models, with a folder shortcut on the group header. Rename the first Hub tab to Recommended, give the format dropdown colored dots, even out the tab row spacing, and tighten the popover width. Align the folder browser with the app dialogs (soft surface, roomier padding, green confirm, grey hover). * Studio: fix On Device controls and nudge the folder browser close The Hub redesign merge dropped the old Search Hub button styling, so the On Device search row rendered flat. Point the search input and Search Hub button at the shared .field-soft surface so they match the rest of the Hub controls, and lift the folder browser close button slightly. * Studio: run the Select model search on the Hub search stack Point the picker at the Hub's useHubModelSearch and useHubInfiniteScroll instead of its own useHfModelSearch/useInfiniteScroll, scoped to unsloth so the listing matches the old one. Both the search and the recommended feed now share the Hub implementation, so there is one search path. The Hub result folds GGUF params into totalParams, so the dead ggufParams fallback is dropped. * Studio: trim the recommended sort to Recommended, Trending, Recent Drop Downloads and Most likes from the sort dropdown. * Studio: give the section tabs room off the rounded edge The fit-mode toggle wrapped the tabs with no inset, so On Device sat tight against the rounded-full edge. Add a small horizontal inset and widen the popover a touch to fit it. * Studio: drop the legacy HF search hooks for the Hub ones Migrate the training model and dataset sections, export page, onboarding steps and recipe dataset combobox off useHfModelSearch, useHfDatasetSearch and useInfiniteScroll onto the Hub equivalents, scoped to unsloth so the listings match. The picker reads recommended param counts off the search results it already has instead of a separate fetch. Removes the duplicate search stack: use-hf-model-search, use-hf-dataset-search, use-hf-paginated-search, use-infinite-scroll, use-recommended-model-vram and the old lib/hf-cache. * Fix model selector section toggle proportions Remove the fit-mode track inset so the active pill sits flush to the track edge, matching the Hub's segmented controls. * Tighten model selector width and tab padding Reduce the popover width so the right edge aligns with the row, and widen the fit-mode tab padding so On Device clears the track edge. * Refine Recommended formats, sort width and tab padding Recommended now suggests GGUF anywhere and MLX only on Mac, never safetensors. Size the sort dropdown to its label so Recommended no longer truncates, and match the On Device trailing gap to the active pill's leading inset. * Flush section toggle and match dropdown font to Search Hub Drop the trailing track pad so the active pill fits the track exactly at either end. Size the sort and format dropdown text to text-xs like the Search Hub button, and clip long labels without an ellipsis. * Fix sort menu checkmark overlap and lock dropdown widths Keep the option's right padding so the selected checkmark no longer overlaps the label, and let the open menu expand to fit it. Set the format and sort triggers to a fixed width matching the Search Hub button so they always line up. * Keep section toggle and dropdowns on one row Drop the wrap and size the Search Hub button, format and sort dropdowns to a shared 100px so they stay equal width and fit on one row without widening the box. * Studio: pre-load inference settings dialog with native context Add a gear on downloaded GGUF quant rows that opens a settings dialog to adjust inference parameters before loading a model: - Context length, KV cache dtype, speculative decoding and tensor parallelism, all written to the runtime store the load call reads. - Settings can be remembered per model in localStorage. - The context slider ceiling and "Model supports up to N tokens" come from the model's native context, read from GGUF metadata and returned by /api/models/gguf-variants once a variant is downloaded. Also drop models Studio can't run for chat (diffusion, image, video) from the recommended feed and Hub search, plus minor selector polish on row hover padding, Search Hub and dropdown widths, and tab spacing. * Studio: model selector polish and memory-aware load warning Search and listing: - Drop the "Recommended" and "Hugging Face" section labels while searching so results read as one list; keep the format and sort dropdowns visible so search results can still be sorted and filtered. - Request gguf metadata in the Hub listing so GGUF repos report a parameter count, restoring the OOM badge for repos without a size token in the name (Kimi, MiniMax, GLM). Load settings dialog: - Warn when weights plus the KV cache at the chosen context exceed available memory. The KV size is sized by the backend's architecture-aware estimator via a new kv-cache-estimate endpoint; the budget uses VRAM plus system RAM. Best-effort, no warning on failure or on auto context. - Context Length placeholder reads "auto"; dark background slightly lighter. Other: - Clicking the Custom Folders header opens the folder browser; its title now reads "Select folder to detect models". - On Device sort lists Downloaded last. - Smaller chat template editor font; rounded wrapper clips the prompt and template editor scrollbars so the right corners stay round. * Studio: fix load dialog memory warning budget and KV dropdown width - The memory warning never fired without a discrete GPU. useGpuInfo returned zero system RAM in that case, so the budget was always zero. Surface system RAM even when no GPU is present (Mac unified memory), and have the load dialog read memory directly instead of through props. - Give the dialog fields shrink-0 so the KV Cache Dtype value (e.g. q8_0) is not squeezed and clipped by the row. * Studio: fold fine-tuned models into On Device tab Remove the Hub models and Fine-tuned source tabs. Fine-tuned models now show as a section in the Hub tab's On Device view, above Custom Folders, with the Train icon and a collapse toggle. The section only appears when the user has fine-tuned models. With no external providers the lone Hub tab hides its own toggle. Also: tick-circle Show hidden checkbox and drop the divider above Eject; keep run settings load params (KV cache dtype, speculative, tensor parallel) from being clobbered by a mid-load status poll. * Studio: stage load settings in the sidebar with a Load on selection toggle Replace the pre-load settings popup with a staging flow in the Run settings sidebar. The gear on a downloaded quant row now stages the model and opens Run settings with Load model and Cancel buttons, so options like context length, KV cache, speculative decoding and tensor parallelism are set before the model loads. A "Remember these settings" tick reuses them next time. Add a global Load on selection toggle in Settings, Chat tab (default on). On: Unsloth auto-picks the best settings for your hardware and loads on selection. Off: picking a model stages it in Run settings to customize first. The gear always stages, regardless of the toggle. Other polish in this change: - Fine-tuned models live under the On Device tab, with a train icon on the header that jumps to the Fine-tuned section. - Default to the On Device tab when downloads exist, otherwise the last used section. - Standard Unsloth tooltips on the train, folder and gear icons. - Request the gguf param count on every Hub listing fetch so Kimi, MiniMax and GLM show a size badge. - Search Hub hover state, scrollbar position and minor spacing fixes. Remove the old inference load settings dialog. * Studio: always show the fine-tuned shortcut and smooth out the picker - Fine-tuned section and its train shortcut now always show on On Device, with an empty state when no fine-tuned models exist yet. - Folder icon on the header jumps to Custom Folders instead of opening the browse popup, matching the train shortcut. - Folder browser keeps the list mounted and dims it while refetching, so toggling Show hidden or changing folders no longer flashes. - Drop the tooltip hover grace area in the picker so moving between the train, folder and gear icons switches the tooltip at once. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: add quantization display options and drop the fine-tuned empty text - Settings, Chat: 'Expand quantizations' toggle. On expands every On Device GGUF model's quantizations by default; off keeps them behind a click (default). - Settings, Chat: 'Show all quantizations' toggle. On lists every quant including ones not downloaded (default); off shows downloaded only. - Remove the empty-state line under the Fine-tuned header; the header still shows on its own. * Studio: let expanded quantizations collapse on click and split the On/Off help - With Expand quantizations on, clicking an On Device model now collapses or re-expands its quantizations. The collapse state is in memory only, so it resets on reload and when the setting is toggled. - Put the Off sentence on its own line in the quantization setting descriptions. * Studio: reorder chat settings and rename the model section - Rename the Models section to Select model settings and move it above the Chat menu section. - Trim the section and Load on selection descriptions. * Studio: tighten the On/Off lines in the model setting descriptions Use a line break instead of separate spans so the On and Off lines sit on consecutive lines without the extra paragraph gap. * Studio: top-align the Load on selection toggle Add an alignTop option to SettingsRow and use it so the toggle sits at the top of the row next to the label, not centered against the tall description. * Studio: put the gear hint and example chip on one line Move the gear example chip inline with its label so it reads as a single line instead of wrapping onto its own row. * Studio: move the New badge from API keys to Chat settings Add the New badge to the Chat settings tab and drop it from API keys. * Studio: line the Load on selection toggle up with the first description line Offset the top-aligned control past the label row so it sits next to the On line instead of the label. * Studio: label the chat menu item Chat with Files (RAG) Rename the Chat with Files entry in the chat menu settings to clarify it is RAG. * Studio: drop the pill around the gear example so it fits on one line Remove the background and padding from the gear example chip so it sits inline with its label at a lower height. * Studio: fold the gear example into the description line spacing Render the gear example inline in the same text block so its line spacing matches the On and Off lines instead of an extra flex gap. * Studio: scope Show all quantizations to On Device only Gate the downloaded-only filter on an onDevice flag so Recommended and other browse lists always show every quant, and note On Device in the setting copy. * Studio: tidy On Device GGUF rows - Drop the redundant Quantizations subheading under On Device models. - Relay GGUF vision support up to the model name as a Vision badge instead. - Drop the repo size from On Device GGUF model rows since the quants already show their size. * Studio: pin the eject button and tidy General settings - Move Eject loaded model out of the scrollable list into a centered footer so it stays in view no matter how far the list is scrolled. - Space out and center the gear example in the Load on selection description. - General: drop the duplicate Unsloth version section, move llama.cpp notifications above Helper LLM, and note new models in its description. * Studio: add left padding before the gear example Nudge the gear example away from its label with a small left margin. * Studio: make the eject footer a sticky bar over the list Pin Eject loaded model to the bottom of the scroll area with the menu background so rows scroll under it, and drop the divider line. * Studio: drop the eject footer background, keep it a sticky button Make the sticky eject a centered transparent button so it coexists with the rows scrolling behind it. The wrapper ignores pointer events so only the button is clickable. * Studio: give the eject button a solid background Add the menu background, a border and a soft shadow to the sticky eject button so it reads as a floating button over the list. * Studio: restore the eject footer block, keep hover on the button only Bring back the full-width menu background behind the sticky eject footer, but keep the button compact and centered so the hover stays on the button. * Studio: show the vision badge on On Device rows without expanding - cached-gguf listing reports has_vision (mmproj present), so the badge shows on the model name without opening the quantizations. - Make the vision badge icon-only with a tooltip: "This model can process image inputs". Falls back to the expander-reported value on older backends. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Make LM Studio and Local models sections collapsible * Fade the eject footer instead of a solid block * Wrap the vision badge in a bordered pill * Taller model list with the eject footer pinned to the bottom * Use purple for the vision badge to set it apart from GGUF * Reduce the model list height * Make the eject button inline with no background block * Match the vision badge color to the Hub indigo tone * Shorten the model list and square off the format tags * Pin the eject button so it floats at the bottom of the list * Give the floating eject button a tinted background * Add bottom clearance so the list ends on white space under the eject button * Match eject button to the menu background and unify the settings gear icon * Move eject below the list and match its shadow and dark background * Drop the min height so short model lists leave no white space * Remove the eject button fill so it never covers the list * Nest dropdown hover radius inside the menu corners * Float the eject pill again and fix sort dropdown hover radius * Make the eject button opaque in both themes on hover and dark * Trim the model menu bottom padding so it stops clipping the last row * Match dark eject background to the Search Hub button and pad row indicators * Fade the model list bottom edge while rows sit below the fold * Lift the eject button and trim the section toggle right padding * Nudge the model list taller and run the bottom fade to the box edge * Nudge the model list slightly taller * Remove the eject button shadow * Align the eject button to the right * Widen the Search Hub and dropdowns and right-align them * Seat the eject button at the base and restore On Device right padding * Reduce the Search Hub and dropdown width by 4px * Widen the model menu so the section toggle keeps its padding * Make the eject button an icon-only button with shadow * Tighten section tab padding to cut the grey between tabs * Revert section tab padding back to px-3 * Remove the section toggle trailing padding * Add an eject button beside the model selector trigger * Shrink the in-list eject button to a smaller proportional size * Raise the in-list eject button * Make the trigger eject a bare icon next to the dropdown arrow * Revert eject back to the labeled button on the right * Place the format and sort dropdowns next to the section toggle * Raise the eject button and shorten its label to Eject model * Widen the gap between the toggle and dropdowns slightly * Align Search Hub with the last dropdown via a shared-width grid * Narrow the model menu for symmetric padding * Stretch the search row so Search Hub lines up with the last dropdown * Inset the list so the right padding matches the left * Right-align dropdowns and full-width search so Search Hub meets the last dropdown * Pack section toggle and dropdowns with a uniform gap * Inset search row so Search Hub aligns with the Trending dropdown * Trim model menu right padding to match the left * Nudge model list scrollbar inward * Move eject button to the bottom left with a light shadow * Shorten show all quantizations description * Keep eject button right-aligned, nudged in from the edge * Move Connected into the section toggle as a cloud-icon tab * Align eject button with the format tag edge * Right-align Connected layout so Search Hub meets Trending * Download selected models through the Hub download manager * Add Other models section for non-Unsloth downloads * Add directions icon and shortcut for Other models section * Space out subheadings and gate Other models on non-Unsloth downloads * Use direction-right icon for Other models * Use flag icon for Other models * Widen Connected menu so dropdowns align with Search Hub * Model selector: truncate long quant labels and tidy layout - Hub GGUF card: truncate long file-path quant labels with an ellipsis instead of overflowing the row. - Connected layout: left-pack the dropdowns and size the box so the last dropdown's right gap matches the pill's left gap, with Search Hub on its edge. - On Device: show MLX/Safetensors with the size on non-GGUF rows. - Connected list rows use the same grey hover as the tabs; the selected section tab no longer shows a hover change. * Model selector: drop stale custom section on restore A persisted custom section value no longer maps to a tab, so restoring it opened the picker to an empty view. Fall back to recommended instead. * Model selector: align the non-connected search bar with the All dropdown Nudge the non-connected box width so the search bar's right edge meets the All dropdown, which lands Search Hub on the last dropdown's edge. * Studio chat model selector: remember last tab, route non-GGUF downloads through Hub, stack overlays - Restore the last Hub section (Recommended / On Device) on every open instead of always snapping to On Device when downloads exist. - Route uncached non-GGUF repos (safetensors / MLX) through the Hub download manager via a snapshot download, so every model download shows in the bottom-right indicator and follows Load on selection like GGUF. - Allow safetensors in Recommended on Mac (they run locally there now), and honor the Safetensors format filter instead of dropping it via the recommendation default. - Stack bottom-right overlays in one column so the download panel and banners never overlap. - Add evenly spaced divider lines between the On Device subheadings. - Pad the bottom of the list so the floating Eject pill never covers the last row. * Studio downloads panel: widen left padding on header and rows Bump the left inset to pl-4 while keeping pr-3 so the collapse and cancel buttons stay put. * Studio: update cached-gguf route tests for the has_vision field list_cached_gguf now returns has_vision per row (vision badge on On Device); the expected dicts were missing it. True for the mmproj vision repo, False elsewhere. * Studio: keep MLX/safetensors selectable in chat-only Mac search The empty Recommended view allows GGUF plus MLX/safetensors on Mac, but the curated and HF search lists dropped non-GGUF in chat-only via a GGUF-only filter, so typing a query hid runnable Mac models. Reuse isRecommendableFormat in both lists so search matches the empty view (chat-only non-Mac stays GGUF-only). * Model selector: restore global model search and fix GGUF/device-fit regressions - Search: training, export and onboarding pickers searched only the unsloth org on a typed query. Restore the prior behavior (global Hub search with unsloth floated first when a query is typed, curated unsloth listing when empty). - Recommended browse: the GGUF/MLX-only gate ran before the format filter, so the Safetensors filter and the Trending/Recent sorts always came back empty. Apply that gate only for the Recommended sort and chat-only mode. - GGUF metadata: request the gguf expand field through listModels so repos with no size token in the name (Kimi, MiniMax, GLM) report a param count for the size and OOM badge. - Local GGUF: custom-folder and standalone ./models/*.gguf files now load directly with the GGUF marker instead of dead-ending in the variant expander, and scanned GGUF folders are classified via a backend model_format hint. - Device fit: use system RAM in the budget on unified-memory hosts, and keep MLX rows selectable on chat-only Macs. - kv-cache-estimate: resolve the quant from the snapshot-relative path, skip MTP drafter files, and prefer the most complete snapshot (mirrors the variant scanner). Bound the Ollama manifest walk. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Model selector: classify suffixless local GGUF folders consistently Complete the model_format plumbing so a GGUF folder is detected and loaded through the same GGUF path that the format filter already uses: - _scan_models_dir: a config.json no longer disqualifies a folder whose only weights are .gguf, so HF GGUF repos shipping a config still classify as GGUF. - _scan_lmstudio_dir: emit model_format for every GGUF row (LM Studio dirs rarely carry a -GGUF suffix), via a shared _dir_model_format helper. - Custom Folders and LM Studio rows: use localModelIsGguf (the same helper the filter uses) so the row label, expand-vs-direct-load, and isGguf flag agree; a suffixless GGUF folder no longer filters as GGUF but loads as non-GGUF. Adds tests/test_local_model_format.py covering the classification rule. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio model selector: tighten section spacing Trim each subheading's gap to its rows (pb-1.5 to pb-1) and pull the On Device heading block tight to the controls while Recommended keeps a little top room. * Hub: format filter fix, sort defaults, avatar and layout polish - Format dropdown now filters the feed's Latest list too, so the default GGUF hides fp8/safetensors and picking a format changes the rows. - Latest Unsloth Models sorts by newest created, not recently updated. - Sort dropdown order: Newest, Trending, Most downloads, Recently updated, Most likes. - Unsloth uploads with no upstream provider logo show the Unsloth avatar instead of a colored initial. - Owner scope pill gets a little more room before the chevron. - README detail column lines up with the top bar (both-edges gutter). - Long file-path quant labels truncate instead of overflowing the row. - Model list keyboard nav no longer clips the focus ring. - Run settings sheet: restore the Remember settings toggle and larger Load/Cancel buttons on the staged load flow. * Hub: hide the RAG embedding model from browse previews The Hub discover feed and chat model selector pull from the Hugging Face listing on the client, which the backend _is_hidden_model filter never touches, so the RAG embedder (unsloth/bge-small-en-v1.5-GGUF) and the llama.cpp validation probe leaked into the lists. Added isHiddenModelId mirroring the backend needles and filtered it out of the discover rows, the trending feed, and the selector's recommended and Hugging Face search lists. Per-repo file and download views are untouched, so the model is never deleted and a reinstall still shows it as already downloaded. * Studio: skip hidden dirs when checking a folder for downloaded models _dir_has_downloaded_model walked the tree with rglob("*") bounded by max_entries. rglob yields entries in arbitrary order and counts every one, so a model directory that also holds a large hidden subtree (.git/.cache/venv) could exhaust the budget before reaching the real weights and falsely report no model, hiding a valid Recommended-folder chip. Replace the generic-weights pass with a bounded BFS that skips hidden directories so their entries can't starve the walk. Adds a regression test (50-entry .git beside the weights, max_entries=10). * Fix/adjust model selector handling for PR #6364 * Studio: address codex review on the staging/recommended-folder paths - chat-page auto-load: selectModel only clears pendingSelection on success, so a failed auto-load left the hidden stage (and its edited load knobs) behind. Abandon the stage when it still matches the failed pick. - model picker: count fine-tuned rows in the On Device empty check so a fine-tuned-only tab no longer shows a false 'No models on device' message above the Fine-tuned section. - general settings: add the remembered per-model load settings key to PREFS_KEYS so 'Reset all local preferences' actually clears it. - recommended-folders: recognize PyTorch .bin weights (gated by the scanner's weight-name prefixes) so a .bin-only model folder still earns a chip; add tests. * Studio: name-gate .bin weight detection and complete selector preference reset Follow-up to the codex review on the model_format/recommended-folder paths: - _dir_model_format and _scan_models_dir treated any .bin (incl. tokenizer.bin) as a non-GGUF weight, so a suffixless GGUF folder shipping a companion .bin was misclassified as a plain checkpoint and routed through the wrong load path. Factor the scanner's weight-name gating into shared _is_weight_bin / _has_non_gguf_weights helpers and use them everywhere (also in _dir_has_downloaded_model). - PREFS_KEYS was missing the new 'Select model settings' keys (load on selection, expand/show-all quantizations), so 'Reset all local preferences' left them set. - On Device cached search dropped the active format filter while a query was typed; keep matchesFormatFilter applied so the format dropdown stays consistent. Adds tests for the tokenizer.bin vs weight-.bin classification. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: validate Ollama blobs, gate staged context, honor RAM budget on no-GPU hosts - recommended-folders: only count an Ollama dir once its manifest resolves to an on-disk model blob, so a failed/pruned pull no longer surfaces an empty chip - GGUF variant click: only seed the staged contextLength for already-downloaded picks, so choosing an undownloaded quant from a partially cached repo still starts its download (the staging effect short-circuits on a known context) - device fit: classify GGUF variants against the system-RAM budget on no-GPU / unified-memory hosts instead of reporting everything as fits, and pass systemRamGb to every variant expander regardless of gpu.available * Studio: scope Hub search to Recommended, fix staged non-GGUF settings, keep local MLX on Mac - model picker: only run the Hub search hooks on the Recommended section. On Device / Connected render local data, so typing there no longer fires HF requests or a spinner and the local/offline flow is preserved - chat settings: when a pick is staged, decide the GGUF-only controls from the staged model's type, not the currently loaded model's. A staged non-GGUF Hub repo no longer inherits a loaded GGUF's context/KV/speculative controls - On Device: keep local MLX builds in ./models selectable on Mac (chat-only ran GGUF/MLX only, but the filter dropped MLX before the format toggle) --------- Co-authored-by: shimmyshimmer <info@unsloth.ai> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com> Co-authored-by: Daniel Han <danielhanchen@gmail.com> Co-authored-by: wasimysaid <wasimysdev@gmail.com>
117 lines
3.9 KiB
Python
117 lines
3.9 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
|
|
"""Tests for local GGUF ``model_format`` classification (PR #6364 follow-up).
|
|
|
|
Suffixless GGUF folders (custom folders / LM Studio) carry no ``-GGUF`` name
|
|
hint, so the scanners must surface ``model_format = "gguf"`` for the UI to route
|
|
them through the GGUF load path. The rule, shared by ``_dir_model_format`` and
|
|
``_scan_models_dir``: a directory is GGUF-format when it holds ``.gguf`` files
|
|
and no non-GGUF weights (``.safetensors`` / ``.bin``); a stray ``config.json``
|
|
must not disqualify it.
|
|
|
|
No GPU/network: only file names and sizes are inspected.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import types
|
|
from pathlib import Path
|
|
|
|
# Keep runnable without optional logging deps (mirrors the sibling tests).
|
|
if "structlog" not in sys.modules:
|
|
|
|
class _DummyLogger:
|
|
def __getattr__(self, _name):
|
|
return lambda *args, **kwargs: None
|
|
|
|
sys.modules["structlog"] = types.SimpleNamespace(
|
|
BoundLogger = _DummyLogger,
|
|
get_logger = lambda *args, **kwargs: _DummyLogger(),
|
|
)
|
|
|
|
import routes.models as models_route
|
|
|
|
|
|
def _touch(path: Path) -> Path:
|
|
path.parent.mkdir(parents = True, exist_ok = True)
|
|
path.write_bytes(b"\0")
|
|
return path
|
|
|
|
|
|
def test_dir_model_format_gguf_only(tmp_path):
|
|
d = tmp_path / "model"
|
|
_touch(d / "model-Q4_K_M.gguf")
|
|
assert models_route._dir_model_format(d) == "gguf"
|
|
|
|
|
|
def test_dir_model_format_gguf_with_config_is_still_gguf(tmp_path):
|
|
# A config.json alongside the .gguf must not flip it to non-GGUF.
|
|
d = tmp_path / "model"
|
|
_touch(d / "config.json")
|
|
_touch(d / "model-Q4_K_M.gguf")
|
|
assert models_route._dir_model_format(d) == "gguf"
|
|
|
|
|
|
def test_dir_model_format_mixed_weights_is_not_gguf(tmp_path):
|
|
# Real safetensors weights present -> not a GGUF folder.
|
|
d = tmp_path / "model"
|
|
_touch(d / "model.safetensors")
|
|
_touch(d / "model-Q4_K_M.gguf")
|
|
assert models_route._dir_model_format(d) is None
|
|
|
|
|
|
def test_dir_model_format_no_gguf(tmp_path):
|
|
d = tmp_path / "model"
|
|
_touch(d / "config.json")
|
|
_touch(d / "model.safetensors")
|
|
assert models_route._dir_model_format(d) is None
|
|
|
|
|
|
def test_dir_model_format_ignores_tokenizer_bin(tmp_path):
|
|
# A companion tokenizer.bin is not a weight file, so a GGUF folder shipping
|
|
# one is still GGUF (not misread as a plain .bin checkpoint).
|
|
d = tmp_path / "model"
|
|
_touch(d / "tokenizer.bin")
|
|
_touch(d / "model-Q4_K_M.gguf")
|
|
assert models_route._dir_model_format(d) == "gguf"
|
|
|
|
|
|
def test_dir_model_format_weight_bin_is_not_gguf(tmp_path):
|
|
# A real PyTorch weight .bin alongside a .gguf means mixed weights -> None.
|
|
d = tmp_path / "model"
|
|
_touch(d / "pytorch_model.bin")
|
|
_touch(d / "model-Q4_K_M.gguf")
|
|
assert models_route._dir_model_format(d) is None
|
|
|
|
|
|
def test_scan_models_dir_classifies_gguf_with_config(tmp_path):
|
|
root = tmp_path / "models"
|
|
# GGUF repo that also ships a config.json (the regression case).
|
|
_touch(root / "gguf_repo" / "config.json")
|
|
_touch(root / "gguf_repo" / "model-Q4_K_M.gguf")
|
|
# A plain safetensors checkpoint stays non-GGUF.
|
|
_touch(root / "st_repo" / "config.json")
|
|
_touch(root / "st_repo" / "model.safetensors")
|
|
# A standalone .gguf file is GGUF.
|
|
_touch(root / "loose.gguf")
|
|
|
|
fmt = {Path(m.path).name: m.model_format for m in models_route._scan_models_dir(root)}
|
|
|
|
assert fmt["gguf_repo"] == "gguf"
|
|
assert fmt["st_repo"] is None
|
|
assert fmt["loose.gguf"] == "gguf"
|
|
|
|
|
|
def test_scan_models_dir_classifies_root_gguf_with_config(tmp_path):
|
|
# Custom scan folders can point directly at a GGUF repo, not only at a
|
|
# parent directory that contains model repos.
|
|
root = tmp_path / "SuffixlessRepo"
|
|
_touch(root / "config.json")
|
|
_touch(root / "model-Q4_K_M.gguf")
|
|
|
|
[row] = models_route._scan_models_dir(root)
|
|
|
|
assert row.path == str(root)
|
|
assert row.model_format == "gguf"
|