diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 8ba12bd390c..2a3bb75e6b0 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -2294,7 +2294,7 @@ }, "web/app/components/header/account-setting/model-provider-page/hooks.ts": { "@tanstack/query/prefer-query-options": { - "count": 2 + "count": 1 } }, "web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx": { @@ -5133,7 +5133,7 @@ }, "web/service/use-common.ts": { "@tanstack/query/prefer-query-options": { - "count": 11 + "count": 10 } }, "web/service/use-datasource.ts": { diff --git a/web/__mocks__/provider-context.ts b/web/__mocks__/provider-context.ts index cda58b3f513..5fb7714733a 100644 --- a/web/__mocks__/provider-context.ts +++ b/web/__mocks__/provider-context.ts @@ -9,8 +9,6 @@ export const baseProviderContextValue: ProviderContextState = { refreshModelProviders: async () => {}, isLoadingModelProviders: false, isSuccessModelProviders: false, - textGenerationModelList: [], - isAPIKeySet: true, } export const createMockProviderContextValue = ( diff --git a/web/app/components/app/app-publisher/__tests__/publish-with-multiple-model.spec.tsx b/web/app/components/app/app-publisher/__tests__/publish-with-multiple-model.spec.tsx index ab987936659..3832523e919 100644 --- a/web/app/components/app/app-publisher/__tests__/publish-with-multiple-model.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/publish-with-multiple-model.spec.tsx @@ -2,9 +2,10 @@ import { fireEvent, render, screen } from '@testing-library/react' import * as React from 'react' import PublishWithMultipleModel from '../publish-with-multiple-model' -const mockUseProviderContext = vi.fn() -vi.mock('@/context/provider-context', () => ({ - useProviderContext: () => mockUseProviderContext(), +const mockModelListQuery = vi.fn() +vi.mock('@tanstack/react-query', async (importOriginal) => ({ + ...(await importOriginal()), + useQuery: () => mockModelListQuery(), })) vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ @@ -18,8 +19,8 @@ vi.mock('../../header/account-setting/model-provider-page/model-icon', () => ({ describe('PublishWithMultipleModel', () => { beforeEach(() => { vi.clearAllMocks() - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: [ + mockModelListQuery.mockReturnValue({ + data: [ { provider: 'openai', models: [ diff --git a/web/app/components/app/app-publisher/publish-with-multiple-model.tsx b/web/app/components/app/app-publisher/publish-with-multiple-model.tsx index 4e26d0e5638..299e62f2c84 100644 --- a/web/app/components/app/app-publisher/publish-with-multiple-model.tsx +++ b/web/app/components/app/app-publisher/publish-with-multiple-model.tsx @@ -1,9 +1,9 @@ +import type { + ProviderModelWithStatusEntity, + ProviderWithModelsResponse, +} from '@dify/contracts/api/console/workspaces/types.gen' import type { FC } from 'react' import type { ModelAndParameter } from '../configuration/debug/types' -import type { - Model, - ModelItem, -} from '@/app/components/header/account-setting/model-provider-page/declarations' import { Button } from '@langgenius/dify-ui/button' import { DropdownMenu, @@ -12,31 +12,39 @@ import { DropdownMenuTrigger, } from '@langgenius/dify-ui/dropdown-menu' import { RiArrowDownSLine } from '@remixicon/react' +import { useQuery } from '@tanstack/react-query' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { useProviderContext } from '@/context/provider-context' +import { renderI18nObject } from '@/i18n-config' +import { consoleQuery } from '@/service/console' import ModelIcon from '../../header/account-setting/model-provider-page/model-icon' type PublishWithMultipleModelProps = { disabled?: boolean multipleModelConfigs: ModelAndParameter[] - // textGenerationModelList?: Model[] onSelect: (v: ModelAndParameter) => void } const PublishWithMultipleModel: FC = ({ disabled = false, multipleModelConfigs, - // textGenerationModelList = [], onSelect, }) => { const { t } = useTranslation() const language = useLanguage() - const { textGenerationModelList } = useProviderContext() + const { data: textGenerationModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textGeneration } }, + select: (response) => response.data, + }), + ) const [open, setOpen] = useState(false) - const validModelConfigs: (ModelAndParameter & { modelItem: ModelItem; providerItem: Model })[] = - [] + const validModelConfigs: (ModelAndParameter & { + modelItem: ProviderModelWithStatusEntity + providerItem: ProviderWithModelsResponse + })[] = [] multipleModelConfigs.forEach((item) => { const provider = textGenerationModelList.find((model) => model.provider === item.provider) @@ -80,9 +88,9 @@ const PublishWithMultipleModel: FC = ({
- {item.modelItem.label[language]} + {renderI18nObject(item.modelItem.label, language)}
))} diff --git a/web/app/components/app/configuration/__tests__/configuration-view.spec.tsx b/web/app/components/app/configuration/__tests__/configuration-view.spec.tsx index 6116def05fb..d8b3bd70b56 100644 --- a/web/app/components/app/configuration/__tests__/configuration-view.spec.tsx +++ b/web/app/components/app/configuration/__tests__/configuration-view.spec.tsx @@ -98,7 +98,6 @@ const createDeletedAgentTool = (providerId: string): AgentTool => ({ const createContextValue = (): ComponentProps['value'] => ({ appId: 'app-1', - isAPIKeySet: true, isTrailFinished: false, mode: AppModeEnum.CHAT, modelModeType: ModelModeType.chat, diff --git a/web/app/components/app/configuration/configuration-view/index.tsx b/web/app/components/app/configuration/configuration-view/index.tsx index debbb78ed15..5138469b888 100644 --- a/web/app/components/app/configuration/configuration-view/index.tsx +++ b/web/app/components/app/configuration/configuration-view/index.tsx @@ -177,7 +177,6 @@ const ConfigurationView: FC = ({ >
= ({ />
({ vi.mock('@/context/provider-context', () => ({ useProviderContext: () => ({ modelProviders: [], - textGenerationModelList: [], }), })) vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ - useModelList: (...args: unknown[]) => mockUseModelList(...args), - useModelListAndDefaultModel: (...args: unknown[]) => mockUseModelListAndDefaultModel(...args), + useModelListAndDefaultModel: (...args: unknown[]) => mockModelListQueryAndDefaultModel(...args), useModelListAndDefaultModelAndCurrentProviderAndModel: (...args: unknown[]) => - mockUseModelListAndDefaultModelAndCurrentProviderAndModel(...args), + mockModelListQueryAndDefaultModelAndCurrentProviderAndModel(...args), useCurrentProviderAndModel: (...args: unknown[]) => mockUseCurrentProviderAndModel(...args), })) @@ -251,7 +251,7 @@ describe('SettingsModal', () => { ], }, } as ReturnType) - mockUseModelList.mockImplementation((type: ModelTypeEnum) => { + mockModelListQuery.mockImplementation((type: ModelTypeEnum) => { if (type === ModelTypeEnum.rerank) { return { data: [ @@ -264,8 +264,8 @@ describe('SettingsModal', () => { } return { data: [{ provider: 'embed-provider', models: [{ model: 'embed-model' }] }] } }) - mockUseModelListAndDefaultModel.mockReturnValue({ modelList: [], defaultModel: null }) - mockUseModelListAndDefaultModelAndCurrentProviderAndModel.mockReturnValue({ + mockModelListQueryAndDefaultModel.mockReturnValue({ modelList: [], defaultModel: null }) + mockModelListQueryAndDefaultModelAndCurrentProviderAndModel.mockReturnValue({ defaultModel: null, currentModel: null, }) @@ -436,7 +436,7 @@ describe('SettingsModal', () => { it('should block save when reranking is enabled without model', async () => { // Arrange const user = userEvent.setup() - mockUseModelList.mockReturnValue({ data: [] }) + mockModelListQuery.mockReturnValue({ data: [] }) const dataset = createDataset( {}, createRetrievalConfig({ @@ -601,3 +601,22 @@ describe('SettingsModal', () => { }) }) }) + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useQuery: (options: { + queryKey: OperationKey< + 'query', + { params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] } + > + }) => { + if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options) + + const args = options.queryKey[1].input?.params?.model_type + if (!args) throw new Error('Missing model type in query') + return mockModelListQuery(args) + }, + } +}) diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/retrieval-section.spec.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/retrieval-section.spec.tsx index 3f2681a1de2..8ba6bc71c21 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/retrieval-section.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/retrieval-section.spec.tsx @@ -1,3 +1,5 @@ +import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen' +import type { OperationKey } from '@orpc/tanstack-query' import type { ReactElement } from 'react' import type { DataSet } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' @@ -18,9 +20,9 @@ import { withSelectorKey } from '@/test/i18n-mock' import { RETRIEVE_METHOD } from '@/types/app' import { RetrievalChangeTip, RetrievalSection } from '../retrieval-section' -const mockUseModelList = vi.fn() -const mockUseModelListAndDefaultModel = vi.fn() -const mockUseModelListAndDefaultModelAndCurrentProviderAndModel = vi.fn() +const mockModelListQuery = vi.fn() +const mockModelListQueryAndDefaultModel = vi.fn() +const mockModelListQueryAndDefaultModelAndCurrentProviderAndModel = vi.fn() const mockUseCurrentProviderAndModel = vi.fn() vi.mock('ky', () => { @@ -33,15 +35,14 @@ vi.mock('ky', () => { vi.mock('@/context/provider-context', () => ({ useProviderContext: () => ({ modelProviders: [], - textGenerationModelList: [], }), })) vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ useModelListAndDefaultModelAndCurrentProviderAndModel: (...args: unknown[]) => - mockUseModelListAndDefaultModelAndCurrentProviderAndModel(...args), - useModelListAndDefaultModel: (...args: unknown[]) => mockUseModelListAndDefaultModel(...args), - useModelList: (...args: unknown[]) => mockUseModelList(...args), + mockModelListQueryAndDefaultModelAndCurrentProviderAndModel(...args), + useModelListAndDefaultModel: (...args: unknown[]) => mockModelListQueryAndDefaultModel(...args), + useCurrentProviderAndModel: (...args: unknown[]) => mockUseCurrentProviderAndModel(...args), })) @@ -221,13 +222,13 @@ describe('RetrievalSection', () => { beforeEach(() => { vi.clearAllMocks() - mockUseModelList.mockImplementation((type: ModelTypeEnum) => { + mockModelListQuery.mockImplementation((type: ModelTypeEnum) => { if (type === ModelTypeEnum.rerank) return { data: [{ provider: 'rerank-provider', models: [{ model: 'rerank-model' }] }] } return { data: [] } }) - mockUseModelListAndDefaultModel.mockReturnValue({ modelList: [], defaultModel: null }) - mockUseModelListAndDefaultModelAndCurrentProviderAndModel.mockReturnValue({ + mockModelListQueryAndDefaultModel.mockReturnValue({ modelList: [], defaultModel: null }) + mockModelListQueryAndDefaultModelAndCurrentProviderAndModel.mockReturnValue({ defaultModel: null, currentModel: null, }) @@ -338,3 +339,22 @@ describe('RetrievalSection', () => { ) }) }) + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useQuery: (options: { + queryKey: OperationKey< + 'query', + { params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] } + > + }) => { + if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options) + + const args = options.queryKey[1].input?.params?.model_type + if (!args) throw new Error('Missing model type in query') + return mockModelListQuery(args) + }, + } +}) diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx index a36903a222a..c20fe6381af 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx @@ -8,6 +8,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { Input } from '@langgenius/dify-ui/input' import { Textarea } from '@langgenius/dify-ui/textarea' import { RiCloseLine } from '@remixicon/react' +import { useQuery } from '@tanstack/react-query' import { isEqual } from 'es-toolkit/predicate' import { useQueryState } from 'nuqs' import { useEffect, useId, useMemo, useRef, useState } from 'react' @@ -19,7 +20,6 @@ import IndexMethod from '@/app/components/datasets/settings/index-method' import PermissionSelector from '@/app/components/datasets/settings/permission-selector' import { checkShowMultiModalTip } from '@/app/components/datasets/settings/utils' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' import { ModelSelector } from '@/app/components/header/account-setting/model-provider-page/model-selector' import { settingsQueryParamName, @@ -27,6 +27,7 @@ import { } from '@/app/components/header/account-setting/query-params' import { useDocLink } from '@/context/i18n' import { DatasetPermission } from '@/models/datasets' +import { consoleQuery } from '@/service/console' import { updateDatasetSetting } from '@/service/datasets' import { useMembers } from '@/service/use-common' import { RetrievalChangeTip, RetrievalSection } from './retrieval-section' @@ -52,8 +53,18 @@ const SettingsModal: FC = ({ onCancel, onSave, }) => { - const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding) - const { data: rerankModelList } = useModelList(ModelTypeEnum.rerank) + const { data: embeddingModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textEmbedding } }, + select: (response) => response.data, + }), + ) + const { data: rerankModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.rerank } }, + select: (response) => response.data, + }), + ) const { t } = useTranslation() const translateRetrieval: RetrievalTranslate = (selector, options) => t(selector, options) const docLink = useDocLink() diff --git a/web/app/components/app/configuration/debug/__tests__/index.spec.tsx b/web/app/components/app/configuration/debug/__tests__/index.spec.tsx index a1c68fcb783..97940c03d18 100644 --- a/web/app/components/app/configuration/debug/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/debug/__tests__/index.spec.tsx @@ -39,9 +39,10 @@ const mockState = vi.hoisted(() => ({ fileUploadConfig: undefined as { image_file_size_limit?: number } | undefined, }, }, - mockProviderContext: { - textGenerationModelList: [] as Array<{ + mockModelListResult: { + data: [] as Array<{ provider: string + status: string models: Array<{ model: string features?: string[] @@ -209,8 +210,9 @@ vi.mock('@/context/event-emitter', () => ({ }), })) -vi.mock('@/context/provider-context', () => ({ - useProviderContext: () => mockState.mockProviderContext, +vi.mock('@tanstack/react-query', async (importOriginal) => ({ + ...(await importOriginal()), + useQuery: () => mockState.mockModelListResult, })) vi.mock('@/service/debug', () => ({ @@ -307,7 +309,6 @@ const createContextValue = (overrides: Partial = {}): DebugCo readonly: false, canTestAndRun: true, appId: 'app-id', - isAPIKeySet: true, isTrailFinished: false, mode: AppModeEnum.CHAT, modelModeType: ModelModeType.chat, @@ -454,7 +455,6 @@ const renderDebug = ( ) => { const onSetting = vi.fn() const props: ComponentProps = { - isAPIKeySet: true, onSetting, inputs: {}, modelParameterParams: { @@ -499,10 +499,11 @@ describe('Debug', () => { text2speech: { enabled: false }, file: { enabled: false, allowed_file_upload_methods: [], fileUploadConfig: undefined }, } - mockState.mockProviderContext = { - textGenerationModelList: [ + mockState.mockModelListResult = { + data: [ { provider: 'openai', + status: 'active', models: [ { model: 'vision-model', @@ -525,9 +526,7 @@ describe('Debug', () => { model_id: '', }, }, - props: { - isAPIKeySet: false, - }, + props: {}, }) expect(screen.getByText('appDebug.noModelProviderConfigured'))!.toBeInTheDocument() @@ -546,9 +545,7 @@ describe('Debug', () => { model_id: '', }, }, - props: { - isAPIKeySet: true, - }, + props: {}, }) expect(screen.getByText('appDebug.noModelSelected'))!.toBeInTheDocument() diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/chat-item.spec.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/chat-item.spec.tsx index 3b5d28b439d..9ea7884d95c 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/chat-item.spec.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/chat-item.spec.tsx @@ -9,7 +9,7 @@ import ChatItem from '../chat-item' const mockConsoleStateReader = vi.fn() const mockUseDebugConfigurationContext = vi.fn() -const mockUseProviderContext = vi.fn() +const mockModelListQuery = vi.fn() const mockUseFeatures = vi.fn() const mockUseConfigFromDebugContext = vi.fn() const mockUseFormattingChangedSubscription = vi.fn() @@ -39,8 +39,9 @@ vi.mock('@/context/debug-configuration', () => ({ useDebugConfigurationContext: () => mockUseDebugConfigurationContext(), })) -vi.mock('@/context/provider-context', () => ({ - useProviderContext: () => mockUseProviderContext(), +vi.mock('@tanstack/react-query', async (importOriginal) => ({ + ...(await importOriginal()), + useQuery: () => mockModelListQuery(), })) vi.mock('@/app/components/base/features/hooks', () => ({ @@ -137,8 +138,8 @@ const createDefaultMocks = () => { canTestAndRun: true, }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: [ + mockModelListQuery.mockReturnValue({ + data: [ { provider: 'openai', models: [ @@ -429,8 +430,8 @@ describe('ChatItem', () => { }) it('should not include files when vision is not supported', () => { - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: [ + mockModelListQuery.mockReturnValue({ + data: [ { provider: 'openai', models: [ @@ -624,8 +625,8 @@ describe('ChatItem', () => { describe('edge cases', () => { it('should handle missing provider in textGenerationModelList', () => { - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: [], + mockModelListQuery.mockReturnValue({ + data: [], }) const handleSend = vi.fn() diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/debug-item.spec.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/debug-item.spec.tsx index 17a13ddc697..85f4a8a9d4b 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/debug-item.spec.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/debug-item.spec.tsx @@ -8,7 +8,7 @@ import DebugItem from '../debug-item' const mockUseDebugConfigurationContext = vi.fn() const mockUseDebugWithMultipleModelContext = vi.fn() -const mockUseProviderContext = vi.fn() +const mockModelListQuery = vi.fn() let capturedModelParameterTriggerProps: { modelAndParameter: ModelAndParameter @@ -22,8 +22,9 @@ vi.mock('../context', () => ({ useDebugWithMultipleModelContext: () => mockUseDebugWithMultipleModelContext(), })) -vi.mock('@/context/provider-context', () => ({ - useProviderContext: () => mockUseProviderContext(), +vi.mock('@tanstack/react-query', async (importOriginal) => ({ + ...(await importOriginal()), + useQuery: () => mockModelListQuery(), })) vi.mock('../chat-item', () => ({ @@ -106,10 +107,8 @@ describe('DebugItem', () => { onDebugWithMultipleModelChange: vi.fn(), }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: createTextGenerationModelList([ - { provider: 'openai', model: 'gpt-3.5-turbo' }, - ]), + mockModelListQuery.mockReturnValue({ + data: createTextGenerationModelList([{ provider: 'openai', model: 'gpt-3.5-turbo' }]), }) }) @@ -158,8 +157,8 @@ describe('DebugItem', () => { describe('ChatItem rendering', () => { it('should render ChatItem in CHAT mode with active model', () => { mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: createTextGenerationModelList([ + mockModelListQuery.mockReturnValue({ + data: createTextGenerationModelList([ { provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.active }, ]), }) @@ -172,8 +171,8 @@ describe('DebugItem', () => { it('should render ChatItem in AGENT_CHAT mode with active model', () => { mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.AGENT_CHAT }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: createTextGenerationModelList([ + mockModelListQuery.mockReturnValue({ + data: createTextGenerationModelList([ { provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.active }, ]), }) @@ -185,8 +184,8 @@ describe('DebugItem', () => { it('should not render ChatItem when model is not active', () => { mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: createTextGenerationModelList([ + mockModelListQuery.mockReturnValue({ + data: createTextGenerationModelList([ { provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.disabled }, ]), }) @@ -198,8 +197,8 @@ describe('DebugItem', () => { it('should not render ChatItem when provider not found', () => { mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: createTextGenerationModelList([ + mockModelListQuery.mockReturnValue({ + data: createTextGenerationModelList([ { provider: 'anthropic', model: 'claude-3', status: ModelStatusEnum.active }, ]), }) @@ -211,8 +210,8 @@ describe('DebugItem', () => { it('should not render ChatItem when model not found', () => { mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: createTextGenerationModelList([ + mockModelListQuery.mockReturnValue({ + data: createTextGenerationModelList([ { provider: 'openai', model: 'gpt-4', status: ModelStatusEnum.active }, ]), }) @@ -226,8 +225,8 @@ describe('DebugItem', () => { describe('TextGenerationItem rendering', () => { it('should render TextGenerationItem in COMPLETION mode with active model', () => { mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.COMPLETION }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: createTextGenerationModelList([ + mockModelListQuery.mockReturnValue({ + data: createTextGenerationModelList([ { provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.active }, ]), }) @@ -240,8 +239,8 @@ describe('DebugItem', () => { it('should not render TextGenerationItem when provider is not found', () => { mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.COMPLETION }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: createTextGenerationModelList([ + mockModelListQuery.mockReturnValue({ + data: createTextGenerationModelList([ { provider: 'anthropic', model: 'claude-3', status: ModelStatusEnum.active }, ]), }) @@ -493,8 +492,8 @@ describe('DebugItem', () => { }) it('should handle empty textGenerationModelList', () => { - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: [], + mockModelListQuery.mockReturnValue({ + data: [], }) renderComponent() @@ -505,8 +504,8 @@ describe('DebugItem', () => { it('should handle model with quotaExceeded status', () => { mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: createTextGenerationModelList([ + mockModelListQuery.mockReturnValue({ + data: createTextGenerationModelList([ { provider: 'anthropic', model: 'not-matching', status: ModelStatusEnum.quotaExceeded }, ]), }) diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/text-generation-item.spec.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/text-generation-item.spec.tsx index 96728e360d9..176ead8e435 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/text-generation-item.spec.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/text-generation-item.spec.tsx @@ -5,7 +5,7 @@ import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../../types' import TextGenerationItem from '../text-generation-item' const mockUseDebugConfigurationContext = vi.fn() -const mockUseProviderContext = vi.fn() +const mockModelListQuery = vi.fn() const mockUseFeatures = vi.fn() const mockUseTextGeneration = vi.fn() const mockUseEventEmitterContextContext = vi.fn() @@ -30,8 +30,9 @@ vi.mock('@/context/debug-configuration', () => ({ useDebugConfigurationContext: () => mockUseDebugConfigurationContext(), })) -vi.mock('@/context/provider-context', () => ({ - useProviderContext: () => mockUseProviderContext(), +vi.mock('@tanstack/react-query', async (importOriginal) => ({ + ...(await importOriginal()), + useQuery: () => mockModelListQuery(), })) vi.mock('@/app/components/base/features/hooks', () => ({ @@ -105,8 +106,8 @@ const createDefaultMocks = () => { datasetConfigs: { retrieval_model: 'single' }, }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: [ + mockModelListQuery.mockReturnValue({ + data: [ { provider: 'openai', models: [ @@ -597,8 +598,8 @@ describe('TextGenerationItem', () => { messageId: null, }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: [ + mockModelListQuery.mockReturnValue({ + data: [ { provider: 'openai', models: [ @@ -643,8 +644,8 @@ describe('TextGenerationItem', () => { messageId: null, }) - mockUseProviderContext.mockReturnValue({ - textGenerationModelList: [], + mockModelListQuery.mockReturnValue({ + data: [], }) renderComponent() diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx index 6f0d8756c33..09a68780389 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx @@ -3,18 +3,21 @@ import type { ModelAndParameter } from '../types' import type { InputForm } from '@/app/components/base/chat/chat/type' import type { ChatConfig, OnSend } from '@/app/components/base/chat/types' import { Avatar } from '@langgenius/dify-ui/avatar' -import { useSuspenseQuery } from '@tanstack/react-query' +import { useQuery, useSuspenseQuery } from '@tanstack/react-query' import { memo, useCallback, useMemo } from 'react' import { toast } from '@/app/components/app/configuration/toast' import Chat from '@/app/components/base/chat/chat' import { useChat } from '@/app/components/base/chat/chat/hooks' import { getLastAnswer } from '@/app/components/base/chat/utils' import { useFeatures } from '@/app/components/base/features/hooks' -import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { + ModelFeatureEnum, + ModelTypeEnum, +} from '@/app/components/header/account-setting/model-provider-page/declarations' import { useDebugConfigurationContext } from '@/context/debug-configuration' import { useEventEmitterContextContext } from '@/context/event-emitter' -import { useProviderContext } from '@/context/provider-context' import { userProfileQueryOptions } from '@/features/account-profile/client' +import { consoleQuery } from '@/service/console' import { fetchConversationMessages, fetchSuggestedQuestions, @@ -39,7 +42,12 @@ const ChatItem: FC = ({ modelAndParameter }) => { collectionList, canTestAndRun = false, } = useDebugConfigurationContext() - const { textGenerationModelList } = useProviderContext() + const { data: textGenerationModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textGeneration } }, + select: (response) => response.data, + }), + ) const features = useFeatures((s) => s.features) const configTemplate = useConfigFromDebugContext() const config = useMemo(() => { diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/debug-item.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/debug-item.tsx index dc715f84077..e0c1ed8620e 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/debug-item.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/debug-item.tsx @@ -8,11 +8,15 @@ import { DropdownMenuTrigger, } from '@langgenius/dify-ui/dropdown-menu' import { IconButton } from '@langgenius/dify-ui/icon-button' +import { useQuery } from '@tanstack/react-query' import { memo } from 'react' import { useTranslation } from 'react-i18next' -import { ModelStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { + ModelStatusEnum, + ModelTypeEnum, +} from '@/app/components/header/account-setting/model-provider-page/declarations' import { useDebugConfigurationContext } from '@/context/debug-configuration' -import { useProviderContext } from '@/context/provider-context' +import { consoleQuery } from '@/service/console' import { AppModeEnum } from '@/types/app' import ChatItem from './chat-item' import { useDebugWithMultipleModelContext } from './context' @@ -29,7 +33,12 @@ const DebugItem: FC = ({ modelAndParameter, className, style }) const { mode } = useDebugConfigurationContext() const { multipleModelConfigs, onMultipleModelConfigsChange, onDebugWithMultipleModelChange } = useDebugWithMultipleModelContext() - const { textGenerationModelList } = useProviderContext() + const { data: textGenerationModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textGeneration } }, + select: (response) => response.data, + }), + ) const index = multipleModelConfigs.findIndex((v) => v.id === modelAndParameter.id) const currentProvider = textGenerationModelList.find( diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx index 97bd8c7ec00..c0feb9de223 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react' import type { ModelAndParameter } from '../types' import type { OnSend, TextGenerationConfig } from '@/app/components/base/text-generation/types' +import { useQuery } from '@tanstack/react-query' import { noop } from 'es-toolkit/function' import { cloneDeep } from 'es-toolkit/object' import { memo } from 'react' @@ -9,10 +10,11 @@ import TextGeneration from '@/app/components/app/text-generate/item' import { TransferMethod } from '@/app/components/base/chat/types' import { useFeatures } from '@/app/components/base/features/hooks' import { useTextGeneration } from '@/app/components/base/text-generation/hooks' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' import { useDebugConfigurationContext } from '@/context/debug-configuration' import { useEventEmitterContextContext } from '@/context/event-emitter' -import { useProviderContext } from '@/context/provider-context' +import { consoleQuery } from '@/service/console' import { AppSourceType } from '@/service/share' import { promptVariablesToUserInputsForm } from '@/utils/model-config' import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types' @@ -37,7 +39,12 @@ const TextGenerationItem: FC = ({ modelAndParameter }) dataSets, datasetConfigs, } = useDebugConfigurationContext() - const { textGenerationModelList } = useProviderContext() + const { data: textGenerationModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textGeneration } }, + select: (response) => response.data, + }), + ) const features = useFeatures((s) => s.features) const postDatasets = dataSets.map(({ id }) => ({ dataset: { diff --git a/web/app/components/app/configuration/debug/debug-with-single-model/__tests__/index.spec.tsx b/web/app/components/app/configuration/debug/debug-with-single-model/__tests__/index.spec.tsx index dfcccab95bd..7d1b4c89abc 100644 --- a/web/app/components/app/configuration/debug/debug-with-single-model/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/debug/debug-with-single-model/__tests__/index.spec.tsx @@ -4,17 +4,10 @@ import type { DebugWithSingleModelRefType } from '../index' import type { ChatItem } from '@/app/components/base/chat/types' import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { Collection } from '@/app/components/tools/types' -import type { ProviderContextState } from '@/context/provider-context' import type { DatasetConfigs, ModelConfig } from '@/models/debug' import { act, fireEvent, screen, waitFor } from '@testing-library/react' import { createRef } from 'react' import { useStore as useAppStore } from '@/app/components/app/store' -import { - ConfigurationMethodEnum, - ModelFeatureEnum, - ModelStatusEnum, - ModelTypeEnum, -} from '@/app/components/header/account-setting/model-provider-page/declarations' import { CollectionType } from '@/app/components/tools/types' import { SupportUploadFileTypes } from '@/app/components/workflow/types' import { PromptMode } from '@/models/debug' @@ -93,43 +86,6 @@ function createMockCollections(collections: Partial[] = []): Collect ) } -/** - * Factory function for creating mock Provider Context - */ -function createMockProviderContext( - overrides: Partial = {}, -): ProviderContextState { - return { - textGenerationModelList: [ - { - provider: 'openai', - label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, - icon_small: { en_US: 'icon', zh_Hans: 'icon' }, - status: ModelStatusEnum.active, - models: [ - { - model: 'gpt-3.5-turbo', - label: { en_US: 'GPT-3.5', zh_Hans: 'GPT-3.5' }, - model_type: ModelTypeEnum.textGeneration, - features: [ModelFeatureEnum.vision], - fetch_from: ConfigurationMethodEnum.predefinedModel, - model_properties: {}, - deprecated: false, - }, - ], - }, - ], - hasSettedApiKey: true, - modelProviders: [], - speech2textDefaultModel: null, - ttsDefaultModel: null, - agentThoughtDefaultModel: null, - updateModelList: vi.fn(), - refreshModelProviders: vi.fn(), - ...overrides, - } as ProviderContextState -} - // ============================================================================ // Mock External Dependencies ONLY (Following testing.md guidelines) // ============================================================================ @@ -186,7 +142,6 @@ const mockDebugConfigContext = { readonly: false, canTestAndRun: true, appId: 'test-app-id', - isAPIKeySet: true, isTrailFinished: false, mode: AppModeEnum.CHAT, modelModeType: ModelModeType.chat, @@ -302,18 +257,6 @@ vi.mock('@/context/debug-configuration', () => ({ useDebugConfigurationContext: mockUseDebugConfigurationContext, })) -const mockProviderContext = createMockProviderContext() - -const { mockUseProviderContext } = vi.hoisted(() => ({ - mockUseProviderContext: vi.fn(), -})) - -mockUseProviderContext.mockReturnValue(mockProviderContext) - -vi.mock('@/context/provider-context', () => ({ - useProviderContext: mockUseProviderContext, -})) - const mockConsoleState = { userProfile: { id: 'user-1', @@ -624,7 +567,6 @@ describe('DebugWithSingleModel', () => { // Reset mock implementations using module-level mocks mockUseDebugConfigurationContext.mockReturnValue(mockDebugConfigContext) - mockUseProviderContext.mockReturnValue(mockProviderContext) mockConsoleStateReader.mockReturnValue(mockConsoleState) mockUseConfigFromDebugContext.mockReturnValue(mockConfigFromDebugContext) mockUseFormattingChangedSubscription.mockReturnValue(undefined) @@ -807,52 +749,12 @@ describe('DebugWithSingleModel', () => { }) it('should handle model without vision support', () => { - mockUseProviderContext.mockReturnValue( - createMockProviderContext({ - textGenerationModelList: [ - { - provider: 'openai', - label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, - icon_small: { en_US: 'icon', zh_Hans: 'icon' }, - status: ModelStatusEnum.active, - models: [ - { - model: 'gpt-3.5-turbo', - label: { en_US: 'GPT-3.5', zh_Hans: 'GPT-3.5' }, - model_type: ModelTypeEnum.textGeneration, - features: [], // No vision support - fetch_from: ConfigurationMethodEnum.predefinedModel, - model_properties: {}, - deprecated: false, - status: ModelStatusEnum.active, - load_balancing_enabled: false, - }, - ], - }, - ], - }), - ) - render(} />) expect(screen.getByTestId('chat-component'))!.toBeInTheDocument() }) it('should handle missing model in provider list', () => { - mockUseProviderContext.mockReturnValue( - createMockProviderContext({ - textGenerationModelList: [ - { - provider: 'different-provider', - label: { en_US: 'Different Provider', zh_Hans: '不同提供商' }, - icon_small: { en_US: 'icon', zh_Hans: 'icon' }, - status: ModelStatusEnum.active, - models: [], - }, - ], - }), - ) - render(} />) expect(screen.getByTestId('chat-component'))!.toBeInTheDocument() @@ -1026,32 +928,6 @@ describe('DebugWithSingleModel', () => { }), }) - mockUseProviderContext.mockReturnValue( - createMockProviderContext({ - textGenerationModelList: [ - { - provider: 'openai', - label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, - icon_small: { en_US: 'icon', zh_Hans: 'icon' }, - status: ModelStatusEnum.active, - models: [ - { - model: 'gpt-3.5-turbo', - label: { en_US: 'GPT-3.5', zh_Hans: 'GPT-3.5' }, - model_type: ModelTypeEnum.textGeneration, - features: [ModelFeatureEnum.document], - fetch_from: ConfigurationMethodEnum.predefinedModel, - model_properties: {}, - deprecated: false, - status: ModelStatusEnum.active, - load_balancing_enabled: false, - }, - ], - }, - ], - }), - ) - mockFeaturesState = { ...defaultFeatures, file: { enabled: true }, @@ -1084,32 +960,6 @@ describe('DebugWithSingleModel', () => { }), }) - mockUseProviderContext.mockReturnValue( - createMockProviderContext({ - textGenerationModelList: [ - { - provider: 'openai', - label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, - icon_small: { en_US: 'icon', zh_Hans: 'icon' }, - status: ModelStatusEnum.active, - models: [ - { - model: 'gpt-4-vision', - label: { en_US: 'GPT-4 Vision', zh_Hans: 'GPT-4 Vision' }, - model_type: ModelTypeEnum.textGeneration, - features: [ModelFeatureEnum.vision], - fetch_from: ConfigurationMethodEnum.predefinedModel, - model_properties: {}, - deprecated: false, - status: ModelStatusEnum.active, - load_balancing_enabled: false, - }, - ], - }, - ], - }), - ) - mockFeaturesState = { ...defaultFeatures, file: { enabled: true }, diff --git a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx index 4b5e73a6cff..8c47ed04099 100644 --- a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx +++ b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx @@ -11,7 +11,6 @@ import { useChat } from '@/app/components/base/chat/chat/hooks' import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils' import { useFeatures } from '@/app/components/base/features/hooks' import { useDebugConfigurationContext } from '@/context/debug-configuration' -import { useProviderContext } from '@/context/provider-context' import { userProfileQueryOptions } from '@/features/account-profile/client' import { fetchConversationMessages, @@ -49,7 +48,6 @@ const DebugWithSingleModel = ({ } = useDebugConfigurationContext() const debugInputReadonly = !canTestAndRun const canManageAnnotation = !readonly && canTestAndRun - const { textGenerationModelList } = useProviderContext() const features = useFeatures((s) => s.features) const configTemplate = useConfigFromDebugContext() const config = useMemo(() => { @@ -140,7 +138,6 @@ const DebugWithSingleModel = ({ modelConfig.mode, modelConfig.model_id, modelConfig.provider, - textGenerationModelList, ], ) diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx index 58be6575175..13e9c8b4703 100644 --- a/web/app/components/app/configuration/debug/index.tsx +++ b/web/app/components/app/configuration/debug/index.tsx @@ -9,6 +9,7 @@ import { Button } from '@langgenius/dify-ui/button' import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible' import { IconButton } from '@langgenius/dify-ui/icon-button' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' +import { useQuery } from '@tanstack/react-query' import { useBoolean } from 'ahooks' import { noop } from 'es-toolkit/function' import { cloneDeep } from 'es-toolkit/object' @@ -34,7 +35,7 @@ import { useDefaultModel } from '@/app/components/header/account-setting/model-p import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' import ConfigContext from '@/context/debug-configuration' import { useEventEmitterContextContext } from '@/context/event-emitter' -import { useProviderContext } from '@/context/provider-context' +import { consoleQuery } from '@/service/console' import { sendCompletionMessage } from '@/service/debug' import { AppSourceType } from '@/service/share' import { AppModeEnum, ModelModeType, TransferMethod } from '@/types/app' @@ -48,7 +49,7 @@ import DebugWithSingleModel from './debug-with-single-model' import { APP_CHAT_WITH_MULTIPLE_MODEL, APP_CHAT_WITH_MULTIPLE_MODEL_RESTART } from './types' type IDebug = { - isAPIKeySet: boolean + isPreview?: boolean onSetting: () => void inputs: Inputs modelParameterParams: Pick @@ -58,7 +59,7 @@ type IDebug = { } const Debug: FC = ({ - isAPIKeySet = true, + isPreview = false, onSetting, inputs, modelParameterParams, @@ -332,9 +333,16 @@ const Debug: FC = ({ } }) - const { textGenerationModelList } = useProviderContext() + const { data: textGenerationModelList } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textGeneration } }, + select: (response) => response.data, + }), + ) + const hasActiveProvider = + isPreview || !!textGenerationModelList?.some((provider) => provider.status === 'active') const handleChangeToSingleModel = (item: ModelAndParameter) => { - const currentProvider = textGenerationModelList.find( + const currentProvider = textGenerationModelList?.find( (modelItem) => modelItem.provider === item.provider, ) const currentModel = currentProvider?.models.find((model) => model.model === item.model) @@ -342,8 +350,11 @@ const Debug: FC = ({ modelParameterParams.setModel({ modelId: item.model, provider: item.provider, - mode: currentModel?.model_properties.mode as string, - features: currentModel?.features, + mode: + typeof currentModel?.model_properties.mode === 'string' + ? currentModel.model_properties.mode + : undefined, + features: currentModel?.features ?? undefined, }) modelParameterParams.onCompletionParamsChange(item.parameters) onMultipleModelConfigsChange(false, []) @@ -352,7 +363,7 @@ const Debug: FC = ({ const handleVisionConfigInMultipleModel = useCallback(() => { if (debugWithMultipleModel && mode) { const supportedVision = multipleModelConfigs.some((modelConfig) => { - const currentProvider = textGenerationModelList.find( + const currentProvider = textGenerationModelList?.find( (modelItem) => modelItem.provider === modelConfig.provider, ) const currentModel = currentProvider?.models.find( @@ -541,9 +552,11 @@ const Debug: FC = ({ {!debugWithMultipleModel && (
{/* No model provider configured */} - {(!modelConfig.provider || !isAPIKeySet) && } + {(!modelConfig.provider || !hasActiveProvider) && ( + + )} {/* No model selected */} - {modelConfig.provider && isAPIKeySet && !modelConfig.model_id && ( + {modelConfig.provider && hasActiveProvider && !modelConfig.model_id && (
diff --git a/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx b/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx index 55a52e81086..e07696ba26b 100644 --- a/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx +++ b/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx @@ -84,12 +84,6 @@ vi.mock('nuqs', async (importOriginal) => { return { ...actual, useQueryState: () => [null, mockSetSettingsDestination] } }) -vi.mock('@/context/provider-context', () => ({ - useProviderContext: () => ({ - isAPIKeySet: true, - }), -})) - vi.mock('@/app/components/app/store', () => ({ useStore: (selector: (state: Record) => unknown) => selector({ diff --git a/web/app/components/app/configuration/hooks/build-configuration-context.ts b/web/app/components/app/configuration/hooks/build-configuration-context.ts index bed0d03f54a..12d60053793 100644 --- a/web/app/components/app/configuration/hooks/build-configuration-context.ts +++ b/web/app/components/app/configuration/hooks/build-configuration-context.ts @@ -29,7 +29,6 @@ type ContextBase = Pick< | 'isShowDocumentConfig' | 'isShowVisionConfig' | 'isTrailFinished' - | 'isAPIKeySet' | 'mode' | 'modelModeType' | 'prevPromptConfig' diff --git a/web/app/components/app/configuration/hooks/use-configuration.ts b/web/app/components/app/configuration/hooks/use-configuration.ts index be2cc6da71c..571bd5830b8 100644 --- a/web/app/components/app/configuration/hooks/use-configuration.ts +++ b/web/app/components/app/configuration/hooks/use-configuration.ts @@ -22,7 +22,6 @@ import { settingsQueryParamName, settingsQueryParser, } from '@/app/components/header/account-setting/query-params' -import { useProviderContext } from '@/context/provider-context' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { PromptMode } from '@/models/debug' import { useFileUploadConfig } from '@/service/use-common' @@ -128,7 +127,6 @@ export const useConfiguration = (): ConfigurationViewModel => { const { currentModel: currentRerankModel, currentProvider: currentRerankProvider } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) - const { isAPIKeySet } = useProviderContext() const { currentModel: currModel } = useTextGenerationCurrentProviderAndModelAndModelList({ provider: modelConfig.provider, model: modelConfig.model_id, @@ -367,7 +365,6 @@ export const useConfiguration = (): ConfigurationViewModel => { isAdvancedMode, isAgent, isAllowVideoUpload, - isAPIKeySet, isFunctionCall, isOpenAI: modelConfig.provider === 'langgenius/openai/openai', isShowAudioConfig, diff --git a/web/app/components/app/overview/apikey-info-panel/__tests__/test-utils.tsx b/web/app/components/app/overview/apikey-info-panel/__tests__/test-utils.tsx index 05d10c13de2..386ce075a67 100644 --- a/web/app/components/app/overview/apikey-info-panel/__tests__/test-utils.tsx +++ b/web/app/components/app/overview/apikey-info-panel/__tests__/test-utils.tsx @@ -1,9 +1,9 @@ import type { DeploymentEdition } from '@dify/contracts/api/console/system-features/types.gen' +import type { AvailableModelListResponse } from '@dify/contracts/api/console/workspaces/types.gen' import type { RenderOptions } from '@testing-library/react' -import type { MockedFunction } from 'vite-plus/test' import { fireEvent, screen } from '@testing-library/react' -import { useProviderContext as actualUseProviderContext } from '@/context/provider-context' -import { renderWithConsoleQuery } from '@/test/console/query-data' +import { consoleQuery } from '@/service/console' +import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data' import APIKeyInfoPanel from '../index' const { mockRouterPush, mockSetSettingsDestination } = vi.hoisted(() => ({ @@ -12,9 +12,6 @@ const { mockRouterPush, mockSetSettingsDestination } = vi.hoisted(() => ({ })) // Mock the modules before importing the functions -vi.mock('@/context/provider-context', () => ({ - useProviderContext: vi.fn(), -})) vi.mock('nuqs', async (importOriginal) => { const actual = await importOriginal() @@ -30,24 +27,7 @@ vi.mock('@/next/navigation', () => ({ }), })) -// Type casting for mocks -const mockUseProviderContext = actualUseProviderContext as MockedFunction< - typeof actualUseProviderContext -> -// Default mock data -const defaultProviderContext = { - modelProviders: [], - modelProviderPlugins: {}, - refreshModelProviders: async () => {}, - isLoadingModelProviders: false, - isSuccessModelProviders: false, - textGenerationModelList: [], - isAPIKeySet: false, -} - -type MockOverrides = { - providerContext?: Partial -} +type MockOverrides = { hasActiveProvider?: boolean } type APIKeyInfoPanelRenderOptions = { mockOverrides?: MockOverrides @@ -56,22 +36,33 @@ type APIKeyInfoPanelRenderOptions = { const mainButtonName = /appOverview\.apiKeyInfo\.setAPIBtn/ let deploymentEdition: DeploymentEdition = 'COMMUNITY' -// Setup function to configure mocks -function setupMocks(overrides: MockOverrides = {}) { - mockUseProviderContext.mockReturnValue({ - ...defaultProviderContext, - ...overrides.providerContext, - }) -} - // Custom render function function renderAPIKeyInfoPanel(options: APIKeyInfoPanelRenderOptions = {}) { const { mockOverrides, ...renderOptions } = options - setupMocks(mockOverrides) + const queryClient = createConsoleQueryClient() + queryClient.setQueryData( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({ + input: { params: { model_type: 'llm' } }, + }), + { + data: mockOverrides?.hasActiveProvider + ? [ + { + provider: 'openai', + tenant_id: 'test-workspace', + label: { en_US: 'OpenAI' }, + status: 'active', + models: [], + }, + ] + : [], + } satisfies AvailableModelListResponse, + ) return renderWithConsoleQuery(, { ...renderOptions, + queryClient, systemFeatures: { deployment_edition: deploymentEdition }, }) } @@ -82,7 +73,7 @@ export const scenarios = { withAPIKeyNotSet: (overrides: MockOverrides = {}) => renderAPIKeyInfoPanel({ mockOverrides: { - providerContext: { isAPIKeySet: false }, + hasActiveProvider: false, ...overrides, }, }), @@ -91,7 +82,7 @@ export const scenarios = { withAPIKeySet: (overrides: MockOverrides = {}) => renderAPIKeyInfoPanel({ mockOverrides: { - providerContext: { isAPIKeySet: true }, + hasActiveProvider: true, ...overrides, }, }), diff --git a/web/app/components/app/overview/apikey-info-panel/index.tsx b/web/app/components/app/overview/apikey-info-panel/index.tsx index 5e93e13a771..377aa5df463 100644 --- a/web/app/components/app/overview/apikey-info-panel/index.tsx +++ b/web/app/components/app/overview/apikey-info-panel/index.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { RiCloseLine } from '@remixicon/react' -import { useSuspenseQuery } from '@tanstack/react-query' +import { useQuery, useSuspenseQuery } from '@tanstack/react-query' import { useQueryState } from 'nuqs' import * as React from 'react' import { useState } from 'react' @@ -13,8 +13,8 @@ import { settingsQueryParamName, settingsQueryParser, } from '@/app/components/header/account-setting/query-params' -import { useProviderContext } from '@/context/provider-context' import { systemFeaturesQueryOptions } from '@/features/system-features/client' +import { consoleQuery } from '@/service/console' const APIKeyInfoPanel: FC = () => { const { data: deploymentEdition } = useSuspenseQuery({ @@ -23,14 +23,19 @@ const APIKeyInfoPanel: FC = () => { }) const isCloud = deploymentEdition === 'CLOUD' - const { isAPIKeySet } = useProviderContext() + const { data: hasActiveProvider = false } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: 'llm' } }, + select: (response) => response.data.some((provider) => provider.status === 'active'), + }), + ) const [, setSettingsDestination] = useQueryState(settingsQueryParamName, settingsQueryParser) const { t } = useTranslation() const [isShow, setIsShow] = useState(true) - if (isAPIKeySet) return null + if (hasActiveProvider) return null if (!isShow) return null diff --git a/web/app/components/datasets/common/__tests__/check-rerank-model.spec.ts b/web/app/components/datasets/common/__tests__/check-rerank-model.spec.ts index 21bee3a24d1..8ed9980b9e2 100644 --- a/web/app/components/datasets/common/__tests__/check-rerank-model.spec.ts +++ b/web/app/components/datasets/common/__tests__/check-rerank-model.spec.ts @@ -1,7 +1,7 @@ import type { - Model, - ModelItem, -} from '@/app/components/header/account-setting/model-provider-page/declarations' + ProviderModelWithStatusEntity, + ProviderWithModelsResponse, +} from '@dify/contracts/api/console/workspaces/types.gen' import type { RetrievalConfig } from '@/types/app' import { describe, expect, it } from 'vite-plus/test' import { @@ -27,7 +27,7 @@ const createRetrievalConfig = (overrides: Partial = {}): Retrie ...overrides, }) -const createModelItem = (model: string): ModelItem => ({ +const createModelItem = (model: string): ProviderModelWithStatusEntity => ({ model, label: { en_US: model, zh_Hans: model }, model_type: ModelTypeEnum.rerank, @@ -37,8 +37,9 @@ const createModelItem = (model: string): ModelItem => ({ load_balancing_enabled: false, }) -const createRerankModelList = (): Model[] => [ +const createRerankModelList = (): ProviderWithModelsResponse[] => [ { + tenant_id: 'test-workspace', provider: 'openai', icon_small: { en_US: '', zh_Hans: '' }, label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, @@ -46,6 +47,7 @@ const createRerankModelList = (): Model[] => [ status: ModelStatusEnum.active, }, { + tenant_id: 'test-workspace', provider: 'cohere', icon_small: { en_US: '', zh_Hans: '' }, label: { en_US: 'Cohere', zh_Hans: 'Cohere' }, diff --git a/web/app/components/datasets/common/check-rerank-model.ts b/web/app/components/datasets/common/check-rerank-model.ts index 19655d6aa50..e5271c1bd10 100644 --- a/web/app/components/datasets/common/check-rerank-model.ts +++ b/web/app/components/datasets/common/check-rerank-model.ts @@ -1,4 +1,4 @@ -import type { Model } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen' import type { RetrievalConfig } from '@/types/app' import { RerankingModeEnum } from '@/models/datasets' import { RETRIEVE_METHOD } from '@/types/app' @@ -9,7 +9,7 @@ export const isReRankModelSelected = ({ indexMethod, }: { retrievalConfig: RetrievalConfig - rerankModelList: Model[] + rerankModelList: ProviderWithModelsResponse[] indexMethod?: string }) => { const rerankModelSelected = (() => { diff --git a/web/app/components/datasets/common/multimodal-retrieval-guidance/__tests__/index.spec.tsx b/web/app/components/datasets/common/multimodal-retrieval-guidance/__tests__/index.spec.tsx index e29746fb774..c4352514b20 100644 --- a/web/app/components/datasets/common/multimodal-retrieval-guidance/__tests__/index.spec.tsx +++ b/web/app/components/datasets/common/multimodal-retrieval-guidance/__tests__/index.spec.tsx @@ -1,3 +1,4 @@ +import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { @@ -9,7 +10,10 @@ import { import { MultimodalRetrievalGuidance, MultimodalRetrievalGuidanceLearnMore } from '../index' import { MULTIMODAL_RETRIEVAL_GUIDANCE_DISMISSED_STORAGE_KEY } from '../storage' -const createEmbeddingModelProvider = (features: ModelFeatureEnum[] = []) => ({ +const createEmbeddingModelProvider = ( + features: ModelFeatureEnum[] = [], +): ProviderWithModelsResponse => ({ + tenant_id: 'test-workspace', provider: 'test-provider', icon_small: { en_US: '', zh_Hans: '' }, label: { en_US: 'Test Provider', zh_Hans: 'Test Provider' }, diff --git a/web/app/components/datasets/common/multimodal-retrieval-guidance/index.tsx b/web/app/components/datasets/common/multimodal-retrieval-guidance/index.tsx index d58d276b35a..59f1fa8d5c2 100644 --- a/web/app/components/datasets/common/multimodal-retrieval-guidance/index.tsx +++ b/web/app/components/datasets/common/multimodal-retrieval-guidance/index.tsx @@ -1,9 +1,6 @@ 'use client' - -import type { - DefaultModel, - Model, -} from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen' +import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import { cn } from '@langgenius/dify-ui/cn' import { useTranslation } from 'react-i18next' import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' @@ -18,14 +15,17 @@ type MultimodalRetrievalGuidanceVariant = 'create' | 'settings' | 'pipeline' type MultimodalRetrievalGuidanceProps = { variant: MultimodalRetrievalGuidanceVariant embeddingModel?: DefaultModel - embeddingModelList?: Model[] + embeddingModelList?: ProviderWithModelsResponse[] className?: string } const MULTIMODAL_RETRIEVAL_DOC_URL = 'https://dify.ai/blog/multimodal-retrieval-is-now-available-in-the-knowledge-base' -const isVisionEmbeddingModel = (embeddingModel?: DefaultModel, embeddingModelList?: Model[]) => { +const isVisionEmbeddingModel = ( + embeddingModel?: DefaultModel, + embeddingModelList?: ProviderWithModelsResponse[], +) => { if (!embeddingModel?.provider || !embeddingModel.model) return false const provider = embeddingModelList?.find((item) => item.provider === embeddingModel.provider) diff --git a/web/app/components/datasets/create/step-two/__tests__/index.spec.tsx b/web/app/components/datasets/create/step-two/__tests__/index.spec.tsx index 6913fa65dfc..cc03749fff2 100644 --- a/web/app/components/datasets/create/step-two/__tests__/index.spec.tsx +++ b/web/app/components/datasets/create/step-two/__tests__/index.spec.tsx @@ -1,4 +1,8 @@ -import type { Model } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { + GetWorkspacesCurrentModelsModelTypesByModelTypeData, + ProviderWithModelsResponse, +} from '@dify/contracts/api/console/workspaces/types.gen' +import type { OperationKey } from '@orpc/tanstack-query' import type { DataSourceProvider, NotionPage } from '@/models/common' import type { CrawlOptions, @@ -75,8 +79,9 @@ const mockDefaultEmbeddingModel = { model: 'text-embedding-ada-002', } // Model[] type structure for rerank model list (simplified mock) -const mockRerankModelList: Model[] = [ +const mockRerankModelList: ProviderWithModelsResponse[] = [ { + tenant_id: 'test-workspace', provider: 'cohere', icon_small: { en_US: 'cohere-icon', zh_Hans: 'cohere-icon' }, label: { en_US: 'Cohere', zh_Hans: 'Cohere' }, @@ -104,7 +109,7 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () defaultModel: mockRerankDefaultModel, currentModel: mockIsRerankDefaultModelValid, }), - useModelList: () => ({ data: mockEmbeddingModelList }), + useDefaultModel: () => ({ data: mockDefaultEmbeddingModel }), })) @@ -2620,3 +2625,19 @@ describe('StepTwo Component', () => { }) }) }) + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useQuery: (options: { + queryKey: OperationKey< + 'query', + { params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] } + > + }) => { + if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options) + return { data: mockEmbeddingModelList } + }, + } +}) diff --git a/web/app/components/datasets/create/step-two/components/indexing-mode-section.tsx b/web/app/components/datasets/create/step-two/components/indexing-mode-section.tsx index 8f0e02a4ee5..bc6c1bd1676 100644 --- a/web/app/components/datasets/create/step-two/components/indexing-mode-section.tsx +++ b/web/app/components/datasets/create/step-two/components/indexing-mode-section.tsx @@ -1,10 +1,7 @@ 'use client' - +import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen' import type { FC } from 'react' -import type { - DefaultModel, - Model, -} from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { RetrievalConfig } from '@/types/app' import { AlertDialog, @@ -40,7 +37,7 @@ type IndexingModeSectionProps = { hasSetIndexType: boolean docForm: ChunkingMode embeddingModel: DefaultModel - embeddingModelList?: Model[] + embeddingModelList?: ProviderWithModelsResponse[] retrievalConfig: RetrievalConfig showMultiModalTip: boolean // Flags diff --git a/web/app/components/datasets/create/step-two/hooks/__tests__/use-indexing-config.spec.ts b/web/app/components/datasets/create/step-two/hooks/__tests__/use-indexing-config.spec.ts index 94c7c28a6fa..b299dcd457d 100644 --- a/web/app/components/datasets/create/step-two/hooks/__tests__/use-indexing-config.spec.ts +++ b/web/app/components/datasets/create/step-two/hooks/__tests__/use-indexing-config.spec.ts @@ -1,3 +1,5 @@ +import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen' +import type { OperationKey } from '@orpc/tanstack-query' import { act, renderHook } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vite-plus/test' import { RETRIEVE_METHOD } from '@/types/app' @@ -17,7 +19,7 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () defaultModel: mocks.rerankDefaultModel, currentModel: mocks.isRerankDefaultModelValid, }), - useModelList: () => ({ data: mocks.embeddingModelList }), + useDefaultModel: () => ({ data: mocks.defaultEmbeddingModel }), })) @@ -159,3 +161,19 @@ describe('useIndexingConfig', () => { }) }) }) + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useQuery: (options: { + queryKey: OperationKey< + 'query', + { params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] } + > + }) => { + if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options) + return { data: mocks.embeddingModelList } + }, + } +}) diff --git a/web/app/components/datasets/create/step-two/hooks/use-document-creation.ts b/web/app/components/datasets/create/step-two/hooks/use-document-creation.ts index b17bb62383f..7c7b38af414 100644 --- a/web/app/components/datasets/create/step-two/hooks/use-document-creation.ts +++ b/web/app/components/datasets/create/step-two/hooks/use-document-creation.ts @@ -1,7 +1,5 @@ -import type { - DefaultModel, - Model, -} from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen' +import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { NotionPage } from '@/models/common' import type { ChunkingMode, @@ -60,7 +58,7 @@ type ValidationParams = { overlap: number indexType: IndexingType embeddingModel: DefaultModel - rerankModelList: Model[] + rerankModelList: ProviderWithModelsResponse[] retrievalConfig: RetrievalConfig } export const useDocumentCreation = (options: UseDocumentCreationOptions) => { diff --git a/web/app/components/datasets/create/step-two/hooks/use-indexing-config.ts b/web/app/components/datasets/create/step-two/hooks/use-indexing-config.ts index 04b9f907634..e87a337d1c6 100644 --- a/web/app/components/datasets/create/step-two/hooks/use-indexing-config.ts +++ b/web/app/components/datasets/create/step-two/hooks/use-indexing-config.ts @@ -1,13 +1,14 @@ import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { RetrievalConfig } from '@/types/app' +import { useQuery } from '@tanstack/react-query' import { useEffect, useMemo, useState } from 'react' import { checkShowMultiModalTip } from '@/app/components/datasets/settings/utils' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useDefaultModel, - useModelList, useModelListAndDefaultModelAndCurrentProviderAndModel, } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { consoleQuery } from '@/service/console' import { RETRIEVE_METHOD } from '@/types/app' export enum IndexingType { @@ -52,7 +53,12 @@ export const useIndexingConfig = (options: UseIndexingConfigOptions) => { } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) // Embedding model list - const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding) + const { data: embeddingModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textEmbedding } }, + select: (response) => response.data, + }), + ) const { data: defaultEmbeddingModel } = useDefaultModel(ModelTypeEnum.textEmbedding) // Index type state diff --git a/web/app/components/datasets/hit-testing/__tests__/index.spec.tsx b/web/app/components/datasets/hit-testing/__tests__/index.spec.tsx index 4120370fc93..038a3d386d7 100644 --- a/web/app/components/datasets/hit-testing/__tests__/index.spec.tsx +++ b/web/app/components/datasets/hit-testing/__tests__/index.spec.tsx @@ -1,3 +1,5 @@ +import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen' +import type { OperationKey } from '@orpc/tanstack-query' import type { ReactNode } from 'react' import type { DataSet, HitTesting, HitTestingRecord, HitTestingResponse } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' @@ -154,11 +156,11 @@ vi.mock('@/service/knowledge/use-dataset', () => ({ vi.mock('@/service/knowledge/use-hit-testing', () => ({ useHitTesting: vi.fn(() => ({ mutateAsync: mockHitTestingMutateAsync, - isPending: false, + isLoading: false, })), useExternalKnowledgeBaseHitTesting: vi.fn(() => ({ mutateAsync: mockExternalHitTestingMutateAsync, - isPending: false, + isLoading: false, })), })) @@ -263,10 +265,6 @@ vi.mock('@/context/i18n', () => ({ // Mock model list hook - include all exports used by child components vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ - useModelList: vi.fn(() => ({ - data: [], - isLoading: false, - })), useModelListAndDefaultModelAndCurrentProviderAndModel: vi.fn(() => ({ modelList: [], defaultModel: undefined, @@ -400,11 +398,11 @@ describe('HitTestingPage', () => { await import('@/service/knowledge/use-hit-testing') vi.mocked(useHitTesting).mockReturnValue({ mutateAsync: mockHitTestingMutateAsync, - isPending: false, + isLoading: false, } as unknown as ReturnType) vi.mocked(useExternalKnowledgeBaseHitTesting).mockReturnValue({ mutateAsync: mockExternalHitTestingMutateAsync, - isPending: false, + isLoading: false, } as unknown as ReturnType) const useBreakpoints = await import('@/hooks/use-breakpoints') @@ -578,3 +576,19 @@ describe('HitTestingPage', () => { expect(await screen.findByText('External content')).toBeInTheDocument() }) }) + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useQuery: (options: { + queryKey: OperationKey< + 'query', + { params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] } + > + }) => + options.queryKey[0].includes('modelTypes') + ? { data: [], isPending: false } + : actual.useQuery(options), + } +}) diff --git a/web/app/components/datasets/hit-testing/__tests__/modify-retrieval-modal.spec.tsx b/web/app/components/datasets/hit-testing/__tests__/modify-retrieval-modal.spec.tsx index e2e1a6739fd..4a9582db78b 100644 --- a/web/app/components/datasets/hit-testing/__tests__/modify-retrieval-modal.spec.tsx +++ b/web/app/components/datasets/hit-testing/__tests__/modify-retrieval-modal.spec.tsx @@ -1,3 +1,5 @@ +import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen' +import type { OperationKey } from '@orpc/tanstack-query' import type { RetrievalConfig } from '@/types/app' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vite-plus/test' @@ -50,10 +52,6 @@ vi.mock('@/app/components/datasets/common/economical-retrieval-method-config', ( default: () =>
, })) -vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ - useModelList: () => ({ data: [] }), -})) - vi.mock('@/context/dataset-detail', () => ({ useDatasetDetailContextWithSelector: () => 'model-name', })) @@ -125,3 +123,19 @@ describe('ModifyRetrievalModal', () => { expect(screen.getByText('datasetSettings.form.retrievalSetting.learnMore')).toBeInTheDocument() }) }) + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useQuery: (options: { + queryKey: OperationKey< + 'query', + { params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] } + > + }) => { + if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options) + return { data: [] } + }, + } +}) diff --git a/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx b/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx index ce8d887defc..dce7f4d080e 100644 --- a/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx +++ b/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx @@ -5,15 +5,16 @@ import type { RetrievalConfig } from '@/types/app' import { Button } from '@langgenius/dify-ui/button' import { toast } from '@langgenius/dify-ui/toast' import { RiCloseLine } from '@remixicon/react' +import { useQuery } from '@tanstack/react-query' import * as React from 'react' import { useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config' import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config' -import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import { useDocLink } from '@/context/i18n' +import { consoleQuery } from '@/service/console' import { ModelTypeEnum } from '../../header/account-setting/model-provider-page/declarations' import { checkShowMultiModalTip } from '../settings/utils' @@ -39,8 +40,18 @@ const ModifyRetrievalModal: FC = ({ indexMethod, value, isShow, onHide, o // if (ref) // onHide() // }, ref) - const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding) - const { data: rerankModelList } = useModelList(ModelTypeEnum.rerank) + const { data: embeddingModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textEmbedding } }, + select: (response) => response.data, + }), + ) + const { data: rerankModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.rerank } }, + select: (response) => response.data, + }), + ) const handleSave = () => { if ( !isReRankModelSelected({ diff --git a/web/app/components/datasets/settings/__tests__/summary-index-setting.spec.tsx b/web/app/components/datasets/settings/__tests__/summary-index-setting.spec.tsx index 9308feaae7b..93e5390507f 100644 --- a/web/app/components/datasets/settings/__tests__/summary-index-setting.spec.tsx +++ b/web/app/components/datasets/settings/__tests__/summary-index-setting.spec.tsx @@ -1,21 +1,10 @@ +import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen' +import type { OperationKey } from '@orpc/tanstack-query' import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vite-plus/test' import SummaryIndexSetting from '../summary-index-setting' -// Mock useModelList to return a list of text generation models -vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ - useModelList: () => ({ - data: [ - { - provider: 'openai', - label: { en_US: 'OpenAI' }, - models: [ - { model: 'gpt-4', label: { en_US: 'GPT-4' }, model_type: 'llm', status: 'active' }, - ], - }, - ], - }), -})) +// Mock the model list query. // Mock ModelSelector (external component from header module) vi.mock('@/app/components/header/account-setting/model-provider-page/model-selector', () => ({ @@ -222,3 +211,30 @@ describe('SummaryIndexSetting', () => { }) }) }) + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useQuery: (options: { + queryKey: OperationKey< + 'query', + { params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] } + > + }) => { + if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options) + return { + data: [ + { + tenant_id: 'test-workspace', + provider: 'openai', + label: { en_US: 'OpenAI' }, + models: [ + { model: 'gpt-4', label: { en_US: 'GPT-4' }, model_type: 'llm', status: 'active' }, + ], + }, + ], + } + }, + } +}) diff --git a/web/app/components/datasets/settings/form/__tests__/index.spec.tsx b/web/app/components/datasets/settings/form/__tests__/index.spec.tsx index ce0c21d6cd6..0bc05f9d189 100644 --- a/web/app/components/datasets/settings/form/__tests__/index.spec.tsx +++ b/web/app/components/datasets/settings/form/__tests__/index.spec.tsx @@ -1,3 +1,5 @@ +import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen' +import type { OperationKey } from '@orpc/tanstack-query' import type { ReactElement } from 'react' import type { DataSet } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' @@ -177,7 +179,6 @@ vi.mock('@/service/use-common', () => ({ })) vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ - useModelList: () => ({ data: [], mutate: vi.fn(), isLoading: false }), useCurrentProviderAndModel: () => ({ currentProvider: undefined, currentModel: undefined }), useDefaultModel: () => ({ data: undefined, mutate: vi.fn(), isLoading: false }), useModelListAndDefaultModel: () => ({ modelList: [], defaultModel: undefined }), @@ -206,7 +207,6 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () // Mock provider-context vi.mock('@/context/provider-context', () => ({ useProviderContext: () => ({ - textGenerationModelList: [], embeddingsModelList: [], rerankModelList: [], agentThoughtModelList: [], @@ -561,3 +561,19 @@ describe('Form', () => { }) }) }) + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useQuery: (options: { + queryKey: OperationKey< + 'query', + { params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] } + > + }) => { + if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options) + return { data: [], refetch: vi.fn(), isPending: false } + }, + } +}) diff --git a/web/app/components/datasets/settings/form/components/__tests__/indexing-section.spec.tsx b/web/app/components/datasets/settings/form/components/__tests__/indexing-section.spec.tsx index 04349eea7a0..6fa5f83e660 100644 --- a/web/app/components/datasets/settings/form/components/__tests__/indexing-section.spec.tsx +++ b/web/app/components/datasets/settings/form/components/__tests__/indexing-section.spec.tsx @@ -1,7 +1,5 @@ -import type { - DefaultModel, - Model, -} from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen' +import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { DataSet, SummaryIndexSetting } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' import { fireEvent, screen } from '@testing-library/react' @@ -222,8 +220,9 @@ describe('IndexingSection', () => { model: 'text-embedding-ada-002', } - const mockEmbeddingModelList: Model[] = [ + const mockEmbeddingModelList: ProviderWithModelsResponse[] = [ { + tenant_id: 'test-workspace', provider: 'openai', label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, icon_small: { en_US: '', zh_Hans: '' }, diff --git a/web/app/components/datasets/settings/form/components/indexing-section.tsx b/web/app/components/datasets/settings/form/components/indexing-section.tsx index ae18d021ec6..7257e391d55 100644 --- a/web/app/components/datasets/settings/form/components/indexing-section.tsx +++ b/web/app/components/datasets/settings/form/components/indexing-section.tsx @@ -1,8 +1,6 @@ 'use client' -import type { - DefaultModel, - Model, -} from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen' +import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { DataSet, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' import { useSuspenseQuery } from '@tanstack/react-query' @@ -34,7 +32,7 @@ type IndexingSectionProps = { setKeywordNumber: (value: number) => void embeddingModel: DefaultModel setEmbeddingModel: (value: DefaultModel) => void - embeddingModelList: Model[] + embeddingModelList: ProviderWithModelsResponse[] retrievalConfig: RetrievalConfig setRetrievalConfig: (value: RetrievalConfig) => void summaryIndexSetting: SummaryIndexSettingType | undefined @@ -150,7 +148,7 @@ const IndexingSection = ({
)} - {/* Embedding Model */} + {/* Embedding ProviderWithModelsResponse */} {indexMethod === IndexingType.QUALIFIED && (
diff --git a/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts b/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts index 814074e6f7c..736e8ec15d9 100644 --- a/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts +++ b/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts @@ -1,3 +1,5 @@ +import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen' +import type { OperationKey } from '@orpc/tanstack-query' import type { DataSet } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' import { act, waitFor } from '@testing-library/react' @@ -176,10 +178,6 @@ vi.mock('@/service/use-common', () => ({ }), })) -vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ - useModelList: () => ({ data: [] }), -})) - vi.mock('@/app/components/datasets/common/check-rerank-model', () => ({ isReRankModelSelected: () => true, })) @@ -835,3 +833,19 @@ describe('useFormState', () => { }) }) }) + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useQuery: (options: { + queryKey: OperationKey< + 'query', + { params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] } + > + }) => { + if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options) + return { data: [] } + }, + } +}) diff --git a/web/app/components/datasets/settings/form/hooks/use-form-state.ts b/web/app/components/datasets/settings/form/hooks/use-form-state.ts index b02939980d9..3fa3ac4ca36 100644 --- a/web/app/components/datasets/settings/form/hooks/use-form-state.ts +++ b/web/app/components/datasets/settings/form/hooks/use-form-state.ts @@ -1,22 +1,21 @@ 'use client' - import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Member } from '@/models/common' import type { IconInfo, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' import { toast } from '@langgenius/dify-ui/toast' -import { useSuspenseQuery } from '@tanstack/react-query' +import { useQuery, useSuspenseQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { userProfileQueryOptions } from '@/features/account-profile/client' import { DatasetPermission } from '@/models/datasets' +import { consoleQuery } from '@/service/console' import { updateDatasetSetting } from '@/service/datasets' import { useInvalidDatasetList } from '@/service/knowledge/use-dataset' import { useMembers } from '@/service/use-common' @@ -103,8 +102,18 @@ export const useFormState = () => { ) // Model lists - const { data: rerankModelList } = useModelList(ModelTypeEnum.rerank) - const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding) + const { data: rerankModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.rerank } }, + select: (response) => response.data, + }), + ) + const { data: embeddingModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textEmbedding } }, + select: (response) => response.data, + }), + ) const { data: membersData } = useMembers() const invalidDatasetList = useInvalidDatasetList() diff --git a/web/app/components/datasets/settings/summary-index-setting.tsx b/web/app/components/datasets/settings/summary-index-setting.tsx index 8c524d4a697..d4cbe51e9d8 100644 --- a/web/app/components/datasets/settings/summary-index-setting.tsx +++ b/web/app/components/datasets/settings/summary-index-setting.tsx @@ -2,12 +2,13 @@ import type { DefaultModel } from '@/app/components/header/account-setting/model import type { SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets' import { Switch } from '@langgenius/dify-ui/switch' import { Textarea } from '@langgenius/dify-ui/textarea' +import { useQuery } from '@tanstack/react-query' import { memo, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Infotip } from '@/app/components/base/infotip' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' import { ModelSelector } from '@/app/components/header/account-setting/model-provider-page/model-selector' +import { consoleQuery } from '@/service/console' type SummaryIndexSettingProps = { entry?: 'knowledge-base' | 'dataset-settings' | 'create-document' @@ -22,7 +23,12 @@ const SummaryIndexSetting = ({ readonly = false, }: SummaryIndexSettingProps) => { const { t } = useTranslation() - const { data: textGenerationModelList } = useModelList(ModelTypeEnum.textGeneration) + const { data: textGenerationModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textGeneration } }, + select: (response) => response.data, + }), + ) const summaryIndexModelConfig = useMemo(() => { if (!summaryIndexSetting?.model_name || !summaryIndexSetting?.model_provider_name) return undefined diff --git a/web/app/components/datasets/settings/utils/__tests__/index.spec.ts b/web/app/components/datasets/settings/utils/__tests__/index.spec.ts index 808816a02ac..e7a327bb785 100644 --- a/web/app/components/datasets/settings/utils/__tests__/index.spec.ts +++ b/web/app/components/datasets/settings/utils/__tests__/index.spec.ts @@ -1,8 +1,8 @@ import type { - DefaultModel, - Model, - ModelItem, -} from '@/app/components/header/account-setting/model-provider-page/declarations' + ProviderModelWithStatusEntity, + ProviderWithModelsResponse, +} from '@dify/contracts/api/console/workspaces/types.gen' +import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import { ConfigurationMethodEnum, ModelFeatureEnum, @@ -14,7 +14,10 @@ import { checkShowMultiModalTip } from '../index' describe('checkShowMultiModalTip', () => { // Helper to create a model item with specific features - const createModelItem = (model: string, features: ModelFeatureEnum[] = []): ModelItem => ({ + const createModelItem = ( + model: string, + features: ModelFeatureEnum[] = [], + ): ProviderModelWithStatusEntity => ({ model, label: { en_US: model, zh_Hans: model }, model_type: ModelTypeEnum.textEmbedding, @@ -27,7 +30,11 @@ describe('checkShowMultiModalTip', () => { }) // Helper to create a model provider - const createModelProvider = (provider: string, models: ModelItem[]): Model => ({ + const createModelProvider = ( + provider: string, + models: ProviderModelWithStatusEntity[], + ): ProviderWithModelsResponse => ({ + tenant_id: 'test-workspace', provider, label: { en_US: provider, zh_Hans: provider }, icon_small: { en_US: '', zh_Hans: '' }, @@ -223,7 +230,7 @@ describe('checkShowMultiModalTip', () => { }) it('should handle model with undefined features', () => { - const modelItem: ModelItem = { + const modelItem: ProviderModelWithStatusEntity = { model: 'test-model', label: { en_US: 'test', zh_Hans: 'test' }, model_type: ModelTypeEnum.textEmbedding, @@ -243,7 +250,7 @@ describe('checkShowMultiModalTip', () => { }) it('should handle model with null features', () => { - const modelItem: ModelItem = { + const modelItem: ProviderModelWithStatusEntity = { model: 'text-embedding-ada-002', label: { en_US: 'test', zh_Hans: 'test' }, model_type: ModelTypeEnum.textEmbedding, diff --git a/web/app/components/datasets/settings/utils/index.tsx b/web/app/components/datasets/settings/utils/index.tsx index beb07e38e2c..be3ced811ae 100644 --- a/web/app/components/datasets/settings/utils/index.tsx +++ b/web/app/components/datasets/settings/utils/index.tsx @@ -1,7 +1,5 @@ -import type { - DefaultModel, - Model, -} from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen' +import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { IndexingType } from '../../create/step-two' @@ -13,8 +11,8 @@ type ShowMultiModalTipProps = { rerankingModelName: string } indexMethod: IndexingType | undefined - embeddingModelList: Model[] - rerankModelList: Model[] + embeddingModelList: ProviderWithModelsResponse[] + rerankModelList: ProviderWithModelsResponse[] } export const checkShowMultiModalTip = ({ diff --git a/web/app/components/explore/try-app/preview/basic-app-preview.tsx b/web/app/components/explore/try-app/preview/basic-app-preview.tsx index 775125146b0..f9d4663c61a 100644 --- a/web/app/components/explore/try-app/preview/basic-app-preview.tsx +++ b/web/app/components/explore/try-app/preview/basic-app-preview.tsx @@ -531,7 +531,6 @@ const BasicAppPreview: FC = ({ appId }) => { const value = { readonly: true, appId, - isAPIKeySet: true, isTrailFinished: false, mode, modelModeType: '', @@ -619,7 +618,7 @@ const BasicAppPreview: FC = ({ appId }) => { >
= {}): ModelItem => ({ +const createModelItem = ( + overrides: Partial = {}, +): ProviderModelWithStatusEntity => ({ model: 'text-embedding-3-large', label: { en_US: 'Text Embedding 3 Large', zh_Hans: 'Text Embedding 3 Large' }, model_type: ModelTypeEnum.textEmbedding, @@ -30,7 +36,10 @@ const createModelItem = (overrides: Partial = {}): ModelItem => ({ const createModelProvider = (): ModelProvider => ({ provider: 'openai' }) as ModelProvider -const createModel = (overrides: Partial = {}): Model => ({ +const createModel = ( + overrides: Partial = {}, +): ProviderWithModelsResponse => ({ + tenant_id: 'test-workspace', provider: 'openai', icon_small: { en_US: '', zh_Hans: '' }, label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, diff --git a/web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts b/web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts index b5375efef30..4f6c79c5514 100644 --- a/web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts +++ b/web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts @@ -1,10 +1,10 @@ +import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen' import type { Mock } from 'vite-plus/test' import type { Credential, CustomConfigurationModelFixedFields, CustomModel, DefaultModelResponse, - Model, ModelProvider, } from '../declarations' import { act, renderHook } from '@testing-library/react' @@ -26,7 +26,6 @@ import { useInvalidateDefaultModel, useLanguage, useMarketplaceAllPlugins, - useModelList, useModelListAndDefaultModel, useModelListAndDefaultModelAndCurrentProviderAndModel, useModelModalHandler, @@ -51,7 +50,6 @@ vi.mock('@tanstack/react-query', () => ({ vi.mock('@/service/common', () => ({ fetchDefaultModal: vi.fn(), - fetchModelList: vi.fn(), })) vi.mock('@/service/use-common', () => ({ @@ -133,8 +131,9 @@ describe('hooks', () => { }) describe('useSystemDefaultModelAndModelList', () => { - const createMockModelList = (): Model[] => [ + const createMockModelList = (): ProviderWithModelsResponse[] => [ { + tenant_id: 'test-workspace', provider: 'openai', icon_small: { en_US: 'icon', zh_Hans: 'icon' }, label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, @@ -254,106 +253,6 @@ describe('hooks', () => { }) }) - describe('useModelList', () => { - const mockModelData = [ - { provider: 'openai', models: [{ model: 'gpt-4' }] }, - { provider: 'anthropic', models: [{ model: 'claude-3' }] }, - ] - - it('should use the generated model list key and expose the result', () => { - const refetch = vi.fn() - ;(useQuery as Mock).mockReturnValue({ - data: { data: mockModelData }, - isPending: false, - refetch, - }) - - const { result } = renderHook(() => useModelList(ModelTypeEnum.textGeneration)) - - expect(result.current.data).toEqual(mockModelData) - expect(result.current.isLoading).toBe(false) - expect(useQuery).toHaveBeenCalledWith( - expect.objectContaining({ - queryKey: getModelListQueryKey(ModelTypeEnum.textGeneration), - }), - ) - }) - - it('should return empty array when data is undefined', () => { - ;(useQuery as Mock).mockReturnValue({ - data: undefined, - isPending: false, - refetch: vi.fn(), - }) - - const { result } = renderHook(() => useModelList(ModelTypeEnum.textGeneration)) - - expect(result.current.data).toEqual([]) - }) - - it('should keep the query disabled when requested', () => { - ;(useQuery as Mock).mockReturnValue({ - data: undefined, - isPending: true, - refetch: vi.fn(), - }) - - renderHook(() => useModelList(ModelTypeEnum.textEmbedding, { enabled: false })) - - expect(useQuery).toHaveBeenCalledWith( - expect.objectContaining({ - enabled: false, - queryKey: getModelListQueryKey(ModelTypeEnum.textEmbedding), - }), - ) - }) - - it('should handle loading state', () => { - ;(useQuery as Mock).mockReturnValue({ - data: undefined, - isPending: true, - refetch: vi.fn(), - }) - - const { result } = renderHook(() => useModelList(ModelTypeEnum.textGeneration)) - - expect(result.current.isLoading).toBe(true) - }) - - it('should call mutate to refetch data', () => { - const refetch = vi.fn() - ;(useQuery as Mock).mockReturnValue({ - data: { data: mockModelData }, - isPending: false, - refetch, - }) - - const { result } = renderHook(() => useModelList(ModelTypeEnum.textGeneration)) - - act(() => { - result.current.mutate() - }) - - expect(refetch).toHaveBeenCalled() - }) - - it('should work with different model types', () => { - ;(useQuery as Mock).mockReturnValue({ - data: { data: [] }, - isPending: false, - refetch: vi.fn(), - }) - - const { result: result1 } = renderHook(() => useModelList(ModelTypeEnum.textEmbedding)) - const { result: result2 } = renderHook(() => useModelList(ModelTypeEnum.rerank)) - const { result: result3 } = renderHook(() => useModelList(ModelTypeEnum.tts)) - - expect(result1.current.data).toEqual([]) - expect(result2.current.data).toEqual([]) - expect(result3.current.data).toEqual([]) - }) - }) - describe('useDefaultModel', () => { const mockDefaultModel = { model: 'gpt-4', @@ -447,8 +346,9 @@ describe('hooks', () => { }) describe('getCurrentProviderAndModel', () => { - const createModelList = (): Model[] => [ + const createModelList = (): ProviderWithModelsResponse[] => [ { + tenant_id: 'test-workspace', provider: 'openai', icon_small: { en_US: 'icon', zh_Hans: 'icon' }, label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, @@ -526,8 +426,9 @@ describe('hooks', () => { }) describe('useTextGenerationCurrentProviderAndModelAndModelList', () => { - const createModelList = (): Model[] => [ + const createModelList = (): ProviderWithModelsResponse[] => [ { + tenant_id: 'test-workspace', provider: 'openai', icon_small: { en_US: 'icon', zh_Hans: 'icon' }, label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, @@ -545,6 +446,7 @@ describe('hooks', () => { status: ModelStatusEnum.active, }, { + tenant_id: 'test-workspace', provider: 'anthropic', icon_small: { en_US: 'icon', zh_Hans: 'icon' }, label: { en_US: 'Anthropic', zh_Hans: 'Anthropic' }, @@ -554,19 +456,19 @@ describe('hooks', () => { label: { en_US: 'Claude 3', zh_Hans: 'Claude 3' }, model_type: ModelTypeEnum.textGeneration, fetch_from: ConfigurationMethodEnum.predefinedModel, - status: ModelStatusEnum.disabled, + status: 'no-configure', model_properties: {}, load_balancing_enabled: false, }, ], - status: ModelStatusEnum.disabled, + status: 'no-configure', }, ] it('should return all text generation model lists', () => { const modelList = createModelList() ;(useQuery as Mock).mockReturnValue({ - data: { data: modelList }, + data: modelList, isPending: false, refetch: vi.fn(), }) @@ -584,7 +486,7 @@ describe('hooks', () => { it('should filter active models correctly', () => { const modelList = createModelList() ;(useQuery as Mock).mockReturnValue({ - data: { data: modelList }, + data: modelList, isPending: false, refetch: vi.fn(), }) @@ -598,7 +500,7 @@ describe('hooks', () => { it('should find current provider and model', () => { const modelList = createModelList() ;(useQuery as Mock).mockReturnValue({ - data: { data: modelList }, + data: modelList, isPending: false, refetch: vi.fn(), }) @@ -614,7 +516,7 @@ describe('hooks', () => { it('should handle empty model list', () => { ;(useQuery as Mock).mockReturnValue({ - data: { data: [] }, + data: [], isPending: false, refetch: vi.fn(), }) @@ -631,7 +533,7 @@ describe('hooks', () => { const mockModelData = [{ provider: 'openai', models: [] }] const mockDefaultModel = { model: 'gpt-4', provider: { provider: 'openai' } } ;(useQuery as Mock) - .mockReturnValueOnce({ data: { data: mockModelData }, isPending: false, refetch: vi.fn() }) + .mockReturnValueOnce({ data: mockModelData, isPending: false, refetch: vi.fn() }) .mockReturnValueOnce({ data: { data: mockDefaultModel }, isPending: false, @@ -660,6 +562,7 @@ describe('hooks', () => { it('should return complete data structure', () => { const mockModelData = [ { + tenant_id: 'test-workspace', provider: 'openai', icon_small: { en_US: 'icon', zh_Hans: 'icon' }, label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' }, @@ -683,7 +586,7 @@ describe('hooks', () => { provider: { provider: 'openai', icon_small: { en_US: 'icon', zh_Hans: 'icon' } }, } ;(useQuery as Mock) - .mockReturnValueOnce({ data: { data: mockModelData }, isPending: false, refetch: vi.fn() }) + .mockReturnValueOnce({ data: mockModelData, isPending: false, refetch: vi.fn() }) .mockReturnValueOnce({ data: { data: mockDefaultModel }, isPending: false, @@ -709,7 +612,7 @@ describe('hooks', () => { }, ] ;(useQuery as Mock) - .mockReturnValueOnce({ data: { data: mockModelData }, isPending: false, refetch: vi.fn() }) + .mockReturnValueOnce({ data: mockModelData, isPending: false, refetch: vi.fn() }) .mockReturnValueOnce({ data: undefined, isPending: false, refetch: vi.fn() }) const { result } = renderHook(() => diff --git a/web/app/components/header/account-setting/model-provider-page/declarations.ts b/web/app/components/header/account-setting/model-provider-page/declarations.ts index 62a84bc953b..9f49e9a86a5 100644 --- a/web/app/components/header/account-setting/model-provider-page/declarations.ts +++ b/web/app/components/header/account-setting/model-provider-page/declarations.ts @@ -242,15 +242,6 @@ export type ModelProvider = { allow_custom_token?: boolean } -export type Model = { - provider: string - icon_small: TypeWithI18N - icon_small_dark?: TypeWithI18N - label: TypeWithI18N - models: ModelItem[] - status: ModelStatusEnum -} - export type DefaultModelResponse = { model: string model_type: ModelTypeEnum diff --git a/web/app/components/header/account-setting/model-provider-page/hooks.ts b/web/app/components/header/account-setting/model-provider-page/hooks.ts index 85046ba3e61..73386dc0e69 100644 --- a/web/app/components/header/account-setting/model-provider-page/hooks.ts +++ b/web/app/components/header/account-setting/model-provider-page/hooks.ts @@ -1,4 +1,7 @@ -import type { ModelType } from '@dify/contracts/api/console/workspaces/types.gen' +import type { + ModelType, + ProviderWithModelsResponse, +} from '@dify/contracts/api/console/workspaces/types.gen' import type { ConfigurationMethodEnum, Credential, @@ -6,7 +9,6 @@ import type { CustomModel, DefaultModel, DefaultModelResponse, - Model, ModelModalModeEnum, ModelProvider, } from './declarations' @@ -20,7 +22,7 @@ import { import { PluginCategoryEnum } from '@/app/components/plugins/types' import { useLocale } from '@/context/i18n' import { useModalContextSelector } from '@/context/modal-context' -import { fetchDefaultModal, fetchModelList } from '@/service/common' +import { fetchDefaultModal } from '@/service/common' import { consoleQuery } from '@/service/console' import { commonQueryKeys, modelProviderDetailsQueryOptions } from '@/service/use-common' import { useExpandModelProviderList } from './atoms' @@ -28,7 +30,7 @@ import { CustomConfigurationStatusEnum, ModelStatusEnum, ModelTypeEnum } from '. type UseDefaultModelAndModelList = ( defaultModel: DefaultModelResponse | undefined, - modelList: Model[], + modelList: ProviderWithModelsResponse[], ) => [DefaultModel | undefined, (model: DefaultModel) => void] export const useSystemDefaultModelAndModelList: UseDefaultModelAndModelList = ( defaultModel, @@ -79,26 +81,6 @@ type ModelQueryOptions = { enabled?: boolean } -export const useModelList = (type: ModelTypeEnum, { enabled = true }: ModelQueryOptions = {}) => { - const { data, refetch, isPending } = useQuery({ - queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({ - input: { - params: { - model_type: type, - }, - }, - }), - queryFn: () => fetchModelList(`/workspaces/current/models/model-types/${type}`), - enabled, - }) - - return { - data: data?.data || [], - mutate: refetch, - isLoading: isPending, - } -} - export const useDefaultModel = ( type: ModelTypeEnum, { enabled = true }: ModelQueryOptions = {}, @@ -116,10 +98,6 @@ export const useDefaultModel = ( } } -type ModelFromProvider = TProvider extends { models: Array } - ? TModel - : never - export const getCurrentProviderAndModel = < TProvider extends { models: Array<{ model: string }>; provider: string }, >( @@ -127,9 +105,9 @@ export const getCurrentProviderAndModel = < defaultModel?: DefaultModel, ) => { const currentProvider = modelList.find((provider) => provider.provider === defaultModel?.provider) - const currentModel = currentProvider?.models.find( + const currentModel: TProvider['models'][number] | undefined = currentProvider?.models.find( (model) => model.model === defaultModel?.model, - ) as ModelFromProvider | undefined + ) return { currentProvider, @@ -142,7 +120,12 @@ export { getCurrentProviderAndModel as useCurrentProviderAndModel } export const useTextGenerationCurrentProviderAndModelAndModelList = ( defaultModel?: DefaultModel, ) => { - const { data: textGenerationModelList } = useModelList(ModelTypeEnum.textGeneration) + const { data: textGenerationModelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: ModelTypeEnum.textGeneration } }, + select: (response) => response.data, + }), + ) const activeTextGenerationModelList = textGenerationModelList.filter( (model) => model.status === ModelStatusEnum.active, ) @@ -160,7 +143,12 @@ export const useTextGenerationCurrentProviderAndModelAndModelList = ( } export const useModelListAndDefaultModel = (type: ModelTypeEnum) => { - const { data: modelList } = useModelList(type) + const { data: modelList = [] } = useQuery( + consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({ + input: { params: { model_type: type } }, + select: (response) => response.data, + }), + ) const { data: defaultModel } = useDefaultModel(type) return { diff --git a/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx index 5302f0aaa8b..72f9ea032c5 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx @@ -1,6 +1,9 @@ -import type { ModelProviderSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen' +import type { + ModelProviderSummaryResponse, + ProviderWithModelsResponse, +} from '@dify/contracts/api/console/workspaces/types.gen' import type { FC } from 'react' -import type { Model, ModelProvider } from '../declarations' +import type { ModelProvider } from '../declarations' import type { ModelSelectorProvider } from '../model-selector/types' import { cn } from '@langgenius/dify-ui/cn' import { OpenaiYellow } from '@/app/components/base/icons/src/public/llm' @@ -10,7 +13,11 @@ import { Theme } from '@/types/app' import { useLanguage } from '../hooks' type ModelIconProps = { - provider?: Model | ModelProvider | ModelProviderSummaryResponse | ModelSelectorProvider + provider?: + | ProviderWithModelsResponse + | ModelProvider + | ModelProviderSummaryResponse + | ModelSelectorProvider modelName?: string className?: string iconClassName?: string diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx index 9d30326a8a4..df93e22d2c2 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx @@ -1,6 +1,9 @@ -import type { ModelProviderSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen' +import type { + ModelProviderSummaryResponse, + ProviderModelWithStatusEntity, + ProviderWithModelsResponse, +} from '@dify/contracts/api/console/workspaces/types.gen' import type { ReactNode } from 'react' -import type { Model, ModelItem } from '../../declarations' import { QueryClientProvider } from '@tanstack/react-query' import { fireEvent, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' @@ -9,7 +12,9 @@ import { createConsoleQueryClient } from '@/test/console/query-data' import { ConfigurationMethodEnum, ModelStatusEnum, ModelTypeEnum } from '../../declarations' import { ModelSelector, SplitModelSelector } from '../index' -const makeModelItem = (overrides: Partial = {}): ModelItem => ({ +const makeModelItem = ( + overrides: Partial = {}, +): ProviderModelWithStatusEntity => ({ model: 'gpt-4', label: { en_US: 'GPT-4', zh_Hans: 'GPT-4' }, model_type: ModelTypeEnum.textGeneration, @@ -20,7 +25,7 @@ const makeModelItem = (overrides: Partial = {}): ModelItem => ({ ...overrides, }) -const mockModelProviders = vi.hoisted(() => ({ current: [] as Model[] })) +const mockModelProviders = vi.hoisted(() => ({ current: [] as ProviderWithModelsResponse[] })) const mockSetSettingsDestination = vi.hoisted(() => vi.fn()) vi.mock('nuqs', async (importOriginal) => { @@ -58,7 +63,7 @@ vi.mock('../popup', () => { onConfigureEmptyState?: () => void onHide: () => void onOpenProviderSettings?: () => void - onSelect: (provider: string, model: ModelItem) => void + onSelect: (provider: string, model: ProviderModelWithStatusEntity) => void }) => ( <>