Compare commits

...

2 Commits

Author SHA1 Message Date
ccb255919a Feat: Add HierarchicalMergerForm #9869 (#10122)
### What problem does this PR solve?
Feat:  Add HierarchicalMergerForm #9869

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-09-17 13:47:50 +08:00
b68c84b52e Feat: Add splitter form #9869 (#10115)
### What problem does this PR solve?

Feat: Add splitter form #9869
### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-09-17 09:36:54 +08:00
6 changed files with 269 additions and 4 deletions

View File

@ -44,6 +44,7 @@ import { CategorizeNode } from './node/categorize-node';
import ChunkerNode from './node/chunker-node';
import { InnerNextStepDropdown } from './node/dropdown/next-step-dropdown';
import { GenerateNode } from './node/generate-node';
import { HierarchicalMergerNode } from './node/hierarchical-merger-node';
import { InvokeNode } from './node/invoke-node';
import { IterationNode, IterationStartNode } from './node/iteration-node';
import { KeywordNode } from './node/keyword-node';
@ -84,6 +85,7 @@ export const nodeTypes: NodeTypes = {
chunkerNode: ChunkerNode,
tokenizerNode: TokenizerNode,
splitterNode: SplitterNode,
hierarchicalMergerNode: HierarchicalMergerNode,
};
const edgeTypes = {

View File

@ -378,9 +378,9 @@ export const initialStringTransformValues = {
export const initialParserValues = { outputs: {}, parser: [] };
export const initialSplitterValues = {};
export const initialSplitterValues = { outputs: {}, chunk_token_size: 512 };
export const initialHierarchicalMergerValues = {};
export const initialHierarchicalMergerValues = { outputs: {} };
export const CategorizeAnchorPointPositions = [
{ top: 1, right: 34 },
@ -466,7 +466,7 @@ export const NodeMap = {
[Operator.Chunker]: 'chunkerNode',
[Operator.Tokenizer]: 'tokenizerNode',
[Operator.Splitter]: 'splitterNode',
[Operator.HierarchicalMerger]: 'hierarchicalMergerrNode',
[Operator.HierarchicalMerger]: 'hierarchicalMergerNode',
};
export enum BeginQueryType {

View File

@ -7,6 +7,7 @@ import CodeForm from '../form/code-form';
import CrawlerForm from '../form/crawler-form';
import EmailForm from '../form/email-form';
import ExeSQLForm from '../form/exesql-form';
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
import InvokeForm from '../form/invoke-form';
import IterationForm from '../form/iteration-form';
import IterationStartForm from '../form/iteration-start-from';
@ -16,6 +17,7 @@ import ParserForm from '../form/parser-form';
import RelevantForm from '../form/relevant-form';
import RetrievalForm from '../form/retrieval-form/next';
import RewriteQuestionForm from '../form/rewrite-question-form';
import SplitterForm from '../form/splitter-form';
import StringTransformForm from '../form/string-transform-form';
import SwitchForm from '../form/switch-form';
import TokenizerForm from '../form/tokenizer-form';
@ -94,4 +96,10 @@ export const FormConfigMap = {
[Operator.Tokenizer]: {
component: TokenizerForm,
},
[Operator.Splitter]: {
component: SplitterForm,
},
[Operator.HierarchicalMerger]: {
component: HierarchicalMergerForm,
},
};

View File

@ -0,0 +1,158 @@
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, CardTitle } from '@/components/ui/card';
import { Form } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { zodResolver } from '@hookform/resolvers/zod';
import { X } from 'lucide-react';
import { memo } from 'react';
import { useFieldArray, useForm, useFormContext } from 'react-hook-form';
import { z } from 'zod';
import { initialHierarchicalMergerValues } 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(initialHierarchicalMergerValues.outputs);
enum Hierarchy {
H1 = '1',
H2 = '2',
H3 = '3',
H4 = '4',
H5 = '5',
}
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.number(),
levels: z.array(
z.object({
expressions: z.array(z.object({ expression: z.string() })),
}),
),
});
type RegularExpressionsProps = {
index: number;
parentName: string;
removeParent: (index: number) => void;
};
export function RegularExpressions({
index,
parentName,
removeParent,
}: RegularExpressionsProps) {
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">
<CardTitle>H{index}</CardTitle>
<Button
type="button"
variant={'ghost'}
onClick={() => removeParent(index)}
>
<X />
</Button>
</CardHeader>
<CardContent>
<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>
<Button
type="button"
variant={'ghost'}
onClick={() => remove(index)}
>
<X />
</Button>
</div>
))}
</section>
<BlockButton
onClick={() => append({ expression: '' })}
className="mt-6"
>
Add
</BlockButton>
</CardContent>
</Card>
);
}
const HierarchicalMergerForm = ({ node }: INextOperatorForm) => {
const defaultValues = useFormValues(initialHierarchicalMergerValues, node);
const form = useForm<z.infer<typeof FormSchema>>({
defaultValues,
resolver: zodResolver(FormSchema),
});
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={'hierarchy'}>
<SelectWithSearch options={HierarchyOptions}></SelectWithSearch>
</RAGFlowFormItem>
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-2">
<div className="space-y-2 flex-1">
<RegularExpressions
parentName={name}
index={index}
removeParent={remove}
></RegularExpressions>
</div>
</div>
))}
<BlockButton onClick={() => append({ expressions: [] })}>
Add
</BlockButton>
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</Form>
);
};
export default memo(HierarchicalMergerForm);

View File

@ -0,0 +1,95 @@
import { FormContainer } from '@/components/form-container';
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 { Input } from '@/components/ui/input';
import { zodResolver } from '@hookform/resolvers/zod';
import { X } from 'lucide-react';
import { memo } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { z } from 'zod';
import { initialChunkerValues, initialSplitterValues } 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(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
});
const SplitterForm = ({ node }: INextOperatorForm) => {
const defaultValues = useFormValues(initialChunkerValues, node);
const form = useForm<z.infer<typeof FormSchema>>({
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>
<FormContainer>
<SliderInputFormField
name="chunk_token_size"
max={2048}
label="chunk_token_size"
></SliderInputFormField>
<SliderInputFormField
name="overlapped_percent"
max={0.3}
min={0.1}
step={0.01}
label="overlapped_percent"
></SliderInputFormField>
<span>delimiters</span>
{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"
>
<Input className="!m-0"></Input>
</RAGFlowFormItem>
</div>
<Button
type="button"
variant={'ghost'}
onClick={() => remove(index)}
>
<X />
</Button>
</div>
))}
<BlockButton onClick={() => append({ value: '' })}>Add</BlockButton>
</FormContainer>
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</Form>
);
};
export default memo(SplitterForm);

View File

@ -35,7 +35,9 @@ export function buildOptions(
) {
if (t) {
return Object.values(data).map((val) => ({
label: t(`${prefix ? prefix + '.' : ''}${val.toLowerCase()}`),
label: t(
`${prefix ? prefix + '.' : ''}${typeof val === 'string' ? val.toLowerCase() : val}`,
),
value: val,
}));
}