Feat: Dynamically increase the configuration of the parser operator #9869 (#10060)

### What problem does this PR solve?

Feat: Dynamically increase the configuration of the parser operator
#9869
### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-09-12 10:14:26 +08:00
committed by GitHub
parent 63d7382dc9
commit 3404469e2a
11 changed files with 243 additions and 127 deletions

View File

@ -0,0 +1,25 @@
import { LlmModelType } from '@/constants/knowledge';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks';
import { useTranslation } from 'react-i18next';
import { SelectWithSearch } from '../originui/select-with-search';
import { RAGFlowFormItem } from '../ragflow-form';
type LLMFormFieldProps = {
options?: any[];
name?: string;
};
export function LLMFormField({ options, name }: LLMFormFieldProps) {
const { t } = useTranslation();
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Chat,
LlmModelType.Image2text,
]);
return (
<RAGFlowFormItem name={name || 'llm_id'} label={t('model')}>
<SelectWithSearch options={options || modelOptions}></SelectWithSearch>
</RAGFlowFormItem>
);
}

View File

@ -1,11 +1,9 @@
import { LlmModelType, ModelVariableType } from '@/constants/knowledge'; import { ModelVariableType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks';
import { camelCase } from 'lodash'; import { camelCase } from 'lodash';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { SelectWithSearch } from '../originui/select-with-search';
import { import {
FormControl, FormControl,
FormField, FormField,
@ -20,6 +18,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '../ui/select'; } from '../ui/select';
import { LLMFormField } from './llm-form-field';
import { SliderInputSwitchFormField } from './slider'; import { SliderInputSwitchFormField } from './slider';
import { useHandleFreedomChange } from './use-watch-change'; import { useHandleFreedomChange } from './use-watch-change';
@ -61,11 +60,6 @@ export function LlmSettingFieldItems({
const form = useFormContext(); const form = useFormContext();
const { t } = useTranslate('chat'); const { t } = useTranslate('chat');
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Chat,
LlmModelType.Image2text,
]);
const getFieldWithPrefix = useCallback( const getFieldWithPrefix = useCallback(
(name: string) => { (name: string) => {
return prefix ? `${prefix}.${name}` : name; return prefix ? `${prefix}.${name}` : name;
@ -82,22 +76,7 @@ export function LlmSettingFieldItems({
return ( return (
<div className="space-y-5"> <div className="space-y-5">
<FormField <LLMFormField options={options}></LLMFormField>
control={form.control}
name={'llm_id'}
render={({ field }) => (
<FormItem>
<FormLabel>{t('model')}</FormLabel>
<FormControl>
<SelectWithSearch
options={options || modelOptions}
{...field}
></SelectWithSearch>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name={'parameter'} name={'parameter'}

View File

@ -523,3 +523,15 @@ export enum AgentExceptionMethod {
Comment = 'comment', Comment = 'comment',
Goto = 'goto', Goto = 'goto',
} }
export enum FileType {
PDF = 'pdf',
Spreadsheet = 'spreadsheet',
Image = 'image',
Email = 'email',
TextMarkdown = 'text&markdown',
Docx = 'docx',
PowerPoint = 'ppt',
Video = 'video',
Audio = 'audio',
}

View File

@ -0,0 +1,46 @@
import { LLMFormField } from '@/components/llm-setting-items/llm-form-field';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { CommonProps } from './interface';
import { buildFieldNameWithPrefix } from './utils';
export function LanguageFormField({ prefix }: CommonProps) {
return (
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`lang`, prefix)}
label="lang"
>
<SelectWithSearch options={[]}></SelectWithSearch>
</RAGFlowFormItem>
);
}
export function OutputFormatFormField({ prefix }: CommonProps) {
return (
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`output_format`, prefix)}
label="output_format"
>
<SelectWithSearch options={[]}></SelectWithSearch>
</RAGFlowFormItem>
);
}
export function ParserMethodFormField({ prefix }: CommonProps) {
return (
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`parse_method`, prefix)}
label="parse_method"
>
<SelectWithSearch options={[]}></SelectWithSearch>
</RAGFlowFormItem>
);
}
export function LargeModelFormField({ prefix }: CommonProps) {
return (
<LLMFormField
name={buildFieldNameWithPrefix('llm_id', prefix)}
></LLMFormField>
);
}

View File

@ -0,0 +1,31 @@
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { buildOptions } from '@/utils/form';
import { OutputFormatFormField } from './common-form-fields';
import { CommonProps } from './interface';
import { buildFieldNameWithPrefix } from './utils';
const options = buildOptions([
'from',
'to',
'cc',
'bcc',
'date',
'subject',
'body',
'attachments',
]);
export function EmailFormFields({ prefix }: CommonProps) {
return (
<>
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`fields`, prefix)}
label="fields"
>
<SelectWithSearch options={options}></SelectWithSearch>
</RAGFlowFormItem>
<OutputFormatFormField prefix={prefix}></OutputFormatFormField>
</>
);
}

View File

@ -0,0 +1,17 @@
import {
LargeModelFormField,
OutputFormatFormField,
ParserMethodFormField,
} from './common-form-fields';
import { CommonProps } from './interface';
export function ImageFormFields({ prefix }: CommonProps) {
return (
<>
<ParserMethodFormField prefix={prefix}></ParserMethodFormField>
{/* Multimodal Model */}
<LargeModelFormField prefix={prefix}></LargeModelFormField>
<OutputFormatFormField prefix={prefix}></OutputFormatFormField>
</>
);
}

View File

@ -1,135 +1,101 @@
import { FormContainer } from '@/components/form-container'; import { FormContainer } from '@/components/form-container';
import NumberInput from '@/components/originui/number-input';
import { SelectWithSearch } from '@/components/originui/select-with-search'; import { SelectWithSearch } from '@/components/originui/select-with-search';
import { import { RAGFlowFormItem } from '@/components/ragflow-form';
Form, import { BlockButton, Button } from '@/components/ui/button';
FormControl, import { Form } from '@/components/ui/form';
FormField, import { buildOptions } from '@/utils/form';
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { useTranslate } from '@/hooks/common-hooks';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react'; import { Trash2 } from 'lucide-react';
import { useForm, useFormContext } from 'react-hook-form'; import { memo, useCallback } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { initialParserValues } from '../../constant'; import { FileType, initialParserValues } 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 { GoogleCountryOptions, GoogleLanguageOptions } from '../../options';
import { buildOutputList } from '../../utils/build-output-list'; import { buildOutputList } from '../../utils/build-output-list';
import { ApiKeyField } from '../components/api-key-field';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output'; import { Output } from '../components/output';
import { QueryVariable } from '../components/query-variable'; 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 outputList = buildOutputList(initialParserValues.outputs);
export const GoogleFormPartialSchema = { const FileFormatOptions = buildOptions(FileType);
api_key: z.string(),
country: z.string(), const FileFormatWidgetMap = {
language: z.string(), [FileType.PDF]: PdfFormFields,
[FileType.Video]: VideoFormFields,
[FileType.Audio]: VideoFormFields,
[FileType.Email]: EmailFormFields,
[FileType.Image]: ImageFormFields,
}; };
export const FormSchema = z.object({ export const FormSchema = z.object({
...GoogleFormPartialSchema, parser: z.array(
q: z.string(), z.object({
start: z.number(), fileFormat: z.string().optional(),
num: z.number(), }),
),
}); });
export function GoogleFormWidgets() {
const form = useFormContext();
const { t } = useTranslate('flow');
return (
<>
<FormField
control={form.control}
name={`country`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>{t('country')}</FormLabel>
<FormControl>
<SelectWithSearch
{...field}
options={GoogleCountryOptions}
></SelectWithSearch>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`language`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>{t('language')}</FormLabel>
<FormControl>
<SelectWithSearch
{...field}
options={GoogleLanguageOptions}
></SelectWithSearch>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
);
}
const ParserForm = ({ node }: INextOperatorForm) => { const ParserForm = ({ node }: INextOperatorForm) => {
const { t } = useTranslate('flow');
const defaultValues = useFormValues(initialParserValues, node); const defaultValues = useFormValues(initialParserValues, node);
const form = useForm<z.infer<typeof FormSchema>>({ const form = useForm<z.infer<typeof FormSchema>>({
defaultValues, defaultValues,
resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema),
shouldUnregister: true,
}); });
const name = 'parser';
const { fields, remove, append } = useFieldArray({
name,
control: form.control,
});
const add = useCallback(() => {
append({
fileFormat: undefined,
});
}, [append]);
useWatchFormChange(node?.id, form); useWatchFormChange(node?.id, form);
return ( return (
<Form {...form}> <Form {...form}>
<FormWrapper> {fields.map((field, index) => {
<FormContainer> const prefix = `${name}.${index}`;
<QueryVariable name="q"></QueryVariable> const fileFormat = form.getValues(`${name}.${index}.fileFormat`);
</FormContainer>
<FormContainer> const Widget =
<ApiKeyField placeholder={t('apiKeyPlaceholder')}></ApiKeyField> fileFormat && fileFormat in FileFormatWidgetMap
<FormField ? FileFormatWidgetMap[
control={form.control} fileFormat as keyof typeof FileFormatWidgetMap
name={`start`} ]
render={({ field }) => ( : OutputFormatFormField;
<FormItem> return (
<FormLabel>{t('flowStart')}</FormLabel> <FormContainer key={field.id}>
<FormControl> <div className="flex justify-between items-center">
<NumberInput {...field} className="w-full"></NumberInput> <span className="text-text-primary text-sm">Parser {index}</span>
</FormControl> <Button variant={'ghost'} onClick={() => remove(index)}>
<FormMessage /> <Trash2 />
</FormItem> </Button>
)} </div>
/> <RAGFlowFormItem
<FormField name={buildFieldNameWithPrefix(`fileFormat`, prefix)}
control={form.control} label="File Formats"
name={`num`} >
render={({ field }) => ( <SelectWithSearch options={FileFormatOptions}></SelectWithSearch>
<FormItem> </RAGFlowFormItem>
<FormLabel>{t('flowNum')}</FormLabel> <Widget prefix={prefix}></Widget>
<FormControl> </FormContainer>
<NumberInput {...field} className="w-full"></NumberInput> );
</FormControl> })}
<FormMessage /> <BlockButton onClick={add}>Add Parser</BlockButton>
</FormItem>
)}
/>
<GoogleFormWidgets></GoogleFormWidgets>
</FormContainer>
</FormWrapper>
<div className="p-5"> <div className="p-5">
<Output list={outputList}></Output> <Output list={outputList}></Output>
</div> </div>

View File

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

View File

@ -0,0 +1,19 @@
import {
LanguageFormField,
LargeModelFormField,
OutputFormatFormField,
ParserMethodFormField,
} from './common-form-fields';
import { CommonProps } from './interface';
export function PdfFormFields({ prefix }: CommonProps) {
return (
<>
<ParserMethodFormField prefix={prefix}></ParserMethodFormField>
{/* Multimodal Model */}
<LargeModelFormField prefix={prefix}></LargeModelFormField>
<LanguageFormField prefix={prefix}></LanguageFormField>
<OutputFormatFormField prefix={prefix}></OutputFormatFormField>
</>
);
}

View File

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

View File

@ -0,0 +1,15 @@
import {
LargeModelFormField,
OutputFormatFormField,
} from './common-form-fields';
import { CommonProps } from './interface';
export function VideoFormFields({ prefix }: CommonProps) {
return (
<>
{/* Multimodal Model */}
<LargeModelFormField prefix={prefix}></LargeModelFormField>
<OutputFormatFormField prefix={prefix}></OutputFormatFormField>
</>
);
}