feat: add fan curve control via temperature-based fan speed curves

- Backend HAL (hal/fans.py): NVML v2 fan read/set/reset with min/max queries
- Server endpoints: GET/POST /api/fans, POST /api/fans/reset, POST /api/fans/speed
- Background fan poller: reads GPU temp every 2s, interpolates fan speed from curve
- Profile integration: fan_curve field saved/applied, auto-restore on shutdown
- Frontend: FanCurveEditor (SVG chart with drag/add/delete points), FanMonitor sidebar
- App.tsx: three-tab layout (Curve, Performance, Fans)
- GaugeCard: optional history sparkline, Fan Mode card without sparkline
- fan_mode field populated as 'curve' or 'auto' in GET /api/fans
This commit is contained in:
ARIA committed 2026-08-01 15:59:39 +02:00
1 parent 7605d21e27
commit 477c4becae
11 files changed
+1288 -10

No files matched your search

+9 -1
View File
@@ -1,4 +1,4 @@
import type { CurveState, GpuInfo, MonitoringSample, SnapshotInfo, LimitsState, ProfileData } from '../types';
import type { CurveState, GpuInfo, MonitoringSample, SnapshotInfo, LimitsState, ProfileData, FanState, FanPoint } from '../types';
async function get<T>(path: string, gpuIndex?: number): Promise<T> {
const url = gpuIndex !== undefined ? `/api${path}?gpu_index=${gpuIndex}` : `/api${path}`;
@@ -76,4 +76,12 @@ export const api = {
/** Server config */
setAutoLoadProfile: (name: string | null, gpuIndex: number) =>
post<{ ok: boolean; auto_load_profile: string | null }>('/config', { auto_load_profile: name, gpu_index: gpuIndex }),
/** Fan control */
fans: (gpuIndex: number) => get<FanState>('/fans', gpuIndex),
updateFans: (curve: FanPoint[], gpuIndex: number) =>
post('/fans', { curve }, gpuIndex),
resetFans: (gpuIndex: number) => post('/fans/reset', undefined, gpuIndex),
setFanSpeed: (fanPct: number, gpuIndex: number) =>
post('/fans/speed', { fan_pct: fanPct }, gpuIndex),
};