feat(ui): show success/failure in API usage stats

This commit is contained in:
hkfires
2026-02-06 11:23:50 +08:00
parent b9001c27c5
commit 4a0386472d
2 changed files with 65 additions and 12 deletions

View File

@@ -39,10 +39,18 @@ export function ApiDetailsCard({ apiStats, loading, hasPrices }: ApiDetailsCardP
<span className={styles.apiEndpoint}>{api.endpoint}</span> <span className={styles.apiEndpoint}>{api.endpoint}</span>
<div className={styles.apiStats}> <div className={styles.apiStats}>
<span className={styles.apiBadge}> <span className={styles.apiBadge}>
{t('usage_stats.requests_count')}: {api.totalRequests} <span className={styles.requestCountCell}>
<span>
{t('usage_stats.requests_count')}: {api.totalRequests.toLocaleString()}
</span>
<span className={styles.requestBreakdown}>
(<span className={styles.statSuccess}>{api.successCount.toLocaleString()}</span>{' '}
<span className={styles.statFailure}>{api.failureCount.toLocaleString()}</span>)
</span>
</span>
</span> </span>
<span className={styles.apiBadge}> <span className={styles.apiBadge}>
Tokens: {formatTokensInMillions(api.totalTokens)} {t('usage_stats.tokens_count')}: {formatTokensInMillions(api.totalTokens)}
</span> </span>
{hasPrices && api.totalCost > 0 && ( {hasPrices && api.totalCost > 0 && (
<span className={styles.apiBadge}> <span className={styles.apiBadge}>
@@ -61,7 +69,13 @@ export function ApiDetailsCard({ apiStats, loading, hasPrices }: ApiDetailsCardP
<div key={model} className={styles.modelRow}> <div key={model} className={styles.modelRow}>
<span className={styles.modelName}>{model}</span> <span className={styles.modelName}>{model}</span>
<span className={styles.modelStat}> <span className={styles.modelStat}>
{stats.requests} {t('usage_stats.requests_count')} <span className={styles.requestCountCell}>
<span>{stats.requests.toLocaleString()}</span>
<span className={styles.requestBreakdown}>
(<span className={styles.statSuccess}>{stats.successCount.toLocaleString()}</span>{' '}
<span className={styles.statFailure}>{stats.failureCount.toLocaleString()}</span>)
</span>
</span>
</span> </span>
<span className={styles.modelStat}>{formatTokensInMillions(stats.tokens)}</span> <span className={styles.modelStat}>{formatTokensInMillions(stats.tokens)}</span>
</div> </div>

View File

@@ -54,9 +54,11 @@ export interface UsageDetail {
export interface ApiStats { export interface ApiStats {
endpoint: string; endpoint: string;
totalRequests: number; totalRequests: number;
successCount: number;
failureCount: number;
totalTokens: number; totalTokens: number;
totalCost: number; totalCost: number;
models: Record<string, { requests: number; tokens: number }>; models: Record<string, { requests: number; successCount: number; failureCount: number; tokens: number }>;
} }
const TOKENS_PER_PRICE_UNIT = 1_000_000; const TOKENS_PER_PRICE_UNIT = 1_000_000;
@@ -542,28 +544,65 @@ export function getApiStats(usageData: any, modelPrices: Record<string, ModelPri
const result: ApiStats[] = []; const result: ApiStats[] = [];
Object.entries(apis as Record<string, any>).forEach(([endpoint, apiData]) => { Object.entries(apis as Record<string, any>).forEach(([endpoint, apiData]) => {
const models: Record<string, { requests: number; tokens: number }> = {}; const models: Record<string, { requests: number; successCount: number; failureCount: number; tokens: number }> = {};
let derivedSuccessCount = 0;
let derivedFailureCount = 0;
let totalCost = 0; let totalCost = 0;
const modelsData = apiData?.models || {}; const modelsData = apiData?.models || {};
Object.entries(modelsData as Record<string, any>).forEach(([modelName, modelData]) => { Object.entries(modelsData as Record<string, any>).forEach(([modelName, modelData]) => {
models[modelName] = { const details = Array.isArray(modelData.details) ? modelData.details : [];
requests: modelData.total_requests || 0, const hasExplicitCounts =
tokens: modelData.total_tokens || 0 typeof modelData.success_count === 'number' || typeof modelData.failure_count === 'number';
};
let successCount = 0;
let failureCount = 0;
if (hasExplicitCounts) {
successCount += Number(modelData.success_count) || 0;
failureCount += Number(modelData.failure_count) || 0;
}
const price = modelPrices[modelName]; const price = modelPrices[modelName];
if (price) { if (details.length > 0 && (!hasExplicitCounts || price)) {
const details = Array.isArray(modelData.details) ? modelData.details : [];
details.forEach((detail: any) => { details.forEach((detail: any) => {
totalCost += calculateCost({ ...detail, __modelName: modelName }, modelPrices); if (!hasExplicitCounts) {
if (detail?.failed === true) {
failureCount += 1;
} else {
successCount += 1;
}
}
if (price) {
totalCost += calculateCost({ ...detail, __modelName: modelName }, modelPrices);
}
}); });
} }
models[modelName] = {
requests: modelData.total_requests || 0,
successCount,
failureCount,
tokens: modelData.total_tokens || 0
};
derivedSuccessCount += successCount;
derivedFailureCount += failureCount;
}); });
const hasApiExplicitCounts =
typeof apiData?.success_count === 'number' || typeof apiData?.failure_count === 'number';
const successCount = hasApiExplicitCounts
? (Number(apiData?.success_count) || 0)
: derivedSuccessCount;
const failureCount = hasApiExplicitCounts
? (Number(apiData?.failure_count) || 0)
: derivedFailureCount;
result.push({ result.push({
endpoint: maskUsageSensitiveValue(endpoint) || endpoint, endpoint: maskUsageSensitiveValue(endpoint) || endpoint,
totalRequests: apiData.total_requests || 0, totalRequests: apiData.total_requests || 0,
successCount,
failureCount,
totalTokens: apiData.total_tokens || 0, totalTokens: apiData.total_tokens || 0,
totalCost, totalCost,
models models