feat: add usage-stats display mode to In-Flight Monitor action
This commit is contained in:
@@ -11,6 +11,20 @@
|
||||
<sdpi-item label="API Key">
|
||||
<sdpi-password setting="apiKey" placeholder="Optional" />
|
||||
</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-select setting="modelId" datasource="models" loading="Loading models…" hot-reload placeholder="Select a model" />
|
||||
</sdpi-item>
|
||||
|
||||
@@ -7,18 +7,21 @@ import streamDeck, {
|
||||
type WillAppearEvent,
|
||||
type WillDisappearEvent,
|
||||
} from "@elgato/streamdeck";
|
||||
import { renderInflight, svgDataUrl } from "../lib/render";
|
||||
import { renderInflight, renderUsage, svgDataUrl } from "../lib/render";
|
||||
import { runtime } from "../lib/runtime";
|
||||
import { cfgFromSettings, type CfgSettings } from "../lib/util";
|
||||
|
||||
type InflightSettings = CfgSettings & {
|
||||
modelId?: string;
|
||||
display?: "count" | "usage";
|
||||
primaryStat?: "requests" | "input_tokens" | "output_tokens" | "gen_p95";
|
||||
};
|
||||
|
||||
type InflightState = {
|
||||
settings: InflightSettings;
|
||||
action?: KeyAction<InflightSettings>;
|
||||
unsubscribe?: () => void;
|
||||
unwatchStats?: () => void;
|
||||
history: number[];
|
||||
sampler?: ReturnType<typeof setInterval>;
|
||||
};
|
||||
@@ -45,7 +48,11 @@ export class InflightMonitor extends SingletonAction<InflightSettings> {
|
||||
state.action = ev.action;
|
||||
runtime.ensureConnections(cfgFromSettings(state.settings));
|
||||
state.unsubscribe = runtime.subscribe(() => this.render(state));
|
||||
state.sampler = setInterval(() => this.sample(state), 1000);
|
||||
if (this.displayOf(state) === "usage") {
|
||||
state.unwatchStats = runtime.watchStats(this.modelKeyOf(state));
|
||||
} else {
|
||||
state.sampler = setInterval(() => this.sample(state), 1000);
|
||||
}
|
||||
this.render(state);
|
||||
}
|
||||
|
||||
@@ -53,6 +60,8 @@ export class InflightMonitor extends SingletonAction<InflightSettings> {
|
||||
const state = this.states.get(ev.action.id);
|
||||
if (!state) return;
|
||||
state.unsubscribe?.();
|
||||
state.unwatchStats?.();
|
||||
state.unwatchStats = undefined;
|
||||
if (state.sampler) clearInterval(state.sampler);
|
||||
state.sampler = undefined;
|
||||
this.states.delete(ev.action.id);
|
||||
@@ -65,6 +74,13 @@ export class InflightMonitor extends SingletonAction<InflightSettings> {
|
||||
state.action = ev.action;
|
||||
state.history = [];
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -72,17 +88,47 @@ export class InflightMonitor extends SingletonAction<InflightSettings> {
|
||||
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 {
|
||||
const action = state.action;
|
||||
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 trackerState = runtime.tracker.state(modelId);
|
||||
const count = runtime.tracker.count(modelId);
|
||||
const trackerState = modelId === "all" ? "ready" : runtime.tracker.state(modelId);
|
||||
const count = modelId === "all" ? runtime.tracker.total() : runtime.tracker.count(modelId);
|
||||
const offline = runtime.offline && modelId.length > 0;
|
||||
const modelName = modelId === "" ? "unset" : modelId;
|
||||
void action.setImage(
|
||||
svgDataUrl(
|
||||
renderInflight({
|
||||
modelName: modelId.length > 0 ? modelId : "unset",
|
||||
modelName,
|
||||
state: trackerState,
|
||||
count,
|
||||
offline,
|
||||
@@ -94,7 +140,7 @@ export class InflightMonitor extends SingletonAction<InflightSettings> {
|
||||
|
||||
private sample(state: InflightState): void {
|
||||
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();
|
||||
this.render(state);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export function registerDataSources(): void {
|
||||
let items: DataSourceItem[] = [];
|
||||
try {
|
||||
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 {
|
||||
items = [];
|
||||
}
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ export interface InflightRenderOptions {
|
||||
}
|
||||
|
||||
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) {
|
||||
return frame("#10131a", [
|
||||
|
||||
Reference in New Issue
Block a user