From 7210a73e1fbc183561696b5e1dede0ffb8b9809f Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Thu, 14 May 2026 12:58:26 -0300 Subject: [PATCH] fix(dashboard/api-manager): use getProviderDisplayName for owned_by labels (#2021) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Custom OpenAI-/Anthropic-compatible providers ship with synthetic IDs like "openai-compatible-chat-". ApiManagerPageClient was grouping models by raw `model.owned_by`, so the user saw the full synthetic id in the model picker. Wrap `model.owned_by` with the existing centralized `getProviderDisplayName` helper (already used by Endpoint, Health, and Combos pages). The helper detects the dynamic-compatible pattern and renders it as "Compatible (openai)" / "Compatible (anthropic)" — a small but meaningful improvement that takes the dashboard one step closer to issue #260's "no raw IDs in the UI" goal. Showing the user-entered node name ("Poe") instead of the generic "Compatible (openai)" label remains a follow-up (requires fetching /api/provider-nodes here and matching by id/prefix); tracked separately. Reported by @pulyankote with a complete surface-by-surface table. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 1 + .../dashboard/api-manager/ApiManagerPageClient.tsx | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f33614c55a..702a5e6f56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - **fix(translator/claude-to-openai):** stop including `cache_creation_input_tokens` in `prompt_tokens`. Anthropic pads short prompts up to a 1024-token minimum on cache creation, so a 2-token `"hi"` could be reported as ~2008 `prompt_tokens` and inflate downstream billing (Sub2API/NewAPI/OneAPI) ~250x. `prompt_tokens` now matches the dashboard "Total In" (`input + cache_read`); `cache_creation_tokens` is exposed separately in `prompt_tokens_details.cache_creation_tokens` for auditing. (#2215) - **fix(ui/claude-extra-usage):** clarify the toggle-success notification text to spell out the toggle→effect relationship ("Claude extra-usage blocking enabled/disabled" instead of the ambiguous "blocked/allowed"). (#2157) - **fix(providers/qoder):** disambiguate the "Local CLI runtime is not installed" error when a user pastes a Personal Access Token but the connection is in OAuth/CLI-flavored mode. The test route now surfaces a single actionable message ("switch this connection to API Key auth") instead of cascading CLI + 401 errors. (#2247) +- **fix(dashboard/api-manager):** route custom OpenAI-/Anthropic-compatible provider IDs through `getProviderDisplayName` so the model grouping label shows `Compatible (openai)` instead of leaking the raw synthetic `openai-compatible-chat-` value. (#2021) ### Changed diff --git a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx index 2e28626508..24723a461e 100644 --- a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx +++ b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, useMemo, useCallback, memo } from "react"; import { Card, Button, Input, Modal, CardSkeleton } from "@/shared/components"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; import { useTranslations } from "next-intl"; +import { getProviderDisplayName } from "@/lib/display/names"; // Constants for validation const MAX_KEY_NAME_LENGTH = 200; @@ -434,16 +435,19 @@ export default function ApiManagerPageClient() { // Debounced search for performance const debouncedSearchModel = useDebouncedValue(searchModel, 150); - // Group models by provider + // Group models by provider (issue #2021 — use centralized display helper so + // custom OpenAI-/Anthropic-compatible providers don't leak raw synthetic + // ids like "openai-compatible-chat-" into the grouping label) const modelsByProvider = useMemo((): ProviderGroup[] => { const grouped: Record = {}; for (const model of allModels) { - const provider = model.owned_by || t("unknownProvider"); + const provider = + getProviderDisplayName(model.owned_by) || model.owned_by || t("unknownProvider"); if (!grouped[provider]) grouped[provider] = []; grouped[provider].push(model); } return Object.entries(grouped).sort((a, b) => a[0].localeCompare(b[0])); - }, [allModels]); + }, [allModels, t]); // Filter models based on debounced search const filteredModelsByProvider = useMemo((): ProviderGroup[] => {