diff --git a/src/app/(dashboard)/dashboard/providers/page.tsx b/src/app/(dashboard)/dashboard/providers/page.tsx index 6185f4412a..ad77339f55 100644 --- a/src/app/(dashboard)/dashboard/providers/page.tsx +++ b/src/app/(dashboard)/dashboard/providers/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect, useCallback } from "react"; -import { CardSkeleton, Badge, Button, Input, Toggle } from "@/shared/components"; +import { Card, CardSkeleton, Badge, Button, Input, Toggle } from "@/shared/components"; import { FREE_PROVIDERS, OAUTH_PROVIDERS, @@ -566,10 +566,23 @@ export default function ProvidersPage() { ); const freeSectionEntries = filterConfiguredProviderEntries( freeSectionEntriesAll, - false, + showConfiguredOnly, searchQuery ); + const oauthOnlyEntriesAll = oauthProviderEntriesAll.filter((e) => e.toggleAuthType === "oauth"); + const summaryStats = { + all: { + configured: + oauthProviderEntriesAll.filter((e) => Number(e.stats?.total || 0) > 0).length + + apiKeyProviderEntriesAll.filter((e) => Number(e.stats?.total || 0) > 0).length, + total: oauthProviderEntriesAll.length + apiKeyProviderEntriesAll.length, + }, + free: countConfigured(freeSectionEntriesAll), + oauth: countConfigured(oauthOnlyEntriesAll), + apikey: countConfigured(apiKeyProviderEntriesAll), + }; + if (loading) { return (
@@ -581,51 +594,102 @@ export default function ProvidersPage() { return (
- {/* Search Bar + Dot Legend */} -
-
- - search - - setSearchQuery(e.target.value)} - placeholder={t("searchProviders")} - aria-label={t("searchProviders")} - className="pl-10 pr-10" - /> - {searchQuery && ( + {/* Provider Summary Card */} + +
+ {/* Row 1: Search + Controls */} +
+
+ + search + + setSearchQuery(e.target.value)} + placeholder={t("searchProviders")} + aria-label={t("searchProviders")} + className="pl-10 pr-10" + /> + {searchQuery && ( + + )} +
+
+ - )} +
+ + {/* Row 2: Legend */} +
+ {( + [ + ["bg-green-500", tc("free")], + ["bg-blue-500", t("oauthLabel")], + ["bg-amber-500", t("apiKeyLabel")], + ["bg-orange-500", t("compatibleLabel")], + ["bg-purple-500", t("webCookieProviders")], + ["bg-teal-500", t("searchProvidersHeading")], + ["bg-rose-500", t("audioProvidersHeading")], + ["bg-emerald-500", t("localProviders")], + ["bg-indigo-500", t("upstreamProxyProviders")], + ["bg-violet-500", t("cloudAgentProviders")], + ] as [string, string][] + ).map(([color, label]) => ( + + + {label} + + ))} +
+ + {/* Divider + Stats */} +
+ {( + [ + [null, t("providerSummaryAll"), summaryStats.all], + ["bg-green-500", tc("free"), summaryStats.free], + ["bg-blue-500", t("oauthLabel"), summaryStats.oauth], + ["bg-amber-500", t("apiKeyLabel"), summaryStats.apikey], + ] as [string | null, string, { configured: number; total: number }][] + ).map(([color, label, stat]) => ( + + {color && } + {label} + + {stat.configured} + /{stat.total} + + + ))} +
-
- {( - [ - ["bg-green-500", tc("free")], - ["bg-blue-500", t("oauthLabel")], - ["bg-amber-500", t("apiKeyLabel")], - ["bg-orange-500", t("compatibleLabel")], - ["bg-purple-500", t("webCookieProviders")], - ["bg-teal-500", t("searchProvidersHeading")], - ["bg-rose-500", t("audioProvidersHeading")], - ["bg-emerald-500", t("localProviders")], - ["bg-indigo-500", t("upstreamProxyProviders")], - ["bg-violet-500", t("cloudAgentProviders")], - ] as [string, string][] - ).map(([color, label]) => ( - - - {label} - - ))} -
-
+
{/* API Key Compatible Providers — dynamic (OpenAI/Anthropic compatible) */}
@@ -732,13 +796,30 @@ export default function ProvidersPage() { {/* Free Tier Providers */} {freeSectionEntries.length > 0 && (
-
-

- {t("freeTierProviders")} - - -

-

{t("freeTierProvidersDesc")}

+
+
+

+ {t("freeTierProviders")} + + +

+

{t("freeTierProvidersDesc")}

+
+
{freeSectionEntries.map( @@ -766,13 +847,6 @@ export default function ProvidersPage() {
-