model auth

This commit is contained in:
zxhlyh 2025-08-15 18:34:12 +08:00
parent 3f57e4a643
commit 2e28a64d38
26 changed files with 917 additions and 469 deletions

View File

@ -186,6 +186,22 @@ export type Credential = {
credential_name: string
}
export type CustomModel = {
model: string
model_type: ModelTypeEnum
}
export type CustomModelCredential = CustomModel & {
credentials?: Record<string, any>
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<string, any>
name: string
credential_id: string
}
export type ModelCredential = {
credentials: Record<string, any>
load_balancing: ModelLoadBalancingConfig
available_credentials: Credential[]
}

View File

@ -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)

View File

@ -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 (
<div className={cn(
@ -25,13 +33,9 @@ const AddCredentialInLoadBalancing = () => {
return (
<Authorized
credentials={[]}
pluginPayload={{
provider: '',
category: AuthCategory.model,
}}
canApiKey
offset={4}
provider={provider.provider}
renderTrigger={renderTrigger}
onSetup={onSetup}
/>
)
}

View File

@ -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 (
<Button
variant='ghost-accent'
size='small'
onClick={handleClick}
className={cn(noModels && 'text-text-accent')}
>
<RiAddCircleFill className='mr-1 h-3.5 w-3.5' />
{t('common.modelProvider.addModel')}
</Button>
)
}, [handleClick, noModels])
const renderTrigger = useCallback(() => {
return ButtonComponent
}, [ButtonComponent])
if (noModels)
return ButtonComponent
return (
<Authorized
provider={provider}
configurationMethod={ConfigurationMethodEnum.customizableModel}
items={models.map(model => ({
model,
credentials: model.available_model_credentials ?? [],
}))}
renderTrigger={renderTrigger}
/>
)
}
export default memo(AddCustomModel)

View File

@ -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 (
<PortalToFollowElem
placement='bottom-end'
offset={{
mainAxis: 4,
crossAxis: -4,
}}
open={open}
onOpenChange={setOpen}
>
<PortalToFollowElemTrigger>
<Button
variant='ghost-accent'
size='small'
>
<RiAddCircleFill className='mr-1 h-3.5 w-3.5' />
{t('common.modelProvider.addModel')}
</Button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='w-[360px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
<div className='p-1'>
<div className='flex h-9 items-center'>
<div className='h-5 w-5 shrink-0'></div>
<div
className='system-md-medium mx-1 truncate text-text-primary'
title='chat-finetune-01'
>
chat-finetune-01
</div>
<Tooltip
asChild
popupContent='Add model credential'
>
<Button
className='h-6 w-6 rounded-full p-0'
size='small'
variant='secondary-accent'
>
<RiAddLine className='h-4 w-4' />
</Button>
</Tooltip>
</div>
</div>
<div className='system-xs-medium flex h-10 cursor-pointer items-center border-t border-divider-subtle px-4 text-text-accent-light-mode-only'>
<RiAddLine className='mr-1 h-4 w-4' />
{t('common.modelProvider.addModel')}
</div>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default memo(AddModel)

View File

@ -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 (
<div className='p-1'>
{
model && (
<div
className='flex h-9 items-center'
>
<div className='h-5 w-5 shrink-0'></div>
<div
className='system-md-medium mx-1 truncate text-text-primary'
title={model.model}
>
{model.model}
</div>
<Tooltip
asChild
popupContent='Add model credential'
>
<Button
className='h-6 w-6 rounded-full p-0'
size='small'
variant='secondary-accent'
>
<RiAddLine className='h-4 w-4' />
</Button>
</Tooltip>
</div>
)
}
{
credentials.map(credential => (
<CredentialItem
key={credential.credential_id}
credential={credential}
disabled={disabled}
onDelete={onDelete}
onEdit={handleEdit}
onSetDefault={onSetDefault}
onItemClick={onItemClick}
showSelectedIcon={showItemSelectedIcon}
selectedCredentialId={selectedCredentialId}
disableSetDefault={disableSetDefault}
/>
))
}
</div>
)
}
export default memo(AuthorizedItem)

View File

@ -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)

View File

