mirror of
https://github.com/langgenius/dify.git
synced 2025-12-19 14:19:28 +00:00
refactor: implement SettingsModal with retrieval settings and add tests for RetrievalChangeTip component (#29786)
This commit is contained in:
@@ -0,0 +1,473 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import SettingsModal from './index'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { ChunkingMode, DataSourceType, DatasetPermission, RerankingModeEnum } from '@/models/datasets'
|
||||
import { IndexingType } from '@/app/components/datasets/create/step-two'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { updateDatasetSetting } from '@/service/datasets'
|
||||
import { fetchMembers } from '@/service/common'
|
||||
import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app'
|
||||
|
||||
const mockNotify = jest.fn()
|
||||
const mockOnCancel = jest.fn()
|
||||
const mockOnSave = jest.fn()
|
||||
const mockSetShowAccountSettingModal = jest.fn()
|
||||
let mockIsWorkspaceDatasetOperator = false
|
||||
|
||||
const mockUseModelList = jest.fn()
|
||||
const mockUseModelListAndDefaultModel = jest.fn()
|
||||
const mockUseModelListAndDefaultModelAndCurrentProviderAndModel = jest.fn()
|
||||
const mockUseCurrentProviderAndModel = jest.fn()
|
||||
const mockCheckShowMultiModalTip = jest.fn()
|
||||
|
||||
jest.mock('ky', () => {
|
||||
const ky = () => ky
|
||||
ky.extend = () => ky
|
||||
ky.create = () => ky
|
||||
return { __esModule: true, default: ky }
|
||||
})
|
||||
|
||||
jest.mock('@/app/components/datasets/create/step-two', () => ({
|
||||
__esModule: true,
|
||||
IndexingType: {
|
||||
QUALIFIED: 'high_quality',
|
||||
ECONOMICAL: 'economy',
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock('@/service/datasets', () => ({
|
||||
updateDatasetSetting: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock('@/service/common', () => ({
|
||||
fetchMembers: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({ isCurrentWorkspaceDatasetOperator: mockIsWorkspaceDatasetOperator }),
|
||||
useSelector: <T,>(selector: (value: { userProfile: { id: string; name: string; email: string; avatar_url: string } }) => T) => selector({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'User One',
|
||||
email: 'user@example.com',
|
||||
avatar_url: 'avatar.png',
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
jest.mock('@/context/modal-context', () => ({
|
||||
useModalContext: () => ({
|
||||
setShowAccountSettingModal: mockSetShowAccountSettingModal,
|
||||
}),
|
||||
}))
|
||||
|
||||
jest.mock('@/context/i18n', () => ({
|
||||
useDocLink: () => (path: string) => `https://docs${path}`,
|
||||
}))
|
||||
|
||||
jest.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
modelProviders: [],
|
||||
textGenerationModelList: [],
|
||||
supportRetrievalMethods: [
|
||||
RETRIEVE_METHOD.semantic,
|
||||
RETRIEVE_METHOD.fullText,
|
||||
RETRIEVE_METHOD.hybrid,
|
||||
RETRIEVE_METHOD.keywordSearch,
|
||||
],
|
||||
}),
|
||||
}))
|
||||
|
||||
jest.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
__esModule: true,
|
||||
useModelList: (...args: unknown[]) => mockUseModelList(...args),
|
||||
useModelListAndDefaultModel: (...args: unknown[]) => mockUseModelListAndDefaultModel(...args),
|
||||
useModelListAndDefaultModelAndCurrentProviderAndModel: (...args: unknown[]) =>
|
||||
mockUseModelListAndDefaultModelAndCurrentProviderAndModel(...args),
|
||||
useCurrentProviderAndModel: (...args: unknown[]) => mockUseCurrentProviderAndModel(...args),
|
||||
}))
|
||||
|
||||
jest.mock('@/app/components/header/account-setting/model-provider-page/model-selector', () => ({
|
||||
__esModule: true,
|
||||
default: ({ defaultModel }: { defaultModel?: { provider: string; model: string } }) => (
|
||||
<div data-testid='model-selector'>
|
||||
{defaultModel ? `${defaultModel.provider}/${defaultModel.model}` : 'no-model'}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
jest.mock('@/app/components/datasets/settings/utils', () => ({
|
||||
checkShowMultiModalTip: (...args: unknown[]) => mockCheckShowMultiModalTip(...args),
|
||||
}))
|
||||
|
||||
const mockUpdateDatasetSetting = updateDatasetSetting as jest.MockedFunction<typeof updateDatasetSetting>
|
||||
const mockFetchMembers = fetchMembers as jest.MockedFunction<typeof fetchMembers>
|
||||
|
||||
const createRetrievalConfig = (overrides: Partial<RetrievalConfig> = {}): RetrievalConfig => ({
|
||||
search_method: RETRIEVE_METHOD.semantic,
|
||||
reranking_enable: false,
|
||||
reranking_model: {
|
||||
reranking_provider_name: '',
|
||||
reranking_model_name: '',
|
||||
},
|
||||
top_k: 2,
|
||||
score_threshold_enabled: false,
|
||||
score_threshold: 0.5,
|
||||
reranking_mode: RerankingModeEnum.RerankingModel,
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const createDataset = (overrides: Partial<DataSet> = {}, retrievalOverrides: Partial<RetrievalConfig> = {}): DataSet => {
|
||||
const retrievalConfig = createRetrievalConfig(retrievalOverrides)
|
||||
return {
|
||||
id: 'dataset-id',
|
||||
name: 'Test Dataset',
|
||||
indexing_status: 'completed',
|
||||
icon_info: {
|
||||
icon: 'icon',
|
||||
icon_type: 'emoji',
|
||||
},
|
||||
description: 'Description',
|
||||
permission: DatasetPermission.allTeamMembers,
|
||||
data_source_type: DataSourceType.FILE,
|
||||
indexing_technique: IndexingType.QUALIFIED,
|
||||
author_name: 'Author',
|
||||
created_by: 'creator',
|
||||
updated_by: 'updater',
|
||||
updated_at: 1700000000,
|
||||
app_count: 0,
|
||||
doc_form: ChunkingMode.text,
|
||||
document_count: 0,
|
||||
total_document_count: 0,
|
||||
total_available_documents: 0,
|
||||
word_count: 0,
|
||||
provider: 'internal',
|
||||
embedding_model: 'embed-model',
|
||||
embedding_model_provider: 'embed-provider',
|
||||
embedding_available: true,
|
||||
tags: [],
|
||||
partial_member_list: [],
|
||||
external_knowledge_info: {
|
||||
external_knowledge_id: 'ext-id',
|
||||
external_knowledge_api_id: 'ext-api-id',
|
||||
external_knowledge_api_name: 'External API',
|
||||
external_knowledge_api_endpoint: 'https://api.example.com',
|
||||
},
|
||||
external_retrieval_model: {
|
||||
top_k: 2,
|
||||
score_threshold: 0.5,
|
||||
score_threshold_enabled: false,
|
||||
},
|
||||
built_in_field_enabled: false,
|
||||
doc_metadata: [],
|
||||
keyword_number: 10,
|
||||
pipeline_id: 'pipeline-id',
|
||||
is_published: false,
|
||||
runtime_mode: 'general',
|
||||
enable_api: true,
|
||||
is_multimodal: false,
|
||||
...overrides,
|
||||
retrieval_model_dict: {
|
||||
...retrievalConfig,
|
||||
...overrides.retrieval_model_dict,
|
||||
},
|
||||
retrieval_model: {
|
||||
...retrievalConfig,
|
||||
...overrides.retrieval_model,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const renderWithProviders = (dataset: DataSet) => {
|
||||
return render(
|
||||
<ToastContext.Provider value={{ notify: mockNotify, close: jest.fn() }}>
|
||||
<SettingsModal
|
||||
currentDataset={dataset}
|
||||
onCancel={mockOnCancel}
|
||||
onSave={mockOnSave}
|
||||
/>
|
||||
</ToastContext.Provider>,
|
||||
)
|
||||
}
|
||||
|
||||
describe('SettingsModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
mockIsWorkspaceDatasetOperator = false
|
||||
mockUseModelList.mockImplementation((type: ModelTypeEnum) => {
|
||||
if (type === ModelTypeEnum.rerank) {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
provider: 'rerank-provider',
|
||||
models: [{ model: 'rerank-model' }],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
return { data: [{ provider: 'embed-provider', models: [{ model: 'embed-model' }] }] }
|
||||
})
|
||||
mockUseModelListAndDefaultModel.mockReturnValue({ modelList: [], defaultModel: null })
|
||||
mockUseModelListAndDefaultModelAndCurrentProviderAndModel.mockReturnValue({ defaultModel: null, currentModel: null })
|
||||
mockUseCurrentProviderAndModel.mockReturnValue({ currentProvider: null, currentModel: null })
|
||||
mockCheckShowMultiModalTip.mockReturnValue(false)
|
||||
mockFetchMembers.mockResolvedValue({
|
||||
accounts: [
|
||||
{
|
||||
id: 'user-1',
|
||||
name: 'User One',
|
||||
email: 'user@example.com',
|
||||
avatar: 'avatar.png',
|
||||
avatar_url: 'avatar.png',
|
||||
status: 'active',
|
||||
role: 'owner',
|
||||
},
|
||||
{
|
||||
id: 'member-2',
|
||||
name: 'Member Two',
|
||||
email: 'member@example.com',
|
||||
avatar: 'avatar.png',
|
||||
avatar_url: 'avatar.png',
|
||||
status: 'active',
|
||||
role: 'editor',
|
||||
},
|
||||
],
|
||||
})
|
||||
mockUpdateDatasetSetting.mockResolvedValue(createDataset())
|
||||
})
|
||||
|
||||
it('renders dataset details', async () => {
|
||||
renderWithProviders(createDataset())
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
expect(screen.getByPlaceholderText('datasetSettings.form.namePlaceholder')).toHaveValue('Test Dataset')
|
||||
expect(screen.getByPlaceholderText('datasetSettings.form.descPlaceholder')).toHaveValue('Description')
|
||||
})
|
||||
|
||||
it('calls onCancel when cancel is clicked', async () => {
|
||||
renderWithProviders(createDataset())
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
expect(mockOnCancel).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('shows external knowledge info for external datasets', async () => {
|
||||
const dataset = createDataset({
|
||||
provider: 'external',
|
||||
external_knowledge_info: {
|
||||
external_knowledge_id: 'ext-id-123',
|
||||
external_knowledge_api_id: 'ext-api-id-123',
|
||||
external_knowledge_api_name: 'External Knowledge API',
|
||||
external_knowledge_api_endpoint: 'https://api.external.com',
|
||||
},
|
||||
})
|
||||
|
||||
renderWithProviders(dataset)
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
expect(screen.getByText('External Knowledge API')).toBeInTheDocument()
|
||||
expect(screen.getByText('https://api.external.com')).toBeInTheDocument()
|
||||
expect(screen.getByText('ext-id-123')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('updates name when user types', async () => {
|
||||
renderWithProviders(createDataset())
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
const nameInput = screen.getByPlaceholderText('datasetSettings.form.namePlaceholder')
|
||||
await userEvent.clear(nameInput)
|
||||
await userEvent.type(nameInput, 'New Dataset Name')
|
||||
|
||||
expect(nameInput).toHaveValue('New Dataset Name')
|
||||
})
|
||||
|
||||
it('updates description when user types', async () => {
|
||||
renderWithProviders(createDataset())
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
const descriptionInput = screen.getByPlaceholderText('datasetSettings.form.descPlaceholder')
|
||||
await userEvent.clear(descriptionInput)
|
||||
await userEvent.type(descriptionInput, 'New description')
|
||||
|
||||
expect(descriptionInput).toHaveValue('New description')
|
||||
})
|
||||
|
||||
it('shows and dismisses retrieval change tip when index method changes', async () => {
|
||||
const dataset = createDataset({ indexing_technique: IndexingType.ECONOMICAL })
|
||||
|
||||
renderWithProviders(dataset)
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
await userEvent.click(screen.getByText('datasetCreation.stepTwo.qualified'))
|
||||
|
||||
expect(await screen.findByText('appDebug.datasetConfig.retrieveChangeTip')).toBeInTheDocument()
|
||||
|
||||
await userEvent.click(screen.getByLabelText('close-retrieval-change-tip'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('appDebug.datasetConfig.retrieveChangeTip')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('requires dataset name before saving', async () => {
|
||||
renderWithProviders(createDataset())
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
const nameInput = screen.getByPlaceholderText('datasetSettings.form.namePlaceholder')
|
||||
await userEvent.clear(nameInput)
|
||||
await userEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'error',
|
||||
message: 'datasetSettings.form.nameError',
|
||||
}))
|
||||
expect(mockUpdateDatasetSetting).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('requires rerank model when reranking is enabled', async () => {
|
||||
mockUseModelList.mockReturnValue({ data: [] })
|
||||
const dataset = createDataset({}, createRetrievalConfig({
|
||||
reranking_enable: true,
|
||||
reranking_model: {
|
||||
reranking_provider_name: '',
|
||||
reranking_model_name: '',
|
||||
},
|
||||
}))
|
||||
|
||||
renderWithProviders(dataset)
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
await userEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'error',
|
||||
message: 'appDebug.datasetConfig.rerankModelRequired',
|
||||
}))
|
||||
expect(mockUpdateDatasetSetting).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('saves internal dataset changes', async () => {
|
||||
const rerankRetrieval = createRetrievalConfig({
|
||||
reranking_enable: true,
|
||||
reranking_model: {
|
||||
reranking_provider_name: 'rerank-provider',
|
||||
reranking_model_name: 'rerank-model',
|
||||
},
|
||||
})
|
||||
const dataset = createDataset({
|
||||
retrieval_model: rerankRetrieval,
|
||||
retrieval_model_dict: rerankRetrieval,
|
||||
})
|
||||
|
||||
renderWithProviders(dataset)
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
const nameInput = screen.getByPlaceholderText('datasetSettings.form.namePlaceholder')
|
||||
await userEvent.clear(nameInput)
|
||||
await userEvent.type(nameInput, 'Updated Internal Dataset')
|
||||
await userEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
await waitFor(() => expect(mockUpdateDatasetSetting).toHaveBeenCalled())
|
||||
|
||||
expect(mockUpdateDatasetSetting).toHaveBeenCalledWith(expect.objectContaining({
|
||||
body: expect.objectContaining({
|
||||
name: 'Updated Internal Dataset',
|
||||
permission: DatasetPermission.allTeamMembers,
|
||||
}),
|
||||
}))
|
||||
expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'success',
|
||||
message: 'common.actionMsg.modifiedSuccessfully',
|
||||
}))
|
||||
expect(mockOnSave).toHaveBeenCalledWith(expect.objectContaining({
|
||||
name: 'Updated Internal Dataset',
|
||||
retrieval_model_dict: expect.objectContaining({
|
||||
reranking_enable: true,
|
||||
}),
|
||||
}))
|
||||
})
|
||||
|
||||
it('saves external dataset with partial members and updated retrieval params', async () => {
|
||||
const dataset = createDataset({
|
||||
provider: 'external',
|
||||
permission: DatasetPermission.partialMembers,
|
||||
partial_member_list: ['member-2'],
|
||||
external_retrieval_model: {
|
||||
top_k: 5,
|
||||
score_threshold: 0.3,
|
||||
score_threshold_enabled: true,
|
||||
},
|
||||
}, {
|
||||
score_threshold_enabled: true,
|
||||
score_threshold: 0.8,
|
||||
})
|
||||
|
||||
renderWithProviders(dataset)
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
await waitFor(() => expect(mockUpdateDatasetSetting).toHaveBeenCalled())
|
||||
|
||||
expect(mockUpdateDatasetSetting).toHaveBeenCalledWith(expect.objectContaining({
|
||||
body: expect.objectContaining({
|
||||
permission: DatasetPermission.partialMembers,
|
||||
external_retrieval_model: expect.objectContaining({
|
||||
top_k: 5,
|
||||
}),
|
||||
partial_member_list: [
|
||||
{
|
||||
user_id: 'member-2',
|
||||
role: 'editor',
|
||||
},
|
||||
],
|
||||
}),
|
||||
}))
|
||||
expect(mockOnSave).toHaveBeenCalledWith(expect.objectContaining({
|
||||
retrieval_model_dict: expect.objectContaining({
|
||||
score_threshold_enabled: true,
|
||||
score_threshold: 0.8,
|
||||
}),
|
||||
}))
|
||||
})
|
||||
|
||||
it('disables save button while saving', async () => {
|
||||
mockUpdateDatasetSetting.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)))
|
||||
|
||||
renderWithProviders(createDataset())
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
const saveButton = screen.getByRole('button', { name: 'common.operation.save' })
|
||||
await userEvent.click(saveButton)
|
||||
|
||||
expect(saveButton).toBeDisabled()
|
||||
})
|
||||
|
||||
it('shows error toast when save fails', async () => {
|
||||
mockUpdateDatasetSetting.mockRejectedValue(new Error('API Error'))
|
||||
|
||||
renderWithProviders(createDataset())
|
||||
|
||||
await waitFor(() => expect(mockFetchMembers).toHaveBeenCalled())
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' }))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -4,10 +4,8 @@ import { useMount } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import cn from '@/utils/classnames'
|
||||
import IndexMethod from '@/app/components/datasets/settings/index-method'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
@@ -18,11 +16,7 @@ import { useAppContext } from '@/context/app-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import type { RetrievalConfig } from '@/types/app'
|
||||
import RetrievalSettings from '@/app/components/datasets/external-knowledge-base/create/RetrievalSettings'
|
||||
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
|
||||
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
|
||||
import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import PermissionSelector from '@/app/components/datasets/settings/permission-selector'
|
||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
@@ -32,6 +26,7 @@ import type { Member } from '@/models/common'
|
||||
import { IndexingType } from '@/app/components/datasets/create/step-two'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { checkShowMultiModalTip } from '@/app/components/datasets/settings/utils'
|
||||
import { RetrievalChangeTip, RetrievalSection } from './retrieval-section'
|
||||
|
||||
type SettingsModalProps = {
|
||||
currentDataset: DataSet
|
||||
@@ -298,92 +293,37 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
||||
)}
|
||||
|
||||
{/* Retrieval Method Config */}
|
||||
{currentDataset?.provider === 'external'
|
||||
? <>
|
||||
<div className={rowClass}><Divider /></div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div>
|
||||
</div>
|
||||
<RetrievalSettings
|
||||
topK={topK}
|
||||
scoreThreshold={scoreThreshold}
|
||||
scoreThresholdEnabled={scoreThresholdEnabled}
|
||||
onChange={handleSettingsChange}
|
||||
isInRetrievalSetting={true}
|
||||
/>
|
||||
</div>
|
||||
<div className={rowClass}><Divider /></div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.externalKnowledgeAPI')}</div>
|
||||
</div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<div className='flex h-full items-center gap-1 rounded-lg bg-components-input-bg-normal px-3 py-2'>
|
||||
<ApiConnectionMod className='h-4 w-4 text-text-secondary' />
|
||||
<div className='system-sm-medium overflow-hidden text-ellipsis text-text-secondary'>
|
||||
{currentDataset?.external_knowledge_info.external_knowledge_api_name}
|
||||
</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>·</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{currentDataset?.external_knowledge_info.external_knowledge_api_endpoint}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.externalKnowledgeID')}</div>
|
||||
</div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<div className='flex h-full items-center gap-1 rounded-lg bg-components-input-bg-normal px-3 py-2'>
|
||||
<div className='system-xs-regular text-text-tertiary'>{currentDataset?.external_knowledge_info.external_knowledge_id}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={rowClass}><Divider /></div>
|
||||
</>
|
||||
: <div className={rowClass}>
|
||||
<div className={cn(labelClass, 'w-auto min-w-[168px]')}>
|
||||
<div>
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div>
|
||||
<div className='text-xs font-normal leading-[18px] text-text-tertiary'>
|
||||
<a target='_blank' rel='noopener noreferrer' href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods#setting-the-retrieval-setting')} className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a>
|
||||
{t('datasetSettings.form.retrievalSetting.description')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{indexMethod === IndexingType.QUALIFIED
|
||||
? (
|
||||
<RetrievalMethodConfig
|
||||
value={retrievalConfig}
|
||||
onChange={setRetrievalConfig}
|
||||
showMultiModalTip={showMultiModalTip}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<EconomicalRetrievalMethodConfig
|
||||
value={retrievalConfig}
|
||||
onChange={setRetrievalConfig}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>}
|
||||
{isExternal ? (
|
||||
<RetrievalSection
|
||||
isExternal
|
||||
rowClass={rowClass}
|
||||
labelClass={labelClass}
|
||||
t={t}
|
||||
topK={topK}
|
||||
scoreThreshold={scoreThreshold}
|
||||
scoreThresholdEnabled={scoreThresholdEnabled}
|
||||
onExternalSettingChange={handleSettingsChange}
|
||||
currentDataset={currentDataset}
|
||||
/>
|
||||
) : (
|
||||
<RetrievalSection
|
||||
isExternal={false}
|
||||
rowClass={rowClass}
|
||||
labelClass={labelClass}
|
||||
t={t}
|
||||
indexMethod={indexMethod}
|
||||
retrievalConfig={retrievalConfig}
|
||||
showMultiModalTip={showMultiModalTip}
|
||||
onRetrievalConfigChange={setRetrievalConfig}
|
||||
docLink={docLink}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isRetrievalChanged && !isHideChangedTip && (
|
||||
<div className='absolute bottom-[76px] left-[30px] right-[30px] z-10 flex h-10 items-center justify-between rounded-lg border border-[#FEF0C7] bg-[#FFFAEB] px-3 shadow-lg'>
|
||||
<div className='flex items-center'>
|
||||
<AlertTriangle className='mr-1 h-3 w-3 text-[#F79009]' />
|
||||
<div className='text-xs font-medium leading-[18px] text-gray-700'>{t('appDebug.datasetConfig.retrieveChangeTip')}</div>
|
||||
</div>
|
||||
<div className='cursor-pointer p-1' onClick={(e) => {
|
||||
setIsHideChangedTip(true)
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
}}>
|
||||
<RiCloseLine className='h-4 w-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<RetrievalChangeTip
|
||||
visible={isRetrievalChanged && !isHideChangedTip}
|
||||
message={t('appDebug.datasetConfig.retrieveChangeTip')}
|
||||
onDismiss={() => setIsHideChangedTip(true)}
|
||||
/>
|
||||
|
||||
<div
|
||||
className='sticky bottom-0 z-[5] flex w-full justify-end border-t border-divider-regular bg-background-section px-6 py-4'
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { ChunkingMode, DataSourceType, DatasetPermission, RerankingModeEnum } from '@/models/datasets'
|
||||
import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app'
|
||||
import { IndexingType } from '@/app/components/datasets/create/step-two'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { RetrievalChangeTip, RetrievalSection } from './retrieval-section'
|
||||
|
||||
const mockUseModelList = jest.fn()
|
||||
const mockUseModelListAndDefaultModel = jest.fn()
|
||||
const mockUseModelListAndDefaultModelAndCurrentProviderAndModel = jest.fn()
|
||||
const mockUseCurrentProviderAndModel = jest.fn()
|
||||
|
||||
jest.mock('ky', () => {
|
||||
const ky = () => ky
|
||||
ky.extend = () => ky
|
||||
ky.create = () => ky
|
||||
return { __esModule: true, default: ky }
|
||||
})
|
||||
|
||||
jest.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
modelProviders: [],
|
||||
textGenerationModelList: [],
|
||||
supportRetrievalMethods: [
|
||||
RETRIEVE_METHOD.semantic,
|
||||
RETRIEVE_METHOD.fullText,
|
||||
RETRIEVE_METHOD.hybrid,
|
||||
RETRIEVE_METHOD.keywordSearch,
|
||||
],
|
||||
}),
|
||||
}))
|
||||
|
||||
jest.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||
__esModule: true,
|
||||
useModelListAndDefaultModelAndCurrentProviderAndModel: (...args: unknown[]) =>
|
||||
mockUseModelListAndDefaultModelAndCurrentProviderAndModel(...args),
|
||||
useModelListAndDefaultModel: (...args: unknown[]) => mockUseModelListAndDefaultModel(...args),
|
||||
useModelList: (...args: unknown[]) => mockUseModelList(...args),
|
||||
useCurrentProviderAndModel: (...args: unknown[]) => mockUseCurrentProviderAndModel(...args),
|
||||
}))
|
||||
|
||||
jest.mock('@/app/components/header/account-setting/model-provider-page/model-selector', () => ({
|
||||
__esModule: true,
|
||||
default: ({ defaultModel }: { defaultModel?: { provider: string; model: string } }) => (
|
||||
<div data-testid='model-selector'>
|
||||
{defaultModel ? `${defaultModel.provider}/${defaultModel.model}` : 'no-model'}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
jest.mock('@/app/components/datasets/create/step-two', () => ({
|
||||
__esModule: true,
|
||||
IndexingType: {
|
||||
QUALIFIED: 'high_quality',
|
||||
ECONOMICAL: 'economy',
|
||||
},
|
||||
}))
|
||||
|
||||
const createRetrievalConfig = (overrides: Partial<RetrievalConfig> = {}): RetrievalConfig => ({
|
||||
search_method: RETRIEVE_METHOD.semantic,
|
||||
reranking_enable: false,
|
||||
reranking_model: {
|
||||
reranking_provider_name: '',
|
||||
reranking_model_name: '',
|
||||
},
|
||||
top_k: 2,
|
||||
score_threshold_enabled: false,
|
||||
score_threshold: 0.5,
|
||||
reranking_mode: RerankingModeEnum.RerankingModel,
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const createDataset = (overrides: Partial<DataSet> = {}, retrievalOverrides: Partial<RetrievalConfig> = {}): DataSet => {
|
||||
const retrievalConfig = createRetrievalConfig(retrievalOverrides)
|
||||
return {
|
||||
id: 'dataset-id',
|
||||
name: 'Test Dataset',
|
||||
indexing_status: 'completed',
|
||||
icon_info: {
|
||||
icon: 'icon',
|
||||
icon_type: 'emoji',
|
||||
},
|
||||
description: 'Description',
|
||||
permission: DatasetPermission.allTeamMembers,
|
||||
data_source_type: DataSourceType.FILE,
|
||||
indexing_technique: IndexingType.QUALIFIED,
|
||||
author_name: 'Author',
|
||||
created_by: 'creator',
|
||||
updated_by: 'updater',
|
||||
updated_at: 1700000000,
|
||||
app_count: 0,
|
||||
doc_form: ChunkingMode.text,
|
||||
document_count: 0,
|
||||
total_document_count: 0,
|
||||
total_available_documents: 0,
|
||||
word_count: 0,
|
||||
provider: 'internal',
|
||||
embedding_model: 'embed-model',
|
||||
embedding_model_provider: 'embed-provider',
|
||||
embedding_available: true,
|
||||
tags: [],
|
||||
partial_member_list: [],
|
||||
external_knowledge_info: {
|
||||
external_knowledge_id: 'ext-id',
|
||||
external_knowledge_api_id: 'ext-api-id',
|
||||
external_knowledge_api_name: 'External API',
|
||||
external_knowledge_api_endpoint: 'https://api.example.com',
|
||||
},
|
||||
external_retrieval_model: {
|
||||
top_k: 2,
|
||||
score_threshold: 0.5,
|
||||
score_threshold_enabled: false,
|
||||
},
|
||||
built_in_field_enabled: false,
|
||||
doc_metadata: [],
|
||||
keyword_number: 10,
|
||||
pipeline_id: 'pipeline-id',
|
||||
is_published: false,
|
||||
runtime_mode: 'general',
|
||||
enable_api: true,
|
||||
is_multimodal: false,
|
||||
...overrides,
|
||||
retrieval_model_dict: {
|
||||
...retrievalConfig,
|
||||
...overrides.retrieval_model_dict,
|
||||
},
|
||||
retrieval_model: {
|
||||
...retrievalConfig,
|
||||
...overrides.retrieval_model,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('RetrievalChangeTip', () => {
|
||||
const defaultProps = {
|
||||
visible: true,
|
||||
message: 'Test message',
|
||||
onDismiss: jest.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('renders and supports dismiss', async () => {
|
||||
// Arrange
|
||||
const onDismiss = jest.fn()
|
||||
render(<RetrievalChangeTip {...defaultProps} onDismiss={onDismiss} />)
|
||||
|
||||
// Act
|
||||
await userEvent.click(screen.getByRole('button', { name: 'close-retrieval-change-tip' }))
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('Test message')).toBeInTheDocument()
|
||||
expect(onDismiss).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not render when hidden', () => {
|
||||
// Arrange & Act
|
||||
render(<RetrievalChangeTip {...defaultProps} visible={false} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByText('Test message')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('RetrievalSection', () => {
|
||||
const t = (key: string) => key
|
||||
const rowClass = 'row'
|
||||
const labelClass = 'label'
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
mockUseModelList.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({ defaultModel: null, currentModel: null })
|
||||
mockUseCurrentProviderAndModel.mockReturnValue({ currentProvider: null, currentModel: null })
|
||||
})
|
||||
|
||||
it('renders external retrieval details and propagates changes', async () => {
|
||||
// Arrange
|
||||
const dataset = createDataset({
|
||||
provider: 'external',
|
||||
external_knowledge_info: {
|
||||
external_knowledge_id: 'ext-id-999',
|
||||
external_knowledge_api_id: 'ext-api-id-999',
|
||||
external_knowledge_api_name: 'External API',
|
||||
external_knowledge_api_endpoint: 'https://api.external.com',
|
||||
},
|
||||
})
|
||||
const handleExternalChange = jest.fn()
|
||||
|
||||
// Act
|
||||
render(
|
||||
<RetrievalSection
|
||||
isExternal
|
||||
rowClass={rowClass}
|
||||
labelClass={labelClass}
|
||||
t={t}
|
||||
topK={3}
|
||||
scoreThreshold={0.4}
|
||||
scoreThresholdEnabled
|
||||
onExternalSettingChange={handleExternalChange}
|
||||
currentDataset={dataset}
|
||||
/>,
|
||||
)
|
||||
const [topKIncrement] = screen.getAllByLabelText('increment')
|
||||
await userEvent.click(topKIncrement)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('External API')).toBeInTheDocument()
|
||||
expect(screen.getByText('https://api.external.com')).toBeInTheDocument()
|
||||
expect(screen.getByText('ext-id-999')).toBeInTheDocument()
|
||||
expect(handleExternalChange).toHaveBeenCalledWith(expect.objectContaining({ top_k: 4 }))
|
||||
})
|
||||
|
||||
it('renders internal retrieval config with doc link', () => {
|
||||
// Arrange
|
||||
const docLink = jest.fn((path: string) => `https://docs.example${path}`)
|
||||
const retrievalConfig = createRetrievalConfig()
|
||||
|
||||
// Act
|
||||
render(
|
||||
<RetrievalSection
|
||||
isExternal={false}
|
||||
rowClass={rowClass}
|
||||
labelClass={labelClass}
|
||||
t={t}
|
||||
indexMethod={IndexingType.QUALIFIED}
|
||||
retrievalConfig={retrievalConfig}
|
||||
showMultiModalTip
|
||||
onRetrievalConfigChange={jest.fn()}
|
||||
docLink={docLink}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('dataset.retrieval.semantic_search.title')).toBeInTheDocument()
|
||||
const learnMoreLink = screen.getByRole('link', { name: 'datasetSettings.form.retrievalSetting.learnMore' })
|
||||
expect(learnMoreLink).toHaveAttribute('href', 'https://docs.example/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods#setting-the-retrieval-setting')
|
||||
expect(docLink).toHaveBeenCalledWith('/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods#setting-the-retrieval-setting')
|
||||
})
|
||||
|
||||
it('propagates retrieval config changes for economical indexing', async () => {
|
||||
// Arrange
|
||||
const handleRetrievalChange = jest.fn()
|
||||
|
||||
// Act
|
||||
render(
|
||||
<RetrievalSection
|
||||
isExternal={false}
|
||||
rowClass={rowClass}
|
||||
labelClass={labelClass}
|
||||
t={t}
|
||||
indexMethod={IndexingType.ECONOMICAL}
|
||||
retrievalConfig={createRetrievalConfig()}
|
||||
showMultiModalTip={false}
|
||||
onRetrievalConfigChange={handleRetrievalChange}
|
||||
docLink={path => path}
|
||||
/>,
|
||||
)
|
||||
const [topKIncrement] = screen.getAllByLabelText('increment')
|
||||
await userEvent.click(topKIncrement)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('dataset.retrieval.keyword_search.title')).toBeInTheDocument()
|
||||
expect(handleRetrievalChange).toHaveBeenCalledWith(expect.objectContaining({
|
||||
top_k: 3,
|
||||
}))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,218 @@
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import type { FC } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import RetrievalSettings from '@/app/components/datasets/external-knowledge-base/create/RetrievalSettings'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { IndexingType } from '@/app/components/datasets/create/step-two'
|
||||
import type { RetrievalConfig } from '@/types/app'
|
||||
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
|
||||
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
|
||||
|
||||
type CommonSectionProps = {
|
||||
rowClass: string
|
||||
labelClass: string
|
||||
t: (key: string, options?: any) => string
|
||||
}
|
||||
|
||||
type ExternalRetrievalSectionProps = CommonSectionProps & {
|
||||
topK: number
|
||||
scoreThreshold: number
|
||||
scoreThresholdEnabled: boolean
|
||||
onExternalSettingChange: (data: { top_k?: number; score_threshold?: number; score_threshold_enabled?: boolean }) => void
|
||||
currentDataset: DataSet
|
||||
}
|
||||
|
||||
const ExternalRetrievalSection: FC<ExternalRetrievalSectionProps> = ({
|
||||
rowClass,
|
||||
labelClass,
|
||||
t,
|
||||
topK,
|
||||
scoreThreshold,
|
||||
scoreThresholdEnabled,
|
||||
onExternalSettingChange,
|
||||
currentDataset,
|
||||
}) => (
|
||||
<>
|
||||
<div className={rowClass}><Divider /></div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div>
|
||||
</div>
|
||||
<RetrievalSettings
|
||||
topK={topK}
|
||||
scoreThreshold={scoreThreshold}
|
||||
scoreThresholdEnabled={scoreThresholdEnabled}
|
||||
onChange={onExternalSettingChange}
|
||||
isInRetrievalSetting={true}
|
||||
/>
|
||||
</div>
|
||||
<div className={rowClass}><Divider /></div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.externalKnowledgeAPI')}</div>
|
||||
</div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<div className='flex h-full items-center gap-1 rounded-lg bg-components-input-bg-normal px-3 py-2'>
|
||||
<ApiConnectionMod className='h-4 w-4 text-text-secondary' />
|
||||
<div className='system-sm-medium overflow-hidden text-ellipsis text-text-secondary'>
|
||||
{currentDataset?.external_knowledge_info.external_knowledge_api_name}
|
||||
</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>·</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{currentDataset?.external_knowledge_info.external_knowledge_api_endpoint}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.externalKnowledgeID')}</div>
|
||||
</div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<div className='flex h-full items-center gap-1 rounded-lg bg-components-input-bg-normal px-3 py-2'>
|
||||
<div className='system-xs-regular text-text-tertiary'>{currentDataset?.external_knowledge_info.external_knowledge_id}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={rowClass}><Divider /></div>
|
||||
</>
|
||||
)
|
||||
|
||||
type InternalRetrievalSectionProps = CommonSectionProps & {
|
||||
indexMethod: IndexingType
|
||||
retrievalConfig: RetrievalConfig
|
||||
showMultiModalTip: boolean
|
||||
onRetrievalConfigChange: (value: RetrievalConfig) => void
|
||||
docLink: (path: string) => string
|
||||
}
|
||||
|
||||
const InternalRetrievalSection: FC<InternalRetrievalSectionProps> = ({
|
||||
rowClass,
|
||||
labelClass,
|
||||
t,
|
||||
indexMethod,
|
||||
retrievalConfig,
|
||||
showMultiModalTip,
|
||||
onRetrievalConfigChange,
|
||||
docLink,
|
||||
}) => (
|
||||
<div className={rowClass}>
|
||||
<div className={cn(labelClass, 'w-auto min-w-[168px]')}>
|
||||
<div>
|
||||
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div>
|
||||
<div className='text-xs font-normal leading-[18px] text-text-tertiary'>
|
||||
<a target='_blank' rel='noopener noreferrer' href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods#setting-the-retrieval-setting')} className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a>
|
||||
{t('datasetSettings.form.retrievalSetting.description')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{indexMethod === IndexingType.QUALIFIED
|
||||
? (
|
||||
<RetrievalMethodConfig
|
||||
value={retrievalConfig}
|
||||
onChange={onRetrievalConfigChange}
|
||||
showMultiModalTip={showMultiModalTip}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<EconomicalRetrievalMethodConfig
|
||||
value={retrievalConfig}
|
||||
onChange={onRetrievalConfigChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
type RetrievalSectionProps
|
||||
= | (ExternalRetrievalSectionProps & { isExternal: true })
|
||||
| (InternalRetrievalSectionProps & { isExternal: false })
|
||||
|
||||
export const RetrievalSection: FC<RetrievalSectionProps> = (props) => {
|
||||
if (props.isExternal) {
|
||||
const {
|
||||
rowClass,
|
||||
labelClass,
|
||||
t,
|
||||
topK,
|
||||
scoreThreshold,
|
||||
scoreThresholdEnabled,
|
||||
onExternalSettingChange,
|
||||
currentDataset,
|
||||
} = props
|
||||
|
||||
return (
|
||||
<ExternalRetrievalSection
|
||||
rowClass={rowClass}
|
||||
labelClass={labelClass}
|
||||
t={t}
|
||||
topK={topK}
|
||||
scoreThreshold={scoreThreshold}
|
||||
scoreThresholdEnabled={scoreThresholdEnabled}
|
||||
onExternalSettingChange={onExternalSettingChange}
|
||||
currentDataset={currentDataset}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
rowClass,
|
||||
labelClass,
|
||||
t,
|
||||
indexMethod,
|
||||
retrievalConfig,
|
||||
showMultiModalTip,
|
||||
onRetrievalConfigChange,
|
||||
docLink,
|
||||
} = props
|
||||
|
||||
return (
|
||||
<InternalRetrievalSection
|
||||
rowClass={rowClass}
|
||||
labelClass={labelClass}
|
||||
t={t}
|
||||
indexMethod={indexMethod}
|
||||
retrievalConfig={retrievalConfig}
|
||||
showMultiModalTip={showMultiModalTip}
|
||||
onRetrievalConfigChange={onRetrievalConfigChange}
|
||||
docLink={docLink}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type RetrievalChangeTipProps = {
|
||||
visible: boolean
|
||||
message: string
|
||||
onDismiss: () => void
|
||||
}
|
||||
|
||||
export const RetrievalChangeTip: FC<RetrievalChangeTipProps> = ({
|
||||
visible,
|
||||
message,
|
||||
onDismiss,
|
||||
}) => {
|
||||
if (!visible)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='absolute bottom-[76px] left-[30px] right-[30px] z-10 flex h-10 items-center justify-between rounded-lg border border-[#FEF0C7] bg-[#FFFAEB] px-3 shadow-lg'>
|
||||
<div className='flex items-center'>
|
||||
<AlertTriangle className='mr-1 h-3 w-3 text-[#F79009]' />
|
||||
<div className='text-xs font-medium leading-[18px] text-gray-700'>{message}</div>
|
||||
</div>
|
||||
<button
|
||||
type='button'
|
||||
className='cursor-pointer p-1'
|
||||
onClick={(event) => {
|
||||
onDismiss()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
aria-label='close-retrieval-change-tip'
|
||||
>
|
||||
<RiCloseLine className='h-4 w-4 text-gray-500' />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user