feat: add multi model credentials (#24451)

Co-authored-by: zxhlyh <jasonapring2015@outlook.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
非法操作
2025-08-25 16:12:29 +08:00
committed by GitHub
parent b08bfa203a
commit 6010d5f24c
65 changed files with 5202 additions and 1814 deletions

View File

@@ -0,0 +1,6 @@
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'
export * from './use-credential-data'

View File

@@ -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, 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) => {
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,
}
}

View File

@@ -0,0 +1,158 @@
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,
isModelCredential?: boolean,
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<string | null>(null)
const pendingOperationModel = useRef<CustomModel | null>(null)
const [deleteCredentialId, setDeleteCredentialId] = useState<string | null>(null)
const openConfirmDelete = useCallback((credential?: Credential, model?: CustomModel) => {
if (credential)
pendingOperationCredentialId.current = credential.credential_id
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 (credential: Credential, model?: CustomModel) => {
if (doingActionRef.current)
return
try {
handleSetDoingAction(true)
await getActiveCredentialService(!!model)({
credential_id: credential.credential_id,
model: model?.model,
model_type: model?.model_type,
})
notify({
type: 'success',
message: t('common.api.actionSuccess'),
})
onUpdate?.()
handleRefreshModel(provider, configurationMethod, undefined)
}
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(!!isModelCredential)({
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
pendingOperationModel.current = null
}
finally {
handleSetDoingAction(false)
}
}, [onUpdate, notify, t, handleSetDoingAction, getDeleteCredentialService, isModelCredential])
const handleAddCredential = useCallback((model?: CustomModel) => {
if (model)
pendingOperationModel.current = model
}, [])
const handleSaveCredential = useCallback(async (payload: Record<string, any>) => {
if (doingActionRef.current)
return
try {
handleSetDoingAction(true)
let res: { result?: string } = {}
if (payload.credential_id)
res = await getEditCredentialService(!!isModelCredential)(payload as any)
else
res = await getAddCredentialService(!!isModelCredential)(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((credential?: Credential, model?: CustomModel) => {
handleOpenModelModal(
provider,
configurationMethod,
currentCustomConfigurationModelFixedFields,
isModelCredential,
credential,
model,
onUpdate,
)
}, [handleOpenModelModal, provider, configurationMethod, currentCustomConfigurationModelFixedFields, isModelCredential, onUpdate])
return {
pendingOperationCredentialId,
pendingOperationModel,
openConfirmDelete,
closeConfirmDelete,
doingAction,
handleActiveCredential,
handleConfirmDelete,
handleAddCredential,
deleteCredentialId,
handleSaveCredential,
handleOpenModal,
}
}

View File

@@ -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,
}
}

View File

@@ -0,0 +1,26 @@
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
const currentCredential = available_credentials?.find(credential => credential.credential_id === current_credential_id)
return useMemo(() => ({
hasCredential,
authorized,
authRemoved,
current_credential_id,
current_credential_name,
available_credentials,
notAllowedToUse: currentCredential?.not_allowed_to_use,
}), [hasCredential, authorized, authRemoved, current_credential_id, current_credential_name, available_credentials])
}

View File

@@ -0,0 +1,9 @@
import type {
ModelProvider,
} from '../../declarations'
export const useCustomModels = (provider: ModelProvider) => {
const { custom_models } = provider.custom_configuration
return custom_models || []
}

View File

@@ -0,0 +1,83 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import type {
Credential,
CustomModelCredential,
ModelLoadBalancingConfig,
ModelProvider,
} from '../../declarations'
import {
genModelNameFormSchema,
genModelTypeFormSchema,
} from '../../utils'
import { FormTypeEnum } from '@/app/components/base/form/types'
export const useModelFormSchemas = (
provider: ModelProvider,
providerFormSchemaPredefined: boolean,
credentials?: Record<string, any>,
credential?: Credential,
model?: CustomModelCredential,
draftConfig?: ModelLoadBalancingConfig,
) => {
const { t } = useTranslation()
const {
provider_credential_schema,
supported_model_types,
model_credential_schema,
} = provider
const formSchemas = useMemo(() => {
const modelTypeSchema = genModelTypeFormSchema(supported_model_types)
const modelNameSchema = genModelNameFormSchema(model_credential_schema?.model)
if (!!model) {
modelTypeSchema.disabled = true
modelNameSchema.disabled = true
}
return providerFormSchemaPredefined
? provider_credential_schema.credential_form_schemas
: [
modelTypeSchema,
modelNameSchema,
...(draftConfig?.enabled ? [] : model_credential_schema.credential_form_schemas),
]
}, [
providerFormSchemaPredefined,
provider_credential_schema?.credential_form_schemas,
supported_model_types,
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 [
authorizationNameSchema,
...formSchemas,
]
}, [formSchemas, t])
const formValues = useMemo(() => {
let result = {}
if (credential) {
result = { ...result, __authorization_name__: credential?.credential_name }
if (credentials)
result = { ...result, ...credentials }
}
if (model)
result = { ...result, __model_name: model?.model, __model_type: model?.model_type }
return result
}, [credentials, credential, model])
return {
formSchemas: formSchemasWithAuthorizationName,
formValues,
}
}