import { writable, derived } from "svelte/store"; import { invoke } from "@tauri-apps/api/core"; import { notificationManager } from "$lib/notifications/notificationManager"; // Types matching Rust backend export interface DailyCost { date: string; input_tokens: number; output_tokens: number; cost_usd: number; messages_sent: number; sessions_count: number; } export interface CostSummary { period_days: number; total_input_tokens: number; total_output_tokens: number; total_cost: number; total_messages: number; total_sessions: number; average_daily_cost: number; daily_breakdown: DailyCost[]; } export type AlertType = "Daily" | "Weekly" | "Monthly"; export interface CostAlert { alert_type: AlertType; threshold: number; current_cost: number; } export interface CostAlertThresholds { daily: number | null; weekly: number | null; monthly: number | null; } // Store state interface CostTrackingState { todayCost: number; weekCost: number; monthCost: number; summary: CostSummary | null; alerts: CostAlert[]; thresholds: CostAlertThresholds; isLoading: boolean; lastUpdated: Date | null; } const defaultState: CostTrackingState = { todayCost: 0, weekCost: 0, monthCost: 0, summary: null, alerts: [], thresholds: { daily: null, weekly: null, monthly: null }, isLoading: false, lastUpdated: null, }; function createCostTrackingStore() { const { subscribe, set, update } = writable(defaultState); return { subscribe, async refresh() { update((s) => ({ ...s, isLoading: true })); try { const [todayCost, weekCost, monthCost, alerts] = await Promise.all([ invoke("get_today_cost"), invoke("get_week_cost"), invoke("get_month_cost"), invoke("get_cost_alerts"), ]); update((s) => ({ ...s, todayCost, weekCost, monthCost, alerts, isLoading: false, lastUpdated: new Date(), })); // Trigger notifications for any new alerts if (alerts.length > 0) { for (const alert of alerts) { const message = getAlertMessage(alert); notificationManager.notifyCostAlert(message); } } return alerts; } catch (error) { console.error("Failed to refresh cost tracking:", error); update((s) => ({ ...s, isLoading: false })); return []; } }, async getSummary(days: number): Promise { try { const summary = await invoke("get_cost_summary", { days }); update((s) => ({ ...s, summary })); return summary; } catch (error) { console.error("Failed to get cost summary:", error); return null; } }, async setAlertThresholds(thresholds: CostAlertThresholds) { try { await invoke("set_cost_alert_thresholds", { daily: thresholds.daily, weekly: thresholds.weekly, monthly: thresholds.monthly, }); update((s) => ({ ...s, thresholds })); } catch (error) { console.error("Failed to set alert thresholds:", error); } }, async exportCsv(days: number): Promise { try { return await invoke("export_cost_csv", { days }); } catch (error) { console.error("Failed to export CSV:", error); return null; } }, reset() { set(defaultState); }, }; } export const costTrackingStore = createCostTrackingStore(); // Derived stores for formatted values export const formattedCosts = derived(costTrackingStore, ($store) => ({ today: formatCost($store.todayCost), week: formatCost($store.weekCost), month: formatCost($store.monthCost), todayRaw: $store.todayCost, weekRaw: $store.weekCost, monthRaw: $store.monthCost, })); // Helper functions export function formatCost(cost: number): string { if (cost < 0.01) { return `$${cost.toFixed(4)}`; } if (cost < 1) { return `$${cost.toFixed(3)}`; } return `$${cost.toFixed(2)}`; } export function formatAlertType(type: AlertType): string { switch (type) { case "Daily": return "Today"; case "Weekly": return "This Week"; case "Monthly": return "This Month"; } } export function getAlertMessage(alert: CostAlert): string { const period = formatAlertType(alert.alert_type); return `${period}'s spending (${formatCost(alert.current_cost)}) has exceeded your ${formatCost(alert.threshold)} threshold`; }