Compare commits

..

2 Commits

Author SHA1 Message Date
WTW0313
f0a33866fb Merge branch 'main' into fix/empty-plugin-list 2025-09-11 16:50:31 +08:00
WTW0313
373f354a15 fix: enhance plugin fetching logic in ListWrapper component 2025-09-02 14:08:32 +08:00
19 changed files with 23 additions and 181 deletions

View File

@@ -175,22 +175,6 @@ class ModelProviderCredentialSwitchApi(Resource):
return {"result": "success"}
class ModelProviderCredentialCancelApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self, provider: str):
if not current_user.is_admin_or_owner:
raise Forbidden()
service = ModelProviderService()
service.cancel_provider_credential(
tenant_id=current_user.current_tenant_id,
provider=provider,
)
return {"result": "success"}
class ModelProviderValidateApi(Resource):
@setup_required
@login_required
@@ -305,9 +289,6 @@ api.add_resource(ModelProviderCredentialApi, "/workspaces/current/model-provider
api.add_resource(
ModelProviderCredentialSwitchApi, "/workspaces/current/model-providers/<path:provider>/credentials/switch"
)
api.add_resource(
ModelProviderCredentialCancelApi, "/workspaces/current/model-providers/<path:provider>/credentials/cancel"
)
api.add_resource(ModelProviderValidateApi, "/workspaces/current/model-providers/<path:provider>/credentials/validate")
api.add_resource(

View File

@@ -33,7 +33,6 @@ from core.plugin.entities.plugin import ModelProviderID
from extensions.ext_database import db
from libs.datetime_utils import naive_utc_now
from models.provider import (
CredentialStatus,
LoadBalancingModelConfig,
Provider,
ProviderCredential,
@@ -44,7 +43,6 @@ from models.provider import (
TenantPreferredModelProvider,
)
from services.enterprise.plugin_manager_service import PluginCredentialType
from services.entities.model_provider_entities import CustomConfigurationStatus
logger = logging.getLogger(__name__)
@@ -191,22 +189,6 @@ class ProviderConfiguration(BaseModel):
else SystemConfigurationStatus.QUOTA_EXCEEDED
)
def get_custom_configuration_status(self) -> Optional[CustomConfigurationStatus]:
"""
Get custom configuration status.
:return:
"""
if self.is_custom_configuration_available():
return CustomConfigurationStatus.ACTIVE
provider = self.custom_configuration.provider
if provider and hasattr(provider, "current_credential_status"):
status = provider.current_credential_status
if status:
return status
return CustomConfigurationStatus.NO_CONFIGURE
def is_custom_configuration_available(self) -> bool:
"""
Check custom configuration available.
@@ -661,7 +643,6 @@ class ProviderConfiguration(BaseModel):
self.switch_preferred_provider_type(provider_type=ProviderType.SYSTEM, session=session)
elif provider_record and provider_record.credential_id == credential_id:
provider_record.credential_id = None
provider_record.credential_status = CredentialStatus.REMOVED.value
provider_record.updated_at = naive_utc_now()
provider_model_credentials_cache = ProviderCredentialsCache(
@@ -700,34 +681,6 @@ class ProviderConfiguration(BaseModel):
try:
provider_record.credential_id = credential_record.id
provider_record.credential_status = CredentialStatus.ACTIVE.value
provider_record.updated_at = naive_utc_now()
session.commit()
provider_model_credentials_cache = ProviderCredentialsCache(
tenant_id=self.tenant_id,
identity_id=provider_record.id,
cache_type=ProviderCredentialsCacheType.PROVIDER,
)
provider_model_credentials_cache.delete()
self.switch_preferred_provider_type(ProviderType.CUSTOM, session=session)
except Exception:
session.rollback()
raise
def cancel_provider_credential(self):
"""
Cancel select the active provider credential.
:return:
"""
with Session(db.engine) as session:
provider_record = self._get_provider_record(session)
if not provider_record:
raise ValueError("Provider record not found.")
try:
provider_record.credential_id = None
provider_record.credential_status = CredentialStatus.CANCELED.value
provider_record.updated_at = naive_utc_now()
session.commit()

View File

@@ -11,7 +11,6 @@ from core.entities.parameter_entities import (
)
from core.model_runtime.entities.model_entities import ModelType
from core.tools.entities.common_entities import I18nObject
from models.provider import CredentialStatus
class ProviderQuotaType(Enum):
@@ -98,7 +97,6 @@ class CustomProviderConfiguration(BaseModel):
credentials: dict
current_credential_id: Optional[str] = None
current_credential_name: Optional[str] = None
current_credential_status: Optional[CredentialStatus] = None
available_credentials: list[CredentialConfiguration] = []

View File

@@ -711,7 +711,6 @@ class ProviderManager:
credentials=provider_credentials,
current_credential_name=custom_provider_record.credential_name,
current_credential_id=custom_provider_record.credential_id,
current_credential_status=custom_provider_record.credential_status,
available_credentials=self.get_provider_available_credentials(
tenant_id, custom_provider_record.provider_name
),

View File

@@ -1,33 +0,0 @@
"""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

@@ -1,5 +1,5 @@
from datetime import datetime
from enum import Enum, StrEnum
from enum import Enum
from functools import cached_property
from typing import Optional
@@ -42,12 +42,6 @@ class ProviderQuotaType(Enum):
raise ValueError(f"No matching enum found for value '{value}'")
class CredentialStatus(StrEnum):
ACTIVE = "active"
CANCELED = "canceled"
REMOVED = "removed"
class Provider(Base):
"""
Provider model representing the API providers and their configurations.
@@ -71,9 +65,6 @@ class Provider(Base):
is_valid: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, server_default=text("false"))
last_used: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
credential_id: Mapped[Optional[str]] = mapped_column(StringUUID, nullable=True)
credential_status: Mapped[Optional[str]] = mapped_column(
String(20), nullable=True, server_default=text("'active'::character varying")
)
quota_type: Mapped[Optional[str]] = mapped_column(
String(40), nullable=True, server_default=text("''::character varying")

View File

@@ -34,8 +34,6 @@ class CustomConfigurationStatus(Enum):
ACTIVE = "active"
NO_CONFIGURE = "no-configure"
CANCELED = "canceled"
REMOVED = "removed"
class CustomConfigurationResponse(BaseModel):

View File

@@ -89,7 +89,9 @@ class ModelProviderService:
model_credential_schema=provider_configuration.provider.model_credential_schema,
preferred_provider_type=provider_configuration.preferred_provider_type,
custom_configuration=CustomConfigurationResponse(
status=provider_configuration.get_custom_configuration_status(),
status=CustomConfigurationStatus.ACTIVE
if provider_configuration.is_custom_configuration_available()
else CustomConfigurationStatus.NO_CONFIGURE,
current_credential_id=getattr(provider_config, "current_credential_id", None),
current_credential_name=getattr(provider_config, "current_credential_name", None),
available_credentials=getattr(provider_config, "available_credentials", []),
@@ -212,16 +214,6 @@ class ModelProviderService:
provider_configuration = self._get_provider_configuration(tenant_id, provider)
provider_configuration.switch_active_provider_credential(credential_id=credential_id)
def cancel_provider_credential(self, tenant_id: str, provider: str):
"""
:param tenant_id: workspace id
:param provider: provider name
:param credential_id: credential id
:return:
"""
provider_configuration = self._get_provider_configuration(tenant_id, provider)
provider_configuration.cancel_provider_credential()
def get_model_credential(
self, tenant_id: str, provider: str, model_type: str, model: str, credential_id: str | None
) -> Optional[dict]:

View File

@@ -107,8 +107,6 @@ export const MODEL_STATUS_TEXT: { [k: string]: TypeWithI18N } = {
export enum CustomConfigurationStatusEnum {
active = 'active',
noConfigure = 'no-configure',
canceled = 'canceled',
removed = 'removed',
}
export type FormShowOnObject = {

View File

@@ -46,7 +46,6 @@ const ModelProviderPage = ({ searchText }: Props) => {
providers.forEach((provider) => {
if (
provider.custom_configuration.status === CustomConfigurationStatusEnum.active
|| provider.custom_configuration.status === CustomConfigurationStatusEnum.removed
|| (
provider.system_configuration.enabled === true
&& provider.system_configuration.quota_configurations.find(item => item.quota_type === provider.system_configuration.current_quota_type)

View File

@@ -64,7 +64,6 @@ type AuthorizedProps = {
showModelTitle?: boolean
disableDeleteButShowAction?: boolean
disableDeleteTip?: string
showDeselect?: boolean
}
const Authorized = ({
provider,
@@ -89,7 +88,6 @@ const Authorized = ({
showModelTitle,
disableDeleteButShowAction,
disableDeleteTip,
showDeselect,
}: AuthorizedProps) => {
const { t } = useTranslation()
const [isLocalOpen, setIsLocalOpen] = useState(false)
@@ -114,7 +112,6 @@ const Authorized = ({
handleConfirmDelete,
deleteCredentialId,
handleOpenModal,
handleDeselect,
} = useAuth(
provider,
configurationMethod,
@@ -174,15 +171,8 @@ const Authorized = ({
)}>
{
popupTitle && (
<div className='system-xs-medium flex items-center justify-between px-3 pb-0.5 pt-[10px] text-text-tertiary'>
<div className='system-xs-medium px-3 pb-0.5 pt-[10px] text-text-tertiary'>
{popupTitle}
{
showDeselect && (
<div onClick={() => handleDeselect()} className='cursor-pointer'>
{t('common.modelProvider.auth.deselect')}
</div>
)
}
</div>
)
}

View File

@@ -29,11 +29,10 @@ const ConfigProvider = ({
const { t } = useTranslation()
const {
hasCredential,
authorized,
current_credential_id,
current_credential_name,
available_credentials,
authorized,
unAuthorized,
} = useCredentialStatus(provider)
const notAllowCustomCredential = provider.allow_custom_token === false
@@ -42,11 +41,11 @@ const ConfigProvider = ({
<Button
className='grow'
size='small'
variant={unAuthorized ? 'secondary-accent' : 'secondary'}
variant={!authorized ? 'secondary-accent' : 'secondary'}
>
<RiEqualizer2Line className='mr-1 h-3.5 w-3.5' />
{!unAuthorized && t('common.operation.config')}
{unAuthorized && t('common.operation.setup')}
{hasCredential && t('common.operation.config')}
{!hasCredential && t('common.operation.setup')}
</Button>
)
if (notAllowCustomCredential && !hasCredential) {
@@ -60,7 +59,7 @@ const ConfigProvider = ({
)
}
return Item
}, [hasCredential, notAllowCustomCredential, t])
}, [authorized, hasCredential, notAllowCustomCredential, t])
return (
<Authorized
@@ -69,6 +68,7 @@ const ConfigProvider = ({
currentCustomConfigurationModelFixedFields={currentCustomConfigurationModelFixedFields}
items={[
{
title: t('common.modelProvider.auth.apiKeys'),
credentials: available_credentials ?? [],
selectedCredential: {
credential_id: current_credential_id ?? '',
@@ -77,10 +77,9 @@ const ConfigProvider = ({
},
]}
showItemSelectedIcon
showModelTitle
renderTrigger={renderTrigger}
triggerOnlyOpenModal={!hasCredential && !notAllowCustomCredential}
showDeselect={authorized}
popupTitle={t('common.modelProvider.auth.apiKeys')}
/>
)
}

View File

@@ -18,10 +18,7 @@ import {
useModelModalHandler,
useRefreshModel,
} from '@/app/components/header/account-setting/model-provider-page/hooks'
import {
useDeleteModel,
useDeselectModelCredential,
} from '@/service/use-models'
import { useDeleteModel } from '@/service/use-models'
export const useAuth = (
provider: ModelProvider,
@@ -49,7 +46,6 @@ export const useAuth = (
getAddCredentialService,
} = useAuthService(provider.provider)
const { mutateAsync: deleteModelService } = useDeleteModel(provider.provider)
const { mutateAsync: deselectModelCredentialService } = useDeselectModelCredential(provider.provider)
const handleOpenModelModal = useModelModalHandler()
const { handleRefreshModel } = useRefreshModel()
const pendingOperationCredentialId = useRef<string | null>(null)
@@ -181,11 +177,6 @@ export const useAuth = (
mode,
])
const handleDeselect = useCallback(async () => {
await deselectModelCredentialService()
handleRefreshModel(provider, configurationMethod, undefined)
}, [deselectModelCredentialService, handleRefreshModel, provider, configurationMethod])
return {
pendingOperationCredentialId,
pendingOperationModel,
@@ -198,6 +189,5 @@ export const useAuth = (
deleteModel,
handleSaveCredential,
handleOpenModal,
handleDeselect,
}
}

View File

@@ -2,19 +2,16 @@ import { useMemo } from 'react'
import type {
ModelProvider,
} from '../../declarations'
import { CustomConfigurationStatusEnum } from '../../declarations'
export const useCredentialStatus = (provider: ModelProvider) => {
const {
current_credential_id,
current_credential_name,
available_credentials,
status: customConfigurationStatus,
} = provider.custom_configuration
const hasCredential = !!available_credentials?.length
const authorized = customConfigurationStatus === CustomConfigurationStatusEnum.active
const authRemoved = customConfigurationStatus === CustomConfigurationStatusEnum.removed
const unAuthorized = customConfigurationStatus === CustomConfigurationStatusEnum.noConfigure || customConfigurationStatus === CustomConfigurationStatusEnum.canceled
const authorized = current_credential_id && current_credential_name
const authRemoved = hasCredential && !current_credential_id && !current_credential_name
const currentCredential = available_credentials?.find(credential => credential.credential_id === current_credential_id)
return useMemo(() => ({
@@ -24,8 +21,6 @@ export const useCredentialStatus = (provider: ModelProvider) => {
current_credential_id,
current_credential_name,
available_credentials,
customConfigurationStatus,
notAllowedToUse: currentCredential?.not_allowed_to_use,
unAuthorized,
}), [hasCredential, authorized, authRemoved, current_credential_id, current_credential_name, available_credentials])
}

View File

@@ -45,7 +45,6 @@ const CredentialPanel = ({
authRemoved,
current_credential_name,
notAllowedToUse,
unAuthorized,
} = useCredentialStatus(provider)
const handleChangePriority = async (key: PreferredProviderTypeEnum) => {
@@ -71,23 +70,23 @@ const CredentialPanel = ({
}
}
const credentialLabel = useMemo(() => {
if (unAuthorized)
if (!hasCredential)
return t('common.modelProvider.auth.unAuthorized')
if (authRemoved)
return t('common.modelProvider.auth.authRemoved')
if (authorized)
return current_credential_name
if (authRemoved)
return t('common.modelProvider.auth.authRemoved')
return ''
}, [authorized, authRemoved, current_credential_name, unAuthorized])
}, [authorized, authRemoved, current_credential_name, hasCredential])
const color = useMemo(() => {
if (authRemoved || !hasCredential || unAuthorized)
if (authRemoved || !hasCredential)
return 'red'
if (notAllowedToUse)
return 'gray'
return 'green'
}, [authRemoved, notAllowedToUse, hasCredential, unAuthorized])
}, [authRemoved, notAllowedToUse, hasCredential])
return (
<>

View File

@@ -32,7 +32,8 @@ const ListWrapper = ({
const handleMoreClick = useMarketplaceContext(v => v.handleMoreClick)
useEffect(() => {
if (!marketplaceCollectionsFromClient?.length && isSuccessCollections)
// Fetch plugins if there are no collections available
if ((!marketplaceCollectionsFromClient?.length && isSuccessCollections) || !marketplaceCollections.length)
handleQueryPlugins()
}, [handleQueryPlugins, marketplaceCollections, marketplaceCollectionsFromClient, isSuccessCollections])

View File

@@ -523,7 +523,6 @@ const translation = {
removeModel: 'Remove Model',
selectModelCredential: 'Select a model credential',
customModelCredentialsDeleteTip: 'Credential is in use and cannot be deleted',
deselect: 'Deselect',
},
},
dataSource: {

View File

@@ -517,7 +517,6 @@ const translation = {
removeModel: '移除模型',
selectModelCredential: '选择模型凭据',
customModelCredentialsDeleteTip: '模型凭据正在使用中,无法删除',
deselect: '取消选择',
},
},
dataSource: {

View File

@@ -153,9 +153,3 @@ export const useUpdateModelLoadBalancingConfig = (provider: string) => {
}),
})
}
export const useDeselectModelCredential = (provider: string) => {
return useMutation({
mutationFn: () => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/credentials/cancel`),
})
}