Feat:new component list operations (#11276)

### What problem does this PR solve?
issue:
https://github.com/infiniflow/ragflow/issues/10427
change:
new component list operations

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
buua436
2025-11-14 16:33:20 +08:00
committed by GitHub
parent 12db62b9c7
commit db4fd19c82
13 changed files with 384 additions and 2 deletions

View File

@ -109,6 +109,7 @@ export enum Operator {
SearXNG = 'SearXNG',
Placeholder = 'Placeholder',
DataOperations = 'DataOperations',
ListOperations = 'ListOperations',
VariableAssigner = 'VariableAssigner',
VariableAggregator = 'VariableAggregator',
File = 'File', // pipeline

View File

@ -1591,6 +1591,8 @@ This delimiter is used to split the input text into several text pieces echo of
codeDescription: 'It allows developers to write custom Python logic.',
dataOperations: 'Data operations',
dataOperationsDescription: 'Perform various operations on a Data object.',
listOperations: 'List operations',
listOperationsDescription: 'Perform operations on a list.',
variableAssigner: 'Variable assigner',
variableAssignerDescription:
'This component performs operations on Data objects, including extracting, filtering, and editing keys and values in the Data.',
@ -1806,6 +1808,19 @@ Important structured information may include: names, dates, locations, events, k
removeKeys: 'Remove keys',
renameKeys: 'Rename keys',
},
ListOperationsOptions: {
topN: 'Top N',
head: 'Head',
tail: 'Tail',
sort: 'Sort',
filter: 'Filter',
dropDuplicates: 'Drop duplicates',
},
sortMethod: 'Sort method',
SortMethodOptions: {
asc: 'Ascending',
desc: 'Descending',
},
},
llmTools: {
bad_calculator: {

View File

@ -1508,6 +1508,8 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
codeDescription: '它允许开发人员编写自定义 Python 逻辑。',
dataOperations: '数据操作',
dataOperationsDescription: '对数据对象执行各种操作。',
listOperations: '列表操作',
listOperationsDescription: '对列表对象执行各种操作。',
variableAssigner: '变量赋值器',
variableAssignerDescription:
'此组件对数据对象执行操作,包括提取、筛选和编辑数据中的键和值。',
@ -1679,6 +1681,19 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
removeKeys: '删除键',
renameKeys: '重命名键',
},
ListOperationsOptions: {
topN: '取前N项',
head: '取前第N项',
tail: '取后第N项',
sort: '排序',
filter: '筛选',
dropDuplicates: '去重',
},
sortMethod: '排序方式',
SortMethodOptions: {
asc: '升序',
desc: '降序',
},
},
footer: {
profile: 'All rights reserved @ React',

View File

@ -61,6 +61,7 @@ import { FileNode } from './node/file-node';
import { InvokeNode } from './node/invoke-node';
import { IterationNode, IterationStartNode } from './node/iteration-node';
import { KeywordNode } from './node/keyword-node';
import { ListOperationsNode } from './node/list-operations-node';
import { MessageNode } from './node/message-node';
import NoteNode from './node/note-node';
import ParserNode from './node/parser-node';
@ -101,6 +102,7 @@ export const nodeTypes: NodeTypes = {
splitterNode: SplitterNode,
contextNode: ExtractorNode,
dataOperationsNode: DataOperationsNode,
listOperationsNode: ListOperationsNode,
variableAssignerNode: VariableAssignerNode,
variableAggregatorNode: VariableAggregatorNode,
};

View File

@ -79,6 +79,7 @@ export function AccordionOperators({
Operator.Code,
Operator.StringTransform,
Operator.DataOperations,
Operator.ListOperations,
// Operator.VariableAssigner,
Operator.VariableAggregator,
]}

View File

@ -0,0 +1,22 @@
import { BaseNode } from '@/interfaces/database/agent';
import { NodeProps } from '@xyflow/react';
import { camelCase } from 'lodash';
import { useTranslation } from 'react-i18next';
import { RagNode } from '.';
import { ListOperationsFormSchemaType } from '../../form/list-operations-form';
import { LabelCard } from './card';
export function ListOperationsNode({
...props
}: NodeProps<BaseNode<ListOperationsFormSchemaType>>) {
const { data } = props;
const { t } = useTranslation();
return (
<RagNode {...props}>
<LabelCard>
{t(`flow.ListOperationsOptions.${camelCase(data.form?.operations)}`)}
</LabelCard>
</RagNode>
);
}

View File

@ -595,6 +595,35 @@ export const initialDataOperationsValues = {
},
},
};
export enum SortMethod {
Asc = 'asc',
Desc = 'desc',
}
export enum ListOperations {
TopN = 'topN',
Head = 'head',
Tail = 'tail',
Filter = 'filter',
Sort = 'sort',
DropDuplicates = 'drop_duplicates',
}
export const initialListOperationsValues = {
query: '',
operations: ListOperations.TopN,
outputs: {
result: {
type: 'Array<?>',
},
first: {
type: '?',
},
last: {
type: '?',
},
},
};
export const initialVariableAssignerValues = {};
@ -673,6 +702,7 @@ export const RestrictedUpstreamMap = {
[Operator.Tool]: [Operator.Begin],
[Operator.Placeholder]: [Operator.Begin],
[Operator.DataOperations]: [Operator.Begin],
[Operator.ListOperations]: [Operator.Begin],
[Operator.Parser]: [Operator.Begin], // pipeline
[Operator.Splitter]: [Operator.Begin],
[Operator.HierarchicalMerger]: [Operator.Begin],
@ -729,6 +759,7 @@ export const NodeMap = {
[Operator.HierarchicalMerger]: 'splitterNode',
[Operator.Extractor]: 'contextNode',
[Operator.DataOperations]: 'dataOperationsNode',
[Operator.ListOperations]: 'listOperationsNode',
[Operator.VariableAssigner]: 'variableAssignerNode',
[Operator.VariableAggregator]: 'variableAggregatorNode',
};

View File

@ -21,6 +21,7 @@ import IterationForm from '../form/iteration-form';
import IterationStartForm from '../form/iteration-start-from';
import Jin10Form from '../form/jin10-form';
import KeywordExtractForm from '../form/keyword-extract-form';
import ListOperationsForm from '../form/list-operations-form';
import MessageForm from '../form/message-form';
import ParserForm from '../form/parser-form';
import PubMedForm from '../form/pubmed-form';
@ -184,6 +185,9 @@ export const FormConfigMap = {
[Operator.DataOperations]: {
component: DataOperationsForm,
},
[Operator.ListOperations]: {
component: ListOperationsForm,
},
[Operator.VariableAssigner]: {
component: VariableAssignerForm,
},

View File

@ -0,0 +1,140 @@
import NumberInput from '@/components/originui/number-input';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Separator } from '@/components/ui/separator';
import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options';
import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
DataOperationsOperatorOptions,
JsonSchemaDataType,
ListOperations,
SortMethod,
initialListOperationsValues,
} from '../../constant';
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, OutputSchema } from '../components/output';
import { PromptEditor } from '../components/prompt-editor';
import { QueryVariable } from '../components/query-variable';
export const RetrievalPartialSchema = {
query: z.string(),
operations: z.string(),
n: z.number().int().min(0).optional(),
sort_method: z.string().optional(),
filter: z
.object({
value: z.string().optional(),
operator: z.string().optional(),
})
.optional(),
...OutputSchema,
};
export const FormSchema = z.object(RetrievalPartialSchema);
export type ListOperationsFormSchemaType = z.infer<typeof FormSchema>;
const outputList = buildOutputList(initialListOperationsValues.outputs);
function ListOperationsForm({ node }: INextOperatorForm) {
const { t } = useTranslation();
const defaultValues = useFormValues(initialListOperationsValues, node);
const form = useForm<ListOperationsFormSchemaType>({
defaultValues: defaultValues,
mode: 'onChange',
resolver: zodResolver(FormSchema),
shouldUnregister: true,
});
const operations = useWatch({ control: form.control, name: 'operations' });
const ListOperationsOptions = buildOptions(
ListOperations,
t,
`flow.ListOperationsOptions`,
true,
);
const SortMethodOptions = buildOptions(
SortMethod,
t,
`flow.SortMethodOptions`,
true,
);
const operatorOptions = useBuildSwitchOperatorOptions(
DataOperationsOperatorOptions,
);
useWatchFormChange(node?.id, form, true);
return (
<Form {...form}>
<FormWrapper>
<QueryVariable
name="query"
className="flex-1"
types={[JsonSchemaDataType.Array]}
></QueryVariable>
<Separator />
<RAGFlowFormItem name="operations" label={t('flow.operations')}>
<SelectWithSearch options={ListOperationsOptions} />
</RAGFlowFormItem>
{[
ListOperations.TopN,
ListOperations.Head,
ListOperations.Tail,
].includes(operations as ListOperations) && (
<FormField
control={form.control}
name="n"
render={({ field }) => (
<FormItem>
<FormLabel>{t('flowNum')}</FormLabel>
<FormControl>
<NumberInput {...field} className="w-full"></NumberInput>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{[ListOperations.Sort].includes(operations as ListOperations) && (
<RAGFlowFormItem name="sort_method" label={t('flow.sortMethod')}>
<SelectWithSearch options={SortMethodOptions} />
</RAGFlowFormItem>
)}
{[ListOperations.Filter].includes(operations as ListOperations) && (
<div className="flex items-center gap-2">
<RAGFlowFormItem name="filter.operator" className="flex-1">
<SelectWithSearch options={operatorOptions}></SelectWithSearch>
</RAGFlowFormItem>
<Separator className="w-2" />
<RAGFlowFormItem name="filter.value" className="flex-1">
<PromptEditor showToolbar={false} multiLine={false} />
</RAGFlowFormItem>
</div>
)}
<Output list={outputList} isFormRequired></Output>
</FormWrapper>
</Form>
);
}
export default memo(ListOperationsForm);

View File

@ -31,6 +31,7 @@ import {
initialIterationValues,
initialJin10Values,
initialKeywordExtractValues,
initialListOperationsValues,
initialMessageValues,
initialNoteValues,
initialParserValues,
@ -129,6 +130,7 @@ export const useInitializeOperatorParams = () => {
prompts: t('flow.prompts.user.summary'),
},
[Operator.DataOperations]: initialDataOperationsValues,
[Operator.ListOperations]: initialListOperationsValues,
[Operator.VariableAssigner]: initialVariableAssignerValues,
[Operator.VariableAggregator]: initialVariableAggregatorValues,
};

View File

@ -14,7 +14,7 @@ import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.s
import { IconFont } from '@/components/icon-font';
import { cn } from '@/lib/utils';
import { Equal, FileCode, HousePlus, Variable } from 'lucide-react';
import { Columns3, Equal, FileCode, HousePlus, Variable } from 'lucide-react';
import { Operator } from './constant';
interface IProps {
@ -57,6 +57,7 @@ export const SVGIconMap = {
};
export const LucideIconMap = {
[Operator.DataOperations]: FileCode,
[Operator.ListOperations]: Columns3,
[Operator.VariableAssigner]: Equal,
[Operator.VariableAggregator]: Variable,
};

View File

@ -328,7 +328,6 @@ export const buildDslComponentsByGraph = (
case Operator.DataOperations:
params = transformDataOperationsParams(params);
break;
default:
break;
}