feat(hub): enable Run/New Chat for downloaded GGUF models, fix README (#6152)
* feat(hub): enable Run/New Chat for downloaded GGUF models, fix README bottom spacing - enable the Run / New Chat action for GGUF models that are already downloaded, across the download card, on-device card, and hub page - remove the extra bottom spacing under the model inspector README * fix(hub): sync active GGUF variant on mount * feat(hub): open a new chat immediately when Run is clicked --------- Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
This commit is contained in:
parent
c689dc586a
commit
cae12b793f
6 changed files with 84 additions and 39 deletions
|
|
@ -19,6 +19,7 @@ export {
|
|||
export { useChatRuntimeStore } from "./stores/chat-runtime-store";
|
||||
export { useChatSearchStore } from "./stores/chat-search-store";
|
||||
export { useChatModelRuntime } from "./hooks/use-chat-model-runtime";
|
||||
export { isExternalModelId } from "./external-providers";
|
||||
export { ChatSearchDialog } from "./components/chat-search-dialog";
|
||||
export { setTrainingCompareHandoff } from "./lib/training-compare-handoff";
|
||||
export type { ProjectRecord } from "./types";
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import {
|
|||
import { type GgufVariantDetail, deleteCachedModel } from "../inventory";
|
||||
import { formatBytes } from "../lib/format";
|
||||
import { type GgufFitClass, classifyGgufFit } from "../lib/gguf-fit";
|
||||
import { HUB_POST_DOWNLOAD_ACTIONS_VISIBLE } from "../lib/hub-feature-flags";
|
||||
import { HUB_GGUF_RUN_ACTIONS_VISIBLE } from "../lib/hub-feature-flags";
|
||||
import {
|
||||
ggufVariantsMatch,
|
||||
normalizeGgufVariantIdentity,
|
||||
|
|
@ -794,8 +794,7 @@ export function GgufDownloadCard({
|
|||
downloadingThisVariant &&
|
||||
!cancelling &&
|
||||
"hover:bg-rose-500/10 hover:text-rose-600 dark:hover:text-rose-400",
|
||||
// Hide post-download CTAs (Run / New Chat) for this PR.
|
||||
!HUB_POST_DOWNLOAD_ACTIONS_VISIBLE &&
|
||||
!HUB_GGUF_RUN_ACTIONS_VISIBLE &&
|
||||
!downloadingThisVariant &&
|
||||
!cancelling &&
|
||||
!isLoadingThisModel &&
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
|
||||
import { TrainIcon } from "../components/train-icon";
|
||||
import { HUB_POST_DOWNLOAD_ACTIONS_VISIBLE } from "../lib/hub-feature-flags";
|
||||
import {
|
||||
HUB_GGUF_RUN_ACTIONS_VISIBLE,
|
||||
HUB_POST_DOWNLOAD_ACTIONS_VISIBLE,
|
||||
} from "../lib/hub-feature-flags";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
|
@ -295,6 +298,9 @@ export function LocalOnDeviceCard({
|
|||
const formatTone =
|
||||
modelFormat === "adapter" ? "adapter" : isGguf ? "gguf" : "checkpoint";
|
||||
const showOldCacheHint = source === "hf_cache" && !!unsupportedReason;
|
||||
const runActionsVisible = isGguf
|
||||
? HUB_GGUF_RUN_ACTIONS_VISIBLE
|
||||
: HUB_POST_DOWNLOAD_ACTIONS_VISIBLE;
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
|
|
@ -440,11 +446,10 @@ export function LocalOnDeviceCard({
|
|||
className="ml-1 mr-0 h-5 w-px shrink-0 bg-foreground/[0.06] opacity-100 transition-opacity duration-150 group-hover/dl:opacity-0 dark:bg-white/[0.04]"
|
||||
/>
|
||||
)}
|
||||
{/* Run + Train pair hidden until Hub->chat/train pickers ship. */}
|
||||
<div
|
||||
className={cn(
|
||||
"group/pair flex h-9 shrink-0 items-stretch gap-1.5",
|
||||
!HUB_POST_DOWNLOAD_ACTIONS_VISIBLE && "hidden",
|
||||
!runActionsVisible && "hidden",
|
||||
)}
|
||||
>
|
||||
{onTrain && (
|
||||
|
|
|
|||
|
|
@ -725,8 +725,6 @@ export const ModelInspector = memo(function ModelInspector({
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div aria-hidden="true" className="h-3 shrink-0" />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
|
||||
// Hub page: browse + download surface only. Train CTA hidden until Hub->train picker ships.
|
||||
import { useHubInventory } from "@/features/hub/inventory";
|
||||
import { useDebouncedValue } from "@/hooks/use-debounced-value";
|
||||
import { useGpuInfo } from "@/hooks/use-gpu-info";
|
||||
|
|
@ -12,9 +11,15 @@ import {
|
|||
import { useOnlineStatus } from "@/features/hub/hooks/use-online-status";
|
||||
import { useIsHubDesktop } from "@/features/hub/hooks/use-is-hub-desktop";
|
||||
import { useHubInfiniteScroll } from "@/features/hub/hooks/use-hub-infinite-scroll";
|
||||
import { modelIdsMatch } from "@/features/hub/lib/model-identity";
|
||||
import { ggufVariantsMatch, modelIdsMatch } from "@/features/hub/lib/model-identity";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useHfTokenStore } from "@/features/hub/stores/hf-token-store";
|
||||
import {
|
||||
getInferenceStatus,
|
||||
isExternalModelId,
|
||||
useChatModelRuntime,
|
||||
useChatRuntimeStore,
|
||||
} from "@/features/chat";
|
||||
import { ArrowLeft01Icon } from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||
|
|
@ -179,14 +184,35 @@ export function ModelsPage() {
|
|||
const gpu = useGpuInfo();
|
||||
const online = useOnlineStatus();
|
||||
|
||||
// "Use in chat" just navigates to /chat; checkpoint/variant handoff lands with the Hub-aware picker.
|
||||
const activeCheckpoint: string | null = null;
|
||||
const activeGgufVariant: string | null = null;
|
||||
// Stubs so the inspector + load-status memo still typecheck; cast keeps the shape from narrowing to `never`.
|
||||
const loadingModel = null as { id: string } | null;
|
||||
const loadProgress = null as {
|
||||
phase?: "downloading" | "starting";
|
||||
} | null;
|
||||
const { selectModel, loadingModel, loadProgress, ejectModel } =
|
||||
useChatModelRuntime();
|
||||
const checkpoint = useChatRuntimeStore((s) => s.params.checkpoint);
|
||||
const activeCheckpoint =
|
||||
checkpoint && !isExternalModelId(checkpoint) ? checkpoint : null;
|
||||
const activeGgufVariant = useChatRuntimeStore((s) => s.activeGgufVariant);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
void getInferenceStatus()
|
||||
.then((status) => {
|
||||
if (cancelled || !status.active_model) return;
|
||||
const store = useChatRuntimeStore.getState();
|
||||
if (
|
||||
!isExternalModelId(store.params.checkpoint) &&
|
||||
(!modelIdsMatch(store.params.checkpoint, status.active_model) ||
|
||||
!ggufVariantsMatch(
|
||||
store.activeGgufVariant,
|
||||
status.gguf_variant ?? null,
|
||||
))
|
||||
) {
|
||||
store.setCheckpoint(status.active_model, status.gguf_variant ?? null);
|
||||
}
|
||||
})
|
||||
.catch(() => undefined);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { tab, setTab: setModelsTab } = useModelsTabState();
|
||||
const [query, setQuery] = useState("");
|
||||
|
|
@ -522,8 +548,8 @@ export function ModelsPage() {
|
|||
);
|
||||
|
||||
const isLoadingThisModel = useMemo(() => {
|
||||
if (!loadingModel) return false;
|
||||
return selectedRepoMatchesRuntime(selectedModel, loadingModel.id, null);
|
||||
if (!loadingModel || !selectedModel) return false;
|
||||
return modelIdsMatch(loadingModel.id, selectedModel.resource.runId);
|
||||
}, [loadingModel, selectedModel]);
|
||||
|
||||
const { vramInfo, minMemory } = useHubModelVram(selectedModel, gpu);
|
||||
|
|
@ -536,22 +562,41 @@ export function ModelsPage() {
|
|||
? `${Math.floor(gpu.systemRamAvailableGb)} GB`
|
||||
: "Unavailable";
|
||||
|
||||
// Load / use-in-chat / train handlers are stubs; they wire in once the
|
||||
// Hub-aware chat and train pickers ship.
|
||||
const openNewChat = useCallback(() => {
|
||||
void navigate({ to: "/chat", search: { new: crypto.randomUUID() } });
|
||||
}, [navigate]);
|
||||
const runSelectedModel = useCallback(
|
||||
(opts: ModelLoadOptions, isDownloaded: boolean) => {
|
||||
if (!selectedModel) return;
|
||||
const runId = selectedModel.resource.runId;
|
||||
void selectModel({
|
||||
id: runId,
|
||||
ggufVariant: opts.ggufVariant,
|
||||
isDownloaded,
|
||||
expectedBytes: opts.expectedBytes,
|
||||
throwOnError: true,
|
||||
})
|
||||
.then(() => {
|
||||
const store = useChatRuntimeStore.getState();
|
||||
if (!modelIdsMatch(store.params.checkpoint, runId)) {
|
||||
store.setCheckpoint(runId, opts.ggufVariant ?? null);
|
||||
}
|
||||
})
|
||||
.catch(() => undefined);
|
||||
openNewChat();
|
||||
},
|
||||
[openNewChat, selectModel, selectedModel],
|
||||
);
|
||||
const handleLoad = useCallback(
|
||||
(_opts: ModelLoadOptions) => undefined,
|
||||
[],
|
||||
(opts: ModelLoadOptions) =>
|
||||
runSelectedModel(opts, selectedModel?.isDownloaded ?? true),
|
||||
[runSelectedModel, selectedModel],
|
||||
);
|
||||
const handleLoadLocal = useCallback(
|
||||
(_opts: ModelLoadOptions = {}) => undefined,
|
||||
[],
|
||||
(opts: ModelLoadOptions = {}) => runSelectedModel(opts, true),
|
||||
[runSelectedModel],
|
||||
);
|
||||
const handleUseInChat = useCallback(() => {
|
||||
void navigate({ to: "/chat" });
|
||||
}, [navigate]);
|
||||
const handleTrain = useCallback(() => {
|
||||
// Train CTA hidden until Hub->train picker ships.
|
||||
}, []);
|
||||
const handleTrain = useCallback(() => undefined, []);
|
||||
|
||||
const inspectorRuntime = useMemo(
|
||||
() => ({
|
||||
|
|
@ -582,14 +627,14 @@ export function ModelsPage() {
|
|||
() => ({
|
||||
onLoad: handleLoad,
|
||||
onLoadLocal: handleLoadLocal,
|
||||
onUseInChat: handleUseInChat,
|
||||
onUseInChat: openNewChat,
|
||||
onTrain: handleTrain,
|
||||
onInventoryChange: refreshInventory,
|
||||
}),
|
||||
[
|
||||
handleLoad,
|
||||
handleLoadLocal,
|
||||
handleUseInChat,
|
||||
openNewChat,
|
||||
handleTrain,
|
||||
refreshInventory,
|
||||
],
|
||||
|
|
@ -685,7 +730,7 @@ export function ModelsPage() {
|
|||
ramLabel={ramLabel}
|
||||
activeCheckpoint={activeCheckpoint}
|
||||
activeGgufVariant={activeGgufVariant}
|
||||
onEject={() => undefined}
|
||||
onEject={() => void ejectModel()}
|
||||
/>
|
||||
|
||||
<section className="elevated-card flex min-h-0 flex-1 flex-col overflow-hidden bg-card">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
|
||||
// Hub feature flags for staged rollout. Each gates a CTA whose integration ships
|
||||
// in a later PR; JSX/wiring stay in place so flipping here is the only re-enable edit.
|
||||
|
||||
// Post-download Run / chat / Train CTAs, hidden until the Hub-aware chat and train pickers ship.
|
||||
export const HUB_POST_DOWNLOAD_ACTIONS_VISIBLE = false;
|
||||
export const HUB_GGUF_RUN_ACTIONS_VISIBLE = true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue