diff --git a/client/src/network/SocketClient.ts b/client/src/network/SocketClient.ts index f26a760..779a9d3 100644 --- a/client/src/network/SocketClient.ts +++ b/client/src/network/SocketClient.ts @@ -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'); } diff --git a/client/src/scenes/GameScene.ts b/client/src/scenes/GameScene.ts index 6383c1d..4eb224e 100644 --- a/client/src/scenes/GameScene.ts +++ b/client/src/scenes/GameScene.ts @@ -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); diff --git a/client/src/ui/AdminPanel.ts b/client/src/ui/AdminPanel.ts index fd7d44e..6ff8a09 100644 --- a/client/src/ui/AdminPanel.ts +++ b/client/src/ui/AdminPanel.ts @@ -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 { diff --git a/docs/superpowers/specs/2026-03-13-llm-stats-reset-design.md b/docs/superpowers/specs/2026-03-13-llm-stats-reset-design.md new file mode 100644 index 0000000..8a1326f --- /dev/null +++ b/docs/superpowers/specs/2026-03-13-llm-stats-reset-design.md @@ -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. diff --git a/server/src/llm/llmStatsService.ts b/server/src/llm/llmStatsService.ts index a3882dc..9bd286a 100644 --- a/server/src/llm/llmStatsService.ts +++ b/server/src/llm/llmStatsService.ts @@ -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 + } + }, }; } diff --git a/server/src/network/SocketServer.ts b/server/src/network/SocketServer.ts index c6dee8c..77f1a16 100644 --- a/server/src/network/SocketServer.ts +++ b/server/src/network/SocketServer.ts @@ -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); diff --git a/shared/dist/types.d.ts b/shared/dist/types.d.ts index f0a358b..95d2d91 100644 --- a/shared/dist/types.d.ts +++ b/shared/dist/types.d.ts @@ -393,4 +393,5 @@ export interface ClientEvents { 'admin-toggle-llm-tracking': (data: { enabled: boolean; }) => void; + 'admin-reset-llm-stats': () => void; } diff --git a/shared/src/types.ts b/shared/src/types.ts index 1ea7d81..263bf2a 100644 --- a/shared/src/types.ts +++ b/shared/src/types.ts @@ -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; }