Fix: Clearing the files of the dataset selected in the chat should result in an error message appearing on the chat page. (#18510)

This commit is contained in:
balibabu
2026-08-19 16:55:47 +08:00
committed by GitHub
parent 9e913c3fb0
commit 2c6f838566
7 changed files with 119 additions and 41 deletions

View File

@@ -104,38 +104,43 @@ export function useDisableDifferenceEmbeddingDataset(name: string) {
}, [datasetId, datasetList]);
const nextOptions = useMemo(() => {
const datasetListMap = datasetList.map((item: IDataset) => {
return {
label: item.name,
icon: () => (
<RAGFlowAvatar
className="size-4"
avatar={item.avatar}
name={item.name}
/>
),
suffix: (
<section className="flex gap-2">
<DatasetLabel text={item.nickname} />
<DatasetLabel
text={
item.embedding_model_name
? item.embedding_model_name
: item.embedding_model
}
/>
</section>
),
value: item.id,
disabled:
item.chunk_count <= 0 ||
item.chunk_method === DocumentParserType.Tag ||
(selectedEmbedBaseName !== '' &&
getEmbeddingBaseName(item.embedding_model) !== selectedEmbedBaseName),
};
});
return datasetListMap;
return (
datasetList
// Datasets without chunks are not selectable. A stale selected value
// (emptied or deleted dataset) is excluded as well — the MultiSelect
// badge falls back to rendering its raw id and stays removable.
.filter((item) => item.chunk_count > 0)
.map((item: IDataset) => {
return {
label: item.name,
icon: () => (
<RAGFlowAvatar
className="size-4"
avatar={item.avatar}
name={item.name}
/>
),
suffix: (
<section className="flex gap-2">
<DatasetLabel text={item.nickname} />
<DatasetLabel
text={
item.embedding_model_name
? item.embedding_model_name
: item.embedding_model
}
/>
</section>
),
value: item.id,
disabled:
item.chunk_method === DocumentParserType.Tag ||
(selectedEmbedBaseName !== '' &&
getEmbeddingBaseName(item.embedding_model) !==
selectedEmbedBaseName),
};
})
);
}, [datasetList, selectedEmbedBaseName]);
const handleSearchChange = useCallback((value: string) => {

View File

@@ -5,6 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import {
CheckIcon,
ChevronDown,
TriangleAlert,
WandSparkles,
XCircle,
XIcon,
@@ -377,8 +378,20 @@ export const MultiSelect = React.forwardRef<
{IconComponent && (
<IconComponent className="h-4 w-4" />
)}
<div className="max-w-28 text-ellipsis overflow-hidden">
{option?.label}
{/* A selected value with no matching option (e.g. the
entity no longer exists) gets a warning marker and
falls back to the raw value so the badge stays
readable and removable. */}
{!option && (
<TriangleAlert className="h-4 w-4 flex-shrink-0 text-text-disabled" />
)}
<div
className={cn(
'max-w-28 text-ellipsis overflow-hidden',
{ 'text-text-disabled': !option },
)}
>
{option?.label ?? value}
</div>
{canRemoveValue(value) && (
<XCircle

View File

@@ -1145,9 +1145,9 @@ export const useSelectKnowledgeOptions = () => {
};
/**
* Fetch datasets by a set of IDs. Used to resolve the names of
* already-selected datasets that are not present in the first page of
* the paginated list so they can be echoed back in the form field.
* Fetch datasets by a set of IDs. Used to resolve already-selected datasets
* that are not present in the first page of the paginated list, e.g. to echo
* their names in a form field. For staleness checks see `useStaleDatasetIds`.
*/
export const useFetchDatasetsByIds = (ids: string[]) => {
const sortedIds = useMemo(() => [...ids].sort(), [ids]);
@@ -1164,6 +1164,29 @@ export const useFetchDatasetsByIds = (ids: string[]) => {
return { data, loading };
};
/**
* Resolve which of the given persisted dataset ids have gone stale — the
* dataset no longer exists or has been emptied of chunks. The set stays
* empty while the lookup is in flight so consumers can hold off validation
* until it settles; `settled` flips true once the lookup has finished.
*/
export const useStaleDatasetIds = (datasetIds?: string[]) => {
const persistedIds = useMemo(() => datasetIds ?? [], [datasetIds]);
const { data: datasets, loading } = useFetchDatasetsByIds(persistedIds);
const staleDatasetIds = useMemo(() => {
if (loading) {
return new Set<string>();
}
const usableIds = new Set(
(datasets ?? []).filter((x) => x.chunk_count > 0).map((x) => x.id),
);
return new Set(persistedIds.filter((id) => !usableIds.has(id)));
}, [datasets, loading, persistedIds]);
return { staleDatasetIds, settled: !loading };
};
//#region tags
export const useRenameTag = () => {
const knowledgeBaseId = useKnowledgeBaseId();

View File

@@ -1040,6 +1040,8 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
knowledgeBases: 'Datasets',
knowledgeBasesPlaceholder: 'Select value',
knowledgeBasesMessage: 'Please select',
datasetUnavailable:
'The selected knowledge base is unavailable (deleted or has no chunks), please re-select',
knowledgeBasesTip:
'Select the datasets to associate with this chat assistant. An empty dataset will not appear in the dropdown list.',
system: 'System prompt',

View File

@@ -938,6 +938,7 @@ NER使用 spaCy NER 和基于规则的关键词提取来抽取 Entities 和 R
knowledgeBases: '知识库',
knowledgeBasesPlaceholder: '请选择',
knowledgeBasesMessage: '请选择',
datasetUnavailable: '所选知识库不可用(已删除或无 chunk请重新选择',
knowledgeBasesTip:
'选择关联的知识库。新建或空知识库不会在下拉菜单中显示。',
system: '系统提示词',

View File

@@ -4,6 +4,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { DatasetMetadata } from '@/constants/chat';
import { useSetModalState } from '@/hooks/common-hooks';
import { useFetchChat, useUpdateChat } from '@/hooks/use-chat-request';
import { useStaleDatasetIds } from '@/hooks/use-knowledge-request';
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
import { cn } from '@/lib/utils';
import {
@@ -14,7 +15,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { isEmpty, omit } from 'lodash';
import { LucidePanelRightClose, LucideSettings } from 'lucide-react';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router';
import { z } from 'zod';
@@ -27,8 +28,16 @@ import { getWebSearchProvider } from '../web-search-api-key';
type ChatSettingsProps = { hasSingleChatBox: boolean };
export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
const formSchema = useChatSettingSchema();
const { data } = useFetchChat();
// Only the persisted ids need validation: ids picked from the dataset
// select are valid by construction, while a persisted id may reference a
// dataset that has since been deleted or emptied of chunks.
const { staleDatasetIds, settled: datasetsFetched } = useStaleDatasetIds(
data?.dataset_ids,
);
const formSchema = useChatSettingSchema(staleDatasetIds);
const { updateChat, loading } = useUpdateChat();
const findLlmByUuid = useFindLlmByUuid();
const { id } = useParams();
@@ -42,6 +51,7 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
const form = useForm<FormSchemaType>({
resolver: zodResolver(formSchema),
shouldUnregister: false,
mode: 'onChange',
defaultValues: {
name: '',
icon: '',
@@ -145,6 +155,20 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
}
}, [data, form]);
const datasetIds = useWatch({ control: form.control, name: 'dataset_ids' });
const trigger = form.trigger;
// A persisted dataset_ids value never fires onChange validation, so once
// the lookup of those ids has settled, revalidate explicitly — it may
// reference datasets that have since been deleted or emptied of chunks.
useEffect(() => {
if (!datasetsFetched || !datasetIds?.length) {
return;
}
trigger('dataset_ids');
}, [trigger, datasetsFetched, datasetIds.length]);
return (
<>
{settingVisible || (

View File

@@ -13,7 +13,7 @@ import { WebSearchProvider } from '@/constants/chat';
import { useTranslate } from '@/hooks/common-hooks';
import { z } from 'zod';
export function useChatSettingSchema() {
export function useChatSettingSchema(staleDatasetIds: Set<string>) {
const { t } = useTranslate('chat');
const promptConfigSchema = z.object({
@@ -65,5 +65,15 @@ export function useChatSettingSchema() {
...MetadataFilterSchema,
});
return formSchema;
// A persisted dataset_ids value may reference datasets that have since been
// deleted or emptied of chunks — those stale ids are flagged here.
return formSchema.superRefine((data, ctx) => {
if (data.dataset_ids.some((id) => staleDatasetIds.has(id))) {
ctx.addIssue({
path: ['dataset_ids'],
message: t('datasetUnavailable'),
code: z.ZodIssueCode.custom,
});
}
});
}