import { useState, useEffect } from "react"; import { Check, X, RotateCcw } from "lucide-react"; import { api } from "../../api/client.js"; import { useCurveStore } from "../../store/curveStore.js"; import type { LimitsState } from "../../types.js"; import { toast } from "sonner"; import { ConfirmDialog } from "../common/ConfirmDialog.js"; type Pending = Pick; export function PerformancePanel() { const { selectedGpuIndex } = useCurveStore(); const [limits, setLimits] = useState(null); const [pending, setPending] = useState>({}); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [confirmApply, setConfirmApply] = useState(false); const [confirmReset, setConfirmReset] = useState(false); async function fetchLimits() { try { setLoading(true); setLimits(await api.limits(selectedGpuIndex)); } catch { toast.error("Failed to load performance limits"); } finally { setLoading(false); } } // Data fetch on GPU change — setState calls happen after the await, not // synchronously in the effect body (rule false-positive on async fetch). useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect fetchLimits(); }, [selectedGpuIndex]); async function handleApply() { setBusy(true); setError(null); try { await api.updateLimits(pending, selectedGpuIndex); useCurveStore.getState().setActiveProfile(null); setPending({}); setConfirmApply(false); await fetchLimits(); toast.success("Performance limits applied"); } catch (e: unknown) { setError(e instanceof Error ? e.message : String(e)); setConfirmApply(false); } finally { setBusy(false); } } async function handleReset() { setBusy(true); setError(null); try { await api.resetLimits(selectedGpuIndex); useCurveStore.getState().setActiveProfile(null); setPending({}); setConfirmReset(false); await fetchLimits(); toast.success("Performance limits reset to defaults"); } catch (e: unknown) { setError(e instanceof Error ? e.message : String(e)); setConfirmReset(false); } finally { setBusy(false); } } async function handleModeChange(enabled: boolean) { setBusy(true); try { await api.updateLimits( { power_cap_mode: enabled ? "ioctl" : "nvml" }, selectedGpuIndex, ); await fetchLimits(); } catch (e: unknown) { toast.error(e instanceof Error ? e.message : String(e)); } finally { setBusy(false); } } if (loading && !limits) { return (
); } if (!limits) return null; const hasPending = Object.keys(pending).length > 0; // Power const pwrVal = pending.power_limit_w ?? limits.power_limit_w ?? 0; const pwrMin = limits.min_power_limit_w ?? 100; const pwrMax = limits.max_power_limit_w ?? 600; // Memory offset const memVal = pending.mem_offset_mhz ?? limits.mem_offset_mhz ?? 0; const memMin = limits.min_mem_offset_mhz ?? -2000; const memMax = Math.min(limits.max_mem_offset_mhz ?? 3000, 3000); return ( <>
{/* ── Header ─────────────────────────────────────────────────────── */}
Performance {hasPending && ( {Object.keys(pending).length} pending )}
{/* ── Error banner ────────────────────────────────────────────────── */} {error && (
⚠ {error}
)}
{/* ── Experimental NVIDIA power control ─────────────────────────── */} {(limits.rm_power_supported || limits.power_cap_mode === "ioctl") && (
{limits.rm_power_supported ? (
⚠ WARNING: uses an undocumented driver interface for ALL power limits, including resets. Allows values below the VBIOS minimum (down to 30 W) and may cause instability or stop working after driver updates. Enable at your own risk. Based on the work of{" "} panchovix {" "} (LACT PR #1205).
) : (
⚠ Interface not currently available. The driver no longer exposes the RM power interface (it may have been updated). Experimental mode is still enabled, so power-limit changes will fail. Uncheck to switch back to the standard NVML mode.
)}
)} {/* ── Board Power Limit ─────────────────────────────────────────── */}
Board Power Limit {limits.power_cap_mode === "ioctl" && limits.min_power_limit_w_native != null && ( VBIOS min {limits.min_power_limit_w_native} W )}
{ const v = parseInt(e.target.value); if (!isNaN(v)) setPending((p) => ({ ...p, power_limit_w: v })); }} className="w-14 bg-zinc-950 border border-zinc-800 rounded text-xs px-2 py-1 text-right font-mono focus:outline-none focus:border-cyan-500" /> W
{pwrMin} setPending((p) => ({ ...p, power_limit_w: parseInt(e.target.value), })) } className="flex-1 accent-cyan-400 h-1 cursor-pointer" /> {pwrMax}
{/* ── Memory Clock Offset ───────────────────────────────────────── */}
Memory Clock Offset
{ const v = parseInt(e.target.value); if (!isNaN(v)) setPending((p) => ({ ...p, mem_offset_mhz: v })); }} className="w-16 bg-zinc-950 border border-zinc-800 rounded text-xs px-2 py-1 text-right font-mono focus:outline-none focus:border-cyan-500" /> MHz
{memMin} setPending((p) => ({ ...p, mem_offset_mhz: parseInt(e.target.value), })) } className="flex-1 accent-cyan-400 h-1 cursor-pointer" /> +{memMax}
{confirmApply && ( setConfirmApply(false)} /> )} {confirmReset && ( setConfirmReset(false)} /> )} ); }