Refactor/model credential (#24994)

This commit is contained in:
zxhlyh 2025-09-03 13:36:59 +08:00 • committed by GitHub
commit 9e125e2029
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1226 additions and 596 deletions

View file

@ -2,18 +2,17 @@ import {
memo,
useCallback,
} from 'react'
import { RiAddLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import CredentialItem from './credential-item'
import type {
Credential,
CustomModel,
CustomModelCredential,
ModelProvider,
} from '../../declarations'
import Button from '@/app/components/base/button'
import Tooltip from '@/app/components/base/tooltip'
import ModelIcon from '../../model-icon'
type AuthorizedItemProps = {
provider: ModelProvider
model?: CustomModelCredential
title?: string
disabled?: boolean
@ -25,8 +24,12 @@ type AuthorizedItemProps = {
onItemClick?: (credential: Credential, model?: CustomModel) => void
enableAddModelCredential?: boolean
notAllowCustomCredential?: boolean
showModelTitle?: boolean
disableDeleteButShowAction?: boolean
disableDeleteTip?: string
}
export const AuthorizedItem = ({
provider,
model,
title,
credentials,
@ -36,10 +39,10 @@ export const AuthorizedItem = ({
showItemSelectedIcon,
selectedCredentialId,
onItemClick,
enableAddModelCredential,
notAllowCustomCredential,
showModelTitle,
disableDeleteButShowAction,
disableDeleteTip,
}: AuthorizedItemProps) => {
const { t } = useTranslation()
const handleEdit = useCallback((credential?: Credential) => {
onEdit?.(credential, model)
}, [onEdit, model])
@ -52,34 +55,29 @@ export const AuthorizedItem = ({
return (
<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 grow truncate text-text-primary'
title={title ?? model?.model}
>
{title ?? model?.model}
</div>
{
enableAddModelCredential && !notAllowCustomCredential && (
<Tooltip
asChild
popupContent={t('common.modelProvider.auth.addModelCredential')}
{
showModelTitle && (
<div
className='flex h-9 items-center px-2'
>
{
model?.model && (
<ModelIcon
className='mr-1 h-5 w-5 shrink-0'
provider={provider}
modelName={model.model}
/>
)
}
<div
className='system-md-medium mx-1 grow truncate text-text-primary'
title={title ?? model?.model}
>
<Button
className='h-6 w-6 shrink-0 rounded-full p-0'
size='small'
variant='secondary-accent'
onClick={() => handleEdit?.()}
>
<RiAddLine className='h-4 w-4' />
</Button>
</Tooltip>
)
}
</div>
{title ?? model?.model}
</div>
</div>
)
}
{
credentials.map(credential => (
<CredentialItem
@ -91,6 +89,8 @@ export const AuthorizedItem = ({
showSelectedIcon={showItemSelectedIcon}
selectedCredentialId={selectedCredentialId}
onItemClick={handleItemClick}
disableDeleteButShowAction={disableDeleteButShowAction}
disableDeleteTip={disableDeleteTip}
/>
))
}

View file

@ -24,6 +24,8 @@ type CredentialItemProps = {
disableRename?: boolean
disableEdit?: boolean
disableDelete?: boolean
disableDeleteButShowAction?: boolean
disableDeleteTip?: string
showSelectedIcon?: boolean
selectedCredentialId?: string
}
@ -36,6 +38,8 @@ const CredentialItem = ({
disableRename,
disableEdit,
disableDelete,
disableDeleteButShowAction,
disableDeleteTip,
showSelectedIcon,
selectedCredentialId,
}: CredentialItemProps) => {
@ -43,6 +47,9 @@ const CredentialItem = ({
const showAction = useMemo(() => {
return !(disableRename && disableEdit && disableDelete)
}, [disableRename, disableEdit, disableDelete])
const disableDeleteWhenSelected = useMemo(() => {
return disableDeleteButShowAction && selectedCredentialId === credential.credential_id
}, [disableDeleteButShowAction, selectedCredentialId, credential.credential_id])
const Item = (
<div
@ -104,16 +111,21 @@ const CredentialItem = ({
}
{
!disableDelete && !credential.from_enterprise && (
<Tooltip popupContent={t('common.operation.delete')}>
<Tooltip popupContent={disableDeleteWhenSelected ? disableDeleteTip : t('common.operation.delete')}>
<ActionButton
className='hover:bg-transparent'
disabled={disabled}
onClick={(e) => {
if (disabled || disableDeleteWhenSelected)
return
e.stopPropagation()
onDelete?.(credential)
}}
>
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary hover:text-text-destructive' />
<RiDeleteBinLine className={cn(
'h-4 w-4 text-text-tertiary',
!disableDeleteWhenSelected && 'hover:text-text-destructive',
disableDeleteWhenSelected && 'opacity-50',
)} />
</ActionButton>
</Tooltip>
)

View file

@ -1,12 +1,11 @@
import {
Fragment,
memo,
useCallback,
useMemo,
useState,
} from 'react'
import {
RiAddLine,
RiEqualizer2Line,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import {
@ -25,6 +24,7 @@ import type {
Credential,
CustomConfigurationModelFixedFields,
CustomModel,
ModelModalModeEnum,
ModelProvider,
} from '../../declarations'
import { useAuth } from '../hooks'
@ -34,15 +34,20 @@ type AuthorizedProps = {
provider: ModelProvider,
configurationMethod: ConfigurationMethodEnum,
currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields,
isModelCredential?: boolean
authParams?: {
isModelCredential?: boolean
onUpdate?: (newPayload?: any, formValues?: Record<string, any>) => void
onRemove?: (credentialId: string) => void
mode?: ModelModalModeEnum
}
items: {
title?: string
model?: CustomModel
selectedCredential?: Credential
credentials: Credential[]
}[]
selectedCredential?: Credential
disabled?: boolean
renderTrigger?: (open?: boolean) => React.ReactNode
renderTrigger: (open?: boolean) => React.ReactNode
isOpen?: boolean
onOpenChange?: (open: boolean) => void
offset?: PortalToFollowElemOptions['offset']
@ -50,18 +55,22 @@ type AuthorizedProps = {
triggerPopupSameWidth?: boolean
popupClassName?: string
showItemSelectedIcon?: boolean
onUpdate?: () => void
onItemClick?: (credential: Credential, model?: CustomModel) => void
enableAddModelCredential?: boolean
bottomAddModelCredentialText?: string
triggerOnlyOpenModal?: boolean
hideAddAction?: boolean
disableItemClick?: boolean
popupTitle?: string
showModelTitle?: boolean
disableDeleteButShowAction?: boolean
disableDeleteTip?: string
}
const Authorized = ({
provider,
configurationMethod,
currentCustomConfigurationModelFixedFields,
items,
isModelCredential,
selectedCredential,
authParams,
disabled,
renderTrigger,
isOpen,
@ -71,10 +80,14 @@ const Authorized = ({
triggerPopupSameWidth = false,
popupClassName,
showItemSelectedIcon,
onUpdate,
onItemClick,
enableAddModelCredential,
bottomAddModelCredentialText,
triggerOnlyOpenModal,
hideAddAction,
disableItemClick,
popupTitle,
showModelTitle,
disableDeleteButShowAction,
disableDeleteTip,
}: AuthorizedProps) => {
const { t } = useTranslation()
const [isLocalOpen, setIsLocalOpen] = useState(false)
@ -85,6 +98,12 @@ const Authorized = ({
setIsLocalOpen(open)
}, [onOpenChange])
const {
isModelCredential,
onUpdate,
onRemove,
mode,
} = authParams || {}
const {
openConfirmDelete,
closeConfirmDelete,
@ -93,7 +112,17 @@ const Authorized = ({
handleConfirmDelete,
deleteCredentialId,
handleOpenModal,
} = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields, isModelCredential, onUpdate)
} = useAuth(
provider,
configurationMethod,
currentCustomConfigurationModelFixedFields,
{
isModelCredential,
onUpdate,
onRemove,
mode,
},
)
const handleEdit = useCallback((credential?: Credential, model?: CustomModel) => {
handleOpenModal(credential, model)
@ -101,28 +130,18 @@ const Authorized = ({
}, [handleOpenModal, setMergedIsOpen])
const handleItemClick = useCallback((credential: Credential, model?: CustomModel) => {
if (disableItemClick)
return
if (onItemClick)
onItemClick(credential, model)
else
handleActiveCredential(credential, model)
setMergedIsOpen(false)
}, [handleActiveCredential, onItemClick, setMergedIsOpen])
}, [handleActiveCredential, onItemClick, setMergedIsOpen, disableItemClick])
const notAllowCustomCredential = provider.allow_custom_token === false
const Trigger = useMemo(() => {
const Item = (
<Button
className='grow'
size='small'
>
<RiEqualizer2Line className='mr-1 h-3.5 w-3.5' />
{t('common.operation.config')}
</Button>
)
return Item
}, [t])
return (
<>
<PortalToFollowElem
@ -134,44 +153,60 @@ const Authorized = ({
>
<PortalToFollowElemTrigger
onClick={() => {
if (triggerOnlyOpenModal) {
handleOpenModal()
return
}
setMergedIsOpen(!mergedIsOpen)
}}
asChild
>
{
renderTrigger
? renderTrigger(mergedIsOpen)
: Trigger
}
{renderTrigger(mergedIsOpen)}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[100]'>
<div className={cn(
'w-[360px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg',
'w-[360px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]',
popupClassName,
)}>
{
popupTitle && (
<div className='system-xs-medium px-3 pb-0.5 pt-[10px] text-text-tertiary'>
{popupTitle}
</div>
)
}
<div className='max-h-[304px] overflow-y-auto'>
{
items.map((item, index) => (
<AuthorizedItem
key={index}
title={item.title}
model={item.model}
credentials={item.credentials}
disabled={disabled}
onDelete={openConfirmDelete}
onEdit={handleEdit}
showItemSelectedIcon={showItemSelectedIcon}
selectedCredentialId={selectedCredential?.credential_id}
onItemClick={handleItemClick}
enableAddModelCredential={enableAddModelCredential}
notAllowCustomCredential={notAllowCustomCredential}
/>
<Fragment key={index}>
<AuthorizedItem
provider={provider}
title={item.title}
model={item.model}
credentials={item.credentials}
disabled={disabled}
onDelete={openConfirmDelete}
disableDeleteButShowAction={disableDeleteButShowAction}
disableDeleteTip={disableDeleteTip}
onEdit={handleEdit}
showItemSelectedIcon={showItemSelectedIcon}
selectedCredentialId={item.selectedCredential?.credential_id}
onItemClick={handleItemClick}
showModelTitle={showModelTitle}
/>
{
index !== items.length - 1 && (
<div className='h-[1px] bg-divider-subtle'></div>
)
}
</Fragment>
))
}
</div>
<div className='h-[1px] bg-divider-subtle'></div>
{
isModelCredential && !notAllowCustomCredential && (
isModelCredential && !notAllowCustomCredential && !hideAddAction && (
<div
onClick={() => handleEdit(
undefined,
@ -182,15 +217,15 @@ const Authorized = ({
}
: undefined,
)}
className='system-xs-medium flex h-[30px] cursor-pointer items-center px-3 text-text-accent-light-mode-only'
className='system-xs-medium flex h-[40px] cursor-pointer items-center px-3 text-text-accent-light-mode-only'
>
<RiAddLine className='mr-1 h-4 w-4' />
{bottomAddModelCredentialText ?? t('common.modelProvider.auth.addModelCredential')}
{t('common.modelProvider.auth.addModelCredential')}
</div>
)
}
{
!isModelCredential && !notAllowCustomCredential && (
!isModelCredential && !notAllowCustomCredential && !hideAddAction && (
<div className='p-2'>
<Button
onClick={() => handleEdit()}