mirror of
https://github.com/langgenius/dify.git
synced 2026-09-09 05:41:00 +08:00
refactor(web): move model lists out of provider context (#41939)
This commit is contained in:
parent
e26dcf55a9
commit
1006519012
@ -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": {
|
||||
|
||||
@ -9,8 +9,6 @@ export const baseProviderContextValue: ProviderContextState = {
|
||||
refreshModelProviders: async () => {},
|
||||
isLoadingModelProviders: false,
|
||||
isSuccessModelProviders: false,
|
||||
textGenerationModelList: [],
|
||||
isAPIKeySet: true,
|
||||
}
|
||||
|
||||
export const createMockProviderContextValue = (
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()),
|
||||
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: [
|
||||
|
||||
@ -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<PublishWithMultipleModelProps> = ({
|
||||
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<PublishWithMultipleModelProps> = ({
|
||||
<ModelIcon modelName={item.model} provider={item.providerItem} className="ml-2" />
|
||||
<div
|
||||
className="ml-1 truncate text-text-secondary"
|
||||
title={item.modelItem.label[language]}
|
||||
title={renderI18nObject(item.modelItem.label, language)}
|
||||
>
|
||||
{item.modelItem.label[language]}
|
||||
{renderI18nObject(item.modelItem.label, language)}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
|
||||
@ -98,7 +98,6 @@ const createDeletedAgentTool = (providerId: string): AgentTool => ({
|
||||
|
||||
const createContextValue = (): ComponentProps<typeof ConfigContext.Provider>['value'] => ({
|
||||
appId: 'app-1',
|
||||
isAPIKeySet: true,
|
||||
isTrailFinished: false,
|
||||
mode: AppModeEnum.CHAT,
|
||||
modelModeType: ModelModeType.chat,
|
||||
|
||||
@ -177,7 +177,6 @@ const ConfigurationView: FC<ConfigurationViewModel> = ({
|
||||
>
|
||||
<div className="flex grow flex-col rounded-tl-2xl border-t-[0.5px] border-l-[0.5px] border-components-panel-border bg-chatbot-bg">
|
||||
<Debug
|
||||
isAPIKeySet={contextValue.isAPIKeySet}
|
||||
onSetting={onOpenAccountSettings}
|
||||
inputs={contextValue.inputs}
|
||||
modelParameterParams={{
|
||||
@ -260,7 +259,6 @@ const ConfigurationView: FC<ConfigurationViewModel> = ({
|
||||
/>
|
||||
</div>
|
||||
<Debug
|
||||
isAPIKeySet={contextValue.isAPIKeySet}
|
||||
onSetting={onOpenAccountSettings}
|
||||
inputs={contextValue.inputs}
|
||||
modelParameterParams={{
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { OperationKey } from '@orpc/tanstack-query'
|
||||
import type { MockedFunction } from 'vite-plus/test'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import type { RetrievalConfig } from '@/types/app'
|
||||
@ -53,9 +55,9 @@ const mockOnCancel = vi.fn()
|
||||
const mockOnSave = vi.fn()
|
||||
const mockSetSettingsDestination = vi.fn()
|
||||
|
||||
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()
|
||||
const mockCheckShowMultiModalTip = vi.fn()
|
||||
|
||||
@ -94,15 +96,13 @@ vi.mock('@/context/i18n', () => ({
|
||||
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<typeof useMembers>)
|
||||
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<typeof import('@tanstack/react-query')>()
|
||||
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)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()
|
||||
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)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -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<SettingsModalProps> = ({
|
||||
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()
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()),
|
||||
useQuery: () => mockState.mockModelListResult,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/debug', () => ({
|
||||
@ -307,7 +309,6 @@ const createContextValue = (overrides: Partial<DebugContextValue> = {}): 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<typeof Debug> = {
|
||||
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()
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()),
|
||||
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()
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()),
|
||||
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 },
|
||||
]),
|
||||
})
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()),
|
||||
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()
|
||||
|
||||
@ -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<ChatItemProps> = ({ 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(() => {
|
||||
|
||||
@ -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<DebugItemProps> = ({ 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(
|
||||
|
||||
@ -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<TextGenerationItemProps> = ({ 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: {
|
||||
|
||||
@ -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<Collection>[] = []): Collect
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function for creating mock Provider Context
|
||||
*/
|
||||
function createMockProviderContext(
|
||||
overrides: Partial<ProviderContextState> = {},
|
||||
): 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(<DebugWithSingleModel ref={ref as RefObject<DebugWithSingleModelRefType>} />)
|
||||
|
||||
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(<DebugWithSingleModel ref={ref as RefObject<DebugWithSingleModelRefType>} />)
|
||||
|
||||
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 },
|
||||
|
||||
@ -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,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@ -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<ModelParameterModalProps, 'setModel' | 'onCompletionParamsChange'>
|
||||
@ -58,7 +59,7 @@ type IDebug = {
|
||||
}
|
||||
|
||||
const Debug: FC<IDebug> = ({
|
||||
isAPIKeySet = true,
|
||||
isPreview = false,
|
||||
onSetting,
|
||||
inputs,
|
||||
modelParameterParams,
|
||||
@ -332,9 +333,16 @@ const Debug: FC<IDebug> = ({
|
||||
}
|
||||
})
|
||||
|
||||
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<IDebug> = ({
|
||||
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<IDebug> = ({
|
||||
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<IDebug> = ({
|
||||
{!debugWithMultipleModel && (
|
||||
<div className="flex grow flex-col" ref={ref}>
|
||||
{/* No model provider configured */}
|
||||
{(!modelConfig.provider || !isAPIKeySet) && <HasNotSetAPIKEY onSetting={onSetting} />}
|
||||
{(!modelConfig.provider || !hasActiveProvider) && (
|
||||
<HasNotSetAPIKEY onSetting={onSetting} />
|
||||
)}
|
||||
{/* No model selected */}
|
||||
{modelConfig.provider && isAPIKeySet && !modelConfig.model_id && (
|
||||
{modelConfig.provider && hasActiveProvider && !modelConfig.model_id && (
|
||||
<div className="flex grow flex-col items-center justify-center pb-30">
|
||||
<div className="flex w-full max-w-100 flex-col gap-2 px-4 py-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-[10px]">
|
||||
|
||||
@ -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<string, unknown>) => unknown) =>
|
||||
selector({
|
||||
|
||||
@ -29,7 +29,6 @@ type ContextBase = Pick<
|
||||
| 'isShowDocumentConfig'
|
||||
| 'isShowVisionConfig'
|
||||
| 'isTrailFinished'
|
||||
| 'isAPIKeySet'
|
||||
| 'mode'
|
||||
| 'modelModeType'
|
||||
| 'prevPromptConfig'
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<typeof import('nuqs')>()
|
||||
@ -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<typeof defaultProviderContext>
|
||||
}
|
||||
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(<APIKeyInfoPanel />, {
|
||||
...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,
|
||||
},
|
||||
}),
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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<RetrievalConfig> = {}): 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' },
|
||||
|
||||
@ -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 = (() => {
|
||||
|
||||
@ -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' },
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) => {
|
||||
if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options)
|
||||
return { data: mockEmbeddingModelList }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) => {
|
||||
if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options)
|
||||
return { data: mocks.embeddingModelList }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<typeof useHitTesting>)
|
||||
vi.mocked(useExternalKnowledgeBaseHitTesting).mockReturnValue({
|
||||
mutateAsync: mockExternalHitTestingMutateAsync,
|
||||
isPending: false,
|
||||
isLoading: false,
|
||||
} as unknown as ReturnType<typeof useExternalKnowledgeBaseHitTesting>)
|
||||
|
||||
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<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) =>
|
||||
options.queryKey[0].includes('modelTypes')
|
||||
? { data: [], isPending: false }
|
||||
: actual.useQuery(options),
|
||||
}
|
||||
})
|
||||
|
||||
@ -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: () => <div data-testid="economical-config" />,
|
||||
}))
|
||||
|
||||
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<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) => {
|
||||
if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options)
|
||||
return { data: [] }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -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<Props> = ({ 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({
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()
|
||||
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' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()
|
||||
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 }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -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: '' },
|
||||
|
||||
@ -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 = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Embedding Model */}
|
||||
{/* Embedding ProviderWithModelsResponse */}
|
||||
{indexMethod === IndexingType.QUALIFIED && (
|
||||
<div className={rowClass}>
|
||||
<div className="flex w-45 shrink-0 flex-col pt-1">
|
||||
|
||||
@ -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<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) => {
|
||||
if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options)
|
||||
return { data: [] }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 = ({
|
||||
|
||||
@ -531,7 +531,6 @@ const BasicAppPreview: FC<Props> = ({ appId }) => {
|
||||
const value = {
|
||||
readonly: true,
|
||||
appId,
|
||||
isAPIKeySet: true,
|
||||
isTrailFinished: false,
|
||||
mode,
|
||||
modelModeType: '',
|
||||
@ -619,7 +618,7 @@ const BasicAppPreview: FC<Props> = ({ appId }) => {
|
||||
>
|
||||
<div className="flex grow flex-col rounded-tl-2xl border-t-[0.5px] border-l-[0.5px] border-components-panel-border bg-chatbot-bg">
|
||||
<Debug
|
||||
isAPIKeySet
|
||||
isPreview
|
||||
onSetting={noop}
|
||||
inputs={inputs}
|
||||
modelParameterParams={{
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import type { Model, ModelItem, ModelProvider } from '../declarations'
|
||||
import type {
|
||||
ProviderModelWithStatusEntity,
|
||||
ProviderWithModelsResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ModelProvider } from '../declarations'
|
||||
import type { CredentialPanelState } from '../provider-added-card/use-credential-panel-state'
|
||||
import { ConfigurationMethodEnum, ModelStatusEnum, ModelTypeEnum } from '../declarations'
|
||||
import { deriveModelStatus } from '../derive-model-status'
|
||||
@ -17,7 +21,9 @@ const createCredentialState = (
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const createModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
const createModelItem = (
|
||||
overrides: Partial<ProviderModelWithStatusEntity> = {},
|
||||
): 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> = {}): ModelItem => ({
|
||||
|
||||
const createModelProvider = (): ModelProvider => ({ provider: 'openai' }) as ModelProvider
|
||||
|
||||
const createModel = (overrides: Partial<Model> = {}): Model => ({
|
||||
const createModel = (
|
||||
overrides: Partial<ProviderWithModelsResponse> = {},
|
||||
): ProviderWithModelsResponse => ({
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'openai',
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' },
|
||||
|
||||
@ -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(() =>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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> = TProvider extends { models: Array<infer TModel> }
|
||||
? 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<TProvider> | 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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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> = {}): ModelItem => ({
|
||||
const makeModelItem = (
|
||||
overrides: Partial<ProviderModelWithStatusEntity> = {},
|
||||
): 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> = {}): 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
|
||||
}) => (
|
||||
<>
|
||||
<button type="button" onClick={() => onSelect('openai', makeModelItem())}>
|
||||
@ -82,7 +87,10 @@ vi.mock('../popup', () => {
|
||||
}
|
||||
})
|
||||
|
||||
const makeModel = (overrides: Partial<Model> = {}): Model => ({
|
||||
const makeModel = (
|
||||
overrides: Partial<ProviderWithModelsResponse> = {},
|
||||
): ProviderWithModelsResponse => ({
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'openai',
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' },
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import type {
|
||||
ProviderModelWithStatusEntity,
|
||||
ProviderWithModelsResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ReactNode } from 'react'
|
||||
import type { Model, ModelItem } from '../../declarations'
|
||||
import { Popover } from '@langgenius/dify-ui/popover'
|
||||
import { render as renderComponent, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
@ -23,7 +26,9 @@ vi.mock('../../provider-added-card/use-credential-panel-state', () => ({
|
||||
useCredentialPanelState: mockUseCredentialPanelState,
|
||||
}))
|
||||
|
||||
const createModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
const createModelItem = (
|
||||
overrides: Partial<ProviderModelWithStatusEntity> = {},
|
||||
): ProviderModelWithStatusEntity => ({
|
||||
model: 'gpt-4',
|
||||
label: { en_US: 'GPT-4', zh_Hans: 'GPT-4' },
|
||||
model_type: ModelTypeEnum.textGeneration,
|
||||
@ -35,7 +40,10 @@ const createModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const createModel = (overrides: Partial<Model> = {}): Model => ({
|
||||
const createModel = (
|
||||
overrides: Partial<ProviderWithModelsResponse> = {},
|
||||
): ProviderWithModelsResponse => ({
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'openai',
|
||||
icon_small: {
|
||||
en_US: 'https://example.com/openai-light.png',
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import type {
|
||||
ProviderModelWithStatusEntity,
|
||||
ProviderWithModelsResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ReactElement, ReactNode } from 'react'
|
||||
import type { DefaultModel, Model, ModelItem } from '../../declarations'
|
||||
import type { DefaultModel } from '../../declarations'
|
||||
import type { ModelSelectorPreviewPayload } from '../popup-item'
|
||||
import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
@ -41,7 +45,7 @@ vi.mock('../../model-name', () => ({
|
||||
nameClassName,
|
||||
children,
|
||||
}: {
|
||||
modelItem: ModelItem
|
||||
modelItem: ProviderModelWithStatusEntity
|
||||
className?: string
|
||||
nameClassName?: string
|
||||
children?: ReactNode
|
||||
@ -107,7 +111,9 @@ vi.mock('@/context/permission-state', async () => {
|
||||
}))
|
||||
})
|
||||
|
||||
const makeModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
const makeModelItem = (
|
||||
overrides: Partial<ProviderModelWithStatusEntity> = {},
|
||||
): ProviderModelWithStatusEntity => ({
|
||||
model: 'gpt-4',
|
||||
label: { en_US: 'GPT-4', zh_Hans: 'GPT-4' },
|
||||
model_type: ModelTypeEnum.textGeneration,
|
||||
@ -119,7 +125,10 @@ const makeModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const makeModel = (overrides: Partial<Model> = {}): Model => ({
|
||||
const makeModel = (
|
||||
overrides: Partial<ProviderWithModelsResponse> = {},
|
||||
): ProviderWithModelsResponse => ({
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'openai',
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' },
|
||||
@ -311,7 +320,7 @@ describe('PopupItem', () => {
|
||||
|
||||
it('should open model modal when clicking add on unconfigured model', async () => {
|
||||
const onSelect = vi.fn()
|
||||
const { rerender } = renderPopupItem(
|
||||
renderPopupItem(
|
||||
<PopupItem
|
||||
{...previewCardProps()}
|
||||
model={makeModel({ models: [makeModelItem({ status: ModelStatusEnum.noConfigure })] })}
|
||||
@ -335,35 +344,6 @@ describe('PopupItem', () => {
|
||||
|
||||
expect(mockUpdateModelProviders).toHaveBeenCalled()
|
||||
expect(mockUpdateModelList).toHaveBeenCalledWith(ModelTypeEnum.textGeneration)
|
||||
|
||||
rerender(
|
||||
createPopupItemNode(
|
||||
<PopupItem
|
||||
{...previewCardProps()}
|
||||
model={makeModel({
|
||||
models: [
|
||||
makeModelItem({
|
||||
status: ModelStatusEnum.noConfigure,
|
||||
model_type: undefined as unknown as ModelTypeEnum,
|
||||
}),
|
||||
],
|
||||
})}
|
||||
onHide={vi.fn()}
|
||||
/>,
|
||||
),
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('COMMON.OPERATION.ADD'))
|
||||
await waitFor(() => {
|
||||
expect(mockSetShowModelModal).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
const call2 = mockSetShowModelModal.mock.calls.at(-1)?.[0] as
|
||||
| { onSaveCallback?: () => void }
|
||||
| undefined
|
||||
call2?.onSaveCallback?.()
|
||||
|
||||
expect(mockUpdateModelProviders).toHaveBeenCalled()
|
||||
expect(mockUpdateModelList).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should show selected state when defaultModel matches', () => {
|
||||
@ -387,8 +367,8 @@ describe('PopupItem', () => {
|
||||
<PopupItem
|
||||
{...previewCardProps()}
|
||||
model={makeModel({
|
||||
label: { en_US: 'OpenAI only' } as Model['label'],
|
||||
models: [makeModelItem({ label: { en_US: 'GPT-4 only' } as ModelItem['label'] })],
|
||||
label: { en_US: 'OpenAI only' },
|
||||
models: [makeModelItem({ label: { en_US: 'GPT-4 only' } })],
|
||||
})}
|
||||
onHide={vi.fn()}
|
||||
/>,
|
||||
|
||||
@ -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 { ReactElement } from 'react'
|
||||
import type { Model, ModelItem } from '../../declarations'
|
||||
import type { PopupProps } from '../popup'
|
||||
import { Popover, PopoverContent, PopoverTitle, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
@ -57,7 +60,7 @@ vi.mock('../../hooks', async () => {
|
||||
})
|
||||
|
||||
vi.mock('../popup-item', () => ({
|
||||
default: ({ model }: { model: Model }) => (
|
||||
default: ({ model }: { model: ProviderWithModelsResponse }) => (
|
||||
<div>
|
||||
<span>{model.provider}</span>
|
||||
{model.models.map((modelItem) => (
|
||||
@ -199,7 +202,9 @@ vi.mock('../../utils', async () => {
|
||||
}
|
||||
})
|
||||
|
||||
const makeModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
const makeModelItem = (
|
||||
overrides: Partial<ProviderModelWithStatusEntity> = {},
|
||||
): ProviderModelWithStatusEntity => ({
|
||||
model: 'gpt-4',
|
||||
label: { en_US: 'GPT-4', zh_Hans: 'GPT-4' },
|
||||
model_type: ModelTypeEnum.textGeneration,
|
||||
@ -210,7 +215,10 @@ const makeModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const makeModel = (overrides: Partial<Model> = {}): Model => ({
|
||||
const makeModel = (
|
||||
overrides: Partial<ProviderWithModelsResponse> = {},
|
||||
): ProviderWithModelsResponse => ({
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'openai',
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' },
|
||||
@ -523,7 +531,10 @@ describe('Popup', () => {
|
||||
models: [
|
||||
makeModelItem({
|
||||
model: 'openrouter-model',
|
||||
label: { en_US: 'OpenRouter Model', zh_Hans: 'OpenRouter Model' },
|
||||
label: {
|
||||
en_US: 'OpenRouter Model',
|
||||
zh_Hans: 'OpenRouter Model',
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@ -533,7 +544,10 @@ describe('Popup', () => {
|
||||
models: [
|
||||
makeModelItem({
|
||||
model: 'compatible-model',
|
||||
label: { en_US: 'Compatible Model', zh_Hans: 'Compatible Model' },
|
||||
label: {
|
||||
en_US: 'Compatible Model',
|
||||
zh_Hans: 'Compatible Model',
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
@ -2,6 +2,7 @@ import type { DefaultModelResponse } from '../../declarations'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { vi } from 'vite-plus/test'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { renderWithNuqs as render } from '@/test/nuqs-testing'
|
||||
import { ModelTypeEnum } from '../../declarations'
|
||||
import SystemModel from '../index'
|
||||
@ -33,7 +34,7 @@ const mockToastSuccess = vi.hoisted(() => vi.fn())
|
||||
const mockUpdateModelList = vi.hoisted(() => vi.fn())
|
||||
const mockInvalidateDefaultModel = vi.hoisted(() => vi.fn())
|
||||
const mockUpdateDefaultModel = vi.hoisted(() => vi.fn(() => Promise.resolve({ result: 'success' })))
|
||||
const mockUseModelList = vi.hoisted(() => vi.fn())
|
||||
const mockModelListQuery = vi.hoisted(() => vi.fn())
|
||||
const mockModelSelectorProps = vi.hoisted(
|
||||
() =>
|
||||
[] as Array<{
|
||||
@ -52,12 +53,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
textGenerationModelList: [],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@langgenius/dify-ui/toast')>()
|
||||
return {
|
||||
@ -70,7 +65,6 @@ vi.mock('@langgenius/dify-ui/toast', async (importOriginal) => {
|
||||
})
|
||||
|
||||
vi.mock('../../hooks', () => ({
|
||||
useModelList: mockUseModelList,
|
||||
useSystemDefaultModelAndModelList: (defaultModel: DefaultModelResponse | undefined) => [
|
||||
defaultModel || {
|
||||
model: '',
|
||||
@ -123,13 +117,13 @@ const defaultProps = {
|
||||
speech2textDefaultModel: undefined,
|
||||
ttsDefaultModel: undefined,
|
||||
notConfigured: false,
|
||||
isLoading: false,
|
||||
isPending: false,
|
||||
}
|
||||
|
||||
describe('SystemModel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUseModelList.mockReturnValue({ data: [], isLoading: false })
|
||||
mockModelListQuery.mockReturnValue({ data: [], isPending: false })
|
||||
mockModelSelectorProps.length = 0
|
||||
mockWorkspacePermissionKeys = ['plugin.model_config']
|
||||
})
|
||||
@ -152,7 +146,14 @@ describe('SystemModel', () => {
|
||||
render(<SystemModel {...defaultProps} />, { searchParams: '?dialog=system-models' })
|
||||
|
||||
expect(await screen.findByRole('button', { name: /save/i })).toBeInTheDocument()
|
||||
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.textEmbedding, { enabled: true })
|
||||
expect(mockModelListQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({
|
||||
input: { params: { model_type: ModelTypeEnum.textEmbedding } },
|
||||
}),
|
||||
enabled: true,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('clears only the dialog URL state when closed', async () => {
|
||||
@ -172,24 +173,80 @@ describe('SystemModel', () => {
|
||||
const user = userEvent.setup()
|
||||
render(<SystemModel {...defaultProps} />)
|
||||
|
||||
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.textEmbedding, { enabled: false })
|
||||
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.rerank, { enabled: false })
|
||||
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.speech2text, { enabled: false })
|
||||
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.tts, { enabled: false })
|
||||
expect(mockModelListQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({
|
||||
input: { params: { model_type: ModelTypeEnum.textEmbedding } },
|
||||
}),
|
||||
enabled: false,
|
||||
}),
|
||||
)
|
||||
expect(mockModelListQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({
|
||||
input: { params: { model_type: ModelTypeEnum.rerank } },
|
||||
}),
|
||||
enabled: false,
|
||||
}),
|
||||
)
|
||||
expect(mockModelListQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({
|
||||
input: { params: { model_type: ModelTypeEnum.speech2text } },
|
||||
}),
|
||||
enabled: false,
|
||||
}),
|
||||
)
|
||||
expect(mockModelListQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({
|
||||
input: { params: { model_type: ModelTypeEnum.tts } },
|
||||
}),
|
||||
enabled: false,
|
||||
}),
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /system model settings/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.textEmbedding, { enabled: true })
|
||||
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.rerank, { enabled: true })
|
||||
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.speech2text, { enabled: true })
|
||||
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.tts, { enabled: true })
|
||||
expect(mockModelListQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({
|
||||
input: { params: { model_type: ModelTypeEnum.textEmbedding } },
|
||||
}),
|
||||
enabled: true,
|
||||
}),
|
||||
)
|
||||
expect(mockModelListQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({
|
||||
input: { params: { model_type: ModelTypeEnum.rerank } },
|
||||
}),
|
||||
enabled: true,
|
||||
}),
|
||||
)
|
||||
expect(mockModelListQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({
|
||||
input: { params: { model_type: ModelTypeEnum.speech2text } },
|
||||
}),
|
||||
enabled: true,
|
||||
}),
|
||||
)
|
||||
expect(mockModelListQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({
|
||||
input: { params: { model_type: ModelTypeEnum.tts } },
|
||||
}),
|
||||
enabled: true,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows loading instead of empty model selectors while model lists load', async () => {
|
||||
const user = userEvent.setup()
|
||||
mockUseModelList.mockReturnValue({ data: [], isLoading: true })
|
||||
mockModelListQuery.mockReturnValue({ data: [], isPending: true })
|
||||
render(<SystemModel {...defaultProps} />)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /system model settings/i }))
|
||||
@ -312,3 +369,8 @@ describe('SystemModel', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return { ...actual, useQuery: mockModelListQuery }
|
||||
})
|
||||
|
||||
@ -5,19 +5,19 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogClose, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { parseAsStringLiteral, useQueryState } from 'nuqs'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { updateDefaultModel } from '@/service/common'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { ModelTypeEnum } from '../declarations'
|
||||
import {
|
||||
useInvalidateDefaultModel,
|
||||
useModelList,
|
||||
useSystemDefaultModelAndModelList,
|
||||
useUpdateModelList,
|
||||
} from '../hooks'
|
||||
@ -66,7 +66,12 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
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 canManageSystemDefaultModel = hasPermission(workspacePermissionKeys, 'plugin.model_config')
|
||||
const updateModelList = useUpdateModelList()
|
||||
const invalidateDefaultModel = useInvalidateDefaultModel()
|
||||
@ -77,21 +82,34 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
setManuallyOpen(nextOpen)
|
||||
if (!nextOpen && activeDialog === 'system-models') void setActiveDialog(null)
|
||||
}
|
||||
const { data: embeddingModelList, isLoading: isEmbeddingModelListLoading } = useModelList(
|
||||
ModelTypeEnum.textEmbedding,
|
||||
{ enabled: open },
|
||||
const { data: embeddingModelList = [], isPending: isEmbeddingModelListLoading } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.textEmbedding } },
|
||||
select: (response) => response.data,
|
||||
enabled: open,
|
||||
}),
|
||||
)
|
||||
const { data: rerankModelList, isLoading: isRerankModelListLoading } = useModelList(
|
||||
ModelTypeEnum.rerank,
|
||||
{ enabled: open },
|
||||
const { data: rerankModelList = [], isPending: isRerankModelListLoading } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.rerank } },
|
||||
select: (response) => response.data,
|
||||
enabled: open,
|
||||
}),
|
||||
)
|
||||
const { data: speech2textModelList, isLoading: isSpeech2textModelListLoading } = useModelList(
|
||||
ModelTypeEnum.speech2text,
|
||||
{ enabled: open },
|
||||
const { data: speech2textModelList = [], isPending: isSpeech2textModelListLoading } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.speech2text } },
|
||||
select: (response) => response.data,
|
||||
enabled: open,
|
||||
}),
|
||||
)
|
||||
const { data: ttsModelList = [], isPending: isTTSModelListLoading } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.tts } },
|
||||
select: (response) => response.data,
|
||||
enabled: open,
|
||||
}),
|
||||
)
|
||||
const { data: ttsModelList, isLoading: isTTSModelListLoading } = useModelList(ModelTypeEnum.tts, {
|
||||
enabled: open,
|
||||
})
|
||||
const [changedModelTypes, setChangedModelTypes] = useState<ModelTypeEnum[]>([])
|
||||
const [currentTextGenerationDefaultModel, changeCurrentTextGenerationDefaultModel] =
|
||||
useSystemDefaultModelAndModelList(textGenerationDefaultModel, textGenerationModelList)
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { OperationKey } from '@orpc/tanstack-query'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { PluginCategoryEnum } from '../../../types'
|
||||
@ -36,16 +38,6 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/declaration
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useModelList: (type: string) => {
|
||||
const map: Record<string, { mutate: ReturnType<typeof vi.fn> }> = {
|
||||
llm: { mutate: mockRefetchLLMModelList },
|
||||
'text-embedding': { mutate: mockRefetchEmbeddingModelList },
|
||||
rerank: { mutate: mockRefetchRerankModelList },
|
||||
speech2text: { mutate: mockRefetchSpeech2textModelList },
|
||||
tts: { mutate: mockRefetchTTSModelList },
|
||||
}
|
||||
return map[type] ?? { mutate: vi.fn() }
|
||||
},
|
||||
useInvalidateDefaultModel: () => mockInvalidateDefaultModel,
|
||||
}))
|
||||
|
||||
@ -194,3 +186,29 @@ describe('useRefreshPluginList', () => {
|
||||
expect(mockInvalidateStrategyProviders).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) => {
|
||||
if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options)
|
||||
|
||||
const type = options.queryKey[1].input?.params?.model_type
|
||||
if (!type) throw new Error('Missing model type in query')
|
||||
const map: Record<string, { refetch: ReturnType<typeof vi.fn> }> = {
|
||||
llm: { refetch: mockRefetchLLMModelList },
|
||||
'text-embedding': { refetch: mockRefetchEmbeddingModelList },
|
||||
rerank: { refetch: mockRefetchRerankModelList },
|
||||
speech2text: { refetch: mockRefetchSpeech2textModelList },
|
||||
tts: { refetch: mockRefetchTTSModelList },
|
||||
}
|
||||
return map[type] ?? { refetch: vi.fn() }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../../types'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import {
|
||||
useInvalidateDefaultModel,
|
||||
useModelList,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { useInvalidateDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { useInvalidDataSourceListAuth } from '@/service/use-datasource'
|
||||
import { useInvalidDataSourceList } from '@/service/use-pipeline'
|
||||
import {
|
||||
@ -35,11 +34,31 @@ const SYSTEM_MODEL_TYPES = [
|
||||
const useRefreshPluginList = () => {
|
||||
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
|
||||
const invalidateCheckInstalled = useInvalidateCheckInstalled()
|
||||
const { mutate: refetchLLMModelList } = useModelList(ModelTypeEnum.textGeneration)
|
||||
const { mutate: refetchEmbeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)
|
||||
const { mutate: refetchRerankModelList } = useModelList(ModelTypeEnum.rerank)
|
||||
const { mutate: refetchSpeech2textModelList } = useModelList(ModelTypeEnum.speech2text)
|
||||
const { mutate: refetchTTSModelList } = useModelList(ModelTypeEnum.tts)
|
||||
const { refetch: refetchLLMModelList } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.textGeneration } },
|
||||
}),
|
||||
)
|
||||
const { refetch: refetchEmbeddingModelList } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.textEmbedding } },
|
||||
}),
|
||||
)
|
||||
const { refetch: refetchRerankModelList } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.rerank } },
|
||||
}),
|
||||
)
|
||||
const { refetch: refetchSpeech2textModelList } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.speech2text } },
|
||||
}),
|
||||
)
|
||||
const { refetch: refetchTTSModelList } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.tts } },
|
||||
}),
|
||||
)
|
||||
const invalidateDefaultModel = useInvalidateDefaultModel()
|
||||
const { refreshModelProviders } = useProviderContext()
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import type {
|
||||
Model,
|
||||
ModelItem,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
GetWorkspacesCurrentModelsModelTypesByModelTypeData,
|
||||
ProviderModelWithStatusEntity,
|
||||
ProviderWithModelsResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { OperationKey } from '@orpc/tanstack-query'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
@ -42,33 +44,12 @@ vi.mock('@/context/provider-context', () => ({
|
||||
}))
|
||||
|
||||
// Mock model list hook
|
||||
const mockTextGenerationList: Model[] = []
|
||||
const mockTextEmbeddingList: Model[] = []
|
||||
const mockRerankList: Model[] = []
|
||||
const mockModerationList: Model[] = []
|
||||
const mockSttList: Model[] = []
|
||||
const mockTtsList: Model[] = []
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useModelList: (type: ModelTypeEnum) => {
|
||||
switch (type) {
|
||||
case ModelTypeEnum.textGeneration:
|
||||
return { data: mockTextGenerationList }
|
||||
case ModelTypeEnum.textEmbedding:
|
||||
return { data: mockTextEmbeddingList }
|
||||
case ModelTypeEnum.rerank:
|
||||
return { data: mockRerankList }
|
||||
case ModelTypeEnum.moderation:
|
||||
return { data: mockModerationList }
|
||||
case ModelTypeEnum.speech2text:
|
||||
return { data: mockSttList }
|
||||
case ModelTypeEnum.tts:
|
||||
return { data: mockTtsList }
|
||||
default:
|
||||
return { data: [] }
|
||||
}
|
||||
},
|
||||
}))
|
||||
const mockTextGenerationList: ProviderWithModelsResponse[] = []
|
||||
const mockTextEmbeddingList: ProviderWithModelsResponse[] = []
|
||||
const mockRerankList: ProviderWithModelsResponse[] = []
|
||||
const mockModerationList: ProviderWithModelsResponse[] = []
|
||||
const mockSttList: ProviderWithModelsResponse[] = []
|
||||
const mockTtsList: ProviderWithModelsResponse[] = []
|
||||
|
||||
// Mock fetchAndMergeValidCompletionParams
|
||||
const mockFetchAndMergeValidCompletionParams = vi.fn()
|
||||
@ -88,7 +69,7 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-selec
|
||||
onValueChange,
|
||||
}: {
|
||||
value?: { provider?: string; model?: string }
|
||||
models?: Model[]
|
||||
models?: ProviderWithModelsResponse[]
|
||||
scopeFeatures?: string[]
|
||||
surface?: 'default' | 'workflow'
|
||||
disabled?: boolean
|
||||
@ -163,7 +144,7 @@ vi.mock('../tts-params-panel', () => ({
|
||||
voice,
|
||||
onChange,
|
||||
}: {
|
||||
currentModel?: ModelItem
|
||||
currentModel?: ProviderModelWithStatusEntity
|
||||
language?: string
|
||||
voice?: string
|
||||
onChange?: (language: string, voice: string) => void
|
||||
@ -183,9 +164,11 @@ vi.mock('../tts-params-panel', () => ({
|
||||
// ==================== Test Utilities ====================
|
||||
|
||||
/**
|
||||
* Factory function to create a ModelItem with defaults
|
||||
* Factory function to create a ProviderModelWithStatusEntity with defaults
|
||||
*/
|
||||
const createModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
const createModelItem = (
|
||||
overrides: Partial<ProviderModelWithStatusEntity> = {},
|
||||
): ProviderModelWithStatusEntity => ({
|
||||
model: 'test-model',
|
||||
label: { en_US: 'Test Model', zh_Hans: 'Test Model' },
|
||||
model_type: ModelTypeEnum.textGeneration,
|
||||
@ -198,9 +181,12 @@ const createModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
})
|
||||
|
||||
/**
|
||||
* Factory function to create a Model (provider with models) with defaults
|
||||
* Factory function to create a ProviderWithModelsResponse (provider with models) with defaults
|
||||
*/
|
||||
const createModel = (overrides: Partial<Model> = {}): Model => ({
|
||||
const createModel = (
|
||||
overrides: Partial<ProviderWithModelsResponse> = {},
|
||||
): ProviderWithModelsResponse => ({
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'openai',
|
||||
icon_small: { en_US: 'icon-small.png', zh_Hans: 'icon-small.png' },
|
||||
label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' },
|
||||
@ -226,12 +212,12 @@ const createDefaultProps = (
|
||||
*/
|
||||
const setupModelLists = (
|
||||
config: {
|
||||
textGeneration?: Model[]
|
||||
textEmbedding?: Model[]
|
||||
rerank?: Model[]
|
||||
moderation?: Model[]
|
||||
stt?: Model[]
|
||||
tts?: Model[]
|
||||
textGeneration?: ProviderWithModelsResponse[]
|
||||
textEmbedding?: ProviderWithModelsResponse[]
|
||||
rerank?: ProviderWithModelsResponse[]
|
||||
moderation?: ProviderWithModelsResponse[]
|
||||
stt?: ProviderWithModelsResponse[]
|
||||
tts?: ProviderWithModelsResponse[]
|
||||
} = {},
|
||||
) => {
|
||||
mockTextGenerationList.length = 0
|
||||
@ -1374,3 +1360,37 @@ describe('ModelParameterModal', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) => {
|
||||
if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options)
|
||||
|
||||
const type = options.queryKey[1].input?.params?.model_type
|
||||
if (!type) throw new Error('Missing model type in query')
|
||||
switch (type) {
|
||||
case ModelTypeEnum.textGeneration:
|
||||
return { data: mockTextGenerationList }
|
||||
case ModelTypeEnum.textEmbedding:
|
||||
return { data: mockTextEmbeddingList }
|
||||
case ModelTypeEnum.rerank:
|
||||
return { data: mockRerankList }
|
||||
case ModelTypeEnum.moderation:
|
||||
return { data: mockModerationList }
|
||||
case ModelTypeEnum.speech2text:
|
||||
return { data: mockSttList }
|
||||
case ModelTypeEnum.tts:
|
||||
return { data: mockTtsList }
|
||||
default:
|
||||
return { data: [] }
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -7,15 +7,16 @@ import type { ModelSelectorValue } from '@/app/components/header/account-setting
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Popover, PopoverContent } from '@langgenius/dify-ui/popover'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
ModelStatusEnum,
|
||||
ModelTypeEnum,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelSettingsTrigger } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-settings-trigger'
|
||||
import { SplitModelSelector } from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
|
||||
import LLMParamsPanel from './llm-params-panel'
|
||||
import TTSParamsPanel from './tts-params-panel'
|
||||
@ -69,12 +70,42 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
.map((item) => item as ModelFeatureEnum)
|
||||
}, [scopeArray])
|
||||
|
||||
const { data: textGenerationList } = useModelList(ModelTypeEnum.textGeneration)
|
||||
const { data: textEmbeddingList } = useModelList(ModelTypeEnum.textEmbedding)
|
||||
const { data: rerankList } = useModelList(ModelTypeEnum.rerank)
|
||||
const { data: moderationList } = useModelList(ModelTypeEnum.moderation)
|
||||
const { data: sttList } = useModelList(ModelTypeEnum.speech2text)
|
||||
const { data: ttsList } = useModelList(ModelTypeEnum.tts)
|
||||
const { data: textGenerationList = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.textGeneration } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const { data: textEmbeddingList = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.textEmbedding } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const { data: rerankList = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.rerank } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const { data: moderationList = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.moderation } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const { data: sttList = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.speech2text } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const { data: ttsList = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.tts } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
|
||||
const scopedModelList = useMemo(() => {
|
||||
if (scopeArray.includes('all')) {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import type {
|
||||
ProviderModelWithStatusEntity,
|
||||
ProviderWithModelsResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type {
|
||||
DefaultModel,
|
||||
Model,
|
||||
ModelItem,
|
||||
ModelProvider,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { CredentialPanelState } from '@/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state'
|
||||
@ -14,7 +16,9 @@ import {
|
||||
PreferredProviderTypeEnum,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
export function createModelItem(overrides: Partial<ModelItem> = {}): ModelItem {
|
||||
export function createModelItem(
|
||||
overrides: Partial<ProviderModelWithStatusEntity> = {},
|
||||
): ProviderModelWithStatusEntity {
|
||||
return {
|
||||
model: 'text-embedding-3-large',
|
||||
label: { en_US: 'Text Embedding 3 Large', zh_Hans: 'Text Embedding 3 Large' },
|
||||
@ -27,8 +31,11 @@ export function createModelItem(overrides: Partial<ModelItem> = {}): ModelItem {
|
||||
}
|
||||
}
|
||||
|
||||
export function createModel(overrides: Partial<Model> = {}): Model {
|
||||
export function createModel(
|
||||
overrides: Partial<ProviderWithModelsResponse> = {},
|
||||
): ProviderWithModelsResponse {
|
||||
return {
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'openai',
|
||||
icon_small: { en_US: 'icon', zh_Hans: 'icon' },
|
||||
icon_small_dark: { en_US: 'icon-dark', zh_Hans: 'icon-dark' },
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import type { AgentSoulDifyToolConfig } from '@dify/contracts/api/console/apps/types.gen'
|
||||
import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { OperationKey } from '@orpc/tanstack-query'
|
||||
import type { CommonNodeType, Node } from '../../types'
|
||||
import type { ChecklistItem } from '../use-checklist'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
@ -79,10 +81,6 @@ vi.mock('@/service/use-strategy', () => ({
|
||||
useStrategyProviders: () => ({ data: [] }),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useModelList: () => ({ data: [] }),
|
||||
}))
|
||||
|
||||
type CheckValidFn = (data: CommonNodeType, t: unknown, extra?: unknown) => { errorMessage: string }
|
||||
const mockNodesMap: Record<
|
||||
string,
|
||||
@ -855,3 +853,19 @@ describe('useWorkflowRunValidation', () => {
|
||||
expect(result.current.validateBeforeRun()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) => {
|
||||
if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options)
|
||||
return { data: [] }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -19,14 +19,13 @@ import type { AgentToolPublishIssue } from '@/features/agent-v2/agent-detail/con
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import type { I18nKeysWithPrefix } from '@/types/i18n'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQueries, useQueryClient } from '@tanstack/react-query'
|
||||
import { useQueries, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import isDeepEqual from 'fast-deep-equal'
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEdges, useStoreApi } from 'reactflow'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
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 { normalizeModelProviderModelsResponse } from '@/app/components/header/account-setting/model-provider-page/utils'
|
||||
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
|
||||
import { MAX_TREE_DEPTH } from '@/config'
|
||||
@ -277,8 +276,18 @@ export const useChecklist = (nodes: Node[], edges: Edge[], options?: { flowType?
|
||||
inlineAgentIssueTools,
|
||||
inlineAgentToolProviderCatalog,
|
||||
)
|
||||
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 knowledgeBaseEmbeddingProviders = useMemo(() => {
|
||||
const providers = new Set<string>()
|
||||
|
||||
@ -614,8 +623,18 @@ export const useChecklistBeforePublish = () => {
|
||||
const updateTimeRef = useRef(0)
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { getNodesAvailableVarList } = useGetNodesAvailableVarList()
|
||||
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 { data: buildInTools } = useAllBuiltInTools()
|
||||
const { data: customTools } = useAllCustomTools()
|
||||
const { data: workflowTools } = useAllWorkflowTools()
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { OperationKey } from '@orpc/tanstack-query'
|
||||
/* oxlint-disable typescript/no-explicit-any */
|
||||
import type { AgentNodeType } from '../types'
|
||||
import type { StrategyParamItem } from '@/app/components/plugins/types'
|
||||
@ -40,17 +42,6 @@ let mockMarketplaceIcon: string | Record<string, string> | undefined
|
||||
|
||||
const mockResetEditor = vi.fn()
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useModelList: (modelType: ModelTypeEnum) => {
|
||||
if (modelType === ModelTypeEnum.textGeneration) return { data: mockTextGenerationModels }
|
||||
if (modelType === ModelTypeEnum.moderation) return { data: mockModerationModels }
|
||||
if (modelType === ModelTypeEnum.rerank) return { data: mockRerankModels }
|
||||
if (modelType === ModelTypeEnum.speech2text) return { data: mockSpeech2TextModels }
|
||||
if (modelType === ModelTypeEnum.textEmbedding) return { data: mockTextEmbeddingModels }
|
||||
return { data: mockTtsModels }
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/model-selector', () => ({
|
||||
ModelSelector: ({ value, models }: any) => (
|
||||
<div>
|
||||
@ -383,3 +374,27 @@ describe('agent path', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) => {
|
||||
if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options)
|
||||
|
||||
const modelType = options.queryKey[1].input?.params?.model_type
|
||||
if (!modelType) throw new Error('Missing model type in query')
|
||||
if (modelType === ModelTypeEnum.textGeneration) return { data: mockTextGenerationModels }
|
||||
if (modelType === ModelTypeEnum.moderation) return { data: mockModerationModels }
|
||||
if (modelType === ModelTypeEnum.rerank) return { data: mockRerankModels }
|
||||
if (modelType === ModelTypeEnum.speech2text) return { data: mockSpeech2TextModels }
|
||||
if (modelType === ModelTypeEnum.textEmbedding) return { data: mockTextEmbeddingModels }
|
||||
return { data: mockTtsModels }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { GetWorkspacesCurrentModelsModelTypesByModelTypeData } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { OperationKey } from '@orpc/tanstack-query'
|
||||
import type { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { ModelBar } from '../model-bar'
|
||||
@ -7,13 +9,7 @@ type ModelProviderItem = {
|
||||
models: Array<{ model: string }>
|
||||
}
|
||||
|
||||
const mockModelLists = new Map<ModelTypeEnum, ModelProviderItem[]>()
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useModelList: (modelType: ModelTypeEnum) => ({
|
||||
data: mockModelLists.get(modelType) || [],
|
||||
}),
|
||||
}))
|
||||
const mockModelLists = new Map<string, ModelProviderItem[]>()
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/model-selector', () => ({
|
||||
ModelSelector: ({
|
||||
@ -72,3 +68,24 @@ describe('agent/model-bar', () => {
|
||||
expect(screen.getByText('workflow.nodes.agent.modelNotInstallTooltip')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) => {
|
||||
if (!options.queryKey[0].includes('modelTypes')) return actual.useQuery(options)
|
||||
|
||||
const modelType = options.queryKey[1].input?.params?.model_type
|
||||
if (!modelType) throw new Error('Missing model type in query')
|
||||
return {
|
||||
data: mockModelLists.get(modelType) || [],
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import type { FC } from 'react'
|
||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 ModelBarProps =
|
||||
| {
|
||||
@ -18,12 +19,42 @@ type ModelBarProps =
|
||||
}
|
||||
|
||||
const useAllModel = () => {
|
||||
const { data: textGeneration } = useModelList(ModelTypeEnum.textGeneration)
|
||||
const { data: moderation } = useModelList(ModelTypeEnum.moderation)
|
||||
const { data: rerank } = useModelList(ModelTypeEnum.rerank)
|
||||
const { data: speech2text } = useModelList(ModelTypeEnum.speech2text)
|
||||
const { data: textEmbedding } = useModelList(ModelTypeEnum.textEmbedding)
|
||||
const { data: tts } = useModelList(ModelTypeEnum.tts)
|
||||
const { data: textGeneration = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.textGeneration } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const { data: moderation = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.moderation } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const { data: rerank = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.rerank } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const { data: speech2text = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.speech2text } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const { data: textEmbedding = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.textEmbedding } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const { data: tts = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.tts } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const models = useMemo(() => {
|
||||
return textGeneration
|
||||
.concat(moderation)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { KnowledgeBaseNodeType } from '../types'
|
||||
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 { KnowledgeBaseNodeType } from '../types'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
ModelStatusEnum,
|
||||
@ -14,8 +14,9 @@ import { ChunkStructureEnum, IndexMethodEnum, RetrievalSearchMethodEnum } from '
|
||||
|
||||
const t = withSelectorKey((key: string, _options?: Record<string, unknown>) => key)
|
||||
|
||||
const makeEmbeddingModelList = (status: ModelStatusEnum): Model[] => [
|
||||
const makeEmbeddingModelList = (status: ModelStatusEnum): ProviderWithModelsResponse[] => [
|
||||
{
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'openai',
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' },
|
||||
@ -30,11 +31,13 @@ const makeEmbeddingModelList = (status: ModelStatusEnum): Model[] => [
|
||||
load_balancing_enabled: false,
|
||||
},
|
||||
],
|
||||
status,
|
||||
status: 'active',
|
||||
},
|
||||
]
|
||||
|
||||
const makeEmbeddingProviderModelList = (status: ModelStatusEnum): ModelItem[] => [
|
||||
const makeEmbeddingProviderModelList = (
|
||||
status: ModelStatusEnum,
|
||||
): ProviderModelWithStatusEntity[] => [
|
||||
{
|
||||
model: 'text-embedding-3-large',
|
||||
label: { en_US: 'Text Embedding 3 Large', zh_Hans: 'Text Embedding 3 Large' },
|
||||
|
||||
@ -11,18 +11,10 @@ import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import Node from '../node'
|
||||
import { ChunkStructureEnum, IndexMethodEnum, RetrievalSearchMethodEnum } from '../types'
|
||||
|
||||
const mockUseModelList = vi.hoisted(() => vi.fn())
|
||||
const mockModelListQuery = vi.hoisted(() => vi.fn())
|
||||
const mockUseSettingsDisplay = vi.hoisted(() => vi.fn())
|
||||
const mockUseEmbeddingModelStatus = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: () => ({ data: undefined }),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock(
|
||||
'@/app/components/header/account-setting/model-provider-page/hooks',
|
||||
async (importOriginal) => {
|
||||
@ -33,7 +25,6 @@ vi.mock(
|
||||
return {
|
||||
...actual,
|
||||
useLanguage: () => 'en_US',
|
||||
useModelList: mockUseModelList,
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -81,7 +72,7 @@ const createNodeData = (
|
||||
describe('KnowledgeBaseNode', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUseModelList.mockReturnValue({ data: [] })
|
||||
mockModelListQuery.mockReturnValue({ data: [] })
|
||||
mockUseSettingsDisplay.mockReturnValue({
|
||||
[IndexMethodEnum.QUALIFIED]: 'High Quality',
|
||||
[RetrievalSearchMethodEnum.semantic]: 'Vector Search',
|
||||
@ -193,19 +184,22 @@ describe('KnowledgeBaseNode', () => {
|
||||
})
|
||||
|
||||
it('should render a warning value for retrieval settings when reranking is incomplete', () => {
|
||||
mockUseModelList.mockImplementation((modelType: ModelTypeEnum) => {
|
||||
if (modelType === ModelTypeEnum.textEmbedding) {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
provider: 'openai',
|
||||
models: [createModelItem()],
|
||||
},
|
||||
],
|
||||
mockModelListQuery.mockImplementation(
|
||||
({ input }: { input: { params: { model_type: string } } }) => {
|
||||
const modelType = input.params.model_type
|
||||
if (modelType === ModelTypeEnum.textEmbedding) {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
provider: 'openai',
|
||||
models: [createModelItem()],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
return { data: [] }
|
||||
})
|
||||
return { data: [] }
|
||||
},
|
||||
)
|
||||
|
||||
render(
|
||||
<Node
|
||||
@ -239,3 +233,14 @@ describe('KnowledgeBaseNode', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: { queryKey?: readonly [readonly string[], unknown] }) =>
|
||||
options.queryKey?.[0].includes('modelTypes')
|
||||
? mockModelListQuery(options)
|
||||
: { data: undefined },
|
||||
}
|
||||
})
|
||||
|
||||
@ -5,7 +5,7 @@ import { ModelTypeEnum } from '@/app/components/header/account-setting/model-pro
|
||||
import Panel from '../panel'
|
||||
import { ChunkStructureEnum, IndexMethodEnum, RetrievalSearchMethodEnum } from '../types'
|
||||
|
||||
const mockUseModelList = vi.hoisted(() => vi.fn())
|
||||
const mockModelListQuery = vi.hoisted(() => vi.fn())
|
||||
const mockUseQuery = vi.hoisted(() => vi.fn())
|
||||
const mockUseEmbeddingModelStatus = vi.hoisted(() => vi.fn())
|
||||
const mockChunkStructure = vi.hoisted(() => vi.fn(() => <div data-testid="chunk-structure" />))
|
||||
@ -15,44 +15,36 @@ const mockSummaryIndexSetting = vi.hoisted(() =>
|
||||
)
|
||||
const mockQueryOptions = vi.hoisted(() => vi.fn((options: unknown) => options))
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('@tanstack/react-query')>()),
|
||||
useQuery: mockUseQuery,
|
||||
useSuspenseQuery: ({ select }: { select: (value: unknown) => unknown }) => ({
|
||||
data: select({ deployment_edition: 'COMMUNITY' }),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/console', () => ({
|
||||
consoleQuery: {
|
||||
systemFeatures: {
|
||||
get: {
|
||||
queryKey: () => ['console', 'systemFeatures', 'get'],
|
||||
queryOptions: (options?: Record<string, unknown>) => ({
|
||||
queryKey: ['console', 'systemFeatures', 'get'],
|
||||
...options,
|
||||
}),
|
||||
vi.mock('@/service/console', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/console')>()
|
||||
return {
|
||||
consoleQuery: {
|
||||
systemFeatures: {
|
||||
get: {
|
||||
queryKey: () => ['console', 'systemFeatures', 'get'],
|
||||
queryOptions: (options?: Record<string, unknown>) => ({
|
||||
queryKey: ['console', 'systemFeatures', 'get'],
|
||||
...options,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
workspaces: {
|
||||
current: {
|
||||
modelProviders: {
|
||||
byProvider: {
|
||||
models: {
|
||||
get: {
|
||||
queryOptions: mockQueryOptions,
|
||||
workspaces: {
|
||||
current: {
|
||||
models: actual.consoleQuery.workspaces.current.models,
|
||||
modelProviders: {
|
||||
byProvider: {
|
||||
models: {
|
||||
get: {
|
||||
queryOptions: mockQueryOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useModelList: mockUseModelList,
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../hooks/use-workflow', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../../../hooks/use-workflow')>()
|
||||
@ -169,19 +161,23 @@ describe('KnowledgeBasePanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUseQuery.mockReturnValue({ data: undefined })
|
||||
mockUseModelList.mockImplementation((modelType: ModelTypeEnum) => {
|
||||
if (modelType === ModelTypeEnum.textEmbedding) {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
provider: 'openai',
|
||||
models: [{ model: 'text-embedding-3-large' }],
|
||||
},
|
||||
],
|
||||
mockModelListQuery.mockImplementation(
|
||||
({ input }: { input: { params: { model_type: string } } }) => {
|
||||
const type = input.params.model_type
|
||||
const modelType = type
|
||||
if (modelType === ModelTypeEnum.textEmbedding) {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
provider: 'openai',
|
||||
models: [{ model: 'text-embedding-3-large' }],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
return { data: [] }
|
||||
})
|
||||
return { data: [] }
|
||||
},
|
||||
)
|
||||
mockUseEmbeddingModelStatus.mockReturnValue({ status: 'active' })
|
||||
})
|
||||
|
||||
@ -253,3 +249,17 @@ describe('KnowledgeBasePanel', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useSuspenseQuery: ({ select }: { select: (value: unknown) => unknown }) => ({
|
||||
data: select({ deployment_edition: 'COMMUNITY' }),
|
||||
}),
|
||||
useQuery: (options: { queryKey?: readonly [readonly string[], unknown] }) =>
|
||||
options.queryKey?.[0].includes('modelTypes')
|
||||
? mockModelListQuery(options)
|
||||
: mockUseQuery(options),
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { KnowledgeBaseNodeType } from '../types'
|
||||
import type {
|
||||
Model,
|
||||
ModelItem,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { ModelItem } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
ModelStatusEnum,
|
||||
@ -18,9 +16,10 @@ import {
|
||||
KnowledgeBaseValidationIssueCode,
|
||||
} from '../utils'
|
||||
|
||||
const makeEmbeddingModelList = (status: ModelStatusEnum): Model[] => {
|
||||
const makeEmbeddingModelList = (status: ModelStatusEnum): ProviderWithModelsResponse[] => {
|
||||
return [
|
||||
{
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'openai',
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' },
|
||||
@ -35,7 +34,7 @@ const makeEmbeddingModelList = (status: ModelStatusEnum): Model[] => {
|
||||
load_balancing_enabled: false,
|
||||
},
|
||||
],
|
||||
status,
|
||||
status: 'active',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { render } from '@testing-library/react'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import EmbeddingModel from '../embedding-model'
|
||||
|
||||
const mockUseModelList = vi.hoisted(() => vi.fn())
|
||||
const mockModelListQuery = vi.hoisted(() => vi.fn())
|
||||
const mockModelSelector = vi.hoisted(() =>
|
||||
vi.fn(() => <div data-testid="model-selector">selector</div>),
|
||||
)
|
||||
@ -22,10 +22,6 @@ vi.mock('@/app/components/workflow/nodes/_base/components/layout', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useModelList: mockUseModelList,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/model-selector', () => ({
|
||||
ModelSelector: mockModelSelector,
|
||||
}))
|
||||
@ -33,7 +29,7 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-selec
|
||||
describe('EmbeddingModel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUseModelList.mockReturnValue({
|
||||
mockModelListQuery.mockReturnValue({
|
||||
data: [{ provider: 'openai', model: 'text-embedding-3-large' }],
|
||||
})
|
||||
})
|
||||
@ -50,7 +46,9 @@ describe('EmbeddingModel', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.textEmbedding)
|
||||
expect(mockModelListQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ input: { params: { model_type: ModelTypeEnum.textEmbedding } } }),
|
||||
)
|
||||
expect(mockModelSelector).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
value: {
|
||||
@ -76,3 +74,8 @@ describe('EmbeddingModel', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return { ...actual, useQuery: mockModelListQuery }
|
||||
})
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { memo, useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
@ -6,9 +7,9 @@ import {
|
||||
MultimodalRetrievalGuidanceLearnMore,
|
||||
} from '@/app/components/datasets/common/multimodal-retrieval-guidance'
|
||||
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 { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
|
||||
type EmbeddingModelProps = {
|
||||
embeddingModel?: string
|
||||
@ -28,7 +29,12 @@ const EmbeddingModel = ({
|
||||
readonly = false,
|
||||
}: EmbeddingModelProps) => {
|
||||
const { t } = useTranslation()
|
||||
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 embeddingModelConfig = useMemo(() => {
|
||||
if (!embeddingModel || !embeddingModelProvider) return undefined
|
||||
|
||||
|
||||
@ -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 { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import {
|
||||
@ -12,7 +10,7 @@ import RerankingModelSelector from '../reranking-model-selector'
|
||||
|
||||
type MockModelSelectorProps = {
|
||||
value?: DefaultModel
|
||||
models: Model[]
|
||||
models: ProviderWithModelsResponse[]
|
||||
onValueChange?: (model: DefaultModel) => void
|
||||
}
|
||||
|
||||
@ -45,6 +43,7 @@ describe('RerankingModelSelector', () => {
|
||||
mockUseModelListAndDefaultModel.mockReturnValue({
|
||||
modelList: [
|
||||
createModel({
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'cohere',
|
||||
label: { en_US: 'Cohere', zh_Hans: 'Cohere' },
|
||||
models: [
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { ModelProviderSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type {
|
||||
Model,
|
||||
ModelItem,
|
||||
ModelProvider,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
ModelProviderSummaryResponse,
|
||||
ProviderModelWithStatusEntity,
|
||||
ProviderWithModelsResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useMemo } from 'react'
|
||||
import { deriveModelStatus } from '@/app/components/header/account-setting/model-provider-page/derive-model-status'
|
||||
import { useCredentialPanelState } from '@/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state'
|
||||
@ -12,13 +12,13 @@ import { useProviderContext } from '@/context/provider-context'
|
||||
type UseEmbeddingModelStatusProps = {
|
||||
embeddingModel?: string
|
||||
embeddingModelProvider?: string
|
||||
embeddingModelList: Model[]
|
||||
embeddingModelList: ProviderWithModelsResponse[]
|
||||
}
|
||||
|
||||
type UseEmbeddingModelStatusResult = {
|
||||
providerMeta: ModelProviderSummaryResponse | ModelProvider | undefined
|
||||
modelProvider: Model | undefined
|
||||
currentModel: ModelItem | undefined
|
||||
modelProvider: ProviderWithModelsResponse | undefined
|
||||
currentModel: ProviderModelWithStatusEntity | undefined
|
||||
status: ReturnType<typeof deriveModelStatus>
|
||||
}
|
||||
|
||||
|
||||
@ -7,11 +7,9 @@ import { memo, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { DERIVED_MODEL_STATUS_BADGE_I18N } from '@/app/components/header/account-setting/model-provider-page/derive-model-status'
|
||||
import {
|
||||
useLanguage,
|
||||
useModelList,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { normalizeModelProviderModelsResponse } from '@/app/components/header/account-setting/model-provider-page/utils'
|
||||
import { renderI18nObject } from '@/i18n-config'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { useEmbeddingModelStatus } from './hooks/use-embedding-model-status'
|
||||
import { useSettingsDisplay } from './hooks/use-settings-display'
|
||||
@ -62,8 +60,18 @@ const Node: FC<NodeProps<KnowledgeBaseNodeType>> = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useLanguage()
|
||||
const settingsDisplay = useSettingsDisplay()
|
||||
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 chunkStructure = data.chunk_structure
|
||||
const indexChunkVariableSelector = data.index_chunk_variable_selector
|
||||
const indexingTechnique = data.indexing_technique
|
||||
@ -149,8 +157,7 @@ const Node: FC<NodeProps<KnowledgeBaseNodeType>> = ({ data }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
currentEmbeddingModel?.label[language] ||
|
||||
currentEmbeddingModel?.label.en_US ||
|
||||
(currentEmbeddingModel && renderI18nObject(currentEmbeddingModel.label, language)) ||
|
||||
data.embedding_model ||
|
||||
'-'
|
||||
)
|
||||
|
||||
@ -7,7 +7,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import SummaryIndexSetting from '@/app/components/datasets/settings/summary-index-setting'
|
||||
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 { normalizeModelProviderModelsResponse } from '@/app/components/header/account-setting/model-provider-page/utils'
|
||||
import {
|
||||
BoxGroup,
|
||||
@ -36,8 +35,18 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({ id, data }) => {
|
||||
})
|
||||
const isNonCloudEdition = deploymentEdition === 'COMMUNITY' || deploymentEdition === 'ENTERPRISE'
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
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 chunkStructure = data.chunk_structure
|
||||
const indexChunkVariableSelector = data.index_chunk_variable_selector
|
||||
const indexingTechnique = data.indexing_technique
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import type { ProviderWithModelsResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { IndexingType } from '@/app/components/datasets/create/step-two'
|
||||
import type {
|
||||
Model,
|
||||
ModelItem,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { ModelItem } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import type { RerankingModeEnum, WeightedScoreEnum } from '@/models/datasets'
|
||||
import type { RETRIEVE_METHOD } from '@/types/app'
|
||||
@ -59,8 +57,8 @@ export type KnowledgeBaseNodeType = CommonNodeType & {
|
||||
embedding_model_provider?: string
|
||||
keyword_number: number
|
||||
retrieval_model: RetrievalSetting
|
||||
_embeddingModelList?: Model[]
|
||||
_embeddingModelList?: ProviderWithModelsResponse[]
|
||||
_embeddingProviderModelList?: ModelItem[]
|
||||
_rerankModelList?: Model[]
|
||||
_rerankModelList?: ProviderWithModelsResponse[]
|
||||
summary_index_setting?: SummaryIndexSetting
|
||||
}
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
import type { AvailableModelListResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { LLMNodeType } from '../../types'
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { Type } from '../../types'
|
||||
import useLLMStructuredOutputConfig from '../use-llm-structured-output-config'
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
useModelList: vi.fn(),
|
||||
}))
|
||||
|
||||
const mockUseModelList = vi.mocked(useModelList)
|
||||
const mockModelListQuery = vi.hoisted(() =>
|
||||
vi.fn<() => { data: AvailableModelListResponse['data'] }>(),
|
||||
)
|
||||
|
||||
const createPayload = (overrides: Partial<LLMNodeType> = {}): LLMNodeType => ({
|
||||
type: BlockEnum.LLM,
|
||||
@ -41,19 +39,27 @@ describe('use-llm-structured-output-config', () => {
|
||||
})
|
||||
|
||||
it('detects supported models and updates structured output state', () => {
|
||||
mockUseModelList.mockReturnValue({
|
||||
mockModelListQuery.mockReturnValue({
|
||||
data: [
|
||||
{
|
||||
provider: 'openai',
|
||||
tenant_id: 'test-workspace',
|
||||
label: { en_US: 'OpenAI' },
|
||||
status: 'active',
|
||||
models: [
|
||||
{
|
||||
model: 'gpt-4o',
|
||||
label: { en_US: 'gpt-4o' },
|
||||
model_type: 'llm',
|
||||
fetch_from: 'predefined-model',
|
||||
status: 'active',
|
||||
model_properties: {},
|
||||
features: [ModelFeatureEnum.StructuredOutput],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as ReturnType<typeof useModelList>)
|
||||
})
|
||||
|
||||
const inputRef = {
|
||||
current: createPayload(),
|
||||
@ -136,21 +142,27 @@ describe('use-llm-structured-output-config', () => {
|
||||
})
|
||||
|
||||
it('returns undefined support when the model is missing from the list', () => {
|
||||
mockUseModelList.mockReturnValue({
|
||||
mockModelListQuery.mockReturnValue({
|
||||
data: [
|
||||
{
|
||||
provider: 'anthropic',
|
||||
tenant_id: 'test-workspace',
|
||||
label: { en_US: 'Anthropic' },
|
||||
status: 'active',
|
||||
models: [
|
||||
{
|
||||
model: 'claude',
|
||||
label: { en_US: 'claude' },
|
||||
model_type: 'llm',
|
||||
fetch_from: 'predefined-model',
|
||||
status: 'active',
|
||||
model_properties: {},
|
||||
features: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
mutate: vi.fn(),
|
||||
isLoading: false,
|
||||
} as unknown as ReturnType<typeof useModelList>)
|
||||
})
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLLMStructuredOutputConfig({
|
||||
@ -165,3 +177,8 @@ describe('use-llm-structured-output-config', () => {
|
||||
expect(result.current.isModelSupportStructuredOutput).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return { ...actual, useQuery: mockModelListQuery }
|
||||
})
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { LLMNodeType, StructuredOutput } from '../types'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { produce } from 'immer'
|
||||
import { useCallback, useState } from 'react'
|
||||
import {
|
||||
ModelFeatureEnum,
|
||||
ModelTypeEnum,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
|
||||
type Params = {
|
||||
id: string
|
||||
@ -23,7 +24,12 @@ const useLLMStructuredOutputConfig = ({
|
||||
setInputs,
|
||||
deleteNodeInspectorVars,
|
||||
}: Params) => {
|
||||
const { data: modelList } = useModelList(ModelTypeEnum.textGeneration)
|
||||
const { data: modelList = [] } = useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.textGeneration } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const isModelSupportStructuredOutput = modelList
|
||||
?.find((providerItem) => providerItem.provider === model?.provider)
|
||||
?.models.find((modelItem) => modelItem.model === model?.name)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { ReactElement } from 'react'
|
||||
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 { ReactElement } from 'react'
|
||||
import type { Shape } from '@/app/components/workflow/store/workflow'
|
||||
import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
@ -21,12 +21,12 @@ type MockModelParameterModalProps = {
|
||||
provider: string
|
||||
modelId: string
|
||||
completionParams: Record<string, unknown>
|
||||
modelList?: Model[]
|
||||
modelList?: ProviderWithModelsResponse[]
|
||||
setModel: (model: { provider: string; modelId: string; mode?: string }) => void
|
||||
onCompletionParamsChange: (params: Record<string, unknown>) => void
|
||||
}
|
||||
|
||||
let mockTextGenerationModelList: Model[] = []
|
||||
let mockTextGenerationModelList: ProviderWithModelsResponse[] = []
|
||||
let latestModelParameterModalProps: MockModelParameterModalProps | undefined
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
@ -89,7 +89,7 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
|
||||
const mockToastError = vi.mocked(toast.error)
|
||||
|
||||
const createModelItem = (model: string, mode: string): ModelItem => ({
|
||||
const createModelItem = (model: string, mode: string): ProviderModelWithStatusEntity => ({
|
||||
model,
|
||||
label: { en_US: model, zh_Hans: model },
|
||||
model_type: ModelTypeEnum.textGeneration,
|
||||
@ -100,7 +100,8 @@ const createModelItem = (model: string, mode: string): ModelItem => ({
|
||||
load_balancing_enabled: false,
|
||||
})
|
||||
|
||||
const createModelProvider = (): Model => ({
|
||||
const createModelProvider = (): ProviderWithModelsResponse => ({
|
||||
tenant_id: 'test-workspace',
|
||||
provider: 'openai',
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' },
|
||||
|
||||
@ -37,7 +37,6 @@ type IDebugConfiguration = {
|
||||
readonly?: boolean
|
||||
canTestAndRun?: boolean
|
||||
appId: string
|
||||
isAPIKeySet: boolean
|
||||
isTrailFinished: boolean
|
||||
mode: AppModeEnum
|
||||
modelModeType: ModelModeType
|
||||
@ -120,7 +119,6 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({
|
||||
readonly: false,
|
||||
canTestAndRun: false,
|
||||
appId: '',
|
||||
isAPIKeySet: false,
|
||||
isTrailFinished: false,
|
||||
mode: AppModeEnum.CHAT,
|
||||
modelModeType: ModelModeType.chat,
|
||||
|
||||
@ -2,12 +2,8 @@
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
ModelStatusEnum,
|
||||
ModelTypeEnum,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { commonQueryKeys, useModelListByType } from '@/service/use-common'
|
||||
import { commonQueryKeys } from '@/service/use-common'
|
||||
import { ProviderContext } from './provider-context'
|
||||
|
||||
type ProviderContextProviderProps = {
|
||||
@ -21,7 +17,6 @@ export const ProviderContextProvider = ({ children }: ProviderContextProviderPro
|
||||
isLoading: isLoadingModelProviders,
|
||||
isSuccess: isSuccessModelProviders,
|
||||
} = useQuery(consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions())
|
||||
const { data: textGenerationModelList } = useModelListByType(ModelTypeEnum.textGeneration)
|
||||
|
||||
const refreshModelProviders = () =>
|
||||
Promise.all([
|
||||
@ -39,10 +34,6 @@ export const ProviderContextProvider = ({ children }: ProviderContextProviderPro
|
||||
isLoadingModelProviders,
|
||||
isSuccessModelProviders,
|
||||
refreshModelProviders,
|
||||
textGenerationModelList: textGenerationModelList?.data || [],
|
||||
isAPIKeySet: !!textGenerationModelList?.data?.some(
|
||||
(model) => model.status === ModelStatusEnum.active,
|
||||
),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -4,7 +4,6 @@ import type {
|
||||
ModelProviderPluginSummaryResponse,
|
||||
ModelProviderSummaryResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { Model } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { createContext, useContext, useContextSelector } from 'use-context-selector'
|
||||
|
||||
export type ProviderContextState = {
|
||||
@ -13,8 +12,6 @@ export type ProviderContextState = {
|
||||
isLoadingModelProviders: boolean
|
||||
isSuccessModelProviders: boolean
|
||||
refreshModelProviders: () => Promise<void>
|
||||
textGenerationModelList: Model[]
|
||||
isAPIKeySet: boolean
|
||||
}
|
||||
|
||||
const baseProviderContextValue: ProviderContextState = {
|
||||
@ -23,8 +20,6 @@ const baseProviderContextValue: ProviderContextState = {
|
||||
isLoadingModelProviders: false,
|
||||
isSuccessModelProviders: false,
|
||||
refreshModelProviders: async () => {},
|
||||
textGenerationModelList: [],
|
||||
isAPIKeySet: true,
|
||||
}
|
||||
|
||||
export const ProviderContext = createContext<ProviderContextState>(baseProviderContextValue)
|
||||
|
||||
@ -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 {
|
||||
ConfigurationMethodEnum,
|
||||
ModelStatusEnum,
|
||||
@ -9,7 +9,8 @@ import {
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { isAgentCompatibleModel, isAgentSuggestedModel } from '../model-compatibility'
|
||||
|
||||
const createModel = (provider: string): Model => ({
|
||||
const createModel = (provider: string): ProviderWithModelsResponse => ({
|
||||
tenant_id: 'test-workspace',
|
||||
provider,
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
label: { en_US: provider, zh_Hans: provider },
|
||||
@ -17,7 +18,10 @@ const createModel = (provider: string): Model => ({
|
||||
status: ModelStatusEnum.active,
|
||||
})
|
||||
|
||||
const createModelItem = (model: string, overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
const createModelItem = (
|
||||
model: string,
|
||||
overrides: Partial<ProviderModelWithStatusEntity> = {},
|
||||
): ProviderModelWithStatusEntity => ({
|
||||
model,
|
||||
label: { en_US: model, zh_Hans: model },
|
||||
model_type: ModelTypeEnum.textGeneration,
|
||||
@ -31,8 +35,8 @@ const createModelItem = (model: string, overrides: Partial<ModelItem> = {}): Mod
|
||||
const createModelItemWithLabel = (
|
||||
model: string,
|
||||
label: string,
|
||||
overrides: Partial<ModelItem> = {},
|
||||
): ModelItem =>
|
||||
overrides: Partial<ProviderModelWithStatusEntity> = {},
|
||||
): ProviderModelWithStatusEntity =>
|
||||
createModelItem(model, {
|
||||
label: { en_US: label, zh_Hans: label },
|
||||
...overrides,
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import type {
|
||||
GetWorkspacesCurrentModelsModelTypesByModelTypeData,
|
||||
SkillDetailResponse,
|
||||
SkillReferenceResponse,
|
||||
SkillVersionResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { OperationKey } from '@orpc/tanstack-query'
|
||||
import type userEvent from '@testing-library/user-event'
|
||||
import type { ReactNode } from 'react'
|
||||
import { detectPlatform } from '@tanstack/react-hotkeys'
|
||||
@ -113,10 +115,6 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', ()
|
||||
useDefaultModel: () => ({
|
||||
data: mocks.defaultTextGenerationModel,
|
||||
}),
|
||||
useModelList: () => ({
|
||||
data: mocks.textGenerationModelList,
|
||||
isLoading: false,
|
||||
}),
|
||||
useTextGenerationCurrentProviderAndModelAndModelList: () => ({
|
||||
currentProvider: mocks.textGenerationModelList[0],
|
||||
currentModel: mocks.textGenerationModelList[0]?.models[0],
|
||||
@ -186,98 +184,102 @@ vi.mock('@/utils/download', () => ({
|
||||
downloadBlob: mocks.downloadBlob,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/console', () => ({
|
||||
consoleClient: {
|
||||
workspaces: {
|
||||
current: {
|
||||
skills: {
|
||||
bySkillId: {
|
||||
get: mocks.skillDetailGetFn,
|
||||
vi.mock('@/service/console', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/console')>()
|
||||
return {
|
||||
consoleClient: {
|
||||
workspaces: {
|
||||
current: {
|
||||
skills: {
|
||||
bySkillId: {
|
||||
get: mocks.skillDetailGetFn,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
consoleQuery: {
|
||||
workspaces: {
|
||||
current: {
|
||||
agents: {
|
||||
byAgentId: {
|
||||
skills: {
|
||||
get: {
|
||||
key: mocks.agentSkillBindingsKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skills: {
|
||||
get: {
|
||||
key: mocks.skillListKey,
|
||||
},
|
||||
tags: {
|
||||
get: {
|
||||
key: mocks.skillTagsKey,
|
||||
queryOptions: mocks.skillTagsQueryOptions,
|
||||
},
|
||||
},
|
||||
bySkillId: {
|
||||
delete: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.deleteSkillMutationFn }),
|
||||
},
|
||||
duplicate: {
|
||||
post: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.duplicateSkillMutationFn }),
|
||||
},
|
||||
},
|
||||
get: {
|
||||
key: mocks.skillDetailKey,
|
||||
queryOptions: mocks.skillDetailQueryOptions,
|
||||
},
|
||||
patch: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.skillMetadataMutationFn }),
|
||||
},
|
||||
publish: {
|
||||
post: {
|
||||
mutationOptions: (options?: unknown) => {
|
||||
mocks.publishSkillMutationOptions(options)
|
||||
return { mutationFn: mocks.publishSkillMutationFn }
|
||||
consoleQuery: {
|
||||
workspaces: {
|
||||
current: {
|
||||
models: actual.consoleQuery.workspaces.current.models,
|
||||
agents: {
|
||||
byAgentId: {
|
||||
skills: {
|
||||
get: {
|
||||
key: mocks.agentSkillBindingsKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
references: {
|
||||
},
|
||||
skills: {
|
||||
get: {
|
||||
key: mocks.skillListKey,
|
||||
},
|
||||
tags: {
|
||||
get: {
|
||||
queryOptions: mocks.skillReferencesQueryOptions,
|
||||
key: mocks.skillTagsKey,
|
||||
queryOptions: mocks.skillTagsQueryOptions,
|
||||
},
|
||||
},
|
||||
restore: {
|
||||
post: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.restoreSkillMutationFn }),
|
||||
bySkillId: {
|
||||
delete: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.deleteSkillMutationFn }),
|
||||
},
|
||||
},
|
||||
files: {
|
||||
check: {
|
||||
duplicate: {
|
||||
post: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.checkDraftFilesMutationFn }),
|
||||
mutationOptions: () => ({ mutationFn: mocks.duplicateSkillMutationFn }),
|
||||
},
|
||||
},
|
||||
get: {
|
||||
key: mocks.skillDetailKey,
|
||||
queryOptions: mocks.skillDetailQueryOptions,
|
||||
},
|
||||
patch: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.saveDraftFileMutationFn }),
|
||||
mutationOptions: () => ({ mutationFn: mocks.skillMetadataMutationFn }),
|
||||
},
|
||||
},
|
||||
versions: {
|
||||
get: {
|
||||
key: mocks.skillVersionsKey,
|
||||
queryOptions: mocks.skillVersionsQueryOptions,
|
||||
publish: {
|
||||
post: {
|
||||
mutationOptions: (options?: unknown) => {
|
||||
mocks.publishSkillMutationOptions(options)
|
||||
return { mutationFn: mocks.publishSkillMutationFn }
|
||||
},
|
||||
},
|
||||
},
|
||||
byVersionId: {
|
||||
references: {
|
||||
get: {
|
||||
queryOptions: mocks.skillVersionDetailQueryOptions,
|
||||
queryOptions: mocks.skillReferencesQueryOptions,
|
||||
},
|
||||
},
|
||||
restore: {
|
||||
post: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.restoreSkillMutationFn }),
|
||||
},
|
||||
},
|
||||
files: {
|
||||
check: {
|
||||
post: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.checkDraftFilesMutationFn }),
|
||||
},
|
||||
},
|
||||
patch: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.versionPatchMutationFn }),
|
||||
mutationOptions: () => ({ mutationFn: mocks.saveDraftFileMutationFn }),
|
||||
},
|
||||
delete: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.versionDeleteMutationFn }),
|
||||
},
|
||||
versions: {
|
||||
get: {
|
||||
key: mocks.skillVersionsKey,
|
||||
queryOptions: mocks.skillVersionsQueryOptions,
|
||||
},
|
||||
byVersionId: {
|
||||
get: {
|
||||
queryOptions: mocks.skillVersionDetailQueryOptions,
|
||||
},
|
||||
patch: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.versionPatchMutationFn }),
|
||||
},
|
||||
delete: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.versionDeleteMutationFn }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -285,8 +287,8 @@ vi.mock('@/service/console', () => ({
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../permissions', () => ({
|
||||
useSkillPermissions: () => ({ canDelete: true, canEdit: true, canPublish: true }),
|
||||
@ -846,3 +848,19 @@ export function resetDetailPageFixture() {
|
||||
size: 10,
|
||||
})
|
||||
}
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) =>
|
||||
options.queryKey[0].includes('modelTypes')
|
||||
? { data: mocks.textGenerationModelList, isPending: false }
|
||||
: actual.useQuery(options),
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,20 +1,16 @@
|
||||
'use client'
|
||||
|
||||
/* oxlint-disable eslint-react/set-state-in-effect -- The builder resets its local transcript when the authoritative detail snapshot changes. */
|
||||
|
||||
import type {
|
||||
ProviderWithModelsResponse,
|
||||
SkillDetailResponse,
|
||||
SkillFileResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
/* oxlint-disable eslint-react/set-state-in-effect -- The builder resets its local transcript when the authoritative detail snapshot changes. */
|
||||
import type { BuilderChatMessage, SkillBuilderAttachment, SkillBuilderModel } from './shared'
|
||||
import type {
|
||||
FormValue,
|
||||
Model,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
@ -22,12 +18,10 @@ import {
|
||||
ModelStatusEnum,
|
||||
ModelTypeEnum,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import {
|
||||
useDefaultModel,
|
||||
useModelList,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import { ModelSelector } from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { sendSkillAssistMessage, uploadSkillFile } from '../client'
|
||||
import { SkillBuilderGridTexture } from './builder-grid-texture'
|
||||
import {
|
||||
@ -78,7 +72,7 @@ function BuilderModelSelector({
|
||||
onSelect,
|
||||
}: {
|
||||
isLoading: boolean
|
||||
modelList: Model[]
|
||||
modelList: ProviderWithModelsResponse[]
|
||||
selectedModel: SkillBuilderModel | undefined
|
||||
onSelect: (model: SkillBuilderModel) => void
|
||||
}) {
|
||||
@ -345,8 +339,13 @@ export function SkillBuilderPanel({
|
||||
const selectedFileRef = useRef(selectedFile)
|
||||
const assistAbortControllerRef = useRef<AbortController | null>(null)
|
||||
const { data: defaultTextGenerationModel } = useDefaultModel(ModelTypeEnum.textGeneration)
|
||||
const { data: textGenerationModelList, isLoading: isTextGenerationModelListLoading } =
|
||||
useModelList(ModelTypeEnum.textGeneration)
|
||||
const { data: textGenerationModelList = [], isPending: isTextGenerationModelListLoading } =
|
||||
useQuery(
|
||||
consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryOptions({
|
||||
input: { params: { model_type: ModelTypeEnum.textGeneration } },
|
||||
select: (response) => response.data,
|
||||
}),
|
||||
)
|
||||
const fallbackModel = useMemo<SkillBuilderModel | undefined>(() => {
|
||||
for (const provider of textGenerationModelList) {
|
||||
if (provider.status !== ModelStatusEnum.active) continue
|
||||
|
||||
@ -5,7 +5,6 @@ import type {
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type {
|
||||
DefaultModelResponse,
|
||||
Model,
|
||||
ModelParameterRule,
|
||||
ModelTypeEnum,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
@ -153,10 +152,6 @@ export const activateMember = ({
|
||||
return post<LoginResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchModelList = (url: string): Promise<{ data: Model[] }> => {
|
||||
return get<{ data: Model[] }>(url)
|
||||
}
|
||||
|
||||
export const fetchDefaultModal = (url: string): Promise<{ data: DefaultModelResponse }> => {
|
||||
return get<{ data: DefaultModelResponse }>(url)
|
||||
}
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import type { ModelType } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { FileTypesRes } from './datasets'
|
||||
import type {
|
||||
Model,
|
||||
ModelParameterRule,
|
||||
ModelProvider,
|
||||
ModelTypeEnum,
|
||||
@ -17,7 +15,6 @@ import type {
|
||||
} from '@/models/common'
|
||||
import { queryOptions, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { discardRegistrationSessionState } from '@/app/components/base/amplitude/registration-session-state'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
// oxlint-disable-next-line no-restricted-imports
|
||||
import { get, post } from './base'
|
||||
|
||||
@ -211,20 +208,6 @@ export const useModelProviderDetails = (enabled = true) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const useModelListByType = (type: ModelTypeEnum | ModelType, enabled = true) => {
|
||||
return useQuery<{ data: Model[] }>({
|
||||
queryKey: consoleQuery.workspaces.current.models.modelTypes.byModelType.get.queryKey({
|
||||
input: {
|
||||
params: {
|
||||
model_type: type,
|
||||
},
|
||||
},
|
||||
}),
|
||||
queryFn: () => get<{ data: Model[] }>(`/workspaces/current/models/model-types/${type}`),
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
export const useCodeBasedExtensions = (module: string) => {
|
||||
return useQuery<CodeBasedExtension>({
|
||||
queryKey: commonQueryKeys.codeBasedExtensions(module),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user