mirror of
https://github.com/langgenius/dify.git
synced 2026-10-28 03:35:04 +08:00
fix: can't debug model plugins (#38500)
This commit is contained in:
+4
-1
@@ -41,6 +41,7 @@ vi.mock('@/context/provider-context', () => ({
|
||||
|
||||
vi.mock('../hooks', () => ({
|
||||
useDefaultModel: () => ({ data: null, isLoading: false }),
|
||||
useLanguage: () => 'en_US',
|
||||
}))
|
||||
|
||||
vi.mock('../provider-added-card', () => ({
|
||||
@@ -84,9 +85,11 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
useCheckInstalled: () => ({
|
||||
useInstalledPluginList: () => ({
|
||||
data: { plugins: [] },
|
||||
}),
|
||||
useInvalidateInstalledPluginList: () => vi.fn(),
|
||||
useInvalidateCheckInstalled: () => vi.fn(),
|
||||
usePluginAutoUpgradeSettings: () => ({
|
||||
data: {
|
||||
category: 'model',
|
||||
|
||||
+267
-8
@@ -1,7 +1,9 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { PluginDeclaration, PluginDetail } from '@/app/components/plugins/types'
|
||||
import { act, fireEvent, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
||||
import { PluginCategoryEnum, PluginSource } from '@/app/components/plugins/types'
|
||||
import {
|
||||
CurrentSystemQuotaTypeEnum,
|
||||
CustomConfigurationStatusEnum,
|
||||
@@ -41,10 +43,18 @@ const { mockReferenceSetting, mockAutoUpgradeError } = vi.hoisted(() => ({
|
||||
},
|
||||
}))
|
||||
|
||||
const { mockProviderContextState } = vi.hoisted(() => ({
|
||||
const { mockProviderContextState, mockRefreshModelProviders } = vi.hoisted(() => ({
|
||||
mockProviderContextState: {
|
||||
isLoadingModelProviders: false,
|
||||
},
|
||||
mockRefreshModelProviders: vi.fn(),
|
||||
}))
|
||||
|
||||
const { mockInstalledModelPlugins, mockUseInstalledPluginList } = vi.hoisted(() => ({
|
||||
mockInstalledModelPlugins: {
|
||||
value: [] as PluginDetail[],
|
||||
},
|
||||
mockUseInstalledPluginList: vi.fn(),
|
||||
}))
|
||||
|
||||
const mockQuotaConfig = {
|
||||
@@ -79,6 +89,70 @@ const saveUpdateSettings = () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
}
|
||||
|
||||
const createPluginDeclaration = (overrides: Partial<PluginDeclaration> = {}): PluginDeclaration => ({
|
||||
plugin_unique_identifier: 'langgenius/debug-model:1.0.0',
|
||||
version: '1.0.0',
|
||||
author: 'langgenius',
|
||||
icon: 'debug-model.png',
|
||||
icon_dark: 'debug-model-dark.png',
|
||||
name: 'debug-model',
|
||||
category: PluginCategoryEnum.model,
|
||||
label: { en_US: 'Debug Model' } as unknown as PluginDeclaration['label'],
|
||||
description: { en_US: 'Debug model provider' } as unknown as PluginDeclaration['description'],
|
||||
created_at: '2024-01-01',
|
||||
resource: null,
|
||||
plugins: null,
|
||||
verified: false,
|
||||
endpoint: null,
|
||||
tool: undefined,
|
||||
datasource: undefined,
|
||||
model: {},
|
||||
tags: [],
|
||||
agent_strategy: null,
|
||||
meta: {
|
||||
version: '1.0.0',
|
||||
},
|
||||
trigger: {} as unknown as PluginDeclaration['trigger'],
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const createPluginDetail = (overrides: Partial<PluginDetail> = {}): PluginDetail => {
|
||||
const {
|
||||
declaration: overrideDeclaration,
|
||||
plugin_id: overridePluginId,
|
||||
...restOverrides
|
||||
} = overrides
|
||||
const declaration = overrideDeclaration ?? createPluginDeclaration()
|
||||
const pluginId = overridePluginId ?? 'langgenius/debug-model'
|
||||
|
||||
return {
|
||||
id: 'plugin-installation-id',
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01',
|
||||
name: declaration.name,
|
||||
plugin_id: pluginId,
|
||||
plugin_unique_identifier: declaration.plugin_unique_identifier,
|
||||
declaration,
|
||||
installation_id: 'plugin-installation-id',
|
||||
tenant_id: 'tenant-id',
|
||||
endpoints_setups: 0,
|
||||
endpoints_active: 0,
|
||||
version: '1.0.0',
|
||||
latest_version: '1.0.0',
|
||||
latest_unique_identifier: declaration.plugin_unique_identifier,
|
||||
source: PluginSource.debugging,
|
||||
meta: {
|
||||
repo: '',
|
||||
version: '1.0.0',
|
||||
package: '',
|
||||
},
|
||||
status: 'active',
|
||||
deprecated_reason: '',
|
||||
alternative_plugin_id: '',
|
||||
...restOverrides,
|
||||
}
|
||||
}
|
||||
|
||||
const mockProviders = [
|
||||
{
|
||||
provider: 'openai',
|
||||
@@ -106,6 +180,7 @@ vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
modelProviders: mockProviders,
|
||||
isLoadingModelProviders: mockProviderContextState.isLoadingModelProviders,
|
||||
refreshModelProviders: mockRefreshModelProviders,
|
||||
}),
|
||||
}))
|
||||
|
||||
@@ -119,6 +194,7 @@ const mockDefaultModels: Record<string, { data: unknown, isLoading: boolean }> =
|
||||
|
||||
vi.mock('../hooks', () => ({
|
||||
useDefaultModel: (type: string) => mockDefaultModels[type] ?? { data: null, isLoading: false },
|
||||
useLanguage: () => 'en_US',
|
||||
}))
|
||||
|
||||
vi.mock('../install-from-marketplace', () => ({
|
||||
@@ -126,7 +202,24 @@ vi.mock('../install-from-marketplace', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('../provider-added-card', () => ({
|
||||
default: ({ provider }: { provider: { provider: string } }) => <div data-testid="provider-card">{provider.provider}</div>,
|
||||
default: ({
|
||||
notConfigured,
|
||||
provider,
|
||||
pluginDetail,
|
||||
}: {
|
||||
notConfigured?: boolean
|
||||
provider: { provider: string }
|
||||
pluginDetail?: { plugin_id: string, source?: string }
|
||||
}) => (
|
||||
<div
|
||||
data-testid="provider-card"
|
||||
data-not-configured={String(!!notConfigured)}
|
||||
data-plugin-id={pluginDetail?.plugin_id ?? ''}
|
||||
data-plugin-source={pluginDetail?.source ?? ''}
|
||||
>
|
||||
{provider.provider}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('../provider-added-card/quota-panel', () => ({
|
||||
@@ -160,12 +253,12 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
useInstalledPluginList: () => ({
|
||||
data: { plugins: [] },
|
||||
}),
|
||||
useCheckInstalled: () => ({
|
||||
data: { plugins: [] },
|
||||
}),
|
||||
useInstalledPluginList: (...args: unknown[]) => {
|
||||
mockUseInstalledPluginList(...args)
|
||||
return {
|
||||
data: { plugins: mockInstalledModelPlugins.value },
|
||||
}
|
||||
},
|
||||
usePluginAutoUpgradeSettings: () => ({
|
||||
data: mockReferenceSetting.auto_upgrade
|
||||
? {
|
||||
@@ -280,6 +373,9 @@ describe('ModelProviderPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.clearAllMocks()
|
||||
mockUseInstalledPluginList.mockClear()
|
||||
mockRefreshModelProviders.mockClear()
|
||||
mockInstalledModelPlugins.value = []
|
||||
mockProviderContextState.isLoadingModelProviders = false
|
||||
mockAutoUpgradeError.value = undefined
|
||||
mockReferenceSetting.auto_upgrade = {
|
||||
@@ -418,6 +514,107 @@ describe('ModelProviderPage', () => {
|
||||
expect(screen.getByText('anthropic')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should use the model plugin installation list to attach plugin detail to provider cards', () => {
|
||||
mockProviders.splice(0, mockProviders.length, {
|
||||
provider: 'langgenius/openai/openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
custom_configuration: { status: CustomConfigurationStatusEnum.active },
|
||||
system_configuration: {
|
||||
enabled: false,
|
||||
current_quota_type: CurrentSystemQuotaTypeEnum.free,
|
||||
quota_configurations: [mockQuotaConfig],
|
||||
},
|
||||
})
|
||||
mockInstalledModelPlugins.value = [
|
||||
createPluginDetail({
|
||||
plugin_id: 'langgenius/openai',
|
||||
declaration: createPluginDeclaration({
|
||||
plugin_unique_identifier: 'langgenius/openai:1.0.0',
|
||||
name: 'openai',
|
||||
label: { en_US: 'OpenAI Plugin' } as unknown as PluginDeclaration['label'],
|
||||
}),
|
||||
}),
|
||||
]
|
||||
|
||||
renderModelProviderPage()
|
||||
|
||||
expect(mockUseInstalledPluginList).toHaveBeenCalledWith(false, 100, { category: PluginCategoryEnum.model })
|
||||
expect(screen.getByTestId('provider-card')).toHaveAttribute('data-plugin-id', 'langgenius/openai')
|
||||
expect(screen.queryByText('OpenAI Plugin')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render installed model plugins that are not registered as model providers', () => {
|
||||
mockInstalledModelPlugins.value = [
|
||||
createPluginDetail({
|
||||
plugin_id: 'langgenius/debug-model',
|
||||
declaration: createPluginDeclaration({
|
||||
label: { en_US: 'Debug Model' } as unknown as PluginDeclaration['label'],
|
||||
description: { en_US: 'Debug model provider' } as unknown as PluginDeclaration['description'],
|
||||
}),
|
||||
}),
|
||||
]
|
||||
|
||||
renderModelProviderPage()
|
||||
|
||||
expect(screen.queryByText('Debug Model')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('langgenius/debug-model')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'plugin actions langgenius/debug-model' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should refresh model providers once when a debugging model plugin is missing from providers', () => {
|
||||
mockInstalledModelPlugins.value = [
|
||||
createPluginDetail({
|
||||
plugin_id: 'langgenius/debug-model',
|
||||
declaration: createPluginDeclaration({
|
||||
label: { en_US: 'Debug Model' } as unknown as PluginDeclaration['label'],
|
||||
}),
|
||||
}),
|
||||
]
|
||||
|
||||
renderModelProviderPage()
|
||||
|
||||
expect(mockRefreshModelProviders).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should prefer debugging plugin detail when an installed model plugin shares the same plugin id', () => {
|
||||
mockProviders.splice(0, mockProviders.length, {
|
||||
provider: 'langgenius/openai/openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
custom_configuration: { status: CustomConfigurationStatusEnum.active },
|
||||
system_configuration: {
|
||||
enabled: false,
|
||||
current_quota_type: CurrentSystemQuotaTypeEnum.free,
|
||||
quota_configurations: [mockQuotaConfig],
|
||||
},
|
||||
})
|
||||
mockInstalledModelPlugins.value = [
|
||||
createPluginDetail({
|
||||
plugin_id: 'langgenius/openai',
|
||||
declaration: createPluginDeclaration({
|
||||
plugin_unique_identifier: 'langgenius/openai:debug',
|
||||
name: 'openai',
|
||||
label: { en_US: 'OpenAI Debug Plugin' } as unknown as PluginDeclaration['label'],
|
||||
}),
|
||||
source: PluginSource.debugging,
|
||||
}),
|
||||
createPluginDetail({
|
||||
plugin_id: 'langgenius/openai',
|
||||
declaration: createPluginDeclaration({
|
||||
plugin_unique_identifier: 'langgenius/openai:1.0.0',
|
||||
name: 'openai',
|
||||
label: { en_US: 'OpenAI Installed Plugin' } as unknown as PluginDeclaration['label'],
|
||||
}),
|
||||
source: PluginSource.marketplace,
|
||||
}),
|
||||
]
|
||||
|
||||
renderModelProviderPage()
|
||||
|
||||
expect(screen.getByTestId('provider-card')).toHaveAttribute('data-plugin-id', 'langgenius/openai')
|
||||
expect(screen.getByTestId('provider-card')).toHaveAttribute('data-plugin-source', PluginSource.debugging)
|
||||
expect(mockRefreshModelProviders).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should show provider placeholders while model providers are loading', () => {
|
||||
mockProviderContextState.isLoadingModelProviders = true
|
||||
|
||||
@@ -572,4 +769,66 @@ describe('ModelProviderPage', () => {
|
||||
])
|
||||
expect(screen.queryByText('common.modelProvider.toBeConfigured')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should prioritize debugging model plugins within their provider section', () => {
|
||||
mockProviders.splice(0, mockProviders.length, {
|
||||
provider: 'langgenius/openai/openai',
|
||||
label: { en_US: 'OpenAI Fixed' },
|
||||
custom_configuration: { status: CustomConfigurationStatusEnum.active },
|
||||
system_configuration: {
|
||||
enabled: false,
|
||||
current_quota_type: CurrentSystemQuotaTypeEnum.free,
|
||||
quota_configurations: [mockQuotaConfig],
|
||||
},
|
||||
}, {
|
||||
provider: 'zeta-provider',
|
||||
label: { en_US: 'Zeta Provider' },
|
||||
custom_configuration: { status: CustomConfigurationStatusEnum.active },
|
||||
system_configuration: {
|
||||
enabled: false,
|
||||
current_quota_type: CurrentSystemQuotaTypeEnum.free,
|
||||
quota_configurations: [mockQuotaConfig],
|
||||
},
|
||||
}, {
|
||||
provider: 'langgenius/normal-model/normal-model',
|
||||
label: { en_US: 'Normal Model' },
|
||||
custom_configuration: { status: CustomConfigurationStatusEnum.noConfigure },
|
||||
system_configuration: {
|
||||
enabled: false,
|
||||
current_quota_type: CurrentSystemQuotaTypeEnum.free,
|
||||
quota_configurations: [mockQuotaConfig],
|
||||
},
|
||||
}, {
|
||||
provider: 'langgenius/debug-model/debug-model',
|
||||
label: { en_US: 'Debug Model' },
|
||||
custom_configuration: { status: CustomConfigurationStatusEnum.noConfigure },
|
||||
system_configuration: {
|
||||
enabled: false,
|
||||
current_quota_type: CurrentSystemQuotaTypeEnum.free,
|
||||
quota_configurations: [mockQuotaConfig],
|
||||
},
|
||||
})
|
||||
mockInstalledModelPlugins.value = [
|
||||
createPluginDetail({
|
||||
plugin_id: 'langgenius/debug-model',
|
||||
declaration: createPluginDeclaration({
|
||||
plugin_unique_identifier: 'langgenius/debug-model:1.0.0',
|
||||
name: 'debug-model',
|
||||
label: { en_US: 'Debug Model' } as unknown as PluginDeclaration['label'],
|
||||
}),
|
||||
}),
|
||||
]
|
||||
|
||||
renderModelProviderPage()
|
||||
|
||||
const renderedProviders = screen.getAllByTestId('provider-card').map(item => item.textContent)
|
||||
expect(renderedProviders).toEqual([
|
||||
'langgenius/openai/openai',
|
||||
'zeta-provider',
|
||||
'langgenius/debug-model/debug-model',
|
||||
'langgenius/normal-model/normal-model',
|
||||
])
|
||||
expect(screen.getAllByTestId('provider-card')[2]).toHaveAttribute('data-not-configured', 'true')
|
||||
expect(screen.getByText('common.modelProvider.toBeConfigured')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,15 +6,15 @@ import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useDebounce } from 'ahooks'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useMemo } from 'react'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SearchInput } from '@/app/components/base/search-input'
|
||||
import { usePluginsWithLatestVersion } from '@/app/components/plugins/hooks'
|
||||
import { usePluginSettingsAccess } from '@/app/components/plugins/plugin-page/use-reference-setting'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import { PluginCategoryEnum, PluginSource } from '@/app/components/plugins/types'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useCheckInstalled } from '@/service/use-plugins'
|
||||
import { useInstalledPluginList } from '@/service/use-plugins'
|
||||
import UpdateSettingDialog from '../update-setting-dialog'
|
||||
import {
|
||||
CustomConfigurationStatusEnum,
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from './hooks'
|
||||
import ModelProviderPageBody from './model-provider-page-body'
|
||||
import SystemModelSelector from './system-model-selector'
|
||||
import { providerToPluginId } from './utils'
|
||||
|
||||
type SystemModelConfigStatus = 'no-provider' | 'none-configured' | 'partially-configured' | 'fully-configured'
|
||||
|
||||
@@ -58,23 +57,43 @@ const ModelProviderPage = ({
|
||||
const { data: rerankDefaultModel, isLoading: isRerankDefaultModelLoading } = useDefaultModel(ModelTypeEnum.rerank)
|
||||
const { data: speech2textDefaultModel, isLoading: isSpeech2textDefaultModelLoading } = useDefaultModel(ModelTypeEnum.speech2text)
|
||||
const { data: ttsDefaultModel, isLoading: isTTSDefaultModelLoading } = useDefaultModel(ModelTypeEnum.tts)
|
||||
const { modelProviders: providers, isLoadingModelProviders } = useProviderContext()
|
||||
const { modelProviders: providers, isLoadingModelProviders, refreshModelProviders } = useProviderContext()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
|
||||
const allPluginIds = useMemo(() => {
|
||||
return [...new Set(providers.map(p => providerToPluginId(p.provider)).filter(Boolean))]
|
||||
}, [providers])
|
||||
const { data: installedPlugins } = useCheckInstalled({
|
||||
pluginIds: allPluginIds,
|
||||
enabled: allPluginIds.length > 0,
|
||||
const { data: installedModelPlugins } = useInstalledPluginList(false, 100, {
|
||||
category: PluginCategoryEnum.model,
|
||||
})
|
||||
const enrichedPlugins = usePluginsWithLatestVersion(installedPlugins?.plugins)
|
||||
const enrichedPlugins = usePluginsWithLatestVersion(installedModelPlugins?.plugins)
|
||||
const pluginDetailMap = useMemo(() => {
|
||||
const map = new Map<string, PluginDetail>()
|
||||
for (const plugin of enrichedPlugins)
|
||||
map.set(plugin.plugin_id, plugin)
|
||||
for (const plugin of enrichedPlugins) {
|
||||
const existingPlugin = map.get(plugin.plugin_id)
|
||||
if (!existingPlugin || plugin.source === PluginSource.debugging)
|
||||
map.set(plugin.plugin_id, plugin)
|
||||
}
|
||||
return map
|
||||
}, [enrichedPlugins])
|
||||
const debuggingModelPluginKey = useMemo(() => {
|
||||
const debuggingModelPluginIds = enrichedPlugins
|
||||
.filter(plugin => plugin.source === PluginSource.debugging)
|
||||
.map(plugin => `${plugin.plugin_id}:${plugin.plugin_unique_identifier}`)
|
||||
.sort()
|
||||
|
||||
return debuggingModelPluginIds.join(',')
|
||||
}, [enrichedPlugins])
|
||||
const refreshedDebuggingModelPluginKeyRef = useRef('')
|
||||
useEffect(() => {
|
||||
if (!debuggingModelPluginKey) {
|
||||
refreshedDebuggingModelPluginKeyRef.current = ''
|
||||
return
|
||||
}
|
||||
|
||||
if (refreshedDebuggingModelPluginKeyRef.current === debuggingModelPluginKey)
|
||||
return
|
||||
|
||||
refreshedDebuggingModelPluginKeyRef.current = debuggingModelPluginKey
|
||||
refreshModelProviders?.()
|
||||
}, [debuggingModelPluginKey, refreshModelProviders])
|
||||
const enableMarketplace = systemFeatures.enable_marketplace
|
||||
const isDefaultModelLoading = isTextGenerationDefaultModelLoading
|
||||
|| isEmbeddingsDefaultModelLoading
|
||||
|
||||
+29
-9
@@ -3,6 +3,7 @@ import type { ModelProvider } from './declarations'
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { SkeletonContainer, SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton'
|
||||
import { PluginSource } from '@/app/components/plugins/types'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import InstallFromMarketplace from './install-from-marketplace'
|
||||
import ProviderAddedCard from './provider-added-card'
|
||||
@@ -99,21 +100,40 @@ type ProviderCardListProps = {
|
||||
notConfigured?: boolean
|
||||
}
|
||||
|
||||
function isDebuggingProvider(provider: ModelProvider, pluginDetailMap: Map<string, PluginDetail>) {
|
||||
return pluginDetailMap.get(providerToPluginId(provider.provider))?.source === PluginSource.debugging
|
||||
}
|
||||
|
||||
function ProviderCardList({
|
||||
providers,
|
||||
pluginDetailMap,
|
||||
notConfigured,
|
||||
}: ProviderCardListProps) {
|
||||
const sortedProviders = [...providers]
|
||||
.sort((a, b) => {
|
||||
const aIsDebuggingPlugin = isDebuggingProvider(a, pluginDetailMap)
|
||||
const bIsDebuggingPlugin = isDebuggingProvider(b, pluginDetailMap)
|
||||
|
||||
if (aIsDebuggingPlugin === bIsDebuggingPlugin)
|
||||
return 0
|
||||
|
||||
return aIsDebuggingPlugin ? -1 : 1
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col gap-2">
|
||||
{providers.map(provider => (
|
||||
<ProviderAddedCard
|
||||
key={provider.provider}
|
||||
notConfigured={notConfigured}
|
||||
provider={provider}
|
||||
pluginDetail={pluginDetailMap.get(providerToPluginId(provider.provider))}
|
||||
/>
|
||||
))}
|
||||
{sortedProviders.map((provider) => {
|
||||
const pluginDetail = pluginDetailMap.get(providerToPluginId(provider.provider))
|
||||
|
||||
return (
|
||||
<ProviderAddedCard
|
||||
key={provider.provider}
|
||||
notConfigured={notConfigured}
|
||||
provider={provider}
|
||||
pluginDetail={pluginDetail}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -157,8 +177,8 @@ const ModelProviderPageBody: FC<ModelProviderPageBodyProps> = ({
|
||||
<div className="flex flex-col gap-2 pt-2">
|
||||
<div className="flex h-5 items-center system-md-semibold text-text-primary">{t('modelProvider.toBeConfigured', { ns: 'common' })}</div>
|
||||
<ProviderCardList
|
||||
notConfigured
|
||||
providers={filteredNotConfiguredProviders}
|
||||
notConfigured
|
||||
pluginDetailMap={pluginDetailMap}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+9
@@ -158,6 +158,15 @@ describe('ProviderCardActions', () => {
|
||||
expect(mockHandleUpdate).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it('should show a compact debug badge after the version for debugging plugins', () => {
|
||||
render(<ProviderCardActions detail={createDetail({ source: PluginSource.debugging })} />)
|
||||
|
||||
const version = screen.getByText('1.0.0')
|
||||
const debugBadge = screen.getByText('appDebug.operation.debugConfig')
|
||||
|
||||
expect(version.compareDocumentPosition(debugBadge) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should trigger the latest marketplace update when clicking the update button', () => {
|
||||
render(<ProviderCardActions detail={createDetail()} />)
|
||||
|
||||
|
||||
+32
-21
@@ -30,6 +30,7 @@ const ProviderCardActions: FC<Props> = ({ detail, onUpdate }) => {
|
||||
const { source, version, latest_version, latest_unique_identifier, meta } = detail
|
||||
const author = detail.declaration?.author ?? ''
|
||||
const name = detail.declaration?.name ?? detail.name
|
||||
const isDebuggingPlugin = source === PluginSource.debugging
|
||||
|
||||
const {
|
||||
modalStates,
|
||||
@@ -80,31 +81,41 @@ const ProviderCardActions: FC<Props> = ({ detail, onUpdate }) => {
|
||||
return (
|
||||
<>
|
||||
{!!version && (
|
||||
<PluginVersionPicker
|
||||
disabled={!isFromMarketplace || !canUpdatePlugin}
|
||||
isShow={versionPicker.isShow}
|
||||
onShowChange={versionPicker.setIsShow}
|
||||
pluginID={detail.plugin_id}
|
||||
currentVersion={version}
|
||||
onSelect={handleVersionSelect}
|
||||
sideOffset={4}
|
||||
alignOffset={0}
|
||||
trigger={(
|
||||
<>
|
||||
<PluginVersionPicker
|
||||
disabled={!isFromMarketplace || !canUpdatePlugin}
|
||||
isShow={versionPicker.isShow}
|
||||
onShowChange={versionPicker.setIsShow}
|
||||
pluginID={detail.plugin_id}
|
||||
currentVersion={version}
|
||||
onSelect={handleVersionSelect}
|
||||
sideOffset={4}
|
||||
alignOffset={0}
|
||||
trigger={(
|
||||
<Badge
|
||||
className={cn(
|
||||
canUpdatePlugin && isFromMarketplace && 'cursor-pointer hover:bg-state-base-hover',
|
||||
)}
|
||||
uppercase={false}
|
||||
text={(
|
||||
<>
|
||||
<span>{version}</span>
|
||||
{canUpdatePlugin && isFromMarketplace && <span className="ml-1 i-ri-arrow-left-right-line size-3" />}
|
||||
</>
|
||||
)}
|
||||
hasRedCornerMark={hasNewVersion}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{isDebuggingPlugin && (
|
||||
<Badge
|
||||
className={cn(
|
||||
canUpdatePlugin && isFromMarketplace && 'cursor-pointer hover:bg-state-base-hover',
|
||||
)}
|
||||
className="border-state-warning-active bg-state-warning-hover text-text-warning"
|
||||
size="xs"
|
||||
uppercase={false}
|
||||
text={(
|
||||
<>
|
||||
<span>{version}</span>
|
||||
{canUpdatePlugin && isFromMarketplace && <span className="ml-1 i-ri-arrow-left-right-line size-3" />}
|
||||
</>
|
||||
)}
|
||||
hasRedCornerMark={hasNewVersion}
|
||||
text={t('operation.debugConfig', { ns: 'appDebug' })}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{canUpdatePlugin && (hasNewVersion || isFromGitHub) && (
|
||||
|
||||
Reference in New Issue
Block a user