Feat: Display the pipeline on the agent canvas #9869 (#10638)

### What problem does this PR solve?

Feat: Display the pipeline on the agent canvas #9869
### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-10-17 18:47:33 +08:00
committed by GitHub
parent c9e56d20cf
commit 685114d253
48 changed files with 2636 additions and 423 deletions

View File

@ -0,0 +1,107 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import { LargeModelFormField } from '@/components/large-model-form-field';
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Form } from '@/components/ui/form';
import { PromptEditor } from '@/pages/agent/form/components/prompt-editor';
import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
ContextGeneratorFieldName,
initialExtractorValues,
} from '../../constant/pipeline';
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({
field_name: z.string(),
sys_prompt: z.string(),
prompts: z.string().optional(),
...LlmSettingSchema,
});
export type ExtractorFormSchemaType = z.infer<typeof FormSchema>;
const outputList = buildOutputList(initialExtractorValues.outputs);
const ExtractorForm = ({ node }: INextOperatorForm) => {
const defaultValues = useFormValues(initialExtractorValues, node);
const { t } = useTranslation();
const form = useForm<ExtractorFormSchemaType>({
defaultValues,
resolver: zodResolver(FormSchema),
// mode: 'onChange',
});
const promptOptions = useBuildNodeOutputOptions(node?.id);
const options = buildOptions(ContextGeneratorFieldName, t, 'dataflow');
const {
handleFieldNameChange,
confirmSwitch,
hideModal,
visible,
cancelSwitch,
} = useSwitchPrompt(form);
useWatchFormChange(node?.id, form);
return (
<Form {...form}>
<FormWrapper>
<LargeModelFormField></LargeModelFormField>
<RAGFlowFormItem label={t('dataflow.fieldName')} name="field_name">
{(field) => (
<SelectWithSearch
onChange={(value) => {
field.onChange(value);
handleFieldNameChange(value);
}}
value={field.value}
placeholder={t('dataFlowPlaceholder')}
options={options}
></SelectWithSearch>
)}
</RAGFlowFormItem>
<RAGFlowFormItem label={t('flow.systemPrompt')} name="sys_prompt">
<PromptEditor
placeholder={t('flow.messagePlaceholder')}
showToolbar={true}
baseOptions={promptOptions}
></PromptEditor>
</RAGFlowFormItem>
<RAGFlowFormItem label={t('flow.userPrompt')} name="prompts">
<PromptEditor
showToolbar={true}
baseOptions={promptOptions}
></PromptEditor>
</RAGFlowFormItem>
<Output list={outputList}></Output>
</FormWrapper>
{visible && (
<ConfirmDeleteDialog
title={t('dataflow.switchPromptMessage')}
open
onOpenChange={hideModal}
onOk={confirmSwitch}
onCancel={cancelSwitch}
></ConfirmDeleteDialog>
)}
</Form>
);
};
export default memo(ExtractorForm);

View File

@ -0,0 +1,69 @@
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
import { useSetModalState } from '@/hooks/common-hooks';
import { useCallback, useRef } from 'react';
import { UseFormReturn } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
export const FormSchema = z.object({
field_name: z.string(),
sys_prompt: z.string(),
prompts: z.string().optional(),
...LlmSettingSchema,
});
export type ExtractorFormSchemaType = z.infer<typeof FormSchema>;
export function useSwitchPrompt(form: UseFormReturn<ExtractorFormSchemaType>) {
const { visible, showModal, hideModal } = useSetModalState();
const { t } = useTranslation();
const previousFieldNames = useRef<string[]>([form.getValues('field_name')]);
const setPromptValue = useCallback(
(field: keyof ExtractorFormSchemaType, key: string, value: string) => {
form.setValue(field, t(`dataflow.prompts.${key}.${value}`), {
shouldDirty: true,
shouldValidate: true,
});
},
[form, t],
);
const handleFieldNameChange = useCallback(
(value: string) => {
if (value) {
const names = previousFieldNames.current;
if (names.length > 1) {
names.shift();
}
names.push(value);
showModal();
}
},
[showModal],
);
const confirmSwitch = useCallback(() => {
const value = form.getValues('field_name');
setPromptValue('sys_prompt', 'system', value);
setPromptValue('prompts', 'user', value);
}, [form, setPromptValue]);
const cancelSwitch = useCallback(() => {
const previousValue = previousFieldNames.current.at(-2);
if (previousValue) {
form.setValue('field_name', previousValue, {
shouldDirty: true,
shouldValidate: true,
});
}
}, [form]);
return {
handleFieldNameChange,
confirmSwitch,
hideModal,
visible,
cancelSwitch,
};
}

