diff --git a/messages/en-US.json b/messages/en-US.json index 52458fcbe..e41d06f7b 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1678,6 +1678,9 @@ "commandVirtualApiKeys": "Virtual API Keys", "virtualApiKeysTitle": "Manage Virtual API Keys", "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", "virtualApiKeysSearch": "Search keys...", "virtualApiKeysCreate": "Create Virtual API Key", @@ -1687,7 +1690,7 @@ "virtualApiKeysName": "Name", "virtualApiKeysDescriptionOptional": "Description (optional)", "virtualApiKeysAssociateUserOptional": "Associate User (optional)", - "virtualApiKeysAssociateUserDescription": "Associate this key with a user to track usage.", + "virtualApiKeysAssociateUserDescription": "Associate this key with a user to track usage. Once created, this key immediately grants access to the selected public AI gateways. You do not need to associate a user to manually to the resource. The key will also show up in the user's profile.", "virtualApiKeysAllResources": "All public AI gateways", "virtualApiKeysAllResourcesDescription": "Allow this key to access every public AI gateway in the organization", "virtualApiKeysSelectResources": "Public AI Gateways", @@ -1742,6 +1745,8 @@ "myVirtualApiKeysViewSecretDescription": "This secret authenticates you to AI Gateway resources", "aiProvidersTitle": "AI Providers", "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", "aiProvidersSearch": "Search providers...", "aiProvidersEmpty": "No AI providers yet", diff --git a/src/app/[orgId]/settings/ai-providers/page.tsx b/src/app/[orgId]/settings/ai-providers/page.tsx index 024170590..572ce90d4 100644 --- a/src/app/[orgId]/settings/ai-providers/page.tsx +++ b/src/app/[orgId]/settings/ai-providers/page.tsx @@ -1,3 +1,4 @@ +import AiProvidersBanner from "@app/components/AiProvidersBanner"; import AiProvidersTable from "@app/components/AiProvidersTable"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { internal } from "@app/lib/api"; @@ -49,6 +50,8 @@ export default async function AiProvidersPage({ params, searchParams }: Props) { description={t("aiProvidersDescription")} /> + + ({ diff --git a/src/app/[orgId]/settings/virtual-api-keys/page.tsx b/src/app/[orgId]/settings/virtual-api-keys/page.tsx index ba8f4ea01..28a73253e 100644 --- a/src/app/[orgId]/settings/virtual-api-keys/page.tsx +++ b/src/app/[orgId]/settings/virtual-api-keys/page.tsx @@ -6,6 +6,7 @@ import { redirect } from "next/navigation"; import { cache } from "react"; import { GetOrgResponse } from "@server/routers/org"; import OrgProvider from "@app/providers/OrgProvider"; +import VirtualApiKeysBanner from "@app/components/VirtualApiKeysBanner"; import VirtualApiKeysTable, { type VirtualApiKeyRow } from "@app/components/VirtualApiKeysTable"; @@ -142,6 +143,8 @@ export default async function VirtualApiKeysPage( description={t("virtualApiKeysDescription")} /> + + + icon: }, { title: "sidebarLogsAiUsage", diff --git a/src/components/AiProvidersBanner.tsx b/src/components/AiProvidersBanner.tsx new file mode 100644 index 000000000..e82bd8bb7 --- /dev/null +++ b/src/components/AiProvidersBanner.tsx @@ -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 ( + } + description={t("aiProvidersBannerDescription")} + /> + ); +}; + +export default AiProvidersBanner; diff --git a/src/components/VirtualApiKeysBanner.tsx b/src/components/VirtualApiKeysBanner.tsx new file mode 100644 index 000000000..dd2aa1186 --- /dev/null +++ b/src/components/VirtualApiKeysBanner.tsx @@ -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 ( + } + description={t("virtualApiKeysBannerDescription", { keysUrl })} + > + + + + + ); +}; + +export default VirtualApiKeysBanner;