From 76d5240fb5eb203c7a8c808e44291d27f97b5ca4 Mon Sep 17 00:00:00 2001 From: Wang Qi Date: Tue, 12 May 2026 19:36:23 +0800 Subject: [PATCH] Fix #14801 to allow search dataset list when add (#14841) ### What problem does this PR solve? Fix #14801 to allow search dataset list when add, following on #14825 image ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --- web/src/components/knowledge-base-item.tsx | 68 ++++++++++++++++++---- web/src/components/ui/multi-select.tsx | 18 +++++- web/src/hooks/use-knowledge-request.ts | 17 +++++- 3 files changed, 88 insertions(+), 15 deletions(-) diff --git a/web/src/components/knowledge-base-item.tsx b/web/src/components/knowledge-base-item.tsx index a161f8036f..c657059375 100644 --- a/web/src/components/knowledge-base-item.tsx +++ b/web/src/components/knowledge-base-item.tsx @@ -2,8 +2,9 @@ import { DocumentParserType } from '@/constants/knowledge'; import { useFetchKnowledgeList } from '@/hooks/use-knowledge-request'; import { IDataset } from '@/interfaces/database/dataset'; import { useBuildQueryVariableOptions } from '@/pages/agent/hooks/use-get-begin-query'; +import { useDebounce } from 'ahooks'; import { toLower } from 'lodash'; -import { useMemo } from 'react'; +import { type ReactNode, useCallback, useMemo, useRef, useState } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { RAGFlowAvatar } from './ragflow-avatar'; @@ -23,17 +24,43 @@ function DatasetLabel({ text }: { text: string }) { } export function useDisableDifferenceEmbeddingDataset(name: string) { - const { list: datasetListOrigin } = useFetchKnowledgeList(true); const form = useFormContext(); const datasetId = useWatch({ name, control: form.control }); + const [searchString, setSearchString] = useState(''); + const debouncedSearchString = useDebounce(searchString, { wait: 500 }); + const { list: datasetListOrigin, loading } = useFetchKnowledgeList( + true, + debouncedSearchString, + ); + const datasetCacheRef = useRef(new Map()); - const selectedEmbedId = useMemo(() => { - const data = datasetListOrigin?.find((item) => item.id === datasetId?.[0]); - return data?.embedding_model ?? ''; + const datasetList = useMemo(() => { + datasetListOrigin.forEach((dataset) => { + datasetCacheRef.current.set(dataset.id, dataset); + }); + + const selectedDatasetIds = Array.isArray(datasetId) ? datasetId : []; + const selectedDatasets = selectedDatasetIds + .map((id) => datasetCacheRef.current.get(id)) + .filter(Boolean) as IDataset[]; + + return Array.from( + new Map( + [...datasetListOrigin, ...selectedDatasets].map((dataset) => [ + dataset.id, + dataset, + ]), + ).values(), + ); }, [datasetId, datasetListOrigin]); + const selectedEmbedId = useMemo(() => { + const data = datasetList?.find((item) => item.id === datasetId?.[0]); + return data?.embedding_model ?? ''; + }, [datasetId, datasetList]); + const nextOptions = useMemo(() => { - const datasetListMap = datasetListOrigin + const datasetListMap = datasetList .filter((x) => x.chunk_method !== DocumentParserType.Tag) .map((item: IDataset) => { return { @@ -58,10 +85,17 @@ export function useDisableDifferenceEmbeddingDataset(name: string) { }); return datasetListMap; - }, [datasetListOrigin, selectedEmbedId]); + }, [datasetList, selectedEmbedId]); + + const handleSearchChange = useCallback((value: string) => { + setSearchString(value); + }, []); return { datasetOptions: nextOptions, + handleSearchChange, + loading, + searchString, }; } @@ -76,7 +110,8 @@ export function KnowledgeBaseFormField({ }) { const { t } = useTranslation(); - const { datasetOptions } = useDisableDifferenceEmbeddingDataset(name); + const { datasetOptions, handleSearchChange, loading, searchString } = + useDisableDifferenceEmbeddingDataset(name); const nextOptions = buildQueryVariableOptionsByShowVariable(showVariable)(); @@ -89,17 +124,26 @@ export function KnowledgeBaseFormField({ options: knowledgeOptions, }, ...nextOptions.map((x) => { + const groupLabel = (('label' in x + ? x.label + : 'title' in x + ? x.title + : '') ?? '') as ReactNode; + return { ...x, + label: groupLabel, options: x.options .filter((y) => toLower(y.type).includes('string')) .map((x) => ({ ...x, + label: x.label ?? x.value ?? '', + value: x.value ?? '', icon: () => ( ), })), @@ -130,6 +174,10 @@ export function KnowledgeBaseFormField({ showSelectAll={false} popoverTestId="datasets-options" optionTestIdPrefix="datasets" + searchValue={searchString} + onSearchChange={handleSearchChange} + isSearching={loading} + shouldFilter={false} {...field} /> )} diff --git a/web/src/components/ui/multi-select.tsx b/web/src/components/ui/multi-select.tsx index 287ec26e43..200df2a42d 100644 --- a/web/src/components/ui/multi-select.tsx +++ b/web/src/components/ui/multi-select.tsx @@ -188,6 +188,10 @@ interface MultiSelectProps showSelectAll?: boolean; popoverTestId?: string; optionTestIdPrefix?: string; + searchValue?: string; + onSearchChange?: (value: string) => void; + isSearching?: boolean; + shouldFilter?: boolean; } export const MultiSelect = React.forwardRef< @@ -209,6 +213,10 @@ export const MultiSelect = React.forwardRef< showSelectAll = true, popoverTestId, optionTestIdPrefix, + searchValue, + onSearchChange, + isSearching = false, + shouldFilter, ...props }, ref, @@ -434,15 +442,19 @@ export const MultiSelect = React.forwardRef< onEscapeKeyDown={() => setIsPopoverOpen(false)} data-testid={popoverTestId} > - - {options && options.length > 0 && ( + + {((options && options.length > 0) || onSearchChange) && ( )} - No results found. + + {isSearching ? t('common.searching') : t('common.noDataFound')} + {showSelectAll && options && options.length > 0 && ( { export const useFetchKnowledgeList = ( shouldFilterListWithoutDocument: boolean = false, + keywords = '', ): { list: IDataset[]; loading: boolean; } => { const { data, isFetching: loading } = useQuery({ - queryKey: [KnowledgeApiAction.FetchKnowledgeList], + queryKey: [ + KnowledgeApiAction.FetchKnowledgeList, + shouldFilterListWithoutDocument, + keywords, + ], initialData: [], gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3 queryFn: async () => { - const { data } = await listDataset(); + const { data } = await listDataset( + keywords + ? { + ext: { + keywords, + }, + } + : undefined, + ); const list = data?.data ?? []; return shouldFilterListWithoutDocument ? list.filter((x: IDataset) => x.chunk_count > 0)