View File

@ -0,0 +1,191 @@
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Form, FormLabel } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { zodResolver } from '@hookform/resolvers/zod';
import { Plus, Trash2 } from 'lucide-react';
import { memo } from 'react';
import { useFieldArray, useForm, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
Hierarchy,
initialHierarchicalMergerValues,
} from '../../constant/pipeline';
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';
const outputList = buildOutputList(initialHierarchicalMergerValues.outputs);
const HierarchyOptions = [
{ label: 'H1', value: Hierarchy.H1 },
{ label: 'H2', value: Hierarchy.H2 },
{ label: 'H3', value: Hierarchy.H3 },
{ label: 'H4', value: Hierarchy.H4 },
{ label: 'H5', value: Hierarchy.H5 },
];
export const FormSchema = z.object({
hierarchy: z.string(),
levels: z.array(
z.object({
expressions: z.array(
z.object({
expression: z.string().refine(
(val) => {
try {
// Try converting the string to a RegExp
new RegExp(val);
return true;
} catch {
return false;
}
},
{
message: 'Must be a valid regular expression string',
},
),
}),
),
}),
),
});
export type HierarchicalMergerFormSchemaType = z.infer<typeof FormSchema>;
type RegularExpressionsProps = {
index: number;
parentName: string;
removeParent: (index: number) => void;
isLatest: boolean;
};
export function RegularExpressions({
index,
parentName,
isLatest,
removeParent,
}: RegularExpressionsProps) {
const { t } = useTranslation();
const form = useFormContext();
const name = `${parentName}.${index}.expressions`;
const { fields, append, remove } = useFieldArray({
name: name,
control: form.control,
});
return (
<Card>
<CardHeader className="flex-row justify-between items-center">
<span>H{index + 1}</span>
{isLatest && (
<Button
type="button"
variant={'ghost'}
onClick={() => removeParent(index)}
>
<Trash2 />
</Button>
)}
</CardHeader>
<CardContent>
<FormLabel required className="mb-2 text-text-secondary">
{t('dataflow.regularExpressions')}
</FormLabel>
<section className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-2">
<div className="space-y-2 flex-1">
<RAGFlowFormItem
name={`${name}.${index}.expression`}
label={'expression'}
labelClassName="!hidden"
>
<Input className="!m-0"></Input>
</RAGFlowFormItem>
</div>
{index === 0 ? (
<Button
onClick={() => append({ expression: '' })}
variant={'ghost'}
>
<Plus></Plus>
</Button>
) : (
<Button
type="button"
variant={'ghost'}
onClick={() => remove(index)}
>
<Trash2 />
</Button>
)}
</div>
))}
</section>
</CardContent>
</Card>
);
}
const HierarchicalMergerForm = ({ node }: INextOperatorForm) => {
const { t } = useTranslation();
const defaultValues = useFormValues(initialHierarchicalMergerValues, node);
const form = useForm<HierarchicalMergerFormSchemaType>({
defaultValues,
resolver: zodResolver(FormSchema),
mode: 'onChange',
});
const name = 'levels';
const { fields, append, remove } = useFieldArray({
name: name,
control: form.control,
});
useWatchFormChange(node?.id, form);
return (
<Form {...form}>
<FormWrapper>
<RAGFlowFormItem name={'hierarchy'} label={t('dataflow.hierarchy')}>
<SelectWithSearch options={HierarchyOptions}></SelectWithSearch>
</RAGFlowFormItem>
{fields.map((field, index) => (
<div key={field.id} className="flex items-center">
<div className="flex-1">
<RegularExpressions
parentName={name}
index={index}
removeParent={remove}
isLatest={index === fields.length - 1}
></RegularExpressions>
</div>
</div>
))}
{fields.length < 5 && (
<BlockButton
onClick={() => append({ expressions: [{ expression: '' }] })}
>
{t('common.add')}
</BlockButton>
)}
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</Form>
);
};
export default memo(HierarchicalMergerForm);

View File