@ -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<string | null>(null)
const [deleteCredentialId, setDeleteCredentialId] = useState<string | null>(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,
)}>
<div className='max-h-[304px] overflow-y-auto py-1'>
<div className='max-h-[304px] overflow-y-auto'>
{
!!credentials.length && (
<div className='p-1'>
<div className={cn(
'system-xs-medium px-3 pb-0.5 pt-1 text-text-tertiary',
showItemSelectedIcon && 'pl-7',
)}>
API Keys
</div>
{
credentials.map(credential => (
<Item
key={credential.credential_id}
credential={credential}
disabled={disabled}
onDelete={openConfirm}
onEdit={handleOpenSetup}
onSetDefault={handleSetDefault}
onItemClick={onItemClick}
showSelectedIcon={showItemSelectedIcon}
selectedCredentialId={selectedCredentialId}
/>
))
}
</div>
)
items.map((item, index) => (
<AuthorizedItem
key={index}
model={item.model}
credentials={item.credentials}
disabled={disabled}
onDelete={openConfirmDelete}
onEdit={handleEdit}
onSetDefault={handleActiveCredential}
onItemClick={onItemClick}
showItemSelectedIcon={showItemSelectedIcon}
selectedCredentialId={selectedCredential?.credential_id}
disableSetDefault={disableSetDefault}
/>
))
}
</div>
<div className='h-[1px] bg-divider-subtle'></div>
<div className='p-2'>
<Button
onClick={() => handleOpenSetup()}
onClick={() => handleOpenModal()}
className='w-full'
>
add api key
@ -211,10 +160,10 @@ const Authorized = ({
deleteCredentialId && (
<Confirm
isShow
title={t('datasetDocuments.list.delete.title')}
title={t('common.modelProvider.confirmDelete')}
isDisabled={doingAction}
onCancel={closeConfirm}
onConfirm={handleConfirm}
onCancel={closeConfirmDelete}
onConfirm={handleConfirmDelete}
/>
)
}

View File

@ -0,0 +1,82 @@
import {
memo,
useCallback,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
RiEqualizer2Line,
} from '@remixicon/react'
import {
Button,
} from '@/app/components/base/button'
import type {
CustomConfigurationModelFixedFields,
ModelProvider,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import Authorized from './authorized'
import { useAuth, useCredentialStatus } from './hooks'
type ConfigProviderProps = {
provider: ModelProvider,
configurationMethod: ConfigurationMethodEnum,
currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields,
}
const ConfigProvider = ({
provider,
configurationMethod,
currentCustomConfigurationModelFixedFields,
}: ConfigProviderProps) => {
const { t } = useTranslation()
const {
handleOpenModal,
} = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields)
const {
hasCredential,
authorized,
current_credential_id,
current_credential_name,
available_credentials,
} = useCredentialStatus(provider)
const handleClick = useCallback(() => {
if (!hasCredential)
handleOpenModal()
}, [handleOpenModal, hasCredential])
const ButtonComponent = useMemo(() => {
return (
<Button
className='grow'
size='small'
onClick={handleClick}
variant={!authorized ? 'secondary-accent' : 'secondary'}
>
<RiEqualizer2Line className='mr-1 h-3.5 w-3.5' />
{t('common.operation.setup')}
</Button>
)
}, [handleClick, authorized])
if (!hasCredential)
return ButtonComponent
return (
<Authorized
provider={provider}
configurationMethod={ConfigurationMethodEnum.predefinedModel}
items={[
{
credentials: available_credentials ?? [],
},
]}
selectedCredential={{
credential_id: current_credential_id ?? '',
credential_name: current_credential_name ?? '',
}}
showItemSelectedIcon
/>
)
}
export default memo(ConfigProvider)

View File

@ -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'

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

View File

@ -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<string | null>(null)
const pendingOperationModel = useRef<CustomModel | null>(null)
const [deleteCredentialId, setDeleteCredentialId] = useState<string | null>(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<string, any>) => {
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,
}
}

View File

@ -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])
}

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

