refactor(datasets): extract hooks and components with comprehensive tests (#31707)

Co-authored-by: CodingOnStar <hanxujiang@dify.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Coding On Star
2026-02-04 18:12:17 +08:00
committed by GitHub
parent cc5705cb71
commit 297dd832aa
38 changed files with 9328 additions and 1653 deletions

View File

@ -0,0 +1,763 @@
import type { DataSet } from '@/models/datasets'
import type { RetrievalConfig } from '@/types/app'
import { act, renderHook, waitFor } from '@testing-library/react'
import { ChunkingMode, DatasetPermission, DataSourceType, WeightedScoreEnum } from '@/models/datasets'
import { RETRIEVE_METHOD } from '@/types/app'
import { IndexingType } from '../../../create/step-two'
import { useFormState } from './use-form-state'
// Mock contexts
const mockMutateDatasets = vi.fn()
const mockInvalidDatasetList = vi.fn()
vi.mock('@/context/app-context', () => ({
useSelector: () => false, // isCurrentWorkspaceDatasetOperator
}))
const createDefaultMockDataset = (): DataSet => ({
id: 'dataset-1',
name: 'Test Dataset',
description: 'Test description',
permission: DatasetPermission.onlyMe,
icon_info: {
icon_type: 'emoji',
icon: '📚',
icon_background: '#FFFFFF',
icon_url: '',
},
indexing_technique: IndexingType.QUALIFIED,
indexing_status: 'completed',
data_source_type: DataSourceType.FILE,
doc_form: ChunkingMode.text,
embedding_model: 'text-embedding-ada-002',
embedding_model_provider: 'openai',
embedding_available: true,
app_count: 0,
document_count: 5,
total_document_count: 5,
word_count: 1000,
provider: 'vendor',
tags: [],
partial_member_list: [],
external_knowledge_info: {
external_knowledge_id: 'ext-1',
external_knowledge_api_id: 'api-1',
external_knowledge_api_name: 'External API',
external_knowledge_api_endpoint: 'https://api.example.com',
},
external_retrieval_model: {
top_k: 3,
score_threshold: 0.7,
score_threshold_enabled: true,
},
retrieval_model_dict: {
search_method: RETRIEVE_METHOD.semantic,
reranking_enable: false,
reranking_model: {
reranking_provider_name: '',
reranking_model_name: '',
},
top_k: 3,
score_threshold_enabled: false,
score_threshold: 0.5,
} as RetrievalConfig,
retrieval_model: {
search_method: RETRIEVE_METHOD.semantic,
reranking_enable: false,
reranking_model: {
reranking_provider_name: '',
reranking_model_name: '',
},
top_k: 3,
score_threshold_enabled: false,
score_threshold: 0.5,
} as RetrievalConfig,
built_in_field_enabled: false,
keyword_number: 10,
created_by: 'user-1',
updated_by: 'user-1',
updated_at: Date.now(),
runtime_mode: 'general',
enable_api: true,
is_multimodal: false,
})
let mockDataset: DataSet = createDefaultMockDataset()
vi.mock('@/context/dataset-detail', () => ({
useDatasetDetailContextWithSelector: (selector: (state: { dataset: DataSet | null, mutateDatasetRes: () => void }) => unknown) => {
const state = {
dataset: mockDataset,
mutateDatasetRes: mockMutateDatasets,
}
return selector(state)
},
}))
// Mock services
vi.mock('@/service/datasets', () => ({
updateDatasetSetting: vi.fn().mockResolvedValue({}),
}))
vi.mock('@/service/knowledge/use-dataset', () => ({
useInvalidDatasetList: () => mockInvalidDatasetList,
}))
vi.mock('@/service/use-common', () => ({
useMembers: () => ({
data: {
accounts: [
{ id: 'user-1', name: 'User 1', email: 'user1@example.com', role: 'owner', avatar: '', avatar_url: '', last_login_at: '', created_at: '', status: 'active' },
{ id: 'user-2', name: 'User 2', email: 'user2@example.com', role: 'admin', avatar: '', avatar_url: '', last_login_at: '', created_at: '', status: 'active' },
],
},
}),
}))
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
useModelList: () => ({ data: [] }),
}))
vi.mock('@/app/components/datasets/common/check-rerank-model', () => ({
isReRankModelSelected: () => true,
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: vi.fn(),
},
}))
describe('useFormState', () => {
beforeEach(() => {
vi.clearAllMocks()
mockDataset = createDefaultMockDataset()
})
describe('Initial State', () => {
it('should initialize with dataset values', () => {
const { result } = renderHook(() => useFormState())
expect(result.current.name).toBe('Test Dataset')
expect(result.current.description).toBe('Test description')
expect(result.current.permission).toBe(DatasetPermission.onlyMe)
expect(result.current.indexMethod).toBe(IndexingType.QUALIFIED)
expect(result.current.keywordNumber).toBe(10)
})
it('should initialize icon info from dataset', () => {
const { result } = renderHook(() => useFormState())
expect(result.current.iconInfo).toEqual({
icon_type: 'emoji',
icon: '📚',
icon_background: '#FFFFFF',
icon_url: '',
})
})
it('should initialize external retrieval settings', () => {
const { result } = renderHook(() => useFormState())
expect(result.current.topK).toBe(3)
expect(result.current.scoreThreshold).toBe(0.7)
expect(result.current.scoreThresholdEnabled).toBe(true)
})
it('should derive member list from API data', () => {
const { result } = renderHook(() => useFormState())
expect(result.current.memberList).toHaveLength(2)
expect(result.current.memberList[0].name).toBe('User 1')
})
it('should return currentDataset from context', () => {
const { result } = renderHook(() => useFormState())
expect(result.current.currentDataset).toBeDefined()
expect(result.current.currentDataset?.id).toBe('dataset-1')
})
})
describe('State Setters', () => {
it('should update name when setName is called', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.setName('New Name')
})
expect(result.current.name).toBe('New Name')
})
it('should update description when setDescription is called', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.setDescription('New Description')
})
expect(result.current.description).toBe('New Description')
})
it('should update permission when setPermission is called', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.setPermission(DatasetPermission.allTeamMembers)
})
expect(result.current.permission).toBe(DatasetPermission.allTeamMembers)
})
it('should update indexMethod when setIndexMethod is called', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.setIndexMethod(IndexingType.ECONOMICAL)
})
expect(result.current.indexMethod).toBe(IndexingType.ECONOMICAL)
})
it('should update keywordNumber when setKeywordNumber is called', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.setKeywordNumber(20)
})
expect(result.current.keywordNumber).toBe(20)
})
it('should update selectedMemberIDs when setSelectedMemberIDs is called', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.setSelectedMemberIDs(['user-1', 'user-2'])
})
expect(result.current.selectedMemberIDs).toEqual(['user-1', 'user-2'])
})
})
describe('Icon Handlers', () => {
it('should open app icon picker and save previous icon', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.handleOpenAppIconPicker()
})
expect(result.current.showAppIconPicker).toBe(true)
})
it('should select emoji icon and close picker', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.handleOpenAppIconPicker()
})
act(() => {
result.current.handleSelectAppIcon({
type: 'emoji',
icon: '🎉',
background: '#FF0000',
})
})
expect(result.current.showAppIconPicker).toBe(false)
expect(result.current.iconInfo).toEqual({
icon_type: 'emoji',
icon: '🎉',
icon_background: '#FF0000',
icon_url: undefined,
})
})
it('should select image icon and close picker', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.handleOpenAppIconPicker()
})
act(() => {
result.current.handleSelectAppIcon({
type: 'image',
fileId: 'file-123',
url: 'https://example.com/icon.png',
})
})
expect(result.current.showAppIconPicker).toBe(false)
expect(result.current.iconInfo).toEqual({
icon_type: 'image',
icon: 'file-123',
icon_background: undefined,
icon_url: 'https://example.com/icon.png',
})
})
it('should restore previous icon when picker is closed', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.handleOpenAppIconPicker()
})
act(() => {
result.current.handleSelectAppIcon({
type: 'emoji',
icon: '🎉',
background: '#FF0000',
})
})
act(() => {
result.current.handleOpenAppIconPicker()
})
act(() => {
result.current.handleCloseAppIconPicker()
})
expect(result.current.showAppIconPicker).toBe(false)
// After close, icon should be restored to the icon before opening
expect(result.current.iconInfo).toEqual({
icon_type: 'emoji',
icon: '🎉',
icon_background: '#FF0000',
icon_url: undefined,
})
})
})
describe('External Retrieval Settings Handler', () => {
it('should update topK when provided', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.handleSettingsChange({ top_k: 5 })
})
expect(result.current.topK).toBe(5)
})
it('should update scoreThreshold when provided', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.handleSettingsChange({ score_threshold: 0.8 })
})
expect(result.current.scoreThreshold).toBe(0.8)
})
it('should update scoreThresholdEnabled when provided', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.handleSettingsChange({ score_threshold_enabled: false })
})
expect(result.current.scoreThresholdEnabled).toBe(false)
})
it('should update multiple settings at once', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.handleSettingsChange({
top_k: 10,
score_threshold: 0.9,
score_threshold_enabled: true,
})
})
expect(result.current.topK).toBe(10)
expect(result.current.scoreThreshold).toBe(0.9)
expect(result.current.scoreThresholdEnabled).toBe(true)
})
})
describe('Summary Index Setting Handler', () => {
it('should update summary index setting', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.handleSummaryIndexSettingChange({
enable: true,
})
})
expect(result.current.summaryIndexSetting).toMatchObject({
enable: true,
})
})
it('should merge with existing settings', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.handleSummaryIndexSettingChange({
enable: true,
})
})
act(() => {
result.current.handleSummaryIndexSettingChange({
model_provider_name: 'openai',
model_name: 'gpt-4',
})
})
expect(result.current.summaryIndexSetting).toMatchObject({
enable: true,
model_provider_name: 'openai',
model_name: 'gpt-4',
})
})
})
describe('handleSave', () => {
it('should show error toast when name is empty', async () => {
const Toast = await import('@/app/components/base/toast')
const { result } = renderHook(() => useFormState())
act(() => {
result.current.setName('')
})
await act(async () => {
await result.current.handleSave()
})
expect(Toast.default.notify).toHaveBeenCalledWith({
type: 'error',
message: expect.any(String),
})
})
it('should show error toast when name is whitespace only', async () => {
const Toast = await import('@/app/components/base/toast')
const { result } = renderHook(() => useFormState())
act(() => {
result.current.setName(' ')
})
await act(async () => {
await result.current.handleSave()
})
expect(Toast.default.notify).toHaveBeenCalledWith({
type: 'error',
message: expect.any(String),
})
})
it('should call updateDatasetSetting with correct params', async () => {
const { updateDatasetSetting } = await import('@/service/datasets')
const { result } = renderHook(() => useFormState())
await act(async () => {
await result.current.handleSave()
})
expect(updateDatasetSetting).toHaveBeenCalledWith({
datasetId: 'dataset-1',
body: expect.objectContaining({
name: 'Test Dataset',
description: 'Test description',
permission: DatasetPermission.onlyMe,
}),
})
})
it('should show success toast on successful save', async () => {
const Toast = await import('@/app/components/base/toast')
const { result } = renderHook(() => useFormState())
await act(async () => {
await result.current.handleSave()
})
await waitFor(() => {
expect(Toast.default.notify).toHaveBeenCalledWith({
type: 'success',
message: expect.any(String),
})
})
})
it('should call mutateDatasets after successful save', async () => {
const { result } = renderHook(() => useFormState())
await act(async () => {
await result.current.handleSave()
})
await waitFor(() => {
expect(mockMutateDatasets).toHaveBeenCalled()
})
})
it('should call invalidDatasetList after successful save', async () => {
const { result } = renderHook(() => useFormState())
await act(async () => {
await result.current.handleSave()
})
await waitFor(() => {
expect(mockInvalidDatasetList).toHaveBeenCalled()
})
})
it('should set loading to true during save', async () => {
const { result } = renderHook(() => useFormState())
expect(result.current.loading).toBe(false)
const savePromise = act(async () => {
await result.current.handleSave()
})
// Loading should be true during the save operation
await savePromise
expect(result.current.loading).toBe(false) // After completion
})
it('should not save when already loading', async () => {
const { updateDatasetSetting } = await import('@/service/datasets')
vi.mocked(updateDatasetSetting).mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)))
const { result } = renderHook(() => useFormState())
// Start first save
act(() => {
result.current.handleSave()
})
// Try to start second save immediately
await act(async () => {
await result.current.handleSave()
})
// Should only have been called once
expect(updateDatasetSetting).toHaveBeenCalledTimes(1)
})
it('should show error toast on save failure', async () => {
const { updateDatasetSetting } = await import('@/service/datasets')
const Toast = await import('@/app/components/base/toast')
vi.mocked(updateDatasetSetting).mockRejectedValueOnce(new Error('Network error'))
const { result } = renderHook(() => useFormState())
await act(async () => {
await result.current.handleSave()
})
expect(Toast.default.notify).toHaveBeenCalledWith({
type: 'error',
message: expect.any(String),
})
})
it('should include partial_member_list when permission is partialMembers', async () => {
const { updateDatasetSetting } = await import('@/service/datasets')
const { result } = renderHook(() => useFormState())
act(() => {
result.current.setPermission(DatasetPermission.partialMembers)
result.current.setSelectedMemberIDs(['user-1', 'user-2'])
})
await act(async () => {
await result.current.handleSave()
})
expect(updateDatasetSetting).toHaveBeenCalledWith({
datasetId: 'dataset-1',
body: expect.objectContaining({
partial_member_list: expect.arrayContaining([
expect.objectContaining({ user_id: 'user-1' }),
expect.objectContaining({ user_id: 'user-2' }),
]),
}),
})
})
})
describe('Embedding Model', () => {
it('should initialize embedding model from dataset', () => {
const { result } = renderHook(() => useFormState())
expect(result.current.embeddingModel).toEqual({
provider: 'openai',
model: 'text-embedding-ada-002',
})
})
it('should update embedding model when setEmbeddingModel is called', () => {
const { result } = renderHook(() => useFormState())
act(() => {
result.current.setEmbeddingModel({
provider: 'cohere',
model: 'embed-english-v3.0',
})
})
expect(result.current.embeddingModel).toEqual({
provider: 'cohere',
model: 'embed-english-v3.0',
})
})
})
describe('Retrieval Config', () => {
it('should initialize retrieval config from dataset', () => {
const { result } = renderHook(() => useFormState())
expect(result.current.retrievalConfig).toBeDefined()
expect(result.current.retrievalConfig.search_method).toBe(RETRIEVE_METHOD.semantic)
})
it('should update retrieval config when setRetrievalConfig is called', () => {
const { result } = renderHook(() => useFormState())
const newConfig: RetrievalConfig = {
...result.current.retrievalConfig,
reranking_enable: true,
}
act(() => {
result.current.setRetrievalConfig(newConfig)
})
expect(result.current.retrievalConfig.reranking_enable).toBe(true)
})
it('should include weights in save request when weights are set', async () => {
const { updateDatasetSetting } = await import('@/service/datasets')
const { result } = renderHook(() => useFormState())
// Set retrieval config with weights
const configWithWeights: RetrievalConfig = {
...result.current.retrievalConfig,
search_method: RETRIEVE_METHOD.hybrid,
weights: {
weight_type: WeightedScoreEnum.Customized,
vector_setting: {
vector_weight: 0.7,
embedding_provider_name: '',
embedding_model_name: '',
},
keyword_setting: {
keyword_weight: 0.3,
},
},
}
act(() => {
result.current.setRetrievalConfig(configWithWeights)
})
await act(async () => {
await result.current.handleSave()
})
// Verify that weights were included and embedding model info was added
expect(updateDatasetSetting).toHaveBeenCalledWith({
datasetId: 'dataset-1',
body: expect.objectContaining({
retrieval_model: expect.objectContaining({
weights: expect.objectContaining({
vector_setting: expect.objectContaining({
embedding_provider_name: 'openai',
embedding_model_name: 'text-embedding-ada-002',
}),
}),
}),
}),
})
})
})
describe('External Provider', () => {
beforeEach(() => {
// Update mock dataset to be external provider
mockDataset = {
...mockDataset,
provider: 'external',
external_knowledge_info: {
external_knowledge_id: 'ext-123',
external_knowledge_api_id: 'api-456',
external_knowledge_api_name: 'External API',
external_knowledge_api_endpoint: 'https://api.example.com',
},
external_retrieval_model: {
top_k: 5,
score_threshold: 0.8,
score_threshold_enabled: true,
},
}
})
it('should include external knowledge info in save request for external provider', async () => {
const { updateDatasetSetting } = await import('@/service/datasets')
const { result } = renderHook(() => useFormState())
await act(async () => {
await result.current.handleSave()
})
expect(updateDatasetSetting).toHaveBeenCalledWith({
datasetId: 'dataset-1',
body: expect.objectContaining({
external_knowledge_id: 'ext-123',
external_knowledge_api_id: 'api-456',
external_retrieval_model: expect.objectContaining({
top_k: expect.any(Number),
score_threshold: expect.any(Number),
score_threshold_enabled: expect.any(Boolean),
}),
}),
})
})
it('should use correct external retrieval settings', async () => {
const { updateDatasetSetting } = await import('@/service/datasets')
const { result } = renderHook(() => useFormState())
// Update external retrieval settings
act(() => {
result.current.handleSettingsChange({
top_k: 10,
score_threshold: 0.9,
score_threshold_enabled: false,
})
})
await act(async () => {
await result.current.handleSave()
})
expect(updateDatasetSetting).toHaveBeenCalledWith({
datasetId: 'dataset-1',
body: expect.objectContaining({
external_retrieval_model: {
top_k: 10,
score_threshold: 0.9,
score_threshold_enabled: false,
},
}),
})
})
})
})

