mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Modify the anchor point positioning of the classification operator node #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,15 +1,14 @@
|
|||||||
import LLMLabel from '@/components/llm-select/llm-label';
|
import LLMLabel from '@/components/llm-select/llm-label';
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { ICategorizeNode } from '@/interfaces/database/flow';
|
import { ICategorizeNode } from '@/interfaces/database/flow';
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
import { NodeProps, Position } from '@xyflow/react';
|
||||||
import { Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { CommonHandle } from './handle';
|
||||||
import { RightHandleStyle } from './handle-icon';
|
import { RightHandleStyle } from './handle-icon';
|
||||||
import { useBuildCategorizeHandlePositions } from './hooks';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
import NodeHeader from './node-header';
|
||||||
|
import { NodeWrapper } from './node-wrapper';
|
||||||
|
import { ToolBar } from './toolbar';
|
||||||
|
import { useBuildCategorizeHandlePositions } from './use-build-categorize-handle-positions';
|
||||||
|
|
||||||
export function InnerCategorizeNode({
|
export function InnerCategorizeNode({
|
||||||
id,
|
id,
|
||||||
@ -17,54 +16,42 @@ export function InnerCategorizeNode({
|
|||||||
selected,
|
selected,
|
||||||
}: NodeProps<ICategorizeNode>) {
|
}: NodeProps<ICategorizeNode>) {
|
||||||
const { positions } = useBuildCategorizeHandlePositions({ data, id });
|
const { positions } = useBuildCategorizeHandlePositions({ data, id });
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<section
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
className={classNames(
|
<NodeWrapper>
|
||||||
styles.logicNode,
|
<CommonHandle
|
||||||
theme === 'dark' ? styles.dark : '',
|
type="target"
|
||||||
{
|
position={Position.Left}
|
||||||
[styles.selectedNode]: selected,
|
isConnectable
|
||||||
},
|
id={'a'}
|
||||||
)}
|
></CommonHandle>
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable
|
|
||||||
className={styles.handle}
|
|
||||||
id={'a'}
|
|
||||||
></Handle>
|
|
||||||
|
|
||||||
<NodeHeader
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={styles.nodeHeader}
|
|
||||||
></NodeHeader>
|
|
||||||
|
|
||||||
<Flex vertical gap={8}>
|
<section className="flex flex-col gap-2">
|
||||||
<div className={styles.nodeText}>
|
<div className={'bg-background-card rounded-sm px-1'}>
|
||||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
||||||
</div>
|
</div>
|
||||||
{positions.map((position, idx) => {
|
{positions.map((position, idx) => {
|
||||||
return (
|
return (
|
||||||
<div key={idx}>
|
<div key={idx}>
|
||||||
<div className={styles.nodeText}>{position.text}</div>
|
<div className={'bg-background-card rounded-sm p-1'}>
|
||||||
<Handle
|
{position.text}
|
||||||
key={position.text}
|
</div>
|
||||||
id={position.text}
|
<CommonHandle
|
||||||
type="source"
|
key={position.text}
|
||||||
position={Position.Right}
|
id={position.text}
|
||||||
isConnectable
|
type="source"
|
||||||
className={styles.handle}
|
position={Position.Right}
|
||||||
style={{ ...RightHandleStyle, top: position.top }}
|
isConnectable
|
||||||
></Handle>
|
style={{ ...RightHandleStyle, top: position.top }}
|
||||||
</div>
|
></CommonHandle>
|
||||||
);
|
</div>
|
||||||
})}
|
);
|
||||||
</Flex>
|
})}
|
||||||
</section>
|
</section>
|
||||||
|
</NodeWrapper>
|
||||||
|
</ToolBar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export function NodeWrapper({
|
|||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-background-header-bar p-2.5 rounded-md w-[200px]',
|
'bg-background-header-bar p-2.5 rounded-md w-[200px] text-xs',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -7,10 +7,10 @@ import { SwitchOperatorOptions } from '../../constant';
|
|||||||
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
|
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
|
||||||
import { CommonHandle } from './handle';
|
import { CommonHandle } from './handle';
|
||||||
import { RightHandleStyle } from './handle-icon';
|
import { RightHandleStyle } from './handle-icon';
|
||||||
import { useBuildSwitchHandlePositions } from './hooks';
|
|
||||||
import NodeHeader from './node-header';
|
import NodeHeader from './node-header';
|
||||||
import { NodeWrapper } from './node-wrapper';
|
import { NodeWrapper } from './node-wrapper';
|
||||||
import { ToolBar } from './toolbar';
|
import { ToolBar } from './toolbar';
|
||||||
|
import { useBuildSwitchHandlePositions } from './use-build-switch-handle-positions';
|
||||||
|
|
||||||
const getConditionKey = (idx: number, length: number) => {
|
const getConditionKey = (idx: number, length: number) => {
|
||||||
if (idx === 0 && length !== 1) {
|
if (idx === 0 && length !== 1) {
|
||||||
|
|||||||
@ -0,0 +1,45 @@
|
|||||||
|
import { ICategorizeItemResult } from '@/interfaces/database/agent';
|
||||||
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
|
import { useUpdateNodeInternals } from '@xyflow/react';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
export const useBuildCategorizeHandlePositions = ({
|
||||||
|
data,
|
||||||
|
id,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
data: RAGFlowNodeType['data'];
|
||||||
|
}) => {
|
||||||
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
|
|
||||||
|
const categoryData: ICategorizeItemResult = useMemo(() => {
|
||||||
|
return get(data, `form.category_description`, {});
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const positions = useMemo(() => {
|
||||||
|
const list: Array<{
|
||||||
|
text: string;
|
||||||
|
top: number;
|
||||||
|
idx: number;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
Object.keys(categoryData)
|
||||||
|
.sort((a, b) => categoryData[a].index - categoryData[b].index)
|
||||||
|
.forEach((x, idx) => {
|
||||||
|
list.push({
|
||||||
|
text: x,
|
||||||
|
idx,
|
||||||
|
top: idx === 0 ? 86 : list[idx - 1].top + 8 + 24,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}, [categoryData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateNodeInternals(id);
|
||||||
|
}, [id, updateNodeInternals, categoryData]);
|
||||||
|
|
||||||
|
return { positions };
|
||||||
|
};
|
||||||
@ -1,55 +1,10 @@
|
|||||||
|
import { ISwitchCondition, RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { useUpdateNodeInternals } from '@xyflow/react';
|
import { useUpdateNodeInternals } from '@xyflow/react';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { SwitchElseTo } from '../../constant';
|
import { SwitchElseTo } from '../../constant';
|
||||||
|
|
||||||
import {
|
|
||||||
ICategorizeItemResult,
|
|
||||||
ISwitchCondition,
|
|
||||||
RAGFlowNodeType,
|
|
||||||
} from '@/interfaces/database/flow';
|
|
||||||
import { generateSwitchHandleText } from '../../utils';
|
import { generateSwitchHandleText } from '../../utils';
|
||||||
|
|
||||||
export const useBuildCategorizeHandlePositions = ({
|
|
||||||
data,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
data: RAGFlowNodeType['data'];
|
|
||||||
}) => {
|
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
|
||||||
|
|
||||||
const categoryData: ICategorizeItemResult = useMemo(() => {
|
|
||||||
return get(data, `form.category_description`, {});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const positions = useMemo(() => {
|
|
||||||
const list: Array<{
|
|
||||||
text: string;
|
|
||||||
top: number;
|
|
||||||
idx: number;
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
Object.keys(categoryData)
|
|
||||||
.sort((a, b) => categoryData[a].index - categoryData[b].index)
|
|
||||||
.forEach((x, idx) => {
|
|
||||||
list.push({
|
|
||||||
text: x,
|
|
||||||
idx,
|
|
||||||
top: idx === 0 ? 98 + 20 : list[idx - 1].top + 8 + 26,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}, [categoryData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateNodeInternals(id);
|
|
||||||
}, [id, updateNodeInternals, categoryData]);
|
|
||||||
|
|
||||||
return { positions };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useBuildSwitchHandlePositions = ({
|
export const useBuildSwitchHandlePositions = ({
|
||||||
data,
|
data,
|
||||||
id,
|
id,
|
||||||
@ -63,6 +18,10 @@ export const useBuildSwitchHandlePositions = ({
|
|||||||
return get(data, 'form.conditions', []);
|
return get(data, 'form.conditions', []);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.info('xxx0000');
|
||||||
|
}, [conditions]);
|
||||||
|
|
||||||
const positions = useMemo(() => {
|
const positions = useMemo(() => {
|
||||||
const list: Array<{
|
const list: Array<{
|
||||||
text: string;
|
text: string;
|
||||||
@ -72,12 +31,12 @@ export const useBuildSwitchHandlePositions = ({
|
|||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
[...conditions, ''].forEach((x, idx) => {
|
[...conditions, ''].forEach((x, idx) => {
|
||||||
let top = idx === 0 ? 58 + 20 : list[idx - 1].top + 10; // case number (Case 1) height + flex gap
|
let top = idx === 0 ? 53 : list[idx - 1].top + 10 + 14; // case number (Case 1) height + flex gap
|
||||||
if (idx - 1 >= 0) {
|
if (idx >= 1) {
|
||||||
const previousItems = conditions[idx - 1]?.items ?? [];
|
const previousItems = conditions[idx - 1]?.items ?? [];
|
||||||
if (previousItems.length > 0) {
|
if (previousItems.length > 0) {
|
||||||
// top += 12; // ConditionBlock padding
|
// top += 12; // ConditionBlock padding
|
||||||
top += previousItems.length * 22; // condition variable height
|
top += previousItems.length * 26; // condition variable height
|
||||||
// top += (previousItems.length - 1) * 25; // operator height
|
// top += (previousItems.length - 1) * 25; // operator height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,9 +27,9 @@ import {
|
|||||||
} from '../../constant';
|
} from '../../constant';
|
||||||
import { useBuildFormSelectOptions } from '../../form-hooks';
|
import { useBuildFormSelectOptions } from '../../form-hooks';
|
||||||
import { useBuildComponentIdAndBeginOptions } from '../../hooks/use-get-begin-query';
|
import { useBuildComponentIdAndBeginOptions } from '../../hooks/use-get-begin-query';
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
import { IOperatorForm } from '../../interface';
|
||||||
import { useValues } from './use-values';
|
import { useValues } from './use-values';
|
||||||
|
import { useWatchFormChange } from './use-watch-change';
|
||||||
|
|
||||||
const ConditionKey = 'conditions';
|
const ConditionKey = 'conditions';
|
||||||
const ItemKey = 'items';
|
const ItemKey = 'items';
|
||||||
|
|||||||
23
web/src/pages/agent/form/switch-form/use-watch-change.ts
Normal file
23
web/src/pages/agent/form/switch-form/use-watch-change.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ISwitchCondition } from '@/interfaces/database/agent';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||||
|
import useGraphStore from '../../store';
|
||||||
|
|
||||||
|
export function useWatchFormChange(id?: string, form?: UseFormReturn) {
|
||||||
|
let values = useWatch({ control: form?.control });
|
||||||
|
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Manually triggered form updates are synchronized to the canvas
|
||||||
|
if (id && form?.formState.isDirty) {
|
||||||
|
values = form?.getValues();
|
||||||
|
let nextValues: any = {
|
||||||
|
...values,
|
||||||
|
conditions:
|
||||||
|
values?.conditions?.map((x: ISwitchCondition) => ({ ...x })) ?? [], // Changing the form value with useFieldArray does not change the array reference
|
||||||
|
};
|
||||||
|
|
||||||
|
updateNodeForm(id, nextValues);
|
||||||
|
}
|
||||||
|
}, [form?.formState.isDirty, id, updateNodeForm, values]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user