generated from nhcarrigan/template
42c3b4ee83
- Add lifetime stats persistence to Rust backend - Sync achievement state between frontend and backend on startup - Add commands for loading/saving stats to disk - Expand achievement definitions with 150+ new achievements - Fix stats store to properly track total vs session metrics
132 lines
4.3 KiB
TypeScript
132 lines
4.3 KiB
TypeScript
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<string, number>;
|
|
session_tools_usage: Record<string, number>;
|
|
session_duration_seconds: number;
|
|
}
|
|
|
|
// Main stats store
|
|
export const stats = writable<UsageStats>({
|
|
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<UsageStats>("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,
|
|
}));
|
|
}
|