import ModelProviderConfig from "@/app/workspace/providers/views/modelProviderConfig"; import FullPageLoader from "@/components/fullPageLoader"; import { Badge } from "@/components/ui/badge"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { DefaultNetworkConfig, DefaultPerformanceConfig } from "@/lib/constants/config"; import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons"; import { ProviderLabels, ProviderNames } from "@/lib/constants/logs"; import { getErrorMessage, setSelectedProvider, useAppDispatch, useAppSelector, useCreateProviderMutation, useGetProvidersQuery, useLazyGetProviderQuery, } from "@/lib/store"; import { KnownProvider, ModelProviderName, ProviderStatus } from "@/lib/types/config"; import { cn } from "@/lib/utils"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import { AlertCircle } from "lucide-react"; import { useNavigate } from "@tanstack/react-router"; import { useQueryState } from "nuqs"; import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import AddCustomProviderSheet from "./dialogs/addNewCustomProviderSheet"; import ConfirmDeleteProviderDialog from "./dialogs/confirmDeleteProviderDialog"; import ConfirmRedirectionDialog from "./dialogs/confirmRedirection"; import { AddProviderDropdown } from "./views/addProviderDropdown"; import { ProvidersEmptyState } from "./views/providersEmptyState"; export default function Providers() { const dispatch = useAppDispatch(); const navigate = useNavigate(); const hasProvidersAccess = useRbac(RbacResource.ModelProvider, RbacOperation.View); const hasSettingsOnly = useRbac(RbacResource.Settings, RbacOperation.View); const hasProviderCreateAccess = useRbac(RbacResource.ModelProvider, RbacOperation.Create); // Redirect Settings-only users to Custom pricing tab useEffect(() => { if (!hasProvidersAccess && hasSettingsOnly) { navigate({ to: "/workspace/custom-pricing", replace: true }); } }, [hasProvidersAccess, hasSettingsOnly, navigate]); const selectedProvider = useAppSelector((state) => state.provider.selectedProvider); const providerFormIsDirty = useAppSelector((state) => state.provider.isDirty); const [showRedirectionDialog, setShowRedirectionDialog] = useState(false); const [showDeleteProviderDialog, setShowDeleteProviderDialog] = useState(false); const [pendingRedirection, setPendingRedirection] = useState(undefined); const [showCustomProviderSheet, setShowCustomProviderSheet] = useState(false); const [provider, setProvider] = useQueryState("provider"); const { data: savedProviders, isLoading: isLoadingProviders } = useGetProvidersQuery(); const [getProvider, { isLoading: isLoadingProvider }] = useLazyGetProviderQuery(); const [createProvider] = useCreateProviderMutation(); const configuredProviders = (savedProviders ?? []).slice().sort((a, b) => a.name.localeCompare(b.name)); const configuredProviderNamesArr = configuredProviders.map((p) => p.name); const configuredProviderNamesKey = JSON.stringify(configuredProviderNamesArr); const existingInSidebarNames = new Set(configuredProviders.map((p) => p.name)); const knownProviders = ProviderNames.map((name) => ({ name })); useEffect(() => { if (!provider) return; const newSelectedProvider = configuredProviders.find((p) => p.name === provider); if (newSelectedProvider) { dispatch(setSelectedProvider(newSelectedProvider)); } getProvider(provider) .unwrap() .then((providerInfo) => { dispatch(setSelectedProvider(providerInfo)); }) .catch((err) => { if (err.status === 404) { dispatch( setSelectedProvider({ name: provider as ModelProviderName, concurrency_and_buffer_size: DefaultPerformanceConfig, network_config: DefaultNetworkConfig, custom_provider_config: undefined, proxy_config: undefined, send_back_raw_request: undefined, send_back_raw_response: undefined, provider_status: "error", }), ); return; } toast.error("Something went wrong", { description: `We encountered an error while getting provider config: ${getErrorMessage(err)}`, }); }); }, [provider, isLoadingProviders]); useEffect(() => { if (selectedProvider || configuredProviders.length === 0 || provider) return; setProvider(configuredProviders[0].name); // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedProvider, configuredProviderNamesKey]); // When current provider is no longer configured (e.g. all keys deleted), switch to another configured provider useEffect(() => { if (!provider || configuredProviderNamesArr.length === 0) return; const isCurrentConfigured = configuredProviderNamesArr.includes(provider as ModelProviderName); if (!isCurrentConfigured) { setProvider(configuredProviderNamesArr[0]); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [provider, configuredProviderNamesKey]); if (!hasProvidersAccess && hasSettingsOnly) { return ; } if (isLoadingProviders) { return ; } const handleSelectKnownProvider = async (name: string) => { try { await createProvider({ provider: name as ModelProviderName }).unwrap(); setProvider(name); } catch (err: any) { if (err?.status === 409) { setProvider(name); return; } toast.error("Failed to add provider", { description: getErrorMessage(err), }); } }; if (configuredProviders.length === 0) { return (
setShowCustomProviderSheet(true)} variant="empty" /> } /> setShowCustomProviderSheet(false)} onSave={(providerName) => { setTimeout(() => setProvider(providerName), 300); setShowCustomProviderSheet(false); }} />
); } return (
setShowDeleteProviderDialog(false)} onDelete={() => { const next = configuredProviders.filter((p) => p.name !== selectedProvider?.name)[0]; setProvider(next?.name ?? null); setShowDeleteProviderDialog(false); }} /> setShowRedirectionDialog(false)} onContinue={() => { setShowRedirectionDialog(false); if (pendingRedirection) setProvider(pendingRedirection); setPendingRedirection(undefined); }} /> setShowCustomProviderSheet(false)} onSave={(providerName) => { setTimeout(() => setProvider(providerName), 300); setShowCustomProviderSheet(false); }} />
{/* Configured Providers (standard with keys + custom) */} {configuredProviders.length > 0 && (
Configured Providers
{configuredProviders.map((p) => { const isCustom = !ProviderNames.includes(p.name as KnownProvider); const label = isCustom ? p.name : ProviderLabels[p.name as keyof typeof ProviderLabels]; return (
{ e.preventDefault(); e.stopPropagation(); if (providerFormIsDirty) { setPendingRedirection(p.name); setShowRedirectionDialog(true); return; } setProvider(p.name); }} > {isCustom && ( CUSTOM )}
); })}
)}
setShowCustomProviderSheet(true)} />
{isLoadingProvider && (
)} {!selectedProvider && (
Select a provider
)} {!isLoadingProvider && selectedProvider && ( setShowDeleteProviderDialog(true)} /> )}
); } function TruncatedName({ name }: { name: string }) { const textRef = useRef(null); const [isTruncated, setIsTruncated] = useState(false); const checkTruncation = useCallback(() => { const el = textRef.current; if (el) { setIsTruncated(el.scrollWidth > el.clientWidth); } }, []); useEffect(() => { checkTruncation(); window.addEventListener("resize", checkTruncation); return () => window.removeEventListener("resize", checkTruncation); }, [checkTruncation, name]); const inner = (
{name}
); if (!isTruncated) return inner; return ( {inner} {name} ); } function ProviderStatusBadge({ status }: { status: ProviderStatus }) { return status != "active" ? ( {status === "error" ? "Provider could not be initialized" : "Provider is deleted"} ) : null; } function KeyDiscoveryFailedBadge({ provider, }: { provider: { status?: string; description?: string; }; }) { const providerFailed = provider.status === "list_models_failed"; if (!providerFailed) return null; return ( {provider.description || "Provider model discovery failed."} ); }