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;
+}