@ -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 = (

View File

@ -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'

View File

@ -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<SetStateAction<ModelLoadBalancingConfig | undefined>>
}
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 (
<Button
variant='secondary'
className='space-x-1'
className={cn(
'shrink-0 space-x-1',
authRemoved && 'text-components-button-destructive-secondary-text',
)}
>
<Indicator />
chat-enterprise
<Badge>enterprise</Badge>
<Indicator
className='mr-2'
color={authRemoved ? 'red' : 'green'}
/>
{
authRemoved ? t('common.model.authRemoved') : name
}
{
!authRemoved && (
<Badge>enterprise</Badge>
)
}
<RiArrowDownSLine className='h-4 w-4' />
</Button>
)
}, [])
}, [current_credential_name, t, draftConfig, available_credentials])
return (
<Authorized
credentials={[]}
pluginPayload={{
provider: '',
category: AuthCategory.model,
}}
canApiKey
placement='bottom-end'
offset={{
mainAxis: 4,
crossAxis: -8,
}}
provider={provider.provider}
credentials={available_credentials || []}
onSetup={handleSetup}
renderTrigger={renderTrigger}
onItemClick={handleItemClick}
disableSetDefault
/>
)
}

View File

@ -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<ModelModalProps> = ({
provider,
model,
configurateMethod,
currentCustomConfigurationModelFixedFields,
credential,
@ -68,134 +62,62 @@ const ModelModal: FC<ModelModalProps> = ({
}) => {
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<ModelLoadBalancingConfig>()
const originalConfigMap = useMemo(() => {
if (!originalConfig)
return {}
return originalConfig?.configs.reduce((prev, config) => {
if (config.id)
prev[config.id] = config
return prev
}, {} as Record<string, ModelLoadBalancingConfigEntry>)
}, [originalConfig])
useEffect(() => {
if (originalConfig && !draftConfig)
setDraftConfig(originalConfig)
}, [draftConfig, originalConfig])
const { formSchemas } = useModelFormSchemas(provider, providerFormSchemaPredefined, draftConfig)
const { formSchemas } = useModelFormSchemas(provider, providerFormSchemaPredefined)
const formRef = useRef<FormRefObject>(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<ModelModalProps> = ({
/>
)
}
{
!!draftConfig && (
<>
<div className='mb-4 mt-1 border-t-[0.5px] border-t-divider-regular' />
<ModelLoadBalancingConfigs withSwitch {...{
draftConfig,
setDraftConfig,
provider,
currentCustomConfigurationModelFixedFields,
configurationMethod: configurateMethod,
}} />
</>
)
}
</div>
<div className='sticky bottom-0 -mx-2 mt-2 flex flex-wrap items-center justify-between gap-y-2 bg-components-panel-bg px-2 pb-6 pt-4'>
@ -278,7 +186,7 @@ const ModelModal: FC<ModelModalProps> = ({
variant='warning'
size='large'
className='mr-2'
onClick={() => setShowConfirm(true)}
onClick={() => openConfirmDelete(credential?.credential_id, model)}
>
{t('common.operation.remove')}
</Button>
@ -295,11 +203,7 @@ const ModelModal: FC<ModelModalProps> = ({
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')}
</Button>
@ -322,12 +226,13 @@ const ModelModal: FC<ModelModalProps> = ({
</div>
</div>
{
showConfirm && (
deleteCredentialId && (
<Confirm
isShow
title={t('common.modelProvider.confirmDelete')}
isShow={showConfirm}
onCancel={() => setShowConfirm(false)}
onConfirm={handleRemove}
isDisabled={doingAction}
onCancel={closeConfirmDelete}
onConfirm={handleConfirmDelete}
/>
)
}

View File

@ -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 = ({
<Indicator className='shrink-0' color={authorized ? 'green' : 'red'} />
</div>
<div className='flex items-center gap-0.5'>
{
!hasCredential && (
<Button
className='grow'
size='small'
onClick={() => onSetup()}
variant={!authorized ? 'secondary-accent' : 'secondary'}
>
<RiEqualizer2Line className='mr-1 h-3.5 w-3.5' />
{t('common.operation.setup')}
</Button>
)
}
{
(hasCredential || authRemoved) && (
<Authorized
provider={provider.provider}
onSetup={onSetup}
credentials={available_credentials ?? []}
selectedCredentialId={current_credential_id}
showItemSelectedIcon
onUpdate={onUpdate}
/>
)
}
<ConfigProvider
provider={provider}
configurationMethod={ConfigurationMethodEnum.predefinedModel}
/>
{
systemConfig.enabled && isCustomConfigured && (
<PrioritySelector

View File

@ -28,7 +28,6 @@ import { useEventEmitterContextContext } from '@/context/event-emitter'
import { IS_CE_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context'
import cn from '@/utils/classnames'
import { useRefreshModel } from '../hooks'
export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
type ProviderAddedCardProps = {
@ -82,8 +81,6 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
getModelList(v.payload)
})
const { handleRefreshModel } = useRefreshModel()
return (
<div
className={cn(
@ -118,8 +115,6 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
{
showCredential && (
<CredentialPanel
onSetup={(credential?: Credential) => onOpenModal(ConfigurationMethodEnum.predefinedModel, undefined, credential)}
onUpdate={() => handleRefreshModel(provider, ConfigurationMethodEnum.predefinedModel, undefined)}
provider={provider}
/>
)

View File

@ -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<ModelListProps> = ({
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<ModelListProps> = ({
<RiArrowRightSLine className='mr-0.5 h-4 w-4 rotate-90' />
</span>
</span>
{/* {
isConfigurable && canSystemConfig && (
<span className='flex items-center'>
<Tab active='all' onSelect={() => {}} />
</span>
)
} */}
{
isConfigurable && isCurrentWorkspaceManager && (
<div className='flex grow justify-end'>
<AddModelButton onClick={() => onConfig()} />
<AddCustomModel
provider={provider}
configurationMethod={ConfigurationMethodEnum.customizableModel}
currentCustomConfigurationModelFixedFields={undefined}
models={customModels}
/>
</div>
)
}

View File

@ -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 = ({
</div>
)
})}
<div
className='mt-1 flex h-8 items-center px-3 text-[13px] font-medium text-primary-600'
onClick={() => toggleEntryModal()}
>
<div className='flex cursor-pointer items-center'>
<Plus02 className='mr-2 h-3 w-3' />{t('common.modelProvider.addConfig')}
</div>
</div>
<AddCredentialInLoadBalancing
provider={provider}
onSetup={() => toggleEntryModal()}
/>
</div>
)}
{

View File

@ -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
<div className='text-sm text-text-secondary'>{t('common.modelProvider.providerManaged')}</div>
<div className='text-xs text-text-tertiary'>{t('common.modelProvider.providerManagedDescription')}</div>
</div>
<SwitchCredentialInLoadBalancing
draftConfig={draftConfig}
setDraftConfig={setDraftConfig}
provider={provider}
/>
</div>
</div>

View File

@ -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<CredentialFormSchemaTextInput, 'label' | 'placeholder'>) => {
@ -198,5 +197,5 @@ export const genModelNameFormSchema = (model?: Pick<CredentialFormSchemaTextInpu
zh_Hans: '请输入模型名称',
en_US: 'Please enter model name',
},
} as CredentialFormSchemaTextInput
} as any
}

View File

@ -8,6 +8,7 @@ import type {
ConfigurationMethodEnum,
Credential,
CustomConfigurationModelFixedFields,
CustomModel,
ModelLoadBalancingConfigEntry,
ModelProvider,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
@ -81,6 +82,7 @@ export type ModelModalType = {
currentConfigurationMethod: ConfigurationMethodEnum
currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields
credential?: Credential
model?: CustomModel
}
export type LoadBalancingEntryModalType = ModelModalType & {
entry?: ModelLoadBalancingConfigEntry
@ -338,6 +340,7 @@ export const ModalContextProvider = ({
<ModelModal
provider={showModelModal.payload.currentProvider}
configurateMethod={showModelModal.payload.currentConfigurationMethod}
model={showModelModal.payload.model}
currentCustomConfigurationModelFixedFields={showModelModal.payload.currentCustomConfigurationModelFixedFields}
credential={showModelModal.payload.credential}
onCancel={handleCancelModelModal}

View File

@ -2,9 +2,13 @@ import {
del,
get,
post,
put,
} from './base'
import type {
ModelCredential,
ModelItem,
ModelTypeEnum,
ProviderCredential,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import {
useMutation,
@ -21,35 +25,114 @@ export const useModelProviderModelList = (provider: string) => {
})
}
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,
}),
})
}