@ -0,0 +1,106 @@
import { crossLanguageOptions } from '@/components/cross-language-form-field';
import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field';
import {
LLMFormField,
LLMFormFieldProps,
} from '@/components/llm-setting-items/llm-form-field';
import {
SelectWithSearch,
SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { upperCase, upperFirst } from 'lodash';
import { useTranslation } from 'react-i18next';
import {
FileType,
OutputFormatMap,
SpreadsheetOutputFormat,
} from '../../constant/pipeline';
import { CommonProps } from './interface';
import { buildFieldNameWithPrefix } from './utils';
const UppercaseFields = [
SpreadsheetOutputFormat.Html,
SpreadsheetOutputFormat.Json,
];
function buildOutputOptionsFormatMap() {
return Object.entries(OutputFormatMap).reduce<
Record<string, SelectWithSearchFlagOptionType[]>
>((pre, [key, value]) => {
pre[key] = Object.values(value).map((v) => ({
label: UppercaseFields.some((x) => x === v)
? upperCase(v)
: upperFirst(v),
value: v,
}));
return pre;
}, {});
}
export type OutputFormatFormFieldProps = CommonProps & {
fileType: FileType;
};
export function OutputFormatFormField({
prefix,
fileType,
}: OutputFormatFormFieldProps) {
const { t } = useTranslation();
return (
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`output_format`, prefix)}
label={t('dataflow.outputFormat')}
>
<SelectWithSearch
options={buildOutputOptionsFormatMap()[fileType]}
></SelectWithSearch>
</RAGFlowFormItem>
);
}
export function ParserMethodFormField({
prefix,
optionsWithoutLLM,
}: CommonProps & { optionsWithoutLLM?: { value: string; label: string }[] }) {
const { t } = useTranslation();
return (
<LayoutRecognizeFormField
name={buildFieldNameWithPrefix(`parse_method`, prefix)}
horizontal={false}
optionsWithoutLLM={optionsWithoutLLM}
label={t('dataflow.parserMethod')}
></LayoutRecognizeFormField>
);
}
export function LargeModelFormField({
prefix,
options,
}: CommonProps & Pick<LLMFormFieldProps, 'options'>) {
return (
<LLMFormField
name={buildFieldNameWithPrefix('llm_id', prefix)}
options={options}
></LLMFormField>
);
}
export function LanguageFormField({ prefix }: CommonProps) {
const { t } = useTranslation();
return (
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`lang`, prefix)}
label={t('dataflow.lang')}
>
{(field) => (
<SelectWithSearch
options={crossLanguageOptions}
value={field.value}
onChange={field.onChange}
></SelectWithSearch>
)}
</RAGFlowFormItem>
);
}

View File

@ -0,0 +1,30 @@
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { MultiSelect } from '@/components/ui/multi-select';
import { buildOptions } from '@/utils/form';
import { useTranslation } from 'react-i18next';
import { ParserFields } from '../../constant/pipeline';
import { CommonProps } from './interface';
import { buildFieldNameWithPrefix } from './utils';
const options = buildOptions(ParserFields);
export function EmailFormFields({ prefix }: CommonProps) {
const { t } = useTranslation();
return (
<>
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`fields`, prefix)}
label={t('dataflow.fields')}
>
{(field) => (
<MultiSelect
options={options}
onValueChange={field.onChange}
defaultValue={field.value}
variant="inverted"
></MultiSelect>
)}
</RAGFlowFormItem>
</>
);
}

View File

@ -0,0 +1,60 @@
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Textarea } from '@/components/ui/textarea';
import { buildOptions } from '@/utils/form';
import { isEmpty } from 'lodash';
import { useEffect, useMemo } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { ImageParseMethod } from '../../constant/pipeline';
import { LanguageFormField, ParserMethodFormField } from './common-form-fields';
import { CommonProps } from './interface';
import { useSetInitialLanguage } from './use-set-initial-language';
import { buildFieldNameWithPrefix } from './utils';
export function ImageFormFields({ prefix }: CommonProps) {
const { t } = useTranslation();
const form = useFormContext();
const options = buildOptions(
ImageParseMethod,
t,
'dataflow.imageParseMethodOptions',
);
const parseMethodName = buildFieldNameWithPrefix('parse_method', prefix);
const parseMethod = useWatch({
name: parseMethodName,
});
const languageShown = useMemo(() => {
return !isEmpty(parseMethod) && parseMethod !== ImageParseMethod.OCR;
}, [parseMethod]);
useEffect(() => {
if (isEmpty(form.getValues(parseMethodName))) {
form.setValue(parseMethodName, ImageParseMethod.OCR, {
shouldValidate: true,
shouldDirty: true,
});
}
}, [form, parseMethodName]);
useSetInitialLanguage({ prefix, languageShown });
return (
<>
<ParserMethodFormField
prefix={prefix}
optionsWithoutLLM={options}
></ParserMethodFormField>
{languageShown && <LanguageFormField prefix={prefix}></LanguageFormField>}
{languageShown && (
<RAGFlowFormItem
name={buildFieldNameWithPrefix('system_prompt', prefix)}
label={t('dataflow.systemPrompt')}
>
<Textarea placeholder={t('dataflow.systemPromptPlaceholder')} />
</RAGFlowFormItem>
)}
</>
);
}

