enterprise model credential

This commit is contained in:
zxhlyh
2025-08-20 15:12:20 +08:00
parent ca0d04b841
commit b3e5a4ecb7
21 changed files with 298 additions and 113 deletions

View File

@ -187,7 +187,7 @@ export type Credential = {
credential_id: string
credential_name?: string
from_enterprise?: boolean
allowed_to_use?: boolean
not_allowed_to_use?: boolean
}
export type CustomModel = {
@ -242,6 +242,7 @@ export type ModelProvider = {
current_quota_type: CurrentSystemQuotaTypeEnum
quota_configurations: QuotaConfiguration[]
}
allow_custom_token?: boolean
}
export type Model = {

View File

@ -13,6 +13,7 @@ import type {
ModelProvider,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import Tooltip from '@/app/components/base/tooltip'
type AddCredentialInLoadBalancingProps = {
provider: ModelProvider
@ -35,11 +36,13 @@ const AddCredentialInLoadBalancing = ({
available_credentials,
} = modelCredential
const customModel = configurationMethod === ConfigurationMethodEnum.customizableModel
const notAllowCustomCredential = provider.allow_custom_token === false
const renderTrigger = useCallback((open?: boolean) => {
return (
const Item = (
<div className={cn(
'system-sm-medium flex h-8 items-center rounded-lg px-3 text-text-accent hover:bg-state-base-hover',
open && 'bg-state-base-hover',
notAllowCustomCredential && 'cursor-not-allowed opacity-50',
)}>
<RiAddLine className='mr-2 h-4 w-4' />
{
@ -49,7 +52,19 @@ const AddCredentialInLoadBalancing = ({
}
</div>
)
}, [])
if (notAllowCustomCredential) {
return (
<Tooltip
asChild
popupContent={t('plugin.auth.credentialUnavailable')}
>
{Item}
</Tooltip>
)
}
return Item
}, [notAllowCustomCredential, t, customModel])
return (
<Authorized

View File

@ -21,6 +21,7 @@ import {
useCustomModels,
} from './hooks'
import cn from '@/utils/classnames'
import Tooltip from '@/app/components/base/tooltip'
type AddCustomModelProps = {
provider: ModelProvider,
@ -41,8 +42,9 @@ const AddCustomModel = ({
const handleClick = useCallback(() => {
handleOpenModal()
}, [handleOpenModal])
const notAllowCustomCredential = provider.allow_custom_token === false
const ButtonComponent = useMemo(() => {
return (
const Item = (
<Button
variant='ghost-accent'
size='small'
@ -52,10 +54,21 @@ const AddCustomModel = ({
{t('common.modelProvider.addModel')}
</Button>
)
}, [handleClick])
if (notAllowCustomCredential) {
return (
<Tooltip
asChild
popupContent={t('plugin.auth.credentialUnavailable')}
>
{Item}
</Tooltip>
)
}
return Item
}, [handleClick, notAllowCustomCredential, t])
const renderTrigger = useCallback((open?: boolean) => {
return (
const Item = (
<Button
variant='ghost'
size='small'
@ -65,7 +78,18 @@ const AddCustomModel = ({
{t('common.modelProvider.addModel')}
</Button>
)
}, [t])
if (notAllowCustomCredential) {
return (
<Tooltip
asChild
popupContent={t('plugin.auth.credentialUnavailable')}
>
{Item}
</Tooltip>
)
}
return Item
}, [notAllowCustomCredential, t])
if (noModels)
return ButtonComponent

View File

@ -24,6 +24,7 @@ type AuthorizedItemProps = {
credentials: Credential[]
onItemClick?: (credential: Credential, model?: CustomModel) => void
enableAddModelCredential?: boolean
notAllowCustomCredential?: boolean
}
export const AuthorizedItem = ({
model,
@ -36,6 +37,7 @@ export const AuthorizedItem = ({
selectedCredentialId,
onItemClick,
enableAddModelCredential,
notAllowCustomCredential,
}: AuthorizedItemProps) => {
const { t } = useTranslation()
const handleEdit = useCallback((credential?: Credential) => {
@ -61,7 +63,7 @@ export const AuthorizedItem = ({
{title ?? model?.model}
</div>
{
enableAddModelCredential && (
enableAddModelCredential && !notAllowCustomCredential && (
<Tooltip
asChild
popupContent={t('common.modelProvider.auth.addModelCredential')}

View File

@ -44,13 +44,16 @@ const CredentialItem = ({
return !(disableRename && disableEdit && disableDelete)
}, [disableRename, disableEdit, disableDelete])
return (
const Item = (
<div
key={credential.credential_id}
className={cn(
'group flex h-8 items-center rounded-lg p-1 hover:bg-state-base-hover',
(disabled || credential.not_allowed_to_use) && 'cursor-not-allowed opacity-50',
)}
onClick={() => {
if (disabled || credential.not_allowed_to_use)
return
onItemClick?.(credential)
}}
>
@ -85,7 +88,7 @@ const CredentialItem = ({
showAction && (
<div className='ml-2 hidden shrink-0 items-center group-hover:flex'>
{
!disableEdit && (
!disableEdit && !credential.not_allowed_to_use && !credential.from_enterprise && (
<Tooltip popupContent={t('common.operation.edit')}>
<ActionButton
disabled={disabled}
@ -100,7 +103,7 @@ const CredentialItem = ({
)
}
{
!disableDelete && (
!disableDelete && !credential.from_enterprise && (
<Tooltip popupContent={t('common.operation.delete')}>
<ActionButton
className='hover:bg-transparent'
@ -120,6 +123,15 @@ const CredentialItem = ({
}
</div>
)
if (credential.not_allowed_to_use) {
return (
<Tooltip popupContent={t('plugin.auth.customCredentialUnavailable')}>
{Item}
</Tooltip>
)
}
return Item
}
export default memo(CredentialItem)

View File

@ -1,6 +1,7 @@
import {
memo,
useCallback,
useMemo,
useState,
} from 'react'
import {
@ -28,6 +29,7 @@ import type {
} from '../../declarations'
import { useAuth } from '../hooks'
import AuthorizedItem from './authorized-item'
import Tooltip from '@/app/components/base/tooltip'
type AuthorizedProps = {
provider: ModelProvider,
@ -107,6 +109,33 @@ const Authorized = ({
setMergedIsOpen(false)
}, [handleActiveCredential, onItemClick, setMergedIsOpen])
const notAllowCustomCredential = provider.allow_custom_token === false
const Trigger = useMemo(() => {
const hasValidCredential = items.some(item => item.credentials.some(credential => !credential.not_allowed_to_use))
const Item = (
<Button
className='grow'
size='small'
disabled={notAllowCustomCredential && !hasValidCredential}
>
<RiEqualizer2Line className='mr-1 h-3.5 w-3.5' />
{t('common.operation.config')}
</Button>
)
if (notAllowCustomCredential && !hasValidCredential) {
return (
<Tooltip
asChild
popupContent={t('plugin.auth.credentialUnavailable')}
>
{Item}
</Tooltip>
)
}
return Item
}, [notAllowCustomCredential, t, items])
return (
<>
@ -118,21 +147,20 @@ const Authorized = ({
triggerPopupSameWidth={triggerPopupSameWidth}
>
<PortalToFollowElemTrigger
onClick={() => setMergedIsOpen(!mergedIsOpen)}
onClick={() => {
if (notAllowCustomCredential) {
const hasValidCredential = items.some(item => item.credentials.some(credential => !credential.not_allowed_to_use))
if (!hasValidCredential)
return
}
setMergedIsOpen(!mergedIsOpen)
}}
asChild
>
{
renderTrigger
? renderTrigger(mergedIsOpen)
: (
<Button
className='grow'
size='small'
>
<RiEqualizer2Line className='mr-1 h-3.5 w-3.5' />
{t('common.operation.config')}
</Button>
)
: Trigger
}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[100]'>
@ -155,13 +183,14 @@ const Authorized = ({
selectedCredentialId={selectedCredential?.credential_id}
onItemClick={handleItemClick}
enableAddModelCredential={enableAddModelCredential}
notAllowCustomCredential={notAllowCustomCredential}
/>
))
}
</div>
<div className='h-[1px] bg-divider-subtle'></div>
{
isModelCredential && (
isModelCredential && !notAllowCustomCredential && (
<div
onClick={() => handleEdit(
undefined,
@ -180,7 +209,7 @@ const Authorized = ({
)
}
{
!isModelCredential && (
!isModelCredential && !notAllowCustomCredential && (
<div className='p-2'>
<Button
onClick={() => handleEdit()}

View File

@ -22,7 +22,7 @@ const ConfigModel = ({
}: ConfigModelProps) => {
const { t } = useTranslation()
if (loadBalancingEnabled && loadBalancingInvalid && !credentialRemoved) {
if (loadBalancingInvalid) {
return (
<div
className='system-2xs-medium-uppercase relative flex h-[18px] items-center rounded-[5px] border border-text-warning bg-components-badge-bg-dimm px-1.5 text-text-warning'
@ -54,7 +54,7 @@ const ConfigModel = ({
)
}
{
!loadBalancingEnabled && !credentialRemoved && (
!loadBalancingEnabled && !credentialRemoved && !loadBalancingInvalid && (
<>
<RiEqualizer2Line className='mr-1 h-4 w-4' />
{t('common.operation.config')}
@ -62,7 +62,7 @@ const ConfigModel = ({
)
}
{
loadBalancingEnabled && !loadBalancingInvalid && !credentialRemoved && (
loadBalancingEnabled && !credentialRemoved && !loadBalancingInvalid && (
<>
<RiScales3Line className='mr-1 h-4 w-4' />
{t('common.modelProvider.auth.configLoadBalancing')}

View File

@ -17,6 +17,7 @@ import type {
import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import Authorized from './authorized'
import { useAuth, useCredentialStatus } from './hooks'
import Tooltip from '@/app/components/base/tooltip'
type ConfigProviderProps = {
provider: ModelProvider,
@ -39,24 +40,36 @@ const ConfigProvider = ({
current_credential_name,
available_credentials,
} = useCredentialStatus(provider)
const notAllowCustomCredential = provider.allow_custom_token === false
const handleClick = useCallback(() => {
if (!hasCredential)
if (!hasCredential && !notAllowCustomCredential)
handleOpenModal()
}, [handleOpenModal, hasCredential])
}, [handleOpenModal, hasCredential, notAllowCustomCredential])
const ButtonComponent = useMemo(() => {
return (
const Item = (
<Button
className='grow'
size='small'
onClick={handleClick}
variant={!authorized ? 'secondary-accent' : 'secondary'}
disabled={notAllowCustomCredential}
>
<RiEqualizer2Line className='mr-1 h-3.5 w-3.5' />
{t('common.operation.setup')}
</Button>
)
}, [handleClick, authorized])
if (notAllowCustomCredential) {
return (
<Tooltip
asChild
popupContent={t('plugin.auth.credentialUnavailable')}
>
{Item}
</Tooltip>
)
}
return Item
}, [handleClick, authorized, notAllowCustomCredential, t])
if (!hasCredential)
return ButtonComponent

View File

@ -65,7 +65,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onModifyLoadBalancing
>
</ModelName>
<div className='flex shrink-0 items-center'>
{modelLoadBalancingEnabled && !model.deprecated && model.load_balancing_enabled && (
{modelLoadBalancingEnabled && !model.deprecated && model.load_balancing_enabled && !model.has_invalid_load_balancing_configs && (
<Badge className='mr-1 h-[18px] w-[18px] items-center justify-center border-text-accent-secondary p-0'>
<Balance className='h-3 w-3 text-text-accent-secondary' />
</Badge>

View File

@ -125,15 +125,7 @@ const ModelLoadBalancingConfigs = ({
const validDraftConfigList = useMemo(() => {
if (!draftConfig)
return []
return draftConfig.configs.filter((config) => {
if (config.name === '__inherit__')
return true
if (config.credential_id)
return true
return false
})
return draftConfig.configs
}, [draftConfig])
if (!draftConfig)
@ -206,40 +198,50 @@ const ModelLoadBalancingConfigs = ({
{!isProviderManaged && (
<>
<div className='flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100'>
<span
className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-components-button-secondary-bg text-text-tertiary transition-colors hover:bg-components-button-secondary-bg-hover'
onClick={() => {
handleOpenModal(
provider,
configurationMethod,
currentCustomConfigurationModelFixedFields,
configurationMethod === ConfigurationMethodEnum.customizableModel,
(config.credential_id && config.name) ? {
credential_id: config.credential_id,
credential_name: config.name,
} : undefined,
model,
)
}}
>
<RiEqualizer2Line className='h-4 w-4' />
</span>
{
config.credential_id && (
<span
className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-components-button-secondary-bg text-text-tertiary transition-colors hover:bg-components-button-secondary-bg-hover'
onClick={() => {
handleOpenModal(
provider,
configurationMethod,
currentCustomConfigurationModelFixedFields,
configurationMethod === ConfigurationMethodEnum.customizableModel,
(config.credential_id && config.name) ? {
credential_id: config.credential_id,
credential_name: config.name,
} : undefined,
model,
)
}}
>
<RiEqualizer2Line className='h-4 w-4' />
</span>
)
}
<span
className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-components-button-secondary-bg text-text-tertiary transition-colors hover:bg-components-button-secondary-bg-hover'
onClick={() => updateConfigEntry(index, () => undefined)}
>
<RiDeleteBinLine className='h-4 w-4' />
</span>
<span className='mr-2 h-3 border-r border-r-divider-subtle' />
</div>
</>
)}
<Switch
defaultValue={Boolean(config.enabled)}
size='md'
className='justify-self-end'
onChange={value => toggleConfigEntryEnabled(index, value)}
/>
{
(config.credential_id || config.name === '__inherit__') && (
<>
<span className='mr-2 h-3 border-r border-r-divider-subtle' />
<Switch
defaultValue={Boolean(config.enabled)}
size='md'
className='justify-self-end'
onChange={value => toggleConfigEntryEnabled(index, value)}
/>
</>
)
}
</div>
</div>
)