Files
nvcurve/frontend/src/components/CurveEditor/CurveTooltip.tsx
T
ARIA e148c83622 feat: experimental NVIDIA power control via RM ioctl interface
Adds an experimental power-cap mode using the undocumented RM ioctl
interface (based on panchovix's LACT PR #1205) to set power limits
below the VBIOS minimum (down to 30 W).

- hal/rm_power.py: RM ioctl power-cap read/write/reset + runtime probe
- limits.py: power_cap_mode (nvml/ioctl) with support detection
- config.py: persist power_cap_mode per GPU
- profiles: record/apply power_cap_mode
- server.py: POST /api/limits validates ioctl support (409 on failure)
- cli.py: profile save falls back to persisted mode
- client.py: power_cap_mode in Limits
- frontend: toggle + warning with panchovix attribution (LACT #1205)
- tests: test_rm_power.py (unit) + integration coverage
- Makefile: add test_rm_power.py to make test

Also includes automated linter reformatting (prettier, ruff, shellcheck,
isort, markdownlint) that the linter would apply anyway.
2026-09-17 22:44:27 +02:00

84 lines
3.4 KiB
TypeScript

import { fmt } from "../../utils/units.js";
import type { VFPoint } from "../../types.js";
interface Props {
point: VFPoint;
/** Pending delta in kHz (from store), if any */
pendingDeltaKhz?: number;
/** True if this point is being held up by monotonicity enforcement */
isClamped?: boolean;
}
/**
* HTML tooltip rendered as an absolutely-positioned overlay on top of the
* SVG so it's never clipped by the SVG viewport.
*/
export function CurveTooltip({ point, pendingDeltaKhz, isClamped }: Props) {
const hasPending = pendingDeltaKhz !== undefined;
const pendingMhz = hasPending ? pendingDeltaKhz! / 1000 : 0;
const deltaChange = hasPending ? pendingDeltaKhz! - point.delta_khz : 0;
const pendingEffMhz = hasPending
? point.freq_mhz + deltaChange / 1000
: null;
return (
<div className="absolute right-4 bottom-4 pointer-events-none z-50 w-[172px] bg-zinc-800 border border-zinc-700 rounded-md p-2 text-xs shadow-xl">
<div className="text-zinc-400 mb-1">Point {point.index}</div>
<div className="text-zinc-200">
<span className="text-zinc-400">Volt: </span>
{fmt.mv(point.volt_mv, 1)}
</div>
<div className="text-zinc-200">
<span className="text-zinc-400">Offset: </span>
<span
className={
point.delta_khz > 0
? "text-emerald-400"
: point.delta_khz < 0
? "text-red-400"
: "text-zinc-400"
}
>
{point.delta_khz > 0 ? "+" : ""}
{fmt.mhz(point.delta_mhz, 1)}
</span>
</div>
<div className="text-emerald-300 font-semibold">
<span className="text-zinc-400">Eff.: </span>
{fmt.mhz(point.freq_mhz, 0)}
{isClamped && <span className="text-amber-500 ml-1">⇡</span>}
</div>
{isClamped && (
<div className="text-amber-500/80 text-[10px] mt-0.5">
Clamped by lower-voltage point
</div>
)}
{hasPending && (
<>
<div className="border-t border-zinc-700 mt-1.5 pt-1.5">
<div className="text-zinc-200">
<span className="text-zinc-400">Pending: </span>
<span
className={
pendingMhz > 0
? "text-cyan-400"
: pendingMhz < 0
? "text-orange-400"
: "text-zinc-400"
}
>
{pendingMhz > 0 ? "+" : ""}
{pendingMhz.toFixed(1)} MHz
</span>
</div>
<div className="text-cyan-300 font-semibold">
<span className="text-zinc-400">→ Eff.: </span>
{fmt.mhz(pendingEffMhz, 0)}
</div>
</div>
</>
)}
</div>
);
}