View File

@ -0,0 +1,226 @@
import {
SelectWithSearch,
SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button';
import { Form } from '@/components/ui/form';
import { Separator } from '@/components/ui/separator';
import { cn } from '@/lib/utils';
import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useHover } from 'ahooks';
import { Trash2 } from 'lucide-react';
import { memo, useCallback, useMemo, useRef } from 'react';
import {
UseFieldArrayRemove,
useFieldArray,
useForm,
useFormContext,
} from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
FileType,
InitialOutputFormatMap,
initialParserValues,
} from '../../constant/pipeline';
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 { Output } from '../components/output';
import { OutputFormatFormField } from './common-form-fields';
import { EmailFormFields } from './email-form-fields';
import { ImageFormFields } from './image-form-fields';
import { PdfFormFields } from './pdf-form-fields';
import { buildFieldNameWithPrefix } from './utils';
import { VideoFormFields } from './video-form-fields';
const outputList = buildOutputList(initialParserValues.outputs);
const FileFormatWidgetMap = {
[FileType.PDF]: PdfFormFields,
[FileType.Video]: VideoFormFields,
[FileType.Audio]: VideoFormFields,
[FileType.Email]: EmailFormFields,
[FileType.Image]: ImageFormFields,
};
type ParserItemProps = {
name: string;
index: number;
fieldLength: number;
remove: UseFieldArrayRemove;
fileFormatOptions: SelectWithSearchFlagOptionType[];
};
export const FormSchema = z.object({
setups: z.array(
z.object({
fileFormat: z.string().nullish(),
output_format: z.string().optional(),
parse_method: z.string().optional(),
lang: z.string().optional(),
fields: z.array(z.string()).optional(),
llm_id: z.string().optional(),
system_prompt: z.string().optional(),
}),
),
});
export type ParserFormSchemaType = z.infer<typeof FormSchema>;
function ParserItem({
name,
index,
fieldLength,
remove,
fileFormatOptions,
}: ParserItemProps) {
const { t } = useTranslation();
const form = useFormContext<ParserFormSchemaType>();
const ref = useRef(null);
const isHovering = useHover(ref);
const prefix = `${name}.${index}`;
const fileFormat = form.getValues(`setups.${index}.fileFormat`);
const values = form.getValues();
const parserList = values.setups.slice(); // Adding, deleting, or modifying the parser array will not change the reference.
const filteredFileFormatOptions = useMemo(() => {
const otherFileFormatList = parserList
.filter((_, idx) => idx !== index)
.map((x) => x.fileFormat);
return fileFormatOptions.filter((x) => {
return !otherFileFormatList.includes(x.value);
});
}, [fileFormatOptions, index, parserList]);
const Widget =
typeof fileFormat === 'string' && fileFormat in FileFormatWidgetMap
? FileFormatWidgetMap[fileFormat as keyof typeof FileFormatWidgetMap]
: () => <></>;
const handleFileTypeChange = useCallback(
(value: FileType) => {
form.setValue(
`setups.${index}.output_format`,
InitialOutputFormatMap[value],
{ shouldDirty: true, shouldValidate: true, shouldTouch: true },
);
},
[form, index],
);
return (
<section
className={cn('space-y-5 py-2.5 rounded-md', {
'bg-state-error-5': isHovering,
})}
>
<div className="flex justify-between items-center">
<span className="text-text-primary text-sm font-medium">
Parser {index + 1}
</span>
{index > 0 && (
<Button variant={'ghost'} onClick={() => remove(index)} ref={ref}>
<Trash2 />
</Button>
)}
</div>
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`fileFormat`, prefix)}
label={t('dataflow.fileFormats')}
>
{(field) => (
<SelectWithSearch
value={field.value}
onChange={(val) => {
field.onChange(val);
handleFileTypeChange(val as FileType);
}}
options={filteredFileFormatOptions}
></SelectWithSearch>
)}
</RAGFlowFormItem>
<Widget prefix={prefix} fileType={fileFormat as FileType}></Widget>
<div className="hidden">
<OutputFormatFormField
prefix={prefix}
fileType={fileFormat as FileType}
/>
</div>
{index < fieldLength - 1 && <Separator />}
</section>
);
}
const ParserForm = ({ node }: INextOperatorForm) => {
const { t } = useTranslation();
const defaultValues = useFormValues(initialParserValues, node);
const FileFormatOptions = buildOptions(
FileType,
t,
'dataflow.fileFormatOptions',
).filter(
(x) => x.value !== FileType.Video, // Temporarily hide the video option
);
const form = useForm<z.infer<typeof FormSchema>>({
defaultValues,
resolver: zodResolver(FormSchema),
shouldUnregister: true,
});
const name = 'setups';
const { fields, remove, append } = useFieldArray({
name,
control: form.control,
});
const add = useCallback(() => {
append({
fileFormat: null,
output_format: '',
parse_method: '',
lang: '',
fields: [],
llm_id: '',
});
}, [append]);
useWatchFormChange(node?.id, form);
return (
<Form {...form}>
<form className="px-5">
{fields.map((field, index) => {
return (
<ParserItem
key={field.id}
name={name}
index={index}
fieldLength={fields.length}
remove={remove}
fileFormatOptions={FileFormatOptions}
></ParserItem>
);
})}
{fields.length < FileFormatOptions.length && (
<BlockButton onClick={add} type="button" className="mt-2.5">
{t('dataflow.addParser')}
</BlockButton>
)}
</form>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</Form>
);
};
export default memo(ParserForm);

