transparent image rail; hide fine-tuned shortcut; custom folders filtered to diffusion GGUFs
This commit is contained in:
parent
126e0f3792
commit
efdc5ad9a2
5 changed files with 83 additions and 51 deletions
|
|
@ -183,6 +183,12 @@ class LocalModelInfo(BaseModel):
|
|||
None,
|
||||
description = "Unix timestamp of latest observed update",
|
||||
)
|
||||
task: Optional[str] = Field(
|
||||
None,
|
||||
description = "HF pipeline task inferred from a GGUF's architecture "
|
||||
"('text-to-image' for diffusion, 'text-generation' otherwise). Lets the "
|
||||
"Images picker show only diffusion GGUFs.",
|
||||
)
|
||||
|
||||
|
||||
class LocalModelListResponse(BaseModel):
|
||||
|
|
|
|||
|
|
@ -843,6 +843,11 @@ async def list_local_models(
|
|||
reverse = True,
|
||||
)
|
||||
models = [m for m in models if not _is_hidden_model(m.id, m.path)]
|
||||
# Tag each GGUF with its task so the Images picker can filter to diffusion.
|
||||
models = [
|
||||
m.model_copy(update = {"task": _local_model_task(m.path, m.model_format)})
|
||||
for m in models
|
||||
]
|
||||
|
||||
return LocalModelListResponse(
|
||||
models_dir = str(models_root),
|
||||
|
|
@ -3117,6 +3122,12 @@ def _gguf_architecture(path: str) -> Optional[str]:
|
|||
return None
|
||||
|
||||
|
||||
def _arch_to_task(arch: Optional[str]) -> Optional[str]:
|
||||
if arch is None:
|
||||
return None
|
||||
return "text-to-image" if arch.lower() in _DIFFUSION_GGUF_ARCHS else "text-generation"
|
||||
|
||||
|
||||
def _repo_gguf_task(repo_info) -> Optional[str]:
|
||||
"""HF pipeline task of a cached GGUF repo, from its architecture:
|
||||
'text-to-image' for diffusion archs, else 'text-generation' (None if unreadable)."""
|
||||
|
|
@ -3124,10 +3135,29 @@ def _repo_gguf_task(repo_info) -> Optional[str]:
|
|||
for path in _iter_gguf_paths(Path(repo_info.repo_path)):
|
||||
if _is_mmproj_filename(path.name):
|
||||
continue
|
||||
arch = _gguf_architecture(str(path))
|
||||
if arch is None:
|
||||
task = _arch_to_task(_gguf_architecture(str(path)))
|
||||
if task is not None:
|
||||
return task
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _local_model_task(path: str, model_format: Optional[str]) -> Optional[str]:
|
||||
"""Same classification for a local model: read its GGUF architecture. The
|
||||
path may be the .gguf file itself or a folder containing one."""
|
||||
if model_format != "gguf":
|
||||
return None
|
||||
try:
|
||||
p = Path(path)
|
||||
if p.suffix.lower() == ".gguf" and p.is_file():
|
||||
return _arch_to_task(_gguf_architecture(str(p)))
|
||||
for f in _iter_gguf_paths(p):
|
||||
if _is_mmproj_filename(f.name):
|
||||
continue
|
||||
return "text-to-image" if arch.lower() in _DIFFUSION_GGUF_ARCHS else "text-generation"
|
||||
task = _arch_to_task(_gguf_architecture(str(f)))
|
||||
if task is not None:
|
||||
return task
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1745,20 +1745,20 @@ export function HubModelPicker({
|
|||
normalizeForSearch(
|
||||
`${m.model_id ?? ""} ${m.display_name} ${m.id}`,
|
||||
).includes(localQuery);
|
||||
// A task filter (the Images page) wants diffusion GGUFs only; local folders /
|
||||
// LM Studio / fine-tuned models aren't that, so drop them in that mode.
|
||||
// A task filter (the Images page) wants diffusion GGUFs only, so local models
|
||||
// are filtered to that task (by the GGUF architecture the backend reports).
|
||||
const sortedLmStudio = useMemo(
|
||||
() =>
|
||||
task
|
||||
? []
|
||||
: sortLocalModels(
|
||||
lmStudioModels.filter(
|
||||
(m) =>
|
||||
localModelMatchesFormat(m, formatFilter) && matchesLocalQuery(m),
|
||||
),
|
||||
downloadedSort,
|
||||
loadTimes,
|
||||
),
|
||||
sortLocalModels(
|
||||
lmStudioModels.filter(
|
||||
(m) =>
|
||||
(!task || taskMatchesFilter(m.task, task)) &&
|
||||
localModelMatchesFormat(m, formatFilter) &&
|
||||
matchesLocalQuery(m),
|
||||
),
|
||||
downloadedSort,
|
||||
loadTimes,
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[lmStudioModels, downloadedSort, formatFilter, loadTimes, localQuery, task],
|
||||
);
|
||||
|
|
@ -1767,20 +1767,19 @@ export function HubModelPicker({
|
|||
// rule). An MLX build a Mac user dropped in ./models stays selectable.
|
||||
const sortedLocalDir = useMemo(
|
||||
() =>
|
||||
task
|
||||
? []
|
||||
: sortLocalModels(
|
||||
localDirModels.filter(
|
||||
(m) =>
|
||||
(!chatOnly ||
|
||||
localModelIsGguf(m) ||
|
||||
(isMac && localModelIsMlx(m))) &&
|
||||
localModelMatchesFormat(m, formatFilter) &&
|
||||
matchesLocalQuery(m),
|
||||
),
|
||||
downloadedSort,
|
||||
loadTimes,
|
||||
),
|
||||
sortLocalModels(
|
||||
localDirModels.filter(
|
||||
(m) =>
|
||||
(!task || taskMatchesFilter(m.task, task)) &&
|
||||
(!chatOnly ||
|
||||
localModelIsGguf(m) ||
|
||||
(isMac && localModelIsMlx(m))) &&
|
||||
localModelMatchesFormat(m, formatFilter) &&
|
||||
matchesLocalQuery(m),
|
||||
),
|
||||
downloadedSort,
|
||||
loadTimes,
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
localDirModels,
|
||||
|
|
@ -1795,16 +1794,16 @@ export function HubModelPicker({
|
|||
);
|
||||
const sortedCustomFolderModels = useMemo(
|
||||
() =>
|
||||
task
|
||||
? []
|
||||
: sortLocalModels(
|
||||
customFolderModels.filter(
|
||||
(m) =>
|
||||
localModelMatchesFormat(m, formatFilter) && matchesLocalQuery(m),
|
||||
),
|
||||
customSort,
|
||||
loadTimes,
|
||||
),
|
||||
sortLocalModels(
|
||||
customFolderModels.filter(
|
||||
(m) =>
|
||||
(!task || taskMatchesFilter(m.task, task)) &&
|
||||
localModelMatchesFormat(m, formatFilter) &&
|
||||
matchesLocalQuery(m),
|
||||
),
|
||||
customSort,
|
||||
loadTimes,
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[customFolderModels, customSort, formatFilter, loadTimes, localQuery, task],
|
||||
);
|
||||
|
|
@ -2208,9 +2207,7 @@ export function HubModelPicker({
|
|||
// On Device owns the downloaded and custom-folder models; the Unsloth tab
|
||||
// searches the HF listing (below). Both filter locally by the query.
|
||||
const showDownloaded = section === "downloaded";
|
||||
// Custom Folders (server-side scan dirs) hold arbitrary local models, not
|
||||
// diffusion GGUFs — hide the whole section under a task filter (Images).
|
||||
const showCustom = section === "downloaded" && !task;
|
||||
const showCustom = section === "downloaded";
|
||||
const showRecommendedSection = !showHfSection && section === "recommended";
|
||||
const downloadedEmpty =
|
||||
visibleCachedGguf.length === 0 &&
|
||||
|
|
@ -2611,6 +2608,7 @@ export function HubModelPicker({
|
|||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{!task && (
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
|
|
@ -2632,6 +2630,7 @@ export function HubModelPicker({
|
|||
Go to fine-tuned models
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -289,6 +289,10 @@ export interface LocalModelInfo {
|
|||
// classify scanned folders whose name lacks a -GGUF suffix.
|
||||
model_format?: string | null;
|
||||
updated_at?: number | null;
|
||||
// HF pipeline task inferred from the GGUF architecture, so the Images picker
|
||||
// can filter local models to diffusion ("text-to-image"). Optional for
|
||||
// older-backend compatibility.
|
||||
task?: string | null;
|
||||
}
|
||||
|
||||
interface LocalModelListResponse {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import {
|
|||
import { Slider } from "@/components/ui/slider";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { SectionCard } from "@/components/section-card";
|
||||
import { ModelSelector } from "@/components/assistant-ui/model-selector";
|
||||
import type {
|
||||
ModelOption,
|
||||
|
|
@ -312,13 +311,7 @@ export function ImagesPage() {
|
|||
|
||||
{/* ── Controls rail + preview canvas ─────────────────── */}
|
||||
<div className="flex min-h-0 min-w-0 flex-1 gap-4 overflow-hidden px-4 pb-4 sm:px-6 sm:pb-6">
|
||||
<SectionCard
|
||||
icon={<HugeiconsIcon icon={ImageAdd02Icon} className="size-5" strokeWidth={1.5} />}
|
||||
title="Generate"
|
||||
description="Prompt and settings"
|
||||
accent="indigo"
|
||||
className="w-[360px] shrink-0 gap-4 overflow-y-auto"
|
||||
>
|
||||
<div className="flex w-[340px] shrink-0 flex-col gap-4 overflow-y-auto">
|
||||
<Field label="Prompt">
|
||||
<Textarea rows={4} value={prompt} onChange={(e) => setPrompt(e.target.value)} />
|
||||
</Field>
|
||||
|
|
@ -370,7 +363,7 @@ export function ImagesPage() {
|
|||
{busy === "generating" ? <Spinner className="mr-2 size-4" /> : null}
|
||||
Generate
|
||||
</Button>
|
||||
</SectionCard>
|
||||
</div>
|
||||
|
||||
<div className="bg-card corner-squircle relative flex min-w-0 flex-1 flex-col overflow-hidden rounded-3xl ring-1 ring-foreground/10">
|
||||
<div className="relative flex flex-1 items-center justify-center overflow-auto p-6">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue