import { useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Card } from '@/components/ui/Card'; import { formatCompactNumber, formatUsd } from '@/utils/usage'; import styles from '@/pages/UsagePage.module.scss'; export interface ModelStat { model: string; requests: number; successCount: number; failureCount: number; tokens: number; cost: number; } export interface ModelStatsCardProps { modelStats: ModelStat[]; loading: boolean; hasPrices: boolean; } type SortKey = 'model' | 'requests' | 'tokens' | 'cost' | 'successRate'; type SortDir = 'asc' | 'desc'; interface ModelStatWithRate extends ModelStat { successRate: number; } export function ModelStatsCard({ modelStats, loading, hasPrices }: ModelStatsCardProps) { const { t } = useTranslation(); const [sortKey, setSortKey] = useState('requests'); const [sortDir, setSortDir] = useState('desc'); const handleSort = (key: SortKey) => { if (sortKey === key) { setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')); } else { setSortKey(key); setSortDir(key === 'model' ? 'asc' : 'desc'); } }; const sorted = useMemo((): ModelStatWithRate[] => { const list: ModelStatWithRate[] = modelStats.map((s) => ({ ...s, successRate: s.requests > 0 ? (s.successCount / s.requests) * 100 : 100, })); const dir = sortDir === 'asc' ? 1 : -1; list.sort((a, b) => { if (sortKey === 'model') return dir * a.model.localeCompare(b.model); return dir * ((a[sortKey] as number) - (b[sortKey] as number)); }); return list; }, [modelStats, sortKey, sortDir]); const arrow = (key: SortKey) => sortKey === key ? (sortDir === 'asc' ? ' ▲' : ' ▼') : ''; const ariaSort = (key: SortKey): 'none' | 'ascending' | 'descending' => sortKey === key ? (sortDir === 'asc' ? 'ascending' : 'descending') : 'none'; return ( {loading ? (
{t('common.loading')}
) : sorted.length > 0 ? (
{hasPrices && ( )} {sorted.map((stat) => ( {hasPrices && } ))}
{stat.model} {stat.requests.toLocaleString()} ({stat.successCount.toLocaleString()}{' '} {stat.failureCount.toLocaleString()}) {formatCompactNumber(stat.tokens)} = 95 ? styles.statSuccess : stat.successRate >= 80 ? styles.statNeutral : styles.statFailure } > {stat.successRate.toFixed(1)}% {stat.cost > 0 ? formatUsd(stat.cost) : '--'}
) : (
{t('usage_stats.no_data')}
)}
); }