import type { CSSProperties, ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { Line } from 'react-chartjs-2'; import { IconDiamond, IconDollarSign, IconSatellite, IconTimer, IconTrendingUp } from '@/components/ui/icons'; import { formatTokensInMillions, formatPerMinuteValue, formatUsd, calculateTokenBreakdown, calculateRecentPerMinuteRates, calculateTotalCost, type ModelPrice } from '@/utils/usage'; import { sparklineOptions } from '@/utils/usage/chartConfig'; import type { UsagePayload } from './hooks/useUsageData'; import type { SparklineBundle } from './hooks/useSparklines'; import styles from '@/pages/UsagePage.module.scss'; interface StatCardData { key: string; label: string; icon: ReactNode; accent: string; accentSoft: string; accentBorder: string; value: string; meta?: ReactNode; trend: SparklineBundle | null; } export interface StatCardsProps { usage: UsagePayload | null; loading: boolean; modelPrices: Record; sparklines: { requests: SparklineBundle | null; tokens: SparklineBundle | null; rpm: SparklineBundle | null; tpm: SparklineBundle | null; cost: SparklineBundle | null; }; } export function StatCards({ usage, loading, modelPrices, sparklines }: StatCardsProps) { const { t } = useTranslation(); const tokenBreakdown = usage ? calculateTokenBreakdown(usage) : { cachedTokens: 0, reasoningTokens: 0 }; const rateStats = usage ? calculateRecentPerMinuteRates(30, usage) : { rpm: 0, tpm: 0, windowMinutes: 30, requestCount: 0, tokenCount: 0 }; const totalCost = usage ? calculateTotalCost(usage, modelPrices) : 0; const hasPrices = Object.keys(modelPrices).length > 0; const statsCards: StatCardData[] = [ { key: 'requests', label: t('usage_stats.total_requests'), icon: , accent: '#3b82f6', accentSoft: 'rgba(59, 130, 246, 0.18)', accentBorder: 'rgba(59, 130, 246, 0.35)', value: loading ? '-' : (usage?.total_requests ?? 0).toLocaleString(), meta: ( <> {t('usage_stats.success_requests')}: {loading ? '-' : (usage?.success_count ?? 0)} {t('usage_stats.failed_requests')}: {loading ? '-' : (usage?.failure_count ?? 0)} ), trend: sparklines.requests }, { key: 'tokens', label: t('usage_stats.total_tokens'), icon: , accent: '#8b5cf6', accentSoft: 'rgba(139, 92, 246, 0.18)', accentBorder: 'rgba(139, 92, 246, 0.35)', value: loading ? '-' : formatTokensInMillions(usage?.total_tokens ?? 0), meta: ( <> {t('usage_stats.cached_tokens')}: {loading ? '-' : formatTokensInMillions(tokenBreakdown.cachedTokens)} {t('usage_stats.reasoning_tokens')}: {loading ? '-' : formatTokensInMillions(tokenBreakdown.reasoningTokens)} ), trend: sparklines.tokens }, { key: 'rpm', label: t('usage_stats.rpm_30m'), icon: , accent: '#22c55e', accentSoft: 'rgba(34, 197, 94, 0.18)', accentBorder: 'rgba(34, 197, 94, 0.32)', value: loading ? '-' : formatPerMinuteValue(rateStats.rpm), meta: ( {t('usage_stats.total_requests')}: {loading ? '-' : rateStats.requestCount.toLocaleString()} ), trend: sparklines.rpm }, { key: 'tpm', label: t('usage_stats.tpm_30m'), icon: , accent: '#f97316', accentSoft: 'rgba(249, 115, 22, 0.18)', accentBorder: 'rgba(249, 115, 22, 0.32)', value: loading ? '-' : formatPerMinuteValue(rateStats.tpm), meta: ( {t('usage_stats.total_tokens')}: {loading ? '-' : formatTokensInMillions(rateStats.tokenCount)} ), trend: sparklines.tpm }, { key: 'cost', label: t('usage_stats.total_cost'), icon: , accent: '#f59e0b', accentSoft: 'rgba(245, 158, 11, 0.18)', accentBorder: 'rgba(245, 158, 11, 0.32)', value: loading ? '-' : hasPrices ? formatUsd(totalCost) : '--', meta: ( <> {t('usage_stats.total_tokens')}: {loading ? '-' : formatTokensInMillions(usage?.total_tokens ?? 0)} {!hasPrices && ( {t('usage_stats.cost_need_price')} )} ), trend: hasPrices ? sparklines.cost : null } ]; return (
{statsCards.map((card) => (
{card.label}
{card.icon}
{card.value}
{card.meta &&
{card.meta}
}
{card.trend ? ( ) : (
)}
))}
); }