From 473b465efbf94d259763557ec1327bf916ead1ab Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Mon, 18 Aug 2025 18:03:36 +0800 Subject: [PATCH] model auth --- .../base/form/components/base/base-field.tsx | 7 +- web/app/components/base/form/types.ts | 1 + .../model-provider-page/declarations.ts | 2 +- .../model-provider-page/hooks.ts | 2 + .../model-provider-page/index.tsx | 8 - .../add-credential-in-load-balancing.tsx | 47 +++- .../model-auth/add-custom-model.tsx | 41 ++- .../model-auth/authorized/authorized-item.tsx | 69 +++-- .../model-auth/authorized/credential-item.tsx | 33 +-- .../model-auth/authorized/index.tsx | 62 ++-- .../model-auth/config-model.tsx | 32 +++ .../model-auth/config-provider.tsx | 3 + .../model-auth/hooks/index.ts | 1 + .../model-auth/hooks/use-auth-service.ts | 8 +- .../model-auth/hooks/use-auth.ts | 26 +- .../model-auth/hooks/use-credential-data.ts | 24 ++ .../hooks/use-model-form-schemas.ts | 42 ++- .../model-provider-page/model-auth/index.tsx | 1 + .../switch-credential-in-load-balancing.tsx | 51 ++-- .../model-provider-page/model-modal/index.tsx | 30 +- .../model-load-balancing-entry-modal.tsx | 264 ------------------ .../provider-added-card/credential-panel.tsx | 4 +- .../provider-added-card/index.tsx | 13 +- .../provider-added-card/model-list-item.tsx | 41 +-- .../provider-added-card/model-list.tsx | 10 +- .../model-load-balancing-configs.tsx | 109 ++++---- .../model-load-balancing-modal.tsx | 100 ++++--- web/context/modal-context.tsx | 42 +-- web/i18n/en-US/common.ts | 16 +- web/i18n/zh-Hans/common.ts | 16 +- web/service/use-models.ts | 19 +- 31 files changed, 493 insertions(+), 631 deletions(-) create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/config-model.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-credential-data.ts delete mode 100644 web/app/components/header/account-setting/model-provider-page/model-modal/model-load-balancing-entry-modal.tsx diff --git a/web/app/components/base/form/components/base/base-field.tsx b/web/app/components/base/form/components/base/base-field.tsx index 3e5c4986e2..2f8479f89c 100644 --- a/web/app/components/base/form/components/base/base-field.tsx +++ b/web/app/components/base/form/components/base/base-field.tsx @@ -30,7 +30,7 @@ const BaseField = ({ inputClassName, formSchema, field, - disabled, + disabled: propsDisabled, }: BaseFieldProps) => { const renderI18nObject = useRenderI18nObject() const { @@ -40,7 +40,9 @@ const BaseField = ({ options, labelClassName: formLabelClassName, show_on = [], + disabled: formSchemaDisabled, } = formSchema + const disabled = propsDisabled || formSchemaDisabled const memorizedLabel = useMemo(() => { if (isValidElement(label)) @@ -182,9 +184,10 @@ const BaseField = ({ className={cn( 'system-sm-regular hover:bg-components-option-card-option-hover-bg hover:border-components-option-card-option-hover-border flex h-8 flex-[1] grow cursor-pointer items-center justify-center rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary', value === option.value && 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary shadow-xs', + disabled && 'cursor-not-allowed opacity-50', inputClassName, )} - onClick={() => field.handleChange(option.value)} + onClick={() => !disabled && field.handleChange(option.value)} > { formSchema.showRadioUI && ( diff --git a/web/app/components/base/form/types.ts b/web/app/components/base/form/types.ts index 9b3beeee7f..5c8e361266 100644 --- a/web/app/components/base/form/types.ts +++ b/web/app/components/base/form/types.ts @@ -59,6 +59,7 @@ export type FormSchema = { labelClassName?: string validators?: AnyValidators showRadioUI?: boolean + disabled?: boolean } export type FormValues = Record diff --git a/web/app/components/header/account-setting/model-provider-page/declarations.ts b/web/app/components/header/account-setting/model-provider-page/declarations.ts index 16ac26c17a..6d9cbf0195 100644 --- a/web/app/components/header/account-setting/model-provider-page/declarations.ts +++ b/web/app/components/header/account-setting/model-provider-page/declarations.ts @@ -183,7 +183,7 @@ export type QuotaConfiguration = { export type Credential = { credential_id: string - credential_name: string + credential_name?: string } export type CustomModel = { diff --git a/web/app/components/header/account-setting/model-provider-page/hooks.ts b/web/app/components/header/account-setting/model-provider-page/hooks.ts index 5527e283cc..87117161e3 100644 --- a/web/app/components/header/account-setting/model-provider-page/hooks.ts +++ b/web/app/components/header/account-setting/model-provider-page/hooks.ts @@ -354,6 +354,7 @@ export const useModelModalHandler = () => { provider: ModelProvider, configurationMethod: ConfigurationMethodEnum, CustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, + isModelCredential?: boolean, credential?: Credential, model?: CustomModel, ) => { @@ -362,6 +363,7 @@ export const useModelModalHandler = () => { currentProvider: provider, currentConfigurationMethod: configurationMethod, currentCustomConfigurationModelFixedFields: CustomConfigurationModelFixedFields, + isModelCredential, credential, model, }, diff --git a/web/app/components/header/account-setting/model-provider-page/index.tsx b/web/app/components/header/account-setting/model-provider-page/index.tsx index 569ca4c777..35de29185f 100644 --- a/web/app/components/header/account-setting/model-provider-page/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/index.tsx @@ -8,9 +8,6 @@ import { import SystemModelSelector from './system-model-selector' import ProviderAddedCard from './provider-added-card' import type { - ConfigurationMethodEnum, - Credential, - CustomConfigurationModelFixedFields, ModelProvider, } from './declarations' import { @@ -19,7 +16,6 @@ import { } from './declarations' import { useDefaultModel, - useModelModalHandler, } from './hooks' import InstallFromMarketplace from './install-from-marketplace' import { useProviderContext } from '@/context/provider-context' @@ -85,8 +81,6 @@ const ModelProviderPage = ({ searchText }: Props) => { return [filteredConfiguredProviders, filteredNotConfiguredProviders] }, [configuredProviders, debouncedSearchText, notConfiguredProviders]) - const handleOpenModal = useModelModalHandler() - return (
@@ -127,7 +121,6 @@ const ModelProviderPage = ({ searchText }: Props) => { handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields, credential)} /> ))}
@@ -141,7 +134,6 @@ const ModelProviderPage = ({ searchText }: Props) => { notConfigured key={provider.provider} provider={provider} - onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, credential?: Credential) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields, credential)} /> ))}
diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/add-credential-in-load-balancing.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/add-credential-in-load-balancing.tsx index d1c366f4f1..52adebd262 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/add-credential-in-load-balancing.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/add-credential-in-load-balancing.tsx @@ -3,39 +3,70 @@ import { useCallback, } from 'react' import { RiAddLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' import { Authorized } from '@/app/components/header/account-setting/model-provider-page/model-auth' import cn from '@/utils/classnames' import type { Credential, + CustomModelCredential, + ModelCredential, ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' type AddCredentialInLoadBalancingProps = { provider: ModelProvider - onSetup: (credential?: Credential) => void + model: CustomModelCredential + configurationMethod: ConfigurationMethodEnum + modelCredential: ModelCredential + onSelectCredential: (credential: Credential) => void + onUpdate?: () => void } const AddCredentialInLoadBalancing = ({ provider, - onSetup, + model, + configurationMethod, + modelCredential, + onSelectCredential, + onUpdate, }: AddCredentialInLoadBalancingProps) => { + const { t } = useTranslation() + const { + available_credentials, + } = modelCredential + const customModel = configurationMethod === ConfigurationMethodEnum.customizableModel const renderTrigger = useCallback((open?: boolean) => { return (
- - Add credential + + { + customModel + ? t('common.modelProvider.auth.addCredential') + : t('common.modelProvider.auth.addApiKey') + }
) }, []) return ( ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx index 36908e0260..23c54f1062 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx @@ -12,52 +12,60 @@ import { } from '@/app/components/base/button' import type { CustomConfigurationModelFixedFields, - CustomModelCredential, ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import Authorized from './authorized' -import { useAuth } from './hooks' +import { + useAuth, + useCustomModels, +} from './hooks' import cn from '@/utils/classnames' type AddCustomModelProps = { provider: ModelProvider, configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, - models: CustomModelCredential[] } const AddCustomModel = ({ provider, configurationMethod, currentCustomConfigurationModelFixedFields, - models, }: AddCustomModelProps) => { const { t } = useTranslation() - const noModels = !models.length + const customModels = useCustomModels(provider) + const noModels = !customModels.length const { handleOpenModal, - } = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields) + } = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields, true) const handleClick = useCallback(() => { - if (noModels) - handleOpenModal() - }, [handleOpenModal, noModels]) + handleOpenModal() + }, [handleOpenModal]) const ButtonComponent = useMemo(() => { return ( ) - }, [handleClick, noModels]) + }, [handleClick]) - const renderTrigger = useCallback(() => { - return ButtonComponent - }, [ButtonComponent]) + const renderTrigger = useCallback((open?: boolean) => { + return ( + + ) + }, [t]) if (noModels) return ButtonComponent @@ -66,11 +74,14 @@ const AddCustomModel = ({ ({ + items={customModels.map(model => ({ model, credentials: model.available_model_credentials ?? [], }))} renderTrigger={renderTrigger} + isModelCredential + enableAddModelCredential + bottomAddModelCredentialText={t('common.modelProvider.auth.addNewModel')} /> ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/authorized-item.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/authorized-item.tsx index f95161e46b..3bc317a594 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/authorized-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/authorized-item.tsx @@ -3,41 +3,51 @@ import { useCallback, } from 'react' import { RiAddLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' import CredentialItem from './credential-item' import type { Credential, CustomModel, + CustomModelCredential, } from '../../declarations' import Button from '@/app/components/base/button' import Tooltip from '@/app/components/base/tooltip' type AuthorizedItemProps = { - model?: CustomModel + model?: CustomModelCredential + title?: string disabled?: boolean - onDelete?: (id: string) => void - onEdit?: (model?: CustomModel, credential?: Credential) => void - onSetDefault?: (id: string) => void - onItemClick?: (id: string) => void + onDelete?: (credential?: Credential, model?: CustomModel) => void + onEdit?: (credential?: Credential, model?: CustomModel) => void showItemSelectedIcon?: boolean selectedCredentialId?: string - disableSetDefault?: boolean credentials: Credential[] + onItemClick?: (credential: Credential, model?: CustomModel) => void + enableAddModelCredential?: boolean } export const AuthorizedItem = ({ model, + title, credentials, disabled, onDelete, onEdit, - onSetDefault, - onItemClick, showItemSelectedIcon, selectedCredentialId, - disableSetDefault, + onItemClick, + enableAddModelCredential, }: AuthorizedItemProps) => { + const { t } = useTranslation() const handleEdit = useCallback((credential?: Credential) => { - onEdit?.(model, credential) + onEdit?.(credential, model) }, [onEdit, model]) + const handleDelete = useCallback((credential?: Credential) => { + onDelete?.(credential, model) + }, [onDelete, model]) + const handleItemClick = useCallback((credential: Credential) => { + onItemClick?.(credential, model) + }, [onItemClick, model]) + return (
{ @@ -47,23 +57,28 @@ export const AuthorizedItem = ({ >
- {model.model} + {title ?? model.model}
- - - + { + enableAddModelCredential && ( + + + + ) + }
) } @@ -73,13 +88,11 @@ export const AuthorizedItem = ({ key={credential.credential_id} credential={credential} disabled={disabled} - onDelete={onDelete} + onDelete={handleDelete} onEdit={handleEdit} - onSetDefault={onSetDefault} - onItemClick={onItemClick} showSelectedIcon={showItemSelectedIcon} selectedCredentialId={selectedCredentialId} - disableSetDefault={disableSetDefault} + onItemClick={handleItemClick} /> )) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx index f0f872dd84..c7dc37031f 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx @@ -13,19 +13,16 @@ import ActionButton from '@/app/components/base/action-button' import Tooltip from '@/app/components/base/tooltip' import cn from '@/utils/classnames' import type { Credential } from '../../declarations' -import Button from '@/app/components/base/button' type CredentialItemProps = { credential: Credential disabled?: boolean - onDelete?: (id: string) => void + onDelete?: (credential: Credential) => void onEdit?: (credential?: Credential) => void - onSetDefault?: (id: string) => void + onItemClick?: (credential: Credential) => void disableRename?: boolean disableEdit?: boolean disableDelete?: boolean - disableSetDefault?: boolean - onItemClick?: (id: string) => void showSelectedIcon?: boolean selectedCredentialId?: string } @@ -34,19 +31,17 @@ const CredentialItem = ({ disabled, onDelete, onEdit, - onSetDefault, + onItemClick, disableRename, disableEdit, disableDelete, - disableSetDefault, - onItemClick, showSelectedIcon, selectedCredentialId, }: CredentialItemProps) => { const { t } = useTranslation() const showAction = useMemo(() => { - return !(disableRename && disableEdit && disableDelete && disableSetDefault) - }, [disableRename, disableEdit, disableDelete, disableSetDefault]) + return !(disableRename && disableEdit && disableDelete) + }, [disableRename, disableEdit, disableDelete]) return (
onItemClick?.(credential.credential_id)} + onClick={() => onItemClick?.(credential)} >
{ @@ -79,20 +74,6 @@ const CredentialItem = ({ { showAction && (
- { - !disableSetDefault && ( - - ) - } { !disableEdit && ( @@ -116,7 +97,7 @@ const CredentialItem = ({ disabled={disabled} onClick={(e) => { e.stopPropagation() - onDelete?.(credential.credential_id) + onDelete?.(credential) }} > diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx index 6063ad02be..fd2cabfc53 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx @@ -4,6 +4,7 @@ import { useState, } from 'react' import { + RiAddLine, RiEqualizer2Line, } from '@remixicon/react' import { useTranslation } from 'react-i18next' @@ -32,6 +33,7 @@ type AuthorizedProps = { provider: ModelProvider, configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, + isModelCredential?: boolean items: { model?: CustomModel credentials: Credential[] @@ -45,16 +47,18 @@ type AuthorizedProps = { placement?: PortalToFollowElemOptions['placement'] triggerPopupSameWidth?: boolean popupClassName?: string - onItemClick?: (id: string) => void showItemSelectedIcon?: boolean onUpdate?: () => void - disableSetDefault?: boolean + onItemClick?: (credential: Credential, model?: CustomModel) => void + enableAddModelCredential?: boolean + bottomAddModelCredentialText?: string } const Authorized = ({ provider, configurationMethod, currentCustomConfigurationModelFixedFields, items, + isModelCredential, selectedCredential, disabled, renderTrigger, @@ -64,10 +68,11 @@ const Authorized = ({ placement = 'bottom-end', triggerPopupSameWidth = false, popupClassName, - onItemClick, showItemSelectedIcon, onUpdate, - disableSetDefault, + onItemClick, + enableAddModelCredential, + bottomAddModelCredentialText, }: AuthorizedProps) => { const { t } = useTranslation() const [isLocalOpen, setIsLocalOpen] = useState(false) @@ -86,13 +91,20 @@ const Authorized = ({ handleConfirmDelete, deleteCredentialId, handleOpenModal, - } = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields, onUpdate) + } = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields, isModelCredential, onUpdate) - const handleEdit = useCallback((model?: CustomModel, credential?: Credential) => { - handleOpenModal(model, credential) + const handleEdit = useCallback((credential?: Credential, model?: CustomModel) => { + handleOpenModal(credential, model) setMergedIsOpen(false) }, [handleOpenModal, setMergedIsOpen]) + const handleItemClick = useCallback((credential: Credential, model?: CustomModel) => { + if (!onItemClick) + return handleActiveCredential(credential, model) + + onItemClick?.(credential, model) + }, [handleActiveCredential, onItemClick]) + return ( <> )) }
-
- -
+ { + isModelCredential && ( +
handleEdit()} + className='system-xs-medium flex h-[30px] cursor-pointer items-center px-3 text-text-accent-light-mode-only' + > + + {bottomAddModelCredentialText ?? t('common.modelProvider.auth.addModelCredential')} +
+ ) + } + { + !isModelCredential && ( +
+ +
+ ) + }
diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/config-model.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/config-model.tsx new file mode 100644 index 0000000000..5693dac45b --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/config-model.tsx @@ -0,0 +1,32 @@ +import { memo } from 'react' +import { RiEqualizer2Line } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import cn from '@/utils/classnames' + +type ConfigModelProps = { + className?: string + onClick?: () => void +} +const ConfigModel = ({ + className, + onClick, +}: ConfigModelProps) => { + const { t } = useTranslation() + return ( + + ) +} + +export default memo(ConfigModel) diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/config-provider.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/config-provider.tsx index f3e6c2d601..29286cf9d1 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/config-provider.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/config-provider.tsx @@ -67,6 +67,9 @@ const ConfigProvider = ({ configurationMethod={ConfigurationMethodEnum.predefinedModel} items={[ { + model: { + model: t('common.modelProvider.auth.apiKeys'), + } as any, credentials: available_credentials ?? [], }, ]} diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/index.ts b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/index.ts index 738fe8a6ba..fd0bee512f 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/index.ts +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/index.ts @@ -3,3 +3,4 @@ export * from './use-credential-status' export * from './use-custom-models' export * from './use-auth' export * from './use-auth-service' +export * from './use-credential-data' diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth-service.ts b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth-service.ts index 0f5de9bde9..317a1fe1a9 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth-service.ts +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth-service.ts @@ -15,10 +15,10 @@ import type { CustomModel, } from '@/app/components/header/account-setting/model-provider-page/declarations' -export const useGetCredential = (provider: string, credentialId?: string, model?: CustomModel, configFrom?: string) => { - const providerData = useGetProviderCredential(!model && !!credentialId, provider, credentialId) - const modelData = useGetModelCredential(!!model && !!credentialId, provider, credentialId, model?.model, model?.model_type, configFrom) - return model ? modelData : providerData +export const useGetCredential = (provider: string, isModelCredential?: boolean, credentialId?: string, model?: CustomModel, configFrom?: string) => { + const providerData = useGetProviderCredential(!isModelCredential && !!credentialId, provider, credentialId) + const modelData = useGetModelCredential(!!isModelCredential && !!credentialId, provider, credentialId, model?.model, model?.model_type, configFrom) + return isModelCredential ? modelData : providerData } export const useAuthService = (provider: string) => { diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth.ts b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth.ts index f33901b1f9..a5723b4046 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth.ts +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth.ts @@ -22,6 +22,7 @@ export const useAuth = ( provider: ModelProvider, configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, + isModelCredential?: boolean, onUpdate?: () => void, ) => { const { t } = useTranslation() @@ -37,9 +38,9 @@ export const useAuth = ( const pendingOperationCredentialId = useRef(null) const pendingOperationModel = useRef(null) const [deleteCredentialId, setDeleteCredentialId] = useState(null) - const openConfirmDelete = useCallback((credentialId?: string, model?: CustomModel) => { - if (credentialId) - pendingOperationCredentialId.current = credentialId + const openConfirmDelete = useCallback((credential?: Credential, model?: CustomModel) => { + if (credential) + pendingOperationCredentialId.current = credential.credential_id if (model) pendingOperationModel.current = model @@ -55,13 +56,13 @@ export const useAuth = ( doingActionRef.current = doing setDoingAction(doing) }, []) - const handleActiveCredential = useCallback(async (id: string, model?: CustomModel) => { + const handleActiveCredential = useCallback(async (credential: Credential, model?: CustomModel) => { if (doingActionRef.current) return try { handleSetDoingAction(true) await getActiveCredentialService(!!model)({ - credential_id: id, + credential_id: credential.credential_id, model: model?.model, model_type: model?.model_type, }) @@ -70,6 +71,7 @@ export const useAuth = ( message: t('common.api.actionSuccess'), }) onUpdate?.() + handleRefreshModel(provider, configurationMethod, undefined) } finally { handleSetDoingAction(false) @@ -84,7 +86,7 @@ export const useAuth = ( } try { handleSetDoingAction(true) - await getDeleteCredentialService(!!pendingOperationModel.current)({ + await getDeleteCredentialService(!!isModelCredential)({ credential_id: pendingOperationCredentialId.current, model: pendingOperationModel.current?.model, model_type: pendingOperationModel.current?.model_type, @@ -97,11 +99,12 @@ export const useAuth = ( handleRefreshModel(provider, configurationMethod, undefined) setDeleteCredentialId(null) pendingOperationCredentialId.current = null + pendingOperationModel.current = null } finally { handleSetDoingAction(false) } - }, [onUpdate, notify, t, handleSetDoingAction, getDeleteCredentialService]) + }, [onUpdate, notify, t, handleSetDoingAction, getDeleteCredentialService, isModelCredential]) const handleAddCredential = useCallback((model?: CustomModel) => { if (model) pendingOperationModel.current = model @@ -114,9 +117,9 @@ export const useAuth = ( let res: { result?: string } = {} if (payload.credential_id) - res = await getEditCredentialService(!!payload.model)(payload as any) + res = await getEditCredentialService(!!isModelCredential)(payload as any) else - res = await getAddCredentialService(!!payload.model)(payload as any) + res = await getAddCredentialService(!!isModelCredential)(payload as any) if (res.result === 'success') { notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) @@ -127,15 +130,16 @@ export const useAuth = ( handleSetDoingAction(false) } }, [onUpdate, notify, t, handleSetDoingAction, getEditCredentialService, getAddCredentialService]) - const handleOpenModal = useCallback((model?: CustomModel, credential?: Credential) => { + const handleOpenModal = useCallback((credential?: Credential, model?: CustomModel) => { handleOpenModelModal( provider, configurationMethod, currentCustomConfigurationModelFixedFields, + isModelCredential, credential, model, ) - }, [handleOpenModelModal, provider, configurationMethod, currentCustomConfigurationModelFixedFields]) + }, [handleOpenModelModal, provider, configurationMethod, currentCustomConfigurationModelFixedFields, isModelCredential]) return { pendingOperationCredentialId, diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-credential-data.ts b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-credential-data.ts new file mode 100644 index 0000000000..2fbc8b1033 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-credential-data.ts @@ -0,0 +1,24 @@ +import { useMemo } from 'react' +import { useGetCredential } from './use-auth-service' +import type { + Credential, + CustomModelCredential, + ModelProvider, +} from '@/app/components/header/account-setting/model-provider-page/declarations' + +export const useCredentialData = (provider: ModelProvider, providerFormSchemaPredefined: boolean, isModelCredential?: boolean, credential?: Credential, model?: CustomModelCredential) => { + const configFrom = useMemo(() => { + if (providerFormSchemaPredefined) + return 'predefined-model' + return 'custom-model' + }, [providerFormSchemaPredefined]) + const { + isLoading, + data: credentialData = {}, + } = useGetCredential(provider.provider, isModelCredential, credential?.credential_id, model, configFrom) + + return { + isLoading, + credentialData, + } +} diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-model-form-schemas.ts b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-model-form-schemas.ts index 463c905959..1769d8217d 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-model-form-schemas.ts +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-model-form-schemas.ts @@ -1,6 +1,8 @@ import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import type { + Credential, + CustomModelCredential, ModelLoadBalancingConfig, ModelProvider, } from '../../declarations' @@ -13,6 +15,9 @@ import { FormTypeEnum } from '@/app/components/base/form/types' export const useModelFormSchemas = ( provider: ModelProvider, providerFormSchemaPredefined: boolean, + credentials?: Record, + credential?: Credential, + model?: CustomModelCredential, draftConfig?: ModelLoadBalancingConfig, ) => { const { t } = useTranslation() @@ -22,11 +27,17 @@ export const useModelFormSchemas = ( model_credential_schema, } = provider const formSchemas = useMemo(() => { + const modelTypeSchema = genModelTypeFormSchema(supported_model_types) + const modelNameSchema = genModelNameFormSchema(model_credential_schema?.model) + if (!!model) { + modelTypeSchema.disabled = true + modelNameSchema.disabled = true + } return providerFormSchemaPredefined ? provider_credential_schema.credential_form_schemas : [ - genModelTypeFormSchema(supported_model_types), - genModelNameFormSchema(model_credential_schema?.model), + modelTypeSchema, + modelNameSchema, ...(draftConfig?.enabled ? [] : model_credential_schema.credential_form_schemas), ] }, [ @@ -36,21 +47,36 @@ export const useModelFormSchemas = ( model_credential_schema?.credential_form_schemas, model_credential_schema?.model, draftConfig?.enabled, + model, ]) const formSchemasWithAuthorizationName = useMemo(() => { + const authorizationNameSchema = { + type: FormTypeEnum.textInput, + variable: '__authorization_name__', + label: t('plugin.auth.authorizationName'), + required: true, + } + return [ - { - type: FormTypeEnum.textInput, - variable: '__authorization_name__', - label: t('plugin.auth.authorizationName'), - required: true, - }, + authorizationNameSchema, ...formSchemas, ] }, [formSchemas, t]) + const formValues = useMemo(() => { + let result = {} + if (credentials) + result = { ...credentials } + if (credential) + result = { ...result, __authorization_name__: credential?.credential_name } + if (model) + result = { ...result, __model_name: model?.model, __model_type: model?.model_type } + return result + }, [credentials, credential, model]) + return { formSchemas: formSchemasWithAuthorizationName, + formValues, } } diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/index.tsx index e33e30da3a..05effcea7c 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/index.tsx @@ -3,3 +3,4 @@ export { default as SwitchCredentialInLoadBalancing } from './switch-credential- export { default as AddCredentialInLoadBalancing } from './add-credential-in-load-balancing' export { default as AddCustomModel } from './add-custom-model' export { default as ConfigProvider } from './config-provider' +export { default as ConfigModel } from './config-model' diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx index e9e1f16358..fca3e76d1f 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx @@ -10,13 +10,11 @@ import Indicator from '@/app/components/header/indicator' import Badge from '@/app/components/base/badge' import Authorized from './authorized' import type { - Credential, ModelLoadBalancingConfig, ModelProvider, } from '../declarations' import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useCredentialStatus } from './hooks' -import { useModelModalHandler } from '../hooks' import cn from '@/utils/classnames' type SwitchCredentialInLoadBalancingProps = { @@ -27,42 +25,16 @@ type SwitchCredentialInLoadBalancingProps = { const SwitchCredentialInLoadBalancing = ({ provider, draftConfig, - setDraftConfig, }: SwitchCredentialInLoadBalancingProps) => { const { t } = useTranslation() const { available_credentials, current_credential_name, } = useCredentialStatus(provider) - const handleOpenModal = useModelModalHandler() - console.log(draftConfig, 'draftConfig') - const handleSetup = useCallback((credential?: Credential) => { - handleOpenModal(provider, ConfigurationMethodEnum.predefinedModel, undefined, credential) - }, [handleOpenModal, provider]) - - const handleItemClick = useCallback((id: string) => { - setDraftConfig((prev) => { - if (!prev) - return prev - const newConfigs = [...prev.configs] - const index = newConfigs.findIndex(config => config.name === '__inherit__') - const inheritConfig = newConfigs[index] - const modifiedConfig = inheritConfig ? { - ...inheritConfig, - credential_id: id, - } : { - name: '__inherit__', - credential_id: id, - credentials: {}, - } - newConfigs.splice(index, 1, modifiedConfig) - return { - ...prev, - configs: newConfigs, - } - }) - }, [setDraftConfig]) + const handleItemClick = useCallback(() => { + console.log('handleItemClick', draftConfig) + }, []) const renderTrigger = useCallback(() => { const selectedCredentialId = draftConfig?.configs.find(config => config.name === '__inherit__')?.credential_id @@ -96,12 +68,21 @@ const SwitchCredentialInLoadBalancing = ({ return ( ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/index.tsx index 937fe6a9ae..39b26246fc 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/index.tsx @@ -38,33 +38,35 @@ import type { import Loading from '@/app/components/base/loading' import { useAuth, - useGetCredential, + useCredentialData, } from '@/app/components/header/account-setting/model-provider-page/model-auth/hooks' type ModelModalProps = { provider: ModelProvider - model?: CustomModel - credential?: Credential configurateMethod: ConfigurationMethodEnum currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields onCancel: () => void onSave: () => void + model?: CustomModel + credential?: Credential + isModelCredential?: boolean } const ModelModal: FC = ({ provider, - model, configurateMethod, currentCustomConfigurationModelFixedFields, - credential, onCancel, onSave, + model, + credential, + isModelCredential, }) => { const providerFormSchemaPredefined = configurateMethod === ConfigurationMethodEnum.predefinedModel const { isLoading, - data: credentialData = {}, - } = useGetCredential(provider.provider, credential?.credential_id, model) + credentialData, + } = useCredentialData(provider, providerFormSchemaPredefined, isModelCredential, credential, model) const { handleSaveCredential, handleConfirmDelete, @@ -72,7 +74,7 @@ const ModelModal: FC = ({ closeConfirmDelete, openConfirmDelete, doingAction, - } = useAuth(provider, configurateMethod, currentCustomConfigurationModelFixedFields, onSave) + } = useAuth(provider, configurateMethod, currentCustomConfigurationModelFixedFields, isModelCredential, onSave) const { credentials: formSchemasValue, } = credentialData as any @@ -81,7 +83,10 @@ const ModelModal: FC = ({ const isEditMode = !!formSchemasValue && isCurrentWorkspaceManager const { t } = useTranslation() const language = useLanguage() - const { formSchemas } = useModelFormSchemas(provider, providerFormSchemaPredefined) + const { + formSchemas, + formValues, + } = useModelFormSchemas(provider, providerFormSchemaPredefined, formSchemasValue, credential, model) const formRef = useRef(null) const handleSave = useCallback(async () => { @@ -152,10 +157,7 @@ const ModelModal: FC = ({ showRadioUI: formSchema.type === FormTypeEnum.radio, } }) as FormSchema[]} - defaultValues={{ - ...formSchemasValue, - __authorization_name__: credential?.credential_name, - }} + defaultValues={formValues} inputClassName='justify-start' ref={formRef} /> @@ -186,7 +188,7 @@ const ModelModal: FC = ({ variant='warning' size='large' className='mr-2' - onClick={() => openConfirmDelete(credential?.credential_id, model)} + onClick={() => openConfirmDelete(credential, model)} > {t('common.operation.remove')} diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/model-load-balancing-entry-modal.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/model-load-balancing-entry-modal.tsx deleted file mode 100644 index 38bde219ab..0000000000 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/model-load-balancing-entry-modal.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import type { FC } from 'react' -import { - memo, - useEffect, - useMemo, - useRef, - useState, -} from 'react' -import { useTranslation } from 'react-i18next' -import type { - CustomConfigurationModelFixedFields, - ModelLoadBalancingConfigEntry, - ModelProvider, -} from '../declarations' -import { - ConfigurationMethodEnum, - FormTypeEnum, -} from '../declarations' - -import { - useLanguage, -} from '../hooks' -import { ValidatedStatus } from '../../key-validator/declarations' -import { validateLoadBalancingCredentials } from '../utils' -import Button from '@/app/components/base/button' -import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' -import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' -import { - PortalToFollowElem, - PortalToFollowElemContent, -} from '@/app/components/base/portal-to-follow-elem' -import { useToastContext } from '@/app/components/base/toast' -import Confirm from '@/app/components/base/confirm' -import AuthForm from '@/app/components/base/form/form-scenarios/auth' -import type { - FormRefObject, - FormSchema, -} from '@/app/components/base/form/types' - -type ModelModalProps = { - provider: ModelProvider - configurationMethod: ConfigurationMethodEnum - currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields - entry?: ModelLoadBalancingConfigEntry - onCancel: () => void - onSave: (entry: ModelLoadBalancingConfigEntry) => void - onRemove: () => void -} - -const ModelLoadBalancingEntryModal: FC = ({ - provider, - configurationMethod, - currentCustomConfigurationModelFixedFields, - entry, - onCancel, - onSave, - onRemove, -}) => { - const providerFormSchemaPredefined = configurationMethod === ConfigurationMethodEnum.predefinedModel - const isEditMode = !!entry - const { t } = useTranslation() - const { notify } = useToastContext() - const language = useLanguage() - const [loading, setLoading] = useState(false) - const [showConfirm, setShowConfirm] = useState(false) - const formSchemas = useMemo(() => { - return [ - { - type: FormTypeEnum.textInput, - label: { - en_US: 'Config Name', - zh_Hans: '配置名称', - }, - variable: 'name', - required: true, - show_on: [], - placeholder: { - en_US: 'Enter your Config Name here', - zh_Hans: '输入配置名称', - }, - } as any, - ...( - providerFormSchemaPredefined - ? provider.provider_credential_schema.credential_form_schemas - : provider.model_credential_schema.credential_form_schemas - ), - ] - }, [ - providerFormSchemaPredefined, - provider.provider_credential_schema?.credential_form_schemas, - provider.model_credential_schema?.credential_form_schemas, - ]) - const formRef = useRef(null) - - const [ - defaultFormSchemaValue, - ] = useMemo(() => { - const defaultFormSchemaValue: Record = {} - - formSchemas.forEach((formSchema) => { - if (formSchema.default) - defaultFormSchemaValue[formSchema.variable] = formSchema.default - }) - - return [ - defaultFormSchemaValue, - ] - }, [formSchemas]) - const [initialValue, setInitialValue] = useState() - useEffect(() => { - if (entry && !initialValue) { - setInitialValue({ - ...defaultFormSchemaValue, - ...entry.credentials, - id: entry.id, - name: entry.name, - } as Record) - } - }, [entry, defaultFormSchemaValue, initialValue]) - const formSchemasValue = useMemo(() => ({ - ...currentCustomConfigurationModelFixedFields, - ...initialValue, - }), [currentCustomConfigurationModelFixedFields, initialValue]) - - const handleSave = async () => { - try { - setLoading(true) - const { - isCheckValidated, - values, - } = formRef.current?.getFormValues({ - needCheckValidatedValues: true, - needTransformWhenSecretFieldIsPristine: true, - }) || { isCheckValidated: false, values: {} } - if (!isCheckValidated) - return - const res = await validateLoadBalancingCredentials( - providerFormSchemaPredefined, - provider.provider, - values, - entry?.id, - ) - if (res.status === ValidatedStatus.Success) { - // notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - const { __model_type, __model_name, name, ...credentials } = values - onSave({ - ...(entry || {}), - name: name as string, - credentials: credentials as Record, - }) - // onCancel() - } - else { - notify({ type: 'error', message: res.message || '' }) - } - } - finally { - setLoading(false) - } - } - - const handleRemove = () => { - onRemove?.() - } - - return ( - - -
-
-
-
-
{t(isEditMode ? 'common.modelProvider.editConfig' : 'common.modelProvider.addConfig')}
-
- { - return { - ...formSchema, - name: formSchema.variable, - showRadioUI: formSchema.type === FormTypeEnum.radio, - } - }) as FormSchema[]} - defaultValues={formSchemasValue} - inputClassName='justify-start' - ref={formRef} - /> -
- { - (provider.help && (provider.help.title || provider.help.url)) - ? ( - !provider.help.url && e.preventDefault()} - > - {provider.help.title?.[language] || provider.help.url[language] || provider.help.title?.en_US || provider.help.url.en_US} - - - ) - :
- } -
- { - isEditMode && ( - - ) - } - - -
-
-
-
-
- - {t('common.modelProvider.encrypted.front')} - - PKCS1_OAEP - - {t('common.modelProvider.encrypted.back')} -
-
-
- { - showConfirm && ( - setShowConfirm(false)} - onConfirm={handleRemove} - /> - ) - } -
- - - ) -} - -export default memo(ModelLoadBalancingEntryModal) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx index 02a2b83934..b047896a05 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx @@ -70,11 +70,11 @@ const CredentialPanel = ({ } const credentialLabel = useMemo(() => { if (!hasCredential) - return t('common.model.unAuthorized') + return t('common.modelProvider.auth.unAuthorized') if (authorized) return current_credential_name if (authRemoved) - return t('common.model.authRemoved') + return t('common.modelProvider.auth.authRemoved') return '' }, [authorized, authRemoved, current_credential_name, hasCredential]) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx index b296b418f8..559f630b49 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx @@ -7,12 +7,10 @@ import { RiLoader2Line, } from '@remixicon/react' import type { - CustomConfigurationModelFixedFields, ModelItem, ModelProvider, } from '../declarations' import { ConfigurationMethodEnum } from '../declarations' -import type { Credential } from '../declarations' import { MODEL_PROVIDER_QUOTA_GET_PAID, modelTypeFormat, @@ -22,23 +20,21 @@ import ModelBadge from '../model-badge' import CredentialPanel from './credential-panel' import QuotaPanel from './quota-panel' import ModelList from './model-list' -import AddModelButton from './add-model-button' import { fetchModelProviderModelList } from '@/service/common' import { useEventEmitterContextContext } from '@/context/event-emitter' import { IS_CE_EDITION } from '@/config' import { useAppContext } from '@/context/app-context' import cn from '@/utils/classnames' +import { AddCustomModel } from '@/app/components/header/account-setting/model-provider-page/model-auth' export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST' type ProviderAddedCardProps = { notConfigured?: boolean provider: ModelProvider - onOpenModal: (configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, credential?: Credential) => void } const ProviderAddedCard: FC = ({ notConfigured, provider, - onOpenModal, }) => { const { t } = useTranslation() const { eventEmitter } = useEventEmitterContextContext() @@ -159,9 +155,9 @@ const ProviderAddedCard: FC = ({ )} { configurationMethods.includes(ConfigurationMethodEnum.customizableModel) && isCurrentWorkspaceManager && ( - onOpenModal(ConfigurationMethodEnum.customizableModel)} - className='flex' + ) } @@ -174,7 +170,6 @@ const ProviderAddedCard: FC = ({ provider={provider} models={modelList} onCollapse={() => setCollapsed(true)} - onConfig={currentCustomConfigurationModelFixedFields => onOpenModal(ConfigurationMethodEnum.customizableModel, currentCustomConfigurationModelFixedFields)} onChange={(provider: string) => getModelList(provider)} /> ) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx index 8908d9a039..72ccb30cec 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx @@ -1,31 +1,29 @@ import { memo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useDebounceFn } from 'ahooks' -import type { CustomConfigurationModelFixedFields, ModelItem, ModelProvider } from '../declarations' -import { ConfigurationMethodEnum, ModelStatusEnum } from '../declarations' +import type { ModelItem, ModelProvider } from '../declarations' +import { ModelStatusEnum } from '../declarations' import ModelBadge from '../model-badge' import ModelIcon from '../model-icon' import ModelName from '../model-name' import classNames from '@/utils/classnames' -import Button from '@/app/components/base/button' import { Balance } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' -import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' import Switch from '@/app/components/base/switch' import Tooltip from '@/app/components/base/tooltip' import { useProviderContext, useProviderContextSelector } from '@/context/provider-context' import { disableModel, enableModel } from '@/service/common' import { Plan } from '@/app/components/billing/type' import { useAppContext } from '@/context/app-context' +import { ConfigModel } from '../model-auth' export type ModelListItemProps = { model: ModelItem provider: ModelProvider isConfigurable: boolean - onConfig: (currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => void onModifyLoadBalancing?: (model: ModelItem) => void } -const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoadBalancing }: ModelListItemProps) => { +const ModelListItem = ({ model, provider, isConfigurable, onModifyLoadBalancing }: ModelListItemProps) => { const { t } = useTranslation() const { plan } = useProviderContext() const modelLoadBalancingEnabled = useProviderContextSelector(state => state.modelLoadBalancingEnabled) @@ -46,7 +44,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad return (
{ - model.fetch_from === ConfigurationMethodEnum.customizableModel - ? (isCurrentWorkspaceManager && ( - - )) - : (isCurrentWorkspaceManager && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status)) - ? ( - - ) - : null + (isCurrentWorkspaceManager && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status)) && ( + onModifyLoadBalancing?.(model)} + /> + ) } { model.deprecated diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx index 9bcf226064..8d902043ff 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx @@ -6,7 +6,6 @@ import { } from '@remixicon/react' import type { Credential, - CustomConfigurationModelFixedFields, ModelItem, ModelProvider, } from '../declarations' @@ -17,33 +16,30 @@ import { import ModelListItem from './model-list-item' import { useModalContextSelector } from '@/context/modal-context' import { useAppContext } from '@/context/app-context' -import { useCustomModels } from '@/app/components/header/account-setting/model-provider-page/model-auth/hooks' import { AddCustomModel } from '@/app/components/header/account-setting/model-provider-page/model-auth' type ModelListProps = { provider: ModelProvider models: ModelItem[] onCollapse: () => void - onConfig: (currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => void onChange?: (provider: string) => void } const ModelList: FC = ({ provider, models, onCollapse, - onConfig, onChange, }) => { const { t } = useTranslation() const configurativeMethods = provider.configurate_methods.filter(method => method !== ConfigurationMethodEnum.fetchFromRemote) const { isCurrentWorkspaceManager } = useAppContext() const isConfigurable = configurativeMethods.includes(ConfigurationMethodEnum.customizableModel) - const customModels = useCustomModels(provider) const setShowModelLoadBalancingModal = useModalContextSelector(state => state.setShowModelLoadBalancingModal) const onModifyLoadBalancing = useCallback((model: ModelItem, credential?: Credential) => { setShowModelLoadBalancingModal({ provider, credential, + configurateMethod: model.fetch_from, model: model!, open: !!model, onClose: () => setShowModelLoadBalancingModal(null), @@ -75,7 +71,6 @@ const ModelList: FC = ({ provider={provider} configurationMethod={ConfigurationMethodEnum.customizableModel} currentCustomConfigurationModelFixedFields={undefined} - models={customModels} />
) @@ -84,12 +79,11 @@ const ModelList: FC = ({ { models.map(model => ( diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx index efe2a8f94d..dd1ab5bcb0 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx @@ -3,23 +3,32 @@ import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { RiDeleteBinLine, + RiEqualizer2Line, } from '@remixicon/react' -import type { ConfigurationMethodEnum, CustomConfigurationModelFixedFields, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, ModelProvider } from '../declarations' +import type { + Credential, + CustomConfigurationModelFixedFields, + CustomModelCredential, + ModelCredential, + ModelLoadBalancingConfig, + ModelLoadBalancingConfigEntry, + ModelProvider, +} from '../declarations' +import { ConfigurationMethodEnum } from '../declarations' import Indicator from '../../../indicator' import CooldownTimer from './cooldown-timer' import classNames from '@/utils/classnames' import Tooltip from '@/app/components/base/tooltip' import Switch from '@/app/components/base/switch' import { Balance } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' -import { Edit02 } from '@/app/components/base/icons/src/vender/line/general' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' -import { useModalContextSelector } from '@/context/modal-context' import UpgradeBtn from '@/app/components/billing/upgrade-btn' import s from '@/app/components/custom/style.module.css' import GridMask from '@/app/components/base/grid-mask' import { useProviderContextSelector } from '@/context/provider-context' import { IS_CE_EDITION } from '@/config' import { AddCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth' +import { useModelModalHandler } from '@/app/components/header/account-setting/model-provider-page/hooks' export type ModelLoadBalancingConfigsProps = { draftConfig?: ModelLoadBalancingConfig @@ -29,19 +38,26 @@ export type ModelLoadBalancingConfigsProps = { currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields withSwitch?: boolean className?: string + modelCredential: ModelCredential + onUpdate?: () => void + model: CustomModelCredential } const ModelLoadBalancingConfigs = ({ draftConfig, setDraftConfig, provider, + model, configurationMethod, currentCustomConfigurationModelFixedFields, withSwitch = false, className, + modelCredential, + onUpdate, }: ModelLoadBalancingConfigsProps) => { const { t } = useTranslation() const modelLoadBalancingEnabled = useProviderContextSelector(state => state.modelLoadBalancingEnabled) + const handleOpenModal = useModelModalHandler() const updateConfigEntry = useCallback( ( @@ -66,6 +82,21 @@ const ModelLoadBalancingConfigs = ({ [setDraftConfig], ) + const addConfigEntry = useCallback((credential: Credential) => { + setDraftConfig((prev: any) => { + if (!prev) + return prev + return { + ...prev, + configs: [...prev.configs, { + credential_id: credential.credential_id, + enabled: true, + name: credential.credential_name, + }], + } + }) + }, [setDraftConfig]) + const toggleModalBalancing = useCallback((enabled: boolean) => { if ((modelLoadBalancingEnabled || !enabled) && draftConfig) { setDraftConfig({ @@ -82,54 +113,6 @@ const ModelLoadBalancingConfigs = ({ })) }, [updateConfigEntry]) - const setShowModelLoadBalancingEntryModal = useModalContextSelector(state => state.setShowModelLoadBalancingEntryModal) - - const toggleEntryModal = useCallback((index?: number, entry?: ModelLoadBalancingConfigEntry) => { - setShowModelLoadBalancingEntryModal({ - payload: { - currentProvider: provider, - currentConfigurationMethod: configurationMethod, - currentCustomConfigurationModelFixedFields, - entry, - index, - }, - onSaveCallback: ({ entry: result }) => { - if (entry) { - // edit - setDraftConfig(prev => ({ - ...prev, - enabled: !!prev?.enabled, - configs: prev?.configs.map((config, i) => i === index ? result! : config) || [], - })) - } - else { - // add - setDraftConfig(prev => ({ - ...prev, - enabled: !!prev?.enabled, - configs: (prev?.configs || []).concat([{ ...result!, enabled: true }]), - })) - } - }, - onRemoveCallback: ({ index }) => { - if (index !== undefined && (draftConfig?.configs?.length ?? 0) > index) { - setDraftConfig(prev => ({ - ...prev, - enabled: !!prev?.enabled, - configs: prev?.configs.filter((_, i) => i !== index) || [], - })) - } - }, - }) - }, [ - configurationMethod, - currentCustomConfigurationModelFixedFields, - draftConfig?.configs?.length, - provider, - setDraftConfig, - setShowModelLoadBalancingEntryModal, - ]) - const clearCountdown = useCallback((index: number) => { updateConfigEntry(index, ({ ttl: _, ...entry }) => { return { @@ -211,9 +194,21 @@ const ModelLoadBalancingConfigs = ({
toggleEntryModal(index, config)} + onClick={() => { + handleOpenModal( + provider, + configurationMethod, + currentCustomConfigurationModelFixedFields, + configurationMethod === ConfigurationMethodEnum.customizableModel, + (config.credential_id && config.name) ? { + credential_id: config.credential_id, + credential_name: config.name, + } : undefined, + model, + ) + }} > - + toggleEntryModal()} + model={model} + configurationMethod={configurationMethod} + modelCredential={modelCredential} + onSelectCredential={addConfigEntry} + onUpdate={onUpdate} />
)} { draftConfig.enabled && draftConfig.configs.length < 2 && ( -
+
{t('common.modelProvider.loadBalancingLeastKeyWarning')}
diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx index a31d8a5c00..7f2b88a186 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx @@ -1,6 +1,5 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import useSWR from 'swr' import type { Credential, ModelItem, @@ -8,21 +7,27 @@ import type { ModelLoadBalancingConfigEntry, ModelProvider, } from '../declarations' -import { FormTypeEnum } from '../declarations' +import { + ConfigurationMethodEnum, + FormTypeEnum, +} from '../declarations' import ModelIcon from '../model-icon' import ModelName from '../model-name' -import { savePredefinedLoadBalancingConfig } from '../utils' import ModelLoadBalancingConfigs from './model-load-balancing-configs' import classNames from '@/utils/classnames' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' -import { fetchModelLoadBalancingConfig } from '@/service/common' import Loading from '@/app/components/base/loading' import { useToastContext } from '@/app/components/base/toast' -import { SwitchCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth' +// import { SwitchCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth' +import { + useGetModelCredential, + useUpdateModelLoadBalancingConfig, +} from '@/service/use-models' export type ModelLoadBalancingModalProps = { provider: ModelProvider + configurateMethod: ConfigurationMethodEnum model: ModelItem credential?: Credential open?: boolean @@ -33,6 +38,7 @@ export type ModelLoadBalancingModalProps = { // model balancing config modal const ModelLoadBalancingModal = ({ provider, + configurateMethod, model, credential, open = false, @@ -43,13 +49,18 @@ const ModelLoadBalancingModal = ({ const { notify } = useToastContext() const [loading, setLoading] = useState(false) - - const { data, mutate } = useSWR( - `/workspaces/current/model-providers/${provider.provider}/models/credentials?model=${model.model}&model_type=${model.model_type}`, - fetchModelLoadBalancingConfig, - ) - - const originalConfig = data?.load_balancing + const providerFormSchemaPredefined = configurateMethod === ConfigurationMethodEnum.predefinedModel + const configFrom = providerFormSchemaPredefined ? 'predefined-model' : 'custom-model' + const { + isLoading, + data, + refetch, + } = useGetModelCredential(true, provider.provider, credential?.credential_id, model.model, model.model_type, configFrom) + const modelCredential = data + const { + load_balancing, + } = modelCredential ?? {} + const originalConfig = load_balancing const [draftConfig, setDraftConfig] = useState() const originalConfigMap = useMemo(() => { if (!originalConfig) @@ -90,25 +101,24 @@ const ModelLoadBalancingModal = ({ return result }, [extendedSecretFormSchemas, originalConfigMap]) + const { mutateAsync: updateModelLoadBalancingConfig } = useUpdateModelLoadBalancingConfig(provider.provider) const handleSave = async () => { try { setLoading(true) - const res = await savePredefinedLoadBalancingConfig( - provider.provider, - ({ - ...(data?.credentials ?? {}), - __model_type: model.model_type, - __model_name: model.model, - }), + const res = await updateModelLoadBalancingConfig( { - ...draftConfig, - enabled: Boolean(draftConfig?.enabled), - configs: draftConfig!.configs.map(encodeConfigEntrySecretValues), + config_from: configFrom, + model: model.model, + model_type: model.model_type, + load_balancing: { + ...draftConfig, + configs: draftConfig!.configs.map(encodeConfigEntrySecretValues), + enabled: Boolean(draftConfig?.enabled), + }, }, ) if (res.result === 'success') { notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - mutate() onSave?.(provider.provider) onClose?.() } @@ -125,7 +135,11 @@ const ModelLoadBalancingModal = ({ className='w-[640px] max-w-none px-8 pt-8' title={
-
{t('common.modelProvider.configLoadBalancing')}
+
{ + draftConfig?.enabled + ? t('common.modelProvider.auth.configLoadBalancing') + : t('common.modelProvider.auth.configModel') + }
{Boolean(model) && (
{t('common.modelProvider.providerManaged')}
{t('common.modelProvider.providerManagedDescription')}
- + /> */}
- - + { + modelCredential && ( + + ) + }
@@ -196,6 +219,7 @@ const ModelLoadBalancingModal = ({ disabled={ loading || (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2) + || isLoading } >{t('common.operation.save')}
diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx index f62addad83..4c1d1c5a8b 100644 --- a/web/context/modal-context.tsx +++ b/web/context/modal-context.tsx @@ -56,9 +56,6 @@ const ExternalAPIModal = dynamic(() => import('@/app/components/datasets/externa const ModelLoadBalancingModal = dynamic(() => import('@/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal'), { ssr: false, }) -const ModelLoadBalancingEntryModal = dynamic(() => import('@/app/components/header/account-setting/model-provider-page/model-modal/model-load-balancing-entry-modal'), { - ssr: false, -}) const OpeningSettingModal = dynamic(() => import('@/app/components/base/features/new-feature-panel/conversation-opener/modal'), { ssr: false, }) @@ -81,6 +78,7 @@ export type ModelModalType = { currentProvider: ModelProvider currentConfigurationMethod: ConfigurationMethodEnum currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields + isModelCredential?: boolean credential?: Credential model?: CustomModel } @@ -99,7 +97,6 @@ export type ModalContextState = { setShowModelModal: Dispatch | null>> setShowExternalKnowledgeAPIModal: Dispatch | null>> setShowModelLoadBalancingModal: Dispatch> - setShowModelLoadBalancingEntryModal: Dispatch | null>> setShowOpeningModal: Dispatch({ setShowModelModal: noop, setShowExternalKnowledgeAPIModal: noop, setShowModelLoadBalancingModal: noop, - setShowModelLoadBalancingEntryModal: noop, setShowOpeningModal: noop, setShowUpdatePluginModal: noop, }) @@ -142,7 +138,6 @@ export const ModalContextProvider = ({ const [showModelModal, setShowModelModal] = useState | null>(null) const [showExternalKnowledgeAPIModal, setShowExternalKnowledgeAPIModal] = useState | null>(null) const [showModelLoadBalancingModal, setShowModelLoadBalancingModal] = useState(null) - const [showModelLoadBalancingEntryModal, setShowModelLoadBalancingEntryModal] = useState | null>(null) const [showOpeningModal, setShowOpeningModal] = useState { - showModelLoadBalancingEntryModal?.onCancelCallback?.() - setShowModelLoadBalancingEntryModal(null) - }, [showModelLoadBalancingEntryModal]) - const handleCancelOpeningModal = useCallback(() => { setShowOpeningModal(null) if (showOpeningModal?.onCancelCallback) showOpeningModal.onCancelCallback() }, [showOpeningModal]) - const handleSaveModelLoadBalancingEntryModal = useCallback((entry: ModelLoadBalancingConfigEntry) => { - showModelLoadBalancingEntryModal?.onSaveCallback?.({ - ...showModelLoadBalancingEntryModal.payload, - entry, - }) - setShowModelLoadBalancingEntryModal(null) - }, [showModelLoadBalancingEntryModal]) - - const handleRemoveModelLoadBalancingEntry = useCallback(() => { - showModelLoadBalancingEntryModal?.onRemoveCallback?.(showModelLoadBalancingEntryModal.payload) - setShowModelLoadBalancingEntryModal(null) - }, [showModelLoadBalancingEntryModal]) - const handleSaveApiBasedExtension = (newApiBasedExtension: ApiBasedExtension) => { if (showApiBasedExtensionModal?.onSaveCallback) showApiBasedExtensionModal.onSaveCallback(newApiBasedExtension) @@ -273,7 +250,6 @@ export const ModalContextProvider = ({ setShowModelModal, setShowExternalKnowledgeAPIModal, setShowModelLoadBalancingModal, - setShowModelLoadBalancingEntryModal, setShowOpeningModal, setShowUpdatePluginModal, }}> @@ -340,9 +316,10 @@ export const ModalContextProvider = ({ @@ -365,19 +342,6 @@ export const ModalContextProvider = ({ ) } - { - !!showModelLoadBalancingEntryModal && ( - - ) - } {showOpeningModal && ( get<{ data: ProviderCredential }>(`/workspaces/current/model-providers/${provider}/credentials${credentialId ? `?credential_id=${credentialId}` : ''}`), + queryFn: () => get(`/workspaces/current/model-providers/${provider}/credentials${credentialId ? `?credential_id=${credentialId}` : ''}`), }) } @@ -82,7 +83,8 @@ export const useGetModelCredential = ( return useQuery({ enabled, queryKey: [NAME_SPACE, 'model-list', provider, model, modelType, credentialId], - queryFn: () => get<{ data: ModelCredential }>(`/workspaces/current/model-providers/${provider}/models/credentials?model=${model}&model_type=${modelType}$credential_id=${credentialId}$config_from=${configFrom}`), + queryFn: () => get(`/workspaces/current/model-providers/${provider}/models/credentials?model=${model}&model_type=${modelType}&config_from=${configFrom}${credentialId ? `&credential_id=${credentialId}` : ''}`), + staleTime: 0, }) } @@ -136,3 +138,16 @@ export const useActiveModelCredential = (provider: string) => { }), }) } + +export const useUpdateModelLoadBalancingConfig = (provider: string) => { + return useMutation({ + mutationFn: (data: { + config_from: string + model: string + model_type: ModelTypeEnum + load_balancing: ModelLoadBalancingConfig + }) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/models`, { + body: data, + }), + }) +}