View File

@ -0,0 +1,264 @@
'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 { useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Toast from '@/app/components/base/toast'
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 { useSelector as useAppContextWithSelector } from '@/context/app-context'
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
import { DatasetPermission } from '@/models/datasets'
import { updateDatasetSetting } from '@/service/datasets'
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
import { useMembers } from '@/service/use-common'
import { checkShowMultiModalTip } from '../../utils'
const DEFAULT_APP_ICON: IconInfo = {
icon_type: 'emoji',
icon: '📙',
icon_background: '#FFF4ED',
icon_url: '',
}
export const useFormState = () => {
const { t } = useTranslation()
const isCurrentWorkspaceDatasetOperator = useAppContextWithSelector(state => state.isCurrentWorkspaceDatasetOperator)
const currentDataset = useDatasetDetailContextWithSelector(state => state.dataset)
const mutateDatasets = useDatasetDetailContextWithSelector(state => state.mutateDatasetRes)
// Basic form state
const [loading, setLoading] = useState(false)
const [name, setName] = useState(currentDataset?.name ?? '')
const [description, setDescription] = useState(currentDataset?.description ?? '')
// Icon state
const [iconInfo, setIconInfo] = useState(currentDataset?.icon_info || DEFAULT_APP_ICON)
const [showAppIconPicker, setShowAppIconPicker] = useState(false)
const previousAppIcon = useRef(DEFAULT_APP_ICON)
// Permission state
const [permission, setPermission] = useState(currentDataset?.permission)
const [selectedMemberIDs, setSelectedMemberIDs] = useState<string[]>(currentDataset?.partial_member_list || [])
// External retrieval state
const [topK, setTopK] = useState(currentDataset?.external_retrieval_model.top_k ?? 2)
const [scoreThreshold, setScoreThreshold] = useState(currentDataset?.external_retrieval_model.score_threshold ?? 0.5)
const [scoreThresholdEnabled, setScoreThresholdEnabled] = useState(currentDataset?.external_retrieval_model.score_threshold_enabled ?? false)
// Indexing and retrieval state
const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique)
const [keywordNumber, setKeywordNumber] = useState(currentDataset?.keyword_number ?? 10)
const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig)
const [embeddingModel, setEmbeddingModel] = useState<DefaultModel>(
currentDataset?.embedding_model
? {
provider: currentDataset.embedding_model_provider,
model: currentDataset.embedding_model,
}
: {
provider: '',
model: '',
},
)
// Summary index state
const [summaryIndexSetting, setSummaryIndexSetting] = useState(currentDataset?.summary_index_setting)
// Model lists
const { data: rerankModelList } = useModelList(ModelTypeEnum.rerank)
const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)
const { data: membersData } = useMembers()
const invalidDatasetList = useInvalidDatasetList()
// Derive member list from API data
const memberList = useMemo<Member[]>(() => {
return membersData?.accounts ?? []
}, [membersData])
// Icon handlers
const handleOpenAppIconPicker = useCallback(() => {
setShowAppIconPicker(true)
previousAppIcon.current = iconInfo
}, [iconInfo])
const handleSelectAppIcon = useCallback((icon: AppIconSelection) => {
const newIconInfo: IconInfo = {
icon_type: icon.type,
icon: icon.type === 'emoji' ? icon.icon : icon.fileId,
icon_background: icon.type === 'emoji' ? icon.background : undefined,
icon_url: icon.type === 'emoji' ? undefined : icon.url,
}
setIconInfo(newIconInfo)
setShowAppIconPicker(false)
}, [])
const handleCloseAppIconPicker = useCallback(() => {
setIconInfo(previousAppIcon.current)
setShowAppIconPicker(false)
}, [])
// External retrieval settings handler
const handleSettingsChange = useCallback((data: { top_k?: number, score_threshold?: number, score_threshold_enabled?: boolean }) => {
if (data.top_k !== undefined)
setTopK(data.top_k)
if (data.score_threshold !== undefined)
setScoreThreshold(data.score_threshold)
if (data.score_threshold_enabled !== undefined)
setScoreThresholdEnabled(data.score_threshold_enabled)
}, [])
// Summary index setting handler
const handleSummaryIndexSettingChange = useCallback((payload: SummaryIndexSettingType) => {
setSummaryIndexSetting(prev => ({ ...prev, ...payload }))
}, [])
// Save handler
const handleSave = async () => {
if (loading)
return
if (!name?.trim()) {
Toast.notify({ type: 'error', message: t('form.nameError', { ns: 'datasetSettings' }) })
return
}
if (!isReRankModelSelected({ rerankModelList, retrievalConfig, indexMethod })) {
Toast.notify({ type: 'error', message: t('datasetConfig.rerankModelRequired', { ns: 'appDebug' }) })
return
}
if (retrievalConfig.weights) {
retrievalConfig.weights.vector_setting.embedding_provider_name = embeddingModel.provider || ''
retrievalConfig.weights.vector_setting.embedding_model_name = embeddingModel.model || ''
}
try {
setLoading(true)
const body: Record<string, unknown> = {
name,
icon_info: iconInfo,
doc_form: currentDataset?.doc_form,
description,
permission,
indexing_technique: indexMethod,
retrieval_model: {
...retrievalConfig,
score_threshold: retrievalConfig.score_threshold_enabled ? retrievalConfig.score_threshold : 0,
},
embedding_model: embeddingModel.model,
embedding_model_provider: embeddingModel.provider,
keyword_number: keywordNumber,
summary_index_setting: summaryIndexSetting,
}
if (currentDataset!.provider === 'external') {
body.external_knowledge_id = currentDataset!.external_knowledge_info.external_knowledge_id
body.external_knowledge_api_id = currentDataset!.external_knowledge_info.external_knowledge_api_id
body.external_retrieval_model = {
top_k: topK,
score_threshold: scoreThreshold,
score_threshold_enabled: scoreThresholdEnabled,
}
}
if (permission === DatasetPermission.partialMembers) {
body.partial_member_list = selectedMemberIDs.map((id) => {
return {
user_id: id,
role: memberList.find(member => member.id === id)?.role,
}
})
}
await updateDatasetSetting({ datasetId: currentDataset!.id, body })
Toast.notify({ type: 'success', message: t('actionMsg.modifiedSuccessfully', { ns: 'common' }) })
if (mutateDatasets) {
await mutateDatasets()
invalidDatasetList()
}
}
catch {
Toast.notify({ type: 'error', message: t('actionMsg.modifiedUnsuccessfully', { ns: 'common' }) })
}
finally {
setLoading(false)
}
}
// Computed values
const showMultiModalTip = useMemo(() => {
return checkShowMultiModalTip({
embeddingModel,
rerankingEnable: retrievalConfig.reranking_enable,
rerankModel: {
rerankingProviderName: retrievalConfig.reranking_model.reranking_provider_name,
rerankingModelName: retrievalConfig.reranking_model.reranking_model_name,
},
indexMethod,
embeddingModelList,
rerankModelList,
})
}, [embeddingModel, rerankModelList, retrievalConfig.reranking_enable, retrievalConfig.reranking_model, embeddingModelList, indexMethod])
return {
// Context values
currentDataset,
isCurrentWorkspaceDatasetOperator,
// Loading state
loading,
// Basic form
name,
setName,
description,
setDescription,
// Icon
iconInfo,
showAppIconPicker,
handleOpenAppIconPicker,
handleSelectAppIcon,
handleCloseAppIconPicker,
// Permission
permission,
setPermission,
selectedMemberIDs,
setSelectedMemberIDs,
memberList,
// External retrieval
topK,
scoreThreshold,
scoreThresholdEnabled,
handleSettingsChange,
// Indexing and retrieval
indexMethod,
setIndexMethod,
keywordNumber,
setKeywordNumber,
retrievalConfig,
setRetrievalConfig,
embeddingModel,
setEmbeddingModel,
embeddingModelList,
// Summary index
summaryIndexSetting,
handleSummaryIndexSettingChange,
// Computed
showMultiModalTip,
// Actions
handleSave,
}
}