diff --git a/src/app/(dashboard)/dashboard/costs/quota-share/components/QuotaEndpointsCard.tsx b/src/app/(dashboard)/dashboard/costs/quota-share/components/QuotaEndpointsCard.tsx index 5a0a8da8ce..15fec0d288 100644 --- a/src/app/(dashboard)/dashboard/costs/quota-share/components/QuotaEndpointsCard.tsx +++ b/src/app/(dashboard)/dashboard/costs/quota-share/components/QuotaEndpointsCard.tsx @@ -1,11 +1,16 @@ "use client"; -import { useState, useMemo } from "react"; +import { useState, useMemo, useEffect } from "react"; import { useTranslations } from "next-intl"; import useEmailPrivacyStore from "@/store/emailPrivacyStore"; import { maskEmailLikeValue } from "@/shared/utils/maskEmail"; import Card from "@/shared/components/Card"; -import { quotaModelName, quotaGroupSlug } from "@/lib/quota/quotaModelNaming"; +import { + quotaModelName, + quotaGroupSlug, + parseQuotaModelName, + isQuotaModelName, +} from "@/lib/quota/quotaModelNaming"; // ───────────────────────────────────────────────────────────────────────────── // Local types (mirrors QuotaSharePageClient) @@ -88,6 +93,31 @@ export default function QuotaEndpointsCard({ const [previewModels, setPreviewModels] = useState(null); const [loadingPreview, setLoadingPreview] = useState(false); const [collapsed, setCollapsed] = useState(false); + const [realCombos, setRealCombos] = useState(null); + + // Fetch the REAL minted qtSd/* combo names so the default (no-key) view shows + // actual models instead of the representative PREVIEW_MODELS_BY_PROVIDER + // placeholders (model-a/b/c for providers not in the hardcoded map). + useEffect(() => { + let alive = true; + void fetch("/api/combos") + .then((r) => (r.ok ? r.json() : null)) + .then((body) => { + if (!alive) return; + const names = Array.isArray(body?.combos) + ? (body.combos as Array<{ name?: unknown }>) + .map((c) => (typeof c.name === "string" ? c.name : "")) + .filter((n) => n.length > 0 && isQuotaModelName(n)) + : []; + setRealCombos(names); + }) + .catch(() => { + if (alive) setRealCombos(null); + }); + return () => { + alive = false; + }; + }, []); // Anthropic-format providers (claude*/anthropic) can be called on the native // Messages endpoint too, so we surface POST /v1/messages when one is in scope. @@ -145,6 +175,37 @@ export default function QuotaEndpointsCard({ }); }, [groups, pools, connections]); + // Real qtSd combos grouped by group → provider (preferred over placeholders). + const realByGroup = useMemo< + Array<{ group: QuotaGroup; entries: Array<{ provider: string; models: string[] }> }> | null + >(() => { + if (!realCombos || realCombos.length === 0) return null; + const byGroupSlug = new Map>(); + for (const name of realCombos) { + const parsed = parseQuotaModelName(name); + if (!parsed) continue; + if (!byGroupSlug.has(parsed.groupSlug)) byGroupSlug.set(parsed.groupSlug, new Map()); + const provMap = byGroupSlug.get(parsed.groupSlug)!; + if (!provMap.has(parsed.provider)) provMap.set(parsed.provider, []); + provMap.get(parsed.provider)!.push(name); + } + return groups + .map((group) => { + const provMap = byGroupSlug.get(quotaGroupSlug(group.name)); + if (!provMap) return null; + const entries = [...provMap.entries()].map(([provider, models]) => ({ provider, models })); + return { group, entries }; + }) + .filter( + (g): g is { group: QuotaGroup; entries: Array<{ provider: string; models: string[] }> } => + g !== null + ); + }, [realCombos, groups]); + + // Default (no-key) view prefers the real combos; falls back to placeholders + // only when the combos fetch failed or returned nothing. + const viewByGroup = realByGroup ?? defaultByGroup; + // ── Key name with optional email masking ───────────────────────────────────── const keyLabel = (key: ApiKey) => { @@ -178,7 +239,7 @@ export default function QuotaEndpointsCard({ // ── Compute the combined default model count across all groups ──────────────── - const hasAnyDefaultModels = defaultByGroup.some((g) => + const hasAnyDefaultModels = viewByGroup.some((g) => g.entries.some((e) => e.models.length > 0) ); @@ -286,9 +347,9 @@ export default function QuotaEndpointsCard({ )} ) : hasData && hasAnyDefaultModels ? ( - // Default view: grouped by group → provider → model ids + // Default view: grouped by group → provider → real qtSd model ids
- {defaultByGroup.map(({ group, entries }) => { + {viewByGroup.map(({ group, entries }) => { if (entries.length === 0) return null; return (
diff --git a/tests/unit/quota-share-bugfixes-v388.test.ts b/tests/unit/quota-share-bugfixes-v388.test.ts index 1b5530a07e..56783e9246 100644 --- a/tests/unit/quota-share-bugfixes-v388.test.ts +++ b/tests/unit/quota-share-bugfixes-v388.test.ts @@ -140,6 +140,25 @@ test("B5b: endpoints card has a collapse/expand toggle", () => { ); }); +// ── B5 default view shows REAL combos, not model-a/b/c placeholders ────────── + +test("B5: endpoints default view uses real minted qtSd combos (not placeholders)", () => { + assert.ok( + endpointsSrc.includes('fetch("/api/combos")'), + "card must fetch real combos for the default view" + ); + assert.ok( + endpointsSrc.includes("isQuotaModelName") && endpointsSrc.includes("parseQuotaModelName"), + "must filter+parse real qtSd combo names" + ); + assert.ok( + endpointsSrc.includes("const realByGroup") && + endpointsSrc.includes("realByGroup ?? defaultByGroup") && + endpointsSrc.includes("viewByGroup.map"), + "default view must prefer real combos (realByGroup) over the placeholder map" + ); +}); + // ── i18n parity for every new key ──────────────────────────────────────────── test("i18n: new quotaShare keys exist in both en and pt-BR", () => {