From 63353644bd65b71fffa173d7bcf727e79bce8a84 Mon Sep 17 00:00:00 2001 From: joshistoast Date: Tue, 26 May 2026 15:18:01 -0600 Subject: [PATCH 1/2] feat(model manager): external providers UI tweaks --- .../ExternalProvidersForm.tsx | 103 +++++++++++++----- 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/ExternalProvidersForm.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/ExternalProvidersForm.tsx index 9e1b861cce4..c4a000c5c7f 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/ExternalProvidersForm.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/ExternalProvidersForm.tsx @@ -1,3 +1,4 @@ +import type { SystemStyleObject } from '@invoke-ai/ui-library'; import { Badge, Button, @@ -8,6 +9,7 @@ import { FormLabel, Heading, Input, + Switch, Text, Tooltip, } from '@invoke-ai/ui-library'; @@ -19,7 +21,9 @@ import { $installModelsTabIndex } from 'features/modelManagerV2/store/installMod import type { ChangeEvent } from 'react'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import type { IconType } from 'react-icons'; import { PiCheckBold, PiWarningBold } from 'react-icons/pi'; +import { SiAlibabacloud, SiBytedance, SiGooglegemini, SiOpenai } from 'react-icons/si'; import { useGetExternalProviderConfigsQuery, useResetExternalProviderConfigMutation, @@ -30,9 +34,33 @@ import type { ExternalProviderConfig, StarterModel } from 'services/api/types'; const PROVIDER_SORT_ORDER = ['gemini', 'openai', 'seedream', 'alibabacloud']; +function resolveProviderIcon(providerId: string): IconType | null { + const provider = providerId.toLowerCase(); + + switch (provider) { + case 'openai': + return SiOpenai; + case 'gemini': + return SiGooglegemini; + case 'seedream': + return SiBytedance; + case 'alibabacloud': + return SiAlibabacloud; + default: + return null; + } +} + +const FORM_CONTROL_SX: SystemStyleObject = { + flexDir: 'column', + alignItems: 'flex-start', + gap: 2, +}; + type ProviderCardProps = { provider: ExternalProviderConfig; onInstallModels: (providerId: string) => void; + iconResolver: (providerId: string) => IconType | null; }; type UpdatePayload = { @@ -47,7 +75,6 @@ export const ExternalProvidersForm = memo(() => { const { data: starterModels } = useGetStarterModelsQuery(); const [installModel] = useInstallModel(); const { getIsInstalled, buildModelInstallArg } = useBuildModelInstallArg(); - const tabIndex = useStore($installModelsTabIndex); const externalModelsByProvider = useMemo(() => { const groups = new Map(); @@ -107,8 +134,9 @@ export const ExternalProvidersForm = memo(() => { return ( - {t('modelManager.externalSetupTitle')} - {t('modelManager.externalSetupDescription')} + {t('modelManager.externalSetupTitle')} + {t('modelManager.externalSetupDescription')} + {t('modelManager.externalSetupFooter')} @@ -120,31 +148,29 @@ export const ExternalProvidersForm = memo(() => { ))} - {tabIndex === 3 && ( - - {t('modelManager.externalSetupFooter')} - - )} ); }); ExternalProvidersForm.displayName = 'ExternalProvidersForm'; -const ProviderCard = memo(({ provider, onInstallModels }: ProviderCardProps) => { +const ProviderCard = memo(({ provider, onInstallModels, iconResolver }: ProviderCardProps) => { const { t } = useTranslation(); const [apiKey, setApiKey] = useState(''); const [baseUrl, setBaseUrl] = useState(provider.base_url ?? ''); const [saveConfig, { isLoading }] = useSetExternalProviderConfigMutation(); const [resetConfig, { isLoading: isResetting }] = useResetExternalProviderConfigMutation(); + const [overrideBaseUrl, setOverrideBaseUrl] = useState(!!provider.base_url); useEffect(() => { setBaseUrl(provider.base_url ?? ''); + setOverrideBaseUrl(!!provider.base_url); }, [provider.base_url]); const handleSave = useCallback(() => { @@ -173,6 +199,7 @@ const ProviderCard = memo(({ provider, onInstallModels }: ProviderCardProps) => } if (result.base_url !== undefined) { setBaseUrl(result.base_url ?? ''); + setOverrideBaseUrl(!!result.base_url); } }); }, [apiKey, baseUrl, onInstallModels, provider.base_url, provider.provider_id, saveConfig]); @@ -183,6 +210,7 @@ const ProviderCard = memo(({ provider, onInstallModels }: ProviderCardProps) => .then((result) => { setApiKey(''); setBaseUrl(result.base_url ?? ''); + setOverrideBaseUrl(!!result.base_url); }); }, [provider.provider_id, resetConfig]); @@ -206,21 +234,34 @@ const ProviderCard = memo(({ provider, onInstallModels }: ProviderCardProps) => ); + const handleOverrideBaseUrlChange = useCallback((event: ChangeEvent) => { + event.stopPropagation(); + setOverrideBaseUrl(event.target.checked); + if (!event.target.checked) { + setBaseUrl(''); + } + }, []); + + const ProviderIcon = iconResolver(provider.provider_id); + return ( - + - - - {provider.provider_id} - - - {t('modelManager.externalProviderCardDescription', { providerId: provider.provider_id })} - + + {ProviderIcon && } + + + {provider.provider_id} + + + {t('modelManager.externalProviderCardDescription', { providerId: provider.provider_id })} + + {statusBadge} - + {t('modelManager.externalApiKey')} /> {t('modelManager.externalApiKeyHelper')} - - {t('modelManager.externalBaseUrl')} - + - {t('modelManager.externalBaseUrlHelper')} + Override Base URL - + +