diff --git a/src/components/usage/ModelStatsCard.tsx b/src/components/usage/ModelStatsCard.tsx index 859fa16..763cf10 100644 --- a/src/components/usage/ModelStatsCard.tsx +++ b/src/components/usage/ModelStatsCard.tsx @@ -6,6 +6,8 @@ import styles from '@/pages/UsagePage.module.scss'; export interface ModelStat { model: string; requests: number; + successCount: number; + failureCount: number; tokens: number; cost: number; } @@ -38,7 +40,15 @@ export function ModelStatsCard({ modelStats, loading, hasPrices }: ModelStatsCar {modelStats.map((stat) => ( {stat.model} - {stat.requests.toLocaleString()} + + + {stat.requests.toLocaleString()} + + ({stat.successCount.toLocaleString()}{' '} + {stat.failureCount.toLocaleString()}) + + + {formatTokensInMillions(stat.tokens)} {hasPrices && {stat.cost > 0 ? formatUsd(stat.cost) : '--'}} diff --git a/src/pages/UsagePage.module.scss b/src/pages/UsagePage.module.scss index 962913a..bce9d85 100644 --- a/src/pages/UsagePage.module.scss +++ b/src/pages/UsagePage.module.scss @@ -456,6 +456,18 @@ word-break: break-all; } +.requestCountCell { + display: inline-flex; + align-items: baseline; + gap: 6px; + font-variant-numeric: tabular-nums; +} + +.requestBreakdown { + color: var(--text-secondary); + white-space: nowrap; +} + // Pricing Section (80%比例) .pricingSection { display: flex; diff --git a/src/utils/usage.ts b/src/utils/usage.ts index a01e3c4..48bf20c 100644 --- a/src/utils/usage.ts +++ b/src/utils/usage.ts @@ -579,6 +579,8 @@ export function getApiStats(usageData: any, modelPrices: Record): Array<{ model: string; requests: number; + successCount: number; + failureCount: number; tokens: number; cost: number; }> { @@ -586,20 +588,39 @@ export function getModelStats(usageData: any, modelPrices: Record(); + const modelMap = new Map(); Object.values(usageData.apis as Record).forEach(apiData => { const models = apiData?.models || {}; Object.entries(models as Record).forEach(([modelName, modelData]) => { - const existing = modelMap.get(modelName) || { requests: 0, tokens: 0, cost: 0 }; + const existing = modelMap.get(modelName) || { requests: 0, successCount: 0, failureCount: 0, tokens: 0, cost: 0 }; existing.requests += modelData.total_requests || 0; existing.tokens += modelData.total_tokens || 0; + const details = Array.isArray(modelData.details) ? modelData.details : []; + const price = modelPrices[modelName]; - if (price) { - const details = Array.isArray(modelData.details) ? modelData.details : []; + + const hasExplicitCounts = + typeof modelData.success_count === 'number' || typeof modelData.failure_count === 'number'; + if (hasExplicitCounts) { + existing.successCount += Number(modelData.success_count) || 0; + existing.failureCount += Number(modelData.failure_count) || 0; + } + + if (details.length > 0 && (!hasExplicitCounts || price)) { details.forEach((detail: any) => { - existing.cost += calculateCost({ ...detail, __modelName: modelName }, modelPrices); + if (!hasExplicitCounts) { + if (detail?.failed === true) { + existing.failureCount += 1; + } else { + existing.successCount += 1; + } + } + + if (price) { + existing.cost += calculateCost({ ...detail, __modelName: modelName }, modelPrices); + } }); } modelMap.set(modelName, existing);