add help banner to providers and virtual api keys

This commit is contained in:
miloschwartz
2026-08-14 12:23:50 -04:00
parent 193f6da8d1
commit d7bff686c0
5 changed files with 77 additions and 0 deletions
+5
View File
@@ -1678,6 +1678,9 @@
"commandVirtualApiKeys": "Virtual API Keys", "commandVirtualApiKeys": "Virtual API Keys",
"virtualApiKeysTitle": "Manage Virtual API Keys", "virtualApiKeysTitle": "Manage Virtual API Keys",
"virtualApiKeysDescription": "Create and manage manual API keys for AI Gateway access to public AI gateways", "virtualApiKeysDescription": "Create and manage manual API keys for AI Gateway access to public AI gateways",
"virtualApiKeysBannerTitle": "Identity Keys for Every User",
"virtualApiKeysBannerDescription": "Every user already has an identity key available at {keysUrl}. You can also manually generate keys here that grant direct access to public AI gateways.",
"virtualApiKeysBannerButtonText": "View Identity Keys",
"virtualApiKeys": "Virtual API Keys", "virtualApiKeys": "Virtual API Keys",
"virtualApiKeysSearch": "Search keys...", "virtualApiKeysSearch": "Search keys...",
"virtualApiKeysCreate": "Create Virtual API Key", "virtualApiKeysCreate": "Create Virtual API Key",
@@ -1742,6 +1745,8 @@
"myVirtualApiKeysViewSecretDescription": "This secret authenticates you to AI Gateway resources", "myVirtualApiKeysViewSecretDescription": "This secret authenticates you to AI Gateway resources",
"aiProvidersTitle": "AI Providers", "aiProvidersTitle": "AI Providers",
"aiProvidersDescription": "Connect model providers for AI workloads in this organization", "aiProvidersDescription": "Connect model providers for AI workloads in this organization",
"aiProvidersBannerTitle": "Connect Model Providers",
"aiProvidersBannerDescription": "Providers are the model backends Pangolin uses for AI workloads. Connect OpenAI, Anthropic, and other providers here, then attach them to public AI gateways so users can call models with identity-aware access, budgets, and logging.",
"aiProvidersAdd": "Add Provider", "aiProvidersAdd": "Add Provider",
"aiProvidersSearch": "Search providers...", "aiProvidersSearch": "Search providers...",
"aiProvidersEmpty": "No AI providers yet", "aiProvidersEmpty": "No AI providers yet",
@@ -1,3 +1,4 @@
import AiProvidersBanner from "@app/components/AiProvidersBanner";
import AiProvidersTable from "@app/components/AiProvidersTable"; import AiProvidersTable from "@app/components/AiProvidersTable";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { internal } from "@app/lib/api"; import { internal } from "@app/lib/api";
@@ -49,6 +50,8 @@ export default async function AiProvidersPage({ params, searchParams }: Props) {
description={t("aiProvidersDescription")} description={t("aiProvidersDescription")}
/> />
<AiProvidersBanner />
<AiProvidersTable <AiProvidersTable
orgId={orgId} orgId={orgId}
providers={providers.map((provider) => ({ providers={providers.map((provider) => ({
@@ -6,6 +6,7 @@ import { redirect } from "next/navigation";
import { cache } from "react"; import { cache } from "react";
import { GetOrgResponse } from "@server/routers/org"; import { GetOrgResponse } from "@server/routers/org";
import OrgProvider from "@app/providers/OrgProvider"; import OrgProvider from "@app/providers/OrgProvider";
import VirtualApiKeysBanner from "@app/components/VirtualApiKeysBanner";
import VirtualApiKeysTable, { import VirtualApiKeysTable, {
type VirtualApiKeyRow type VirtualApiKeyRow
} from "@app/components/VirtualApiKeysTable"; } from "@app/components/VirtualApiKeysTable";
@@ -142,6 +143,8 @@ export default async function VirtualApiKeysPage(
description={t("virtualApiKeysDescription")} description={t("virtualApiKeysDescription")}
/> />
<VirtualApiKeysBanner orgId={params.orgId} />
<OrgProvider org={org}> <OrgProvider org={org}>
<VirtualApiKeysTable <VirtualApiKeysTable
virtualApiKeys={rows} virtualApiKeys={rows}
+21
View File
@@ -0,0 +1,21 @@
"use client";
import { Sparkles } from "lucide-react";
import { useTranslations } from "next-intl";
import DismissableBanner from "./DismissableBanner";
export const AiProvidersBanner = () => {
const t = useTranslations();
return (
<DismissableBanner
storageKey="ai-providers-banner-dismissed"
version={1}
title={t("aiProvidersBannerTitle")}
titleIcon={<Sparkles className="w-5 h-5 text-primary" />}
description={t("aiProvidersBannerDescription")}
/>
);
};
export default AiProvidersBanner;
+45
View File
@@ -0,0 +1,45 @@
"use client";
import { Button } from "@app/components/ui/button";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { ArrowRight, KeyRound } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import DismissableBanner from "./DismissableBanner";
type VirtualApiKeysBannerProps = {
orgId: string;
};
export const VirtualApiKeysBanner = ({ orgId }: VirtualApiKeysBannerProps) => {
const t = useTranslations();
const { env } = useEnvContext();
const dashboardUrl = env.app.dashboardUrl?.replace(/\/$/, "") ?? "";
const keysUrl = dashboardUrl
? `${dashboardUrl}/${orgId}/keys`
: `/${orgId}/keys`;
return (
<DismissableBanner
storageKey="virtual-api-keys-banner-dismissed"
version={1}
title={t("virtualApiKeysBannerTitle")}
titleIcon={<KeyRound className="w-5 h-5 text-primary" />}
description={t("virtualApiKeysBannerDescription", { keysUrl })}
>
<Link href={`/${orgId}/keys`}>
<Button
variant="outline"
size="sm"
className="gap-2 hover:bg-primary/10 hover:border-primary/50 transition-colors"
>
{t("virtualApiKeysBannerButtonText")}
<ArrowRight className="w-4 h-4" />
</Button>
</Link>
</DismissableBanner>
);
};
export default VirtualApiKeysBanner;