fix(dashboard): label usage-by-provider column as Provider / API Key

This commit is contained in:
2026-08-22 14:34:02 +07:00
parent eedad6c5ea
commit 55f10c11e5

View File

@@ -280,7 +280,7 @@ const ENDPOINT_COLUMNS = [
];
const PROVIDER_COLUMNS = [
{ field: "provider", label: "Provider" },
{ field: "provider", label: "Provider / API Key" },
{ field: "requests", label: "Requests", align: "right" },
{ field: "lastUsed", label: "Last Used", align: "right" },
];
@@ -321,10 +321,13 @@ export default function UsageStats({
// Resolve a provider key (built-in id, alias, or custom node id like
// "openai-compatible-chat-096baf9a-...") to a human-friendly name.
// Prefers the connected-providers state (nodeName > connection name)
// and falls back to the module-level registry lookup.
// Prefers the canonical provider name from the registry so e.g. "commandcode"
// shows "Command Code" instead of a connection/account name; falls back to
// the connected-providers state (nodeName > connection name) for custom nodes.
const getProviderLabel = useCallback((providerId) => {
if (!providerId) return "Unknown Provider";
const known = getProviderByAlias(providerId) || AI_PROVIDERS[providerId] || FREE_PROVIDERS[providerId];
if (known?.name) return known.name;
const match = providers.find((p) => p.provider === providerId || p.id === providerId);
if (match) return match.nodeName || match.name || providerId;
return resolveProviderLabel(providerId);
@@ -591,12 +594,55 @@ export default function UsageStats({
};
}
case "provider": {
// Detail rows = the API keys used per provider (grouped from byApiKey),
// so expanding a provider shows its keys instead of repeating the provider.
// byApiKey[].provider is the display name (node name for custom nodes);
// map display names back to provider ids via the connected-providers state.
const displayToId = {};
for (const p of providers) {
if (p.nodeName) displayToId[p.nodeName] = p.provider;
if (p.name && !displayToId[p.name]) displayToId[p.name] = p.provider;
}
const keyGroupsByProvider = {};
for (const ak of Object.values(sortData(stats.byApiKey, {}, sortBy, sortOrder))) {
const pid = displayToId[ak.provider] || ak.provider || "unknown";
const kname = ak.keyName || ak.apiKeyMasked || "Unknown Key";
const bucket = keyGroupsByProvider[pid] || (keyGroupsByProvider[pid] = {});
const g = bucket[kname] || (bucket[kname] = {
requests: 0,
promptTokens: 0,
completionTokens: 0,
cachedTokens: 0,
totalTokens: 0,
cost: 0,
inputCost: 0,
cachedCost: 0,
outputCost: 0,
lastUsed: null,
keyName: kname,
});
g.requests += ak.requests || 0;
g.promptTokens += ak.promptTokens || 0;
g.completionTokens += ak.completionTokens || 0;
g.cachedTokens += ak.cachedTokens || 0;
g.totalTokens += ak.totalTokens || 0;
g.cost += ak.cost || 0;
g.inputCost += ak.inputCost || 0;
g.cachedCost += ak.cachedCost || 0;
g.outputCost += ak.outputCost || 0;
if (ak.lastUsed && (!g.lastUsed || new Date(ak.lastUsed) > new Date(g.lastUsed)))
g.lastUsed = ak.lastUsed;
}
const groupedData = groupDataByKey(
sortData(stats.byProvider, {}, sortBy, sortOrder),
"provider",
).map((group) => ({
...group,
items: Object.values(keyGroupsByProvider[group.groupKey] || {}),
}));
return {
columns: PROVIDER_COLUMNS,
groupedData: groupDataByKey(
sortData(stats.byProvider, {}, sortBy, sortOrder),
"provider",
),
groupedData,
storageKey: "usage-stats:expanded-providers",
emptyMessage: "No usage recorded yet.",
renderSummaryCells: (group) => (
@@ -612,7 +658,7 @@ export default function UsageStats({
renderDetailCells: (item) => (
<>
<td className="px-6 py-3 font-medium">
{getProviderLabel(item.provider || item.key)}
{item.keyName}
</td>
<td className="px-6 py-3 text-right">{fmt(item.requests)}</td>
<td className="px-6 py-3 text-right text-text-muted whitespace-nowrap">
@@ -664,7 +710,7 @@ export default function UsageStats({
};
}
}
}, [stats, tableView, sortBy, sortOrder, getProviderLabel]);
}, [stats, tableView, sortBy, sortOrder, getProviderLabel, providers]);
if (!stats && !loading)
return (