fix(dashboard): show provider names instead of node ids in Usage by Provider

The Usage by Provider table (and related provider cells) rendered raw
provider keys, so requests routed through a custom OpenAI-compatible node
appeared as "openai-compatible-chat-096baf9a-6433-4f22-b079-20371092555a"
instead of the node's display name.

- UsageStats: add getProviderLabel() resolving a provider key (built-in id,
  alias, or custom node id) to a friendly name via the providers state
  (nodeName > connection name) and AI_PROVIDERS/getProviderByAlias fallback
- Apply the label in group headers, detail rows, badges, and recent requests
  (raw key kept as hover title)
- UsageTable: accept providerLabel prop, use it for provider group headers
This commit is contained in:
2026-08-17 14:24:13 +07:00
parent b5c0f10610
commit c61dc6de46
2 changed files with 28 additions and 10 deletions

View File

@@ -2,7 +2,7 @@
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import { FREE_PROVIDERS, AI_PROVIDERS } from "@/shared/constants/providers";
import { FREE_PROVIDERS, AI_PROVIDERS, getProviderByAlias } from "@/shared/constants/providers";
// Keep providers without serviceKinds (default LLM) or with "llm" in serviceKinds
function isLLMProvider(id) {
@@ -104,7 +104,7 @@ function RecentRequests({ requests = [] }) {
className="py-1.5 font-mono truncate max-w-[80px] text-text-muted"
title={r.provider}
>
{r.provider || "—"}
{getProviderLabel(r.provider) || "—"}
</td>
<td className="py-1.5 text-right whitespace-nowrap">
<span className="text-primary">
@@ -310,6 +310,17 @@ export default function UsageStats({
const [tableView, setTableView] = useState("provider");
const [viewMode, setViewMode] = useState("costs");
const [providers, setProviders] = useState([]);
// Resolve a provider key (built-in id, alias, or custom node id like
// "openai-compatible-chat-096baf9a-...") to a human-friendly name.
const getProviderLabel = useCallback((providerId) => {
if (!providerId) return "Unknown Provider";
const match = providers.find((p) => p.provider === providerId || p.id === providerId);
if (match) return match.nodeName || match.name || providerId;
// Fallback: known built-in provider (by id, alias, or display prefix)
const known = getProviderByAlias(providerId) || AI_PROVIDERS[providerId] || FREE_PROVIDERS[providerId];
return known?.name || known?.uiAlias || known?.alias || providerId;
}, [providers]);
const [periodLocal, setPeriodLocal] = useState("today");
const isInitialLoad = useRef(true);
const hasLoadedStats = useRef(false);
@@ -458,7 +469,7 @@ export default function UsageStats({
variant={item.pending > 0 ? "primary" : "neutral"}
size="sm"
>
{item.provider}
{getProviderLabel(item.provider)}
</Badge>
</td>
<td className="px-6 py-3 text-right">{fmt(item.requests)}</td>
@@ -522,7 +533,7 @@ export default function UsageStats({
variant={item.pending > 0 ? "primary" : "neutral"}
size="sm"
>
{item.provider}
{getProviderLabel(item.provider)}
</Badge>
</td>
<td className="px-6 py-3 text-right">{fmt(item.requests)}</td>
@@ -560,7 +571,7 @@ export default function UsageStats({
<td className="px-6 py-3">{item.rawModel}</td>
<td className="px-6 py-3">
<Badge variant="neutral" size="sm">
{item.provider}
{getProviderLabel(item.provider)}
</Badge>
</td>
<td className="px-6 py-3 text-right">{fmt(item.requests)}</td>
@@ -593,7 +604,7 @@ export default function UsageStats({
renderDetailCells: (item) => (
<>
<td className="px-6 py-3 font-medium">
{item.provider || item.key}
{getProviderLabel(item.provider || item.key)}
</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">
@@ -633,7 +644,7 @@ export default function UsageStats({
<td className="px-6 py-3">{item.rawModel}</td>
<td className="px-6 py-3">
<Badge variant="neutral" size="sm">
{item.provider}
{getProviderLabel(item.provider)}
</Badge>
</td>
<td className="px-6 py-3 text-right">{fmt(item.requests)}</td>
@@ -645,7 +656,7 @@ export default function UsageStats({
};
}
}
}, [stats, tableView, sortBy, sortOrder]);
}, [stats, tableView, sortBy, sortOrder, getProviderLabel]);
if (!stats && !loading)
return (
@@ -752,6 +763,7 @@ export default function UsageStats({
renderSummaryCells={activeTableConfig.renderSummaryCells}
renderDetailCells={activeTableConfig.renderDetailCells}
emptyMessage={activeTableConfig.emptyMessage}
providerLabel={getProviderLabel}
/>
)}
</div>