import { useQueryClient } from "@tanstack/react-query"; import { Button } from "@unom/ui/button"; import type { FC } from "react"; import { getListGpusQueryKey, useListGpus, useSetGpuPreference, } from "@/api/gen/gpu/gpu"; import type { GpuState } from "@/api/gen/model"; import { QueryState } from "@/components/query-state"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import type { Loadable } from "@/lib/query"; import { m } from "@/paraglide/messages"; /** * Container: the host's GPU inventory + selection. Polls (a stream starting/stopping moves the * "In use" badge; an eGPU can appear) and applies auto/preferred choices via the mgmt API. A * preference applies to the NEXT session — the help text says so. */ export const GpuSection: FC = () => { const qc = useQueryClient(); const gpus = useListGpus({ query: { refetchInterval: 5_000 } }); const setPref = useSetGpuPreference(); const apply = (mode: "auto" | "manual", gpuId?: string) => setPref.mutate( { data: { mode, gpu_id: gpuId ?? null } }, { onSuccess: () => qc.invalidateQueries({ queryKey: getListGpusQueryKey() }), }, ); return ; }; const fmtVram = (mb: number) => mb >= 1024 ? `${Math.round(mb / 1024)} GiB` : `${mb} MiB`; /** * The vendor an explicit `PUNKTFUNK_ENCODER` pin can open on (display name) — the console mirror * of the host's backend→vendor table. Vendor-agnostic pins (software) and unknown/multi-vendor * spellings (vaapi, vulkan, pyrowave) map to nothing: no conflict to warn about. */ const encoderPinVendor: Record = { nvenc: "NVIDIA", nvidia: "NVIDIA", cuda: "NVIDIA", hw: "NVIDIA", amf: "AMD", amd: "AMD", qsv: "Intel", intel: "Intel", }; /** * The host.env encoder pin, surfaced so a conflicting GPU choice doesn't just look broken: amber * when the pin's vendor contradicts the next session's GPU (the host overrides the pin at session * open — the stale pin should be removed), a muted note otherwise. */ const EncoderPinNote: FC<{ state: GpuState; pin: string }> = ({ state, pin, }) => { const vendor = encoderPinVendor[pin]; const conflicting = vendor && state.selected && state.selected.vendor !== vendor.toLowerCase(); return conflicting && state.selected ? (

{m.gpu_encoder_pin_warning({ value: pin, vendor, name: state.selected.name, })}

) : (

{m.gpu_encoder_pin_note({ value: pin })}

); }; /** * GPU list in the compositors-card style: per-GPU badges for the manual pick ("Preferred"), what * the next session will use ("Next session"), and what live sessions encode on right now * ("In use · NVENC"), plus an Automatic/Prefer control pair. */ export const GpuCard: FC<{ state: Loadable; onApply: (mode: "auto" | "manual", gpuId?: string) => void; busy: boolean; }> = ({ state, onApply, busy }) => { const s = state.data; return ( {m.host_gpus()} {s && s.gpus.length > 0 && ( )}

{m.host_gpus_help()}

{s && (s.gpus.length === 0 ? (

{m.gpu_none()}

) : (
    {s.gpus.map((g) => { const isActive = s.active?.id === g.id; const isSelected = s.selected?.id === g.id; const isPreferred = s.mode === "manual" && s.preferred_id === g.id; return (
  • {g.name} {isPreferred && ( {m.gpu_preferred()} )} {isActive && s.active ? ( {m.gpu_in_use({ backend: s.active.backend.toUpperCase(), })} ) : ( isSelected && ( {m.gpu_next_session()} ) )}
    {g.vendor} {g.vram_mb > 0 ? ` · ${fmtVram(g.vram_mb)}` : ""} {` · ${g.id}`}
  • ); })}
))} {s?.selected?.source === "preference_missing" && (

{m.gpu_missing_warning({ name: s.preferred_name ?? "?" })}

)} {s?.env_override && s.mode === "auto" && (

{m.gpu_env_note({ value: s.env_override })}

)} {s?.encoder_pin && }
); };