feat(onboarding): replace hardcoded system info with live /api/system/hardware data

This commit is contained in:
Roland Tannous 2026-02-16 12:26:49 +00:00
commit 883ae7ff8c
3 changed files with 85 additions and 15 deletions

View file

@ -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() {
</div>
<div className="flex flex-col flex-1">
<span className="text-xs text-muted-foreground">GPU</span>
<span className="text-sm font-medium">{SYSTEM_INFO.gpu}</span>
<span className="text-sm font-medium">{hw.gpuName ?? "—"}</span>
</div>
<Badge variant="secondary">{SYSTEM_INFO.vram}</Badge>
<Badge variant="secondary">{hw.vramTotalGb != null ? `${hw.vramTotalGb} GB` : "—"}</Badge>
</div>
<Separator />
<div className="space-y-1 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">torch</span>
<span className="font-mono text-xs">{SYSTEM_INFO.pytorch}</span>
<span className="text-muted-foreground">unsloth</span>
<span className="font-mono text-xs">{hw.unsloth ?? "—"}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">cuda</span>
<span className="font-mono text-xs">{SYSTEM_INFO.cuda}</span>
<span className="text-muted-foreground">torch</span>
<span className="font-mono text-xs">{hw.torch ?? "—"}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">transformers</span>
<span className="font-mono text-xs">
{SYSTEM_INFO.transformers}
{hw.transformers ?? "—"}
</span>
</div>
</div>

View file

@ -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";

View file

@ -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<HardwareInfo> | null = null;
async function fetchOnce(): Promise<HardwareInfo> {
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<HardwareInfo>(cached ?? DEFAULT);
useEffect(() => {
if (cached) return;
let cancelled = false;
fetchOnce().then((hw) => {
if (!cancelled) setInfo(hw);
});
return () => { cancelled = true; };
}, []);
return info;
}