Release/e-1.8.1 (#25613)

Co-authored-by: zxhlyh <jasonapring2015@outlook.com>
Co-authored-by: GareArc <chen4851@purdue.edu>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: hjlarry <hjlarry@163.com>
This commit is contained in:
Garfield Dai 2025-09-15 14:49:23 +08:00 committed by GitHub
parent bb5b8d2902
commit 88d5e27fe8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 109 additions and 30 deletions

View File

@ -0,0 +1,33 @@
"""Add credential status for provider table
Revision ID: cf7c38a32b2d
Revises: c20211f18133
Create Date: 2025-09-11 15:37:17.771298
"""
from alembic import op
import models as models
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'cf7c38a32b2d'
down_revision = 'c20211f18133'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('providers', schema=None) as batch_op:
batch_op.add_column(sa.Column('credential_status', sa.String(length=20), server_default=sa.text("'active'::character varying"), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('providers', schema=None) as batch_op:
batch_op.drop_column('credential_status')
# ### end Alembic commands ###

View File

@ -9,9 +9,9 @@ from services.errors.base import BaseServiceError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PluginCredentialType(enum.IntEnum): class PluginCredentialType(enum.Enum):
MODEL = enum.auto() MODEL = 0 # must be 0 for API contract compatibility
TOOL = enum.auto() TOOL = 1 # must be 1 for API contract compatibility
def to_number(self): def to_number(self):
return self.value return self.value

View File

@ -92,10 +92,10 @@ const CredentialItem = ({
) )
} }
{ {
showAction && ( showAction && !credential.from_enterprise && (
<div className='ml-2 hidden shrink-0 items-center group-hover:flex'> <div className='ml-2 hidden shrink-0 items-center group-hover:flex'>
{ {
!disableEdit && !credential.not_allowed_to_use && !credential.from_enterprise && ( !disableEdit && !credential.not_allowed_to_use && (
<Tooltip popupContent={t('common.operation.edit')}> <Tooltip popupContent={t('common.operation.edit')}>
<ActionButton <ActionButton
disabled={disabled} disabled={disabled}
@ -110,7 +110,7 @@ const CredentialItem = ({
) )
} }
{ {
!disableDelete && !credential.from_enterprise && ( !disableDelete && (
<Tooltip popupContent={disableDeleteWhenSelected ? disableDeleteTip : t('common.operation.delete')}> <Tooltip popupContent={disableDeleteWhenSelected ? disableDeleteTip : t('common.operation.delete')}>
<ActionButton <ActionButton
className='hover:bg-transparent' className='hover:bg-transparent'

View File

@ -37,51 +37,57 @@ const SwitchCredentialInLoadBalancing = ({
onRemove, onRemove,
}: SwitchCredentialInLoadBalancingProps) => { }: SwitchCredentialInLoadBalancingProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const notAllowCustomCredential = provider.allow_custom_token === false
const handleItemClick = useCallback((credential: Credential) => { const handleItemClick = useCallback((credential: Credential) => {
setCustomModelCredential(credential) setCustomModelCredential(credential)
}, [setCustomModelCredential]) }, [setCustomModelCredential])
const renderTrigger = useCallback(() => { const renderTrigger = useCallback(() => {
const selectedCredentialId = customModelCredential?.credential_id const selectedCredentialId = customModelCredential?.credential_id
const authRemoved = !selectedCredentialId && !!credentials?.length const currentCredential = credentials?.find(c => c.credential_id === selectedCredentialId)
const empty = !credentials?.length
const authRemoved = selectedCredentialId && !currentCredential && !empty
const unavailable = currentCredential?.not_allowed_to_use
let color = 'green' let color = 'green'
if (authRemoved && !customModelCredential?.not_allowed_to_use) if (authRemoved || unavailable)
color = 'red' color = 'red'
if (customModelCredential?.not_allowed_to_use)
color = 'gray'
const Item = ( const Item = (
<Button <Button
variant='secondary' variant='secondary'
className={cn( className={cn(
'shrink-0 space-x-1', 'shrink-0 space-x-1',
authRemoved && 'text-components-button-destructive-secondary-text', (authRemoved || unavailable) && 'text-components-button-destructive-secondary-text',
customModelCredential?.not_allowed_to_use && 'cursor-not-allowed opacity-50', empty && 'cursor-not-allowed opacity-50',
)} )}
> >
<Indicator
className='mr-2'
color={color as any}
/>
{ {
authRemoved && !customModelCredential?.not_allowed_to_use && t('common.modelProvider.auth.authRemoved') !empty && (
<Indicator
className='mr-2'
color={color as any}
/>
)
} }
{ {
!authRemoved && customModelCredential?.not_allowed_to_use && t('plugin.auth.credentialUnavailable') authRemoved && t('common.modelProvider.auth.authRemoved')
} }
{ {
!authRemoved && !customModelCredential?.not_allowed_to_use && customModelCredential?.credential_name (unavailable || empty) && t('plugin.auth.credentialUnavailableInButton')
} }
{ {
customModelCredential?.from_enterprise && ( !authRemoved && !unavailable && !empty && customModelCredential?.credential_name
}
{
currentCredential?.from_enterprise && (
<Badge className='ml-2'>Enterprise</Badge> <Badge className='ml-2'>Enterprise</Badge>
) )
} }
<RiArrowDownSLine className='h-4 w-4' /> <RiArrowDownSLine className='h-4 w-4' />
</Button> </Button>
) )
if (customModelCredential?.not_allowed_to_use) { if (empty && notAllowCustomCredential) {
return ( return (
<Tooltip <Tooltip
asChild asChild
@ -92,7 +98,7 @@ const SwitchCredentialInLoadBalancing = ({
) )
} }
return Item return Item
}, [customModelCredential, t, credentials]) }, [customModelCredential, t, credentials, notAllowCustomCredential])
return ( return (
<Authorized <Authorized
@ -123,6 +129,7 @@ const SwitchCredentialInLoadBalancing = ({
enableAddModelCredential enableAddModelCredential
showItemSelectedIcon showItemSelectedIcon
popupTitle={t('common.modelProvider.auth.modelCredentials')} popupTitle={t('common.modelProvider.auth.modelCredentials')}
triggerOnlyOpenModal={!credentials?.length}
/> />
) )
} }

View File

@ -114,7 +114,7 @@ const ModelModal: FC<ModelModalProps> = ({
const formRef1 = useRef<FormRefObject>(null) const formRef1 = useRef<FormRefObject>(null)
const [selectedCredential, setSelectedCredential] = useState<Credential & { addNewCredential?: boolean } | undefined>() const [selectedCredential, setSelectedCredential] = useState<Credential & { addNewCredential?: boolean } | undefined>()
const formRef2 = useRef<FormRefObject>(null) const formRef2 = useRef<FormRefObject>(null)
const isEditMode = !!Object.keys(formValues).filter((key) => { const isEditMode = !!credential && !!Object.keys(formSchemasValue || {}).filter((key) => {
return key !== '__model_name' && key !== '__model_type' && !!formValues[key] return key !== '__model_name' && key !== '__model_type' && !!formValues[key]
}).length && isCurrentWorkspaceManager }).length && isCurrentWorkspaceManager
@ -376,16 +376,16 @@ const ModelModal: FC<ModelModalProps> = ({
<a <a
href={provider.help?.url[language] || provider.help?.url.en_US} href={provider.help?.url[language] || provider.help?.url.en_US}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
className='system-xs-regular mt-2 inline-flex items-center text-text-accent' className='system-xs-regular mt-2 inline-block align-middle text-text-accent'
onClick={e => !provider.help.url && e.preventDefault()} onClick={e => !provider.help.url && e.preventDefault()}
> >
{provider.help.title?.[language] || provider.help.url[language] || provider.help.title?.en_US || provider.help.url.en_US} {provider.help.title?.[language] || provider.help.url[language] || provider.help.title?.en_US || provider.help.url.en_US}
<LinkExternal02 className='ml-1 h-3 w-3' /> <LinkExternal02 className='ml-1 mt-[-2px] inline-block h-3 w-3' />
</a> </a>
) )
: <div /> : <div />
} }
<div className='flex items-center justify-end space-x-2'> <div className='ml-2 flex items-center justify-end space-x-2'>
{ {
isEditMode && ( isEditMode && (
<Button <Button

View File

@ -36,14 +36,22 @@ const AuthorizedInNode = ({
disabled, disabled,
invalidPluginCredentialInfo, invalidPluginCredentialInfo,
notAllowCustomCredential, notAllowCustomCredential,
} = usePluginAuth(pluginPayload, isOpen || !!credentialId) } = usePluginAuth(pluginPayload, true)
const renderTrigger = useCallback((open?: boolean) => { const renderTrigger = useCallback((open?: boolean) => {
let label = '' let label = ''
let removed = false let removed = false
let unavailable = false let unavailable = false
let color = 'green' let color = 'green'
let defaultUnavailable = false
if (!credentialId) { if (!credentialId) {
label = t('plugin.auth.workspaceDefault') label = t('plugin.auth.workspaceDefault')
const defaultCredential = credentials.find(c => c.is_default)
if (defaultCredential?.not_allowed_to_use) {
color = 'gray'
defaultUnavailable = true
}
} }
else { else {
const credential = credentials.find(c => c.id === credentialId) const credential = credentials.find(c => c.id === credentialId)
@ -63,6 +71,7 @@ const AuthorizedInNode = ({
open && !removed && 'bg-components-button-ghost-bg-hover', open && !removed && 'bg-components-button-ghost-bg-hover',
removed && 'bg-transparent text-text-destructive', removed && 'bg-transparent text-text-destructive',
)} )}
variant={(defaultUnavailable || unavailable) ? 'ghost' : 'secondary'}
> >
<Indicator <Indicator
className='mr-1.5' className='mr-1.5'
@ -70,7 +79,12 @@ const AuthorizedInNode = ({
/> />
{label} {label}
{ {
unavailable && t('plugin.auth.unavailable') (unavailable || defaultUnavailable) && (
<>
&nbsp;
{t('plugin.auth.unavailable')}
</>
)
} }
<RiArrowDownSLine <RiArrowDownSLine
className={cn( className={cn(
@ -81,6 +95,7 @@ const AuthorizedInNode = ({
</Button> </Button>
) )
}, [credentialId, credentials, t]) }, [credentialId, credentials, t])
const defaultUnavailable = credentials.find(c => c.is_default)?.not_allowed_to_use
const extraAuthorizationItems: Credential[] = [ const extraAuthorizationItems: Credential[] = [
{ {
id: '__workspace_default__', id: '__workspace_default__',
@ -88,6 +103,7 @@ const AuthorizedInNode = ({
provider: '', provider: '',
is_default: !credentialId, is_default: !credentialId,
isWorkspaceDefault: true, isWorkspaceDefault: true,
not_allowed_to_use: defaultUnavailable,
}, },
] ]
const handleAuthorizationItemClick = useCallback((id: string) => { const handleAuthorizationItemClick = useCallback((id: string) => {

View File

@ -174,6 +174,7 @@ const Authorized = ({
} }
}, [updatePluginCredential, notify, t, handleSetDoingAction, onUpdate]) }, [updatePluginCredential, notify, t, handleSetDoingAction, onUpdate])
const unavailableCredentials = credentials.filter(credential => credential.not_allowed_to_use) const unavailableCredentials = credentials.filter(credential => credential.not_allowed_to_use)
const unavailableCredential = credentials.find(credential => credential.not_allowed_to_use && credential.is_default)
return ( return (
<> <>
@ -197,7 +198,7 @@ const Authorized = ({
'w-full', 'w-full',
isOpen && 'bg-components-button-secondary-bg-hover', isOpen && 'bg-components-button-secondary-bg-hover',
)}> )}>
<Indicator className='mr-2' /> <Indicator className='mr-2' color={unavailableCredential ? 'gray' : 'green'} />
{credentials.length}&nbsp; {credentials.length}&nbsp;
{ {
credentials.length > 1 credentials.length > 1

View File

@ -2,6 +2,7 @@
import { useGlobalPublicStore } from '@/context/global-public-context' import { useGlobalPublicStore } from '@/context/global-public-context'
import { useFavicon, useTitle } from 'ahooks' import { useFavicon, useTitle } from 'ahooks'
import { basePath } from '@/utils/var' import { basePath } from '@/utils/var'
import { useEffect } from 'react'
export default function useDocumentTitle(title: string) { export default function useDocumentTitle(title: string) {
const isPending = useGlobalPublicStore(s => s.isGlobalPending) const isPending = useGlobalPublicStore(s => s.isGlobalPending)
@ -20,5 +21,24 @@ export default function useDocumentTitle(title: string) {
} }
} }
useTitle(titleStr) useTitle(titleStr)
useEffect(() => {
let apple: HTMLLinkElement | null = null
if (systemFeatures.branding.favicon) {
document
.querySelectorAll(
'link[rel=\'icon\'], link[rel=\'shortcut icon\'], link[rel=\'apple-touch-icon\'], link[rel=\'mask-icon\']',
)
.forEach(n => n.parentNode?.removeChild(n))
apple = document.createElement('link')
apple.rel = 'apple-touch-icon'
apple.href = systemFeatures.branding.favicon
document.head.appendChild(apple)
}
return () => {
apple?.remove()
}
}, [systemFeatures.branding.favicon])
useFavicon(favicon) useFavicon(favicon)
} }

View File

@ -298,6 +298,7 @@ const translation = {
clientInfo: 'As no system client secrets found for this tool provider, setup it manually is required, for redirect_uri, please use', clientInfo: 'As no system client secrets found for this tool provider, setup it manually is required, for redirect_uri, please use',
oauthClient: 'OAuth Client', oauthClient: 'OAuth Client',
credentialUnavailable: 'Credentials currently unavailable. Please contact admin.', credentialUnavailable: 'Credentials currently unavailable. Please contact admin.',
credentialUnavailableInButton: 'Credential unavailable',
customCredentialUnavailable: 'Custom credentials currently unavailable', customCredentialUnavailable: 'Custom credentials currently unavailable',
unavailable: 'Unavailable', unavailable: 'Unavailable',
}, },

View File

@ -298,6 +298,7 @@ const translation = {
clientInfo: '由于未找到此工具提供者的系统客户端密钥,因此需要手动设置,对于 redirect_uri请使用', clientInfo: '由于未找到此工具提供者的系统客户端密钥,因此需要手动设置,对于 redirect_uri请使用',
oauthClient: 'OAuth 客户端', oauthClient: 'OAuth 客户端',
credentialUnavailable: '自定义凭据当前不可用,请联系管理员。', credentialUnavailable: '自定义凭据当前不可用,请联系管理员。',
credentialUnavailableInButton: '凭据不可用',
customCredentialUnavailable: '自定义凭据当前不可用', customCredentialUnavailable: '自定义凭据当前不可用',
unavailable: '不可用', unavailable: '不可用',
}, },