View File

@ -0,0 +1,3 @@
export type CommonProps = {
prefix: string;
};

View File

@ -0,0 +1,44 @@
import { ParseDocumentType } from '@/components/layout-recognize-form-field';
import { isEmpty } from 'lodash';
import { useEffect, useMemo } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { LanguageFormField, ParserMethodFormField } from './common-form-fields';
import { CommonProps } from './interface';
import { useSetInitialLanguage } from './use-set-initial-language';
import { buildFieldNameWithPrefix } from './utils';
export function PdfFormFields({ prefix }: CommonProps) {
const form = useFormContext();
const parseMethodName = buildFieldNameWithPrefix('parse_method', prefix);
const parseMethod = useWatch({
name: parseMethodName,
});
const languageShown = useMemo(() => {
return (
!isEmpty(parseMethod) &&
parseMethod !== ParseDocumentType.DeepDOC &&
parseMethod !== ParseDocumentType.PlainText
);
}, [parseMethod]);
useSetInitialLanguage({ prefix, languageShown });
useEffect(() => {
if (isEmpty(form.getValues(parseMethodName))) {
form.setValue(parseMethodName, ParseDocumentType.DeepDOC, {
shouldValidate: true,
shouldDirty: true,
});
}
}, [form, parseMethodName]);
return (
<>
<ParserMethodFormField prefix={prefix}></ParserMethodFormField>
{languageShown && <LanguageFormField prefix={prefix}></LanguageFormField>}
</>
);
}

View File

@ -0,0 +1,29 @@
import { crossLanguageOptions } from '@/components/cross-language-form-field';
import { isEmpty } from 'lodash';
import { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import { buildFieldNameWithPrefix } from './utils';
export function useSetInitialLanguage({
prefix,
languageShown,
}: {
prefix: string;
languageShown: boolean;
}) {
const form = useFormContext();
const lang = form.getValues(buildFieldNameWithPrefix('lang', prefix));
useEffect(() => {
if (languageShown && isEmpty(lang)) {
form.setValue(
buildFieldNameWithPrefix('lang', prefix),
crossLanguageOptions[0].value,
{
shouldValidate: true,
shouldDirty: true,
},
);
}
}, [form, lang, languageShown, prefix]);
}

View File

@ -0,0 +1,3 @@
export function buildFieldNameWithPrefix(name: string, prefix: string) {
return `${prefix}.${name}`;
}

View File

@ -0,0 +1,22 @@
import { LlmModelType } from '@/constants/knowledge';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks';
import {
LargeModelFormField,
OutputFormatFormFieldProps,
} from './common-form-fields';
export function VideoFormFields({ prefix }: OutputFormatFormFieldProps) {
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Speech2text,
]);
return (
<>
{/* Multimodal Model */}
<LargeModelFormField
prefix={prefix}
options={modelOptions}
></LargeModelFormField>
</>
);
}

