diff --git a/studio/frontend/src/features/onboarding/components/steps/summary-step.tsx b/studio/frontend/src/features/onboarding/components/steps/summary-step.tsx index da2758ebfa..5ece98b6e6 100644 --- a/studio/frontend/src/features/onboarding/components/steps/summary-step.tsx +++ b/studio/frontend/src/features/onboarding/components/steps/summary-step.tsx @@ -2,20 +2,14 @@ import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { useTrainingConfigStore } from "@/features/training"; +import { useHardwareInfo } from "@/hooks"; import { isAdapterMethod } from "@/types/training"; import { GpuIcon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; import { useShallow } from "zustand/react/shallow"; -const SYSTEM_INFO = { - gpu: "NVIDIA RTX 4090", - vram: "24 GB", - pytorch: "2.5.1+cu124", - cuda: "12.4", - transformers: "4.47.1", -}; - export function SummaryStep() { + const hw = useHardwareInfo(); const { modelType, selectedModel, @@ -88,24 +82,24 @@ export function SummaryStep() {
GPU - {SYSTEM_INFO.gpu} + {hw.gpuName ?? "—"}
- {SYSTEM_INFO.vram} + {hw.vramTotalGb != null ? `${hw.vramTotalGb} GB` : "—"}
- torch - {SYSTEM_INFO.pytorch} + unsloth + {hw.unsloth ?? "—"}
- cuda - {SYSTEM_INFO.cuda} + torch + {hw.torch ?? "—"}
transformers - {SYSTEM_INFO.transformers} + {hw.transformers ?? "—"}
diff --git a/studio/frontend/src/hooks/index.ts b/studio/frontend/src/hooks/index.ts index da0d595d9e..16c2c82b75 100644 --- a/studio/frontend/src/hooks/index.ts +++ b/studio/frontend/src/hooks/index.ts @@ -1,6 +1,7 @@ export { useDebouncedValue } from "./use-debounced-value"; export { useGpuInfo } from "./use-gpu-info"; export { useGpuUtilization } from "./use-gpu-utilization"; +export { useHardwareInfo } from "./use-hardware-info"; export { useHfModelSearch } from "./use-hf-model-search"; export { useHfDatasetSearch } from "./use-hf-dataset-search"; export { useHfDatasetSplits } from "./use-hf-dataset-splits"; diff --git a/studio/frontend/src/hooks/use-hardware-info.ts b/studio/frontend/src/hooks/use-hardware-info.ts new file mode 100644 index 0000000000..150e03ad08 --- /dev/null +++ b/studio/frontend/src/hooks/use-hardware-info.ts @@ -0,0 +1,75 @@ +import { authFetch } from "@/features/auth"; +import { useEffect, useState } from "react"; + +export interface HardwareInfo { + gpuName: string | null; + vramTotalGb: number | null; + torch: string | null; + cuda: string | null; + transformers: string | null; + unsloth: string | null; +} + +const DEFAULT: HardwareInfo = { + gpuName: null, + vramTotalGb: null, + torch: null, + cuda: null, + transformers: null, + unsloth: null, +}; + +// Module-level cache so multiple components share one fetch. +let cached: HardwareInfo | null = null; +let fetchPromise: Promise | null = null; + +async function fetchOnce(): Promise { + if (cached) return cached; + if (fetchPromise) return fetchPromise; + + fetchPromise = (async () => { + try { + const res = await authFetch("/api/system/hardware"); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const data = await res.json(); + const info: HardwareInfo = { + gpuName: data?.gpu?.gpu_name ?? null, + vramTotalGb: data?.gpu?.vram_total_gb ?? null, + torch: data?.versions?.torch ?? null, + cuda: data?.versions?.cuda ?? null, + transformers: data?.versions?.transformers ?? null, + unsloth: data?.versions?.unsloth ?? null, + }; + cached = info; + return info; + } catch { + // Reset promise so subsequent calls retry (e.g. backend wasn't ready) + fetchPromise = null; + return DEFAULT; + } + })(); + + return fetchPromise; +} + +/** + * Fetch hardware info from `GET /api/system/hardware`. + * + * The result is cached at module level — only one network request is made + * regardless of how many components call this hook. + */ +export function useHardwareInfo(): HardwareInfo { + const [info, setInfo] = useState(cached ?? DEFAULT); + + useEffect(() => { + if (cached) return; + + let cancelled = false; + fetchOnce().then((hw) => { + if (!cancelled) setInfo(hw); + }); + return () => { cancelled = true; }; + }, []); + + return info; +}