mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 03:10:50 +08:00
feat(usage): show per-model success/failure counts
This commit is contained in:
@@ -6,6 +6,8 @@ import styles from '@/pages/UsagePage.module.scss';
|
|||||||
export interface ModelStat {
|
export interface ModelStat {
|
||||||
model: string;
|
model: string;
|
||||||
requests: number;
|
requests: number;
|
||||||
|
successCount: number;
|
||||||
|
failureCount: number;
|
||||||
tokens: number;
|
tokens: number;
|
||||||
cost: number;
|
cost: number;
|
||||||
}
|
}
|
||||||
@@ -38,7 +40,15 @@ export function ModelStatsCard({ modelStats, loading, hasPrices }: ModelStatsCar
|
|||||||
{modelStats.map((stat) => (
|
{modelStats.map((stat) => (
|
||||||
<tr key={stat.model}>
|
<tr key={stat.model}>
|
||||||
<td className={styles.modelCell}>{stat.model}</td>
|
<td className={styles.modelCell}>{stat.model}</td>
|
||||||
<td>{stat.requests.toLocaleString()}</td>
|
<td>
|
||||||
|
<span className={styles.requestCountCell}>
|
||||||
|
<span>{stat.requests.toLocaleString()}</span>
|
||||||
|
<span className={styles.requestBreakdown}>
|
||||||
|
(<span className={styles.statSuccess}>{stat.successCount.toLocaleString()}</span>{' '}
|
||||||
|
<span className={styles.statFailure}>{stat.failureCount.toLocaleString()}</span>)
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td>{formatTokensInMillions(stat.tokens)}</td>
|
<td>{formatTokensInMillions(stat.tokens)}</td>
|
||||||
{hasPrices && <td>{stat.cost > 0 ? formatUsd(stat.cost) : '--'}</td>}
|
{hasPrices && <td>{stat.cost > 0 ? formatUsd(stat.cost) : '--'}</td>}
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -456,6 +456,18 @@
|
|||||||
word-break: break-all;
|
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%比例)
|
// Pricing Section (80%比例)
|
||||||
.pricingSection {
|
.pricingSection {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -579,6 +579,8 @@ export function getApiStats(usageData: any, modelPrices: Record<string, ModelPri
|
|||||||
export function getModelStats(usageData: any, modelPrices: Record<string, ModelPrice>): Array<{
|
export function getModelStats(usageData: any, modelPrices: Record<string, ModelPrice>): Array<{
|
||||||
model: string;
|
model: string;
|
||||||
requests: number;
|
requests: number;
|
||||||
|
successCount: number;
|
||||||
|
failureCount: number;
|
||||||
tokens: number;
|
tokens: number;
|
||||||
cost: number;
|
cost: number;
|
||||||
}> {
|
}> {
|
||||||
@@ -586,20 +588,39 @@ export function getModelStats(usageData: any, modelPrices: Record<string, ModelP
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelMap = new Map<string, { requests: number; tokens: number; cost: number }>();
|
const modelMap = new Map<string, { requests: number; successCount: number; failureCount: number; tokens: number; cost: number }>();
|
||||||
|
|
||||||
Object.values(usageData.apis as Record<string, any>).forEach(apiData => {
|
Object.values(usageData.apis as Record<string, any>).forEach(apiData => {
|
||||||
const models = apiData?.models || {};
|
const models = apiData?.models || {};
|
||||||
Object.entries(models as Record<string, any>).forEach(([modelName, modelData]) => {
|
Object.entries(models as Record<string, any>).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.requests += modelData.total_requests || 0;
|
||||||
existing.tokens += modelData.total_tokens || 0;
|
existing.tokens += modelData.total_tokens || 0;
|
||||||
|
|
||||||
const price = modelPrices[modelName];
|
|
||||||
if (price) {
|
|
||||||
const details = Array.isArray(modelData.details) ? modelData.details : [];
|
const details = Array.isArray(modelData.details) ? modelData.details : [];
|
||||||
|
|
||||||
|
const price = modelPrices[modelName];
|
||||||
|
|
||||||
|
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) => {
|
details.forEach((detail: any) => {
|
||||||
|
if (!hasExplicitCounts) {
|
||||||
|
if (detail?.failed === true) {
|
||||||
|
existing.failureCount += 1;
|
||||||
|
} else {
|
||||||
|
existing.successCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (price) {
|
||||||
existing.cost += calculateCost({ ...detail, __modelName: modelName }, modelPrices);
|
existing.cost += calculateCost({ ...detail, __modelName: modelName }, modelPrices);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
modelMap.set(modelName, existing);
|
modelMap.set(modelName, existing);
|
||||||
|
|||||||
Reference in New Issue
Block a user