Fix: The form related to switching the dataset pipeline configuration was not rendered. (#17124)

This commit is contained in:
balibabu
2026-07-21 10:12:41 +08:00
committed by GitHub
parent 178b40cb25
commit c098c4c296
7 changed files with 124 additions and 22 deletions

View File

@@ -33,6 +33,7 @@ export interface IDataset {
nickname: string;
pagerank: number;
parser_config: Parserconfig;
parser_id?: string;
permission: string;
pipeline_id: string;
raptor_task_finish_at: string;

View File

@@ -54,6 +54,7 @@ export default function Compilation() {
>
{t('knowledgeDetails.llmWiki')}
</Button>
<Button
variant={viewMode === ViewMode.Skills ? 'default' : 'outline'}
size="sm"

View File

@@ -15,6 +15,7 @@ import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { UseFormReturn } from 'react-hook-form';
@@ -22,6 +23,7 @@ import { useParams, useSearchParams } from 'react-router';
import { z } from 'zod';
import { formSchema } from './form-schema';
import {
buildParserConfigFromNodes,
buildPipelineOperatorNodes,
getOperatorType,
transformApiConfigToForm,
@@ -140,6 +142,55 @@ export const usePipelineOperatorNodes = (
return { operatorNodes, loading };
};
/**
* Resets parser_config when the selected pipeline changes, so stale configs
* from the previous pipeline are never submitted. Once the new pipeline's
* DSL has loaded, parser_config is seeded with the DSL defaults (i.e. what
* the operator tabs display).
*
* The very first pipeline seen is treated as the initial load: the form was
* already reset with the saved parser_config in useFetchDatasetSettingOnMount,
* so it is left untouched.
*/
export const useResetParserConfigOnPipelineChange = (
form: UseFormReturn<z.infer<typeof formSchema>>,
pipelineId: string | undefined,
savedPipelineId: string | undefined,
operatorNodes: RAGFlowNodeType[],
) => {
const previousPipelineIdRef = useRef<string>();
useEffect(() => {
if (!pipelineId) {
// Selection cleared (e.g. parse type switched): drop configs that
// belonged to the previously selected pipeline.
if (previousPipelineIdRef.current) {
previousPipelineIdRef.current = '';
form.setValue('parser_config', {});
}
return;
}
const isInitialLoad =
previousPipelineIdRef.current === undefined &&
pipelineId === savedPipelineId;
if (isInitialLoad || previousPipelineIdRef.current === pipelineId) {
previousPipelineIdRef.current = pipelineId;
return;
}
if (operatorNodes.length === 0) {
// The new pipeline's DSL is still loading — clear the previous
// pipeline's configs right away; defaults are seeded once it arrives.
form.setValue('parser_config', {});
return;
}
previousPipelineIdRef.current = pipelineId;
form.setValue('parser_config', buildParserConfigFromNodes(operatorNodes));
}, [form, pipelineId, savedPipelineId, operatorNodes]);
};
export const useSaveDatasetSetting = () => {
const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge();

View File

@@ -31,6 +31,7 @@ import {
useFetchDatasetSettingOnMount,
usePipelineDataList,
usePipelineOperatorNodes,
useResetParserConfigOnPipelineChange,
useSaveDatasetSetting,
} from './hooks';
import PipelineOperatorTabs from './pipeline-operator-tabs';
@@ -92,10 +93,35 @@ export default function DatasetSetting() {
| Record<string, any>
| undefined;
const { operatorNodes } = usePipelineOperatorNodes(
parseType === ParseType.Pipeline ? pipelineId : builtinPipelineId,
pipelineParserConfig,
parseType === ParseType.BuiltIn,
const isPipelineMode = parseType === ParseType.Pipeline;
const selectedPipelineId = isPipelineMode ? pipelineId : builtinPipelineId;
// The saved parser_config only belongs to the pipeline (and parse type) it
// was saved with — expose its id only while that exact pipeline is selected,
// so that after switching, defaults come purely from the new pipeline's DSL.
const savedParseType = knowledgeDetails?.pipeline_id
? ParseType.Pipeline
: ParseType.BuiltIn;
const savedPipelineId =
parseType === savedParseType
? isPipelineMode
? knowledgeDetails?.pipeline_id
: knowledgeDetails?.parser_id
: undefined;
const { operatorNodes, loading: operatorNodesLoading } =
usePipelineOperatorNodes(
selectedPipelineId,
selectedPipelineId && selectedPipelineId === savedPipelineId
? pipelineParserConfig
: undefined,
!isPipelineMode,
);
useResetParserConfigOnPipelineChange(
form,
selectedPipelineId,
savedPipelineId,
operatorNodes,
);
const { activeTab, setActiveTab } = useActiveTab(operatorNodes);
@@ -211,7 +237,11 @@ export default function DatasetSetting() {
<ButtonLoading
type="submit"
loading={datasetSettingLoading || saveLoading}
loading={
datasetSettingLoading ||
saveLoading ||
(operatorNodesLoading && operatorNodes.length === 0)
}
>
{t('knowledgeConfiguration.save')}
</ButtonLoading>

View File

@@ -13,7 +13,7 @@ import {
transformTitleChunkerParams,
transformTokenChunkerParams,
} from '@/pages/agent/utils';
import { cloneDeep } from 'lodash';
import { cloneDeep, isEmpty } from 'lodash';
export const FileNodeId = 'File';
@@ -278,24 +278,21 @@ export function buildOperatorNode(
const operatorId = dslNode.id;
const operatorType = getOperatorType(operatorId);
// DSL-format config from API's parser_config
const configFromApi = operatorId
? pipelineParserConfig[operatorId]
: undefined;
// Form-format config from DSL graph
const configFromDsl = dslNode.data?.form;
// Convert API config to form format, then merge (DSL template is baseline, API overrides)
const convertedApiConfig = transformApiConfigToForm(
operatorType,
configFromApi,
);
const rawForm = {
...configFromDsl, // template defaults from DSL (form format)
...convertedApiConfig, // user overrides from API (now also form format)
};
if (!isEmpty(pipelineParserConfig)) {
// user overrides from API (now also form format)
Object.assign(
rawForm,
transformApiConfigToForm(operatorType, pipelineParserConfig[operatorId]), // Convert API config to form format, then merge (DSL template is baseline, API overrides)
);
}
return {
...dslNode,
id: '',
@@ -343,3 +340,19 @@ export function buildPipelineOperatorNodes(
.filter((node): node is RAGFlowNodeType => node !== undefined)
.map((node) => buildOperatorNode(node, pipelineParserConfig));
}
/**
* Builds a parser_config record from operator nodes, keyed by operatorId.
* Each entry is the node's merged form (DSL template defaults + overrides),
* i.e. exactly what the operator tabs display.
*/
export function buildParserConfigFromNodes(
operatorNodes: RAGFlowNodeType[],
): Record<string, any> {
return Object.fromEntries(
operatorNodes.map((node) => [
(node.data as Record<string, any>)?.operatorId || node.data?.label || '',
node.data?.form ?? {},
]),
);
}

View File

@@ -29,17 +29,18 @@ import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
BuiltinPipelineItem,
ChunkMethodItem,
EmbeddingModelItem,
ParseTypeItem,
} from '../dataset/dataset-setting/configuration/common-item';
import { isGoBackend } from '@/utils/backend-runtime';
const FormId = 'dataset-creating-form';
const ChunkMethodName = 'parser_id';
export function InputForm({ onOk }: IModalProps<any>) {
const { t } = useTranslation();
const defaultModelDictionary = useFetchDefaultModelDictionary();
const ChunkMethodName = isGoBackend() ? 'parser_id' : 'chunk_method';
const FormSchema = z
.object({
@@ -141,9 +142,12 @@ export function InputForm({ onOk }: IModalProps<any>) {
<EmbeddingModelItem line={2} isEdit={false} />
<ParseTypeItem />
{parseType === ParseType.BuiltIn && (
<BuiltinPipelineItem name={ChunkMethodName} />
)}
{parseType === ParseType.BuiltIn &&
(isGoBackend() ? (
<BuiltinPipelineItem name={ChunkMethodName} />
) : (
<ChunkMethodItem name={ChunkMethodName}></ChunkMethodItem>
))}
{parseType === ParseType.Pipeline && (
<DataFlowSelect
isMult={false}

View File

@@ -32,6 +32,8 @@ export const fetchBackendLanguage = (): Promise<string> => promise;
export const getBackendLanguage = (): string | null => backendLanguage;
export const isGoBackend = (): boolean => backendLanguage === 'go';
export const subscribeBackendLanguage = (listener: Listener): (() => void) => {
listeners.add(listener);
return () => {