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-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>
|
||||||
|
|||||||
@@ -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));
|
||||||
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);
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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", [
|
||||||
|
|||||||
Reference in New Issue
Block a user