From 2c898b030b621d079deae854cf4a7bdc3ad1c730 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 25 May 2026 08:56:13 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=91=20feat:=20Surface=20User-Provided?= =?UTF-8?q?=20API=20Keys=20In=20Settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a dedicated API Keys tab in user settings that lists endpoints configured with `apiKey: "user_provided"` and reuses SetKeyDialog for key entry and revocation. The tab is only shown when at least one endpoint requires a user key. Restores access to user-provided key entry when `modelSpecs.list` is configured, since the sidebar endpoints dropdown is hidden by default in that case (closes #12743). --- client/src/components/Nav/Settings.tsx | 47 +++++++++- .../Nav/SettingsTabs/APIKeys/APIKeyRow.tsx | 94 +++++++++++++++++++ .../Nav/SettingsTabs/APIKeys/APIKeys.tsx | 53 +++++++++++ .../Nav/SettingsTabs/APIKeys/index.ts | 1 + .../src/components/Nav/SettingsTabs/index.ts | 1 + client/src/locales/en/translation.json | 4 + packages/data-provider/src/config.ts | 4 + 7 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 client/src/components/Nav/SettingsTabs/APIKeys/APIKeyRow.tsx create mode 100644 client/src/components/Nav/SettingsTabs/APIKeys/APIKeys.tsx create mode 100644 client/src/components/Nav/SettingsTabs/APIKeys/index.ts diff --git a/client/src/components/Nav/Settings.tsx b/client/src/components/Nav/Settings.tsx index f5574d5616..2f49229042 100644 --- a/client/src/components/Nav/Settings.tsx +++ b/client/src/components/Nav/Settings.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useState, useRef } from 'react'; +import React, { useEffect, useMemo, useState, useRef } from 'react'; import * as Tabs from '@radix-ui/react-tabs'; import { SettingsTabValues } from 'librechat-data-provider'; -import { MessageSquare, Command, DollarSign, Info } from 'lucide-react'; +import { MessageSquare, Command, DollarSign, Info, KeyRound } from 'lucide-react'; import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react'; import { GearIcon, @@ -21,22 +21,48 @@ import { Data, Balance, Account, + APIKeys, About, } from './SettingsTabs'; import usePersonalizationAccess from '~/hooks/usePersonalizationAccess'; import { useLocalize, TranslationKeys } from '~/hooks'; -import { useGetStartupConfig } from '~/data-provider'; +import { useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider'; import { cn } from '~/utils'; export default function Settings({ open, onOpenChange }: TDialogProps) { const isSmallScreen = useMediaQuery('(max-width: 767px)'); const { data: startupConfig } = useGetStartupConfig(); + const { data: endpointsConfig } = useGetEndpointsQuery(); const localize = useLocalize(); const [activeTab, setActiveTab] = useState(SettingsTabValues.GENERAL); const tabRefs = useRef({}); const { hasAnyPersonalizationFeature, hasMemoryOptOut } = usePersonalizationAccess(); const aboutEnabled = startupConfig?.interface?.buildInfo !== false; + const hasUserProvidedEndpoint = useMemo(() => { + if (!endpointsConfig) { + return false; + } + const values = Object.values(endpointsConfig); + for (let i = 0; i < values.length; i++) { + const config = values[i]; + if (!config) { + continue; + } + if ( + config.userProvide || + config.userProvideURL || + config.userProvideAccessKeyId || + config.userProvideSecretAccessKey || + config.userProvideSessionToken || + config.userProvideBearerToken + ) { + return true; + } + } + return false; + }, [endpointsConfig]); + useEffect(() => { if (!aboutEnabled && activeTab === SettingsTabValues.ABOUT) { setActiveTab(SettingsTabValues.GENERAL); @@ -53,6 +79,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) { SettingsTabValues.DATA, ...(startupConfig?.balance?.enabled ? [SettingsTabValues.BALANCE] : []), SettingsTabValues.ACCOUNT, + ...(hasUserProvidedEndpoint ? [SettingsTabValues.API_KEYS] : []), ...(aboutEnabled ? [SettingsTabValues.ABOUT] : []), ]; const currentIndex = tabs.indexOf(activeTab); @@ -130,6 +157,15 @@ export default function Settings({ open, onOpenChange }: TDialogProps) { icon: , label: 'com_nav_setting_account', }, + ...(hasUserProvidedEndpoint + ? [ + { + value: SettingsTabValues.API_KEYS, + icon: