From 5d0232e5dee8e4d7a72550269c4a095db7d74fec Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Fri, 13 Feb 2026 13:23:09 +0800 Subject: [PATCH] feat(usage): add credential (auth index) breakdown card --- src/components/usage/CredentialStatsCard.tsx | 91 ++++++++++++++++++++ src/components/usage/index.ts | 3 + src/i18n/locales/en.json | 4 +- src/i18n/locales/ru.json | 4 +- src/i18n/locales/zh-CN.json | 4 +- src/pages/UsagePage.tsx | 4 + 6 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 src/components/usage/CredentialStatsCard.tsx diff --git a/src/components/usage/CredentialStatsCard.tsx b/src/components/usage/CredentialStatsCard.tsx new file mode 100644 index 0000000..e15dd0f --- /dev/null +++ b/src/components/usage/CredentialStatsCard.tsx @@ -0,0 +1,91 @@ +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Card } from '@/components/ui/Card'; +import { computeKeyStats, formatCompactNumber } from '@/utils/usage'; +import type { UsagePayload } from './hooks/useUsageData'; +import styles from '@/pages/UsagePage.module.scss'; + +export interface CredentialStatsCardProps { + usage: UsagePayload | null; + loading: boolean; +} + +interface CredentialRow { + key: string; + success: number; + failure: number; + total: number; + successRate: number; +} + +export function CredentialStatsCard({ usage, loading }: CredentialStatsCardProps) { + const { t } = useTranslation(); + + const rows = useMemo((): CredentialRow[] => { + if (!usage) return []; + const { byAuthIndex } = computeKeyStats(usage); + return Object.entries(byAuthIndex) + .map(([key, bucket]) => { + const total = bucket.success + bucket.failure; + return { + key, + success: bucket.success, + failure: bucket.failure, + total, + successRate: total > 0 ? (bucket.success / total) * 100 : 100, + }; + }) + .sort((a, b) => b.total - a.total); + }, [usage]); + + return ( + + {loading ? ( +
{t('common.loading')}
+ ) : rows.length > 0 ? ( +
+ + + + + + + + + + {rows.map((row) => ( + + + + + + ))} + +
{t('usage_stats.credential_name')}{t('usage_stats.requests_count')}{t('usage_stats.success_rate')}
{row.key} + + {formatCompactNumber(row.total)} + + ({row.success.toLocaleString()}{' '} + {row.failure.toLocaleString()}) + + + + = 95 + ? styles.statSuccess + : row.successRate >= 80 + ? styles.statNeutral + : styles.statFailure + } + > + {row.successRate.toFixed(1)}% + +
+
+ ) : ( +
{t('usage_stats.no_data')}
+ )} +
+ ); +} diff --git a/src/components/usage/index.ts b/src/components/usage/index.ts index e138619..08f571c 100644 --- a/src/components/usage/index.ts +++ b/src/components/usage/index.ts @@ -26,3 +26,6 @@ export type { ModelStatsCardProps, ModelStat } from './ModelStatsCard'; export { PriceSettingsCard } from './PriceSettingsCard'; export type { PriceSettingsCardProps } from './PriceSettingsCard'; + +export { CredentialStatsCard } from './CredentialStatsCard'; +export type { CredentialStatsCardProps } from './CredentialStatsCard'; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index e1766c6..4dc10d1 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -812,7 +812,9 @@ "cost_axis_label": "Cost ($)", "cost_need_price": "Set a model price to view cost stats", "cost_need_usage": "No usage data available to calculate cost", - "cost_no_data": "No cost data yet" + "cost_no_data": "No cost data yet", + "credential_stats": "Credential Statistics", + "credential_name": "Credential" }, "stats": { "success": "Success", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 87b45ab..389549a 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -815,7 +815,9 @@ "cost_axis_label": "Стоимость ($)", "cost_need_price": "Задайте стоимость модели, чтобы увидеть статистику затрат", "cost_need_usage": "Нет данных использования для расчёта стоимости", - "cost_no_data": "Данных о стоимости ещё нет" + "cost_no_data": "Данных о стоимости ещё нет", + "credential_stats": "Статистика учётных данных", + "credential_name": "Учётные данные" }, "stats": { "success": "Успех", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index a316360..bc2d4bb 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -812,7 +812,9 @@ "cost_axis_label": "花费 ($)", "cost_need_price": "请先设置模型价格", "cost_need_usage": "暂无使用数据,无法计算花费", - "cost_no_data": "没有可计算的花费数据" + "cost_no_data": "没有可计算的花费数据", + "credential_stats": "凭证统计", + "credential_name": "凭证" }, "stats": { "success": "成功", diff --git a/src/pages/UsagePage.tsx b/src/pages/UsagePage.tsx index 3e53983..9c1d779 100644 --- a/src/pages/UsagePage.tsx +++ b/src/pages/UsagePage.tsx @@ -24,6 +24,7 @@ import { ApiDetailsCard, ModelStatsCard, PriceSettingsCard, + CredentialStatsCard, useUsageData, useSparklines, useChartData @@ -362,6 +363,9 @@ export function UsagePage() { + {/* Credential Stats */} + + {/* Price Settings */}