mirror of
https://github.com/langgenius/dify.git
synced 2026-09-09 13:52:42 +08:00
refactor(web): retire provider context (#41944)
This commit is contained in:
parent
7ba2cfb30b
commit
d978fe710f
@ -1,23 +0,0 @@
|
||||
import type { ProviderContextState } from '@/context/provider-context'
|
||||
import { merge } from 'es-toolkit/compat'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
|
||||
// Avoid being mocked in tests
|
||||
export const baseProviderContextValue: ProviderContextState = {
|
||||
modelProviders: [],
|
||||
modelProviderPlugins: {},
|
||||
refreshModelProviders: async () => {},
|
||||
isLoadingModelProviders: false,
|
||||
isSuccessModelProviders: false,
|
||||
}
|
||||
|
||||
export const createMockProviderContextValue = (
|
||||
overrides: Partial<ProviderContextState> = {},
|
||||
): ProviderContextState => {
|
||||
const merged = merge({}, baseProviderContextValue, overrides)
|
||||
|
||||
return {
|
||||
...merged,
|
||||
refreshModelProviders: merged.refreshModelProviders ?? noop,
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,6 @@ import type { ReactNode } from 'react'
|
||||
import { OAuthRegistrationAnalytics } from '@/app/components/oauth-registration-analytics'
|
||||
import { EventEmitterContextProvider } from '@/context/event-emitter-provider'
|
||||
import { ModalContextProvider } from '@/context/modal-context-provider'
|
||||
import { ProviderContextProvider } from '@/context/provider-context-provider'
|
||||
import { ExternalServiceSync } from './external-service-sync'
|
||||
import { CommonLayoutHydrationBoundary } from './hydration-boundary'
|
||||
|
||||
@ -21,9 +20,7 @@ export async function ConsoleRuntimeProviders({ children }: { children: ReactNod
|
||||
export function ConsoleContextProviders({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<EventEmitterContextProvider>
|
||||
<ProviderContextProvider>
|
||||
<ModalContextProvider>{children}</ModalContextProvider>
|
||||
</ProviderContextProvider>
|
||||
<ModalContextProvider>{children}</ModalContextProvider>
|
||||
</EventEmitterContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@ -28,15 +28,6 @@ vi.mock('@/service/annotation', () => ({
|
||||
clearAllAnnotations: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
plan: {
|
||||
usage: { annotatedResponse: 0 },
|
||||
total: { annotatedResponse: 10 },
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/billing/annotation-full', () => ({
|
||||
default: () => <div data-testid="annotation-full" />,
|
||||
}))
|
||||
|
||||
@ -93,12 +93,6 @@ vi.mock('@/context/i18n', () => ({
|
||||
useDocLink: () => (path: string) => `https://docs${path}`,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
modelProviders: [],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useModelListAndDefaultModel: (...args: unknown[]) => mockModelListQueryAndDefaultModel(...args),
|
||||
useModelListAndDefaultModelAndCurrentProviderAndModel: (...args: unknown[]) =>
|
||||
|
||||
@ -32,12 +32,6 @@ vi.mock('ky', () => {
|
||||
return { __esModule: true, default: ky }
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
modelProviders: [],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useModelListAndDefaultModelAndCurrentProviderAndModel: (...args: unknown[]) =>
|
||||
mockModelListQueryAndDefaultModelAndCurrentProviderAndModel(...args),
|
||||
|
||||
@ -7,7 +7,6 @@ import type {
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { createMockProviderContextValue } from '@/__mocks__/provider-context'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
CurrentSystemQuotaTypeEnum,
|
||||
@ -162,15 +161,15 @@ describe('ModelParameterTrigger', () => {
|
||||
onDebugWithMultipleModelChange: vi.fn(),
|
||||
})
|
||||
mockProvidersSummary.mockReturnValue(
|
||||
createMockProviderContextValue({
|
||||
modelProviders: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
...createModelProvider(),
|
||||
is_configured: true,
|
||||
plugin_id: 'langgenius/openai',
|
||||
} as unknown as ModelProviderSummaryResponse,
|
||||
],
|
||||
}).modelProviders,
|
||||
}.data,
|
||||
)
|
||||
mockUseCredentialPanelState.mockReturnValue({
|
||||
variant: 'api-active',
|
||||
|
||||
@ -3,16 +3,27 @@ import type { UpdateWorkflowNodesMapPayload } from '../index'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { act, render, screen } from '@testing-library/react'
|
||||
import { act, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useReactFlow, useStoreApi } from 'reactflow'
|
||||
import { Type } from '@/app/components/workflow/nodes/llm/types'
|
||||
import { BlockEnum, VarType } from '@/app/components/workflow/types'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import { useSelectOrDelete } from '../../../hooks'
|
||||
import WorkflowVariableBlockComponent from '../component'
|
||||
import { UPDATE_WORKFLOW_NODES_MAP } from '../index'
|
||||
import { WorkflowVariableBlockNode } from '../node'
|
||||
|
||||
const render: typeof renderWithConsoleQuery = (ui, options) => {
|
||||
const queryClient = createConsoleQueryClient()
|
||||
queryClient.setQueryData(consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(), {
|
||||
data: [],
|
||||
plugins: {},
|
||||
})
|
||||
return renderWithConsoleQuery(ui, { ...options, queryClient })
|
||||
}
|
||||
|
||||
const { mockVarLabel, mockIsExceptionVariable, mockForcedVariableKind } = vi.hoisted(() => ({
|
||||
mockVarLabel: vi.fn(),
|
||||
mockIsExceptionVariable: vi.fn<(variable: string, nodeType?: BlockEnum) => boolean>(() => false),
|
||||
|
||||
@ -1,15 +1,28 @@
|
||||
import type { ModelProviderSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { WorkflowNodesMap } from '@/app/components/base/prompt-editor/types'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { createConsoleQueryClient, renderHookWithConsoleQuery } from '@/test/console/query-data'
|
||||
import { useLlmModelPluginInstalled } from '../use-llm-model-plugin-installed'
|
||||
|
||||
let mockModelProviders: Array<{ provider: string }> = []
|
||||
const providerSummaryFixture = {
|
||||
provider: 'openai',
|
||||
plugin_id: 'langgenius/openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
configurate_methods: ['predefined-model'],
|
||||
supported_model_types: ['llm'],
|
||||
preferred_provider_type: 'custom',
|
||||
is_configured: true,
|
||||
system_configuration: { enabled: false },
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
available_credentials: [],
|
||||
current_credential_usable: true,
|
||||
has_custom_models: false,
|
||||
},
|
||||
} satisfies ModelProviderSummaryResponse
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContextSelector: <T>(
|
||||
selector: (state: { modelProviders: Array<{ provider: string }> }) => T,
|
||||
): T => selector({ modelProviders: mockModelProviders }),
|
||||
}))
|
||||
let mockModelProviders: Array<{ provider: string }> = []
|
||||
|
||||
const createWorkflowNodesMap = (node: Record<string, unknown>): WorkflowNodesMap =>
|
||||
({
|
||||
@ -20,6 +33,18 @@ const createWorkflowNodesMap = (node: Record<string, unknown>): WorkflowNodesMap
|
||||
},
|
||||
}) as unknown as WorkflowNodesMap
|
||||
|
||||
const renderHook: typeof renderHookWithConsoleQuery = (callback, options) => {
|
||||
const queryClient = createConsoleQueryClient()
|
||||
queryClient.setQueryData(consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(), {
|
||||
data: mockModelProviders.map(
|
||||
(provider) =>
|
||||
({ ...providerSummaryFixture, ...provider }) satisfies ModelProviderSummaryResponse,
|
||||
),
|
||||
plugins: {},
|
||||
})
|
||||
return renderHookWithConsoleQuery(callback, { ...options, queryClient })
|
||||
}
|
||||
|
||||
describe('useLlmModelPluginInstalled', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import type { WorkflowNodesMap } from '@/app/components/base/prompt-editor/types'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { extractPluginId } from '@/app/components/workflow/utils/plugin'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
|
||||
export function useLlmModelPluginInstalled(
|
||||
nodeId: string,
|
||||
@ -11,8 +12,13 @@ export function useLlmModelPluginInstalled(
|
||||
const modelProvider = node?.type === BlockEnum.LLM ? node.modelProvider : undefined
|
||||
const modelPluginId = modelProvider ? extractPluginId(modelProvider) : undefined
|
||||
|
||||
return useProviderContextSelector((state) => {
|
||||
if (!modelPluginId) return true
|
||||
return state.modelProviders.some((p) => extractPluginId(p.provider) === modelPluginId)
|
||||
})
|
||||
const { data: isInstalled = !modelPluginId } = useQuery(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions({
|
||||
select: (response) =>
|
||||
!modelPluginId ||
|
||||
response.data.some((provider) => extractPluginId(provider.provider) === modelPluginId),
|
||||
}),
|
||||
)
|
||||
|
||||
return isInstalled
|
||||
}
|
||||
|
||||
@ -204,22 +204,6 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', ()
|
||||
useModelModalHandler: () => vi.fn(),
|
||||
}))
|
||||
|
||||
// Mock provider-context
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
embeddingsModelList: [],
|
||||
rerankModelList: [],
|
||||
agentThoughtModelList: [],
|
||||
modelProviders: [],
|
||||
textEmbeddingModelList: [],
|
||||
speech2textModelList: [],
|
||||
ttsModelList: [],
|
||||
moderationModelList: [],
|
||||
hasSettedApiKey: true,
|
||||
plan: { type: 'free' },
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/datasets/common/check-rerank-model', () => ({
|
||||
isReRankModelSelected: () => true,
|
||||
}))
|
||||
|
||||
@ -75,7 +75,6 @@ vi.mock('@/service/use-common', async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
useMembers: vi.fn(() => ({ data: { accounts: [] }, refetch: vi.fn() })),
|
||||
useProviderContext: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -1,20 +1,25 @@
|
||||
import type { ModelProviderSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import { screen } from '@testing-library/react'
|
||||
import { renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import {
|
||||
CurrentSystemQuotaTypeEnum,
|
||||
CustomConfigurationStatusEnum,
|
||||
QuotaUnitEnum,
|
||||
} from '../declarations'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import ModelProviderPage from '../index'
|
||||
|
||||
const mockQuotaConfig = {
|
||||
quota_type: CurrentSystemQuotaTypeEnum.free,
|
||||
quota_unit: QuotaUnitEnum.times,
|
||||
quota_limit: 100,
|
||||
quota_used: 1,
|
||||
last_used: 0,
|
||||
is_valid: true,
|
||||
}
|
||||
const providerSummaryFixture = {
|
||||
provider: 'openai',
|
||||
plugin_id: 'langgenius/openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
configurate_methods: ['predefined-model'],
|
||||
supported_model_types: ['llm'],
|
||||
preferred_provider_type: 'custom',
|
||||
is_configured: true,
|
||||
system_configuration: { enabled: false },
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
available_credentials: [],
|
||||
current_credential_usable: true,
|
||||
has_custom_models: false,
|
||||
},
|
||||
} satisfies ModelProviderSummaryResponse
|
||||
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
@ -23,23 +28,6 @@ vi.mock('@/config', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
modelProviders: [
|
||||
{
|
||||
provider: 'openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
custom_configuration: { status: CustomConfigurationStatusEnum.active },
|
||||
system_configuration: {
|
||||
enabled: false,
|
||||
current_quota_type: CurrentSystemQuotaTypeEnum.free,
|
||||
quota_configurations: [mockQuotaConfig],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../hooks', () => ({
|
||||
useDefaultModel: () => ({ data: null, isLoading: false }),
|
||||
useLanguage: () => 'en_US',
|
||||
@ -128,6 +116,18 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
...originalWorkspaces,
|
||||
current: {
|
||||
...originalWorkspaces.current,
|
||||
modelProviders: {
|
||||
summary: {
|
||||
get: {
|
||||
queryKey: () =>
|
||||
originalWorkspaces.current.modelProviders.summary.get.queryKey(),
|
||||
queryOptions: () => ({
|
||||
...originalWorkspaces.current.modelProviders.summary.get.queryOptions(),
|
||||
queryFn: () => new Promise(() => {}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
plugin: {
|
||||
...originalWorkspaces.current.plugin,
|
||||
list: {
|
||||
@ -176,9 +176,18 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
const renderPage: typeof renderWithConsoleQuery = (ui, options) => {
|
||||
const queryClient = createConsoleQueryClient()
|
||||
queryClient.setQueryData(consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(), {
|
||||
data: [{ ...providerSummaryFixture } satisfies ModelProviderSummaryResponse],
|
||||
plugins: {},
|
||||
})
|
||||
return renderWithConsoleQuery(ui, { ...options, queryClient })
|
||||
}
|
||||
|
||||
describe('ModelProviderPage non-cloud branch', () => {
|
||||
it('should skip the quota panel when cloud edition is disabled', () => {
|
||||
renderWithConsoleQuery(<ModelProviderPage searchText="" />, {
|
||||
renderPage(<ModelProviderPage searchText="" />, {
|
||||
systemFeatures: { enable_marketplace: false },
|
||||
})
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ModelProviderSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { PluginDeclaration, PluginDetail } from '@/app/components/plugins/types'
|
||||
import { act, fireEvent, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vite-plus/test'
|
||||
@ -6,7 +7,8 @@ import {
|
||||
getStepByStepTourTargetSelector,
|
||||
STEP_BY_STEP_TOUR_TARGETS,
|
||||
} from '@/app/components/step-by-step-tour/target-registry'
|
||||
import { renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import {
|
||||
CurrentSystemQuotaTypeEnum,
|
||||
CustomConfigurationStatusEnum,
|
||||
@ -15,6 +17,23 @@ import {
|
||||
} from '../declarations'
|
||||
import ModelProviderPage from '../index'
|
||||
|
||||
const providerSummaryFixture = {
|
||||
provider: 'openai',
|
||||
plugin_id: 'langgenius/openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
configurate_methods: ['predefined-model'],
|
||||
supported_model_types: ['llm'],
|
||||
preferred_provider_type: 'custom',
|
||||
is_configured: true,
|
||||
system_configuration: { enabled: false },
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
available_credentials: [],
|
||||
current_credential_usable: true,
|
||||
has_custom_models: false,
|
||||
},
|
||||
} satisfies ModelProviderSummaryResponse
|
||||
|
||||
type MockReferenceSetting = {
|
||||
permission: Record<string, never>
|
||||
auto_upgrade?: {
|
||||
@ -52,11 +71,10 @@ const { mockReferenceSetting, mockAutoUpgradeError } = vi.hoisted(() => ({
|
||||
},
|
||||
}))
|
||||
|
||||
const { mockProviderContextState, mockRefreshModelProviders } = vi.hoisted(() => ({
|
||||
mockProviderContextState: {
|
||||
isLoadingModelProviders: false,
|
||||
isSuccessModelProviders: true,
|
||||
modelProviderPlugins: {} as Record<
|
||||
const { mockSummaryState } = vi.hoisted(() => ({
|
||||
mockSummaryState: {
|
||||
isLoading: false,
|
||||
plugins: {} as Record<
|
||||
string,
|
||||
{
|
||||
installation_id: string
|
||||
@ -68,7 +86,6 @@ const { mockProviderContextState, mockRefreshModelProviders } = vi.hoisted(() =>
|
||||
}
|
||||
>,
|
||||
},
|
||||
mockRefreshModelProviders: vi.fn(),
|
||||
}))
|
||||
|
||||
const { mockInstalledModelPlugins, mockUseInstalledPluginList } = vi.hoisted(() => ({
|
||||
@ -87,22 +104,6 @@ const mockQuotaConfig = {
|
||||
is_valid: true,
|
||||
}
|
||||
|
||||
const renderModelProviderPage = (
|
||||
props: {
|
||||
enableMarketplace?: boolean
|
||||
searchText?: string
|
||||
stickyToolbar?: boolean
|
||||
} = {},
|
||||
) => {
|
||||
const { searchText = '', enableMarketplace = true, stickyToolbar = true } = props
|
||||
return renderWithConsoleQuery(
|
||||
<ModelProviderPage searchText={searchText} stickyToolbar={stickyToolbar} />,
|
||||
{
|
||||
systemFeatures: { enable_marketplace: enableMarketplace },
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const saveUpdateSettings = () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
}
|
||||
@ -214,20 +215,52 @@ const mockProviders: MockProvider[] = [
|
||||
},
|
||||
]
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
modelProviders: mockProviders.map((provider) => ({
|
||||
...provider,
|
||||
is_configured:
|
||||
provider.custom_configuration.status === CustomConfigurationStatusEnum.active ||
|
||||
provider.system_configuration.enabled,
|
||||
})),
|
||||
modelProviderPlugins: mockProviderContextState.modelProviderPlugins,
|
||||
isLoadingModelProviders: mockProviderContextState.isLoadingModelProviders,
|
||||
isSuccessModelProviders: mockProviderContextState.isSuccessModelProviders,
|
||||
refreshModelProviders: mockRefreshModelProviders,
|
||||
}),
|
||||
}))
|
||||
const renderModelProviderPage = (
|
||||
props: {
|
||||
enableMarketplace?: boolean
|
||||
searchText?: string
|
||||
stickyToolbar?: boolean
|
||||
} = {},
|
||||
) => {
|
||||
const { searchText = '', enableMarketplace = true, stickyToolbar = true } = props
|
||||
const queryClient = createConsoleQueryClient()
|
||||
if (mockSummaryState.isLoading) {
|
||||
queryClient.setQueryDefaults(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(),
|
||||
{ queryFn: () => new Promise(() => {}) },
|
||||
)
|
||||
} else {
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(),
|
||||
{
|
||||
data: mockProviders.map(
|
||||
(provider) =>
|
||||
({
|
||||
...providerSummaryFixture,
|
||||
...{
|
||||
...provider,
|
||||
is_configured:
|
||||
provider.custom_configuration.status === CustomConfigurationStatusEnum.active ||
|
||||
provider.system_configuration.enabled,
|
||||
},
|
||||
custom_configuration: {
|
||||
...providerSummaryFixture.custom_configuration,
|
||||
...provider.custom_configuration,
|
||||
},
|
||||
}) satisfies ModelProviderSummaryResponse,
|
||||
),
|
||||
plugins: mockSummaryState.plugins,
|
||||
},
|
||||
)
|
||||
}
|
||||
return renderWithConsoleQuery(
|
||||
<ModelProviderPage searchText={searchText} stickyToolbar={stickyToolbar} />,
|
||||
{
|
||||
queryClient,
|
||||
systemFeatures: { enable_marketplace: enableMarketplace },
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const mockDefaultModels: Record<string, { data: unknown; isLoading: boolean }> = {
|
||||
llm: { data: null, isLoading: false },
|
||||
@ -341,6 +374,18 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
...originalWorkspaces,
|
||||
current: {
|
||||
...originalWorkspaces.current,
|
||||
modelProviders: {
|
||||
summary: {
|
||||
get: {
|
||||
queryKey: () =>
|
||||
originalWorkspaces.current.modelProviders.summary.get.queryKey(),
|
||||
queryOptions: () => ({
|
||||
...originalWorkspaces.current.modelProviders.summary.get.queryOptions(),
|
||||
queryFn: () => new Promise(() => {}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
plugin: {
|
||||
...originalWorkspaces.current.plugin,
|
||||
list: {
|
||||
@ -394,11 +439,9 @@ describe('ModelProviderPage', () => {
|
||||
vi.useFakeTimers()
|
||||
vi.clearAllMocks()
|
||||
mockUseInstalledPluginList.mockClear()
|
||||
mockRefreshModelProviders.mockClear()
|
||||
mockInstalledModelPlugins.value = []
|
||||
mockProviderContextState.isLoadingModelProviders = false
|
||||
mockProviderContextState.isSuccessModelProviders = true
|
||||
mockProviderContextState.modelProviderPlugins = {}
|
||||
mockSummaryState.isLoading = false
|
||||
mockSummaryState.plugins = {}
|
||||
mockPluginSettingsAccess.canSetPluginPreferences = true
|
||||
mockAutoUpgradeError.value = undefined
|
||||
mockReferenceSetting.auto_upgrade = {
|
||||
@ -608,7 +651,7 @@ describe('ModelProviderPage', () => {
|
||||
quota_configurations: [mockQuotaConfig],
|
||||
},
|
||||
})
|
||||
mockProviderContextState.modelProviderPlugins = {
|
||||
mockSummaryState.plugins = {
|
||||
'langgenius/openai-marketplace': {
|
||||
installation_id: 'openai-installation',
|
||||
plugin_id: 'langgenius/openai-marketplace',
|
||||
@ -652,7 +695,7 @@ describe('ModelProviderPage', () => {
|
||||
})
|
||||
|
||||
it('should not refresh providers when remote plugin metadata already comes from summary', () => {
|
||||
mockProviderContextState.modelProviderPlugins = {
|
||||
mockSummaryState.plugins = {
|
||||
'langgenius/debug-model': {
|
||||
installation_id: 'debug-installation',
|
||||
plugin_id: 'langgenius/debug-model',
|
||||
@ -664,8 +707,6 @@ describe('ModelProviderPage', () => {
|
||||
}
|
||||
|
||||
renderModelProviderPage()
|
||||
|
||||
expect(mockRefreshModelProviders).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should render remote source from the authoritative summary plugin entry', () => {
|
||||
@ -680,7 +721,7 @@ describe('ModelProviderPage', () => {
|
||||
quota_configurations: [mockQuotaConfig],
|
||||
},
|
||||
})
|
||||
mockProviderContextState.modelProviderPlugins = {
|
||||
mockSummaryState.plugins = {
|
||||
'langgenius/openai': {
|
||||
installation_id: 'openai-debug-installation',
|
||||
plugin_id: 'langgenius/openai',
|
||||
@ -698,12 +739,10 @@ describe('ModelProviderPage', () => {
|
||||
'langgenius/openai',
|
||||
)
|
||||
expect(screen.getByTestId('provider-card')).toHaveAttribute('data-plugin-source', 'remote')
|
||||
expect(mockRefreshModelProviders).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should show provider placeholders while model providers are loading', () => {
|
||||
mockProviderContextState.isLoadingModelProviders = true
|
||||
mockProviderContextState.isSuccessModelProviders = false
|
||||
mockSummaryState.isLoading = true
|
||||
|
||||
renderModelProviderPage()
|
||||
|
||||
@ -923,7 +962,7 @@ describe('ModelProviderPage', () => {
|
||||
},
|
||||
},
|
||||
)
|
||||
mockProviderContextState.modelProviderPlugins = {
|
||||
mockSummaryState.plugins = {
|
||||
'langgenius/debug-model': {
|
||||
installation_id: 'debug-installation',
|
||||
plugin_id: 'langgenius/debug-model',
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type {
|
||||
ModelProviderPluginSummaryResponse,
|
||||
ModelProviderSummaryListResponse,
|
||||
ModelProviderSummaryResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ReactNode } from 'react'
|
||||
@ -11,7 +12,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import { SearchInput } from '@/app/components/base/search-input'
|
||||
import { usePluginSettingsAccess } from '@/app/components/plugins/plugin-page/use-reference-setting'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import UpdateSettingDialog from '../update-setting-dialog'
|
||||
@ -20,6 +20,9 @@ import { useDefaultModel } from './hooks'
|
||||
import ModelProviderPageBody from './model-provider-page-body'
|
||||
import SystemModelSelector from './system-model-selector'
|
||||
|
||||
const EMPTY_MODEL_PROVIDERS: ModelProviderSummaryListResponse['data'] = []
|
||||
const EMPTY_MODEL_PROVIDER_PLUGINS: ModelProviderSummaryListResponse['plugins'] = {}
|
||||
|
||||
type SystemModelConfigStatus =
|
||||
| 'no-provider'
|
||||
| 'none-configured'
|
||||
@ -68,11 +71,11 @@ const ModelProviderPage = ({
|
||||
ModelTypeEnum.tts,
|
||||
defaultModelQueryOptions,
|
||||
)
|
||||
const {
|
||||
modelProviders: providers,
|
||||
modelProviderPlugins = {},
|
||||
isLoadingModelProviders,
|
||||
} = useProviderContext()
|
||||
const { data: providerSummary, isLoading: isLoadingModelProviders } = useQuery(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions(),
|
||||
)
|
||||
const providers = providerSummary?.data ?? EMPTY_MODEL_PROVIDERS
|
||||
const modelProviderPlugins = providerSummary?.plugins ?? EMPTY_MODEL_PROVIDER_PLUGINS
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
|
||||
const marketplacePluginIds = useMemo(
|
||||
|
||||
@ -41,12 +41,6 @@ let activeTextGenerationModelList: Array<Record<string, unknown>> = [
|
||||
},
|
||||
]
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
isAPIKeySet: true,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
useModelParameterRules: () => ({
|
||||
data: {
|
||||
|
||||
@ -36,9 +36,6 @@ vi.mock('nuqs', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({ modelProviders: mockModelProviders.current }),
|
||||
}))
|
||||
vi.mock('../../provider-added-card/use-credential-panel-state', () => ({
|
||||
useCredentialPanelState: () => ({
|
||||
variant: 'credits-active',
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type {
|
||||
ModelProviderSummaryResponse,
|
||||
ProviderModelWithStatusEntity,
|
||||
ProviderWithModelsResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
@ -8,6 +9,7 @@ import type { ModelSelectorPreviewPayload } from '../popup-item'
|
||||
import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { commonQueryKeys } from '@/service/use-common'
|
||||
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import {
|
||||
@ -20,6 +22,23 @@ import {
|
||||
} from '../../declarations'
|
||||
import PopupItem from '../popup-item'
|
||||
|
||||
const providerSummaryFixture = {
|
||||
provider: 'openai',
|
||||
plugin_id: 'langgenius/openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
configurate_methods: ['predefined-model'],
|
||||
supported_model_types: ['llm'],
|
||||
preferred_provider_type: 'custom',
|
||||
is_configured: true,
|
||||
system_configuration: { enabled: false },
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
available_credentials: [],
|
||||
current_credential_usable: true,
|
||||
has_custom_models: false,
|
||||
},
|
||||
} satisfies ModelProviderSummaryResponse
|
||||
|
||||
const mockUpdateModelList = vi.hoisted(() => vi.fn())
|
||||
const mockUpdateModelProviders = vi.hoisted(() => vi.fn())
|
||||
const mockUseLanguage = vi.hoisted(() => vi.fn(() => 'en_US'))
|
||||
@ -88,10 +107,7 @@ vi.mock('@/context/modal-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockUseProviderContext = vi.hoisted(() => vi.fn())
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: mockUseProviderContext,
|
||||
}))
|
||||
const mockProviderSummary = vi.hoisted(() => vi.fn())
|
||||
|
||||
const mockConsoleStateReader = vi.hoisted(() => vi.fn())
|
||||
const mockWorkspacePermissionKeys = vi.hoisted(() => ({
|
||||
@ -137,7 +153,12 @@ const makeModel = (
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const makeProvider = (overrides: Record<string, unknown> = {}) => ({
|
||||
const makeProvider = (
|
||||
overrides: Partial<Omit<ModelProviderSummaryResponse, 'custom_configuration'>> & {
|
||||
custom_configuration?: Partial<ModelProviderSummaryResponse['custom_configuration']>
|
||||
} = {},
|
||||
) => ({
|
||||
...providerSummaryFixture,
|
||||
provider: 'openai',
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
custom_configuration: {
|
||||
@ -160,6 +181,10 @@ const renderPopupItem = (node: ReactElement) => {
|
||||
queryClient.setQueryData(commonQueryKeys.modelProviderDetails, {
|
||||
data: [makeProvider()],
|
||||
})
|
||||
queryClient.setQueryData(consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(), {
|
||||
...mockProviderSummary(),
|
||||
plugins: {},
|
||||
})
|
||||
return renderWithConsoleQuery(createPopupItemNode(node), { queryClient })
|
||||
}
|
||||
|
||||
@ -168,8 +193,8 @@ describe('PopupItem', () => {
|
||||
vi.clearAllMocks()
|
||||
mockWorkspacePermissionKeys.value = ['credential.use', 'credential.create', 'credential.manage']
|
||||
mockUseLanguage.mockReturnValue('en_US')
|
||||
mockUseProviderContext.mockReturnValue({
|
||||
modelProviders: [makeProvider()],
|
||||
mockProviderSummary.mockReturnValue({
|
||||
data: [makeProvider()],
|
||||
})
|
||||
mockConsoleStateReader.mockReturnValue({
|
||||
currentWorkspace: { trial_credits: 200, trial_credits_used: 0 },
|
||||
@ -187,8 +212,8 @@ describe('PopupItem', () => {
|
||||
})
|
||||
|
||||
it('should render nothing when provider is not found in modelProviders', () => {
|
||||
mockUseProviderContext.mockReturnValue({
|
||||
modelProviders: [],
|
||||
mockProviderSummary.mockReturnValue({
|
||||
data: [],
|
||||
})
|
||||
|
||||
const { container } = renderPopupItem(
|
||||
@ -423,8 +448,8 @@ describe('PopupItem', () => {
|
||||
})
|
||||
|
||||
it('should show configure required when no credential name', () => {
|
||||
mockUseProviderContext.mockReturnValue({
|
||||
modelProviders: [
|
||||
mockProviderSummary.mockReturnValue({
|
||||
data: [
|
||||
makeProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
@ -450,8 +475,8 @@ describe('PopupItem', () => {
|
||||
})
|
||||
|
||||
it('should show credits info when using system provider with remaining credits', () => {
|
||||
mockUseProviderContext.mockReturnValue({
|
||||
modelProviders: [
|
||||
mockProviderSummary.mockReturnValue({
|
||||
data: [
|
||||
makeProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.system,
|
||||
}),
|
||||
@ -474,8 +499,8 @@ describe('PopupItem', () => {
|
||||
})
|
||||
|
||||
it('should show credits exhausted when system provider has no credits', () => {
|
||||
mockUseProviderContext.mockReturnValue({
|
||||
modelProviders: [
|
||||
mockProviderSummary.mockReturnValue({
|
||||
data: [
|
||||
makeProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.system,
|
||||
}),
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type {
|
||||
ModelProviderPluginSummaryResponse,
|
||||
ModelProviderSummaryResponse,
|
||||
ProviderModelWithStatusEntity,
|
||||
ProviderWithModelsResponse,
|
||||
@ -6,10 +7,11 @@ import type {
|
||||
import type { ReactElement } from 'react'
|
||||
import type { PopupProps } from '../popup'
|
||||
import { Popover, PopoverContent, PopoverTitle, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useState } from 'react'
|
||||
import { renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
ModelFeatureEnum,
|
||||
@ -18,6 +20,23 @@ import {
|
||||
} from '../../declarations'
|
||||
import Popup from '../popup'
|
||||
|
||||
const providerSummaryFixture = {
|
||||
provider: 'openai',
|
||||
plugin_id: 'langgenius/openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
configurate_methods: ['predefined-model'],
|
||||
supported_model_types: ['llm'],
|
||||
preferred_provider_type: 'custom',
|
||||
is_configured: true,
|
||||
system_configuration: { enabled: false },
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
available_credentials: [],
|
||||
current_credential_usable: true,
|
||||
has_custom_models: false,
|
||||
},
|
||||
} satisfies ModelProviderSummaryResponse
|
||||
|
||||
let mockLanguage = 'en_US'
|
||||
|
||||
vi.mock(
|
||||
@ -32,7 +51,7 @@ vi.mock('@/utils/tool-call', () => ({
|
||||
supportFunctionCall: mockSupportFunctionCall,
|
||||
}))
|
||||
|
||||
type MockContextProvider = Pick<
|
||||
type MockSummaryProvider = Pick<
|
||||
ModelProviderSummaryResponse,
|
||||
| 'provider'
|
||||
| 'label'
|
||||
@ -42,10 +61,10 @@ type MockContextProvider = Pick<
|
||||
| 'system_configuration'
|
||||
>
|
||||
|
||||
const mockContextModelProviders = vi.hoisted(() => ({
|
||||
current: [] as MockContextProvider[],
|
||||
const mockModelProviders = vi.hoisted(() => ({
|
||||
current: [] as MockSummaryProvider[],
|
||||
}))
|
||||
const mockContextModelProviderPlugins = vi.hoisted(() => ({
|
||||
const mockModelProviderPlugins = vi.hoisted(() => ({
|
||||
current: {} as Record<string, { plugin_id: string }>,
|
||||
}))
|
||||
const mockTrialModels = vi.hoisted(() => ({
|
||||
@ -70,13 +89,6 @@ vi.mock('../popup-item', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
modelProviders: mockContextModelProviders.current,
|
||||
modelProviderPlugins: mockContextModelProviderPlugins.current,
|
||||
}),
|
||||
}))
|
||||
|
||||
type PopupTestProps = Omit<PopupProps, 'inputValue' | 'onInputValueChange' | 'onSelect'>
|
||||
|
||||
function PopupHarness(props: PopupTestProps) {
|
||||
@ -114,8 +126,36 @@ function PopupContentHarness(props: PopupTestProps) {
|
||||
const renderPopup = (
|
||||
ui: ReactElement<PopupTestProps>,
|
||||
options: Parameters<typeof renderWithConsoleQuery>[1] = {},
|
||||
) =>
|
||||
renderWithConsoleQuery(ui, {
|
||||
) => {
|
||||
const queryClient = options.queryClient ?? createConsoleQueryClient()
|
||||
queryClient.setQueryData(consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(), {
|
||||
data: mockModelProviders.current.map(
|
||||
(provider) =>
|
||||
({
|
||||
...providerSummaryFixture,
|
||||
...provider,
|
||||
custom_configuration: {
|
||||
...providerSummaryFixture.custom_configuration,
|
||||
...provider.custom_configuration,
|
||||
},
|
||||
}) satisfies ModelProviderSummaryResponse,
|
||||
),
|
||||
plugins: Object.fromEntries(
|
||||
Object.entries(mockModelProviderPlugins.current).map(([id, plugin]) => [
|
||||
id,
|
||||
{
|
||||
installation_id: 'installation',
|
||||
plugin_unique_identifier: 'plugin:1.0.0',
|
||||
runtime_type: 'local',
|
||||
source: 'marketplace',
|
||||
version: '1.0.0',
|
||||
...plugin,
|
||||
} satisfies ModelProviderPluginSummaryResponse,
|
||||
]),
|
||||
),
|
||||
})
|
||||
return renderWithConsoleQuery(ui, {
|
||||
queryClient,
|
||||
...options,
|
||||
systemFeatures:
|
||||
options.systemFeatures === null
|
||||
@ -127,6 +167,7 @@ const renderPopup = (
|
||||
},
|
||||
trialModels: options.trialModels ?? mockTrialModels.current,
|
||||
})
|
||||
}
|
||||
|
||||
const mockTrialCredits = vi.hoisted(() => ({
|
||||
credits: 200,
|
||||
@ -227,19 +268,19 @@ const makeModel = (
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const makeContextProvider = (
|
||||
overrides: Partial<MockContextProvider> = {},
|
||||
): MockContextProvider => ({
|
||||
const makeSummaryProvider = (
|
||||
overrides: Partial<MockSummaryProvider> = {},
|
||||
): MockSummaryProvider => ({
|
||||
provider: 'test-openai',
|
||||
label: { en_US: 'Test OpenAI', zh_Hans: 'Test OpenAI' },
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
icon_small_dark: { en_US: '', zh_Hans: '' },
|
||||
custom_configuration: {
|
||||
status: 'no-configure',
|
||||
} as MockContextProvider['custom_configuration'],
|
||||
} as MockSummaryProvider['custom_configuration'],
|
||||
system_configuration: {
|
||||
enabled: false,
|
||||
} as MockContextProvider['system_configuration'],
|
||||
} as MockSummaryProvider['system_configuration'],
|
||||
...overrides,
|
||||
})
|
||||
|
||||
@ -255,8 +296,8 @@ describe('Popup', () => {
|
||||
},
|
||||
},
|
||||
})
|
||||
mockContextModelProviders.current = []
|
||||
mockContextModelProviderPlugins.current = {}
|
||||
mockModelProviders.current = []
|
||||
mockModelProviderPlugins.current = {}
|
||||
mockTrialModels.current = ['test-openai', 'test-anthropic']
|
||||
Object.assign(mockTrialCredits, {
|
||||
credits: 200,
|
||||
@ -1004,12 +1045,12 @@ describe('Popup', () => {
|
||||
totalCredits: 200,
|
||||
isExhausted: true,
|
||||
})
|
||||
mockContextModelProviders.current = [
|
||||
makeContextProvider({
|
||||
mockModelProviders.current = [
|
||||
makeSummaryProvider({
|
||||
provider: 'test-openai',
|
||||
system_configuration: {
|
||||
enabled: true,
|
||||
} as MockContextProvider['system_configuration'],
|
||||
} as MockSummaryProvider['system_configuration'],
|
||||
}),
|
||||
]
|
||||
|
||||
@ -1021,49 +1062,66 @@ describe('Popup', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should only mark API key fallback when the current credential is usable', () => {
|
||||
it('should only mark API key fallback when the current credential is usable', async () => {
|
||||
Object.assign(mockTrialCredits, {
|
||||
credits: 0,
|
||||
totalCredits: 200,
|
||||
isExhausted: true,
|
||||
})
|
||||
mockContextModelProviders.current = [
|
||||
makeContextProvider({
|
||||
mockModelProviders.current = [
|
||||
makeSummaryProvider({
|
||||
provider: 'test-openai',
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
current_credential_usable: false,
|
||||
} as MockContextProvider['custom_configuration'],
|
||||
} as MockSummaryProvider['custom_configuration'],
|
||||
system_configuration: {
|
||||
enabled: true,
|
||||
} as MockContextProvider['system_configuration'],
|
||||
} as MockSummaryProvider['system_configuration'],
|
||||
}),
|
||||
]
|
||||
|
||||
const { rerender } = renderPopup(<PopupHarness modelList={[makeModel()]} onHide={vi.fn()} />)
|
||||
const { queryClient } = renderPopup(<PopupHarness modelList={[makeModel()]} onHide={vi.fn()} />)
|
||||
|
||||
expect(screen.getByTestId('credits-exhausted-alert')).toHaveAttribute(
|
||||
'data-has-api-key-fallback',
|
||||
'false',
|
||||
)
|
||||
|
||||
mockContextModelProviders.current = [
|
||||
makeContextProvider({
|
||||
mockModelProviders.current = [
|
||||
makeSummaryProvider({
|
||||
provider: 'test-openai',
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
current_credential_usable: true,
|
||||
} as MockContextProvider['custom_configuration'],
|
||||
} as MockSummaryProvider['custom_configuration'],
|
||||
system_configuration: {
|
||||
enabled: true,
|
||||
} as MockContextProvider['system_configuration'],
|
||||
} as MockSummaryProvider['system_configuration'],
|
||||
}),
|
||||
]
|
||||
rerender(<PopupHarness modelList={[makeModel()]} onHide={vi.fn()} />)
|
||||
act(() => {
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(),
|
||||
{
|
||||
data: mockModelProviders.current.map((provider) => ({
|
||||
...providerSummaryFixture,
|
||||
...provider,
|
||||
custom_configuration: {
|
||||
...providerSummaryFixture.custom_configuration,
|
||||
...provider.custom_configuration,
|
||||
},
|
||||
})),
|
||||
plugins: {},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
expect(screen.getByTestId('credits-exhausted-alert')).toHaveAttribute(
|
||||
'data-has-api-key-fallback',
|
||||
'true',
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('credits-exhausted-alert')).toHaveAttribute(
|
||||
'data-has-api-key-fallback',
|
||||
'true',
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
@ -1074,12 +1132,12 @@ describe('Popup', () => {
|
||||
isExhausted: true,
|
||||
})
|
||||
mockTrialModels.current = ['test-anthropic']
|
||||
mockContextModelProviders.current = [
|
||||
makeContextProvider({
|
||||
mockModelProviders.current = [
|
||||
makeSummaryProvider({
|
||||
provider: 'test-openai',
|
||||
system_configuration: {
|
||||
enabled: true,
|
||||
} as MockContextProvider['system_configuration'],
|
||||
} as MockSummaryProvider['system_configuration'],
|
||||
}),
|
||||
]
|
||||
|
||||
@ -1095,15 +1153,15 @@ describe('Popup', () => {
|
||||
isExhausted: true,
|
||||
})
|
||||
mockTrialModels.current = ['test-anthropic']
|
||||
mockContextModelProviders.current = [
|
||||
makeContextProvider({
|
||||
mockModelProviders.current = [
|
||||
makeSummaryProvider({
|
||||
provider: 'test-openai',
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
} as MockContextProvider['custom_configuration'],
|
||||
} as MockSummaryProvider['custom_configuration'],
|
||||
system_configuration: {
|
||||
enabled: true,
|
||||
} as MockContextProvider['system_configuration'],
|
||||
} as MockSummaryProvider['system_configuration'],
|
||||
}),
|
||||
]
|
||||
|
||||
@ -1179,8 +1237,8 @@ describe('Popup', () => {
|
||||
})
|
||||
|
||||
it('should render marketplace providers that are not installed', () => {
|
||||
mockContextModelProviders.current = [makeContextProvider({ provider: 'test-openai' })]
|
||||
mockContextModelProviderPlugins.current = {
|
||||
mockModelProviders.current = [makeSummaryProvider({ provider: 'test-openai' })]
|
||||
mockModelProviderPlugins.current = {
|
||||
'langgenius/openai': { plugin_id: 'langgenius/openai' },
|
||||
}
|
||||
|
||||
@ -1199,7 +1257,7 @@ describe('Popup', () => {
|
||||
})
|
||||
|
||||
it('should hide marketplace providers when marketplace is disabled', () => {
|
||||
mockContextModelProviders.current = [makeContextProvider({ provider: 'test-openai' })]
|
||||
mockModelProviders.current = [makeSummaryProvider({ provider: 'test-openai' })]
|
||||
|
||||
renderPopup(
|
||||
<PopupHarness modelList={[makeModel({ provider: 'test-openai' })]} onHide={vi.fn()} />,
|
||||
@ -1218,15 +1276,15 @@ describe('Popup', () => {
|
||||
})
|
||||
|
||||
it('should show installed marketplace providers without models when AI credits are available', () => {
|
||||
mockContextModelProviders.current = [
|
||||
makeContextProvider({
|
||||
mockModelProviders.current = [
|
||||
makeSummaryProvider({
|
||||
provider: 'test-anthropic',
|
||||
system_configuration: {
|
||||
enabled: true,
|
||||
} as MockContextProvider['system_configuration'],
|
||||
} as MockSummaryProvider['system_configuration'],
|
||||
}),
|
||||
]
|
||||
mockContextModelProviderPlugins.current = {
|
||||
mockModelProviderPlugins.current = {
|
||||
'langgenius/anthropic': { plugin_id: 'langgenius/anthropic' },
|
||||
}
|
||||
|
||||
@ -1242,15 +1300,15 @@ describe('Popup', () => {
|
||||
totalCredits: 200,
|
||||
isExhausted: true,
|
||||
})
|
||||
mockContextModelProviders.current = [
|
||||
makeContextProvider({
|
||||
mockModelProviders.current = [
|
||||
makeSummaryProvider({
|
||||
provider: 'test-anthropic',
|
||||
system_configuration: {
|
||||
enabled: true,
|
||||
} as MockContextProvider['system_configuration'],
|
||||
} as MockSummaryProvider['system_configuration'],
|
||||
}),
|
||||
]
|
||||
mockContextModelProviderPlugins.current = {
|
||||
mockModelProviderPlugins.current = {
|
||||
'langgenius/anthropic': { plugin_id: 'langgenius/anthropic' },
|
||||
}
|
||||
|
||||
@ -1276,7 +1334,7 @@ describe('Popup', () => {
|
||||
})
|
||||
|
||||
it('should hide a marketplace provider when its plugin is already installed', () => {
|
||||
mockContextModelProviderPlugins.current = {
|
||||
mockModelProviderPlugins.current = {
|
||||
'langgenius/openai': { plugin_id: 'langgenius/openai' },
|
||||
}
|
||||
|
||||
|
||||
@ -13,12 +13,13 @@ import { Popover, PopoverContent, PopoverTitle, PopoverTrigger } from '@langgeni
|
||||
import { PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useCallback, useId, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useCredentialPermissions } from '@/hooks/use-credential-permissions'
|
||||
import { renderI18nObject } from '@/i18n-config'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { ConfigurationMethodEnum, ModelStatusEnum } from '../declarations'
|
||||
import {
|
||||
useLanguage,
|
||||
@ -68,10 +69,13 @@ function PopupItem({
|
||||
const providerLabel = renderI18nObject(model.label, language)
|
||||
const suggestionTip = t(($) => $['modelProvider.selector.suggestionTip'], { ns: 'common' })
|
||||
const { setShowModelModal } = useModalContext()
|
||||
const { modelProviders } = useProviderContext()
|
||||
const { data: currentProvider } = useQuery(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions({
|
||||
select: (response) => response.data.find((provider) => provider.provider === model.provider),
|
||||
}),
|
||||
)
|
||||
const updateModelList = useUpdateModelList()
|
||||
const updateModelProviders = useUpdateModelProviders()
|
||||
const currentProvider = modelProviders.find((provider) => provider.provider === model.provider)
|
||||
const { providerDetail, loadProviderDetail } = useLazyModelProviderDetail(model.provider)
|
||||
const { canUseCredential, canCreateCredential, canManageCredential } = useCredentialPermissions()
|
||||
const canOpenCredentialDropdown = canUseCredential || canCreateCredential || canManageCredential
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ModelProviderSummaryListResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ModelSelectorPreviewPayload } from './popup-item'
|
||||
import type {
|
||||
ModelSelectorModel,
|
||||
@ -19,7 +20,6 @@ import checkTaskStatus from '@/app/components/plugins/install-plugin/base/check-
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
import useWorkspacePluginInstallPermission from '@/app/components/plugins/install-plugin/hooks/use-workspace-plugin-install-permission'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { renderI18nObject } from '@/i18n-config'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
@ -51,6 +51,9 @@ import {
|
||||
ShowIncompatibleModelsButton,
|
||||
} from './popup-layout'
|
||||
|
||||
const EMPTY_MODEL_PROVIDERS: ModelProviderSummaryListResponse['data'] = []
|
||||
const EMPTY_MODEL_PROVIDER_PLUGINS: ModelProviderSummaryListResponse['plugins'] = {}
|
||||
|
||||
export type PopupProps = {
|
||||
defaultModel?: ModelSelectorValue
|
||||
inputValue: string
|
||||
@ -88,7 +91,11 @@ function Popup({
|
||||
)
|
||||
const [marketplaceCollapsed, setMarketplaceCollapsed] = useState(false)
|
||||
const [showIncompatibleModels, setShowIncompatibleModels] = useState(false)
|
||||
const { modelProviders, modelProviderPlugins = {} } = useProviderContext()
|
||||
const { data: providerSummary } = useQuery(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions(),
|
||||
)
|
||||
const modelProviders = providerSummary?.data ?? EMPTY_MODEL_PROVIDERS
|
||||
const modelProviderPlugins = providerSummary?.plugins ?? EMPTY_MODEL_PROVIDER_PLUGINS
|
||||
const { data: enableMarketplace } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
select: (systemFeatures) => systemFeatures.enable_marketplace,
|
||||
|
||||
@ -4,6 +4,7 @@ import type { ModelProviderPluginSummary } from '../../index'
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import { commonQueryKeys } from '@/service/use-common'
|
||||
import { createQueryClientWrapper } from '@/test/console/query-client'
|
||||
import { seedSystemFeatures } from '@/test/console/query-data'
|
||||
import { render } from '@/test/console/render'
|
||||
@ -20,11 +21,11 @@ let mockWorkspacePermissionKeys: string[] = [
|
||||
'credential.create',
|
||||
'credential.manage',
|
||||
]
|
||||
const { mockInvalidateInstalledPluginList, mockProviderCardActions, mockRefreshModelProviders } =
|
||||
const { mockInvalidateInstalledPluginList, mockProviderCardActions, mockInvalidateQueries } =
|
||||
vi.hoisted(() => ({
|
||||
mockInvalidateInstalledPluginList: vi.fn(),
|
||||
mockProviderCardActions: vi.fn(),
|
||||
mockRefreshModelProviders: vi.fn(),
|
||||
mockInvalidateQueries: vi.fn(),
|
||||
}))
|
||||
const mockFetchModelProviderModels = vi.fn()
|
||||
const mockQueryOptions = vi.fn(
|
||||
@ -49,6 +50,7 @@ vi.mock('@/service/console', () => ({
|
||||
workspaces: {
|
||||
current: {
|
||||
modelProviders: {
|
||||
summary: { get: { key: () => ['model-provider-summary'] } },
|
||||
byProvider: {
|
||||
models: {
|
||||
get: {
|
||||
@ -80,11 +82,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContextSelector: (selector: (state: object) => unknown) =>
|
||||
selector({ refreshModelProviders: mockRefreshModelProviders }),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
useInvalidateInstalledPluginList: () => mockInvalidateInstalledPluginList,
|
||||
}))
|
||||
@ -142,6 +139,7 @@ const createConsoleQueryClient = () =>
|
||||
|
||||
const renderWithQueryClient = (node: ReactElement) => {
|
||||
const queryClient = createConsoleQueryClient()
|
||||
vi.spyOn(queryClient, 'invalidateQueries').mockImplementation(mockInvalidateQueries)
|
||||
seedSystemFeatures(queryClient, { rbac_enabled: mockRbacEnabled })
|
||||
return render(node, { wrapper: createQueryClientWrapper(queryClient) })
|
||||
}
|
||||
@ -249,7 +247,7 @@ describe('ProviderAddedCard', () => {
|
||||
it('refreshes provider data and installed plugin details after an update', async () => {
|
||||
let resolveProviderRefresh: (() => void) | undefined
|
||||
let resolveInstalledPluginRefresh: (() => void) | undefined
|
||||
mockRefreshModelProviders.mockReturnValue(
|
||||
mockInvalidateQueries.mockReturnValue(
|
||||
new Promise<void>((resolve) => {
|
||||
resolveProviderRefresh = resolve
|
||||
}),
|
||||
@ -279,7 +277,10 @@ describe('ProviderAddedCard', () => {
|
||||
})
|
||||
|
||||
expect(mockInvalidateInstalledPluginList).toHaveBeenCalledWith(PluginCategoryEnum.model)
|
||||
expect(mockRefreshModelProviders).toHaveBeenCalledOnce()
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['model-provider-summary'] })
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: commonQueryKeys.modelProviderDetails,
|
||||
})
|
||||
expect(mockInvalidateInstalledPluginList).toHaveBeenCalledTimes(1)
|
||||
|
||||
resolveProviderRefresh?.()
|
||||
|
||||
@ -4,17 +4,17 @@ import type { ModelProvider } from '../declarations'
|
||||
import type { ModelProviderPluginSummary } from '../index'
|
||||
import type { ModelProviderQuotaGetPaid } from '../utils'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useCredentialPermissions } from '@/hooks/use-credential-permissions'
|
||||
import { renderI18nObject } from '@/i18n-config'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { commonQueryKeys } from '@/service/use-common'
|
||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { useModelProviderListExpanded, useSetModelProviderListExpanded } from '../atoms'
|
||||
@ -56,7 +56,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
}),
|
||||
})
|
||||
const language = useLanguage()
|
||||
const refreshModelProviders = useProviderContextSelector((state) => state.refreshModelProviders)
|
||||
const queryClient = useQueryClient()
|
||||
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
|
||||
const currentProviderName = provider.provider
|
||||
const expanded = useModelProviderListExpanded(currentProviderName)
|
||||
@ -113,10 +113,13 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
|
||||
const refreshPluginData = useCallback(async () => {
|
||||
await Promise.all([
|
||||
refreshModelProviders(),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: consoleQuery.workspaces.current.modelProviders.summary.get.key(),
|
||||
}),
|
||||
queryClient.invalidateQueries({ queryKey: commonQueryKeys.modelProviderDetails }),
|
||||
invalidateInstalledPluginList(PluginCategoryEnum.model),
|
||||
])
|
||||
}, [invalidateInstalledPluginList, refreshModelProviders])
|
||||
}, [invalidateInstalledPluginList, queryClient])
|
||||
|
||||
const handleOpenModelList = useCallback(() => {
|
||||
if (loading) return
|
||||
|
||||
@ -2,6 +2,8 @@ import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/
|
||||
import type { OperationKey } from '@orpc/tanstack-query'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { commonQueryKeys } from '@/service/use-common'
|
||||
import { PluginCategoryEnum } from '../../../types'
|
||||
|
||||
// Mock invalidation / refresh functions
|
||||
@ -13,7 +15,7 @@ const mockRefetchRerankModelList = vi.fn()
|
||||
const mockRefetchSpeech2textModelList = vi.fn()
|
||||
const mockRefetchTTSModelList = vi.fn()
|
||||
const mockInvalidateDefaultModel = vi.fn()
|
||||
const mockRefreshModelProviders = vi.fn()
|
||||
const mockInvalidateQueries = vi.fn()
|
||||
const mockInvalidateAllToolProviders = vi.fn()
|
||||
const mockInvalidateAllBuiltInTools = vi.fn()
|
||||
const mockInvalidateAllDataSources = vi.fn()
|
||||
@ -41,10 +43,6 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', ()
|
||||
useInvalidateDefaultModel: () => mockInvalidateDefaultModel,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({ refreshModelProviders: mockRefreshModelProviders }),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-tools', () => ({
|
||||
useInvalidateAllToolProviders: () => mockInvalidateAllToolProviders,
|
||||
useInvalidateAllBuiltInTools: () => mockInvalidateAllBuiltInTools,
|
||||
@ -100,7 +98,12 @@ describe('useRefreshPluginList', () => {
|
||||
result.current.refreshPluginList({ category: PluginCategoryEnum.model } as never)
|
||||
|
||||
expect(mockInvalidateInstalledPluginList).toHaveBeenCalledWith(PluginCategoryEnum.model)
|
||||
expect(mockRefreshModelProviders).toHaveBeenCalledTimes(1)
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: consoleQuery.workspaces.current.modelProviders.summary.get.key(),
|
||||
})
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: commonQueryKeys.modelProviderDetails,
|
||||
})
|
||||
expect(mockRefetchLLMModelList).toHaveBeenCalledTimes(1)
|
||||
expect(mockRefetchEmbeddingModelList).toHaveBeenCalledTimes(1)
|
||||
expect(mockRefetchRerankModelList).toHaveBeenCalledTimes(1)
|
||||
@ -151,7 +154,12 @@ describe('useRefreshPluginList', () => {
|
||||
expect(mockInvalidateAllTriggerPlugins).toHaveBeenCalledTimes(1)
|
||||
expect(mockInvalidateAllDataSources).toHaveBeenCalledTimes(1)
|
||||
expect(mockInvalidateDataSourceListAuth).toHaveBeenCalledTimes(1)
|
||||
expect(mockRefreshModelProviders).toHaveBeenCalledTimes(1)
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: consoleQuery.workspaces.current.modelProviders.summary.get.key(),
|
||||
})
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: commonQueryKeys.modelProviderDetails,
|
||||
})
|
||||
expect(mockRefetchLLMModelList).toHaveBeenCalledTimes(1)
|
||||
expect(mockRefetchEmbeddingModelList).toHaveBeenCalledTimes(1)
|
||||
expect(mockRefetchRerankModelList).toHaveBeenCalledTimes(1)
|
||||
@ -168,7 +176,7 @@ describe('useRefreshPluginList', () => {
|
||||
|
||||
expect(mockInvalidateInstalledPluginList).toHaveBeenCalledTimes(1)
|
||||
expect(mockInvalidateAllToolProviders).not.toHaveBeenCalled()
|
||||
expect(mockRefreshModelProviders).not.toHaveBeenCalled()
|
||||
expect(mockInvalidateQueries).not.toHaveBeenCalled()
|
||||
expect(mockInvalidateAllDataSources).not.toHaveBeenCalled()
|
||||
expect(mockInvalidateAllTriggerPlugins).not.toHaveBeenCalled()
|
||||
expect(mockInvalidateStrategyProviders).not.toHaveBeenCalled()
|
||||
@ -180,7 +188,7 @@ describe('useRefreshPluginList', () => {
|
||||
result.current.refreshPluginList({ category: PluginCategoryEnum.tool } as never)
|
||||
|
||||
expect(mockInvalidateAllToolProviders).toHaveBeenCalledTimes(1)
|
||||
expect(mockRefreshModelProviders).not.toHaveBeenCalled()
|
||||
expect(mockInvalidateQueries).not.toHaveBeenCalled()
|
||||
expect(mockInvalidateAllDataSources).not.toHaveBeenCalled()
|
||||
expect(mockInvalidateAllTriggerPlugins).not.toHaveBeenCalled()
|
||||
expect(mockInvalidateStrategyProviders).not.toHaveBeenCalled()
|
||||
@ -191,6 +199,7 @@ vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQueryClient: () => ({ invalidateQueries: mockInvalidateQueries }),
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../../types'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useInvalidateDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { commonQueryKeys } from '@/service/use-common'
|
||||
import { useInvalidDataSourceListAuth } from '@/service/use-datasource'
|
||||
import { useInvalidDataSourceList } from '@/service/use-pipeline'
|
||||
import {
|
||||
@ -60,7 +60,7 @@ const useRefreshPluginList = () => {
|
||||
}),
|
||||
)
|
||||
const invalidateDefaultModel = useInvalidateDefaultModel()
|
||||
const { refreshModelProviders } = useProviderContext()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const invalidateAllToolProviders = useInvalidateAllToolProviders()
|
||||
const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools()
|
||||
@ -104,7 +104,10 @@ const useRefreshPluginList = () => {
|
||||
|
||||
// model select
|
||||
if ((manifest && PluginCategoryEnum.model.includes(manifest.category)) || refreshAllType) {
|
||||
refreshModelProviders()
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: consoleQuery.workspaces.current.modelProviders.summary.get.key(),
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: commonQueryKeys.modelProviderDetails })
|
||||
refetchLLMModelList()
|
||||
refetchEmbeddingModelList()
|
||||
refetchRerankModelList()
|
||||
|
||||
@ -35,13 +35,6 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
}))
|
||||
|
||||
// Mock provider context
|
||||
const mockProviderContextValue = {
|
||||
isAPIKeySet: true,
|
||||
modelProviders: [],
|
||||
}
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => mockProviderContextValue,
|
||||
}))
|
||||
|
||||
// Mock model list hook
|
||||
const mockTextGenerationList: ProviderWithModelsResponse[] = []
|
||||
@ -243,8 +236,6 @@ describe('ModelParameterModal', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockProviderContextValue.isAPIKeySet = true
|
||||
mockProviderContextValue.modelProviders = []
|
||||
setupModelLists()
|
||||
mockFetchAndMergeValidCompletionParams.mockResolvedValue({ params: {}, removedDetails: {} })
|
||||
})
|
||||
@ -660,7 +651,6 @@ describe('ModelParameterModal', () => {
|
||||
describe('Memoization - disabled', () => {
|
||||
it('should keep active TTS model settings available without an active text generation model', async () => {
|
||||
const user = userEvent.setup()
|
||||
mockProviderContextValue.isAPIKeySet = false
|
||||
const ttsModel = createModel({
|
||||
provider: 'tts-provider',
|
||||
models: [
|
||||
@ -693,7 +683,6 @@ describe('ModelParameterModal', () => {
|
||||
|
||||
it('should keep model selection available when isAPIKeySet is false', () => {
|
||||
// Arrange
|
||||
mockProviderContextValue.isAPIKeySet = false
|
||||
const model = createModel({
|
||||
provider: 'openai',
|
||||
models: [createModelItem({ model: 'gpt-4', status: ModelStatusEnum.active })],
|
||||
@ -737,7 +726,6 @@ describe('ModelParameterModal', () => {
|
||||
|
||||
it('should set disabled to false when all conditions are met', () => {
|
||||
// Arrange
|
||||
mockProviderContextValue.isAPIKeySet = true
|
||||
const model = createModel({
|
||||
provider: 'openai',
|
||||
models: [createModelItem({ model: 'gpt-4', status: ModelStatusEnum.active })],
|
||||
@ -1330,14 +1318,12 @@ describe('ModelParameterModal', () => {
|
||||
models: [createModelItem({ model: 'gpt-4', status: ModelStatusEnum.active })],
|
||||
})
|
||||
setupModelLists({ textGeneration: [model] })
|
||||
mockProviderContextValue.isAPIKeySet = true
|
||||
const props = createDefaultProps({ value: { provider: 'openai', model: 'gpt-4' } })
|
||||
|
||||
// Act
|
||||
const { rerender } = render(<ModelParameterModal {...props} />)
|
||||
expect(screen.getByTestId('trigger')).toHaveAttribute('data-disabled', 'false')
|
||||
|
||||
mockProviderContextValue.isAPIKeySet = false
|
||||
rerender(<ModelParameterModal {...props} />)
|
||||
|
||||
// Assert
|
||||
|
||||
@ -1,14 +1,42 @@
|
||||
import type { ModelProviderSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ReactElement } from 'react'
|
||||
import type { Collection } from '../../types'
|
||||
import { act, cleanup, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { commonQueryKeys } from '@/service/use-common'
|
||||
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import { AuthType, CollectionType } from '../../types'
|
||||
import ProviderDetail from '../detail'
|
||||
|
||||
const providerSummaryFixture = {
|
||||
provider: 'openai',
|
||||
plugin_id: 'langgenius/openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
configurate_methods: ['predefined-model'],
|
||||
supported_model_types: ['llm'],
|
||||
preferred_provider_type: 'custom',
|
||||
is_configured: true,
|
||||
system_configuration: { enabled: false },
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
available_credentials: [],
|
||||
current_credential_usable: true,
|
||||
has_custom_models: false,
|
||||
},
|
||||
} satisfies ModelProviderSummaryResponse
|
||||
|
||||
const render = (ui: ReactElement) => {
|
||||
const queryClient = createConsoleQueryClient()
|
||||
queryClient.setQueryData(consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(), {
|
||||
data: [
|
||||
{
|
||||
...providerSummaryFixture,
|
||||
...{ provider: 'model-collection-id' },
|
||||
} satisfies ModelProviderSummaryResponse,
|
||||
],
|
||||
plugins: {},
|
||||
})
|
||||
queryClient.setQueryData(commonQueryKeys.modelProviderDetails, {
|
||||
data: [{ provider: 'model-collection-id' }],
|
||||
})
|
||||
@ -48,12 +76,6 @@ vi.mock('@/context/modal-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
modelProviders: [{ provider: 'model-collection-id', name: 'TestModel' }],
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockFetchBuiltInToolList = vi.fn().mockResolvedValue([])
|
||||
const mockFetchCustomToolList = vi.fn().mockResolvedValue([])
|
||||
const mockFetchModelToolList = vi.fn().mockResolvedValue([])
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
Collection,
|
||||
CustomCollectionBackend,
|
||||
@ -29,7 +30,7 @@ import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -45,9 +46,9 @@ import ConfigCredential from '@/app/components/tools/setting/build-in/config-cre
|
||||
import { WorkflowToolDrawer } from '@/app/components/tools/workflow-tool'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useCredentialPermissions } from '@/hooks/use-credential-permissions'
|
||||
import { getLanguage } from '@/i18n-config/language'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import {
|
||||
deleteWorkflowTool,
|
||||
fetchBuiltInToolList,
|
||||
@ -92,20 +93,23 @@ const ProviderDetail = ({ collection, onHide, onRefreshData }: Props) => {
|
||||
// built in provider
|
||||
const [showSettingAuth, setShowSettingAuth] = useState(false)
|
||||
const { setShowModelModal } = useModalContext()
|
||||
const { modelProviders: providers } = useProviderContext()
|
||||
const { data: modelProvider } = useQuery(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions({
|
||||
select: (response) => response.data.find((provider) => provider.provider === collection?.id),
|
||||
}),
|
||||
)
|
||||
const queryClient = useQueryClient()
|
||||
const showSettingAuthModal = async () => {
|
||||
if (!canOpenCredentialSettings) return
|
||||
|
||||
if (isModel) {
|
||||
const summary = providers.find((item) => item.provider === collection?.id)
|
||||
if (!summary) return
|
||||
if (!modelProvider) return
|
||||
try {
|
||||
const response = await queryClient.query({
|
||||
...modelProviderDetailsQueryOptions(),
|
||||
staleTime: 'static',
|
||||
})
|
||||
const provider = response.data.find((item) => item.provider === summary.provider)
|
||||
const provider = response.data.find((item) => item.provider === modelProvider.provider)
|
||||
if (!provider) return
|
||||
setShowModelModal({
|
||||
payload: {
|
||||
|
||||
@ -154,12 +154,6 @@ vi.mock('@/context/i18n', () => ({
|
||||
useGetLanguage: () => 'en',
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContextSelector: (
|
||||
selector: (state: { modelProviders: Array<{ provider: string }> }) => unknown,
|
||||
) => selector({ modelProviders: mockModelProviders }),
|
||||
}))
|
||||
|
||||
// useWorkflowNodes reads from WorkflowContext (real store via renderWorkflowHook)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -864,6 +858,8 @@ vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) => {
|
||||
if (options.queryKey[0].includes('modelProviders') && options.queryKey[0].includes('summary'))
|
||||
return { data: mockModelProviders }
|
||||
if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options)
|
||||
return { data: [] }
|
||||
},
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ModelProviderSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { AgentNodeType } from '../nodes/agent/types'
|
||||
import type { DataSourceNodeType } from '../nodes/data-source/types'
|
||||
import type { KnowledgeBaseNodeType } from '../nodes/knowledge-base/types'
|
||||
@ -30,7 +31,6 @@ import { normalizeModelProviderModelsResponse } from '@/app/components/header/ac
|
||||
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
|
||||
import { MAX_TREE_DEPTH } from '@/config'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { agentSoulConfigToFormState } from '@/features/agent-v2/agent-composer/conversions'
|
||||
import {
|
||||
createAgentToolProviderCatalog,
|
||||
@ -80,6 +80,8 @@ import useNodesAvailableVarList, {
|
||||
import { useNodesMetaData } from './use-nodes-meta-data'
|
||||
import { useGetToolIcon } from './use-tool-icon'
|
||||
|
||||
const EMPTY_MODEL_PROVIDERS: ModelProviderSummaryResponse[] = []
|
||||
|
||||
export type ChecklistItem = {
|
||||
id: string
|
||||
type: BlockEnum | string
|
||||
@ -184,7 +186,11 @@ export const useChecklist = (nodes: Node[], edges: Edge[], options?: { flowType?
|
||||
const appMode = useAppStore.getState().appDetail?.mode
|
||||
const shouldCheckStartNode =
|
||||
appMode === AppModeEnum.WORKFLOW || appMode === AppModeEnum.ADVANCED_CHAT
|
||||
const modelProviders = useProviderContextSelector((s) => s.modelProviders)
|
||||
const { data: modelProviders = EMPTY_MODEL_PROVIDERS } = useQuery(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions({
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const workflowStore = useWorkflowStore()
|
||||
const configsMap = useHooksStore((s) => s.configsMap)
|
||||
|
||||
@ -618,7 +624,11 @@ export const useChecklistBeforePublish = () => {
|
||||
const store = useStoreApi()
|
||||
const { nodesMap: nodesExtraData } = useNodesMetaData()
|
||||
const { data: strategyProviders } = useStrategyProviders()
|
||||
const modelProviders = useProviderContextSelector((s) => s.modelProviders)
|
||||
const { data: modelProviders = EMPTY_MODEL_PROVIDERS } = useQuery(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions({
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const updateDatasetsDetail = useDatasetsDetailStore((s) => s.updateDatasetsDetail)
|
||||
const updateTimeRef = useRef(0)
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
@ -1,15 +1,33 @@
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import type { ModelProviderSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import {
|
||||
createCredentialState,
|
||||
createModel,
|
||||
createModelItem,
|
||||
createProviderMeta,
|
||||
} from '@/app/components/workflow/__tests__/model-provider-fixtures'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { createConsoleQueryClient, renderHookWithConsoleQuery } from '@/test/console/query-data'
|
||||
import { useEmbeddingModelStatus } from '../use-embedding-model-status'
|
||||
|
||||
const providerSummaryFixture = {
|
||||
provider: 'openai',
|
||||
plugin_id: 'langgenius/openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
configurate_methods: ['predefined-model'],
|
||||
supported_model_types: ['llm'],
|
||||
preferred_provider_type: 'custom',
|
||||
is_configured: true,
|
||||
system_configuration: { enabled: false },
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
available_credentials: [],
|
||||
current_credential_usable: true,
|
||||
has_custom_models: false,
|
||||
},
|
||||
} satisfies ModelProviderSummaryResponse
|
||||
|
||||
const mockUseCredentialPanelState = vi.hoisted(() => vi.fn())
|
||||
const mockUseProviderContext = vi.hoisted(() => vi.fn())
|
||||
const mockProviderSummary = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock(
|
||||
'@/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state',
|
||||
@ -18,18 +36,26 @@ vi.mock(
|
||||
}),
|
||||
)
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: mockUseProviderContext,
|
||||
}))
|
||||
const renderHook: typeof renderHookWithConsoleQuery = (callback, options) => {
|
||||
const queryClient = createConsoleQueryClient()
|
||||
queryClient.setQueryData(consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(), {
|
||||
...mockProviderSummary(),
|
||||
plugins: {},
|
||||
})
|
||||
return renderHookWithConsoleQuery(callback, { ...options, queryClient })
|
||||
}
|
||||
|
||||
describe('useEmbeddingModelStatus', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUseProviderContext.mockReturnValue({
|
||||
modelProviders: [
|
||||
createProviderMeta({
|
||||
supported_model_types: [ModelTypeEnum.textEmbedding],
|
||||
}),
|
||||
mockProviderSummary.mockReturnValue({
|
||||
data: [
|
||||
{
|
||||
...providerSummaryFixture,
|
||||
...{
|
||||
supported_model_types: [ModelTypeEnum.textEmbedding],
|
||||
},
|
||||
} satisfies ModelProviderSummaryResponse,
|
||||
],
|
||||
})
|
||||
mockUseCredentialPanelState.mockReturnValue(createCredentialState())
|
||||
|
||||
@ -3,11 +3,11 @@ import type {
|
||||
ProviderModelWithStatusEntity,
|
||||
ProviderWithModelsResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useMemo } from 'react'
|
||||
import { deriveModelStatus } from '@/app/components/header/account-setting/model-provider-page/derive-model-status'
|
||||
import { useCredentialPanelState } from '@/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
|
||||
type UseEmbeddingModelStatusProps = {
|
||||
embeddingModel?: string
|
||||
@ -16,7 +16,7 @@ type UseEmbeddingModelStatusProps = {
|
||||
}
|
||||
|
||||
type UseEmbeddingModelStatusResult = {
|
||||
providerMeta: ModelProviderSummaryResponse | ModelProvider | undefined
|
||||
providerMeta: ModelProviderSummaryResponse | undefined
|
||||
modelProvider: ProviderWithModelsResponse | undefined
|
||||
currentModel: ProviderModelWithStatusEntity | undefined
|
||||
status: ReturnType<typeof deriveModelStatus>
|
||||
@ -27,11 +27,12 @@ export const useEmbeddingModelStatus = ({
|
||||
embeddingModelProvider,
|
||||
embeddingModelList,
|
||||
}: UseEmbeddingModelStatusProps): UseEmbeddingModelStatusResult => {
|
||||
const { modelProviders } = useProviderContext()
|
||||
|
||||
const providerMeta = useMemo(() => {
|
||||
return modelProviders.find((provider) => provider.provider === embeddingModelProvider)
|
||||
}, [embeddingModelProvider, modelProviders])
|
||||
const { data: providerMeta } = useQuery(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions({
|
||||
select: (response) =>
|
||||
response.data.find((provider) => provider.provider === embeddingModelProvider),
|
||||
}),
|
||||
)
|
||||
|
||||
const modelProvider = useMemo(() => {
|
||||
return embeddingModelList.find((provider) => provider.provider === embeddingModelProvider)
|
||||
|
||||
@ -1,26 +1,36 @@
|
||||
import type { ModelProviderSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { LLMNodeType } from '../types'
|
||||
import type { ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { ModelParameterModalProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import type { PanelProps } from '@/types/workflow'
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { createMockProviderContextValue } from '@/__mocks__/provider-context'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
CurrentSystemQuotaTypeEnum,
|
||||
CustomConfigurationStatusEnum,
|
||||
ModelTypeEnum,
|
||||
PreferredProviderTypeEnum,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { renderWorkflowFlowComponent } from '@/app/components/workflow/__tests__/workflow-test-env'
|
||||
import { ProviderContext } from '@/context/provider-context'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { createConsoleQueryWrapper } from '@/test/console/query-data'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { FlowType } from '@/types/common'
|
||||
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
|
||||
import { BlockEnum } from '../../../types'
|
||||
import Panel from '../panel'
|
||||
|
||||
const providerSummaryFixture = {
|
||||
provider: 'openai',
|
||||
plugin_id: 'langgenius/openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
configurate_methods: ['predefined-model'],
|
||||
supported_model_types: ['llm'],
|
||||
preferred_provider_type: 'custom',
|
||||
is_configured: true,
|
||||
system_configuration: { enabled: false },
|
||||
custom_configuration: {
|
||||
status: 'active',
|
||||
available_credentials: [],
|
||||
current_credential_usable: true,
|
||||
has_custom_models: false,
|
||||
},
|
||||
} satisfies ModelProviderSummaryResponse
|
||||
|
||||
const mockUseConfig = vi.fn()
|
||||
const mockFetchAndMergeValidCompletionParams = vi.mocked(fetchAndMergeValidCompletionParams)
|
||||
|
||||
@ -82,37 +92,6 @@ vi.mock('../components/reasoning-format-config', () => ({
|
||||
|
||||
type MockUseConfigReturn = ReturnType<typeof mockUseConfig>
|
||||
|
||||
const createMockModelProvider = (provider: string): ModelProvider => ({
|
||||
provider,
|
||||
label: { en_US: provider, zh_Hans: provider },
|
||||
help: {
|
||||
title: { en_US: provider, zh_Hans: provider },
|
||||
url: { en_US: '', zh_Hans: '' },
|
||||
},
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
supported_model_types: [ModelTypeEnum.textGeneration],
|
||||
configurate_methods: [ConfigurationMethodEnum.predefinedModel],
|
||||
provider_credential_schema: {
|
||||
credential_form_schemas: [],
|
||||
},
|
||||
model_credential_schema: {
|
||||
model: {
|
||||
label: { en_US: '', zh_Hans: '' },
|
||||
placeholder: { en_US: '', zh_Hans: '' },
|
||||
},
|
||||
credential_form_schemas: [],
|
||||
},
|
||||
preferred_provider_type: PreferredProviderTypeEnum.system,
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
},
|
||||
system_configuration: {
|
||||
enabled: true,
|
||||
current_quota_type: CurrentSystemQuotaTypeEnum.free,
|
||||
quota_configurations: [],
|
||||
},
|
||||
})
|
||||
|
||||
const baseNodeData: LLMNodeType = {
|
||||
type: BlockEnum.LLM,
|
||||
title: 'LLM',
|
||||
@ -176,24 +155,28 @@ const buildUseConfigResult = (overrides?: Partial<MockUseConfigReturn>) => ({
|
||||
...overrides,
|
||||
})
|
||||
|
||||
let panelQueryClient = createConsoleQueryWrapper().queryClient
|
||||
|
||||
const renderPanelElement = (data?: Partial<LLMNodeType>) => (
|
||||
// oxlint-disable-next-line eslint-react/no-context-provider -- use-context-selector requires its special provider.
|
||||
<ProviderContext.Provider
|
||||
value={createMockProviderContextValue({
|
||||
modelProviders: [
|
||||
{
|
||||
...createMockModelProvider('openai'),
|
||||
is_configured: true,
|
||||
plugin_id: 'langgenius/openai',
|
||||
} as unknown as ModelProviderSummaryResponse,
|
||||
],
|
||||
})}
|
||||
>
|
||||
<QueryClientProvider client={panelQueryClient}>
|
||||
<Panel id="llm-node" data={{ ...baseNodeData, ...data }} panelProps={panelProps} />
|
||||
</ProviderContext.Provider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
const renderPanel = (data?: Partial<LLMNodeType>, flowType?: FlowType) => {
|
||||
panelQueryClient = createConsoleQueryWrapper().queryClient
|
||||
panelQueryClient.setQueryData(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryKey(),
|
||||
{
|
||||
data: [
|
||||
{
|
||||
...providerSummaryFixture,
|
||||
...{ provider: 'openai' },
|
||||
} satisfies ModelProviderSummaryResponse,
|
||||
],
|
||||
plugins: {},
|
||||
},
|
||||
)
|
||||
return renderWorkflowFlowComponent(renderPanelElement(data), {
|
||||
hooksStoreProps: flowType
|
||||
? { configsMap: { flowId: 'test-flow', flowType, fileSettings: {} } }
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
SelectTrigger,
|
||||
} from '@langgenius/dify-ui/select'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useLayoutEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -19,7 +20,7 @@ import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import FormInputTypeSwitch from '@/app/components/workflow/nodes/_base/components/form-input-type-switch'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { FlowType } from '@/types/common'
|
||||
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
|
||||
import { extractPluginId } from '../../utils/plugin'
|
||||
@ -87,15 +88,17 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ id, data }) => {
|
||||
handleReasoningFormatChange,
|
||||
} = useConfig(id, data)
|
||||
|
||||
const isModelProviderInstalled = useProviderContextSelector((state) => {
|
||||
const modelIssue = getLLMModelIssue({ modelProvider: model?.provider })
|
||||
if (modelIssue === LLMModelIssueCode.providerRequired) return true
|
||||
|
||||
const modelProviderPluginId = extractPluginId(model.provider)
|
||||
return state.modelProviders.some(
|
||||
(provider) => extractPluginId(provider.provider) === modelProviderPluginId,
|
||||
)
|
||||
})
|
||||
const isProviderRequired =
|
||||
getLLMModelIssue({ modelProvider: model?.provider }) === LLMModelIssueCode.providerRequired
|
||||
const { data: isModelProviderInstalled = isProviderRequired } = useQuery(
|
||||
consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions({
|
||||
select: (response) =>
|
||||
isProviderRequired ||
|
||||
response.data.some(
|
||||
(provider) => extractPluginId(provider.provider) === extractPluginId(model.provider),
|
||||
),
|
||||
}),
|
||||
)
|
||||
const hasModelWarning =
|
||||
getLLMModelIssue({
|
||||
modelProvider: model?.provider,
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { commonQueryKeys } from '@/service/use-common'
|
||||
import { ProviderContext } from './provider-context'
|
||||
|
||||
type ProviderContextProviderProps = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const ProviderContextProvider = ({ children }: ProviderContextProviderProps) => {
|
||||
const queryClient = useQueryClient()
|
||||
const {
|
||||
data: providersData,
|
||||
isLoading: isLoadingModelProviders,
|
||||
isSuccess: isSuccessModelProviders,
|
||||
} = useQuery(consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions())
|
||||
|
||||
const refreshModelProviders = () =>
|
||||
Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: consoleQuery.workspaces.current.modelProviders.summary.get.key(),
|
||||
}),
|
||||
queryClient.invalidateQueries({ queryKey: commonQueryKeys.modelProviderDetails }),
|
||||
]).then(() => undefined)
|
||||
|
||||
return (
|
||||
<ProviderContext.Provider
|
||||
value={{
|
||||
modelProviders: providersData?.data || [],
|
||||
modelProviderPlugins: providersData?.plugins || {},
|
||||
isLoadingModelProviders,
|
||||
isSuccessModelProviders,
|
||||
refreshModelProviders,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ProviderContext.Provider>
|
||||
)
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
ModelProviderPluginSummaryResponse,
|
||||
ModelProviderSummaryResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import { createContext, useContext, useContextSelector } from 'use-context-selector'
|
||||
|
||||
export type ProviderContextState = {
|
||||
modelProviders: ModelProviderSummaryResponse[]
|
||||
modelProviderPlugins: Record<string, ModelProviderPluginSummaryResponse>
|
||||
isLoadingModelProviders: boolean
|
||||
isSuccessModelProviders: boolean
|
||||
refreshModelProviders: () => Promise<void>
|
||||
}
|
||||
|
||||
const baseProviderContextValue: ProviderContextState = {
|
||||
modelProviders: [],
|
||||
modelProviderPlugins: {},
|
||||
isLoadingModelProviders: false,
|
||||
isSuccessModelProviders: false,
|
||||
refreshModelProviders: async () => {},
|
||||
}
|
||||
|
||||
export const ProviderContext = createContext<ProviderContextState>(baseProviderContextValue)
|
||||
|
||||
export const useProviderContext = () => useContext(ProviderContext)
|
||||
|
||||
// Adding a dangling comma to avoid the generic parsing issue in tsx, see:
|
||||
// https://github.com/microsoft/TypeScript/issues/15713
|
||||
export const useProviderContextSelector = <T>(selector: (state: ProviderContextState) => T): T =>
|
||||
useContextSelector(ProviderContext, selector)
|
||||
Loading…
Reference in New Issue
Block a user