From 9e125e2029184c4642f13a914961d35860aa998e Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Wed, 3 Sep 2025 13:36:59 +0800 Subject: [PATCH] Refactor/model credential (#24994) --- .../base/form/components/base/base-field.tsx | 37 +- .../base/form/components/base/base-form.tsx | 34 +- .../model-provider-page/declarations.ts | 12 + .../model-provider-page/hooks.ts | 25 +- .../add-credential-in-load-balancing.tsx | 71 ++-- .../model-auth/add-custom-model.tsx | 174 ++++++--- .../model-auth/authorized/authorized-item.tsx | 68 ++-- .../model-auth/authorized/credential-item.tsx | 18 +- .../model-auth/authorized/index.tsx | 137 ++++--- .../model-auth/config-model.tsx | 2 +- .../model-auth/config-provider.tsx | 41 +- .../model-auth/credential-selector.tsx | 115 ++++++ .../model-auth/hooks/use-auth-service.ts | 2 +- .../model-auth/hooks/use-auth.ts | 105 +++-- .../model-auth/hooks/use-custom-models.ts | 6 + .../hooks/use-model-form-schemas.ts | 42 +- .../model-provider-page/model-auth/index.tsx | 2 + .../manage-custom-model-credentials.tsx | 82 ++++ .../switch-credential-in-load-balancing.tsx | 32 +- .../model-provider-page/model-modal/index.tsx | 364 ++++++++++++------ .../provider-added-card/credential-panel.tsx | 1 - .../provider-added-card/index.tsx | 20 +- .../provider-added-card/model-list.tsx | 9 +- .../model-load-balancing-configs.tsx | 61 ++- .../model-load-balancing-modal.tsx | 317 ++++++++++----- .../model-provider-page/utils.ts | 2 +- web/context/modal-context.tsx | 23 +- web/i18n/en-US/common.ts | 9 + web/i18n/zh-Hans/common.ts | 9 + web/service/use-models.ts | 2 +- 30 files changed, 1226 insertions(+), 596 deletions(-) create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/manage-custom-model-credentials.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 f25dfb069..bf415e08a 100644 --- a/web/app/components/base/form/components/base/base-field.tsx +++ b/web/app/components/base/form/components/base/base-field.tsx @@ -1,6 +1,7 @@ import { isValidElement, memo, + useCallback, useMemo, } from 'react' import { RiExternalLinkLine } from '@remixicon/react' @@ -23,6 +24,7 @@ export type BaseFieldProps = { formSchema: FormSchema field: AnyFieldApi disabled?: boolean + onChange?: (field: string, value: any) => void } const BaseField = ({ fieldClassName, @@ -32,6 +34,7 @@ const BaseField = ({ formSchema, field, disabled: propsDisabled, + onChange, }: BaseFieldProps) => { const renderI18nObject = useRenderI18nObject() const { @@ -40,7 +43,6 @@ const BaseField = ({ placeholder, options, labelClassName: formLabelClassName, - show_on = [], disabled: formSchemaDisabled, } = formSchema const disabled = propsDisabled || formSchemaDisabled @@ -90,21 +92,11 @@ const BaseField = ({ }) || [] }, [options, renderI18nObject, optionValues]) const value = useStore(field.form.store, s => s.values[field.name]) - const values = useStore(field.form.store, (s) => { - return show_on.reduce((acc, condition) => { - acc[condition.variable] = s.values[condition.variable] - return acc - }, {} as Record) - }) - const show = useMemo(() => { - return show_on.every((condition) => { - const conditionValue = values[condition.variable] - return conditionValue === condition.value - }) - }, [values, show_on]) - if (!show) - return null + const handleChange = useCallback((value: any) => { + field.handleChange(value) + onChange?.(field.name, value) + }, [field, onChange]) return (
@@ -124,7 +116,9 @@ const BaseField = ({ name={field.name} className={cn(inputClassName)} value={value || ''} - onChange={e => field.handleChange(e.target.value)} + onChange={(e) => { + handleChange(e.target.value) + }} onBlur={field.handleBlur} disabled={disabled} placeholder={memorizedPlaceholder} @@ -139,7 +133,7 @@ const BaseField = ({ type='password' className={cn(inputClassName)} value={value || ''} - onChange={e => field.handleChange(e.target.value)} + onChange={e => handleChange(e.target.value)} onBlur={field.handleBlur} disabled={disabled} placeholder={memorizedPlaceholder} @@ -155,7 +149,7 @@ const BaseField = ({ type='number' className={cn(inputClassName)} value={value || ''} - onChange={e => field.handleChange(e.target.value)} + onChange={e => handleChange(e.target.value)} onBlur={field.handleBlur} disabled={disabled} placeholder={memorizedPlaceholder} @@ -166,11 +160,14 @@ const BaseField = ({ formSchema.type === FormTypeEnum.select && ( field.handleChange(v)} + onChange={v => handleChange(v)} disabled={disabled} placeholder={memorizedPlaceholder} options={memorizedOptions} triggerPopupSameWidth + popupProps={{ + className: 'max-h-[320px] overflow-y-auto', + }} /> ) } @@ -189,7 +186,7 @@ const BaseField = ({ disabled && 'cursor-not-allowed opacity-50', inputClassName, )} - onClick={() => !disabled && field.handleChange(option.value)} + onClick={() => !disabled && handleChange(option.value)} > { formSchema.showRadioUI && ( diff --git a/web/app/components/base/form/components/base/base-form.tsx b/web/app/components/base/form/components/base/base-form.tsx index c056829db..6b7e99251 100644 --- a/web/app/components/base/form/components/base/base-form.tsx +++ b/web/app/components/base/form/components/base/base-form.tsx @@ -8,7 +8,10 @@ import type { AnyFieldApi, AnyFormApi, } from '@tanstack/react-form' -import { useForm } from '@tanstack/react-form' +import { + useForm, + useStore, +} from '@tanstack/react-form' import type { FormRef, FormSchema, @@ -32,6 +35,7 @@ export type BaseFormProps = { ref?: FormRef disabled?: boolean formFromProps?: AnyFormApi + onChange?: (field: string, value: any) => void } & Pick const BaseForm = ({ @@ -45,6 +49,7 @@ const BaseForm = ({ ref, disabled, formFromProps, + onChange, }: BaseFormProps) => { const initialDefaultValues = useMemo(() => { if (defaultValues) @@ -63,6 +68,19 @@ const BaseForm = ({ const { getFormValues } = useGetFormValues(form, formSchemas) const { getValidators } = useGetValidators() + const showOnValues = useStore(form.store, (s: any) => { + const result: Record = {} + formSchemas.forEach((schema) => { + const { show_on } = schema + if (show_on?.length) { + show_on.forEach((condition) => { + result[condition.variable] = s.values[condition.variable] + }) + } + }) + return result + }) + useImperativeHandle(ref, () => { return { getForm() { @@ -87,19 +105,29 @@ const BaseForm = ({ inputContainerClassName={inputContainerClassName} inputClassName={inputClassName} disabled={disabled} + onChange={onChange} /> ) } return null - }, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName, disabled]) + }, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName, disabled, onChange]) const renderFieldWrapper = useCallback((formSchema: FormSchema) => { const validators = getValidators(formSchema) const { name, + show_on = [], } = formSchema + const show = show_on?.every((condition) => { + const conditionValue = showOnValues[condition.variable] + return conditionValue === condition.value + }) + + if (!show) + return null + return ( ) - }, [renderField, form, getValidators]) + }, [renderField, form, getValidators, showOnValues]) if (!formSchemas?.length) return null 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 74f47c9d1..9fac34b21 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 @@ -199,6 +199,7 @@ export type CustomModelCredential = CustomModel & { credentials?: Record available_model_credentials?: Credential[] current_credential_id?: string + current_credential_name?: string } export type CredentialWithModel = Credential & { @@ -236,6 +237,10 @@ export type ModelProvider = { current_credential_name?: string available_credentials?: Credential[] custom_models?: CustomModelCredential[] + can_added_models?: { + model: string + model_type: ModelTypeEnum + }[] } system_configuration: { enabled: boolean @@ -323,3 +328,10 @@ export type ModelCredential = { current_credential_id?: string current_credential_name?: string } + +export enum ModelModalModeEnum { + configProviderCredential = 'config-provider-credential', + configCustomModel = 'config-custom-model', + addCustomModelToModelList = 'add-custom-model-to-model-list', + configModelCredential = 'config-model-credential', +} 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 fa5130137..c9e4f9961 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 @@ -13,6 +13,7 @@ import type { DefaultModel, DefaultModelResponse, Model, + ModelModalModeEnum, ModelProvider, ModelTypeEnum, } from './declarations' @@ -348,29 +349,31 @@ export const useRefreshModel = () => { export const useModelModalHandler = () => { const setShowModelModal = useModalContextSelector(state => state.setShowModelModal) - const { handleRefreshModel } = useRefreshModel() return ( provider: ModelProvider, configurationMethod: ConfigurationMethodEnum, CustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, - isModelCredential?: boolean, - credential?: Credential, - model?: CustomModel, - onUpdate?: () => void, + extra: { + isModelCredential?: boolean, + credential?: Credential, + model?: CustomModel, + onUpdate?: (newPayload: any, formValues?: Record) => void, + mode?: ModelModalModeEnum, + } = {}, ) => { setShowModelModal({ payload: { currentProvider: provider, currentConfigurationMethod: configurationMethod, currentCustomConfigurationModelFixedFields: CustomConfigurationModelFixedFields, - isModelCredential, - credential, - model, + isModelCredential: extra.isModelCredential, + credential: extra.credential, + model: extra.model, + mode: extra.mode, }, - onSaveCallback: () => { - handleRefreshModel(provider, configurationMethod, CustomConfigurationModelFixedFields) - onUpdate?.() + onSaveCallback: (newPayload, formValues) => { + extra.onUpdate?.(newPayload, formValues) }, }) } 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 a0c78e329..30d56bced 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 @@ -1,7 +1,6 @@ import { memo, useCallback, - useMemo, } from 'react' import { RiAddLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' @@ -9,20 +8,22 @@ import { Authorized } from '@/app/components/header/account-setting/model-provid import cn from '@/utils/classnames' import type { Credential, + CustomConfigurationModelFixedFields, 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' -import Tooltip from '@/app/components/base/tooltip' +import { ConfigurationMethodEnum, ModelModalModeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' type AddCredentialInLoadBalancingProps = { provider: ModelProvider model: CustomModelCredential configurationMethod: ConfigurationMethodEnum + currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields modelCredential: ModelCredential onSelectCredential: (credential: Credential) => void - onUpdate?: () => void + onUpdate?: (payload?: any, formValues?: Record) => void + onRemove?: (credentialId: string) => void } const AddCredentialInLoadBalancing = ({ provider, @@ -31,41 +32,17 @@ const AddCredentialInLoadBalancing = ({ modelCredential, onSelectCredential, onUpdate, + onRemove, }: AddCredentialInLoadBalancingProps) => { const { t } = useTranslation() const { available_credentials, } = modelCredential - const customModel = configurationMethod === ConfigurationMethodEnum.customizableModel + const isCustomModel = configurationMethod === ConfigurationMethodEnum.customizableModel const notAllowCustomCredential = provider.allow_custom_token === false - - const ButtonComponent = useMemo(() => { - const Item = ( -
- - { - customModel - ? t('common.modelProvider.auth.addCredential') - : t('common.modelProvider.auth.addApiKey') - } -
- ) - - if (notAllowCustomCredential) { - return ( - - {Item} - - ) - } - return Item - }, [notAllowCustomCredential, t, customModel]) + const handleUpdate = useCallback((payload?: any, formValues?: Record) => { + onUpdate?.(payload, formValues) + }, [onUpdate]) const renderTrigger = useCallback((open?: boolean) => { const Item = ( @@ -74,40 +51,40 @@ const AddCredentialInLoadBalancing = ({ open && 'bg-state-base-hover', )}> - { - customModel - ? t('common.modelProvider.auth.addCredential') - : t('common.modelProvider.auth.addApiKey') - } + {t('common.modelProvider.auth.addCredential')}
) return Item - }, [t, customModel]) - - if (!available_credentials?.length) - return ButtonComponent + }, [t, isCustomModel]) 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 0ec6fa45a..dd9284398 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 @@ -1,32 +1,39 @@ import { memo, useCallback, - useMemo, + useState, } from 'react' import { useTranslation } from 'react-i18next' import { RiAddCircleFill, + RiAddLine, } from '@remixicon/react' import { Button, } from '@/app/components/base/button' import type { + ConfigurationMethodEnum, CustomConfigurationModelFixedFields, 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, - useCustomModels, -} from './hooks' +import { ModelModalModeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import cn from '@/utils/classnames' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import ModelIcon from '../model-icon' +import { useCanAddedModels } from './hooks/use-custom-models' +import { useAuth } from './hooks/use-auth' import Tooltip from '@/app/components/base/tooltip' type AddCustomModelProps = { provider: ModelProvider, configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, + open?: boolean + onOpenChange?: (open: boolean) => void } const AddCustomModel = ({ provider, @@ -34,44 +41,32 @@ const AddCustomModel = ({ currentCustomConfigurationModelFixedFields, }: AddCustomModelProps) => { const { t } = useTranslation() - const customModels = useCustomModels(provider) - const noModels = !customModels.length + const [open, setOpen] = useState(false) + const canAddedModels = useCanAddedModels(provider) + const noModels = !canAddedModels.length const { - handleOpenModal, - } = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields, true) + handleOpenModal: handleOpenModalForAddNewCustomModel, + } = useAuth( + provider, + configurationMethod, + currentCustomConfigurationModelFixedFields, + { + isModelCredential: true, + mode: ModelModalModeEnum.configCustomModel, + }, + ) + const { + handleOpenModal: handleOpenModalForAddCustomModelToModelList, + } = useAuth( + provider, + configurationMethod, + currentCustomConfigurationModelFixedFields, + { + isModelCredential: true, + mode: ModelModalModeEnum.addCustomModelToModelList, + }, + ) const notAllowCustomCredential = provider.allow_custom_token === false - const handleClick = useCallback(() => { - if (notAllowCustomCredential) - return - - handleOpenModal() - }, [handleOpenModal, notAllowCustomCredential]) - const ButtonComponent = useMemo(() => { - const Item = ( - - ) - if (notAllowCustomCredential) { - return ( - - {Item} - - ) - } - return Item - }, [handleClick, notAllowCustomCredential, t]) const renderTrigger = useCallback((open?: boolean) => { const Item = ( @@ -79,32 +74,93 @@ const AddCustomModel = ({ variant='ghost' size='small' className={cn( + 'text-text-tertiary', open && 'bg-components-button-ghost-bg-hover', + notAllowCustomCredential && !!noModels && 'cursor-not-allowed opacity-50', )} > {t('common.modelProvider.addModel')} ) + if (notAllowCustomCredential && !!noModels) { + return ( + + {Item} + + ) + } return Item - }, [t]) - - if (noModels) - return ButtonComponent + }, [t, notAllowCustomCredential, noModels]) return ( - ({ - model, - credentials: model.available_model_credentials ?? [], - }))} - renderTrigger={renderTrigger} - isModelCredential - enableAddModelCredential - bottomAddModelCredentialText={t('common.modelProvider.auth.addNewModel')} - /> + + { + if (noModels) { + if (notAllowCustomCredential) + return + handleOpenModalForAddNewCustomModel() + return + } + + setOpen(prev => !prev) + }}> + {renderTrigger(open)} + + +
+
+ { + canAddedModels.map(model => ( +
{ + handleOpenModalForAddCustomModelToModelList(undefined, model) + setOpen(false) + }} + > + +
+ {model.model} +
+
+ )) + } +
+ { + !notAllowCustomCredential && ( +
{ + handleOpenModalForAddNewCustomModel() + setOpen(false) + }} + > + + {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 4f4c30bc9..10dc48585 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 @@ -2,18 +2,17 @@ import { memo, useCallback, } from 'react' -import { RiAddLine } from '@remixicon/react' -import { useTranslation } from 'react-i18next' import CredentialItem from './credential-item' import type { Credential, CustomModel, CustomModelCredential, + ModelProvider, } from '../../declarations' -import Button from '@/app/components/base/button' -import Tooltip from '@/app/components/base/tooltip' +import ModelIcon from '../../model-icon' type AuthorizedItemProps = { + provider: ModelProvider model?: CustomModelCredential title?: string disabled?: boolean @@ -25,8 +24,12 @@ type AuthorizedItemProps = { onItemClick?: (credential: Credential, model?: CustomModel) => void enableAddModelCredential?: boolean notAllowCustomCredential?: boolean + showModelTitle?: boolean + disableDeleteButShowAction?: boolean + disableDeleteTip?: string } export const AuthorizedItem = ({ + provider, model, title, credentials, @@ -36,10 +39,10 @@ export const AuthorizedItem = ({ showItemSelectedIcon, selectedCredentialId, onItemClick, - enableAddModelCredential, - notAllowCustomCredential, + showModelTitle, + disableDeleteButShowAction, + disableDeleteTip, }: AuthorizedItemProps) => { - const { t } = useTranslation() const handleEdit = useCallback((credential?: Credential) => { onEdit?.(credential, model) }, [onEdit, model]) @@ -52,34 +55,29 @@ export const AuthorizedItem = ({ return (
-
-
-
- {title ?? model?.model} -
- { - enableAddModelCredential && !notAllowCustomCredential && ( - + { + model?.model && ( + + ) + } +
- - - ) - } -
+ {title ?? model?.model} +
+
+ ) + } { credentials.map(credential => ( )) } 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 6596e64e0..2d792d170 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 @@ -24,6 +24,8 @@ type CredentialItemProps = { disableRename?: boolean disableEdit?: boolean disableDelete?: boolean + disableDeleteButShowAction?: boolean + disableDeleteTip?: string showSelectedIcon?: boolean selectedCredentialId?: string } @@ -36,6 +38,8 @@ const CredentialItem = ({ disableRename, disableEdit, disableDelete, + disableDeleteButShowAction, + disableDeleteTip, showSelectedIcon, selectedCredentialId, }: CredentialItemProps) => { @@ -43,6 +47,9 @@ const CredentialItem = ({ const showAction = useMemo(() => { return !(disableRename && disableEdit && disableDelete) }, [disableRename, disableEdit, disableDelete]) + const disableDeleteWhenSelected = useMemo(() => { + return disableDeleteButShowAction && selectedCredentialId === credential.credential_id + }, [disableDeleteButShowAction, selectedCredentialId, credential.credential_id]) const Item = (
+ { + if (disabled || disableDeleteWhenSelected) + return e.stopPropagation() 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 2aa64ffb8..6504fbc37 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 @@ -1,12 +1,11 @@ import { + Fragment, memo, useCallback, - useMemo, useState, } from 'react' import { RiAddLine, - RiEqualizer2Line, } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { @@ -25,6 +24,7 @@ import type { Credential, CustomConfigurationModelFixedFields, CustomModel, + ModelModalModeEnum, ModelProvider, } from '../../declarations' import { useAuth } from '../hooks' @@ -34,15 +34,20 @@ type AuthorizedProps = { provider: ModelProvider, configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, - isModelCredential?: boolean + authParams?: { + isModelCredential?: boolean + onUpdate?: (newPayload?: any, formValues?: Record) => void + onRemove?: (credentialId: string) => void + mode?: ModelModalModeEnum + } items: { title?: string model?: CustomModel + selectedCredential?: Credential credentials: Credential[] }[] - selectedCredential?: Credential disabled?: boolean - renderTrigger?: (open?: boolean) => React.ReactNode + renderTrigger: (open?: boolean) => React.ReactNode isOpen?: boolean onOpenChange?: (open: boolean) => void offset?: PortalToFollowElemOptions['offset'] @@ -50,18 +55,22 @@ type AuthorizedProps = { triggerPopupSameWidth?: boolean popupClassName?: string showItemSelectedIcon?: boolean - onUpdate?: () => void onItemClick?: (credential: Credential, model?: CustomModel) => void enableAddModelCredential?: boolean - bottomAddModelCredentialText?: string + triggerOnlyOpenModal?: boolean + hideAddAction?: boolean + disableItemClick?: boolean + popupTitle?: string + showModelTitle?: boolean + disableDeleteButShowAction?: boolean + disableDeleteTip?: string } const Authorized = ({ provider, configurationMethod, currentCustomConfigurationModelFixedFields, items, - isModelCredential, - selectedCredential, + authParams, disabled, renderTrigger, isOpen, @@ -71,10 +80,14 @@ const Authorized = ({ triggerPopupSameWidth = false, popupClassName, showItemSelectedIcon, - onUpdate, onItemClick, - enableAddModelCredential, - bottomAddModelCredentialText, + triggerOnlyOpenModal, + hideAddAction, + disableItemClick, + popupTitle, + showModelTitle, + disableDeleteButShowAction, + disableDeleteTip, }: AuthorizedProps) => { const { t } = useTranslation() const [isLocalOpen, setIsLocalOpen] = useState(false) @@ -85,6 +98,12 @@ const Authorized = ({ setIsLocalOpen(open) }, [onOpenChange]) + const { + isModelCredential, + onUpdate, + onRemove, + mode, + } = authParams || {} const { openConfirmDelete, closeConfirmDelete, @@ -93,7 +112,17 @@ const Authorized = ({ handleConfirmDelete, deleteCredentialId, handleOpenModal, - } = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields, isModelCredential, onUpdate) + } = useAuth( + provider, + configurationMethod, + currentCustomConfigurationModelFixedFields, + { + isModelCredential, + onUpdate, + onRemove, + mode, + }, + ) const handleEdit = useCallback((credential?: Credential, model?: CustomModel) => { handleOpenModal(credential, model) @@ -101,28 +130,18 @@ const Authorized = ({ }, [handleOpenModal, setMergedIsOpen]) const handleItemClick = useCallback((credential: Credential, model?: CustomModel) => { + if (disableItemClick) + return + if (onItemClick) onItemClick(credential, model) else handleActiveCredential(credential, model) setMergedIsOpen(false) - }, [handleActiveCredential, onItemClick, setMergedIsOpen]) + }, [handleActiveCredential, onItemClick, setMergedIsOpen, disableItemClick]) const notAllowCustomCredential = provider.allow_custom_token === false - const Trigger = useMemo(() => { - const Item = ( - - ) - return Item - }, [t]) - return ( <> { + if (triggerOnlyOpenModal) { + handleOpenModal() + return + } + setMergedIsOpen(!mergedIsOpen) }} asChild > - { - renderTrigger - ? renderTrigger(mergedIsOpen) - : Trigger - } + {renderTrigger(mergedIsOpen)}
+ { + popupTitle && ( +
+ {popupTitle} +
+ ) + }
{ items.map((item, index) => ( - + + + { + index !== items.length - 1 && ( +
+ ) + } +
)) }
{ - isModelCredential && !notAllowCustomCredential && ( + isModelCredential && !notAllowCustomCredential && !hideAddAction && (
handleEdit( undefined, @@ -182,15 +217,15 @@ const Authorized = ({ } : undefined, )} - className='system-xs-medium flex h-[30px] cursor-pointer items-center px-3 text-text-accent-light-mode-only' + className='system-xs-medium flex h-[40px] cursor-pointer items-center px-3 text-text-accent-light-mode-only' > - {bottomAddModelCredentialText ?? t('common.modelProvider.auth.addModelCredential')} + {t('common.modelProvider.auth.addModelCredential')}
) } { - !isModelCredential && !notAllowCustomCredential && ( + !isModelCredential && !notAllowCustomCredential && !hideAddAction && (
) - if (notAllowCustomCredential) { + if (notAllowCustomCredential && !hasCredential) { return ( ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx new file mode 100644 index 000000000..ef0a9a9be --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx @@ -0,0 +1,115 @@ +import { + memo, + useCallback, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { + RiAddLine, + RiArrowDownSLine, +} from '@remixicon/react' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import type { Credential } from '@/app/components/header/account-setting/model-provider-page/declarations' +import CredentialItem from './authorized/credential-item' +import Badge from '@/app/components/base/badge' +import Indicator from '@/app/components/header/indicator' + +type CredentialSelectorProps = { + selectedCredential?: Credential & { addNewCredential?: boolean } + credentials: Credential[] + onSelect: (credential: Credential & { addNewCredential?: boolean }) => void + disabled?: boolean + notAllowAddNewCredential?: boolean +} +const CredentialSelector = ({ + selectedCredential, + credentials, + onSelect, + disabled, + notAllowAddNewCredential, +}: CredentialSelectorProps) => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + const handleSelect = useCallback((credential: Credential & { addNewCredential?: boolean }) => { + setOpen(false) + onSelect(credential) + }, [onSelect]) + const handleAddNewCredential = useCallback(() => { + handleSelect({ + credential_id: '__add_new_credential', + addNewCredential: true, + credential_name: t('common.modelProvider.auth.addNewModelCredential'), + }) + }, [handleSelect, t]) + + return ( + + !disabled && setOpen(v => !v)}> +
+ { + selectedCredential && ( +
+ { + !selectedCredential.addNewCredential && + } +
{selectedCredential.credential_name}
+ { + selectedCredential.from_enterprise && ( + Enterprise + ) + } +
+ ) + } + { + !selectedCredential && ( +
{t('common.modelProvider.auth.selectModelCredential')}
+ ) + } + +
+
+ +
+
+ { + credentials.map(credential => ( + + )) + } +
+ { + !notAllowAddNewCredential && ( +
+ + {t('common.modelProvider.auth.addNewModelCredential')} +
+ ) + } +
+
+
+ ) +} + +export default memo(CredentialSelector) 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 317a1fe1a..6de1333ea 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 @@ -17,7 +17,7 @@ import type { 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) + const modelData = useGetModelCredential(!!isModelCredential && (!!credentialId || !!model), provider, credentialId, model?.model, model?.model_type, configFrom) return isModelCredential ? modelData : providerData } 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 d4a0417a4..14b21be7f 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 @@ -11,20 +11,32 @@ import type { Credential, CustomConfigurationModelFixedFields, CustomModel, + ModelModalModeEnum, ModelProvider, } from '../../declarations' import { useModelModalHandler, useRefreshModel, } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { useDeleteModel } from '@/service/use-models' export const useAuth = ( provider: ModelProvider, configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, - isModelCredential?: boolean, - onUpdate?: () => void, + extra: { + isModelCredential?: boolean, + onUpdate?: (newPayload?: any, formValues?: Record) => void, + onRemove?: (credentialId: string) => void, + mode?: ModelModalModeEnum, + } = {}, ) => { + const { + isModelCredential, + onUpdate, + onRemove, + mode, + } = extra const { t } = useTranslation() const { notify } = useToastContext() const { @@ -33,22 +45,30 @@ export const useAuth = ( getEditCredentialService, getAddCredentialService, } = useAuthService(provider.provider) + const { mutateAsync: deleteModelService } = useDeleteModel(provider.provider) const handleOpenModelModal = useModelModalHandler() const { handleRefreshModel } = useRefreshModel() const pendingOperationCredentialId = useRef(null) - const pendingOperationModel = useRef(null) const [deleteCredentialId, setDeleteCredentialId] = useState(null) + const handleSetDeleteCredentialId = useCallback((credentialId: string | null) => { + setDeleteCredentialId(credentialId) + pendingOperationCredentialId.current = credentialId + }, []) + const pendingOperationModel = useRef(null) + const [deleteModel, setDeleteModel] = useState(null) + const handleSetDeleteModel = useCallback((model: CustomModel | null) => { + setDeleteModel(model) + pendingOperationModel.current = model + }, []) const openConfirmDelete = useCallback((credential?: Credential, model?: CustomModel) => { if (credential) - pendingOperationCredentialId.current = credential.credential_id + handleSetDeleteCredentialId(credential.credential_id) if (model) - pendingOperationModel.current = model - - setDeleteCredentialId(pendingOperationCredentialId.current) + handleSetDeleteModel(model) }, []) const closeConfirmDelete = useCallback(() => { - setDeleteCredentialId(null) - pendingOperationCredentialId.current = null + handleSetDeleteCredentialId(null) + handleSetDeleteModel(null) }, []) const [doingAction, setDoingAction] = useState(false) const doingActionRef = useRef(doingAction) @@ -70,45 +90,49 @@ export const useAuth = ( type: 'success', message: t('common.api.actionSuccess'), }) - onUpdate?.() handleRefreshModel(provider, configurationMethod, undefined) } finally { handleSetDoingAction(false) } - }, [getActiveCredentialService, onUpdate, notify, t, handleSetDoingAction]) + }, [getActiveCredentialService, notify, t, handleSetDoingAction]) const handleConfirmDelete = useCallback(async () => { if (doingActionRef.current) return - if (!pendingOperationCredentialId.current) { - setDeleteCredentialId(null) + if (!pendingOperationCredentialId.current && !pendingOperationModel.current) { + closeConfirmDelete() return } try { handleSetDoingAction(true) - await getDeleteCredentialService(!!isModelCredential)({ - credential_id: pendingOperationCredentialId.current, - model: pendingOperationModel.current?.model, - model_type: pendingOperationModel.current?.model_type, - }) + let payload: any = {} + if (pendingOperationCredentialId.current) { + payload = { + credential_id: pendingOperationCredentialId.current, + model: pendingOperationModel.current?.model, + model_type: pendingOperationModel.current?.model_type, + } + await getDeleteCredentialService(!!isModelCredential)(payload) + } + if (!pendingOperationCredentialId.current && pendingOperationModel.current) { + payload = { + model: pendingOperationModel.current.model, + model_type: pendingOperationModel.current.model_type, + } + await deleteModelService(payload) + } notify({ type: 'success', message: t('common.api.actionSuccess'), }) - onUpdate?.() handleRefreshModel(provider, configurationMethod, undefined) - setDeleteCredentialId(null) - pendingOperationCredentialId.current = null - pendingOperationModel.current = null + onRemove?.(pendingOperationCredentialId.current ?? '') + closeConfirmDelete() } finally { handleSetDoingAction(false) } - }, [onUpdate, notify, t, handleSetDoingAction, getDeleteCredentialService, isModelCredential]) - const handleAddCredential = useCallback((model?: CustomModel) => { - if (model) - pendingOperationModel.current = model - }, []) + }, [notify, t, handleSetDoingAction, getDeleteCredentialService, isModelCredential, closeConfirmDelete, handleRefreshModel, provider, configurationMethod, deleteModelService]) const handleSaveCredential = useCallback(async (payload: Record) => { if (doingActionRef.current) return @@ -123,24 +147,35 @@ export const useAuth = ( if (res.result === 'success') { notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - onUpdate?.() + handleRefreshModel(provider, configurationMethod, undefined) } } finally { handleSetDoingAction(false) } - }, [onUpdate, notify, t, handleSetDoingAction, getEditCredentialService, getAddCredentialService]) + }, [notify, t, handleSetDoingAction, getEditCredentialService, getAddCredentialService]) const handleOpenModal = useCallback((credential?: Credential, model?: CustomModel) => { handleOpenModelModal( provider, configurationMethod, currentCustomConfigurationModelFixedFields, - isModelCredential, - credential, - model, - onUpdate, + { + isModelCredential, + credential, + model, + onUpdate, + mode, + }, ) - }, [handleOpenModelModal, provider, configurationMethod, currentCustomConfigurationModelFixedFields, isModelCredential, onUpdate]) + }, [ + handleOpenModelModal, + provider, + configurationMethod, + currentCustomConfigurationModelFixedFields, + isModelCredential, + onUpdate, + mode, + ]) return { pendingOperationCredentialId, @@ -150,8 +185,8 @@ export const useAuth = ( doingAction, handleActiveCredential, handleConfirmDelete, - handleAddCredential, deleteCredentialId, + deleteModel, handleSaveCredential, handleOpenModal, } diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-custom-models.ts b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-custom-models.ts index f3b50f3f4..6abf6f51b 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-custom-models.ts +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-custom-models.ts @@ -7,3 +7,9 @@ export const useCustomModels = (provider: ModelProvider) => { return custom_models || [] } + +export const useCanAddedModels = (provider: ModelProvider) => { + const { can_added_models } = provider.custom_configuration + + return can_added_models || [] +} 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 22fab62be..1cbe8f20b 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 @@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next' import type { Credential, CustomModelCredential, - ModelLoadBalancingConfig, ModelProvider, } from '../../declarations' import { @@ -18,7 +17,6 @@ export const useModelFormSchemas = ( credentials?: Record, credential?: Credential, model?: CustomModelCredential, - draftConfig?: ModelLoadBalancingConfig, ) => { const { t } = useTranslation() const { @@ -27,26 +25,15 @@ 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 - : [ - modelTypeSchema, - modelNameSchema, - ...(draftConfig?.enabled ? [] : model_credential_schema.credential_form_schemas), - ] + : model_credential_schema.credential_form_schemas }, [ providerFormSchemaPredefined, provider_credential_schema?.credential_form_schemas, supported_model_types, model_credential_schema?.credential_form_schemas, model_credential_schema?.model, - draftConfig?.enabled, model, ]) @@ -55,7 +42,7 @@ export const useModelFormSchemas = ( type: FormTypeEnum.textInput, variable: '__authorization_name__', label: t('plugin.auth.authorizationName'), - required: true, + required: false, } return [ @@ -79,8 +66,33 @@ export const useModelFormSchemas = ( return result }, [credentials, credential, model, formSchemas]) + const modelNameAndTypeFormSchemas = useMemo(() => { + if (providerFormSchemaPredefined) + return [] + + const modelNameSchema = genModelNameFormSchema(model_credential_schema?.model) + const modelTypeSchema = genModelTypeFormSchema(supported_model_types) + return [ + modelNameSchema, + modelTypeSchema, + ] + }, [supported_model_types, model_credential_schema?.model, providerFormSchemaPredefined]) + + const modelNameAndTypeFormValues = useMemo(() => { + let result = {} + if (providerFormSchemaPredefined) + return result + + if (model) + result = { ...result, __model_name: model?.model, __model_type: model?.model_type } + + return result + }, [model, providerFormSchemaPredefined]) + return { formSchemas: formSchemasWithAuthorizationName, formValues, + modelNameAndTypeFormSchemas, + modelNameAndTypeFormValues, } } 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 05effcea7..f9708607a 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 @@ -4,3 +4,5 @@ export { default as AddCredentialInLoadBalancing } from './add-credential-in-loa export { default as AddCustomModel } from './add-custom-model' export { default as ConfigProvider } from './config-provider' export { default as ConfigModel } from './config-model' +export { default as ManageCustomModelCredentials } from './manage-custom-model-credentials' +export { default as CredentialSelector } from './credential-selector' diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/manage-custom-model-credentials.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/manage-custom-model-credentials.tsx new file mode 100644 index 000000000..3a9d10ea4 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/manage-custom-model-credentials.tsx @@ -0,0 +1,82 @@ +import { + memo, + useCallback, +} from 'react' +import { useTranslation } from 'react-i18next' +import { + Button, +} from '@/app/components/base/button' +import type { + CustomConfigurationModelFixedFields, + ModelProvider, +} from '@/app/components/header/account-setting/model-provider-page/declarations' +import { + ConfigurationMethodEnum, + ModelModalModeEnum, +} from '@/app/components/header/account-setting/model-provider-page/declarations' +import Authorized from './authorized' +import { + useCustomModels, +} from './hooks' +import cn from '@/utils/classnames' + +type ManageCustomModelCredentialsProps = { + provider: ModelProvider, + currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, +} +const ManageCustomModelCredentials = ({ + provider, + currentCustomConfigurationModelFixedFields, +}: ManageCustomModelCredentialsProps) => { + const { t } = useTranslation() + const customModels = useCustomModels(provider) + const noModels = !customModels.length + + const renderTrigger = useCallback((open?: boolean) => { + const Item = ( + + ) + return Item + }, [t]) + + if (noModels) + return null + + return ( + ({ + model, + credentials: model.available_model_credentials ?? [], + selectedCredential: model.current_credential_id ? { + credential_id: model.current_credential_id, + credential_name: model.current_credential_name, + } : undefined, + }))} + renderTrigger={renderTrigger} + authParams={{ + isModelCredential: true, + mode: ModelModalModeEnum.configModelCredential, + }} + hideAddAction + disableItemClick + popupTitle={t('common.modelProvider.auth.customModelCredentials')} + showModelTitle + disableDeleteButShowAction + disableDeleteTip={t('common.modelProvider.auth.customModelCredentialsDeleteTip')} + /> + ) +} + +export default memo(ManageCustomModelCredentials) 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 8f81107bb..6ca120aea 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 @@ -13,7 +13,7 @@ import type { CustomModel, ModelProvider, } from '../declarations' -import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { ConfigurationMethodEnum, ModelModalModeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import cn from '@/utils/classnames' import Tooltip from '@/app/components/base/tooltip' import Badge from '@/app/components/base/badge' @@ -24,6 +24,8 @@ type SwitchCredentialInLoadBalancingProps = { credentials?: Credential[] customModelCredential?: Credential setCustomModelCredential: Dispatch> + onUpdate?: (payload?: any, formValues?: Record) => void + onRemove?: (credentialId: string) => void } const SwitchCredentialInLoadBalancing = ({ provider, @@ -31,6 +33,8 @@ const SwitchCredentialInLoadBalancing = ({ customModelCredential, setCustomModelCredential, credentials, + onUpdate, + onRemove, }: SwitchCredentialInLoadBalancingProps) => { const { t } = useTranslation() @@ -94,27 +98,31 @@ const SwitchCredentialInLoadBalancing = ({ ) } 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 d754d24d9..adf633831 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 @@ -5,6 +5,7 @@ import { useEffect, useMemo, useRef, + useState, } from 'react' import { RiCloseLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' @@ -15,6 +16,7 @@ import type { import { ConfigurationMethodEnum, FormTypeEnum, + ModelModalModeEnum, } from '../declarations' import { useLanguage, @@ -46,16 +48,19 @@ import { import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon' import Badge from '@/app/components/base/badge' import { useRenderI18nObject } from '@/hooks/use-i18n' +import { CredentialSelector } from '../model-auth' type ModelModalProps = { provider: ModelProvider configurateMethod: ConfigurationMethodEnum currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields onCancel: () => void - onSave: () => void + onSave: (formValues?: Record) => void + onRemove: (formValues?: Record) => void model?: CustomModel credential?: Credential isModelCredential?: boolean + mode?: ModelModalModeEnum } const ModelModal: FC = ({ @@ -67,6 +72,7 @@ const ModelModal: FC = ({ model, credential, isModelCredential, + mode = ModelModalModeEnum.configProviderCredential, }) => { const renderI18nObject = useRenderI18nObject() const providerFormSchemaPredefined = configurateMethod === ConfigurationMethodEnum.predefinedModel @@ -81,40 +87,88 @@ const ModelModal: FC = ({ closeConfirmDelete, openConfirmDelete, doingAction, - } = useAuth(provider, configurateMethod, currentCustomConfigurationModelFixedFields, isModelCredential, onSave) + handleActiveCredential, + } = useAuth( + provider, + configurateMethod, + currentCustomConfigurationModelFixedFields, + { + isModelCredential, + mode, + }, + ) const { credentials: formSchemasValue, + available_credentials, } = credentialData as any const { isCurrentWorkspaceManager } = useAppContext() - const isEditMode = !!formSchemasValue && isCurrentWorkspaceManager const { t } = useTranslation() const language = useLanguage() const { formSchemas, formValues, + modelNameAndTypeFormSchemas, + modelNameAndTypeFormValues, } = useModelFormSchemas(provider, providerFormSchemaPredefined, formSchemasValue, credential, model) - const formRef = useRef(null) + const formRef1 = useRef(null) + const [selectedCredential, setSelectedCredential] = useState() + const formRef2 = useRef(null) + const isEditMode = !!Object.keys(formValues).filter((key) => { + return key !== '__model_name' && key !== '__model_type' + }).length && isCurrentWorkspaceManager const handleSave = useCallback(async () => { + if (mode === ModelModalModeEnum.addCustomModelToModelList && selectedCredential && !selectedCredential?.addNewCredential) { + handleActiveCredential(selectedCredential, model) + onCancel() + return + } + + let modelNameAndTypeIsCheckValidated = true + let modelNameAndTypeValues: Record = {} + + if (mode === ModelModalModeEnum.configCustomModel) { + const formResult = formRef1.current?.getFormValues({ + needCheckValidatedValues: true, + }) || { isCheckValidated: false, values: {} } + modelNameAndTypeIsCheckValidated = formResult.isCheckValidated + modelNameAndTypeValues = formResult.values + } + + if (mode === ModelModalModeEnum.configModelCredential && model) { + modelNameAndTypeValues = { + __model_name: model.model, + __model_type: model.model_type, + } + } + + if (mode === ModelModalModeEnum.addCustomModelToModelList && selectedCredential?.addNewCredential && model) { + modelNameAndTypeValues = { + __model_name: model.model, + __model_type: model.model_type, + } + } const { isCheckValidated, values, - } = formRef.current?.getFormValues({ + } = formRef2.current?.getFormValues({ needCheckValidatedValues: true, needTransformWhenSecretFieldIsPristine: true, }) || { isCheckValidated: false, values: {} } - if (!isCheckValidated) + if (!isCheckValidated || !modelNameAndTypeIsCheckValidated) return const { - __authorization_name__, __model_name, __model_type, + } = modelNameAndTypeValues + const { + __authorization_name__, ...rest } = values - if (__model_name && __model_type) { - handleSaveCredential({ + if (__model_name && __model_type && __authorization_name__) { + await handleSaveCredential({ credential_id: credential?.credential_id, credentials: rest, name: __authorization_name__, @@ -123,41 +177,33 @@ const ModelModal: FC = ({ }) } else { - handleSaveCredential({ + await handleSaveCredential({ credential_id: credential?.credential_id, credentials: rest, name: __authorization_name__, }) } - }, [handleSaveCredential, credential?.credential_id, model]) + onSave(values) + }, [handleSaveCredential, credential?.credential_id, model, onSave, mode, selectedCredential, handleActiveCredential]) const modalTitle = useMemo(() => { - if (!providerFormSchemaPredefined && !model) { - return ( -
- -
-
{t('common.modelProvider.auth.apiKeyModal.addModel')}
-
{renderI18nObject(provider.label)}
-
-
- ) - } let label = t('common.modelProvider.auth.apiKeyModal.title') - if (model) - label = t('common.modelProvider.auth.addModelCredential') + if (mode === ModelModalModeEnum.configCustomModel || mode === ModelModalModeEnum.addCustomModelToModelList) + label = t('common.modelProvider.auth.addModel') + if (mode === ModelModalModeEnum.configModelCredential) { + if (credential) + label = t('common.modelProvider.auth.editModelCredential') + else + label = t('common.modelProvider.auth.addModelCredential') + } return (
{label}
) - }, [providerFormSchemaPredefined, t, model, renderI18nObject]) + }, [t, mode, credential]) const modalDesc = useMemo(() => { if (providerFormSchemaPredefined) { @@ -172,7 +218,18 @@ const ModelModal: FC = ({ }, [providerFormSchemaPredefined, t]) const modalModel = useMemo(() => { - if (model) { + if (mode === ModelModalModeEnum.configCustomModel) { + return ( +
+ +
{renderI18nObject(provider.label)}
+
+ ) + } + if (model && (mode === ModelModalModeEnum.configModelCredential || mode === ModelModalModeEnum.addCustomModelToModelList)) { return (
= ({ } return null - }, [model, provider]) + }, [model, provider, mode, renderI18nObject]) + + const showCredentialLabel = useMemo(() => { + if (mode === ModelModalModeEnum.configCustomModel) + return true + if (mode === ModelModalModeEnum.addCustomModelToModelList) + return selectedCredential?.addNewCredential + }, [mode, selectedCredential]) + const showCredentialForm = useMemo(() => { + if (mode !== ModelModalModeEnum.addCustomModelToModelList) + return true + return selectedCredential?.addNewCredential + }, [mode, selectedCredential]) + const saveButtonText = useMemo(() => { + if (mode === ModelModalModeEnum.addCustomModelToModelList || mode === ModelModalModeEnum.configCustomModel) + return t('common.operation.add') + return t('common.operation.save') + }, [mode, t]) + + const handleDeleteCredential = useCallback(() => { + handleConfirmDelete() + onCancel() + }, [handleConfirmDelete]) + + const handleModelNameAndTypeChange = useCallback((field: string, value: any) => { + const { + getForm, + } = formRef2.current as FormRefObject || {} + if (getForm()) + getForm()?.setFieldValue(field, value) + }, []) + const notAllowCustomCredential = provider.allow_custom_token === false useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { @@ -214,100 +302,132 @@ const ModelModal: FC = ({ >
-
-
- {modalTitle} - {modalDesc} - {modalModel} -
- -
- { - isLoading && ( -
- -
- ) - } - { - !isLoading && ( - { - return { - ...formSchema, - name: formSchema.variable, - showRadioUI: formSchema.type === FormTypeEnum.radio, - } - }) as FormSchema[]} - defaultValues={formValues} - 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 && ( - - ) - } - - -
-
+
+ {modalTitle} + {modalDesc} + {modalModel}
-
-
- - {t('common.modelProvider.encrypted.front')} - + { + mode === ModelModalModeEnum.configCustomModel && ( + { + return { + ...formSchema, + name: formSchema.variable, + } + }) as FormSchema[]} + defaultValues={modelNameAndTypeFormValues} + inputClassName='justify-start' + ref={formRef1} + onChange={handleModelNameAndTypeChange} + /> + ) + } + { + mode === ModelModalModeEnum.addCustomModelToModelList && ( + + ) + } + { + showCredentialLabel && ( +
+ {t('common.modelProvider.auth.modelCredential')} +
+
+ ) + } + { + isLoading && ( +
+ +
+ ) + } + { + !isLoading + && showCredentialForm + && ( + { + return { + ...formSchema, + name: formSchema.variable, + showRadioUI: formSchema.type === FormTypeEnum.radio, + } + }) as FormSchema[]} + defaultValues={formValues} + inputClassName='justify-start' + ref={formRef2} + /> + ) + } +
+
+ { + (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 && ( + + ) + } + +
+ { + (mode === ModelModalModeEnum.configCustomModel || mode === ModelModalModeEnum.configProviderCredential) && ( +
+
+ + {t('common.modelProvider.encrypted.front')} + + PKCS1_OAEP + + {t('common.modelProvider.encrypted.back')} +
+
+ ) + }
{ deleteCredentialId && ( @@ -316,7 +436,7 @@ const ModelModal: FC = ({ title={t('common.modelProvider.confirmDelete')} isDisabled={doingAction} onCancel={closeConfirmDelete} - onConfirm={handleConfirmDelete} + onConfirm={handleDeleteCredential} /> ) } 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 e67da7783..fda6abb2f 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 @@ -111,7 +111,6 @@ const CredentialPanel = ({
{ systemConfig.enabled && isCustomConfigured && ( 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 559f630b4..d3601d04f 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 @@ -25,7 +25,10 @@ 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' +import { + AddCustomModel, + ManageCustomModelCredentials, +} 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 = { @@ -155,10 +158,17 @@ const ProviderAddedCard: FC = ({ )} { configurationMethods.includes(ConfigurationMethodEnum.customizableModel) && isCurrentWorkspaceManager && ( - +
+ + +
) }
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 8d902043f..9e26d233c 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 @@ -16,7 +16,10 @@ import { import ModelListItem from './model-list-item' import { useModalContextSelector } from '@/context/modal-context' import { useAppContext } from '@/context/app-context' -import { AddCustomModel } from '@/app/components/header/account-setting/model-provider-page/model-auth' +import { + AddCustomModel, + ManageCustomModelCredentials, +} from '@/app/components/header/account-setting/model-provider-page/model-auth' type ModelListProps = { provider: ModelProvider @@ -67,6 +70,10 @@ const ModelList: FC = ({ { isConfigurable && isCurrentWorkspaceManager && (
+ void + onUpdate?: (payload?: any, formValues?: Record) => void + onRemove?: (credentialId: string) => void model: CustomModelCredential } @@ -55,11 +54,11 @@ const ModelLoadBalancingConfigs = ({ className, modelCredential, onUpdate, + onRemove, }: ModelLoadBalancingConfigsProps) => { const { t } = useTranslation() const providerFormSchemaPredefined = configurationMethod === ConfigurationMethodEnum.predefinedModel const modelLoadBalancingEnabled = useProviderContextSelector(state => state.modelLoadBalancingEnabled) - const handleOpenModal = useModelModalHandler() const updateConfigEntry = useCallback( ( @@ -130,6 +129,17 @@ const ModelLoadBalancingConfigs = ({ return draftConfig.configs }, [draftConfig]) + const handleUpdate = useCallback((payload?: any, formValues?: Record) => { + onUpdate?.(payload, formValues) + }, [onUpdate]) + + const handleRemove = useCallback((credentialId: string) => { + const index = draftConfig?.configs.findIndex(item => item.credential_id === credentialId && item.name !== '__inherit__') + if (index && index > -1) + updateConfigEntry(index, () => undefined) + onRemove?.(credentialId) + }, [draftConfig?.configs, updateConfigEntry, onRemove]) + if (!draftConfig) return null @@ -190,7 +200,7 @@ const ModelLoadBalancingConfigs = ({ )}
-
+
{isProviderManaged ? t('common.modelProvider.defaultConfig') : config.name}
{isProviderManaged && providerFormSchemaPredefined && ( @@ -206,34 +216,14 @@ const ModelLoadBalancingConfigs = ({ {!isProviderManaged && ( <>
- { - config.credential_id && !credential?.not_allowed_to_use && !credential?.from_enterprise && ( - { - handleOpenModal( - provider, - configurationMethod, - currentCustomConfigurationModelFixedFields, - configurationMethod === ConfigurationMethodEnum.customizableModel, - (config.credential_id && config.name) ? { - credential_id: config.credential_id, - credential_name: config.name, - } : undefined, - model, - ) - }} - > - - - ) - } - updateConfigEntry(index, () => undefined)} - > - - + + updateConfigEntry(index, () => undefined)} + > + + +
)} @@ -261,7 +251,8 @@ const ModelLoadBalancingConfigs = ({ configurationMethod={configurationMethod} modelCredential={modelCredential} onSelectCredential={addConfigEntry} - onUpdate={onUpdate} + onUpdate={handleUpdate} + onRemove={handleRemove} />
)} 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 cbd19c7ca..070c2ee90 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 @@ -2,6 +2,7 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import type { Credential, + CustomConfigurationModelFixedFields, ModelItem, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, @@ -24,10 +25,14 @@ import { useGetModelCredential, useUpdateModelLoadBalancingConfig, } from '@/service/use-models' +import { useAuth } from '../model-auth/hooks/use-auth' +import Confirm from '@/app/components/base/confirm' +import { useRefreshModel } from '../hooks' export type ModelLoadBalancingModalProps = { provider: ModelProvider configurateMethod: ConfigurationMethodEnum + currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields model: ModelItem credential?: Credential open?: boolean @@ -39,6 +44,7 @@ export type ModelLoadBalancingModalProps = { const ModelLoadBalancingModal = ({ provider, configurateMethod, + currentCustomConfigurationModelFixedFields, model, credential, open = false, @@ -47,7 +53,20 @@ const ModelLoadBalancingModal = ({ }: ModelLoadBalancingModalProps) => { const { t } = useTranslation() const { notify } = useToastContext() - + const { + doingAction, + deleteModel, + openConfirmDelete, + closeConfirmDelete, + handleConfirmDelete, + } = useAuth( + provider, + configurateMethod, + currentCustomConfigurationModelFixedFields, + { + isModelCredential: true, + }, + ) const [loading, setLoading] = useState(false) const providerFormSchemaPredefined = configurateMethod === ConfigurationMethodEnum.predefinedModel const configFrom = providerFormSchemaPredefined ? 'predefined-model' : 'custom-model' @@ -121,6 +140,7 @@ const ModelLoadBalancingModal = ({ } }, [current_credential_id, current_credential_name]) const [customModelCredential, setCustomModelCredential] = useState(initialCustomModelCredential) + const { handleRefreshModel } = useRefreshModel() const handleSave = async () => { try { setLoading(true) @@ -139,6 +159,7 @@ const ModelLoadBalancingModal = ({ ) if (res.result === 'success') { notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) + handleRefreshModel(provider, configurateMethod, currentCustomConfigurationModelFixedFields) onSave?.(provider.provider) onClose?.() } @@ -147,120 +168,208 @@ const ModelLoadBalancingModal = ({ setLoading(false) } } + const handleDeleteModel = useCallback(async () => { + await handleConfirmDelete() + onClose?.() + }, [handleConfirmDelete, onClose]) + + const handleUpdate = useCallback(async (payload?: any, formValues?: Record) => { + const result = await refetch() + const available_credentials = result.data?.available_credentials || [] + const credentialName = formValues?.__authorization_name__ + const modelCredential = payload?.credential + + if (!available_credentials.length) { + onClose?.() + return + } + + if (!modelCredential) { + const currentCredential = available_credentials.find(c => c.credential_name === credentialName) + if (currentCredential) { + setDraftConfig((prev: any) => { + if (!prev) + return prev + return { + ...prev, + configs: [...prev.configs, { + credential_id: currentCredential.credential_id, + enabled: true, + name: currentCredential.credential_name, + }], + } + }) + } + } + else { + setDraftConfig((prev) => { + if (!prev) + return prev + const newConfigs = [...prev.configs] + const prevIndex = newConfigs.findIndex(item => item.credential_id === modelCredential.credential_id && item.name !== '__inherit__') + const newIndex = available_credentials.findIndex(c => c.credential_id === modelCredential.credential_id) + + if (newIndex > -1 && prevIndex > -1) + newConfigs[prevIndex].name = available_credentials[newIndex].credential_name || '' + + return { + ...prev, + configs: newConfigs, + } + }) + } + }, [refetch, credential]) + + const handleUpdateWhenSwitchCredential = useCallback(async () => { + const result = await refetch() + const available_credentials = result.data?.available_credentials || [] + if (!available_credentials.length) + onClose?.() + }, [refetch, onClose]) return ( - -
{ - draftConfig?.enabled - ? t('common.modelProvider.auth.configLoadBalancing') - : t('common.modelProvider.auth.configModel') - }
- {Boolean(model) && ( -
- - -
- )} -
- } - > - {!draftConfig - ? - : ( - <> -
-
toggleModalBalancing(false) : undefined} - > -
-
- {Boolean(model) && ( - - )} -
-
-
{ - providerFormSchemaPredefined - ? t('common.modelProvider.auth.providerManaged') - : t('common.modelProvider.auth.specifyModelCredential') - }
-
{ - providerFormSchemaPredefined - ? t('common.modelProvider.auth.providerManagedTip') - : t('common.modelProvider.auth.specifyModelCredentialTip') - }
+ <> + +
{ + draftConfig?.enabled + ? t('common.modelProvider.auth.configLoadBalancing') + : t('common.modelProvider.auth.configModel') + }
+ {Boolean(model) && ( +
+ + +
+ )} +
+ } + > + {!draftConfig + ? + : ( + <> +
+
toggleModalBalancing(false) : undefined} + > +
+
+ {Boolean(model) && ( + + )} +
+
+
{ + providerFormSchemaPredefined + ? t('common.modelProvider.auth.providerManaged') + : t('common.modelProvider.auth.specifyModelCredential') + }
+
{ + providerFormSchemaPredefined + ? t('common.modelProvider.auth.providerManagedTip') + : t('common.modelProvider.auth.specifyModelCredentialTip') + }
+
+ { + !providerFormSchemaPredefined && ( + + ) + }
+
+ { + modelCredential && ( + + ) + } +
+ +
+
{ !providerFormSchemaPredefined && ( - + ) }
+
+ + +
- { - modelCredential && ( - - ) - } -
- -
- - -
- + + ) + } + + { + deleteModel && ( + ) } - + ) } diff --git a/web/app/components/header/account-setting/model-provider-page/utils.ts b/web/app/components/header/account-setting/model-provider-page/utils.ts index f577a536d..f19999cc1 100644 --- a/web/app/components/header/account-setting/model-provider-page/utils.ts +++ b/web/app/components/header/account-setting/model-provider-page/utils.ts @@ -161,7 +161,7 @@ export const modelTypeFormat = (modelType: ModelTypeEnum) => { export const genModelTypeFormSchema = (modelTypes: ModelTypeEnum[]) => { return { - type: FormTypeEnum.radio, + type: FormTypeEnum.select, label: { zh_Hans: '模型类型', en_US: 'Model Type', diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx index dac9ef30d..5baadc934 100644 --- a/web/context/modal-context.tsx +++ b/web/context/modal-context.tsx @@ -9,7 +9,6 @@ import type { Credential, CustomConfigurationModelFixedFields, CustomModel, - ModelLoadBalancingConfigEntry, ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' import { @@ -29,6 +28,7 @@ import { removeSpecificQueryParam } from '@/utils' import { noop } from 'lodash-es' import dynamic from 'next/dynamic' import type { ExpireNoticeModalPayloadProps } from '@/app/education-apply/expire-notice-modal' +import type { ModelModalModeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' const AccountSetting = dynamic(() => import('@/app/components/header/account-setting'), { ssr: false, @@ -71,8 +71,8 @@ const ExpireNoticeModal = dynamic(() => import('@/app/education-apply/expire-not export type ModalState = { payload: T onCancelCallback?: () => void - onSaveCallback?: (newPayload: T) => void - onRemoveCallback?: (newPayload: T) => void + onSaveCallback?: (newPayload?: T, formValues?: Record) => void + onRemoveCallback?: (newPayload?: T, formValues?: Record) => void onEditCallback?: (newPayload: T) => void onValidateBeforeSaveCallback?: (newPayload: T) => boolean isEditMode?: boolean @@ -86,10 +86,7 @@ export type ModelModalType = { isModelCredential?: boolean credential?: Credential model?: CustomModel -} -export type LoadBalancingEntryModalType = ModelModalType & { - entry?: ModelLoadBalancingConfigEntry - index?: number + mode?: ModelModalModeEnum } export type ModalContextState = { @@ -187,9 +184,15 @@ export const ModalContextProvider = ({ showModelModal.onCancelCallback() }, [showModelModal]) - const handleSaveModelModal = useCallback(() => { + const handleSaveModelModal = useCallback((formValues?: Record) => { if (showModelModal?.onSaveCallback) - showModelModal.onSaveCallback(showModelModal.payload) + showModelModal.onSaveCallback(showModelModal.payload, formValues) + setShowModelModal(null) + }, [showModelModal]) + + const handleRemoveModelModal = useCallback((formValues?: Record) => { + if (showModelModal?.onRemoveCallback) + showModelModal.onRemoveCallback(showModelModal.payload, formValues) setShowModelModal(null) }, [showModelModal]) @@ -329,8 +332,10 @@ export const ModalContextProvider = ({ isModelCredential={showModelModal.payload.isModelCredential} credential={showModelModal.payload.credential} model={showModelModal.payload.model} + mode={showModelModal.payload.mode} onCancel={handleCancelModelModal} onSave={handleSaveModelModal} + onRemove={handleRemoveModelModal} /> ) } diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 2f0082edd..a54f6a4e4 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -498,10 +498,13 @@ const translation = { authRemoved: 'Auth removed', apiKeys: 'API Keys', addApiKey: 'Add API Key', + addModel: 'Add model', addNewModel: 'Add new model', addCredential: 'Add credential', addModelCredential: 'Add model credential', + editModelCredential: 'Edit model credential', modelCredentials: 'Model credentials', + modelCredential: 'Model credential', configModel: 'Config model', configLoadBalancing: 'Config Load Balancing', authorizationError: 'Authorization error', @@ -514,6 +517,12 @@ const translation = { desc: 'After configuring credentials, all members within the workspace can use this model when orchestrating applications.', addModel: 'Add model', }, + manageCredentials: 'Manage Credentials', + customModelCredentials: 'Custom Model Credentials', + addNewModelCredential: 'Add new model credential', + removeModel: 'Remove Model', + selectModelCredential: 'Select a model credential', + customModelCredentialsDeleteTip: 'Credential is in use and cannot be deleted', }, }, dataSource: { diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 5d9f01834..a83487e43 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -492,10 +492,13 @@ const translation = { authRemoved: '授权已移除', apiKeys: 'API 密钥', addApiKey: '添加 API 密钥', + addModel: '添加模型', addNewModel: '添加新模型', addCredential: '添加凭据', addModelCredential: '添加模型凭据', + editModelCredential: '编辑模型凭据', modelCredentials: '模型凭据', + modelCredential: '模型凭据', configModel: '配置模型', configLoadBalancing: '配置负载均衡', authorizationError: '授权错误', @@ -508,6 +511,12 @@ const translation = { desc: '配置凭据后,工作空间中的所有成员都可以在编排应用时使用此模型。', addModel: '添加模型', }, + manageCredentials: '管理凭据', + customModelCredentials: '自定义模型凭据', + addNewModelCredential: '添加模型新凭据', + removeModel: '移除模型', + selectModelCredential: '选择模型凭据', + customModelCredentialsDeleteTip: '模型凭据正在使用中,无法删除', }, }, dataSource: { diff --git a/web/service/use-models.ts b/web/service/use-models.ts index f3336dd03..d6eb92964 100644 --- a/web/service/use-models.ts +++ b/web/service/use-models.ts @@ -122,7 +122,7 @@ export const useDeleteModel = (provider: string) => { mutationFn: (data: { model: string model_type: ModelTypeEnum - }) => del<{ result: string }>(`/workspaces/current/model-providers/${provider}/models/credentials`, { + }) => del<{ result: string }>(`/workspaces/current/model-providers/${provider}/models`, { body: data, }), })