diff --git a/ui/src/api/hardware.ts b/ui/src/api/hardware.ts new file mode 100644 index 00000000..4e459bb7 --- /dev/null +++ b/ui/src/api/hardware.ts @@ -0,0 +1,34 @@ +// [nexus] Hardware detection and nexus settings API client +import { api } from "./client"; + +export type HardwareTier = "gpu" | "apple_silicon" | "cpu_only"; + +export interface HardwareInfo { + totalGb: number; + freeGb: number; + usableGb: number; + platform: string; + gpuName: string | null; + gpuVramGb: number | null; + unifiedMemory: boolean; + hardwareTier: HardwareTier; + cpuModel: string | null; +} + +export type NexusMode = "personal_ai" | "project_builder" | "both"; + +export interface NexusSettings { + mode: NexusMode; +} + +export function fetchHardwareInfo(): Promise { + return api.get("/system/providers"); +} + +export function fetchNexusSettings(): Promise { + return api.get("/nexus/settings"); +} + +export function updateNexusSettings(settings: Partial): Promise { + return api.patch("/nexus/settings", settings); +} diff --git a/ui/src/components/onboarding/HardwareSummaryStep.tsx b/ui/src/components/onboarding/HardwareSummaryStep.tsx new file mode 100644 index 00000000..01e78c2f --- /dev/null +++ b/ui/src/components/onboarding/HardwareSummaryStep.tsx @@ -0,0 +1,95 @@ +// [nexus] Hardware summary display for onboarding wizard step 1 +import { Skeleton } from "@/components/ui/skeleton"; +import type { HardwareInfo } from "@/api/hardware"; + +interface HardwareSummaryStepProps { + hardwareInfo: HardwareInfo | undefined; + isLoading: boolean; + isError: boolean; +} + +interface StatRowProps { + label: string; + value: string | number | null | undefined; +} + +function StatRow({ label, value }: StatRowProps) { + return ( +
+ {label} + {value} +
+ ); +} + +export function HardwareSummaryStep({ hardwareInfo, isLoading, isError }: HardwareSummaryStepProps) { + if (isLoading) { + return ( +
+ + + +
+ ); + } + + if (isError) { + return ( +

+ Could not detect hardware. You can still continue. +

+ ); + } + + if (!hardwareInfo) { + return ( +

+ Could not detect hardware. You can still continue. +

+ ); + } + + const { hardwareTier } = hardwareInfo; + + return ( +
+
+ {hardwareTier === "apple_silicon" && ( + <> + + + + + )} + + {hardwareTier === "gpu" && ( + <> + + + + + )} + + {hardwareTier === "cpu_only" && ( + <> + + +

+ Slower than GPU-accelerated models -- cloud AI recommended +

+ + )} +
+ + {hardwareTier !== "cpu_only" && ( +
+ Local AI (recommended for privacy) + + Runs entirely on your machine.{"\n"} + No accounts. No tracking. Works offline. + +
+ )} +
+ ); +} diff --git a/ui/src/components/onboarding/ModeSelector.tsx b/ui/src/components/onboarding/ModeSelector.tsx new file mode 100644 index 00000000..009baed3 --- /dev/null +++ b/ui/src/components/onboarding/ModeSelector.tsx @@ -0,0 +1,49 @@ +// [nexus] Three-card mode selector for onboarding wizard step 2 +import { cn } from "@/lib/utils"; +import type { NexusMode } from "@/api/hardware"; + +interface ModeSelectorProps { + value: NexusMode; + onChange: (mode: NexusMode) => void; +} + +const MODES: { id: NexusMode; label: string; description: string }[] = [ + { + id: "personal_ai", + label: "Personal AI Assistant", + description: "Always available, persistent memory, private.", + }, + { + id: "project_builder", + label: "Project Builder", + description: "Brainstorm -> PM -> Engineer -> shipped product.", + }, + { + id: "both", + label: "Both (recommended)", + description: "A conversation becomes a project with one click.", + }, +]; + +export function ModeSelector({ value, onChange }: ModeSelectorProps) { + return ( +
+ {MODES.map((mode) => ( + + ))} +
+ ); +} diff --git a/ui/src/hooks/useHardwareInfo.ts b/ui/src/hooks/useHardwareInfo.ts new file mode 100644 index 00000000..9cc53107 --- /dev/null +++ b/ui/src/hooks/useHardwareInfo.ts @@ -0,0 +1,14 @@ +// [nexus] React Query hook for hardware detection data +import { useQuery } from "@tanstack/react-query"; +import { fetchHardwareInfo, type HardwareInfo } from "../api/hardware"; +import { queryKeys } from "../lib/queryKeys"; + +export function useHardwareInfo(enabled = true) { + return useQuery({ + queryKey: queryKeys.hardware.info, + queryFn: fetchHardwareInfo, + enabled, + staleTime: 5 * 60 * 1000, // 5 minutes — matches server cache TTL + retry: 1, + }); +} diff --git a/ui/src/lib/queryKeys.ts b/ui/src/lib/queryKeys.ts index df94182e..de5d3944 100644 --- a/ui/src/lib/queryKeys.ts +++ b/ui/src/lib/queryKeys.ts @@ -147,6 +147,9 @@ export const queryKeys = { agentSkills: (agentId: string) => ["skill-groups", "agent", agentId, "skills"] as const, agentEffective: (agentId: string) => ["skill-groups", "agent", agentId, "effective"] as const, }, + hardware: { + info: ["hardware", "info"] as const, + }, plugins: { all: ["plugins"] as const, examples: ["plugins", "examples"] as const,