import streamDeck, { action, type DidReceiveSettingsEvent, type KeyAction, type KeyDownEvent, SingletonAction, type WillAppearEvent, type WillDisappearEvent, } from "@elgato/streamdeck"; import { type GpuMetricKind } from "../lib/metrics-parser"; import { renderGpuGraph, svgDataUrl } from "../lib/render"; import { runtime } from "../lib/runtime"; import { cfgFromSettings, type CfgSettings } from "../lib/util"; type GpuSettings = CfgSettings & { gpuId?: string; metric?: GpuMetricKind; }; type GpuState = { settings: GpuSettings; action?: KeyAction; unsubscribe?: () => void; lastSvg?: string; }; @action({ UUID: "com.bryce.llamawatch.gpu" }) export class GpuGraph extends SingletonAction { private states = new Map(); private stateFor(action: KeyAction): GpuState { let state = this.states.get(action.id); if (!state) { state = { settings: {} }; this.states.set(action.id, state); } return state; } override onWillAppear(ev: WillAppearEvent): void { if (!ev.action.isKey()) return; const state = this.stateFor(ev.action); state.settings = ev.payload.settings; state.action = ev.action; runtime.ensureConnections(cfgFromSettings(state.settings)); state.unsubscribe = runtime.subscribe(() => this.render(state)); this.render(state); } override onWillDisappear(ev: WillDisappearEvent): void { const state = this.states.get(ev.action.id); if (!state) return; state.unsubscribe?.(); this.states.delete(ev.action.id); } override onDidReceiveSettings(ev: DidReceiveSettingsEvent): void { if (!ev.action.isKey()) return; const state = this.stateFor(ev.action); state.settings = ev.payload.settings; state.action = ev.action; runtime.ensureConnections(cfgFromSettings(state.settings)); this.render(state); } override onKeyDown(ev: KeyDownEvent): void { void streamDeck.system.openUrl(`${cfgFromSettings(ev.payload.settings).baseUrl}/ui`); } private render(state: GpuState): void { const action = state.action; if (!action) return; const poller = runtime.pollerInstance; const gpuId = state.settings.gpuId ?? "all"; const metric = state.settings.metric ?? "util_percent"; const value = poller?.getValue(gpuId, metric); const history = poller?.getHistory(gpuId, metric) ?? []; const offline = !poller || poller.isOffline(); const gpuName = gpuId === "all" ? "ALL GPUS" : this.gpuLabel(gpuId); const svg = renderGpuGraph({ gpuName, metric, value, history, offline }); if (svg === state.lastSvg) return; state.lastSvg = svg; void action.setImage(svgDataUrl(svg)); } private gpuLabel(gpuId: string): string { const gpu = runtime.pollerInstance?.gpus().find((g) => g.id === gpuId); return gpu && gpu.name ? `GPU ${gpuId} ยท ${gpu.name}` : `GPU ${gpuId}`; } }