Feat: Initialize the data pipeline canvas. #9869 (#9870)

### What problem does this PR solve?
Feat: Initialize the data pipeline canvas. #9869

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-09-02 15:47:33 +08:00
committed by GitHub
parent c2567844ea
commit cb14dafaca
196 changed files with 21201 additions and 0 deletions

View File

@ -0,0 +1,168 @@
import Editor, { loader } from '@monaco-editor/react';
import { INextOperatorForm } from '../../interface';
import { FormContainer } from '@/components/form-container';
import { useIsDarkTheme } from '@/components/theme-provider';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { RAGFlowSelect } from '@/components/ui/select';
import { ProgrammingLanguage } from '@/constants/agent';
import { ICodeForm } from '@/interfaces/database/agent';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output';
import {
DynamicInputVariable,
TypeOptions,
VariableTitle,
} from './next-variable';
import { FormSchema, FormSchemaType } from './schema';
import { useValues } from './use-values';
import {
useHandleLanguageChange,
useWatchFormChange,
} from './use-watch-change';
loader.config({ paths: { vs: '/vs' } });
const options = [
ProgrammingLanguage.Python,
ProgrammingLanguage.Javascript,
].map((x) => ({ value: x, label: x }));
const DynamicFieldName = 'outputs';
function CodeForm({ node }: INextOperatorForm) {
const formData = node?.data.form as ICodeForm;
const { t } = useTranslation();
const values = useValues(node);
const isDarkTheme = useIsDarkTheme();
const form = useForm<FormSchemaType>({
defaultValues: values,
resolver: zodResolver(FormSchema),
});
useWatchFormChange(node?.id, form);
const handleLanguageChange = useHandleLanguageChange(node?.id, form);
return (
<Form {...form}>
<FormWrapper>
<DynamicInputVariable
node={node}
title={t('flow.input')}
isOutputs={false}
></DynamicInputVariable>
<FormField
control={form.control}
name="script"
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center justify-between">
Code
<FormField
control={form.control}
name="lang"
render={({ field }) => (
<FormItem>
<FormControl>
<RAGFlowSelect
{...field}
onChange={(val) => {
field.onChange(val);
handleLanguageChange(val);
}}
options={options}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</FormLabel>
<FormControl>
<Editor
height={300}
theme={isDarkTheme ? 'vs-dark' : 'vs'}
language={formData.lang}
options={{
minimap: { enabled: false },
automaticLayout: true,
}}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{formData.lang === ProgrammingLanguage.Python ? (
<DynamicInputVariable
node={node}
title={'Return Values'}
name={DynamicFieldName}
isOutputs
></DynamicInputVariable>
) : (
<div>
<VariableTitle title={'Return Values'}></VariableTitle>
<FormContainer className="space-y-5">
<FormField
control={form.control}
name={`${DynamicFieldName}.name`}
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input
{...field}
placeholder={t('common.pleaseInput')}
></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`${DynamicFieldName}.type`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Type</FormLabel>
<FormControl>
<RAGFlowSelect
placeholder={t('common.pleaseSelect')}
options={TypeOptions}
{...field}
></RAGFlowSelect>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</FormContainer>
</div>
)}
</FormWrapper>
<div className="p-5">
<Output list={buildOutputList(formData.outputs)}></Output>
</div>
</Form>
);
}
export default memo(CodeForm);

View File

@ -0,0 +1,128 @@
'use client';
import { FormContainer } from '@/components/form-container';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { BlockButton, Button } from '@/components/ui/button';
import {
FormControl,
FormField,
FormItem,
FormMessage,
} from '@/components/ui/form';
import { BlurInput } from '@/components/ui/input';
import { RAGFlowSelect } from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { X } from 'lucide-react';
import { ReactNode } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query';
interface IProps {
node?: RAGFlowNodeType;
name?: string;
isOutputs: boolean;
}
export const TypeOptions = [
'String',
'Number',
'Boolean',
'Array<String>',
'Array<Number>',
'Object',
].map((x) => ({ label: x, value: x }));
export function DynamicVariableForm({ name = 'arguments', isOutputs }: IProps) {
const { t } = useTranslation();
const form = useFormContext();
const { fields, remove, append } = useFieldArray({
name: name,
control: form.control,
});
const nextOptions = useBuildQueryVariableOptions();
return (
<div className="space-y-5">
{fields.map((field, index) => {
const typeField = `${name}.${index}.name`;
return (
<div key={field.id} className="flex w-full items-center gap-2">
<FormField
control={form.control}
name={typeField}
render={({ field }) => (
<FormItem className="flex-1 overflow-hidden">
<FormControl>
<BlurInput
{...field}
placeholder={t('common.pleaseInput')}
></BlurInput>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Separator className="w-3 text-text-secondary" />
<FormField
control={form.control}
name={`${name}.${index}.type`}
render={({ field }) => (
<FormItem className="flex-1 overflow-hidden">
<FormControl>
{isOutputs ? (
<RAGFlowSelect
placeholder={t('common.pleaseSelect')}
options={TypeOptions}
{...field}
></RAGFlowSelect>
) : (
<SelectWithSearch
options={nextOptions}
{...field}
></SelectWithSearch>
)}
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button variant={'ghost'} onClick={() => remove(index)}>
<X className="text-text-sub-title-invert " />
</Button>
</div>
);
})}
<BlockButton onClick={() => append({ name: '', type: undefined })}>
{t('flow.addVariable')}
</BlockButton>
</div>
);
}
export function VariableTitle({ title }: { title: ReactNode }) {
return <div className="font-medium text-text-primary pb-2">{title}</div>;
}
export function DynamicInputVariable({
node,
name,
title,
isOutputs = false,
}: IProps & { title: ReactNode }) {
return (
<section>
<VariableTitle title={title}></VariableTitle>
<FormContainer>
<DynamicVariableForm
node={node}
name={name}
isOutputs={isOutputs}
></DynamicVariableForm>
</FormContainer>
</section>
);
}

View File

@ -0,0 +1,14 @@
import { ProgrammingLanguage } from '@/constants/agent';
import { z } from 'zod';
export const FormSchema = z.object({
lang: z.enum([ProgrammingLanguage.Python, ProgrammingLanguage.Javascript]),
script: z.string(),
arguments: z.array(z.object({ name: z.string(), type: z.string() })),
outputs: z.union([
z.array(z.object({ name: z.string(), type: z.string() })).optional(),
z.object({ name: z.string(), type: z.string() }),
]),
});
export type FormSchemaType = z.infer<typeof FormSchema>;

View File

@ -0,0 +1,47 @@
import { ProgrammingLanguage } from '@/constants/agent';
import { ICodeForm } from '@/interfaces/database/agent';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { isEmpty } from 'lodash';
import { useMemo } from 'react';
import { initialCodeValues } from '../../constant';
function convertToArray(args: Record<string, string>) {
return Object.entries(args).map(([key, value]) => ({
name: key,
type: value,
}));
}
type OutputsFormType = { name: string; type: string };
function convertOutputsToArray({ lang, outputs = {} }: ICodeForm) {
if (lang === ProgrammingLanguage.Python) {
return Object.entries(outputs).map(([key, val]) => ({
name: key,
type: val.type,
}));
}
return Object.entries(outputs).reduce<OutputsFormType>((pre, [key, val]) => {
pre.name = key;
pre.type = val.type;
return pre;
}, {} as OutputsFormType);
}
export function useValues(node?: RAGFlowNodeType) {
const values = useMemo(() => {
const formData = node?.data?.form;
if (isEmpty(formData)) {
return initialCodeValues;
}
return {
...formData,
arguments: convertToArray(formData.arguments),
outputs: convertOutputsToArray(formData),
};
}, [node?.data?.form]);
return values;
}

View File

@ -0,0 +1,95 @@
import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent';
import { ICodeForm } from '@/interfaces/database/agent';
import { isEmpty } from 'lodash';
import { useCallback, useEffect } from 'react';
import { UseFormReturn, useWatch } from 'react-hook-form';
import useGraphStore from '../../store';
import { FormSchemaType } from './schema';
function convertToObject(list: FormSchemaType['arguments'] = []) {
return list.reduce<Record<string, string>>((pre, cur) => {
pre[cur.name] = cur.type;
return pre;
}, {});
}
type ArrayOutputs = Extract<FormSchemaType['outputs'], Array<any>>;
type ObjectOutputs = Exclude<FormSchemaType['outputs'], Array<any>>;
function convertOutputsToObject({ lang, outputs }: FormSchemaType) {
if (lang === ProgrammingLanguage.Python) {
return (outputs as ArrayOutputs).reduce<ICodeForm['outputs']>(
(pre, cur) => {
pre[cur.name] = {
value: '',
type: cur.type,
};
return pre;
},
{},
);
}
const outputsObject = outputs as ObjectOutputs;
if (isEmpty(outputsObject)) {
return {};
}
return {
[outputsObject.name]: {
value: '',
type: outputsObject.type,
},
};
}
export function useWatchFormChange(
id?: string,
form?: UseFormReturn<FormSchemaType>,
) {
let values = useWatch({ control: form?.control });
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
useEffect(() => {
// Manually triggered form updates are synchronized to the canvas
if (id) {
values = form?.getValues() || {};
let nextValues: any = {
...values,
arguments: convertToObject(
values?.arguments as FormSchemaType['arguments'],
),
outputs: convertOutputsToObject(values as FormSchemaType),
};
updateNodeForm(id, nextValues);
}
}, [form?.formState.isDirty, id, updateNodeForm, values]);
}
export function useHandleLanguageChange(
id?: string,
form?: UseFormReturn<FormSchemaType>,
) {
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
const handleLanguageChange = useCallback(
(lang: string) => {
if (id) {
const script = CodeTemplateStrMap[lang as ProgrammingLanguage];
form?.setValue('script', script);
form?.setValue(
'outputs',
(lang === ProgrammingLanguage.Python
? []
: {}) as FormSchemaType['outputs'],
);
updateNodeForm(id, script, ['script']);
}
},
[form, id, updateNodeForm],
);
return handleLanguageChange;
}