- {!isCurrentWorkspaceDatasetOperator && (
-
{menuItem.name}
- )}
{
menuItem.items.map(item => (
@@ -212,18 +208,14 @@ export default function AccountSetting({
{activeItem?.description}
)}
- {activeMenu === ACCOUNT_SETTING_TAB.PROVIDER && (
-
-
-
- )}
- {activeMenu === ACCOUNT_SETTING_TAB.PROVIDER &&
}
+ {activeMenu === ACCOUNT_SETTING_TAB.PROVIDER && (
+
+ )}
{activeMenu === ACCOUNT_SETTING_TAB.MEMBERS &&
}
{activeMenu === ACCOUNT_SETTING_TAB.BILLING &&
}
{activeMenu === ACCOUNT_SETTING_TAB.DATA_SOURCE &&
}
diff --git a/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx b/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx
index 8f9366635d..84aad65f68 100644
--- a/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx
@@ -59,6 +59,17 @@ vi.mock('../install-from-marketplace', () => ({
default: () =>
,
}))
+vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
+ default: () => ({
+ referenceSetting: { permission: {}, auto_upgrade: {} },
+ setReferenceSettings: vi.fn(),
+ }),
+}))
+
+vi.mock('@/app/components/plugins/reference-setting-modal', () => ({
+ default: () =>
,
+}))
+
vi.mock('@/service/client', async (importOriginal) => {
const actual = await importOriginal
()
const originalPlugins = actual.consoleQuery.plugins as unknown as Record
@@ -91,7 +102,7 @@ vi.mock('@/service/client', async (importOriginal) => {
describe('ModelProviderPage non-cloud branch', () => {
it('should skip the quota panel when cloud edition is disabled', () => {
- renderWithSystemFeatures(, {
+ renderWithSystemFeatures(, {
systemFeatures: { enable_marketplace: false },
})
diff --git a/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx
index 6bf818a544..31d2e60fa1 100644
--- a/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx
@@ -21,7 +21,7 @@ const renderModelProviderPage = (
props: { searchText?: string, enableMarketplace?: boolean } = {},
) => {
const { searchText = '', enableMarketplace = true } = props
- return renderWithSystemFeatures(, {
+ return renderWithSystemFeatures(, {
systemFeatures: { enable_marketplace: enableMarketplace },
})
}
@@ -71,6 +71,17 @@ vi.mock('../install-from-marketplace', () => ({
default: () => ,
}))
+vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
+ default: () => ({
+ referenceSetting: { permission: {}, auto_upgrade: {} },
+ setReferenceSettings: vi.fn(),
+ }),
+}))
+
+vi.mock('@/app/components/plugins/reference-setting-modal', () => ({
+ default: () => ,
+}))
+
vi.mock('../provider-added-card', () => ({
default: ({ provider }: { provider: { provider: string } }) => {provider.provider}
,
}))
@@ -147,8 +158,9 @@ describe('ModelProviderPage', () => {
it('should render main elements', () => {
renderModelProviderPage()
- expect(screen.getByText('common.modelProvider.models')).toBeInTheDocument()
+ expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument()
expect(screen.getByTestId('system-model-selector')).toBeInTheDocument()
+ expect(screen.getByText('plugin.autoUpdate.updateSettings')).toBeInTheDocument()
expect(screen.getByTestId('install-from-marketplace')).toBeInTheDocument()
})
diff --git a/web/app/components/header/account-setting/model-provider-page/index.tsx b/web/app/components/header/account-setting/model-provider-page/index.tsx
index 6522ea2db4..1b72d53bea 100644
--- a/web/app/components/header/account-setting/model-provider-page/index.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/index.tsx
@@ -5,9 +5,12 @@ import type { PluginDetail } from '@/app/components/plugins/types'
import { cn } from '@langgenius/dify-ui/cn'
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
import { useDebounce } from 'ahooks'
-import { useMemo } from 'react'
+import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
+import SearchInput from '@/app/components/base/search-input'
import { usePluginsWithLatestVersion } from '@/app/components/plugins/hooks'
+import useReferenceSetting from '@/app/components/plugins/plugin-page/use-reference-setting'
+import ReferenceSettingModal from '@/app/components/plugins/reference-setting-modal'
import { IS_CLOUD_EDITION } from '@/config'
import { useProviderContext } from '@/context/provider-context'
import { consoleQuery } from '@/service/client'
@@ -22,20 +25,23 @@ import {
import InstallFromMarketplace from './install-from-marketplace'
import ProviderAddedCard from './provider-added-card'
import QuotaPanel from './provider-added-card/quota-panel'
+import { providerSupportsCredits } from './supports-credits'
import SystemModelSelector from './system-model-selector'
import { providerToPluginId } from './utils'
type SystemModelConfigStatus = 'no-provider' | 'none-configured' | 'partially-configured' | 'fully-configured'
type Props = {
+ onSearchTextChange: (value: string) => void
searchText: string
}
const FixedModelProvider = ['langgenius/openai/openai', 'langgenius/anthropic/anthropic']
-const ModelProviderPage = ({ searchText }: Props) => {
+const ModelProviderPage = ({ onSearchTextChange, searchText }: Props) => {
const debouncedSearchText = useDebounce(searchText, { wait: 500 })
const { t } = useTranslation()
+ const [showUpdateSettingModal, setShowUpdateSettingModal] = useState(false)
const { data: textGenerationDefaultModel, isLoading: isTextGenerationDefaultModelLoading } = useDefaultModel(ModelTypeEnum.textGeneration)
const { data: embeddingsDefaultModel, isLoading: isEmbeddingsDefaultModelLoading } = useDefaultModel(ModelTypeEnum.textEmbedding)
const { data: rerankDefaultModel, isLoading: isRerankDefaultModelLoading } = useDefaultModel(ModelTypeEnum.rerank)
@@ -43,6 +49,10 @@ const ModelProviderPage = ({ searchText }: Props) => {
const { data: ttsDefaultModel, isLoading: isTTSDefaultModelLoading } = useDefaultModel(ModelTypeEnum.tts)
const { modelProviders: providers } = useProviderContext()
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
+ const {
+ referenceSetting,
+ setReferenceSettings,
+ } = useReferenceSetting()
const allPluginIds = useMemo(() => {
return [...new Set(providers.map(p => providerToPluginId(p.provider)).filter(Boolean))]
@@ -128,36 +138,70 @@ const ModelProviderPage = ({ searchText }: Props) => {
return [filteredConfiguredProviders, filteredNotConfiguredProviders]
}, [configuredProviders, debouncedSearchText, notConfiguredProviders])
+ const [creditsBackedProviders, otherConfiguredProviders] = useMemo(() => {
+ const creditsBackedProviders: ModelProvider[] = []
+ const otherConfiguredProviders: ModelProvider[] = []
+
+ filteredConfiguredProviders.forEach((provider) => {
+ if (providerSupportsCredits(provider, systemFeatures.trial_models))
+ creditsBackedProviders.push(provider)
+ else
+ otherConfiguredProviders.push(provider)
+ })
+
+ return [creditsBackedProviders, otherConfiguredProviders]
+ }, [filteredConfiguredProviders, systemFeatures.trial_models])
+ const hasConfiguredProviders = creditsBackedProviders.length > 0 || otherConfiguredProviders.length > 0
return (
-
-
{t('modelProvider.models', { ns: 'common' })}
-
- {showWarning &&
}
- {showWarning && (
-
-
- {t(warningTextKey, { ns: 'common' })}
-
+
+
+
+
+
+ >
+ {showWarning &&
}
+ {showWarning && (
+
+
+ {t(warningTextKey, { ns: 'common' })}
+
+ )}
+
+
{IS_CLOUD_EDITION &&
}
- {!filteredConfiguredProviders?.length && (
+ {!hasConfiguredProviders && (
@@ -166,23 +210,46 @@ const ModelProviderPage = ({ searchText }: Props) => {
{t('modelProvider.emptyProviderTip', { ns: 'common' })}
)}
- {!!filteredConfiguredProviders?.length && (
-
- {filteredConfiguredProviders?.map(provider => (
-
- ))}
-
+ {!!creditsBackedProviders.length && (
+
+
+
{t('modelProvider.creditsBackedProviders', { ns: 'common' })}
+
{t('modelProvider.creditsBackedProvidersDesc', { ns: 'common' })}
+
+
+ {creditsBackedProviders.map(provider => (
+
+ ))}
+
+
+ )}
+ {!!otherConfiguredProviders.length && (
+
+ {t('modelProvider.configuredProviders', { ns: 'common' })}
+
+ {otherConfiguredProviders.map(provider => (
+
+ ))}
+
+
)}
{!!filteredNotConfiguredProviders?.length && (
- <>
-
{t('modelProvider.toBeConfigured', { ns: 'common' })}
-
+
+ {t('modelProvider.toBeConfigured', { ns: 'common' })}
+
{filteredNotConfiguredProviders?.map(provider => (
{
/>
))}
- >
+
)}
{
enableMarketplace && (
@@ -200,6 +267,13 @@ const ModelProviderPage = ({ searchText }: Props) => {
/>
)
}
+ {showUpdateSettingModal && referenceSetting && (
+
setShowUpdateSettingModal(false)}
+ onSave={setReferenceSettings}
+ />
+ )}
)
}
diff --git a/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx b/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx
index dd4784916f..96fa797c4f 100644
--- a/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx
@@ -36,11 +36,11 @@ const InstallFromMarketplace = ({
if (plugin.type === 'bundle')
return null
- return
+ return
}, [])
return (
-
+
diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx
index c0bbab04f3..2308dba18f 100644
--- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx
@@ -16,9 +16,11 @@ import {
import { IS_CE_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useProviderContextSelector } from '@/context/provider-context'
+import { renderI18nObject } from '@/i18n-config'
import { consoleQuery } from '@/service/client'
import { useModelProviderListExpanded, useSetModelProviderListExpanded } from '../atoms'
import { ConfigurationMethodEnum } from '../declarations'
+import { useLanguage } from '../hooks'
import ModelBadge from '../model-badge'
import ProviderIcon from '../provider-icon'
import {
@@ -30,16 +32,19 @@ import ModelList from './model-list'
import ProviderCardActions from './provider-card-actions'
type ProviderAddedCardProps = {
+ layout?: 'list' | 'grid'
notConfigured?: boolean
provider: ModelProvider
pluginDetail?: PluginDetail
}
const ProviderAddedCard: FC
= ({
+ layout = 'list',
notConfigured,
provider,
pluginDetail,
}) => {
const { t } = useTranslation()
+ const language = useLanguage()
const refreshModelProviders = useProviderContextSelector(state => state.refreshModelProviders)
const currentProviderName = provider.provider
const expanded = useModelProviderListExpanded(currentProviderName)
@@ -87,6 +92,119 @@ const ProviderAddedCard: FC = ({
refetchModelList().catch(() => {})
}, [expanded, loading, refetchModelList, setExpanded])
+ const providerLabel = renderI18nObject(provider.label, language)
+ const description = renderI18nObject(
+ provider.description || pluginDetail?.declaration.description || provider.help?.title || provider.label,
+ language,
+ )
+ const organization = pluginDetail?.declaration.author || currentProviderName.split('/')[0]
+
+ if (layout === 'grid') {
+ return (
+
+
+
+
+

+
+
+
+
+ {providerLabel}
+
+ {pluginDetail && (
+
+ )}
+
+
+ {organization}
+
+
+
+
+ {description}
+
+
+ {provider.supported_model_types.slice(0, 4).map(modelType => (
+
+ {modelTypeFormat(modelType)}
+
+ ))}
+
+
+
+ {(showModelProvider || !notConfigured) && (
+
+ )}
+ {!showModelProvider && notConfigured && (
+
+
+ {t('modelProvider.configureTip', { ns: 'common' })}
+
+ )}
+ {showCredential && (
+
+ )}
+ {showCustomModelActions && (
+
+ )}
+
+ {!showCollapsedSection && (
+
+ setExpanded(false)}
+ onChange={refreshModelList}
+ />
+
+ )}
+
+ )
+ }
+
return (
= ({
- {t('modelProvider.quota', { ns: 'common' })}
+ {t('modelProvider.quotaLabel', { ns: 'common' })}
{tipText}
@@ -110,6 +110,7 @@ const QuotaPanel: FC
= ({
{credits > 0
? {formatNumber(credits)}
: {t('modelProvider.card.quotaExhausted', { ns: 'common' })}}
+ {t('modelProvider.credits', { ns: 'common' })}
{nextCreditResetDate
? (
<>
diff --git a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx
index 8ceaaaa32e..49dddd303c 100644
--- a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx
@@ -4,6 +4,7 @@ import type {
DefaultModelResponse,
} from '../declarations'
import { Button } from '@langgenius/dify-ui/button'
+import { cn } from '@langgenius/dify-ui/cn'
import {
Dialog,
DialogCloseButton,
@@ -27,6 +28,7 @@ import {
import ModelSelector from '../model-selector'
type SystemModelSelectorProps = {
+ className?: string
textGenerationDefaultModel: DefaultModelResponse | undefined
embeddingsDefaultModel: DefaultModelResponse | undefined
rerankDefaultModel: DefaultModelResponse | undefined
@@ -51,6 +53,7 @@ type SystemModelTipKey
| 'modelProvider.ttsModel.tip'
const SystemModel: FC = ({
+ className,
textGenerationDefaultModel,
embeddingsDefaultModel,
rerankDefaultModel,
@@ -148,7 +151,7 @@ const SystemModel: FC = ({
return (
<>