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 });
|
||||
}
|
||||
|
||||
resetLlmStats(): void {
|
||||
this.socket.emit('admin-reset-llm-stats');
|
||||
}
|
||||
|
||||
subscribeLogs(): void {
|
||||
this.socket.emit('log-subscribe');
|
||||
}
|
||||
|
||||
@@ -336,6 +336,9 @@ export class GameScene extends Phaser.Scene {
|
||||
this.adminPanel.onToggleLlmTracking = (enabled) => {
|
||||
this.client.toggleLlmTracking(enabled);
|
||||
};
|
||||
this.adminPanel.onResetLlmStats = () => {
|
||||
this.client.resetLlmStats();
|
||||
};
|
||||
|
||||
this.client.onLlmStatsResult = (data) => {
|
||||
this.adminPanel.updateLlmStats(data);
|
||||
|
||||
@@ -46,6 +46,7 @@ export class AdminPanel {
|
||||
onReset: (() => void) | null = null;
|
||||
onRequestLlmStats: (() => void) | null = null;
|
||||
onToggleLlmTracking: ((enabled: boolean) => void) | null = null;
|
||||
onResetLlmStats: (() => void) | null = null;
|
||||
private statsColumn: HTMLDivElement | null = null;
|
||||
private trackingToggle: HTMLInputElement | null = null;
|
||||
|
||||
@@ -263,6 +264,26 @@ export class AdminPanel {
|
||||
totalsRow.appendChild(totalsCost);
|
||||
|
||||
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 {
|
||||
|
||||
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;
|
||||
isTrackingEnabled(): boolean;
|
||||
setTrackingEnabled(enabled: boolean): void;
|
||||
resetStats(): void;
|
||||
}
|
||||
|
||||
export function createLlmStatsService(): LlmStatsService {
|
||||
@@ -52,6 +53,12 @@ export function createLlmStatsService(): LlmStatsService {
|
||||
const enabled = loadTrackingEnabled();
|
||||
try {
|
||||
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(`
|
||||
SELECT
|
||||
template_name,
|
||||
@@ -63,9 +70,10 @@ export function createLlmStatsService(): LlmStatsService {
|
||||
SUM(CASE WHEN retries > 0 THEN 1 ELSE 0 END) as retry_count,
|
||||
SUM(failed) as fail_count
|
||||
FROM llm_call_log
|
||||
${whereClause}
|
||||
GROUP BY template_name
|
||||
ORDER BY count DESC
|
||||
`).all() as Array<{
|
||||
`).all(...params) as Array<{
|
||||
template_name: string;
|
||||
count: number;
|
||||
total_cost: number;
|
||||
@@ -114,5 +122,14 @@ export function createLlmStatsService(): LlmStatsService {
|
||||
// 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.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 }) => {
|
||||
if (!this.authenticatedSockets.has(socket.id)) return;
|
||||
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: {
|
||||
enabled: boolean;
|
||||
}) => void;
|
||||
'admin-reset-llm-stats': () => void;
|
||||
}
|
||||
|
||||
@@ -382,4 +382,5 @@ export interface ClientEvents {
|
||||
'log-unsubscribe': () => void;
|
||||
'admin-llm-stats': () => void;
|
||||
'admin-toggle-llm-tracking': (data: { enabled: boolean }) => void;
|
||||
'admin-reset-llm-stats': () => void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user