From 9c53b3336adddd6c0b73e0a15e41135714983b97 Mon Sep 17 00:00:00 2001 From: balibabu Date: Mon, 13 Oct 2025 14:37:30 +0800 Subject: [PATCH] Fix: The Context Generator(Transformer) node can only be followed by a Tokenizer(Indexer) and a Context Generator(Transformer). #9869 (#10515) ### What problem does this PR solve? Fix: The Context Generator node can only be followed by a Tokenizer and a Context Generator. #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- .../components/file-upload-dialog/index.tsx | 2 +- web/src/locales/en.ts | 6 ++-- web/src/pages/data-flow/canvas/index.tsx | 1 + .../node/dropdown/next-step-dropdown.tsx | 28 +++++++++++++------ .../pages/data-flow/canvas/node/handle.tsx | 1 + web/src/pages/data-flow/constant.tsx | 4 ++- .../data-flow/form/extractor-form/index.tsx | 5 ++++ 7 files changed, 34 insertions(+), 13 deletions(-) diff --git a/web/src/components/file-upload-dialog/index.tsx b/web/src/components/file-upload-dialog/index.tsx index 162f6b8d1..aea32b472 100644 --- a/web/src/components/file-upload-dialog/index.tsx +++ b/web/src/components/file-upload-dialog/index.tsx @@ -98,7 +98,7 @@ export function FileUploadDialog({ return ( - + {t('fileManager.uploadFile')} diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index d112d0891..40c04928b 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1703,8 +1703,8 @@ This delimiter is used to split the input text into several text pieces echo of parser: 'Parser', parserDescription: 'Extracts raw text and structure from files for downstream processing.', - tokenizer: 'Tokenizer', - tokenizerRequired: 'Please add the Tokenizer node first', + tokenizer: 'Indexer', + tokenizerRequired: 'Please add the Indexer node first', tokenizerDescription: 'Transforms text into the required data structure (e.g., vector embeddings for Embedding Search) depending on the chosen search method.', splitter: 'Token Splitter', @@ -1713,7 +1713,7 @@ This delimiter is used to split the input text into several text pieces echo of hierarchicalMergerDescription: 'Split documents into sections by title hierarchy with regex rules for finer control.', hierarchicalMerger: 'Title Splitter', - extractor: 'Context Generator', + extractor: 'Transformer', extractorDescription: 'Use an LLM to extract structured insights from document chunks—such as summaries, classifications, etc.', outputFormat: 'Output format', diff --git a/web/src/pages/data-flow/canvas/index.tsx b/web/src/pages/data-flow/canvas/index.tsx index fa1bb341c..8672a2074 100644 --- a/web/src/pages/data-flow/canvas/index.tsx +++ b/web/src/pages/data-flow/canvas/index.tsx @@ -292,6 +292,7 @@ function DataFlowCanvas({ drawerVisible, hideDrawer, showLogSheet }: IProps) { clearActiveDropdown(); }} position={dropdownPosition} + nodeId={connectionStartRef.current?.nodeId || ''} > diff --git a/web/src/pages/data-flow/canvas/node/dropdown/next-step-dropdown.tsx b/web/src/pages/data-flow/canvas/node/dropdown/next-step-dropdown.tsx index 6beadb7e8..2450de579 100644 --- a/web/src/pages/data-flow/canvas/node/dropdown/next-step-dropdown.tsx +++ b/web/src/pages/data-flow/canvas/node/dropdown/next-step-dropdown.tsx @@ -131,16 +131,24 @@ function useRestrictSingleOperatorOnCanvas() { function AccordionOperators({ isCustomDropdown = false, mousePosition, + nodeId, }: { isCustomDropdown?: boolean; mousePosition?: { x: number; y: number }; + nodeId?: string; }) { const singleOperators = useRestrictSingleOperatorOnCanvas(); + const { getOperatorTypeFromId } = useGraphStore((state) => state); + const operators = useMemo(() => { - const list = [...singleOperators]; + let list = [...singleOperators]; + if (getOperatorTypeFromId(nodeId) === Operator.Extractor) { + const Splitters = [Operator.HierarchicalMerger, Operator.Splitter]; + list = list.filter((x) => !Splitters.includes(x)); // The Context Generator node can only be followed by a Tokenizer and a Context Generator. + } list.push(Operator.Extractor); return list; - }, [singleOperators]); + }, [getOperatorTypeFromId, nodeId, singleOperators]); return ( & { + position?: { x: number; y: number }; + onNodeCreated?: (newNodeId: string) => void; + nodeId?: string; + }; export function InnerNextStepDropdown({ children, hideModal, position, onNodeCreated, -}: PropsWithChildren & - IModalProps & { - position?: { x: number; y: number }; - onNodeCreated?: (newNodeId: string) => void; - }) { + nodeId, +}: NextStepDropdownProps) { const dropdownRef = useRef(null); useEffect(() => { @@ -200,6 +211,7 @@ export function InnerNextStepDropdown({ @@ -224,7 +236,7 @@ export function InnerNextStepDropdown({ > {t('flow.nextStep')} - + diff --git a/web/src/pages/data-flow/canvas/node/handle.tsx b/web/src/pages/data-flow/canvas/node/handle.tsx index da61e3b4c..17fe28390 100644 --- a/web/src/pages/data-flow/canvas/node/handle.tsx +++ b/web/src/pages/data-flow/canvas/node/handle.tsx @@ -61,6 +61,7 @@ export function CommonHandle({ hideModal(); clearActiveDropdown(); }} + nodeId={nodeId} > diff --git a/web/src/pages/data-flow/constant.tsx b/web/src/pages/data-flow/constant.tsx index 600febef9..8ebcc671c 100644 --- a/web/src/pages/data-flow/constant.tsx +++ b/web/src/pages/data-flow/constant.tsx @@ -299,7 +299,9 @@ export const initialHierarchicalMergerValues = { export const initialExtractorValues = { ...initialLlmBaseValues, field_name: ContextGeneratorFieldName.Summary, - outputs: {}, + outputs: { + chunks: { type: 'Array', value: [] }, + }, }; export const CategorizeAnchorPointPositions = [ diff --git a/web/src/pages/data-flow/form/extractor-form/index.tsx b/web/src/pages/data-flow/form/extractor-form/index.tsx index cb0abc877..ddc0b8bdd 100644 --- a/web/src/pages/data-flow/form/extractor-form/index.tsx +++ b/web/src/pages/data-flow/form/extractor-form/index.tsx @@ -19,7 +19,9 @@ import { useBuildNodeOutputOptions } from '../../hooks/use-build-options'; import { useFormValues } from '../../hooks/use-form-values'; import { useWatchFormChange } from '../../hooks/use-watch-form-change'; import { INextOperatorForm } from '../../interface'; +import { buildOutputList } from '../../utils/build-output-list'; import { FormWrapper } from '../components/form-wrapper'; +import { Output } from '../components/output'; import { useSwitchPrompt } from './use-switch-prompt'; export const FormSchema = z.object({ @@ -31,6 +33,8 @@ export const FormSchema = z.object({ export type ExtractorFormSchemaType = z.infer; +const outputList = buildOutputList(initialExtractorValues.outputs); + const ExtractorForm = ({ node }: INextOperatorForm) => { const defaultValues = useFormValues(initialExtractorValues, node); const { t } = useTranslation(); @@ -85,6 +89,7 @@ const ExtractorForm = ({ node }: INextOperatorForm) => { baseOptions={promptOptions} > + {visible && (