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

View File

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