import { writable, derived } from "svelte/store"; import { listen } from "@tauri-apps/api/event"; import { invoke } from "@tauri-apps/api/core"; export interface UsageStats { total_input_tokens: number; total_output_tokens: number; total_cost_usd: number; session_input_tokens: number; session_output_tokens: number; session_cost_usd: number; model: string | null; // New fields messages_exchanged: number; session_messages_exchanged: number; code_blocks_generated: number; session_code_blocks_generated: number; files_edited: number; session_files_edited: number; files_created: number; session_files_created: number; tools_usage: Record; session_tools_usage: Record; session_duration_seconds: number; } // Main stats store export const stats = writable({ total_input_tokens: 0, total_output_tokens: 0, total_cost_usd: 0, session_input_tokens: 0, session_output_tokens: 0, session_cost_usd: 0, model: null, messages_exchanged: 0, session_messages_exchanged: 0, code_blocks_generated: 0, session_code_blocks_generated: 0, files_edited: 0, session_files_edited: 0, files_created: 0, session_files_created: 0, tools_usage: {}, session_tools_usage: {}, session_duration_seconds: 0, }); // Derived store for formatted display values export const formattedStats = derived(stats, ($stats) => { const formatNumber = (num: number) => num.toLocaleString(); const formatCost = (cost: number) => `$${cost.toFixed(4)}`; const formatDuration = (seconds: number) => { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; if (hours > 0) { return `${hours}h ${minutes}m ${secs}s`; } else if (minutes > 0) { return `${minutes}m ${secs}s`; } else { return `${secs}s`; } }; return { totalTokens: formatNumber($stats.total_input_tokens + $stats.total_output_tokens), totalInputTokens: formatNumber($stats.total_input_tokens), totalOutputTokens: formatNumber($stats.total_output_tokens), totalCost: formatCost($stats.total_cost_usd), sessionTokens: formatNumber($stats.session_input_tokens + $stats.session_output_tokens), sessionInputTokens: formatNumber($stats.session_input_tokens), sessionOutputTokens: formatNumber($stats.session_output_tokens), sessionCost: formatCost($stats.session_cost_usd), model: $stats.model || "No model selected", // New formatted fields messagesTotal: formatNumber($stats.messages_exchanged), messagesSession: formatNumber($stats.session_messages_exchanged), codeBlocksTotal: formatNumber($stats.code_blocks_generated), codeBlocksSession: formatNumber($stats.session_code_blocks_generated), filesEditedTotal: formatNumber($stats.files_edited), filesEditedSession: formatNumber($stats.session_files_edited), filesCreatedTotal: formatNumber($stats.files_created), filesCreatedSession: formatNumber($stats.session_files_created), sessionDuration: formatDuration($stats.session_duration_seconds), toolsUsage: $stats.tools_usage, sessionToolsUsage: $stats.session_tools_usage, }; }); // Note: Cost calculation is now done in the Rust backend // Initialize stats listener export async function initStatsListener() { // Listen for stats updates from the backend await listen("claude:stats", (event) => { const payload = event.payload as { stats: UsageStats }; const { stats: newStats } = payload; // The backend already tracks all totals - just set the stats directly stats.set(newStats); }); // Load initial persisted stats from backend (no bridge required) try { const initialStats = await invoke("get_persisted_stats"); stats.set(initialStats); console.log("Loaded persisted stats:", initialStats); } catch (error) { console.error("Failed to load initial stats:", error); } } // Reset session stats (call when starting new session) export function resetSessionStats() { stats.update((current) => ({ ...current, session_input_tokens: 0, session_output_tokens: 0, session_cost_usd: 0, session_messages_exchanged: 0, session_code_blocks_generated: 0, session_files_edited: 0, session_files_created: 0, session_tools_usage: {}, session_duration_seconds: 0, })); }