View File

@ -0,0 +1,101 @@
import { DelimiterInput } from '@/components/delimiter-form-field';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { SliderInputFormField } from '@/components/slider-input-form-field';
import { BlockButton, Button } from '@/components/ui/button';
import { Form } from '@/components/ui/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trash2 } from 'lucide-react';
import { memo } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { initialSplitterValues } from '../../constant/pipeline';
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';
const outputList = buildOutputList(initialSplitterValues.outputs);
export const FormSchema = z.object({
chunk_token_size: z.number(),
delimiters: z.array(
z.object({
value: z.string().optional(),
}),
),
overlapped_percent: z.number(), // 0.0 - 0.3 , 0% - 30%
});
export type SplitterFormSchemaType = z.infer<typeof FormSchema>;
const SplitterForm = ({ node }: INextOperatorForm) => {
const defaultValues = useFormValues(initialSplitterValues, node);
const { t } = useTranslation();
const form = useForm<SplitterFormSchemaType>({
defaultValues,
resolver: zodResolver(FormSchema),
});
const name = 'delimiters';
const { fields, append, remove } = useFieldArray({
name: name,
control: form.control,
});
useWatchFormChange(node?.id, form);
return (
<Form {...form}>
<FormWrapper>
<SliderInputFormField
name="chunk_token_size"
max={2048}
label={t('knowledgeConfiguration.chunkTokenNumber')}
></SliderInputFormField>
<SliderInputFormField
name="overlapped_percent"
max={30}
min={0}
label={t('dataflow.overlappedPercent')}
></SliderInputFormField>
<section>
<span className="mb-2 inline-block">{t('flow.delimiters')}</span>
<div className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-2">
<div className="space-y-2 flex-1">
<RAGFlowFormItem
name={`${name}.${index}.value`}
label="delimiter"
labelClassName="!hidden"
>
<DelimiterInput className="!m-0"></DelimiterInput>
</RAGFlowFormItem>
</div>
<Button
type="button"
variant={'ghost'}
onClick={() => remove(index)}
>
<Trash2 />
</Button>
</div>
))}
</div>
</section>
<BlockButton onClick={() => append({ value: '\n' })}>
{t('common.add')}
</BlockButton>
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</Form>
);
};
export default memo(SplitterForm);

View File

@ -0,0 +1,91 @@
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { SliderInputFormField } from '@/components/slider-input-form-field';
import { Form } from '@/components/ui/form';
import { MultiSelect } from '@/components/ui/multi-select';
import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
initialTokenizerValues,
TokenizerFields,
TokenizerSearchMethod,
} 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 } from '../components/output';
const outputList = buildOutputList(initialTokenizerValues.outputs);
export const FormSchema = z.object({
search_method: z.array(z.string()).min(1),
filename_embd_weight: z.number(),
fields: z.string(),
});
export type TokenizerFormSchemaType = z.infer<typeof FormSchema>;
const TokenizerForm = ({ node }: INextOperatorForm) => {
const { t } = useTranslation();
const defaultValues = useFormValues(initialTokenizerValues, node);
const SearchMethodOptions = buildOptions(
TokenizerSearchMethod,
t,
`dataflow.tokenizerSearchMethodOptions`,
);
const FieldsOptions = buildOptions(
TokenizerFields,
t,
'dataflow.tokenizerFieldsOptions',
);
const form = useForm<TokenizerFormSchemaType>({
defaultValues,
resolver: zodResolver(FormSchema),
mode: 'onChange',
});
useWatchFormChange(node?.id, form);
return (
<Form {...form}>
<FormWrapper>
<RAGFlowFormItem
name="search_method"
label={t('dataflow.searchMethod')}
tooltip={t('dataflow.searchMethodTip')}
>
{(field) => (
<MultiSelect
options={SearchMethodOptions}
onValueChange={field.onChange}
defaultValue={field.value}
variant="inverted"
/>
)}
</RAGFlowFormItem>
<SliderInputFormField
name="filename_embd_weight"
label={t('dataflow.filenameEmbeddingWeight')}
max={0.5}
step={0.01}
></SliderInputFormField>
<RAGFlowFormItem name="fields" label={t('dataflow.fields')}>
{(field) => <SelectWithSearch options={FieldsOptions} {...field} />}
</RAGFlowFormItem>
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</Form>
);
};
export default memo(TokenizerForm);