Studio: Show Run button for downloaded non-GGUF models in the Model Hub (#7001)

This commit is contained in:
oobabooga 2026-07-13 19:18:20 -03:00 committed by GitHub
commit 76d7088e0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 57 additions and 13 deletions

View file

@ -4,6 +4,7 @@
import { TrainIcon } from "../components/train-icon";
import {
HUB_GGUF_RUN_ACTIONS_VISIBLE,
HUB_NON_GGUF_RUN_ACTIONS_VISIBLE,
HUB_POST_DOWNLOAD_ACTIONS_VISIBLE,
} from "../lib/hub-feature-flags";
import {
@ -410,7 +411,7 @@ export function LocalOnDeviceCard({
const showOldCacheHint = source === "hf_cache" && !!unsupportedReason;
const runActionsVisible = isGguf
? HUB_GGUF_RUN_ACTIONS_VISIBLE
: HUB_POST_DOWNLOAD_ACTIONS_VISIBLE;
: HUB_NON_GGUF_RUN_ACTIONS_VISIBLE;
return (
<div className="flex w-full flex-col gap-2">

View file

@ -263,17 +263,22 @@ type VramInfo = { est: number; status: "fits" | "tight" | "exceeds" } | null;
function ModelStatusChips({
isDataset,
isGguf,
chatOnly,
unslothSupport,
vramInfo,
}: {
isDataset: boolean;
isGguf: boolean;
chatOnly: boolean;
unslothSupport: UnslothSupport;
vramInfo: VramInfo;
}) {
const showUnsupported = !isDataset && unslothSupport.status === "unsupported";
// The format-unsupported chip already explains itself; this one covers the
// supported-format model a chat-only host still can't run.
const showChatOnly = !isDataset && !isGguf && chatOnly && !showUnsupported;
const showVram = !isDataset && vramInfo && !isGguf;
if (!showUnsupported && !showVram) return null;
if (!showUnsupported && !showChatOnly && !showVram) return null;
const vramTone = vramInfo
? vramInfo.status === "exceeds"
@ -323,6 +328,26 @@ function ModelStatusChips({
</TooltipContent>
</Tooltip>
)}
{showChatOnly && (
<Tooltip>
<TooltipTrigger asChild={true}>
<span tabIndex={0} className="inline-flex outline-none">
<StatusChip tone="warning" label="GGUF-only device" />
</span>
</TooltipTrigger>
<TooltipContent
side="bottom"
sideOffset={6}
className="tooltip-compact max-w-xs"
>
This device has no supported GPU or usable MLX, so only GGUF models
can run here.
<span className="mt-1 block text-[10.5px] font-normal text-white/75">
Still downloadable to your Hugging Face cache.
</span>
</TooltipContent>
</Tooltip>
)}
{showVram && vramInfo && (
<Tooltip>
<TooltipTrigger asChild={true}>
@ -406,6 +431,7 @@ export const ModelInspector = memo(function ModelInspector({
onSearchHub,
} = actions;
const deviceType = usePlatformStore((s) => s.deviceType);
const chatOnly = usePlatformStore((s) => s.isChatOnly());
const hfToken = useHfTokenStore((s) => s.token);
const datasetRepoId = isDataset && model?.hubRepoId ? model.hubRepoId : null;
const datasetSize = useDatasetSize(datasetRepoId, {
@ -504,15 +530,19 @@ export const ModelInspector = memo(function ModelInspector({
const paramsLabel = model.totalParams
? formatCompact(model.totalParams)
: "N/A";
const trainingSupported = unslothSupport.status !== "unsupported";
const unslothSupported = unslothSupport.status !== "unsupported";
// Chat-only hosts (no supported GPU / usable MLX) run inference only through
// llama.cpp, so only GGUF is loadable.
const canRunModel =
!isDataset && (model.runtimeCapabilities?.canChat ?? true);
!isDataset &&
(model.runtimeCapabilities?.canChat ?? true) &&
(model.isGguf || (!chatOnly && unslothSupported));
const canTrainModel =
!isDataset &&
(model.runtimeCapabilities?.canTrain ?? false) &&
model.modelFormat !== "gguf" &&
model.modelFormat !== "adapter" &&
trainingSupported;
unslothSupported;
const languages = parseLanguageTags(model.tags);
const datasetSizeBytes =
@ -765,6 +795,7 @@ export const ModelInspector = memo(function ModelInspector({
<ModelStatusChips
isDataset={isDataset}
isGguf={model.isGguf}
chatOnly={chatOnly}
unslothSupport={unslothSupport}
vramInfo={vramInfo}
/>

View file

@ -16,7 +16,10 @@ import {
PlayIcon,
} from "@hugeicons/core-free-icons";
import { TrainIcon } from "../components/train-icon";
import { HUB_POST_DOWNLOAD_ACTIONS_VISIBLE } from "../lib/hub-feature-flags";
import {
HUB_NON_GGUF_RUN_ACTIONS_VISIBLE,
HUB_POST_DOWNLOAD_ACTIONS_VISIBLE,
} from "../lib/hub-feature-flags";
import { HugeiconsIcon } from "@hugeicons/react";
import { useEffect, useState } from "react";
import { useHfTokenStore } from "../stores/hf-token-store";
@ -158,6 +161,7 @@ export function SafetensorsDownloadCard({
const showActionPair = isDownloaded && !downloading && (canRun || !!onTrain);
const showUnavailableAction =
isDownloaded && !downloading && !canRun && !onTrain;
const trainActionVisible = !!onTrain && HUB_POST_DOWNLOAD_ACTIONS_VISIBLE;
const canDelete =
(isDownloaded || isPartial) &&
!downloading &&
@ -238,17 +242,17 @@ export function SafetensorsDownloadCard({
)}
</div>
</div>
{/* Divider sits above the Download CTA; in the action-pair state it hides with the pair. */}
{(!showActionPair || HUB_POST_DOWNLOAD_ACTIONS_VISIBLE) && <CardDivider />}
{/* Info/actions hairline; dropped for the run action row (no divider before
Run, as in the GGUF card's Run CTA), restored when the Train pair ships. */}
{(!showActionPair || trainActionVisible) && <CardDivider />}
{showActionPair ? (
<div
className={cn(
"group/pair flex h-9 shrink-0 items-stretch gap-1.5",
// Run+Train pair hidden until Hub→chat / Hub→train pickers ship.
!HUB_POST_DOWNLOAD_ACTIONS_VISIBLE && "hidden",
!HUB_NON_GGUF_RUN_ACTIONS_VISIBLE && "hidden",
)}
>
{onTrain && (
{trainActionVisible && (
<button
type="button"
onClick={onTrain}

View file

@ -4,8 +4,13 @@
// Hub feature flags for staged rollout. JSX/wiring stay in place so flipping
// a flag here is the only edit needed to re-enable.
// Post-download Run / New Chat / Use in chat / Train CTAs; hidden until the
// Hub-aware chat and train pickers ship.
// Post-download New Chat / Use in chat / Train CTAs; hidden until the
// Hub-aware chat and train pickers ship. Run CTAs are gated separately below.
export const HUB_POST_DOWNLOAD_ACTIONS_VISIBLE = false;
export const HUB_GGUF_RUN_ACTIONS_VISIBLE = true;
// Post-download Run CTA for non-GGUF models (MLX repos classify as safetensors).
// Run has no dependency on the Hub-aware chat/train pickers, so it enables
// independently of HUB_POST_DOWNLOAD_ACTIONS_VISIBLE, which still gates those.
export const HUB_NON_GGUF_RUN_ACTIONS_VISIBLE = true;

View file

@ -157,6 +157,9 @@ function detectFormatKey(
if (alias) return alias;
}
if (modelId) {
// Owner implies format even when local metadata lacks tags; mirrors the
// backend's _looks_like_mlx_repo heuristic.
if (modelId.trim().toLowerCase().startsWith("mlx-community/")) return "mlx";
const name = repoLeaf(modelId);
for (const { key, pattern } of FORMAT_NAME_PATTERNS) {
if (pattern.test(name)) return key;