feat: implement inflight monitor and GPU graph actions

This commit is contained in:
2026-08-14 10:49:50 -06:00
parent 325722f072
commit 2ff7d55739
2 changed files with 159 additions and 2 deletions
+72 -1
View File
@@ -1 +1,72 @@
export {};
import streamDeck, {
action,
type DidReceiveSettingsEvent,
type KeyAction,
type KeyDownEvent,
SingletonAction,
type WillAppearEvent,
} 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;
};
@action({ UUID: "com.bryce.llamawatch.gpu" })
export class GpuGraph extends SingletonAction<GpuSettings> {
private settings: GpuSettings = {};
private action?: KeyAction<GpuSettings>;
private unsubscribe?: () => void;
private lastSvg?: string;
override onWillAppear(ev: WillAppearEvent<GpuSettings>): void {
if (!ev.action.isKey()) return;
this.settings = ev.payload.settings;
this.action = ev.action;
runtime.ensureConnections(cfgFromSettings(this.settings));
this.unsubscribe = runtime.subscribe(() => this.render());
this.render();
}
override onWillDisappear(): void {
this.unsubscribe?.();
this.unsubscribe = undefined;
this.action = undefined;
}
override onDidReceiveSettings(ev: DidReceiveSettingsEvent<GpuSettings>): void {
if (!ev.action.isKey()) return;
this.settings = ev.payload.settings;
this.action = ev.action;
this.render();
}
override onKeyDown(ev: KeyDownEvent<GpuSettings>): void {
void streamDeck.system.openUrl(`${cfgFromSettings(ev.payload.settings).baseUrl}/ui`);
}
private render(): void {
const action = this.action;
if (!action) return;
const poller = runtime.pollerInstance;
const gpuId = this.settings.gpuId ?? "all";
const metric = this.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 === this.lastSvg) return;
this.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}`;
}
}
+87 -1
View File
@@ -1 +1,87 @@
export {};
import streamDeck, {
action,
type DidReceiveSettingsEvent,
type KeyAction,
type KeyDownEvent,
SingletonAction,
type WillAppearEvent,
} from "@elgato/streamdeck";
import { renderInflight, svgDataUrl } from "../lib/render";
import { runtime } from "../lib/runtime";
import { cfgFromSettings, type CfgSettings } from "../lib/util";
type InflightSettings = CfgSettings & {
modelId?: string;
};
@action({ UUID: "com.bryce.llamawatch.inflight" })
export class InflightMonitor extends SingletonAction<InflightSettings> {
private settings: InflightSettings = {};
private action?: KeyAction<InflightSettings>;
private unsubscribe?: () => void;
private pulseTimer?: ReturnType<typeof setInterval>;
private pulse = false;
override onWillAppear(ev: WillAppearEvent<InflightSettings>): void {
if (!ev.action.isKey()) return;
this.settings = ev.payload.settings;
this.action = ev.action;
runtime.ensureConnections(cfgFromSettings(this.settings));
this.unsubscribe = runtime.subscribe(() => this.render());
this.render();
}
override onWillDisappear(): void {
this.unsubscribe?.();
this.unsubscribe = undefined;
this.action = undefined;
this.clearPulse();
}
override onDidReceiveSettings(ev: DidReceiveSettingsEvent<InflightSettings>): void {
if (!ev.action.isKey()) return;
this.settings = ev.payload.settings;
this.action = ev.action;
runtime.ensureConnections(cfgFromSettings(this.settings));
this.render();
}
override onKeyDown(ev: KeyDownEvent<InflightSettings>): void {
void streamDeck.system.openUrl(`${cfgFromSettings(ev.payload.settings).baseUrl}/ui`);
}
private render(): void {
const action = this.action;
if (!action) return;
const modelId = this.settings.modelId ?? "";
const state = runtime.tracker.state(modelId);
const count = runtime.tracker.count(modelId);
const offline = runtime.offline || modelId.length === 0;
void action.setImage(
svgDataUrl(
renderInflight({
modelName: modelId.length > 0 ? modelId : "unset",
state,
count,
offline,
pulse: this.pulse,
}),
),
);
if (state === "ready" && count > 0 && !this.pulseTimer) {
this.pulseTimer = setInterval(() => {
this.pulse = !this.pulse;
this.render();
}, 500);
} else if (!(state === "ready" && count > 0) && this.pulseTimer) {
this.clearPulse();
}
}
private clearPulse(): void {
if (this.pulseTimer) clearInterval(this.pulseTimer);
this.pulseTimer = undefined;
this.pulse = false;
}
}