### 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)tags/v0.19.1
| @@ -1,15 +1,14 @@ | |||
| import LLMLabel from '@/components/llm-select/llm-label'; | |||
| import { useTheme } from '@/components/theme-provider'; | |||
| import { ICategorizeNode } from '@/interfaces/database/flow'; | |||
| import { Handle, NodeProps, Position } from '@xyflow/react'; | |||
| import { Flex } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { NodeProps, Position } from '@xyflow/react'; | |||
| import { get } from 'lodash'; | |||
| import { memo } from 'react'; | |||
| import { CommonHandle } from './handle'; | |||
| import { RightHandleStyle } from './handle-icon'; | |||
| import { useBuildCategorizeHandlePositions } from './hooks'; | |||
| import styles from './index.less'; | |||
| 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({ | |||
| id, | |||
| @@ -17,54 +16,42 @@ export function InnerCategorizeNode({ | |||
| selected, | |||
| }: NodeProps<ICategorizeNode>) { | |||
| const { positions } = useBuildCategorizeHandlePositions({ data, id }); | |||
| const { theme } = useTheme(); | |||
| return ( | |||
| <section | |||
| className={classNames( | |||
| styles.logicNode, | |||
| theme === 'dark' ? styles.dark : '', | |||
| { | |||
| [styles.selectedNode]: selected, | |||
| }, | |||
| )} | |||
| > | |||
| <Handle | |||
| type="target" | |||
| position={Position.Left} | |||
| isConnectable | |||
| className={styles.handle} | |||
| id={'a'} | |||
| ></Handle> | |||
| <ToolBar selected={selected} id={id} label={data.label}> | |||
| <NodeWrapper> | |||
| <CommonHandle | |||
| type="target" | |||
| position={Position.Left} | |||
| isConnectable | |||
| id={'a'} | |||
| ></CommonHandle> | |||
| <NodeHeader | |||
| id={id} | |||
| name={data.name} | |||
| label={data.label} | |||
| className={styles.nodeHeader} | |||
| ></NodeHeader> | |||
| <NodeHeader id={id} name={data.name} label={data.label}></NodeHeader> | |||
| <Flex vertical gap={8}> | |||
| <div className={styles.nodeText}> | |||
| <LLMLabel value={get(data, 'form.llm_id')}></LLMLabel> | |||
| </div> | |||
| {positions.map((position, idx) => { | |||
| return ( | |||
| <div key={idx}> | |||
| <div className={styles.nodeText}>{position.text}</div> | |||
| <Handle | |||
| key={position.text} | |||
| id={position.text} | |||
| type="source" | |||
| position={Position.Right} | |||
| isConnectable | |||
| className={styles.handle} | |||
| style={{ ...RightHandleStyle, top: position.top }} | |||
| ></Handle> | |||
| </div> | |||
| ); | |||
| })} | |||
| </Flex> | |||
| </section> | |||
| <section className="flex flex-col gap-2"> | |||
| <div className={'bg-background-card rounded-sm px-1'}> | |||
| <LLMLabel value={get(data, 'form.llm_id')}></LLMLabel> | |||
| </div> | |||
| {positions.map((position, idx) => { | |||
| return ( | |||
| <div key={idx}> | |||
| <div className={'bg-background-card rounded-sm p-1'}> | |||
| {position.text} | |||
| </div> | |||
| <CommonHandle | |||
| key={position.text} | |||
| id={position.text} | |||
| type="source" | |||
| position={Position.Right} | |||
| isConnectable | |||
| style={{ ...RightHandleStyle, top: position.top }} | |||
| ></CommonHandle> | |||
| </div> | |||
| ); | |||
| })} | |||
| </section> | |||
| </NodeWrapper> | |||
| </ToolBar> | |||
| ); | |||
| } | |||
| @@ -8,7 +8,7 @@ export function NodeWrapper({ | |||
| return ( | |||
| <section | |||
| 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, | |||
| )} | |||
| > | |||
| @@ -7,10 +7,10 @@ import { SwitchOperatorOptions } from '../../constant'; | |||
| import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query'; | |||
| import { CommonHandle } from './handle'; | |||
| import { RightHandleStyle } from './handle-icon'; | |||
| import { useBuildSwitchHandlePositions } from './hooks'; | |||
| import NodeHeader from './node-header'; | |||
| import { NodeWrapper } from './node-wrapper'; | |||
| import { ToolBar } from './toolbar'; | |||
| import { useBuildSwitchHandlePositions } from './use-build-switch-handle-positions'; | |||
| const getConditionKey = (idx: number, length: number) => { | |||
| 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 get from 'lodash/get'; | |||
| import { useEffect, useMemo } from 'react'; | |||
| import { SwitchElseTo } from '../../constant'; | |||
| import { | |||
| ICategorizeItemResult, | |||
| ISwitchCondition, | |||
| RAGFlowNodeType, | |||
| } from '@/interfaces/database/flow'; | |||
| 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 = ({ | |||
| data, | |||
| id, | |||
| @@ -63,6 +18,10 @@ export const useBuildSwitchHandlePositions = ({ | |||
| return get(data, 'form.conditions', []); | |||
| }, [data]); | |||
| useEffect(() => { | |||
| console.info('xxx0000'); | |||
| }, [conditions]); | |||
| const positions = useMemo(() => { | |||
| const list: Array<{ | |||
| text: string; | |||
| @@ -72,12 +31,12 @@ export const useBuildSwitchHandlePositions = ({ | |||
| }> = []; | |||
| [...conditions, ''].forEach((x, idx) => { | |||
| let top = idx === 0 ? 58 + 20 : list[idx - 1].top + 10; // case number (Case 1) height + flex gap | |||
| if (idx - 1 >= 0) { | |||
| let top = idx === 0 ? 53 : list[idx - 1].top + 10 + 14; // case number (Case 1) height + flex gap | |||
| if (idx >= 1) { | |||
| const previousItems = conditions[idx - 1]?.items ?? []; | |||
| if (previousItems.length > 0) { | |||
| // 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 | |||
| } | |||
| } | |||
| @@ -27,9 +27,9 @@ import { | |||
| } from '../../constant'; | |||
| import { useBuildFormSelectOptions } from '../../form-hooks'; | |||
| import { useBuildComponentIdAndBeginOptions } from '../../hooks/use-get-begin-query'; | |||
| import { useWatchFormChange } from '../../hooks/use-watch-form-change'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import { useValues } from './use-values'; | |||
| import { useWatchFormChange } from './use-watch-change'; | |||
| const ConditionKey = 'conditions'; | |||
| const ItemKey = 'items'; | |||
| @@ -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]); | |||
| } | |||