Files
hikari-desktop/src/lib/stores/stats.ts
T
hikari 42c3b4ee83 feat: add persistent lifetime stats and sync achievements
- 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
2026-01-25 20:06:36 -08:00

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,
}));
}