feat: add Reset Stats button to admin LLM tracking panel
Allows resetting the LLM stats aggregation window without deleting historical data. Stores a reset timestamp in metadata and filters the aggregation query to only include calls after that point. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -164,6 +164,10 @@ export class SocketClient {
|
|||||||
this.socket.emit('admin-toggle-llm-tracking', { enabled });
|
this.socket.emit('admin-toggle-llm-tracking', { enabled });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetLlmStats(): void {
|
||||||
|
this.socket.emit('admin-reset-llm-stats');
|
||||||
|
}
|
||||||
|
|
||||||
subscribeLogs(): void {
|
subscribeLogs(): void {
|
||||||
this.socket.emit('log-subscribe');
|
this.socket.emit('log-subscribe');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -336,6 +336,9 @@ export class GameScene extends Phaser.Scene {
|
|||||||
this.adminPanel.onToggleLlmTracking = (enabled) => {
|
this.adminPanel.onToggleLlmTracking = (enabled) => {
|
||||||
this.client.toggleLlmTracking(enabled);
|
this.client.toggleLlmTracking(enabled);
|
||||||
};
|
};
|
||||||
|
this.adminPanel.onResetLlmStats = () => {
|
||||||
|
this.client.resetLlmStats();
|
||||||
|
};
|
||||||
|
|
||||||
this.client.onLlmStatsResult = (data) => {
|
this.client.onLlmStatsResult = (data) => {
|
||||||
this.adminPanel.updateLlmStats(data);
|
this.adminPanel.updateLlmStats(data);
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export class AdminPanel {
|
|||||||
onReset: (() => void) | null = null;
|
onReset: (() => void) | null = null;
|
||||||
onRequestLlmStats: (() => void) | null = null;
|
onRequestLlmStats: (() => void) | null = null;
|
||||||
onToggleLlmTracking: ((enabled: boolean) => void) | null = null;
|
onToggleLlmTracking: ((enabled: boolean) => void) | null = null;
|
||||||
|
onResetLlmStats: (() => void) | null = null;
|
||||||
private statsColumn: HTMLDivElement | null = null;
|
private statsColumn: HTMLDivElement | null = null;
|
||||||
private trackingToggle: HTMLInputElement | null = null;
|
private trackingToggle: HTMLInputElement | null = null;
|
||||||
|
|
||||||
@@ -263,6 +264,26 @@ export class AdminPanel {
|
|||||||
totalsRow.appendChild(totalsCost);
|
totalsRow.appendChild(totalsCost);
|
||||||
|
|
||||||
this.statsColumn.appendChild(totalsRow);
|
this.statsColumn.appendChild(totalsRow);
|
||||||
|
|
||||||
|
// Reset Stats button
|
||||||
|
const resetStatsBtn = document.createElement('button');
|
||||||
|
resetStatsBtn.style.cssText = `
|
||||||
|
background: #2a2a4e;
|
||||||
|
border: 1px solid #8878a8;
|
||||||
|
color: #e0d0b0;
|
||||||
|
font-family: 'Press Start 2P', monospace;
|
||||||
|
font-size: 8px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: center;
|
||||||
|
`;
|
||||||
|
resetStatsBtn.textContent = 'Reset Stats';
|
||||||
|
resetStatsBtn.addEventListener('click', () => {
|
||||||
|
this.onResetLlmStats?.();
|
||||||
|
});
|
||||||
|
this.statsColumn.appendChild(resetStatsBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildEditor(constants: TunableConstants): void {
|
private buildEditor(constants: TunableConstants): void {
|
||||||
|
|||||||
22
docs/superpowers/specs/2026-03-13-llm-stats-reset-design.md
Normal file
22
docs/superpowers/specs/2026-03-13-llm-stats-reset-design.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# LLM Stats Reset Button
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Add a "Reset Stats" button to the Admin panel's LLM tracking column that gives a clean aggregation window without deleting historical data.
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
1. "Reset Stats" button appears at the bottom of the right column, below the TOTAL row.
|
||||||
|
2. Clicking it emits `admin-reset-llm-stats` socket event.
|
||||||
|
3. Server stores the current timestamp in the `metadata` table as `llm_stats_reset_after`.
|
||||||
|
4. Server's `getAggregatedStats()` adds `WHERE timestamp > ?` using that value.
|
||||||
|
5. Server responds with updated (now-empty) stats via existing `admin-llm-stats-result`.
|
||||||
|
6. Client renders the refreshed stats as usual.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- **`server/src/llm/llmStatsService.ts`** — `getAggregatedStats()` reads reset timestamp from metadata, filters query. New `resetStats()` method to store the timestamp.
|
||||||
|
- **`server/src/network/SocketServer.ts`** — Handle `admin-reset-llm-stats` event: call `resetStats()`, respond with fresh stats.
|
||||||
|
- **`client/src/ui/AdminPanel.ts`** — Add "Reset Stats" button in `updateLlmStats()`, wire click to new callback.
|
||||||
|
- **`client/src/scenes/GameScene.ts`** — Wire the new callback through to SocketClient.
|
||||||
|
- **`client/src/network/SocketClient.ts`** — Add `resetLlmStats()` method emitting the event.
|
||||||
@@ -16,6 +16,7 @@ export interface LlmStatsService {
|
|||||||
getStats(): LlmStatsData;
|
getStats(): LlmStatsData;
|
||||||
isTrackingEnabled(): boolean;
|
isTrackingEnabled(): boolean;
|
||||||
setTrackingEnabled(enabled: boolean): void;
|
setTrackingEnabled(enabled: boolean): void;
|
||||||
|
resetStats(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createLlmStatsService(): LlmStatsService {
|
export function createLlmStatsService(): LlmStatsService {
|
||||||
@@ -52,6 +53,12 @@ export function createLlmStatsService(): LlmStatsService {
|
|||||||
const enabled = loadTrackingEnabled();
|
const enabled = loadTrackingEnabled();
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
|
const resetRow = db.prepare("SELECT value FROM metadata WHERE key = 'llm_stats_reset_after'").get() as { value: string } | undefined;
|
||||||
|
const resetAfter = resetRow?.value ?? null;
|
||||||
|
|
||||||
|
const whereClause = resetAfter ? 'WHERE timestamp > ?' : '';
|
||||||
|
const params = resetAfter ? [resetAfter] : [];
|
||||||
|
|
||||||
const rows = db.prepare(`
|
const rows = db.prepare(`
|
||||||
SELECT
|
SELECT
|
||||||
template_name,
|
template_name,
|
||||||
@@ -63,9 +70,10 @@ export function createLlmStatsService(): LlmStatsService {
|
|||||||
SUM(CASE WHEN retries > 0 THEN 1 ELSE 0 END) as retry_count,
|
SUM(CASE WHEN retries > 0 THEN 1 ELSE 0 END) as retry_count,
|
||||||
SUM(failed) as fail_count
|
SUM(failed) as fail_count
|
||||||
FROM llm_call_log
|
FROM llm_call_log
|
||||||
|
${whereClause}
|
||||||
GROUP BY template_name
|
GROUP BY template_name
|
||||||
ORDER BY count DESC
|
ORDER BY count DESC
|
||||||
`).all() as Array<{
|
`).all(...params) as Array<{
|
||||||
template_name: string;
|
template_name: string;
|
||||||
count: number;
|
count: number;
|
||||||
total_cost: number;
|
total_cost: number;
|
||||||
@@ -114,5 +122,14 @@ export function createLlmStatsService(): LlmStatsService {
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resetStats(): void {
|
||||||
|
try {
|
||||||
|
const db = getDatabase();
|
||||||
|
db.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES ('llm_stats_reset_after', ?)").run(new Date().toISOString());
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,6 +204,14 @@ export class SocketServer {
|
|||||||
socket.emit('admin-llm-stats-result', stats);
|
socket.emit('admin-llm-stats-result', stats);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('admin-reset-llm-stats', () => {
|
||||||
|
if (!this.authenticatedSockets.has(socket.id)) return;
|
||||||
|
this.gameLoop.llmStatsService.resetStats();
|
||||||
|
const stats = this.gameLoop.llmStatsService.getStats();
|
||||||
|
socket.emit('admin-llm-stats-result', stats);
|
||||||
|
this.gameLoop.logService.log('info', 'Game', 'Admin reset LLM stats');
|
||||||
|
});
|
||||||
|
|
||||||
socket.on('admin-toggle-llm-tracking', (data: { enabled: boolean }) => {
|
socket.on('admin-toggle-llm-tracking', (data: { enabled: boolean }) => {
|
||||||
if (!this.authenticatedSockets.has(socket.id)) return;
|
if (!this.authenticatedSockets.has(socket.id)) return;
|
||||||
this.gameLoop.llmStatsService.setTrackingEnabled(data.enabled);
|
this.gameLoop.llmStatsService.setTrackingEnabled(data.enabled);
|
||||||
|
|||||||
1
shared/dist/types.d.ts
vendored
1
shared/dist/types.d.ts
vendored
@@ -393,4 +393,5 @@ export interface ClientEvents {
|
|||||||
'admin-toggle-llm-tracking': (data: {
|
'admin-toggle-llm-tracking': (data: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}) => void;
|
}) => void;
|
||||||
|
'admin-reset-llm-stats': () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -382,4 +382,5 @@ export interface ClientEvents {
|
|||||||
'log-unsubscribe': () => void;
|
'log-unsubscribe': () => void;
|
||||||
'admin-llm-stats': () => void;
|
'admin-llm-stats': () => void;
|
||||||
'admin-toggle-llm-tracking': (data: { enabled: boolean }) => void;
|
'admin-toggle-llm-tracking': (data: { enabled: boolean }) => void;
|
||||||
|
'admin-reset-llm-stats': () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user