From c1c7a43191a1659b332e121075f090343663c85a Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Thu, 24 Jul 2025 16:53:35 +0800 Subject: [PATCH 01/10] add model auth --- .../add-credential-in-load-balancing.tsx | 39 ++++++++++ .../model-auth/add-model.tsx | 76 +++++++++++++++++++ .../model-provider-page/model-auth/index.tsx | 0 .../switch-credential-in-load-balancing.tsx | 47 ++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/add-credential-in-load-balancing.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/add-model.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/index.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx 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 new file mode 100644 index 0000000000..b913f7284d --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/add-credential-in-load-balancing.tsx @@ -0,0 +1,39 @@ +import { + memo, + useCallback, +} from 'react' +import { RiAddLine } from '@remixicon/react' +import { + AuthCategory, + Authorized, +} from '@/app/components/plugins/plugin-auth' +import cn from '@/utils/classnames' + +const AddCredentialInLoadBalancing = () => { + const renderTrigger = useCallback((open?: boolean) => { + return ( +
+ + Add credential +
+ ) + }, []) + + return ( + + ) +} + +export default memo(AddCredentialInLoadBalancing) diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/add-model.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/add-model.tsx new file mode 100644 index 0000000000..fcd4272a27 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/add-model.tsx @@ -0,0 +1,76 @@ +import { + memo, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { + RiAddCircleFill, + RiAddLine, +} from '@remixicon/react' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import Button from '@/app/components/base/button' +import Tooltip from '@/app/components/base/tooltip' + +const AddModel = () => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + + return ( + + + + + +
+
+
+
+
+ chat-finetune-01 +
+ + + +
+
+
+ + {t('common.modelProvider.addModel')} +
+
+
+
+ ) +} + +export default memo(AddModel) 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 new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..bb5b8bd62e --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx @@ -0,0 +1,47 @@ +import { + memo, + useCallback, +} from 'react' +import { RiArrowDownSLine } from '@remixicon/react' +import Button from '@/app/components/base/button' +import { + AuthCategory, + Authorized, +} from '@/app/components/plugins/plugin-auth' +import Indicator from '@/app/components/header/indicator' +import Badge from '@/app/components/base/badge' + +const SwitchCredentialInLoadBalancing = () => { + const renderTrigger = useCallback(() => { + return ( + + ) + }, []) + + return ( + + ) +} + +export default memo(SwitchCredentialInLoadBalancing) From b73487dd673f1bd16c8f44f3488910b7375ea1a0 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 29 Jul 2025 15:17:49 +0800 Subject: [PATCH 02/10] model modal --- .../base/form/components/base/base-field.tsx | 39 +++- .../base/form/components/base/base-form.tsx | 13 +- .../base/form/form-scenarios/auth/index.tsx | 2 + web/app/components/base/form/types.ts | 1 + web/app/components/base/radio/ui.tsx | 10 +- .../model-provider-page/model-modal/index.tsx | 175 +++++------------- 6 files changed, 109 insertions(+), 131 deletions(-) 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 0195f38795..54834e6623 100644 --- a/web/app/components/base/form/components/base/base-field.tsx +++ b/web/app/components/base/form/components/base/base-field.tsx @@ -11,6 +11,7 @@ import PureSelect from '@/app/components/base/select/pure' import type { FormSchema } from '@/app/components/base/form/types' import { FormTypeEnum } from '@/app/components/base/form/types' import { useRenderI18nObject } from '@/hooks/use-i18n' +import RadioE from '@/app/components/base/radio/ui' export type BaseFieldProps = { fieldClassName?: string @@ -57,14 +58,33 @@ const BaseField = ({ if (typeof placeholder === 'object' && placeholder !== null) return renderI18nObject(placeholder as Record) }, [placeholder, renderI18nObject]) + const optionValues = useStore(field.form.store, (s) => { + const result: Record = {} + options?.forEach((option) => { + if (option.show_on?.length) { + option.show_on.forEach((condition) => { + result[condition.variable] = s.values[condition.variable] + }) + } + }) + return result + }) const memorizedOptions = useMemo(() => { - return options?.map((option) => { + return options?.filter((option) => { + if (!option.show_on || option.show_on.length === 0) + return true + + return option.show_on.every((condition) => { + const conditionValue = optionValues[condition.variable] + return conditionValue === condition.value + }) + }).map((option) => { return { label: typeof option.label === 'string' ? option.label : renderI18nObject(option.label), value: option.value, } }) || [] - }, [options, renderI18nObject]) + }, [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) => { @@ -151,17 +171,28 @@ const BaseField = ({ } { formSchema.type === FormTypeEnum.radio && ( -
+
{ memorizedOptions.map(option => (
field.handleChange(option.value)} > + { + formSchema.showRadioUI && ( + + ) + } {option.label}
)) 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 640d474b19..c056829db4 100644 --- a/web/app/components/base/form/components/base/base-form.tsx +++ b/web/app/components/base/form/components/base/base-form.tsx @@ -2,6 +2,7 @@ import { memo, useCallback, useImperativeHandle, + useMemo, } from 'react' import type { AnyFieldApi, @@ -45,8 +46,18 @@ const BaseForm = ({ disabled, formFromProps, }: BaseFormProps) => { + const initialDefaultValues = useMemo(() => { + if (defaultValues) + return defaultValues + + return formSchemas.reduce((acc, schema) => { + if (schema.default) + acc[schema.name] = schema.default + return acc + }, {} as Record) + }, [defaultValues]) const formFromHook = useForm({ - defaultValues, + defaultValues: initialDefaultValues, }) const form: any = formFromProps || formFromHook const { getFormValues } = useGetFormValues(form, formSchemas) diff --git a/web/app/components/base/form/form-scenarios/auth/index.tsx b/web/app/components/base/form/form-scenarios/auth/index.tsx index 3927f90959..f499e43f16 100644 --- a/web/app/components/base/form/form-scenarios/auth/index.tsx +++ b/web/app/components/base/form/form-scenarios/auth/index.tsx @@ -7,6 +7,7 @@ const AuthForm = ({ defaultValues, ref, formFromProps, + ...rest }: BaseFormProps) => { return ( ) } diff --git a/web/app/components/base/form/types.ts b/web/app/components/base/form/types.ts index c165d2939b..9b3beeee7f 100644 --- a/web/app/components/base/form/types.ts +++ b/web/app/components/base/form/types.ts @@ -58,6 +58,7 @@ export type FormSchema = { options?: FormOption[] labelClassName?: string validators?: AnyValidators + showRadioUI?: boolean } export type FormValues = Record diff --git a/web/app/components/base/radio/ui.tsx b/web/app/components/base/radio/ui.tsx index 178262d0b9..ea132c7d4b 100644 --- a/web/app/components/base/radio/ui.tsx +++ b/web/app/components/base/radio/ui.tsx @@ -5,13 +5,21 @@ import cn from '@/utils/classnames' type Props = { isChecked: boolean + className?: string } const RadioUI: FC = ({ isChecked, + className, }) => { 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 bc98081dfa..88ada5673c 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 @@ -4,18 +4,12 @@ import { useCallback, useEffect, useMemo, + useRef, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - RiErrorWarningFill, -} from '@remixicon/react' import type { - CredentialFormSchema, - CredentialFormSchemaRadio, - CredentialFormSchemaSelect, CustomConfigurationModelFixedFields, - FormValue, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, ModelProvider, @@ -35,10 +29,7 @@ import { useLanguage, useProviderCredentialsAndLoadBalancing, } from '../hooks' -import { useValidate } from '../../key-validator/hooks' -import { ValidatedStatus } from '../../key-validator/declarations' import ModelLoadBalancingConfigs from '../provider-added-card/model-load-balancing-configs' -import Form from './Form' 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' @@ -49,6 +40,11 @@ import { import { useToastContext } from '@/app/components/base/toast' import Confirm from '@/app/components/base/confirm' import { useAppContext } from '@/context/app-context' +import AuthForm from '@/app/components/base/form/form-scenarios/auth' +import type { + FormRefObject, + FormSchema, +} from '@/app/components/base/form/types' type ModelModalProps = { provider: ModelProvider @@ -115,77 +111,7 @@ const ModelModal: FC = ({ provider.model_credential_schema?.model, draftConfig?.enabled, ]) - const [ - requiredFormSchemas, - defaultFormSchemaValue, - showOnVariableMap, - ] = useMemo(() => { - const requiredFormSchemas: CredentialFormSchema[] = [] - const defaultFormSchemaValue: Record = {} - const showOnVariableMap: Record = {} - - formSchemas.forEach((formSchema) => { - if (formSchema.required) - requiredFormSchemas.push(formSchema) - - if (formSchema.default) - defaultFormSchemaValue[formSchema.variable] = formSchema.default - - if (formSchema.show_on.length) { - formSchema.show_on.forEach((showOnItem) => { - if (!showOnVariableMap[showOnItem.variable]) - showOnVariableMap[showOnItem.variable] = [] - - if (!showOnVariableMap[showOnItem.variable].includes(formSchema.variable)) - showOnVariableMap[showOnItem.variable].push(formSchema.variable) - }) - } - - if (formSchema.type === FormTypeEnum.select || formSchema.type === FormTypeEnum.radio) { - (formSchema as (CredentialFormSchemaRadio | CredentialFormSchemaSelect)).options.forEach((option) => { - if (option.show_on.length) { - option.show_on.forEach((showOnItem) => { - if (!showOnVariableMap[showOnItem.variable]) - showOnVariableMap[showOnItem.variable] = [] - - if (!showOnVariableMap[showOnItem.variable].includes(formSchema.variable)) - showOnVariableMap[showOnItem.variable].push(formSchema.variable) - }) - } - }) - } - }) - - return [ - requiredFormSchemas, - defaultFormSchemaValue, - showOnVariableMap, - ] - }, [formSchemas]) - const initialFormSchemasValue: Record = useMemo(() => { - return { - ...defaultFormSchemaValue, - ...formSchemasValue, - } as unknown as Record - }, [formSchemasValue, defaultFormSchemaValue]) - const [value, setValue] = useState(initialFormSchemasValue) - useEffect(() => { - setValue(initialFormSchemasValue) - }, [initialFormSchemasValue]) - const [_, validating, validatedStatusState] = useValidate(value) - const filteredRequiredFormSchemas = requiredFormSchemas.filter((requiredFormSchema) => { - if (requiredFormSchema.show_on.length && requiredFormSchema.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)) - return true - - if (!requiredFormSchema.show_on.length) - return true - - return false - }) - - const handleValueChange = (v: FormValue) => { - setValue(v) - } + const formRef = useRef(null) const extendedSecretFormSchemas = useMemo( () => @@ -205,15 +131,6 @@ const ModelModal: FC = ({ ], ) - const encodeSecretValues = useCallback((v: FormValue) => { - const result = { ...v } - extendedSecretFormSchemas.forEach(({ variable }) => { - if (result[variable] === formSchemasValue?.[variable] && result[variable] !== undefined) - result[variable] = '[__HIDDEN__]' - }) - return result - }, [extendedSecretFormSchemas, formSchemasValue]) - const encodeConfigEntrySecretValues = useCallback((entry: ModelLoadBalancingConfigEntry) => { const result = { ...entry } extendedSecretFormSchemas.forEach(({ variable }) => { @@ -226,10 +143,19 @@ const ModelModal: FC = ({ 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 saveCredentials( providerFormSchemaPredefined, provider.provider, - encodeSecretValues(value), + values, { ...draftConfig, enabled: Boolean(draftConfig?.enabled), @@ -251,11 +177,19 @@ const ModelModal: FC = ({ const handleRemove = async () => { try { setLoading(true) - + const { + isCheckValidated, + values, + } = formRef.current?.getFormValues({ + needCheckValidatedValues: true, + needTransformWhenSecretFieldIsPristine: true, + }) || { isCheckValidated: false, values: {} } + if (!isCheckValidated) + return const res = await removeCredentials( providerFormSchemaPredefined, provider.provider, - value, + values, ) if (res.result === 'success') { notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) @@ -285,14 +219,17 @@ const ModelModal: FC = ({
-
{ + return { + ...formSchema, + name: formSchema.variable, + showRadioUI: formSchema.type === FormTypeEnum.radio, + } + }) as FormSchema[]} + defaultValues={formSchemasValue} + inputClassName='justify-start' + ref={formRef} />
= ({ onClick={handleSave} disabled={ loading - || filteredRequiredFormSchemas.some(item => value[item.variable] === undefined) || (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2) } @@ -357,29 +293,18 @@ const ModelModal: FC = ({
- { - (validatedStatusState.status === ValidatedStatus.Error && validatedStatusState.message) - ? ( -
- - {validatedStatusState.message} -
- ) - : ( -
- - {t('common.modelProvider.encrypted.front')} - - PKCS1_OAEP - - {t('common.modelProvider.encrypted.back')} -
- ) - } +
+ + {t('common.modelProvider.encrypted.front')} + + PKCS1_OAEP + + {t('common.modelProvider.encrypted.back')} +
{ From 8e1ea671bd65061423a0d2b911f9015fb42d8e3b Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Wed, 30 Jul 2025 13:50:01 +0800 Subject: [PATCH 03/10] load balancing modal --- .../model-load-balancing-entry-modal.tsx | 170 +++++------------- 1 file changed, 43 insertions(+), 127 deletions(-) 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 index d6285a784b..38bde219ab 100644 --- 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 @@ -1,22 +1,14 @@ import type { FC } from 'react' import { memo, - useCallback, useEffect, useMemo, + useRef, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - RiErrorWarningFill, -} from '@remixicon/react' import type { - CredentialFormSchema, - CredentialFormSchemaRadio, - CredentialFormSchemaSelect, - CredentialFormSchemaTextInput, CustomConfigurationModelFixedFields, - FormValue, ModelLoadBalancingConfigEntry, ModelProvider, } from '../declarations' @@ -28,10 +20,8 @@ import { import { useLanguage, } from '../hooks' -import { useValidate } from '../../key-validator/hooks' import { ValidatedStatus } from '../../key-validator/declarations' import { validateLoadBalancingCredentials } from '../utils' -import Form from './Form' 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' @@ -41,6 +31,11 @@ import { } 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 @@ -62,12 +57,6 @@ const ModelLoadBalancingEntryModal: FC = ({ onRemove, }) => { const providerFormSchemaPredefined = configurationMethod === ConfigurationMethodEnum.predefinedModel - // const { credentials: formSchemasValue } = useProviderCredentialsAndLoadBalancing( - // provider.provider, - // configurationMethod, - // providerFormSchemaPredefined && provider.custom_configuration.status === CustomConfigurationStatusEnum.active, - // currentCustomConfigurationModelFixedFields, - // ) const isEditMode = !!entry const { t } = useTranslation() const { notify } = useToastContext() @@ -89,7 +78,7 @@ const ModelLoadBalancingEntryModal: FC = ({ en_US: 'Enter your Config Name here', zh_Hans: '输入配置名称', }, - } as CredentialFormSchemaTextInput, + } as any, ...( providerFormSchemaPredefined ? provider.provider_credential_schema.credential_form_schemas @@ -101,58 +90,20 @@ const ModelLoadBalancingEntryModal: FC = ({ provider.provider_credential_schema?.credential_form_schemas, provider.model_credential_schema?.credential_form_schemas, ]) + const formRef = useRef(null) const [ - requiredFormSchemas, - secretFormSchemas, defaultFormSchemaValue, - showOnVariableMap, ] = useMemo(() => { - const requiredFormSchemas: CredentialFormSchema[] = [] - const secretFormSchemas: CredentialFormSchema[] = [] const defaultFormSchemaValue: Record = {} - const showOnVariableMap: Record = {} formSchemas.forEach((formSchema) => { - if (formSchema.required) - requiredFormSchemas.push(formSchema) - - if (formSchema.type === FormTypeEnum.secretInput) - secretFormSchemas.push(formSchema) - if (formSchema.default) defaultFormSchemaValue[formSchema.variable] = formSchema.default - - if (formSchema.show_on.length) { - formSchema.show_on.forEach((showOnItem) => { - if (!showOnVariableMap[showOnItem.variable]) - showOnVariableMap[showOnItem.variable] = [] - - if (!showOnVariableMap[showOnItem.variable].includes(formSchema.variable)) - showOnVariableMap[showOnItem.variable].push(formSchema.variable) - }) - } - - if (formSchema.type === FormTypeEnum.select || formSchema.type === FormTypeEnum.radio) { - (formSchema as (CredentialFormSchemaRadio | CredentialFormSchemaSelect)).options.forEach((option) => { - if (option.show_on.length) { - option.show_on.forEach((showOnItem) => { - if (!showOnVariableMap[showOnItem.variable]) - showOnVariableMap[showOnItem.variable] = [] - - if (!showOnVariableMap[showOnItem.variable].includes(formSchema.variable)) - showOnVariableMap[showOnItem.variable].push(formSchema.variable) - }) - } - }) - } }) return [ - requiredFormSchemas, - secretFormSchemas, defaultFormSchemaValue, - showOnVariableMap, ] }, [formSchemas]) const [initialValue, setInitialValue] = useState() @@ -170,55 +121,28 @@ const ModelLoadBalancingEntryModal: FC = ({ ...currentCustomConfigurationModelFixedFields, ...initialValue, }), [currentCustomConfigurationModelFixedFields, initialValue]) - const initialFormSchemasValue: Record = useMemo(() => { - return { - ...defaultFormSchemaValue, - ...formSchemasValue, - } as Record - }, [formSchemasValue, defaultFormSchemaValue]) - const [value, setValue] = useState(initialFormSchemasValue) - useEffect(() => { - setValue(initialFormSchemasValue) - }, [initialFormSchemasValue]) - const [_, validating, validatedStatusState] = useValidate(value) - const filteredRequiredFormSchemas = requiredFormSchemas.filter((requiredFormSchema) => { - if (requiredFormSchema.show_on.length && requiredFormSchema.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)) - return true - if (!requiredFormSchema.show_on.length) - return true - - return false - }) - const getSecretValues = useCallback((v: FormValue) => { - return secretFormSchemas.reduce((prev, next) => { - if (isEditMode && v[next.variable] && v[next.variable] === initialFormSchemasValue[next.variable]) - prev[next.variable] = '[__HIDDEN__]' - - return prev - }, {} as Record) - }, [initialFormSchemasValue, isEditMode, secretFormSchemas]) - - // const handleValueChange = ({ __model_type, __model_name, ...v }: FormValue) => { - const handleValueChange = (v: FormValue) => { - setValue(v) - } 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, - { - ...value, - ...getSecretValues(value), - }, + values, entry?.id, ) if (res.status === ValidatedStatus.Success) { // notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - const { __model_type, __model_name, name, ...credentials } = value + const { __model_type, __model_name, name, ...credentials } = values onSave({ ...(entry || {}), name: name as string, @@ -248,14 +172,17 @@ const ModelLoadBalancingEntryModal: FC = ({
{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} />
{ @@ -296,7 +223,7 @@ const ModelLoadBalancingEntryModal: FC = ({ size='large' variant='primary' onClick={handleSave} - disabled={loading || filteredRequiredFormSchemas.some(item => value[item.variable] === undefined)} + disabled={loading} > {t('common.operation.save')} @@ -304,29 +231,18 @@ const ModelLoadBalancingEntryModal: FC = ({
- { - (validatedStatusState.status === ValidatedStatus.Error && validatedStatusState.message) - ? ( -
- - {validatedStatusState.message} -
- ) - : ( -
- - {t('common.modelProvider.encrypted.front')} - - PKCS1_OAEP - - {t('common.modelProvider.encrypted.back')} -
- ) - } +
+ + {t('common.modelProvider.encrypted.front')} + + PKCS1_OAEP + + {t('common.modelProvider.encrypted.back')} +
{ From d9ccd74f0b392c6b1daf32dca29b50276b643405 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Wed, 6 Aug 2025 11:39:06 +0800 Subject: [PATCH 04/10] fix --- .../model-provider-page/model-modal/index.tsx | 22 +-- .../hooks/use-plugin-auth-action.ts | 125 ++++++++++++++++++ 2 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 web/app/components/plugins/plugin-auth/hooks/use-plugin-auth-action.ts 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 88ada5673c..d23fd6a31e 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 @@ -231,14 +231,20 @@ const ModelModal: FC = ({ inputClassName='justify-start' ref={formRef} /> -
- + { + !!draftConfig && ( + <> +
+ + + ) + }
diff --git a/web/app/components/plugins/plugin-auth/hooks/use-plugin-auth-action.ts b/web/app/components/plugins/plugin-auth/hooks/use-plugin-auth-action.ts new file mode 100644 index 0000000000..fe4ab014ef --- /dev/null +++ b/web/app/components/plugins/plugin-auth/hooks/use-plugin-auth-action.ts @@ -0,0 +1,125 @@ +import { + useCallback, + useRef, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useToastContext } from '@/app/components/base/toast' +import type { PluginPayload } from '@/app/components/plugins/plugin-auth/types' +import { + useDeletePluginCredentialHook, + useSetPluginDefaultCredentialHook, + useUpdatePluginCredentialHook, +} from '../hooks/use-credential' + +export const usePluginAuthAction = ( + pluginPayload: PluginPayload, + onUpdate?: () => void, +) => { + const { t } = useTranslation() + const { notify } = useToastContext() + const pendingOperationCredentialId = useRef(null) + const [deleteCredentialId, setDeleteCredentialId] = useState(null) + const { mutateAsync: deletePluginCredential } = useDeletePluginCredentialHook(pluginPayload) + const openConfirm = useCallback((credentialId?: string) => { + if (credentialId) + pendingOperationCredentialId.current = credentialId + + setDeleteCredentialId(pendingOperationCredentialId.current) + }, []) + const closeConfirm = useCallback(() => { + setDeleteCredentialId(null) + pendingOperationCredentialId.current = null + }, []) + const [doingAction, setDoingAction] = useState(false) + const doingActionRef = useRef(doingAction) + const handleSetDoingAction = useCallback((doing: boolean) => { + doingActionRef.current = doing + setDoingAction(doing) + }, []) + const [editValues, setEditValues] = useState | null>(null) + const handleConfirm = useCallback(async () => { + if (doingActionRef.current) + return + if (!pendingOperationCredentialId.current) { + setDeleteCredentialId(null) + return + } + try { + handleSetDoingAction(true) + await deletePluginCredential({ credential_id: pendingOperationCredentialId.current }) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + onUpdate?.() + setDeleteCredentialId(null) + pendingOperationCredentialId.current = null + setEditValues(null) + } + finally { + handleSetDoingAction(false) + } + }, [deletePluginCredential, onUpdate, notify, t, handleSetDoingAction]) + const handleEdit = useCallback((id: string, values: Record) => { + pendingOperationCredentialId.current = id + setEditValues(values) + }, []) + const handleRemove = useCallback(() => { + setDeleteCredentialId(pendingOperationCredentialId.current) + }, []) + const { mutateAsync: setPluginDefaultCredential } = useSetPluginDefaultCredentialHook(pluginPayload) + const handleSetDefault = useCallback(async (id: string) => { + if (doingActionRef.current) + return + try { + handleSetDoingAction(true) + await setPluginDefaultCredential(id) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + onUpdate?.() + } + finally { + handleSetDoingAction(false) + } + }, [setPluginDefaultCredential, onUpdate, notify, t, handleSetDoingAction]) + const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload) + const handleRename = useCallback(async (payload: { + credential_id: string + name: string + }) => { + if (doingActionRef.current) + return + try { + handleSetDoingAction(true) + await updatePluginCredential(payload) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + onUpdate?.() + } + finally { + handleSetDoingAction(false) + } + }, [updatePluginCredential, notify, t, handleSetDoingAction, onUpdate]) + + return { + doingAction, + handleSetDoingAction, + openConfirm, + closeConfirm, + deleteCredentialId, + setDeleteCredentialId, + handleConfirm, + editValues, + setEditValues, + handleEdit, + handleRemove, + handleSetDefault, + handleRename, + pendingOperationCredentialId, + } +} From 415178fb0d89c1eabd00be44dab4744633cb5212 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Mon, 11 Aug 2025 10:37:14 +0800 Subject: [PATCH 05/10] add auth panel --- .../model-auth/auth-panel.tsx | 83 +++++++++++++++++++ web/i18n/en-US/common.ts | 1 + web/i18n/zh-Hans/common.ts | 1 + 3 files changed, 85 insertions(+) create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/auth-panel.tsx diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/auth-panel.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/auth-panel.tsx new file mode 100644 index 0000000000..ac0cd5dcfd --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/auth-panel.tsx @@ -0,0 +1,83 @@ +import type { FC } from 'react' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { + RiEqualizer2Line, + RiMoreFill, +} from '@remixicon/react' +import type { ModelProvider } from '../declarations' +import { + CustomConfigurationStatusEnum, +} from '../declarations' +import Indicator from '@/app/components/header/indicator' +import Button from '@/app/components/base/button' +import { + AuthCategory, + Authorized, +} from '@/app/components/plugins/plugin-auth' + +type AuthPanelProps = { + provider: ModelProvider + onSetup: () => void +} +const AuthPanel: FC = ({ + provider, + onSetup, +}) => { + const authorized = false + const { t } = useTranslation() + const customConfig = provider.custom_configuration + const isCustomConfigured = customConfig.status === CustomConfigurationStatusEnum.active + + const renderTrigger = useCallback(() => { + return ( + + ) + }, []) + + return ( + <> + { + provider.provider_credential_schema && ( +
+
+ API-KEY + +
+
+ + { + authorized && ( + + ) + } +
+
+ ) + } + + ) +} + +export default AuthPanel diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index d9b17dcbad..0eef43296f 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -66,6 +66,7 @@ const translation = { more: 'More', selectAll: 'Select All', deSelectAll: 'Deselect All', + config: 'Config', }, errorMsg: { fieldRequired: '{{field}} is required', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index e51b84c37e..cd3632e44f 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -66,6 +66,7 @@ const translation = { more: '更多', selectAll: '全选', deSelectAll: '取消全选', + config: '配置', }, errorMsg: { fieldRequired: '{{field}} 为必填项', From 61be6f5d2c110857d26c22bcb44689baf559d01f Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Wed, 13 Aug 2025 17:54:39 +0800 Subject: [PATCH 06/10] model auth --- .../base/form/hooks/use-get-validators.ts | 28 ++- .../model-provider-page/declarations.ts | 8 + .../model-provider-page/hooks.ts | 62 ++++-- .../model-provider-page/index.tsx | 5 +- .../model-auth/auth-panel.tsx | 83 ------- .../model-auth/authorized/index.tsx | 203 ++++++++++++++++++ .../model-auth/authorized/item.tsx | 118 ++++++++++ .../model-provider-page/model-auth/hooks.ts | 56 +++++ .../model-provider-page/model-modal/index.tsx | 29 ++- .../provider-added-card/credential-panel.tsx | 78 +++++-- .../provider-added-card/index.tsx | 9 +- .../model-provider-page/utils.ts | 15 +- web/context/modal-context.tsx | 3 + web/service/use-models.ts | 30 ++- 14 files changed, 574 insertions(+), 153 deletions(-) delete mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/auth-panel.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/authorized/item.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/hooks.ts diff --git a/web/app/components/base/form/hooks/use-get-validators.ts b/web/app/components/base/form/hooks/use-get-validators.ts index 91754bc1ba..63b93d2c0a 100644 --- a/web/app/components/base/form/hooks/use-get-validators.ts +++ b/web/app/components/base/form/hooks/use-get-validators.ts @@ -1,34 +1,52 @@ -import { useCallback } from 'react' +import { + isValidElement, + useCallback, +} from 'react' +import type { ReactNode } from 'react' import { useTranslation } from 'react-i18next' import type { FormSchema } from '../types' +import { useRenderI18nObject } from '@/hooks/use-i18n' export const useGetValidators = () => { const { t } = useTranslation() + const renderI18nObject = useRenderI18nObject() + const getLabel = useCallback((label: string | Record | ReactNode) => { + if (isValidElement(label)) + return '' + + if (typeof label === 'string') + return label + + if (typeof label === 'object' && label !== null) + return renderI18nObject(label as Record) + }, []) const getValidators = useCallback((formSchema: FormSchema) => { const { name, validators, required, + label, } = formSchema let mergedValidators = validators + const memorizedLabel = getLabel(label) if (required && !validators) { mergedValidators = { onMount: ({ value }: any) => { if (!value) - return t('common.errorMsg.fieldRequired', { field: name }) + return t('common.errorMsg.fieldRequired', { field: memorizedLabel || name }) }, onChange: ({ value }: any) => { if (!value) - return t('common.errorMsg.fieldRequired', { field: name }) + return t('common.errorMsg.fieldRequired', { field: memorizedLabel || name }) }, onBlur: ({ value }: any) => { if (!value) - return t('common.errorMsg.fieldRequired', { field: name }) + return t('common.errorMsg.fieldRequired', { field: memorizedLabel }) }, } } return mergedValidators - }, [t]) + }, [t, getLabel]) return { getValidators, 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 1f5ced612c..575c96e1ed 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 @@ -181,6 +181,11 @@ export type QuotaConfiguration = { is_valid: boolean } +export type Credential = { + credential_id: string + credential_name: string +} + export type ModelProvider = { provider: string label: TypeWithI18N @@ -207,6 +212,9 @@ export type ModelProvider = { preferred_provider_type: PreferredProviderTypeEnum custom_configuration: { status: CustomConfigurationStatusEnum + current_credential_id?: string + current_credential_name?: string + available_credentials?: Credential[] } system_configuration: { enabled: boolean 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 48acaeb64a..66b3015a6b 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 @@ -7,6 +7,7 @@ import { import useSWR, { useSWRConfig } from 'swr' import { useContext } from 'use-context-selector' import type { + Credential, CustomConfigurationModelFixedFields, DefaultModel, DefaultModelResponse, @@ -77,16 +78,17 @@ export const useProviderCredentialsAndLoadBalancing = ( configurationMethod: ConfigurationMethodEnum, configured?: boolean, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, + credentialId?: string, ) => { const { data: predefinedFormSchemasValue, mutate: mutatePredefined } = useSWR( - (configurationMethod === ConfigurationMethodEnum.predefinedModel && configured) - ? `/workspaces/current/model-providers/${provider}/credentials` + (configurationMethod === ConfigurationMethodEnum.predefinedModel && configured && credentialId) + ? `/workspaces/current/model-providers/${provider}/credentials${credentialId ? `?credential_id=${credentialId}` : ''}` : null, fetchModelProviderCredentials, ) const { data: customFormSchemasValue, mutate: mutateCustomized } = useSWR( - (configurationMethod === ConfigurationMethodEnum.customizableModel && currentCustomConfigurationModelFixedFields) - ? `/workspaces/current/model-providers/${provider}/models/credentials?model=${currentCustomConfigurationModelFixedFields?.__model_name}&model_type=${currentCustomConfigurationModelFixedFields?.__model_type}` + (configurationMethod === ConfigurationMethodEnum.customizableModel && currentCustomConfigurationModelFixedFields && credentialId) + ? `/workspaces/current/model-providers/${provider}/models/credentials?model=${currentCustomConfigurationModelFixedFields?.__model_name}&model_type=${currentCustomConfigurationModelFixedFields?.__model_type}${credentialId ? `&credential_id=${credentialId}` : ''}` : null, fetchModelProviderCredentials, ) @@ -102,6 +104,7 @@ export const useProviderCredentialsAndLoadBalancing = ( : undefined }, [ configurationMethod, + credentialId, currentCustomConfigurationModelFixedFields, customFormSchemasValue?.credentials, predefinedFormSchemasValue?.credentials, @@ -313,40 +316,53 @@ export const useMarketplaceAllPlugins = (providers: ModelProvider[], searchText: } } -export const useModelModalHandler = () => { - const setShowModelModal = useModalContextSelector(state => state.setShowModelModal) +export const useRefreshModel = () => { + const { eventEmitter } = useEventEmitterContextContext() const updateModelProviders = useUpdateModelProviders() const updateModelList = useUpdateModelList() - const { eventEmitter } = useEventEmitterContextContext() + const handleRefreshModel = useCallback((provider: ModelProvider, configurationMethod: ConfigurationMethodEnum, CustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => { + updateModelProviders() + + provider.supported_model_types.forEach((type) => { + updateModelList(type) + }) + + if (configurationMethod === ConfigurationMethodEnum.customizableModel + && provider.custom_configuration.status === CustomConfigurationStatusEnum.active) { + eventEmitter?.emit({ + type: UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST, + payload: provider.provider, + } as any) + + if (CustomConfigurationModelFixedFields?.__model_type) + updateModelList(CustomConfigurationModelFixedFields.__model_type) + } + }, [eventEmitter, updateModelList, updateModelProviders]) + + return { + handleRefreshModel, + } +} + +export const useModelModalHandler = () => { + const setShowModelModal = useModalContextSelector(state => state.setShowModelModal) + const { handleRefreshModel } = useRefreshModel() return ( provider: ModelProvider, configurationMethod: ConfigurationMethodEnum, CustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, + credential?: Credential, ) => { setShowModelModal({ payload: { currentProvider: provider, currentConfigurationMethod: configurationMethod, currentCustomConfigurationModelFixedFields: CustomConfigurationModelFixedFields, + credential, }, onSaveCallback: () => { - updateModelProviders() - - provider.supported_model_types.forEach((type) => { - updateModelList(type) - }) - - if (configurationMethod === ConfigurationMethodEnum.customizableModel - && provider.custom_configuration.status === CustomConfigurationStatusEnum.active) { - eventEmitter?.emit({ - type: UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST, - payload: provider.provider, - } as any) - - if (CustomConfigurationModelFixedFields?.__model_type) - updateModelList(CustomConfigurationModelFixedFields.__model_type) - } + handleRefreshModel(provider, configurationMethod, CustomConfigurationModelFixedFields) }, }) } 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 4aa98daf66..569ca4c777 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 @@ -9,6 +9,7 @@ import SystemModelSelector from './system-model-selector' import ProviderAddedCard from './provider-added-card' import type { ConfigurationMethodEnum, + Credential, CustomConfigurationModelFixedFields, ModelProvider, } from './declarations' @@ -126,7 +127,7 @@ const ModelProviderPage = ({ searchText }: Props) => { handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)} + onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, credential?: Credential) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields, credential)} /> ))}
@@ -140,7 +141,7 @@ const ModelProviderPage = ({ searchText }: Props) => { notConfigured key={provider.provider} provider={provider} - onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)} + 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/auth-panel.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/auth-panel.tsx deleted file mode 100644 index ac0cd5dcfd..0000000000 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/auth-panel.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import type { FC } from 'react' -import { useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import { - RiEqualizer2Line, - RiMoreFill, -} from '@remixicon/react' -import type { ModelProvider } from '../declarations' -import { - CustomConfigurationStatusEnum, -} from '../declarations' -import Indicator from '@/app/components/header/indicator' -import Button from '@/app/components/base/button' -import { - AuthCategory, - Authorized, -} from '@/app/components/plugins/plugin-auth' - -type AuthPanelProps = { - provider: ModelProvider - onSetup: () => void -} -const AuthPanel: FC = ({ - provider, - onSetup, -}) => { - const authorized = false - const { t } = useTranslation() - const customConfig = provider.custom_configuration - const isCustomConfigured = customConfig.status === CustomConfigurationStatusEnum.active - - const renderTrigger = useCallback(() => { - return ( - - ) - }, []) - - return ( - <> - { - provider.provider_credential_schema && ( -
-
- API-KEY - -
-
- - { - authorized && ( - - ) - } -
-
- ) - } - - ) -} - -export default AuthPanel 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 new file mode 100644 index 0000000000..c56e03e8ff --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx @@ -0,0 +1,203 @@ +import { + memo, + useCallback, + useRef, + useState, +} from 'react' +import { + RiEqualizer2Line, +} from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import type { + PortalToFollowElemOptions, +} from '@/app/components/base/portal-to-follow-elem' +import Button from '@/app/components/base/button' +import cn from '@/utils/classnames' +import Confirm from '@/app/components/base/confirm' +import Item from './item' +import { useToastContext } from '@/app/components/base/toast' +import type { Credential } from '../../declarations' +import { useDeleteModelCredential } from '@/service/use-models' + +type AuthorizedProps = { + provider: string + credentials: Credential[] + disabled?: boolean + renderTrigger?: (open?: boolean) => React.ReactNode + isOpen?: boolean + onOpenChange?: (open: boolean) => void + offset?: PortalToFollowElemOptions['offset'] + placement?: PortalToFollowElemOptions['placement'] + triggerPopupSameWidth?: boolean + popupClassName?: string + onItemClick?: (id: string) => void + showItemSelectedIcon?: boolean + selectedCredentialId?: string + onUpdate?: () => void + onSetup: (credential?: Credential) => void +} +const Authorized = ({ + provider, + credentials, + disabled, + renderTrigger, + isOpen, + onOpenChange, + offset = 8, + placement = 'bottom-end', + triggerPopupSameWidth = false, + popupClassName, + onItemClick, + showItemSelectedIcon, + selectedCredentialId, + onUpdate, + onSetup, +}: AuthorizedProps) => { + const { t } = useTranslation() + const { notify } = useToastContext() + const [isLocalOpen, setIsLocalOpen] = useState(false) + const mergedIsOpen = isOpen ?? isLocalOpen + const setMergedIsOpen = useCallback((open: boolean) => { + if (onOpenChange) + onOpenChange(open) + + setIsLocalOpen(open) + }, [onOpenChange]) + const pendingOperationCredentialId = useRef(null) + const [deleteCredentialId, setDeleteCredentialId] = useState(null) + const openConfirm = useCallback((credentialId?: string) => { + if (credentialId) + pendingOperationCredentialId.current = credentialId + + setDeleteCredentialId(pendingOperationCredentialId.current) + }, []) + const closeConfirm = useCallback(() => { + setDeleteCredentialId(null) + pendingOperationCredentialId.current = null + }, []) + const [doingAction, setDoingAction] = useState(false) + const doingActionRef = useRef(doingAction) + const handleSetDoingAction = useCallback((doing: boolean) => { + doingActionRef.current = doing + setDoingAction(doing) + }, []) + const { mutateAsync: deleteModelCredential } = useDeleteModelCredential(provider) + const handleConfirm = useCallback(async () => { + if (doingActionRef.current) + return + if (!pendingOperationCredentialId.current) { + setDeleteCredentialId(null) + return + } + try { + handleSetDoingAction(true) + await deleteModelCredential(pendingOperationCredentialId.current) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + onUpdate?.() + setDeleteCredentialId(null) + pendingOperationCredentialId.current = null + } + finally { + handleSetDoingAction(false) + } + }, [onUpdate, notify, t, handleSetDoingAction]) + const handleEdit = useCallback((credential: Credential) => { + onSetup(credential) + }, [onSetup]) + + return ( + <> + + setMergedIsOpen(!mergedIsOpen)} + asChild + > + { + renderTrigger + ? renderTrigger(mergedIsOpen) + : ( + + ) + } + + +
+
+ { + !!credentials.length && ( +
+
+ API Keys +
+ { + credentials.map(credential => ( + + )) + } +
+ ) + } +
+
+
+ +
+
+
+
+ { + deleteCredentialId && ( + + ) + } + + ) +} + +export default memo(Authorized) diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/item.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/item.tsx new file mode 100644 index 0000000000..9387839d9f --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/item.tsx @@ -0,0 +1,118 @@ +import { + memo, + useMemo, +} from 'react' +import { useTranslation } from 'react-i18next' +import { + RiCheckLine, + RiDeleteBinLine, + RiEqualizer2Line, +} from '@remixicon/react' +import Indicator from '@/app/components/header/indicator' +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' + +type ItemProps = { + credential: Credential + disabled?: boolean + onDelete?: (id: string) => void + onEdit?: (credential: Credential) => void + onSetDefault?: (id: string) => void + disableRename?: boolean + disableEdit?: boolean + disableDelete?: boolean + disableSetDefault?: boolean + onItemClick?: (id: string) => void + showSelectedIcon?: boolean + selectedCredentialId?: string +} +const Item = ({ + credential, + disabled, + onDelete, + onEdit, + disableRename, + disableEdit, + disableDelete, + disableSetDefault, + onItemClick, + showSelectedIcon, + selectedCredentialId, +}: ItemProps) => { + const { t } = useTranslation() + const showAction = useMemo(() => { + return !(disableRename && disableEdit && disableDelete && disableSetDefault) + }, [disableRename, disableEdit, disableDelete, disableSetDefault]) + + return ( +
onItemClick?.(credential.credential_id)} + > +
+ { + showSelectedIcon && ( +
+ { + selectedCredentialId === credential.credential_id && ( + + ) + } +
+ ) + } + +
+ {credential.credential_name} +
+
+ { + showAction && ( +
+ { + !disableEdit && ( + + { + e.stopPropagation() + onEdit?.(credential) + }} + > + + + + ) + } + { + !disableDelete && ( + + { + e.stopPropagation() + onDelete?.(credential.credential_id) + }} + > + + + + ) + } +
+ ) + } +
+ ) +} + +export default memo(Item) diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks.ts b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks.ts new file mode 100644 index 0000000000..94db6cfc8d --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks.ts @@ -0,0 +1,56 @@ +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import type { + ModelLoadBalancingConfig, + ModelProvider, +} from '../declarations' +import { + genModelNameFormSchema, + genModelTypeFormSchema, +} from '../utils' +import { FormTypeEnum } from '@/app/components/base/form/types' + +export const useModelFormSchemas = ( + provider: ModelProvider, + providerFormSchemaPredefined: boolean, + draftConfig?: ModelLoadBalancingConfig, +) => { + const { t } = useTranslation() + const { + provider_credential_schema, + supported_model_types, + model_credential_schema, + } = provider + const formSchemas = useMemo(() => { + return providerFormSchemaPredefined + ? provider_credential_schema.credential_form_schemas + : [ + genModelTypeFormSchema(supported_model_types), + genModelNameFormSchema(model_credential_schema?.model), + ...(draftConfig?.enabled ? [] : 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, + ]) + + const formSchemasWithAuthorizationName = useMemo(() => { + return [ + { + type: FormTypeEnum.textInput, + variable: '__authorization_name__', + label: t('plugin.auth.authorizationName'), + required: true, + }, + ...formSchemas, + ] + }, [formSchemas, t]) + + return { + formSchemas: formSchemasWithAuthorizationName, + } +} 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 d23fd6a31e..31f9cf0a6d 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 @@ -45,11 +45,14 @@ import type { FormRefObject, FormSchema, } from '@/app/components/base/form/types' +import { useModelFormSchemas } from '../model-auth/hooks' +import type { Credential } from '../declarations' type ModelModalProps = { provider: ModelProvider configurateMethod: ConfigurationMethodEnum currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields + credential?: Credential onCancel: () => void onSave: () => void } @@ -58,6 +61,7 @@ const ModelModal: FC = ({ provider, configurateMethod, currentCustomConfigurationModelFixedFields, + credential, onCancel, onSave, }) => { @@ -71,6 +75,7 @@ const ModelModal: FC = ({ configurateMethod, providerFormSchemaPredefined && provider.custom_configuration.status === CustomConfigurationStatusEnum.active, currentCustomConfigurationModelFixedFields, + credential?.credential_id, ) const { isCurrentWorkspaceManager } = useAppContext() const isEditMode = !!formSchemasValue && isCurrentWorkspaceManager @@ -95,22 +100,7 @@ const ModelModal: FC = ({ setDraftConfig(originalConfig) }, [draftConfig, originalConfig]) - const formSchemas = useMemo(() => { - return providerFormSchemaPredefined - ? provider.provider_credential_schema.credential_form_schemas - : [ - genModelTypeFormSchema(provider.supported_model_types), - genModelNameFormSchema(provider.model_credential_schema?.model), - ...(draftConfig?.enabled ? [] : provider.model_credential_schema.credential_form_schemas), - ] - }, [ - providerFormSchemaPredefined, - provider.provider_credential_schema?.credential_form_schemas, - provider.supported_model_types, - provider.model_credential_schema?.credential_form_schemas, - provider.model_credential_schema?.model, - draftConfig?.enabled, - ]) + const { formSchemas } = useModelFormSchemas(provider, providerFormSchemaPredefined, draftConfig) const formRef = useRef(null) const extendedSecretFormSchemas = useMemo( @@ -152,6 +142,7 @@ const ModelModal: FC = ({ }) || { isCheckValidated: false, values: {} } if (!isCheckValidated) return + const res = await saveCredentials( providerFormSchemaPredefined, provider.provider, @@ -190,6 +181,7 @@ const ModelModal: FC = ({ providerFormSchemaPredefined, provider.provider, values, + credential?.credential_id, ) if (res.result === 'success') { notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) @@ -227,7 +219,10 @@ const ModelModal: FC = ({ showRadioUI: formSchema.type === FormTypeEnum.radio, } }) as FormSchema[]} - defaultValues={formSchemasValue} + defaultValues={{ + ...formSchemasValue, + __authorization_name__: credential?.credential_name, + }} inputClassName='justify-start' ref={formRef} /> 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 822df5f726..059c2ec73b 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 @@ -1,7 +1,10 @@ -import type { FC } from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { RiEqualizer2Line } from '@remixicon/react' -import type { ModelProvider } from '../declarations' +import type { + Credential, + ModelProvider, +} from '../declarations' import { ConfigurationMethodEnum, CustomConfigurationStatusEnum, @@ -19,15 +22,18 @@ import Button from '@/app/components/base/button' import { changeModelProviderPriority } from '@/service/common' import { useToastContext } from '@/app/components/base/toast' import { useEventEmitterContextContext } from '@/context/event-emitter' +import Authorized from '../model-auth/authorized' type CredentialPanelProps = { provider: ModelProvider - onSetup: () => void + onSetup: (credential?: Credential) => void + onUpdate: () => void } -const CredentialPanel: FC = ({ +const CredentialPanel = ({ provider, onSetup, -}) => { + onUpdate, +}: CredentialPanelProps) => { const { t } = useTranslation() const { notify } = useToastContext() const { eventEmitter } = useEventEmitterContextContext() @@ -38,6 +44,13 @@ const CredentialPanel: FC = ({ const priorityUseType = provider.preferred_provider_type const isCustomConfigured = customConfig.status === CustomConfigurationStatusEnum.active const configurateMethods = provider.configurate_methods + const { + current_credential_id, + current_credential_name, + available_credentials, + } = provider.custom_configuration + const authorized = current_credential_id && current_credential_name && available_credentials?.every(item => !!item.credential_id) + const authRemoved = !!available_credentials?.length && available_credentials?.every(item => !item.credential_id) const handleChangePriority = async (key: PreferredProviderTypeEnum) => { const res = await changeModelProviderPriority({ @@ -61,25 +74,58 @@ const CredentialPanel: FC = ({ } as any) } } + const credentialLabel = useMemo(() => { + if (authorized) + return current_credential_name + if (authRemoved) + return 'Auth removed' + return 'Unauthorized' + }, [authorized, authRemoved, current_credential_name]) return ( <> { provider.provider_credential_schema && (
-
- API-KEY - +
+
+ {credentialLabel} +
+
- + { + (!authorized || authRemoved) && ( + + ) + } + { + authorized && ( + + ) + } { systemConfig.enabled && isCustomConfigured && ( void + onOpenModal: (configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, credential?: Credential) => void } const ProviderAddedCard: FC = ({ notConfigured, @@ -80,6 +82,8 @@ const ProviderAddedCard: FC = ({ getModelList(v.payload) }) + const { handleRefreshModel } = useRefreshModel() + return (
= ({ { showCredential && ( onOpenModal(ConfigurationMethodEnum.predefinedModel)} + onSetup={(credential?: Credential) => onOpenModal(ConfigurationMethodEnum.predefinedModel, undefined, credential)} + onUpdate={() => handleRefreshModel(provider, ConfigurationMethodEnum.predefinedModel, undefined)} provider={provider} /> ) 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 9056afe69b..91ed6a96ff 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 @@ -82,12 +82,14 @@ export const saveCredentials = async (predefined: boolean, provider: string, v: let body, url if (predefined) { + const { __authorization_name__, ...rest } = v body = { config_from: ConfigurationMethodEnum.predefinedModel, - credentials: v, + credentials: rest, load_balancing: loadBalancing, + name: __authorization_name__, } - url = `/workspaces/current/model-providers/${provider}` + url = `/workspaces/current/model-providers/${provider}/credentials` } else { const { __model_name, __model_type, ...credentials } = v @@ -117,12 +119,17 @@ export const savePredefinedLoadBalancingConfig = async (provider: string, v: For return setModelProvider({ url, body }) } -export const removeCredentials = async (predefined: boolean, provider: string, v: FormValue) => { +export const removeCredentials = async (predefined: boolean, provider: string, v: FormValue, credentialId?: string) => { let url = '' let body if (predefined) { - url = `/workspaces/current/model-providers/${provider}` + url = `/workspaces/current/model-providers/${provider}/credentials` + if (credentialId) { + body = { + credential_id: credentialId, + } + } } else { if (v) { diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx index f6425ec11f..f6d7e72302 100644 --- a/web/context/modal-context.tsx +++ b/web/context/modal-context.tsx @@ -6,6 +6,7 @@ import { createContext, useContext, useContextSelector } from 'use-context-selec import { useRouter, useSearchParams } from 'next/navigation' import type { ConfigurationMethodEnum, + Credential, CustomConfigurationModelFixedFields, ModelLoadBalancingConfigEntry, ModelProvider, @@ -79,6 +80,7 @@ export type ModelModalType = { currentProvider: ModelProvider currentConfigurationMethod: ConfigurationMethodEnum currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields + credential?: Credential } export type LoadBalancingEntryModalType = ModelModalType & { entry?: ModelLoadBalancingConfigEntry @@ -337,6 +339,7 @@ export const ModalContextProvider = ({ provider={showModelModal.payload.currentProvider} configurateMethod={showModelModal.payload.currentConfigurationMethod} currentCustomConfigurationModelFixedFields={showModelModal.payload.currentCustomConfigurationModelFixedFields} + credential={showModelModal.payload.credential} onCancel={handleCancelModelModal} onSave={handleSaveModelModal} /> diff --git a/web/service/use-models.ts b/web/service/use-models.ts index 84122cdd1f..099aefec6f 100644 --- a/web/service/use-models.ts +++ b/web/service/use-models.ts @@ -1,8 +1,13 @@ -import { get } from './base' +import { + del, + get, + post, +} from './base' import type { ModelItem, } from '@/app/components/header/account-setting/model-provider-page/declarations' import { + useMutation, useQuery, // useQueryClient, } from '@tanstack/react-query' @@ -15,3 +20,26 @@ export const useModelProviderModelList = (provider: string) => { queryFn: () => get<{ data: ModelItem[] }>(`/workspaces/current/model-providers/${provider}/models`), }) } + +export const useAddModelCredential = (providerName: string) => { + return useMutation({ + mutationFn: (data: any) => post<{ result: string }>(`/workspaces/current/model-providers/${providerName}/credentials`, data), + }) +} + +export const useGetModelCredential = (providerName: string, credentialId: string) => { + return useQuery({ + queryKey: [NAME_SPACE, 'model-credential', providerName, credentialId], + queryFn: () => get<{ data: Credential[] }>(`/workspaces/current/model-providers/${providerName}/credentials?credential_id=${credentialId}`), + }) +} + +export const useDeleteModelCredential = (providerName: string) => { + return useMutation({ + mutationFn: (credentialId: string) => del<{ result: string }>(`/workspaces/current/model-providers/${providerName}/credentials`, { + body: { + credential_id: credentialId, + }, + }), + }) +} From 4e02abf784267db538cf9a6fab630eab082ef08c Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Thu, 14 Aug 2025 15:03:55 +0800 Subject: [PATCH 07/10] model auth --- .../plugins/plugin-auth/authorized/item.tsx | 11 +++++++++-- web/app/components/plugins/plugin-auth/types.ts | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/web/app/components/plugins/plugin-auth/authorized/item.tsx b/web/app/components/plugins/plugin-auth/authorized/item.tsx index 5508bcc324..76d43310d4 100644 --- a/web/app/components/plugins/plugin-auth/authorized/item.tsx +++ b/web/app/components/plugins/plugin-auth/authorized/item.tsx @@ -135,6 +135,13 @@ const Item = ({ ) } + { + credential.from_enterprise && ( + + Enterprise + + ) + }
) } @@ -172,7 +179,7 @@ const Item = ({ ) } { - !isOAuth && !disableEdit && ( + !isOAuth && !disableEdit && !credential.from_enterprise && ( isWorkspaceDefault?: boolean + from_enterprise?: boolean } From 3f57e4a643a734066163f7fbe546d1dbf2114d64 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Thu, 14 Aug 2025 17:59:33 +0800 Subject: [PATCH 08/10] model auth --- .../model-provider-page/hooks.ts | 5 ++- .../model-auth/authorized/index.tsx | 36 ++++++++++++--- .../model-auth/authorized/item.tsx | 16 +++++++ .../model-provider-page/model-modal/index.tsx | 45 ++++++++++++------- .../provider-added-card/credential-panel.tsx | 35 +++++++++------ web/i18n/en-US/common.ts | 2 + web/i18n/zh-Hans/common.ts | 2 + web/service/use-models.ts | 10 +++++ 8 files changed, 112 insertions(+), 39 deletions(-) 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 66b3015a6b..c9836ae7e6 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 @@ -80,13 +80,13 @@ export const useProviderCredentialsAndLoadBalancing = ( currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, credentialId?: string, ) => { - const { data: predefinedFormSchemasValue, mutate: mutatePredefined } = useSWR( + const { data: predefinedFormSchemasValue, mutate: mutatePredefined, isLoading: isPredefinedLoading } = useSWR( (configurationMethod === ConfigurationMethodEnum.predefinedModel && configured && credentialId) ? `/workspaces/current/model-providers/${provider}/credentials${credentialId ? `?credential_id=${credentialId}` : ''}` : null, fetchModelProviderCredentials, ) - const { data: customFormSchemasValue, mutate: mutateCustomized } = useSWR( + const { data: customFormSchemasValue, mutate: mutateCustomized, isLoading: isCustomizedLoading } = useSWR( (configurationMethod === ConfigurationMethodEnum.customizableModel && currentCustomConfigurationModelFixedFields && credentialId) ? `/workspaces/current/model-providers/${provider}/models/credentials?model=${currentCustomConfigurationModelFixedFields?.__model_name}&model_type=${currentCustomConfigurationModelFixedFields?.__model_type}${credentialId ? `&credential_id=${credentialId}` : ''}` : null, @@ -122,6 +122,7 @@ export const useProviderCredentialsAndLoadBalancing = ( : customFormSchemasValue )?.load_balancing, mutate, + isLoading: isPredefinedLoading || isCustomizedLoading, } // as ([Record | undefined, ModelLoadBalancingConfig | undefined]) } 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 c56e03e8ff..e20fd58216 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 @@ -22,7 +22,10 @@ import Confirm from '@/app/components/base/confirm' import Item from './item' import { useToastContext } from '@/app/components/base/toast' import type { Credential } from '../../declarations' -import { useDeleteModelCredential } from '@/service/use-models' +import { + useDeleteModelCredential, + useSetModelCredentialDefault, +} from '@/service/use-models' type AuthorizedProps = { provider: string @@ -87,6 +90,23 @@ const Authorized = ({ setDoingAction(doing) }, []) const { mutateAsync: deleteModelCredential } = useDeleteModelCredential(provider) + const { mutateAsync: setModelCredentialDefault } = useSetModelCredentialDefault(provider) + const handleSetDefault = useCallback(async (id: string) => { + if (doingActionRef.current) + return + try { + handleSetDoingAction(true) + await setModelCredentialDefault(id) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + onUpdate?.() + } + finally { + handleSetDoingAction(false) + } + }, [setModelCredentialDefault, onUpdate, notify, t, handleSetDoingAction]) const handleConfirm = useCallback(async () => { if (doingActionRef.current) return @@ -109,9 +129,10 @@ const Authorized = ({ handleSetDoingAction(false) } }, [onUpdate, notify, t, handleSetDoingAction]) - const handleEdit = useCallback((credential: Credential) => { + const handleOpenSetup = useCallback((credential?: Credential) => { onSetup(credential) - }, [onSetup]) + setMergedIsOpen(false) + }, [onSetup, setMergedIsOpen]) return ( <> @@ -142,10 +163,10 @@ const Authorized = ({
-
+
{ !!credentials.length && (
@@ -162,7 +183,8 @@ const Authorized = ({ credential={credential} disabled={disabled} onDelete={openConfirm} - onEdit={handleEdit} + onEdit={handleOpenSetup} + onSetDefault={handleSetDefault} onItemClick={onItemClick} showSelectedIcon={showItemSelectedIcon} selectedCredentialId={selectedCredentialId} @@ -176,7 +198,7 @@ const Authorized = ({
+ ) + } { !disableEdit && ( 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 31f9cf0a6d..4c9652da8f 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 @@ -47,6 +47,7 @@ import type { } from '@/app/components/base/form/types' import { useModelFormSchemas } from '../model-auth/hooks' import type { Credential } from '../declarations' +import Loading from '@/app/components/base/loading' type ModelModalProps = { provider: ModelProvider @@ -70,6 +71,7 @@ const ModelModal: FC = ({ credentials: formSchemasValue, loadBalancing: originalConfig, mutate, + isLoading, } = useProviderCredentialsAndLoadBalancing( provider.provider, configurateMethod, @@ -185,7 +187,7 @@ const ModelModal: FC = ({ ) if (res.result === 'success') { notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - mutate() + // mutate() onSave() onCancel() } @@ -211,21 +213,32 @@ const ModelModal: FC = ({
- { - return { - ...formSchema, - name: formSchema.variable, - showRadioUI: formSchema.type === FormTypeEnum.radio, - } - }) as FormSchema[]} - defaultValues={{ - ...formSchemasValue, - __authorization_name__: credential?.credential_name, - }} - inputClassName='justify-start' - ref={formRef} - /> + { + isLoading && ( +
+ +
+ ) + } + { + !isLoading && ( + { + return { + ...formSchema, + name: formSchema.variable, + showRadioUI: formSchema.type === FormTypeEnum.radio, + } + }) as FormSchema[]} + defaultValues={{ + ...formSchemasValue, + __authorization_name__: credential?.credential_name, + }} + inputClassName='justify-start' + ref={formRef} + /> + ) + } { !!draftConfig && ( <> 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 059c2ec73b..85d013ba45 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 @@ -23,6 +23,7 @@ import { changeModelProviderPriority } from '@/service/common' import { useToastContext } from '@/app/components/base/toast' import { useEventEmitterContextContext } from '@/context/event-emitter' import Authorized from '../model-auth/authorized' +import cn from '@/utils/classnames' type CredentialPanelProps = { provider: ModelProvider @@ -49,8 +50,9 @@ const CredentialPanel = ({ current_credential_name, available_credentials, } = provider.custom_configuration - const authorized = current_credential_id && current_credential_name && available_credentials?.every(item => !!item.credential_id) - const authRemoved = !!available_credentials?.length && available_credentials?.every(item => !item.credential_id) + const hasCredential = !!available_credentials?.length + const authorized = current_credential_id && current_credential_name + const authRemoved = hasCredential && !current_credential_id && !current_credential_name const handleChangePriority = async (key: PreferredProviderTypeEnum) => { const res = await changeModelProviderPriority({ @@ -75,21 +77,30 @@ const CredentialPanel = ({ } } const credentialLabel = useMemo(() => { + if (!hasCredential) + return t('common.model.unAuthorized') if (authorized) return current_credential_name if (authRemoved) - return 'Auth removed' - return 'Unauthorized' - }, [authorized, authRemoved, current_credential_name]) + return t('common.model.authRemoved') + + return '' + }, [authorized, authRemoved, current_credential_name, hasCredential]) return ( <> { provider.provider_credential_schema && ( -
+
{credentialLabel} @@ -98,7 +109,7 @@ const CredentialPanel = ({
{ - (!authorized || authRemoved) && ( + !hasCredential && ( ) } { - authorized && ( + (hasCredential || authRemoved) && ( { }), }) } + +export const useSetModelCredentialDefault = (providerName: string) => { + return useMutation({ + mutationFn: (credentialId: string) => post<{ result: string }>(`/workspaces/current/model-providers/${providerName}/credentials/switch`, { + body: { + credential_id: credentialId, + }, + }), + }) +} From 2e28a64d380712706d87ee80ef33afd7e8ad1572 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Fri, 15 Aug 2025 18:34:12 +0800 Subject: [PATCH 09/10] model auth --- .../model-provider-page/declarations.ts | 30 +++ .../model-provider-page/hooks.ts | 3 + .../add-credential-in-load-balancing.tsx | 26 +- .../model-auth/add-custom-model.tsx | 78 ++++++ .../model-auth/add-model.tsx | 76 ------ .../model-auth/authorized/authorized-item.tsx | 90 +++++++ .../{item.tsx => credential-item.tsx} | 10 +- .../model-auth/authorized/index.tsx | 161 +++++-------- .../model-auth/config-provider.tsx | 82 +++++++ .../model-auth/hooks/index.ts | 5 + .../model-auth/hooks/use-auth-service.ts | 57 +++++ .../model-auth/hooks/use-auth.ts | 153 ++++++++++++ .../model-auth/hooks/use-credential-status.ts | 24 ++ .../model-auth/hooks/use-custom-models.ts | 9 + .../use-model-form-schemas.ts} | 4 +- .../model-provider-page/model-auth/index.tsx | 5 + .../switch-credential-in-load-balancing.tsx | 104 ++++++-- .../model-provider-page/model-modal/index.tsx | 223 +++++------------- .../provider-added-card/credential-panel.tsx | 49 +--- .../provider-added-card/index.tsx | 5 - .../provider-added-card/model-list.tsx | 23 +- .../model-load-balancing-configs.tsx | 16 +- .../model-load-balancing-modal.tsx | 24 +- .../model-provider-page/utils.ts | 5 +- web/context/modal-context.tsx | 3 + web/service/use-models.ts | 121 ++++++++-- 26 files changed, 917 insertions(+), 469 deletions(-) create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx delete mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/add-model.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/authorized/authorized-item.tsx rename web/app/components/header/account-setting/model-provider-page/model-auth/authorized/{item.tsx => credential-item.tsx} (95%) create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/config-provider.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/hooks/index.ts create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth-service.ts create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth.ts create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-credential-status.ts create mode 100644 web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-custom-models.ts rename web/app/components/header/account-setting/model-provider-page/model-auth/{hooks.ts => hooks/use-model-form-schemas.ts} (96%) 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 575c96e1ed..16ac26c17a 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 @@ -186,6 +186,22 @@ export type Credential = { credential_name: string } +export type CustomModel = { + model: string + model_type: ModelTypeEnum +} + +export type CustomModelCredential = CustomModel & { + credentials?: Record + available_model_credentials?: Credential[] + current_credential_id?: string +} + +export type CredentialWithModel = Credential & { + model: string + model_type: ModelTypeEnum +} + export type ModelProvider = { provider: string label: TypeWithI18N @@ -215,6 +231,7 @@ export type ModelProvider = { current_credential_id?: string current_credential_name?: string available_credentials?: Credential[] + custom_models?: CustomModelCredential[] } system_configuration: { enabled: boolean @@ -280,9 +297,22 @@ export type ModelLoadBalancingConfigEntry = { in_cooldown?: boolean /** cooldown time (in seconds) */ ttl?: number + credential_id?: string } export type ModelLoadBalancingConfig = { enabled: boolean configs: ModelLoadBalancingConfigEntry[] } + +export type ProviderCredential = { + credentials: Record + name: string + credential_id: string +} + +export type ModelCredential = { + credentials: Record + load_balancing: ModelLoadBalancingConfig + available_credentials: 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 c9836ae7e6..5527e283cc 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 @@ -9,6 +9,7 @@ import { useContext } from 'use-context-selector' import type { Credential, CustomConfigurationModelFixedFields, + CustomModel, DefaultModel, DefaultModelResponse, Model, @@ -354,6 +355,7 @@ export const useModelModalHandler = () => { configurationMethod: ConfigurationMethodEnum, CustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, credential?: Credential, + model?: CustomModel, ) => { setShowModelModal({ payload: { @@ -361,6 +363,7 @@ export const useModelModalHandler = () => { currentConfigurationMethod: configurationMethod, currentCustomConfigurationModelFixedFields: CustomConfigurationModelFixedFields, credential, + model, }, onSaveCallback: () => { handleRefreshModel(provider, configurationMethod, CustomConfigurationModelFixedFields) 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 b913f7284d..d1c366f4f1 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,13 +3,21 @@ import { useCallback, } from 'react' import { RiAddLine } from '@remixicon/react' -import { - AuthCategory, - Authorized, -} from '@/app/components/plugins/plugin-auth' +import { Authorized } from '@/app/components/header/account-setting/model-provider-page/model-auth' import cn from '@/utils/classnames' +import type { + Credential, + ModelProvider, +} from '@/app/components/header/account-setting/model-provider-page/declarations' -const AddCredentialInLoadBalancing = () => { +type AddCredentialInLoadBalancingProps = { + provider: ModelProvider + onSetup: (credential?: Credential) => void +} +const AddCredentialInLoadBalancing = ({ + provider, + onSetup, +}: AddCredentialInLoadBalancingProps) => { const renderTrigger = useCallback((open?: boolean) => { return (
{ 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 new file mode 100644 index 0000000000..36908e0260 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx @@ -0,0 +1,78 @@ +import { + memo, + useCallback, + useMemo, +} from 'react' +import { useTranslation } from 'react-i18next' +import { + RiAddCircleFill, +} from '@remixicon/react' +import { + Button, +} 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 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 { + handleOpenModal, + } = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields) + const handleClick = useCallback(() => { + if (noModels) + handleOpenModal() + }, [handleOpenModal, noModels]) + const ButtonComponent = useMemo(() => { + return ( + + ) + }, [handleClick, noModels]) + + const renderTrigger = useCallback(() => { + return ButtonComponent + }, [ButtonComponent]) + + if (noModels) + return ButtonComponent + + return ( + ({ + model, + credentials: model.available_model_credentials ?? [], + }))} + renderTrigger={renderTrigger} + /> + ) +} + +export default memo(AddCustomModel) diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/add-model.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/add-model.tsx deleted file mode 100644 index fcd4272a27..0000000000 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/add-model.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { - memo, - useState, -} from 'react' -import { useTranslation } from 'react-i18next' -import { - RiAddCircleFill, - RiAddLine, -} from '@remixicon/react' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' -import Tooltip from '@/app/components/base/tooltip' - -const AddModel = () => { - const { t } = useTranslation() - const [open, setOpen] = useState(false) - - return ( - - - - - -
-
-
-
-
- chat-finetune-01 -
- - - -
-
-
- - {t('common.modelProvider.addModel')} -
-
-
-
- ) -} - -export default memo(AddModel) 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 new file mode 100644 index 0000000000..f95161e46b --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/authorized-item.tsx @@ -0,0 +1,90 @@ +import { + memo, + useCallback, +} from 'react' +import { RiAddLine } from '@remixicon/react' +import CredentialItem from './credential-item' +import type { + Credential, + CustomModel, +} from '../../declarations' +import Button from '@/app/components/base/button' +import Tooltip from '@/app/components/base/tooltip' + +type AuthorizedItemProps = { + model?: CustomModel + disabled?: boolean + onDelete?: (id: string) => void + onEdit?: (model?: CustomModel, credential?: Credential) => void + onSetDefault?: (id: string) => void + onItemClick?: (id: string) => void + showItemSelectedIcon?: boolean + selectedCredentialId?: string + disableSetDefault?: boolean + credentials: Credential[] +} +export const AuthorizedItem = ({ + model, + credentials, + disabled, + onDelete, + onEdit, + onSetDefault, + onItemClick, + showItemSelectedIcon, + selectedCredentialId, + disableSetDefault, +}: AuthorizedItemProps) => { + const handleEdit = useCallback((credential?: Credential) => { + onEdit?.(model, credential) + }, [onEdit, model]) + return ( +
+ { + model && ( +
+
+
+ {model.model} +
+ + + +
+ ) + } + { + credentials.map(credential => ( + + )) + } +
+ ) +} + +export default memo(AuthorizedItem) diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/item.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx similarity index 95% rename from web/app/components/header/account-setting/model-provider-page/model-auth/authorized/item.tsx rename to web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx index b4b77a2493..f0f872dd84 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx @@ -15,11 +15,11 @@ import cn from '@/utils/classnames' import type { Credential } from '../../declarations' import Button from '@/app/components/base/button' -type ItemProps = { +type CredentialItemProps = { credential: Credential disabled?: boolean onDelete?: (id: string) => void - onEdit?: (credential: Credential) => void + onEdit?: (credential?: Credential) => void onSetDefault?: (id: string) => void disableRename?: boolean disableEdit?: boolean @@ -29,7 +29,7 @@ type ItemProps = { showSelectedIcon?: boolean selectedCredentialId?: string } -const Item = ({ +const CredentialItem = ({ credential, disabled, onDelete, @@ -42,7 +42,7 @@ const Item = ({ onItemClick, showSelectedIcon, selectedCredentialId, -}: ItemProps) => { +}: CredentialItemProps) => { const { t } = useTranslation() const showAction = useMemo(() => { return !(disableRename && disableEdit && disableDelete && disableSetDefault) @@ -131,4 +131,4 @@ const Item = ({ ) } -export default memo(Item) +export default memo(CredentialItem) 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 e20fd58216..6063ad02be 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,7 +1,6 @@ import { memo, useCallback, - useRef, useState, } from 'react' import { @@ -19,17 +18,25 @@ import type { import Button from '@/app/components/base/button' import cn from '@/utils/classnames' import Confirm from '@/app/components/base/confirm' -import Item from './item' -import { useToastContext } from '@/app/components/base/toast' -import type { Credential } from '../../declarations' -import { - useDeleteModelCredential, - useSetModelCredentialDefault, -} from '@/service/use-models' +import type { + ConfigurationMethodEnum, + Credential, + CustomConfigurationModelFixedFields, + CustomModel, + ModelProvider, +} from '../../declarations' +import { useAuth } from '../hooks' +import AuthorizedItem from './authorized-item' type AuthorizedProps = { - provider: string - credentials: Credential[] + provider: ModelProvider, + configurationMethod: ConfigurationMethodEnum, + currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, + items: { + model?: CustomModel + credentials: Credential[] + }[] + selectedCredential?: Credential disabled?: boolean renderTrigger?: (open?: boolean) => React.ReactNode isOpen?: boolean @@ -40,13 +47,15 @@ type AuthorizedProps = { popupClassName?: string onItemClick?: (id: string) => void showItemSelectedIcon?: boolean - selectedCredentialId?: string onUpdate?: () => void - onSetup: (credential?: Credential) => void + disableSetDefault?: boolean } const Authorized = ({ provider, - credentials, + configurationMethod, + currentCustomConfigurationModelFixedFields, + items, + selectedCredential, disabled, renderTrigger, isOpen, @@ -57,12 +66,10 @@ const Authorized = ({ popupClassName, onItemClick, showItemSelectedIcon, - selectedCredentialId, onUpdate, - onSetup, + disableSetDefault, }: AuthorizedProps) => { const { t } = useTranslation() - const { notify } = useToastContext() const [isLocalOpen, setIsLocalOpen] = useState(false) const mergedIsOpen = isOpen ?? isLocalOpen const setMergedIsOpen = useCallback((open: boolean) => { @@ -71,68 +78,20 @@ const Authorized = ({ setIsLocalOpen(open) }, [onOpenChange]) - const pendingOperationCredentialId = useRef(null) - const [deleteCredentialId, setDeleteCredentialId] = useState(null) - const openConfirm = useCallback((credentialId?: string) => { - if (credentialId) - pendingOperationCredentialId.current = credentialId + const { + openConfirmDelete, + closeConfirmDelete, + doingAction, + handleActiveCredential, + handleConfirmDelete, + deleteCredentialId, + handleOpenModal, + } = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields, onUpdate) - setDeleteCredentialId(pendingOperationCredentialId.current) - }, []) - const closeConfirm = useCallback(() => { - setDeleteCredentialId(null) - pendingOperationCredentialId.current = null - }, []) - const [doingAction, setDoingAction] = useState(false) - const doingActionRef = useRef(doingAction) - const handleSetDoingAction = useCallback((doing: boolean) => { - doingActionRef.current = doing - setDoingAction(doing) - }, []) - const { mutateAsync: deleteModelCredential } = useDeleteModelCredential(provider) - const { mutateAsync: setModelCredentialDefault } = useSetModelCredentialDefault(provider) - const handleSetDefault = useCallback(async (id: string) => { - if (doingActionRef.current) - return - try { - handleSetDoingAction(true) - await setModelCredentialDefault(id) - notify({ - type: 'success', - message: t('common.api.actionSuccess'), - }) - onUpdate?.() - } - finally { - handleSetDoingAction(false) - } - }, [setModelCredentialDefault, onUpdate, notify, t, handleSetDoingAction]) - const handleConfirm = useCallback(async () => { - if (doingActionRef.current) - return - if (!pendingOperationCredentialId.current) { - setDeleteCredentialId(null) - return - } - try { - handleSetDoingAction(true) - await deleteModelCredential(pendingOperationCredentialId.current) - notify({ - type: 'success', - message: t('common.api.actionSuccess'), - }) - onUpdate?.() - setDeleteCredentialId(null) - pendingOperationCredentialId.current = null - } - finally { - handleSetDoingAction(false) - } - }, [onUpdate, notify, t, handleSetDoingAction]) - const handleOpenSetup = useCallback((credential?: Credential) => { - onSetup(credential) + const handleEdit = useCallback((model?: CustomModel, credential?: Credential) => { + handleOpenModal(model, credential) setMergedIsOpen(false) - }, [onSetup, setMergedIsOpen]) + }, [handleOpenModal, setMergedIsOpen]) return ( <> @@ -166,39 +125,29 @@ const Authorized = ({ 'w-[360px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg', popupClassName, )}> -
+
{ - !!credentials.length && ( -
-
- API Keys -
- { - credentials.map(credential => ( - - )) - } -
- ) + items.map((item, index) => ( + + )) }
+ ) + }, [handleClick, authorized]) + + if (!hasCredential) + return ButtonComponent + + return ( + + ) +} + +export default memo(ConfigProvider) 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 new file mode 100644 index 0000000000..738fe8a6ba --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/index.ts @@ -0,0 +1,5 @@ +export * from './use-model-form-schemas' +export * from './use-credential-status' +export * from './use-custom-models' +export * from './use-auth' +export * from './use-auth-service' 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 new file mode 100644 index 0000000000..0f5de9bde9 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth-service.ts @@ -0,0 +1,57 @@ +import { useCallback } from 'react' +import { + useActiveModelCredential, + useActiveProviderCredential, + useAddModelCredential, + useAddProviderCredential, + useDeleteModelCredential, + useDeleteProviderCredential, + useEditModelCredential, + useEditProviderCredential, + useGetModelCredential, + useGetProviderCredential, +} from '@/service/use-models' +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 useAuthService = (provider: string) => { + const { mutateAsync: addProviderCredential } = useAddProviderCredential(provider) + const { mutateAsync: editProviderCredential } = useEditProviderCredential(provider) + const { mutateAsync: deleteProviderCredential } = useDeleteProviderCredential(provider) + const { mutateAsync: activeProviderCredential } = useActiveProviderCredential(provider) + + const { mutateAsync: addModelCredential } = useAddModelCredential(provider) + const { mutateAsync: activeModelCredential } = useActiveModelCredential(provider) + const { mutateAsync: deleteModelCredential } = useDeleteModelCredential(provider) + const { mutateAsync: editModelCredential } = useEditModelCredential(provider) + + const getAddCredentialService = useCallback((isModel: boolean) => { + return isModel ? addModelCredential : addProviderCredential + }, [addModelCredential, addProviderCredential]) + + const getEditCredentialService = useCallback((isModel: boolean) => { + return isModel ? editModelCredential : editProviderCredential + }, [editModelCredential, editProviderCredential]) + + const getDeleteCredentialService = useCallback((isModel: boolean) => { + return isModel ? deleteModelCredential : deleteProviderCredential + }, [deleteModelCredential, deleteProviderCredential]) + + const getActiveCredentialService = useCallback((isModel: boolean) => { + return isModel ? activeModelCredential : activeProviderCredential + }, [activeModelCredential, activeProviderCredential]) + + return { + getAddCredentialService, + getEditCredentialService, + getDeleteCredentialService, + getActiveCredentialService, + } +} 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 new file mode 100644 index 0000000000..f33901b1f9 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth.ts @@ -0,0 +1,153 @@ +import { + useCallback, + useRef, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useToastContext } from '@/app/components/base/toast' +import { useAuthService } from './use-auth-service' +import type { + ConfigurationMethodEnum, + Credential, + CustomConfigurationModelFixedFields, + CustomModel, + ModelProvider, +} from '../../declarations' +import { + useModelModalHandler, + useRefreshModel, +} from '@/app/components/header/account-setting/model-provider-page/hooks' + +export const useAuth = ( + provider: ModelProvider, + configurationMethod: ConfigurationMethodEnum, + currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, + onUpdate?: () => void, +) => { + const { t } = useTranslation() + const { notify } = useToastContext() + const { + getDeleteCredentialService, + getActiveCredentialService, + getEditCredentialService, + getAddCredentialService, + } = useAuthService(provider.provider) + const handleOpenModelModal = useModelModalHandler() + const { handleRefreshModel } = useRefreshModel() + 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 + if (model) + pendingOperationModel.current = model + + setDeleteCredentialId(pendingOperationCredentialId.current) + }, []) + const closeConfirmDelete = useCallback(() => { + setDeleteCredentialId(null) + pendingOperationCredentialId.current = null + }, []) + const [doingAction, setDoingAction] = useState(false) + const doingActionRef = useRef(doingAction) + const handleSetDoingAction = useCallback((doing: boolean) => { + doingActionRef.current = doing + setDoingAction(doing) + }, []) + const handleActiveCredential = useCallback(async (id: string, model?: CustomModel) => { + if (doingActionRef.current) + return + try { + handleSetDoingAction(true) + await getActiveCredentialService(!!model)({ + credential_id: id, + model: model?.model, + model_type: model?.model_type, + }) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + onUpdate?.() + } + finally { + handleSetDoingAction(false) + } + }, [getActiveCredentialService, onUpdate, notify, t, handleSetDoingAction]) + const handleConfirmDelete = useCallback(async () => { + if (doingActionRef.current) + return + if (!pendingOperationCredentialId.current) { + setDeleteCredentialId(null) + return + } + try { + handleSetDoingAction(true) + await getDeleteCredentialService(!!pendingOperationModel.current)({ + credential_id: pendingOperationCredentialId.current, + model: pendingOperationModel.current?.model, + model_type: pendingOperationModel.current?.model_type, + }) + notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + onUpdate?.() + handleRefreshModel(provider, configurationMethod, undefined) + setDeleteCredentialId(null) + pendingOperationCredentialId.current = null + } + finally { + handleSetDoingAction(false) + } + }, [onUpdate, notify, t, handleSetDoingAction, getDeleteCredentialService]) + const handleAddCredential = useCallback((model?: CustomModel) => { + if (model) + pendingOperationModel.current = model + }, []) + const handleSaveCredential = useCallback(async (payload: Record) => { + if (doingActionRef.current) + return + try { + handleSetDoingAction(true) + + let res: { result?: string } = {} + if (payload.credential_id) + res = await getEditCredentialService(!!payload.model)(payload as any) + else + res = await getAddCredentialService(!!payload.model)(payload as any) + + if (res.result === 'success') { + notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) + onUpdate?.() + } + } + finally { + handleSetDoingAction(false) + } + }, [onUpdate, notify, t, handleSetDoingAction, getEditCredentialService, getAddCredentialService]) + const handleOpenModal = useCallback((model?: CustomModel, credential?: Credential) => { + handleOpenModelModal( + provider, + configurationMethod, + currentCustomConfigurationModelFixedFields, + credential, + model, + ) + }, [handleOpenModelModal, provider, configurationMethod, currentCustomConfigurationModelFixedFields]) + + return { + pendingOperationCredentialId, + pendingOperationModel, + openConfirmDelete, + closeConfirmDelete, + doingAction, + handleActiveCredential, + handleConfirmDelete, + handleAddCredential, + deleteCredentialId, + handleSaveCredential, + handleOpenModal, + } +} diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-credential-status.ts b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-credential-status.ts new file mode 100644 index 0000000000..c83ba1cf80 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-credential-status.ts @@ -0,0 +1,24 @@ +import { useMemo } from 'react' +import type { + ModelProvider, +} from '../../declarations' + +export const useCredentialStatus = (provider: ModelProvider) => { + const { + current_credential_id, + current_credential_name, + available_credentials, + } = provider.custom_configuration + const hasCredential = !!available_credentials?.length + const authorized = current_credential_id && current_credential_name + const authRemoved = hasCredential && !current_credential_id && !current_credential_name + + return useMemo(() => ({ + hasCredential, + authorized, + authRemoved, + current_credential_id, + current_credential_name, + available_credentials, + }), [hasCredential, authorized, authRemoved, current_credential_id, current_credential_name, available_credentials]) +} 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 new file mode 100644 index 0000000000..f3b50f3f49 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-custom-models.ts @@ -0,0 +1,9 @@ +import type { + ModelProvider, +} from '../../declarations' + +export const useCustomModels = (provider: ModelProvider) => { + const { custom_models } = provider.custom_configuration + + return custom_models || [] +} diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks.ts b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-model-form-schemas.ts similarity index 96% rename from web/app/components/header/account-setting/model-provider-page/model-auth/hooks.ts rename to web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-model-form-schemas.ts index 94db6cfc8d..463c905959 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/hooks.ts +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/hooks/use-model-form-schemas.ts @@ -3,11 +3,11 @@ import { useTranslation } from 'react-i18next' import type { ModelLoadBalancingConfig, ModelProvider, -} from '../declarations' +} from '../../declarations' import { genModelNameFormSchema, genModelTypeFormSchema, -} from '../utils' +} from '../../utils' import { FormTypeEnum } from '@/app/components/base/form/types' export const useModelFormSchemas = ( 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 e69de29bb2..e33e30da3a 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 @@ -0,0 +1,5 @@ +export { default as Authorized } from './authorized' +export { default as SwitchCredentialInLoadBalancing } from './switch-credential-in-load-balancing' +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' 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 bb5b8bd62e..e9e1f16358 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 @@ -1,45 +1,107 @@ +import type { Dispatch, SetStateAction } from 'react' import { memo, useCallback, } from 'react' +import { useTranslation } from 'react-i18next' import { RiArrowDownSLine } from '@remixicon/react' import Button from '@/app/components/base/button' -import { - AuthCategory, - Authorized, -} from '@/app/components/plugins/plugin-auth' 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 = { + provider: ModelProvider + draftConfig?: ModelLoadBalancingConfig + setDraftConfig: Dispatch> +} +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 SwitchCredentialInLoadBalancing = () => { const renderTrigger = useCallback(() => { + const selectedCredentialId = draftConfig?.configs.find(config => config.name === '__inherit__')?.credential_id + const selectedCredential = available_credentials?.find(credential => credential.credential_id === selectedCredentialId) + const name = selectedCredential?.credential_name || current_credential_name + const authRemoved = !!selectedCredentialId && !selectedCredential return ( ) - }, []) + }, [current_credential_name, t, draftConfig, available_credentials]) 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 4c9652da8f..937fe6a9ae 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 @@ -2,34 +2,20 @@ import type { FC } from 'react' import { memo, useCallback, - useEffect, - useMemo, useRef, - useState, } from 'react' import { useTranslation } from 'react-i18next' import type { CustomConfigurationModelFixedFields, - ModelLoadBalancingConfig, - ModelLoadBalancingConfigEntry, ModelProvider, } from '../declarations' import { ConfigurationMethodEnum, - CustomConfigurationStatusEnum, FormTypeEnum, } from '../declarations' -import { - genModelNameFormSchema, - genModelTypeFormSchema, - removeCredentials, - saveCredentials, -} from '../utils' import { useLanguage, - useProviderCredentialsAndLoadBalancing, } from '../hooks' -import ModelLoadBalancingConfigs from '../provider-added-card/model-load-balancing-configs' 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' @@ -37,7 +23,6 @@ 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 { useAppContext } from '@/context/app-context' import AuthForm from '@/app/components/base/form/form-scenarios/auth' @@ -46,20 +31,29 @@ import type { FormSchema, } from '@/app/components/base/form/types' import { useModelFormSchemas } from '../model-auth/hooks' -import type { Credential } from '../declarations' +import type { + Credential, + CustomModel, +} from '../declarations' import Loading from '@/app/components/base/loading' +import { + useAuth, + useGetCredential, +} from '@/app/components/header/account-setting/model-provider-page/model-auth/hooks' type ModelModalProps = { provider: ModelProvider + model?: CustomModel + credential?: Credential configurateMethod: ConfigurationMethodEnum currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields - credential?: Credential onCancel: () => void onSave: () => void } const ModelModal: FC = ({ provider, + model, configurateMethod, currentCustomConfigurationModelFixedFields, credential, @@ -68,134 +62,62 @@ const ModelModal: FC = ({ }) => { const providerFormSchemaPredefined = configurateMethod === ConfigurationMethodEnum.predefinedModel const { - credentials: formSchemasValue, - loadBalancing: originalConfig, - mutate, isLoading, - } = useProviderCredentialsAndLoadBalancing( - provider.provider, - configurateMethod, - providerFormSchemaPredefined && provider.custom_configuration.status === CustomConfigurationStatusEnum.active, - currentCustomConfigurationModelFixedFields, - credential?.credential_id, - ) + data: credentialData = {}, + } = useGetCredential(provider.provider, credential?.credential_id, model) + const { + handleSaveCredential, + handleConfirmDelete, + deleteCredentialId, + closeConfirmDelete, + openConfirmDelete, + doingAction, + } = useAuth(provider, configurateMethod, currentCustomConfigurationModelFixedFields, onSave) + const { + credentials: formSchemasValue, + } = credentialData as any + const { isCurrentWorkspaceManager } = useAppContext() const isEditMode = !!formSchemasValue && isCurrentWorkspaceManager const { t } = useTranslation() - const { notify } = useToastContext() const language = useLanguage() - const [loading, setLoading] = useState(false) - const [showConfirm, setShowConfirm] = useState(false) - - const [draftConfig, setDraftConfig] = useState() - const originalConfigMap = useMemo(() => { - if (!originalConfig) - return {} - return originalConfig?.configs.reduce((prev, config) => { - if (config.id) - prev[config.id] = config - return prev - }, {} as Record) - }, [originalConfig]) - useEffect(() => { - if (originalConfig && !draftConfig) - setDraftConfig(originalConfig) - }, [draftConfig, originalConfig]) - - const { formSchemas } = useModelFormSchemas(provider, providerFormSchemaPredefined, draftConfig) + const { formSchemas } = useModelFormSchemas(provider, providerFormSchemaPredefined) const formRef = useRef(null) - const extendedSecretFormSchemas = useMemo( - () => - (providerFormSchemaPredefined - ? provider.provider_credential_schema.credential_form_schemas - : [ - genModelTypeFormSchema(provider.supported_model_types), - genModelNameFormSchema(provider.model_credential_schema?.model), - ...provider.model_credential_schema.credential_form_schemas, - ]).filter(({ type }) => type === FormTypeEnum.secretInput), - [ - provider.model_credential_schema?.credential_form_schemas, - provider.model_credential_schema?.model, - provider.provider_credential_schema?.credential_form_schemas, - provider.supported_model_types, - providerFormSchemaPredefined, - ], - ) + const handleSave = useCallback(async () => { + const { + isCheckValidated, + values, + } = formRef.current?.getFormValues({ + needCheckValidatedValues: true, + needTransformWhenSecretFieldIsPristine: true, + }) || { isCheckValidated: false, values: {} } + if (!isCheckValidated) + return - const encodeConfigEntrySecretValues = useCallback((entry: ModelLoadBalancingConfigEntry) => { - const result = { ...entry } - extendedSecretFormSchemas.forEach(({ variable }) => { - if (entry.id && result.credentials[variable] === originalConfigMap[entry.id]?.credentials?.[variable]) - result.credentials[variable] = '[__HIDDEN__]' - }) - return result - }, [extendedSecretFormSchemas, originalConfigMap]) - - 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 saveCredentials( - providerFormSchemaPredefined, - provider.provider, - values, - { - ...draftConfig, - enabled: Boolean(draftConfig?.enabled), - configs: draftConfig?.configs.map(encodeConfigEntrySecretValues) || [], - }, - ) - if (res.result === 'success') { - notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - mutate() - onSave() - onCancel() - } + const { + __authorization_name__, + __model_name, + __model_type, + ...rest + } = values + if (__model_name && __model_type) { + handleSaveCredential({ + credential_id: credential?.credential_id, + credentials: rest, + name: __authorization_name__, + model: __model_name, + model_type: __model_type, + }) } - finally { - setLoading(false) + else { + handleSaveCredential({ + credential_id: credential?.credential_id, + credentials: rest, + name: __authorization_name__, + }) } - } - - const handleRemove = async () => { - try { - setLoading(true) - const { - isCheckValidated, - values, - } = formRef.current?.getFormValues({ - needCheckValidatedValues: true, - needTransformWhenSecretFieldIsPristine: true, - }) || { isCheckValidated: false, values: {} } - if (!isCheckValidated) - return - const res = await removeCredentials( - providerFormSchemaPredefined, - provider.provider, - values, - credential?.credential_id, - ) - if (res.result === 'success') { - notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - // mutate() - onSave() - onCancel() - } - } - finally { - setLoading(false) - } - } + }, [handleSaveCredential, credential?.credential_id, model]) const renderTitlePrefix = () => { const prefix = isEditMode ? t('common.operation.setup') : t('common.operation.add') @@ -239,20 +161,6 @@ const ModelModal: FC = ({ /> ) } - { - !!draftConfig && ( - <> -
- - - ) - }
@@ -278,7 +186,7 @@ const ModelModal: FC = ({ variant='warning' size='large' className='mr-2' - onClick={() => setShowConfirm(true)} + onClick={() => openConfirmDelete(credential?.credential_id, model)} > {t('common.operation.remove')} @@ -295,11 +203,7 @@ const ModelModal: FC = ({ size='large' variant='primary' onClick={handleSave} - disabled={ - loading - || (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2) - } - + disabled={isLoading || doingAction} > {t('common.operation.save')} @@ -322,12 +226,13 @@ const ModelModal: FC = ({
{ - showConfirm && ( + deleteCredentialId && ( setShowConfirm(false)} - onConfirm={handleRemove} + isDisabled={doingAction} + onCancel={closeConfirmDelete} + onConfirm={handleConfirmDelete} /> ) } 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 85d013ba45..02a2b83934 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 @@ -1,8 +1,6 @@ import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { RiEqualizer2Line } from '@remixicon/react' import type { - Credential, ModelProvider, } from '../declarations' import { @@ -18,22 +16,18 @@ import PrioritySelector from './priority-selector' import PriorityUseTip from './priority-use-tip' import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './index' import Indicator from '@/app/components/header/indicator' -import Button from '@/app/components/base/button' import { changeModelProviderPriority } from '@/service/common' import { useToastContext } from '@/app/components/base/toast' import { useEventEmitterContextContext } from '@/context/event-emitter' -import Authorized from '../model-auth/authorized' import cn from '@/utils/classnames' +import { useCredentialStatus } from '@/app/components/header/account-setting/model-provider-page/model-auth/hooks' +import { ConfigProvider } from '@/app/components/header/account-setting/model-provider-page/model-auth' type CredentialPanelProps = { provider: ModelProvider - onSetup: (credential?: Credential) => void - onUpdate: () => void } const CredentialPanel = ({ provider, - onSetup, - onUpdate, }: CredentialPanelProps) => { const { t } = useTranslation() const { notify } = useToastContext() @@ -46,13 +40,11 @@ const CredentialPanel = ({ const isCustomConfigured = customConfig.status === CustomConfigurationStatusEnum.active const configurateMethods = provider.configurate_methods const { - current_credential_id, + hasCredential, + authorized, + authRemoved, current_credential_name, - available_credentials, - } = provider.custom_configuration - const hasCredential = !!available_credentials?.length - const authorized = current_credential_id && current_credential_name - const authRemoved = hasCredential && !current_credential_id && !current_credential_name + } = useCredentialStatus(provider) const handleChangePriority = async (key: PreferredProviderTypeEnum) => { const res = await changeModelProviderPriority({ @@ -108,31 +100,10 @@ const CredentialPanel = ({
- { - !hasCredential && ( - - ) - } - { - (hasCredential || authRemoved) && ( - - ) - } + { systemConfig.enabled && isCustomConfigured && ( = ({ getModelList(v.payload) }) - const { handleRefreshModel } = useRefreshModel() - return (
= ({ { showCredential && ( onOpenModal(ConfigurationMethodEnum.predefinedModel, undefined, credential)} - onUpdate={() => handleRefreshModel(provider, ConfigurationMethodEnum.predefinedModel, undefined)} provider={provider} /> ) 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 699be6edda..9bcf226064 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 @@ -5,6 +5,7 @@ import { RiArrowRightSLine, } from '@remixicon/react' import type { + Credential, CustomConfigurationModelFixedFields, ModelItem, ModelProvider, @@ -13,10 +14,11 @@ import { ConfigurationMethodEnum, } from '../declarations' // import Tab from './tab' -import AddModelButton from './add-model-button' 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 @@ -36,11 +38,12 @@ const ModelList: FC = ({ 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) => { + const onModifyLoadBalancing = useCallback((model: ModelItem, credential?: Credential) => { setShowModelLoadBalancingModal({ provider, + credential, model: model!, open: !!model, onClose: () => setShowModelLoadBalancingModal(null), @@ -65,17 +68,15 @@ const ModelList: FC = ({ - {/* { - isConfigurable && canSystemConfig && ( - - {}} /> - - ) - } */} { isConfigurable && isCurrentWorkspaceManager && (
- onConfig()} /> +
) } 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 1a3039659a..efe2a8f94d 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 @@ -11,7 +11,7 @@ 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, Plus02 } from '@/app/components/base/icons/src/vender/line/general' +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' @@ -19,6 +19,7 @@ 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' export type ModelLoadBalancingConfigsProps = { draftConfig?: ModelLoadBalancingConfig @@ -234,15 +235,10 @@ const ModelLoadBalancingConfigs = ({
) })} - -
toggleEntryModal()} - > -
- {t('common.modelProvider.addConfig')} -
-
+ toggleEntryModal()} + />
)} { 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 9fb07401f7..a31d8a5c00 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,7 +1,13 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' -import type { ModelItem, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, ModelProvider } from '../declarations' +import type { + Credential, + ModelItem, + ModelLoadBalancingConfig, + ModelLoadBalancingConfigEntry, + ModelProvider, +} from '../declarations' import { FormTypeEnum } from '../declarations' import ModelIcon from '../model-icon' import ModelName from '../model-name' @@ -13,17 +19,26 @@ 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' export type ModelLoadBalancingModalProps = { provider: ModelProvider model: ModelItem + credential?: Credential open?: boolean onClose?: () => void onSave?: (provider: string) => void } // model balancing config modal -const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSave }: ModelLoadBalancingModalProps) => { +const ModelLoadBalancingModal = ({ + provider, + model, + credential, + open = false, + onClose, + onSave, +}: ModelLoadBalancingModalProps) => { const { t } = useTranslation() const { notify } = useToastContext() @@ -152,6 +167,11 @@ const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSav
{t('common.modelProvider.providerManaged')}
{t('common.modelProvider.providerManagedDescription')}
+
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 91ed6a96ff..f577a536dc 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 @@ -1,6 +1,5 @@ import { ValidatedStatus } from '../key-validator/declarations' import type { - CredentialFormSchemaRadio, CredentialFormSchemaTextInput, FormValue, ModelLoadBalancingConfig, @@ -181,7 +180,7 @@ export const genModelTypeFormSchema = (modelTypes: ModelTypeEnum[]) => { show_on: [], } }), - } as CredentialFormSchemaRadio + } as any } export const genModelNameFormSchema = (model?: Pick) => { @@ -198,5 +197,5 @@ export const genModelNameFormSchema = (model?: Pick { }) } -export const useAddModelCredential = (providerName: string) => { - return useMutation({ - mutationFn: (data: any) => post<{ result: string }>(`/workspaces/current/model-providers/${providerName}/credentials`, data), - }) -} - -export const useGetModelCredential = (providerName: string, credentialId: string) => { +export const useGetProviderCredential = (enabled: boolean, provider: string, credentialId?: string) => { return useQuery({ - queryKey: [NAME_SPACE, 'model-credential', providerName, credentialId], - queryFn: () => get<{ data: Credential[] }>(`/workspaces/current/model-providers/${providerName}/credentials?credential_id=${credentialId}`), + enabled, + queryKey: [NAME_SPACE, 'model-list', provider, credentialId], + queryFn: () => get<{ data: ProviderCredential }>(`/workspaces/current/model-providers/${provider}/credentials${credentialId ? `?credential_id=${credentialId}` : ''}`), }) } -export const useDeleteModelCredential = (providerName: string) => { +export const useAddProviderCredential = (provider: string) => { return useMutation({ - mutationFn: (credentialId: string) => del<{ result: string }>(`/workspaces/current/model-providers/${providerName}/credentials`, { - body: { - credential_id: credentialId, - }, + mutationFn: (data: ProviderCredential) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/credentials`, { + body: data, }), }) } -export const useSetModelCredentialDefault = (providerName: string) => { +export const useEditProviderCredential = (provider: string) => { return useMutation({ - mutationFn: (credentialId: string) => post<{ result: string }>(`/workspaces/current/model-providers/${providerName}/credentials/switch`, { - body: { - credential_id: credentialId, - }, + mutationFn: (data: ProviderCredential) => put<{ result: string }>(`/workspaces/current/model-providers/${provider}/credentials`, { + body: data, + }), + }) +} + +export const useDeleteProviderCredential = (provider: string) => { + return useMutation({ + mutationFn: (data: { + credential_id: string + }) => del<{ result: string }>(`/workspaces/current/model-providers/${provider}/credentials`, { + body: data, + }), + }) +} + +export const useActiveProviderCredential = (provider: string) => { + return useMutation({ + mutationFn: (data: { + credential_id: string + model?: string + model_type?: ModelTypeEnum + }) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/credentials/switch`, { + body: data, + }), + }) +} + +export const useGetModelCredential = ( + enabled: boolean, + provider: string, + credentialId?: string, + model?: string, + modelType?: string, + configFrom?: string, +) => { + 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}`), + }) +} + +export const useAddModelCredential = (provider: string) => { + return useMutation({ + mutationFn: (data: ModelCredential) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/models/credentials`, { + body: data, + }), + }) +} + +export const useEditModelCredential = (provider: string) => { + return useMutation({ + mutationFn: (data: ModelCredential) => put<{ result: string }>(`/workspaces/current/model-providers/${provider}/models/credentials`, { + body: data, + }), + }) +} + +export const useDeleteModelCredential = (provider: string) => { + return useMutation({ + mutationFn: (data: { + credential_id: string + model?: string + model_type?: ModelTypeEnum + }) => del<{ result: string }>(`/workspaces/current/model-providers/${provider}/models/credentials`, { + body: data, + }), + }) +} + +export const useDeleteModel = (provider: string) => { + return useMutation({ + mutationFn: (data: { + model: string + model_type: ModelTypeEnum + }) => del<{ result: string }>(`/workspaces/current/model-providers/${provider}/models/credentials`, { + body: data, + }), + }) +} + +export const useActiveModelCredential = (provider: string) => { + return useMutation({ + mutationFn: (data: { + credential_id: string + model?: string + model_type?: ModelTypeEnum + }) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/models/credentials/switch`, { + body: data, }), }) } From 473b465efbf94d259763557ec1327bf916ead1ab Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Mon, 18 Aug 2025 18:03:36 +0800 Subject: [PATCH 10/10] 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, + }), + }) +}