feat: add usage-stats display mode to In-Flight Monitor action

This commit is contained in:
c4ch3c4d3
2026-08-14 12:56:45 -06:00
parent 4c4ec0e8fd
commit e585992087
4 changed files with 68 additions and 8 deletions
@@ -11,6 +11,20 @@
<sdpi-item label="API Key"> <sdpi-item label="API Key">
<sdpi-password setting="apiKey" placeholder="Optional" /> <sdpi-password setting="apiKey" placeholder="Optional" />
</sdpi-item> </sdpi-item>
<sdpi-item label="Display">
<sdpi-select setting="display" default="count" placeholder="Select a display mode">
<option value="count">Request count + activity</option>
<option value="usage">Usage stats</option>
</sdpi-select>
</sdpi-item>
<sdpi-item label="Primary stat (usage view)">
<sdpi-select setting="primaryStat" default="requests" placeholder="Select a primary stat">
<option value="requests">Requests</option>
<option value="input_tokens">Processed tokens</option>
<option value="output_tokens">Generated tokens</option>
<option value="gen_p95">Generation speed P95</option>
</sdpi-select>
</sdpi-item>
<sdpi-item label="Model"> <sdpi-item label="Model">
<sdpi-select setting="modelId" datasource="models" loading="Loading models…" hot-reload placeholder="Select a model" /> <sdpi-select setting="modelId" datasource="models" loading="Loading models…" hot-reload placeholder="Select a model" />
</sdpi-item> </sdpi-item>
+51 -5
View File
@@ -7,18 +7,21 @@ import streamDeck, {
type WillAppearEvent, type WillAppearEvent,
type WillDisappearEvent, type WillDisappearEvent,
} from "@elgato/streamdeck"; } from "@elgato/streamdeck";
import { renderInflight, svgDataUrl } from "../lib/render"; import { renderInflight, renderUsage, svgDataUrl } from "../lib/render";
import { runtime } from "../lib/runtime"; import { runtime } from "../lib/runtime";
import { cfgFromSettings, type CfgSettings } from "../lib/util"; import { cfgFromSettings, type CfgSettings } from "../lib/util";
type InflightSettings = CfgSettings & { type InflightSettings = CfgSettings & {
modelId?: string; modelId?: string;
display?: "count" | "usage";
primaryStat?: "requests" | "input_tokens" | "output_tokens" | "gen_p95";
}; };
type InflightState = { type InflightState = {
settings: InflightSettings; settings: InflightSettings;
action?: KeyAction<InflightSettings>; action?: KeyAction<InflightSettings>;
unsubscribe?: () => void; unsubscribe?: () => void;
unwatchStats?: () => void;
history: number[]; history: number[];
sampler?: ReturnType<typeof setInterval>; sampler?: ReturnType<typeof setInterval>;
}; };
@@ -45,7 +48,11 @@ export class InflightMonitor extends SingletonAction<InflightSettings> {
state.action = ev.action; state.action = ev.action;
runtime.ensureConnections(cfgFromSettings(state.settings)); runtime.ensureConnections(cfgFromSettings(state.settings));
state.unsubscribe = runtime.subscribe(() => this.render(state)); state.unsubscribe = runtime.subscribe(() => this.render(state));
if (this.displayOf(state) === "usage") {
state.unwatchStats = runtime.watchStats(this.modelKeyOf(state));
} else {
state.sampler = setInterval(() => this.sample(state), 1000); state.sampler = setInterval(() => this.sample(state), 1000);
}
this.render(state); this.render(state);
} }
@@ -53,6 +60,8 @@ export class InflightMonitor extends SingletonAction<InflightSettings> {
const state = this.states.get(ev.action.id); const state = this.states.get(ev.action.id);
if (!state) return; if (!state) return;
state.unsubscribe?.(); state.unsubscribe?.();
state.unwatchStats?.();
state.unwatchStats = undefined;
if (state.sampler) clearInterval(state.sampler); if (state.sampler) clearInterval(state.sampler);
state.sampler = undefined; state.sampler = undefined;
this.states.delete(ev.action.id); this.states.delete(ev.action.id);
@@ -65,6 +74,13 @@ export class InflightMonitor extends SingletonAction<InflightSettings> {
state.action = ev.action; state.action = ev.action;
state.history = []; state.history = [];
runtime.ensureConnections(cfgFromSettings(state.settings)); runtime.ensureConnections(cfgFromSettings(state.settings));
state.unwatchStats?.();
state.unwatchStats = undefined;
if (this.displayOf(state) === "usage") {
state.unwatchStats = runtime.watchStats(this.modelKeyOf(state));
} else if (!state.sampler) {
state.sampler = setInterval(() => this.sample(state), 1000);
}
this.render(state); this.render(state);
} }
@@ -72,17 +88,47 @@ export class InflightMonitor extends SingletonAction<InflightSettings> {
void streamDeck.system.openUrl(`${cfgFromSettings(ev.payload.settings).baseUrl}/ui`); void streamDeck.system.openUrl(`${cfgFromSettings(ev.payload.settings).baseUrl}/ui`);
} }
private displayOf(state: InflightState): "count" | "usage" {
return state.settings.display ?? "count";
}
private modelKeyOf(state: InflightState): string {
const modelId = state.settings.modelId ?? "";
return modelId === "" ? "all" : modelId;
}
private primaryStatOf(state: InflightState): "requests" | "input_tokens" | "output_tokens" | "gen_p95" {
return state.settings.primaryStat ?? "requests";
}
private render(state: InflightState): void { private render(state: InflightState): void {
const action = state.action; const action = state.action;
if (!action) return; if (!action) return;
if (this.displayOf(state) === "usage") {
const modelKey = this.modelKeyOf(state);
const offline = runtime.offline;
void action.setImage(
svgDataUrl(
renderUsage({
modelName: modelKey,
stats: runtime.getStats(modelKey),
primaryStat: this.primaryStatOf(state),
offline,
}),
),
);
return;
}
const modelId = state.settings.modelId ?? ""; const modelId = state.settings.modelId ?? "";
const trackerState = runtime.tracker.state(modelId); const trackerState = modelId === "all" ? "ready" : runtime.tracker.state(modelId);
const count = runtime.tracker.count(modelId); const count = modelId === "all" ? runtime.tracker.total() : runtime.tracker.count(modelId);
const offline = runtime.offline && modelId.length > 0; const offline = runtime.offline && modelId.length > 0;
const modelName = modelId === "" ? "unset" : modelId;
void action.setImage( void action.setImage(
svgDataUrl( svgDataUrl(
renderInflight({ renderInflight({
modelName: modelId.length > 0 ? modelId : "unset", modelName,
state: trackerState, state: trackerState,
count, count,
offline, offline,
@@ -94,7 +140,7 @@ export class InflightMonitor extends SingletonAction<InflightSettings> {
private sample(state: InflightState): void { private sample(state: InflightState): void {
const modelId = state.settings.modelId ?? ""; const modelId = state.settings.modelId ?? "";
state.history.push(runtime.tracker.count(modelId)); state.history.push(modelId === "all" ? runtime.tracker.total() : runtime.tracker.count(modelId));
if (state.history.length > HISTORY_LIMIT) state.history.shift(); if (state.history.length > HISTORY_LIMIT) state.history.shift();
this.render(state); this.render(state);
} }
+1 -1
View File
@@ -17,7 +17,7 @@ export function registerDataSources(): void {
let items: DataSourceItem[] = []; let items: DataSourceItem[] = [];
try { try {
const models = await fetchModels(cfgFromSettings(settings)); const models = await fetchModels(cfgFromSettings(settings));
items = models.map((m) => ({ label: `${m.id} (${m.status})`, value: m.id })); items = [{ label: "All models", value: "all" }, ...models.map((m) => ({ label: `${m.id} (${m.status})`, value: m.id }))];
} catch { } catch {
items = []; items = [];
} }
+1 -1
View File
@@ -24,7 +24,7 @@ export interface InflightRenderOptions {
} }
export function renderInflight(opts: InflightRenderOptions): string { export function renderInflight(opts: InflightRenderOptions): string {
const name = escapeXml(shorten(opts.modelName)); const name = escapeXml(shorten(opts.modelName === "all" ? "ALL MODELS" : opts.modelName));
if (opts.offline) { if (opts.offline) {
return frame("#10131a", [ return frame("#10131a", [