From 51918cb5d4177bcebca4e8ba61db8100f76fd75a Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sat, 16 May 2026 11:31:02 -0300 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20providers=20page=20=E2=80=94?= =?UTF-8?q?=20custom=20section=20to=20top,=20smaller=20cards,=20free=20tie?= =?UTF-8?q?r=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves Compatible Providers to the top (before Expiration Banner) so users can add custom OpenAI/Anthropic compatible providers without scrolling. Reduces ProviderCard icon from 32px to 28px and increases grid density by one column at each breakpoint (gap-4→gap-3). Adds a curated Free Tier Providers section with 27 providers (OAuth/noAuth group + API-key free-tier group), positioned between Expiration Banner and OAuth Providers. Cards in the free section suppress the hasFree badge since context makes it implicit. --- .../providers/components/ProviderCard.tsx | 12 +- .../(dashboard)/dashboard/providers/page.tsx | 229 +++++++++++------- src/i18n/messages/en.json | 3 + 3 files changed, 157 insertions(+), 87 deletions(-) diff --git a/src/app/(dashboard)/dashboard/providers/components/ProviderCard.tsx b/src/app/(dashboard)/dashboard/providers/components/ProviderCard.tsx index ed6c405d32..86567d108a 100644 --- a/src/app/(dashboard)/dashboard/providers/components/ProviderCard.tsx +++ b/src/app/(dashboard)/dashboard/providers/components/ProviderCard.tsx @@ -149,20 +149,20 @@ export default function ProviderCard({
{staticIconPath ? ( {provider.name} ) : ( - + )}
diff --git a/src/app/(dashboard)/dashboard/providers/page.tsx b/src/app/(dashboard)/dashboard/providers/page.tsx index ab3a1c583b..fa2f5c30b0 100644 --- a/src/app/(dashboard)/dashboard/providers/page.tsx +++ b/src/app/(dashboard)/dashboard/providers/page.tsx @@ -532,6 +532,44 @@ export default function ProvidersPage() { searchQuery ); + const FREE_SECTION_IDS = new Set([ + "kiro", + "amazon-q", + "gemini-cli", + "qoder", + "pollinations", + "llm7", + "opencode", + "gemini", + "groq", + "cerebras", + "mistral", + "nvidia", + "openrouter", + "cloudflare-ai", + "together", + "siliconflow", + "deepseek", + "longcat", + "glhf", + "morph", + "bazaarlink", + "uncloseai", + "completions", + "freetheai", + "enally", + "puter", + "blackbox", + ]); + const freeSectionEntriesAll = [...oauthProviderEntriesAll, ...apiKeyProviderEntriesAll].filter( + (e) => FREE_SECTION_IDS.has(e.providerId) + ); + const freeSectionEntries = filterConfiguredProviderEntries( + freeSectionEntriesAll, + false, + searchQuery + ); + if (loading) { return (
@@ -568,6 +606,75 @@ export default function ProvidersPage() {
+ {/* API Key Compatible Providers — dynamic (OpenAI/Anthropic compatible) */} +
+
+

+ {t("compatibleProviders")}{" "} + + +

+
+ {(compatibleProviders.length > 0 || + anthropicCompatibleProviders.length > 0 || + ccCompatibleProviders.length > 0) && ( + + )} + {ccCompatibleProviderEnabled && ( + + )} + + +
+
+ {compatibleProviders.length === 0 && + anthropicCompatibleProviders.length === 0 && + ccCompatibleProviders.length === 0 ? ( +
+ + extension + +

{t("noCompatibleYet")}

+

{t("compatibleHint")}

+
+ ) : ( +
+ {compatibleProviderEntries.map( + ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => ( + handleToggleProvider(providerId, toggleAuthType, active)} + /> + ) + )} +
+ )} +
+ {/* Expiration Banner */} {expirations?.summary && (expirations.summary.expired > 0 || expirations.summary.expiringSoon > 0) && ( @@ -604,6 +711,34 @@ export default function ProvidersPage() {
)} + {/* Free Tier Providers */} + {freeSectionEntries.length > 0 && ( +
+
+

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

+

{t("freeTierProvidersDesc")}

+
+
+ {freeSectionEntries.map( + ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => ( + handleToggleProvider(providerId, toggleAuthType, active)} + /> + ) + )} +
+
+ )} + {/* OAuth Providers (including providers that expose free tiers via OAuth) */}
@@ -669,7 +804,7 @@ export default function ProvidersPage() {
-
+
{oauthProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => ( {t("llmProviders")} -
+
{llmProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => ( {t("aggregatorsGateways")} -
+
{aggregatorProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => ( {t("enterpriseCloud")} -
+
{enterpriseProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => ( {t("imageProviders")} -
+
{imageProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => ( {t("videoProviders")} -
+
{videoProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => ( {t("embeddingRerankProviders")} -
+
{embeddingRerankProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
-
+
{webCookieProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
-
+
{searchProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
-
+
{audioProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
-
+
{cloudAgentProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
-
+
{localProviderEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
-
+
{upstreamProxyEntries.map( ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => ( )} - {/* API Key Compatible Providers — dynamic (OpenAI/Anthropic compatible) */} -
-
-

- {t("compatibleProviders")}{" "} - - -

-
- {(compatibleProviders.length > 0 || - anthropicCompatibleProviders.length > 0 || - ccCompatibleProviders.length > 0) && ( - - )} - {ccCompatibleProviderEnabled && ( - - )} - - -
-
- {compatibleProviders.length === 0 && - anthropicCompatibleProviders.length === 0 && - ccCompatibleProviders.length === 0 ? ( -
- - extension - -

{t("noCompatibleYet")}

-

{t("compatibleHint")}

-
- ) : ( -
- {compatibleProviderEntries.map( - ({ providerId, provider, stats, displayAuthType, toggleAuthType }) => ( - handleToggleProvider(providerId, toggleAuthType, active)} - /> - ) - )} -
- )} -