From 4c0a95c7caa8d0d9bd97fc5edea856279da550b2 Mon Sep 17 00:00:00 2001 From: Unsloth Date: Sun, 26 Jul 2026 04:39:14 -0700 Subject: [PATCH] Apply the picker task filter to local model sections LM Studio, ./models and custom-folder rows ignored it, so the Images picker listed chat GGUFs that 400 on a diffusion load. The backend already tags every local model with a task for this purpose. --- .../components/model-selector/pickers.tsx | 9 +++++++-- tests/studio/test_model_picker_contracts.py | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/studio/frontend/src/features/model-picker/components/model-selector/pickers.tsx b/studio/frontend/src/features/model-picker/components/model-selector/pickers.tsx index 9a5e972db3..bd342a7ea1 100644 --- a/studio/frontend/src/features/model-picker/components/model-selector/pickers.tsx +++ b/studio/frontend/src/features/model-picker/components/model-selector/pickers.tsx @@ -2138,13 +2138,16 @@ export function HubModelPicker({ sortLocalModels( lmStudioModels.filter( (m) => + // The backend tags every local model with its task for exactly this: on the + // Images/Video pages a chat GGUF must not be offered (it would 400 on load). + passesTaskGate(m.task, m.model_id ?? m.id, task) && localModelMatchesFormat(m, formatFilter) && matchesLocalQuery(m), ), downloadedSort, loadTimes, ), // eslint-disable-next-line react-hooks/exhaustive-deps - [lmStudioModels, downloadedSort, formatFilter, loadTimes, localQuery], + [lmStudioModels, downloadedSort, formatFilter, loadTimes, localQuery, task], ); // Local ./models entries. Chat-only Unsloth runs GGUF (any host) and MLX (Mac // only), so raw checkpoints there are hidden (mirrors the cached non-GGUF @@ -2156,6 +2159,7 @@ export function HubModelPicker({ sortLocalModels( localDirModels.filter( (m) => + passesTaskGate(m.task, m.model_id ?? m.id, task) && (!chatOnly || Boolean(task) || localModelIsGguf(m) || @@ -2183,13 +2187,14 @@ export function HubModelPicker({ sortLocalModels( customFolderModels.filter( (m) => + passesTaskGate(m.task, m.model_id ?? m.id, task) && localModelMatchesFormat(m, formatFilter) && matchesLocalQuery(m), ), customSort, loadTimes, ), // eslint-disable-next-line react-hooks/exhaustive-deps - [customFolderModels, customSort, formatFilter, loadTimes, localQuery], + [customFolderModels, customSort, formatFilter, loadTimes, localQuery, task], ); // Fine-tuned models for the On Device "Fine-tuned" section: flat, query- diff --git a/tests/studio/test_model_picker_contracts.py b/tests/studio/test_model_picker_contracts.py index b100a1d829..1a4bf151bd 100644 --- a/tests/studio/test_model_picker_contracts.py +++ b/tests/studio/test_model_picker_contracts.py @@ -637,3 +637,17 @@ def test_staged_downloads_always_scope_their_files(): assert "scopeId," in body and "files: current.files," in body assert "? null" not in body and "? undefined" not in body assert "const activeVariant = current ? scopedVariant(scopeId) : null;" in src + + +def test_local_model_sections_respect_the_task_filter(): + """LM Studio / ./models / custom-folder rows must honour the picker's task filter. + The backend tags every local model with a task for exactly this; without the gate the + Images picker listed chat GGUFs (which 400 on a diffusion load) and buried the + diffusion models the page can actually run.""" + src = _read("features/model-picker/components/model-selector/pickers.tsx") + for memo in ("sortedLmStudio", "sortedLocalDir", "sortedCustomFolderModels"): + block = re.search(rf"const {memo} = useMemo\(.*?\n \);", src, re.S) + assert block, f"{memo} not found" + assert "passesTaskGate(m.task" in block.group(0), ( + f"{memo} does not apply the task gate" + )