Feat: Add a form with data operations operators #10427 (#11001)

### What problem does this PR solve?

Feat: Add a form with data operations operators #10427

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-11-04 19:42:59 +08:00
committed by GitHub
parent 880a6a0428
commit db9fa3042b
16 changed files with 433 additions and 49 deletions

View File

@ -17,6 +17,7 @@ type RAGFlowFormItemProps = {
horizontal?: boolean; horizontal?: boolean;
required?: boolean; required?: boolean;
labelClassName?: string; labelClassName?: string;
className?: string;
}; };
export function RAGFlowFormItem({ export function RAGFlowFormItem({
@ -27,6 +28,7 @@ export function RAGFlowFormItem({
horizontal = false, horizontal = false,
required = false, required = false,
labelClassName, labelClassName,
className,
}: RAGFlowFormItemProps) { }: RAGFlowFormItemProps) {
const form = useFormContext(); const form = useFormContext();
return ( return (
@ -35,9 +37,12 @@ export function RAGFlowFormItem({
name={name} name={name}
render={({ field }) => ( render={({ field }) => (
<FormItem <FormItem
className={cn({ className={cn(
{
'flex items-center': horizontal, 'flex items-center': horizontal,
})} },
className,
)}
> >
{label && ( {label && (
<FormLabel <FormLabel

View File

@ -1728,6 +1728,16 @@ Important structured information may include: names, dates, locations, events, k
configuration: 'Configuration', configuration: 'Configuration',
structuredOutput: 'Structured output', structuredOutput: 'Structured output',
}, },
operations: 'Operations',
operationsOptions: {
selectKeys: 'Select keys',
literalEval: 'Literal eval',
combine: 'Combine',
filterValues: 'Filter values',
appendOrUpdate: 'Append or update',
removeKeys: 'Remove keys',
renameKeys: 'Rename keys',
},
}, },
llmTools: { llmTools: {
bad_calculator: { bad_calculator: {

View File

@ -1609,6 +1609,16 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
configuration: '配置', configuration: '配置',
structuredOutput: '结构化输出', structuredOutput: '结构化输出',
}, },
operations: '操作',
operationsOptions: {
selectKeys: '选择键',
literalEval: '字面值求值',
combine: '合并',
filterValues: '筛选值',
appendOrUpdate: '追加或更新',
removeKeys: '删除键',
renameKeys: '重命名键',
},
}, },
footer: { footer: {
profile: 'All rights reserved @ React', profile: 'All rights reserved @ React',

View File

@ -715,8 +715,23 @@ export const initialPlaceholderValues = {
// It's just a visual placeholder // It's just a visual placeholder
}; };
export enum Operations {
SelectKeys = 'select keys',
LiteralEval = 'literal eval',
Combine = 'combine',
FilterValues = 'filter values',
AppendOrUpdate = 'append or update',
RemoveKeys = 'remove keys',
RenameKeys = 'rename keys',
}
export const initialDataOperationsValues = { export const initialDataOperationsValues = {
outputs: {}, operations: Operations.SelectKeys,
outputs: {
result: {
type: 'Array<Object>',
},
},
}; };
export const CategorizeAnchorPointPositions = [ export const CategorizeAnchorPointPositions = [

View File

@ -0,0 +1,46 @@
import { BlockButton, Button } from '@/components/ui/button';
import { X } from 'lucide-react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { JsonSchemaDataType } from '../../constant';
import { QueryVariable } from './query-variable';
type QueryVariableListProps = {
types?: JsonSchemaDataType[];
};
export function QueryVariableList({ types }: QueryVariableListProps) {
const { t } = useTranslation();
const form = useFormContext();
const name = 'inputs';
const { fields, remove, append } = useFieldArray({
name: name,
control: form.control,
});
return (
<div className="space-y-5">
{fields.map((field, index) => {
const nameField = `${name}.${index}.input`;
return (
<div key={field.id} className="flex items-center gap-2">
<QueryVariable
name={nameField}
hideLabel
className="flex-1"
types={types}
></QueryVariable>
<Button variant={'ghost'} onClick={() => remove(index)}>
<X className="text-text-sub-title-invert " />
</Button>
</div>
);
})}
<BlockButton onClick={() => append({ input: '' })}>
{t('common.add')}
</BlockButton>
</div>
);
}

View File

@ -5,24 +5,28 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { toLower } from 'lodash'; import { isEmpty, toLower } from 'lodash';
import { ReactNode, useMemo } from 'react'; import { ReactNode, useMemo } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { VariableType } from '../../constant'; import { JsonSchemaDataType } from '../../constant';
import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query'; import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query';
import { GroupedSelectWithSecondaryMenu } from './select-with-secondary-menu'; import { GroupedSelectWithSecondaryMenu } from './select-with-secondary-menu';
type QueryVariableProps = { type QueryVariableProps = {
name?: string; name?: string;
type?: VariableType; types?: JsonSchemaDataType[];
label?: ReactNode; label?: ReactNode;
hideLabel?: boolean;
className?: string;
}; };
export function QueryVariable({ export function QueryVariable({
name = 'query', name = 'query',
type, types = [],
label, label,
hideLabel = false,
className,
}: QueryVariableProps) { }: QueryVariableProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const form = useFormContext(); const form = useFormContext();
@ -30,23 +34,25 @@ export function QueryVariable({
const nextOptions = useBuildQueryVariableOptions(); const nextOptions = useBuildQueryVariableOptions();
const finalOptions = useMemo(() => { const finalOptions = useMemo(() => {
return type return !isEmpty(types)
? nextOptions.map((x) => { ? nextOptions.map((x) => {
return { return {
...x, ...x,
options: x.options.filter((y) => toLower(y.type).includes(type)), options: x.options.filter((y) =>
types?.some((x) => toLower(y.type).includes(x)),
),
}; };
}) })
: nextOptions; : nextOptions;
}, [nextOptions, type]); }, [nextOptions, types]);
return ( return (
<FormField <FormField
control={form.control} control={form.control}
name={name} name={name}
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className={className}>
{label || ( {hideLabel || label || (
<FormLabel tooltip={t('flow.queryTip')}> <FormLabel tooltip={t('flow.queryTip')}>
{t('flow.query')} {t('flow.query')}
</FormLabel> </FormLabel>
@ -56,7 +62,7 @@ export function QueryVariable({
options={finalOptions} options={finalOptions}
{...field} {...field}
// allowClear // allowClear
type={type} types={types}
></GroupedSelectWithSecondaryMenu> ></GroupedSelectWithSecondaryMenu>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />

View File

@ -23,7 +23,7 @@ import { ChevronDownIcon, XIcon } from 'lucide-react';
import * as React from 'react'; import * as React from 'react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { VariableType } from '../../constant'; import { JsonSchemaDataType } from '../../constant';
import { import {
useFindAgentStructuredOutputLabel, useFindAgentStructuredOutputLabel,
useShowSecondaryMenu, useShowSecondaryMenu,
@ -52,7 +52,7 @@ interface GroupedSelectWithSecondaryMenuProps {
value?: string; value?: string;
onChange?: (value: string) => void; onChange?: (value: string) => void;
placeholder?: string; placeholder?: string;
type?: VariableType; types?: JsonSchemaDataType[];
} }
export function GroupedSelectWithSecondaryMenu({ export function GroupedSelectWithSecondaryMenu({
@ -60,7 +60,7 @@ export function GroupedSelectWithSecondaryMenu({
value, value,
onChange, onChange,
placeholder, placeholder,
type, types,
}: GroupedSelectWithSecondaryMenuProps) { }: GroupedSelectWithSecondaryMenuProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
@ -157,7 +157,7 @@ export function GroupedSelectWithSecondaryMenu({
key={option.value} key={option.value}
data={option} data={option}
click={handleSecondaryMenuClick} click={handleSecondaryMenuClick}
type={type} types={types}
></StructuredOutputSecondaryMenu> ></StructuredOutputSecondaryMenu>
); );
} }

View File

@ -8,7 +8,7 @@ import { get, isEmpty, isPlainObject } from 'lodash';
import { ChevronRight } from 'lucide-react'; import { ChevronRight } from 'lucide-react';
import { PropsWithChildren, ReactNode, useCallback } from 'react'; import { PropsWithChildren, ReactNode, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { JsonSchemaDataType, VariableType } from '../../constant'; import { JsonSchemaDataType } from '../../constant';
import { useGetStructuredOutputByValue } from '../../hooks/use-build-structured-output'; import { useGetStructuredOutputByValue } from '../../hooks/use-build-structured-output';
import { import {
hasJsonSchemaChild, hasJsonSchemaChild,
@ -20,13 +20,13 @@ type DataItem = { label: ReactNode; value: string; parentLabel?: ReactNode };
type StructuredOutputSecondaryMenuProps = { type StructuredOutputSecondaryMenuProps = {
data: DataItem; data: DataItem;
click(option: { label: ReactNode; value: string }): void; click(option: { label: ReactNode; value: string }): void;
type?: VariableType | JsonSchemaDataType; types?: JsonSchemaDataType[];
} & PropsWithChildren; } & PropsWithChildren;
export function StructuredOutputSecondaryMenu({ export function StructuredOutputSecondaryMenu({
data, data,
click, click,
type, types = [],
}: StructuredOutputSecondaryMenuProps) { }: StructuredOutputSecondaryMenuProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const filterStructuredOutput = useGetStructuredOutputByValue(); const filterStructuredOutput = useGetStructuredOutputByValue();
@ -35,18 +35,21 @@ export function StructuredOutputSecondaryMenu({
const handleSubMenuClick = useCallback( const handleSubMenuClick = useCallback(
(option: { label: ReactNode; value: string }, dataType?: string) => () => { (option: { label: ReactNode; value: string }, dataType?: string) => () => {
// The query variable of the iteration operator can only select array type data. // The query variable of the iteration operator can only select array type data.
if ((type && type === dataType) || !type) { if (
(!isEmpty(types) && types?.some((x) => x === dataType)) ||
isEmpty(types)
) {
click(option); click(option);
} }
}, },
[click, type], [click, types],
); );
const handleMenuClick = useCallback(() => { const handleMenuClick = useCallback(() => {
if (isEmpty(type) || type === JsonSchemaDataType.Object) { if (isEmpty(types) || types?.some((x) => x === JsonSchemaDataType.Object)) {
click(data); click(data);
} }
}, [click, data, type]); }, [click, data, types]);
const renderAgentStructuredOutput = useCallback( const renderAgentStructuredOutput = useCallback(
(values: any, option: { label: ReactNode; value: string }) => { (values: any, option: { label: ReactNode; value: string }) => {
@ -62,10 +65,10 @@ export function StructuredOutputSecondaryMenu({
const dataType = get(value, 'type'); const dataType = get(value, 'type');
if ( if (
!type || isEmpty(types) ||
(type && (!isEmpty(types) &&
(dataType === type || (types?.some((x) => x === dataType) ||
hasSpecificTypeChild(value ?? {}, type))) hasSpecificTypeChild(value ?? {}, types)))
) { ) {
return ( return (
<li key={key} className="pl-1"> <li key={key} className="pl-1">
@ -90,10 +93,13 @@ export function StructuredOutputSecondaryMenu({
return <div></div>; return <div></div>;
}, },
[handleSubMenuClick, type], [handleSubMenuClick, types],
); );
if (!hasJsonSchemaChild(structuredOutput)) { if (
!hasJsonSchemaChild(structuredOutput) ||
(!isEmpty(types) && !hasSpecificTypeChild(structuredOutput, types))
) {
return null; return null;
} }

View File

@ -0,0 +1,73 @@
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button';
import { FormLabel } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { X } from 'lucide-react';
import { ReactNode } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
type SelectKeysProps = {
name: string;
label: ReactNode;
tooltip?: string;
keyField?: string;
valueField?: string;
operatorField?: string;
};
export function FilterValues({
name,
label,
tooltip,
keyField = 'key',
valueField = 'value',
operatorField = 'operator',
}: SelectKeysProps) {
const { t } = useTranslation();
const form = useFormContext();
const { fields, remove, append } = useFieldArray({
name: name,
control: form.control,
});
return (
<section className="space-y-2">
<FormLabel tooltip={tooltip}>{label}</FormLabel>
<div className="space-y-5">
{fields.map((field, index) => {
const keyFieldAlias = `${name}.${index}.${keyField}`;
const valueFieldAlias = `${name}.${index}.${valueField}`;
const operatorFieldAlias = `${name}.${index}.${operatorField}`;
return (
<div key={field.id} className="flex items-center gap-2">
<RAGFlowFormItem name={keyFieldAlias} className="flex-1">
<Input></Input>
</RAGFlowFormItem>
<Separator orientation="vertical" className="h-2.5" />
<RAGFlowFormItem name={operatorFieldAlias} className="flex-1">
<SelectWithSearch {...field} options={[]}></SelectWithSearch>
</RAGFlowFormItem>
<Separator orientation="vertical" className="h-2.5" />
<RAGFlowFormItem name={valueFieldAlias} className="flex-1">
<Input></Input>
</RAGFlowFormItem>
<Button variant={'ghost'} onClick={() => remove(index)}>
<X />
</Button>
</div>
);
})}
</div>
<BlockButton onClick={() => append({ [keyField]: '', [valueField]: '' })}>
{t('common.add')}
</BlockButton>
</section>
);
}

View File

@ -1,44 +1,132 @@
import { SelectWithSearch } from '@/components/originui/select-with-search'; import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form'; import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Form } from '@/components/ui/form'; import { Form, FormLabel } from '@/components/ui/form';
import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react'; import { memo } from 'react';
import { useForm } from 'react-hook-form'; import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { z } from 'zod'; import { z } from 'zod';
import { initialDataOperationsValues } from '../../constant'; import {
JsonSchemaDataType,
Operations,
initialDataOperationsValues,
} from '../../constant';
import { useFormValues } from '../../hooks/use-form-values'; import { useFormValues } from '../../hooks/use-form-values';
import { useWatchFormChange } from '../../hooks/use-watch-form-change'; import { useWatchFormChange } from '../../hooks/use-watch-form-change';
import { INextOperatorForm } from '../../interface'; import { INextOperatorForm } from '../../interface';
import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper'; import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output'; import { Output } from '../components/output';
import { QueryVariableList } from '../components/query-variable-list';
import { FilterValues } from './filter-values';
import { SelectKeys } from './select-keys';
import { Updates } from './updates';
export const RetrievalPartialSchema = { export const RetrievalPartialSchema = {
select_operation: z.string(), inputs: z.array(z.object({ input: z.string().optional() })),
operations: z.string(),
select_keys: z.array(z.object({ name: z.string().optional() })).optional(),
remove_keys: z.array(z.object({ name: z.string().optional() })).optional(),
updates: z
.array(
z.object({ key: z.string().optional(), value: z.string().optional() }),
)
.optional(),
rename_keys: z
.array(
z.object({
old_key: z.string().optional(),
new_key: z.string().optional(),
}),
)
.optional(),
filter_values: z
.array(
z.object({
key: z.string().optional(),
value: z.string().optional(),
operator: z.string().optional(),
}),
)
.optional(),
}; };
export const FormSchema = z.object(RetrievalPartialSchema); export const FormSchema = z.object(RetrievalPartialSchema);
export type DataOperationsFormSchemaType = z.infer<typeof FormSchema>;
const outputList = buildOutputList(initialDataOperationsValues.outputs);
function DataOperationsForm({ node }: INextOperatorForm) { function DataOperationsForm({ node }: INextOperatorForm) {
const { t } = useTranslation(); const { t } = useTranslation();
const defaultValues = useFormValues(initialDataOperationsValues, node); const defaultValues = useFormValues(initialDataOperationsValues, node);
const form = useForm({ const form = useForm<DataOperationsFormSchemaType>({
defaultValues: defaultValues, defaultValues: defaultValues,
resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema),
shouldUnregister: true,
}); });
const operations = useWatch({ control: form.control, name: 'operations' });
const OperationsOptions = buildOptions(
Operations,
t,
`flow.operationsOptions`,
true,
);
useWatchFormChange(node?.id, form); useWatchFormChange(node?.id, form);
return ( return (
<Form {...form}> <Form {...form}>
<FormWrapper> <FormWrapper>
<RAGFlowFormItem name="query" label={t('flow.query')}> <div className="space-y-2">
<SelectWithSearch options={[]} allowClear /> <FormLabel tooltip={t('flow.queryTip')}>{t('flow.query')}</FormLabel>
<QueryVariableList
types={[JsonSchemaDataType.Array, JsonSchemaDataType.Object]}
></QueryVariableList>
</div>
<RAGFlowFormItem name="operations" label={t('flow.operations')}>
<SelectWithSearch options={OperationsOptions} allowClear />
</RAGFlowFormItem> </RAGFlowFormItem>
{operations === Operations.SelectKeys && (
<Output list={[]}></Output> <SelectKeys
name="select_keys"
label={t('flow.operationsOptions.selectKeys')}
></SelectKeys>
)}
{operations === Operations.RemoveKeys && (
<SelectKeys
name="remove_keys"
label={t('flow.operationsOptions.removeKeys')}
></SelectKeys>
)}
{operations === Operations.AppendOrUpdate && (
<Updates
name="updates"
label={t('flow.operationsOptions.updates')}
keyField="key"
valueField="value"
></Updates>
)}
{operations === Operations.RenameKeys && (
<Updates
name="rename_keys"
label={t('flow.operationsOptions.renameKeys')}
keyField="old_key"
valueField="new_key"
></Updates>
)}
{operations === Operations.FilterValues && (
<FilterValues
name="filter_values"
label={t('flow.operationsOptions.filterValues')}
></FilterValues>
)}
<Output list={outputList}></Output>
</FormWrapper> </FormWrapper>
</Form> </Form>
); );

View File

@ -0,0 +1,49 @@
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button';
import { FormLabel } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { X } from 'lucide-react';
import { ReactNode } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
type SelectKeysProps = {
name: string;
label: ReactNode;
tooltip?: string;
};
export function SelectKeys({ name, label, tooltip }: SelectKeysProps) {
const { t } = useTranslation();
const form = useFormContext();
const { fields, remove, append } = useFieldArray({
name: name,
control: form.control,
});
return (
<section className="space-y-2">
<FormLabel tooltip={tooltip}>{label}</FormLabel>
<div className="space-y-5">
{fields.map((field, index) => {
const nameField = `${name}.${index}.name`;
return (
<div key={field.id} className="flex items-center gap-2">
<RAGFlowFormItem name={nameField} className="flex-1">
<Input></Input>
</RAGFlowFormItem>
<Button variant={'ghost'} onClick={() => remove(index)}>
<X />
</Button>
</div>
);
})}
</div>
<BlockButton onClick={() => append({ name: '' })}>
{t('common.add')}
</BlockButton>
</section>
);
}

View File

@ -0,0 +1,61 @@
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button';
import { FormLabel } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { X } from 'lucide-react';
import { ReactNode } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
type SelectKeysProps = {
name: string;
label: ReactNode;
tooltip?: string;
keyField: string;
valueField: string;
};
export function Updates({
name,
label,
tooltip,
keyField,
valueField,
}: SelectKeysProps) {
const { t } = useTranslation();
const form = useFormContext();
const { fields, remove, append } = useFieldArray({
name: name,
control: form.control,
});
return (
<section className="space-y-2">
<FormLabel tooltip={tooltip}>{label}</FormLabel>
<div className="space-y-5">
{fields.map((field, index) => {
const keyFieldAlias = `${name}.${index}.${keyField}`;
const valueFieldAlias = `${name}.${index}.${valueField}`;
return (
<div key={field.id} className="flex items-center gap-2">
<RAGFlowFormItem name={keyFieldAlias} className="flex-1">
<Input></Input>
</RAGFlowFormItem>
<RAGFlowFormItem name={valueFieldAlias} className="flex-1">
<Input></Input>
</RAGFlowFormItem>
<Button variant={'ghost'} onClick={() => remove(index)}>
<X />
</Button>
</div>
);
})}
</div>
<BlockButton onClick={() => append({ [keyField]: '', [valueField]: '' })}>
{t('common.add')}
</BlockButton>
</section>
);
}

View File

@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useForm, useWatch } from 'react-hook-form'; import { useForm, useWatch } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { VariableType } from '../../constant'; import { JsonSchemaDataType } from '../../constant';
import { INextOperatorForm } from '../../interface'; import { INextOperatorForm } from '../../interface';
import { FormWrapper } from '../components/form-wrapper'; import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output'; import { Output } from '../components/output';
@ -44,7 +44,7 @@ function IterationForm({ node }: INextOperatorForm) {
<FormContainer> <FormContainer>
<QueryVariable <QueryVariable
name="items_ref" name="items_ref"
type={VariableType.Array} types={[JsonSchemaDataType.Array]}
></QueryVariable> ></QueryVariable>
</FormContainer> </FormContainer>
<DynamicOutput node={node}></DynamicOutput> <DynamicOutput node={node}></DynamicOutput>

View File

@ -29,6 +29,7 @@ import {
NodeHandleId, NodeHandleId,
Operator, Operator,
} from './constant'; } from './constant';
import { DataOperationsFormSchemaType } from './form/data-operations-form';
import { ExtractorFormSchemaType } from './form/extractor-form'; import { ExtractorFormSchemaType } from './form/extractor-form';
import { HierarchicalMergerFormSchemaType } from './form/hierarchical-merger-form'; import { HierarchicalMergerFormSchemaType } from './form/hierarchical-merger-form';
import { ParserFormSchemaType } from './form/parser-form'; import { ParserFormSchemaType } from './form/parser-form';
@ -267,6 +268,15 @@ function transformExtractorParams(params: ExtractorFormSchemaType) {
return { ...params, prompts: [{ content: params.prompts, role: 'user' }] }; return { ...params, prompts: [{ content: params.prompts, role: 'user' }] };
} }
function transformDataOperationsParams(params: DataOperationsFormSchemaType) {
return {
...params,
select_keys: params?.select_keys?.map((x) => x.name),
remove_keys: params?.remove_keys?.map((x) => x.name),
inputs: params.inputs.map((x) => x.input),
};
}
// construct a dsl based on the node information of the graph // construct a dsl based on the node information of the graph
export const buildDslComponentsByGraph = ( export const buildDslComponentsByGraph = (
nodes: RAGFlowNodeType[], nodes: RAGFlowNodeType[],
@ -313,6 +323,9 @@ export const buildDslComponentsByGraph = (
case Operator.Extractor: case Operator.Extractor:
params = transformExtractorParams(params); params = transformExtractorParams(params);
break; break;
case Operator.DataOperations:
params = transformDataOperationsParams(params);
break;
default: default:
break; break;

View File

@ -4,14 +4,14 @@ import { JsonSchemaDataType } from '../constant';
export function hasSpecificTypeChild( export function hasSpecificTypeChild(
data: Record<string, any> | Array<any>, data: Record<string, any> | Array<any>,
type: string, types: string[] = [],
) { ) {
if (Array.isArray(data)) { if (Array.isArray(data)) {
for (const value of data) { for (const value of data) {
if (isPlainObject(value) && value.type === type) { if (isPlainObject(value) && types.some((x) => x === value.type)) {
return true; return true;
} }
if (hasSpecificTypeChild(value, type)) { if (hasSpecificTypeChild(value, types)) {
return true; return true;
} }
} }
@ -19,11 +19,11 @@ export function hasSpecificTypeChild(
if (isPlainObject(data)) { if (isPlainObject(data)) {
for (const value of Object.values(data)) { for (const value of Object.values(data)) {
if (isPlainObject(value) && value.type === type) { if (isPlainObject(value) && types.some((x) => x === value.type)) {
return true; return true;
} }
if (hasSpecificTypeChild(value, type)) { if (hasSpecificTypeChild(value, types)) {
return true; return true;
} }
} }
@ -33,7 +33,7 @@ export function hasSpecificTypeChild(
} }
export function hasArrayChild(data: Record<string, any> | Array<any>) { export function hasArrayChild(data: Record<string, any> | Array<any>) {
return hasSpecificTypeChild(data, JsonSchemaDataType.Array); return hasSpecificTypeChild(data, [JsonSchemaDataType.Array]);
} }
export function hasJsonSchemaChild(data: JSONSchema) { export function hasJsonSchemaChild(data: JSONSchema) {

View File

@ -1,5 +1,6 @@
import { variableEnabledFieldMap } from '@/constants/chat'; import { variableEnabledFieldMap } from '@/constants/chat';
import { TFunction } from 'i18next'; import { TFunction } from 'i18next';
import { camelCase } from 'lodash';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
// chat model setting and generate operator // chat model setting and generate operator
@ -32,11 +33,12 @@ export function buildOptions(
data: Record<string, any>, data: Record<string, any>,
t?: TFunction<['translation', ...string[]], undefined>, t?: TFunction<['translation', ...string[]], undefined>,
prefix?: string, prefix?: string,
camel: boolean = false,
) { ) {
if (t) { if (t) {
return Object.values(data).map((val) => ({ return Object.values(data).map((val) => ({
label: t( label: t(
`${prefix ? prefix + '.' : ''}${typeof val === 'string' ? val.toLowerCase() : val}`, `${prefix ? prefix + '.' : ''}${typeof val === 'string' ? (camel ? camelCase(val) : val.toLowerCase()) : val}`,
), ),
value: val, value: val,
})); }));