diff --git a/studio/frontend/src/features/hub/catalog/local-on-device-card.tsx b/studio/frontend/src/features/hub/catalog/local-on-device-card.tsx
index e40f5fd5ac..3c020a7199 100644
--- a/studio/frontend/src/features/hub/catalog/local-on-device-card.tsx
+++ b/studio/frontend/src/features/hub/catalog/local-on-device-card.tsx
@@ -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 (
diff --git a/studio/frontend/src/features/hub/catalog/model-inspector.tsx b/studio/frontend/src/features/hub/catalog/model-inspector.tsx
index 0f244bce53..a67b2a57cc 100644
--- a/studio/frontend/src/features/hub/catalog/model-inspector.tsx
+++ b/studio/frontend/src/features/hub/catalog/model-inspector.tsx
@@ -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({
)}
+ {showChatOnly && (
+
+
+
+
+
+
+
+ This device has no supported GPU or usable MLX, so only GGUF models
+ can run here.
+
+ Still downloadable to your Hugging Face cache.
+
+
+
+ )}
{showVram && vramInfo && (
@@ -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({
diff --git a/studio/frontend/src/features/hub/catalog/safetensors-download-card.tsx b/studio/frontend/src/features/hub/catalog/safetensors-download-card.tsx
index ccd61c5f04..424afa2e05 100644
--- a/studio/frontend/src/features/hub/catalog/safetensors-download-card.tsx
+++ b/studio/frontend/src/features/hub/catalog/safetensors-download-card.tsx
@@ -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({
)}
- {/* Divider sits above the Download CTA; in the action-pair state it hides with the pair. */}
- {(!showActionPair || HUB_POST_DOWNLOAD_ACTIONS_VISIBLE) && }
+ {/* 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) && }
{showActionPair ? (
- {onTrain && (
+ {trainActionVisible && (