### What problem does this PR solve? Feat: Use react-hook-form to synchronize the data of the categorize form to the agent node. #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.17.1
| const { t } = useTranslate('flow'); | const { t } = useTranslate('flow'); | ||||
| const { handleValuesChange } = useHandleFormValuesChange(node?.id); | |||||
| const { handleValuesChange } = useHandleFormValuesChange( | |||||
| operatorName, | |||||
| node?.id, | |||||
| form, | |||||
| ); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (visible) { | |||||
| if (visible && !form.formState.isDirty) { | |||||
| if (node?.id !== previousId.current) { | if (node?.id !== previousId.current) { | ||||
| // form.resetFields(); | |||||
| form.reset(); | form.reset(); | ||||
| form.clearErrors(); | form.clearErrors(); | ||||
| } | } |
| import { | |||||
| ICategorizeItem, | |||||
| ICategorizeItemResult, | |||||
| } from '@/interfaces/database/flow'; | |||||
| import omit from 'lodash/omit'; | |||||
| import { useCallback } from 'react'; | |||||
| import { IOperatorForm } from '../../interface'; | |||||
| /** | |||||
| * Convert the list in the following form into an object | |||||
| * { | |||||
| "items": [ | |||||
| { | |||||
| "name": "Categorize 1", | |||||
| "description": "111", | |||||
| "examples": "ddd", | |||||
| "to": "Retrieval:LazyEelsStick" | |||||
| } | |||||
| ] | |||||
| } | |||||
| */ | |||||
| const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => { | |||||
| return list.reduce<ICategorizeItemResult>((pre, cur) => { | |||||
| if (cur?.name) { | |||||
| pre[cur.name] = omit(cur, 'name'); | |||||
| } | |||||
| return pre; | |||||
| }, {}); | |||||
| }; | |||||
| export const useHandleFormValuesChange = ({ | |||||
| onValuesChange, | |||||
| }: IOperatorForm) => { | |||||
| const handleValuesChange = useCallback( | |||||
| (changedValues: any, values: any) => { | |||||
| onValuesChange?.(changedValues, { | |||||
| ...omit(values, 'items'), | |||||
| category_description: buildCategorizeObjectFromList(values.items), | |||||
| }); | |||||
| }, | |||||
| [onValuesChange], | |||||
| ); | |||||
| return { handleValuesChange }; | |||||
| }; |
| import DynamicCategorize from './dynamic-categorize'; | import DynamicCategorize from './dynamic-categorize'; | ||||
| const CategorizeForm = ({ form, node }: INextOperatorForm) => { | const CategorizeForm = ({ form, node }: INextOperatorForm) => { | ||||
| // const { handleValuesChange } = useHandleFormValuesChange({ | |||||
| // form, | |||||
| // nodeId: node?.id, | |||||
| // onValuesChange, | |||||
| // }); | |||||
| return ( | return ( | ||||
| <Form | |||||
| // onValuesChange={handleValuesChange} | |||||
| {...form} | |||||
| > | |||||
| <Form {...form}> | |||||
| <form | <form | ||||
| className="space-y-6 p-5 overflow-auto max-h-[76vh]" | className="space-y-6 p-5 overflow-auto max-h-[76vh]" | ||||
| onSubmit={(e) => { | onSubmit={(e) => { |
| } from '@/interfaces/database/flow'; | } from '@/interfaces/database/flow'; | ||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import { humanId } from 'human-id'; | import { humanId } from 'human-id'; | ||||
| import { get, lowerFirst } from 'lodash'; | |||||
| import { get, lowerFirst, omit } from 'lodash'; | |||||
| import trim from 'lodash/trim'; | import trim from 'lodash/trim'; | ||||
| import { UseFormReturn } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { v4 as uuid } from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||
| import { | import { | ||||
| } from './constant'; | } from './constant'; | ||||
| import useGraphStore, { RFState } from './store'; | import useGraphStore, { RFState } from './store'; | ||||
| import { | import { | ||||
| buildCategorizeObjectFromList, | |||||
| generateNodeNamesWithIncreasingIndex, | generateNodeNamesWithIncreasingIndex, | ||||
| generateSwitchHandleText, | generateSwitchHandleText, | ||||
| getNodeDragHandle, | getNodeDragHandle, | ||||
| return { onDrop, onDragOver, setReactFlowInstance }; | return { onDrop, onDragOver, setReactFlowInstance }; | ||||
| }; | }; | ||||
| export const useHandleFormValuesChange = (id?: string) => { | |||||
| export const useHandleFormValuesChange = ( | |||||
| operatorName: Operator, | |||||
| id?: string, | |||||
| form?: UseFormReturn, | |||||
| ) => { | |||||
| const updateNodeForm = useGraphStore((state) => state.updateNodeForm); | const updateNodeForm = useGraphStore((state) => state.updateNodeForm); | ||||
| const handleValuesChange = useCallback( | const handleValuesChange = useCallback( | ||||
| (changedValues: any, values: any) => { | (changedValues: any, values: any) => { | ||||
| [updateNodeForm, id], | [updateNodeForm, id], | ||||
| ); | ); | ||||
| useEffect(() => { | |||||
| const subscription = form?.watch((value, { name, type, values }) => { | |||||
| if (id && name) { | |||||
| console.log('🚀 ~ useEffect ~ value:', type, values); | |||||
| let nextValues: any = value; | |||||
| // Fixed the issue that the related form value does not change after selecting the freedom field of the model | |||||
| if ( | |||||
| name === 'parameter' && | |||||
| value['parameter'] in settledModelVariableMap | |||||
| ) { | |||||
| nextValues = { | |||||
| ...value, | |||||
| ...settledModelVariableMap[ | |||||
| value['parameter'] as keyof typeof settledModelVariableMap | |||||
| ], | |||||
| }; | |||||
| } | |||||
| const categoryDescriptionRegex = /items\.\d+\.name/g; | |||||
| if ( | |||||
| operatorName === Operator.Categorize && | |||||
| categoryDescriptionRegex.test(name) | |||||
| ) { | |||||
| nextValues = { | |||||
| ...omit(value, 'items'), | |||||
| category_description: buildCategorizeObjectFromList(value.items), | |||||
| }; | |||||
| } | |||||
| updateNodeForm(id, nextValues); | |||||
| } | |||||
| }); | |||||
| return () => subscription?.unsubscribe(); | |||||
| }, [form, form?.watch, id, operatorName, updateNodeForm]); | |||||
| return { handleValuesChange }; | return { handleValuesChange }; | ||||
| }; | }; | ||||
| import { Edge, Node, Position, XYPosition } from '@xyflow/react'; | import { Edge, Node, Position, XYPosition } from '@xyflow/react'; | ||||
| import { FormInstance, FormListFieldData } from 'antd'; | import { FormInstance, FormListFieldData } from 'antd'; | ||||
| import { humanId } from 'human-id'; | import { humanId } from 'human-id'; | ||||
| import { curry, get, intersectionWith, isEqual, sample } from 'lodash'; | |||||
| import { curry, get, intersectionWith, isEqual, omit, sample } from 'lodash'; | |||||
| import pipe from 'lodash/fp/pipe'; | import pipe from 'lodash/fp/pipe'; | ||||
| import isObject from 'lodash/isObject'; | import isObject from 'lodash/isObject'; | ||||
| import { v4 as uuidv4 } from 'uuid'; | import { v4 as uuidv4 } from 'uuid'; | ||||
| }, []) | }, []) | ||||
| .sort((a, b) => a.index - b.index); | .sort((a, b) => a.index - b.index); | ||||
| }; | }; | ||||
| /** | |||||
| * Convert the list in the following form into an object | |||||
| * { | |||||
| "items": [ | |||||
| { | |||||
| "name": "Categorize 1", | |||||
| "description": "111", | |||||
| "examples": "ddd", | |||||
| "to": "Retrieval:LazyEelsStick" | |||||
| } | |||||
| ] | |||||
| } | |||||
| */ | |||||
| export const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => { | |||||
| return list.reduce<ICategorizeItemResult>((pre, cur) => { | |||||
| if (cur?.name) { | |||||
| pre[cur.name] = omit(cur, 'name'); | |||||
| } | |||||
| return pre; | |||||
| }, {}); | |||||
| }; |