### What problem does this PR solve? Feat: Use one-way data flow to synchronize the form data to the canvas #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.1
| updateNodeForm(node?.id, nextValues); | updateNodeForm(node?.id, nextValues); | ||||
| } | } | ||||
| console.info('xx:', node); | |||||
| for (const key in values) { | |||||
| if (Object.prototype.hasOwnProperty.call(values, key)) { | |||||
| const element = values[key]; | |||||
| // form.reset({ ...currentValues, ...values }); | |||||
| // for (const key in values) { | |||||
| // if (Object.prototype.hasOwnProperty.call(values, key)) { | |||||
| // const element = values[key]; | |||||
| // form.setValue(key, element); | |||||
| // } | |||||
| // } | |||||
| form.setValue(key, element); | |||||
| } | |||||
| } | |||||
| }, | }, | ||||
| [form, node, updateNodeForm], | [form, node, updateNodeForm], | ||||
| ); | ); |
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | import { RAGFlowNodeType } from '@/interfaces/database/flow'; | ||||
| import { cn } from '@/lib/utils'; | import { cn } from '@/lib/utils'; | ||||
| import { zodResolver } from '@hookform/resolvers/zod'; | import { zodResolver } from '@hookform/resolvers/zod'; | ||||
| import { get, isPlainObject, lowerFirst } from 'lodash'; | |||||
| import omit from 'lodash/omit'; | |||||
| import { lowerFirst } from 'lodash'; | |||||
| import { Play, X } from 'lucide-react'; | import { Play, X } from 'lucide-react'; | ||||
| import { useEffect, useRef } from 'react'; | |||||
| import { useRef } from 'react'; | |||||
| import { useForm } from 'react-hook-form'; | import { useForm } from 'react-hook-form'; | ||||
| import { BeginId, Operator, operatorMap } from '../constant'; | import { BeginId, Operator, operatorMap } from '../constant'; | ||||
| import { FlowFormContext } from '../context'; | import { FlowFormContext } from '../context'; | ||||
| import { useHandleNodeNameChange } from '../hooks'; | import { useHandleNodeNameChange } from '../hooks'; | ||||
| import { useHandleFormValuesChange } from '../hooks/use-watch-form-change'; | import { useHandleFormValuesChange } from '../hooks/use-watch-form-change'; | ||||
| import OperatorIcon from '../operator-icon'; | import OperatorIcon from '../operator-icon'; | ||||
| import { | |||||
| buildCategorizeListFromObject, | |||||
| convertToObjectArray, | |||||
| needsSingleStepDebugging, | |||||
| } from '../utils'; | |||||
| import { needsSingleStepDebugging } from '../utils'; | |||||
| import SingleDebugDrawer from './single-debug-drawer'; | import SingleDebugDrawer from './single-debug-drawer'; | ||||
| import { useFormConfigMap } from './use-form-config-map'; | import { useFormConfigMap } from './use-form-config-map'; | ||||
| import { useValues } from './use-values'; | |||||
| interface IProps { | interface IProps { | ||||
| node?: RAGFlowNodeType; | node?: RAGFlowNodeType; | ||||
| const OperatorForm = currentFormMap.component ?? EmptyContent; | const OperatorForm = currentFormMap.component ?? EmptyContent; | ||||
| const values = useValues(node); | |||||
| const form = useForm({ | const form = useForm({ | ||||
| values: currentFormMap.defaultValues, | |||||
| values: values, | |||||
| resolver: zodResolver(currentFormMap.schema), | resolver: zodResolver(currentFormMap.schema), | ||||
| }); | }); | ||||
| form, | form, | ||||
| ); | ); | ||||
| useEffect(() => { | |||||
| if (visible) { | |||||
| if (node?.id !== previousId.current) { | |||||
| form.reset(); | |||||
| form.clearErrors(); | |||||
| } | |||||
| const formData = node?.data?.form; | |||||
| if (operatorName === Operator.Categorize) { | |||||
| const items = buildCategorizeListFromObject( | |||||
| get(node, 'data.form.category_description', {}), | |||||
| ); | |||||
| if (isPlainObject(formData)) { | |||||
| // form.setFieldsValue({ ...formData, items }); | |||||
| console.info('xxx'); | |||||
| const nextValues = { | |||||
| ...omit(formData, 'category_description'), | |||||
| items, | |||||
| }; | |||||
| // Object.entries(nextValues).forEach(([key, value]) => { | |||||
| // form.setValue(key, value, { shouldDirty: false }); | |||||
| // }); | |||||
| form.reset(nextValues); | |||||
| } | |||||
| } else if (operatorName === Operator.Message) { | |||||
| form.reset({ | |||||
| ...formData, | |||||
| content: convertToObjectArray(formData.content), | |||||
| }); | |||||
| } else { | |||||
| // form.setFieldsValue(node?.data?.form); | |||||
| form.reset(node?.data?.form); | |||||
| } | |||||
| previousId.current = node?.id; | |||||
| } | |||||
| }, [visible, form, node?.data?.form, node?.id, node, operatorName]); | |||||
| // useEffect(() => { | |||||
| // if (visible && !form.formState.isDirty) { | |||||
| // // if (node?.id !== previousId.current) { | |||||
| // // form.reset(); | |||||
| // // form.clearErrors(); | |||||
| // // } | |||||
| // const formData = node?.data?.form; | |||||
| // if (operatorName === Operator.Categorize) { | |||||
| // const items = buildCategorizeListFromObject( | |||||
| // get(node, 'data.form.category_description', {}), | |||||
| // ); | |||||
| // if (isPlainObject(formData)) { | |||||
| // console.info('xxx'); | |||||
| // const nextValues = { | |||||
| // ...omit(formData, 'category_description'), | |||||
| // items, | |||||
| // }; | |||||
| // form.reset(nextValues); | |||||
| // } | |||||
| // } else if (operatorName === Operator.Message) { | |||||
| // form.reset({ | |||||
| // ...formData, | |||||
| // content: convertToObjectArray(formData.content), | |||||
| // }); | |||||
| // } else { | |||||
| // form.reset(node?.data?.form); | |||||
| // } | |||||
| // previousId.current = node?.id; | |||||
| // } | |||||
| // }, [visible, form, node?.data?.form, node?.id, node, operatorName]); | |||||
| return ( | return ( | ||||
| <Sheet open={visible} modal={false}> | <Sheet open={visible} modal={false}> |
| import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | ||||
| import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent'; | import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent'; | ||||
| import { ModelVariableType } from '@/constants/knowledge'; | |||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { AgentDialogueMode, Operator } from '../constant'; | |||||
| import { Operator } from '../constant'; | |||||
| import AkShareForm from '../form/akshare-form'; | import AkShareForm from '../form/akshare-form'; | ||||
| import AnswerForm from '../form/answer-form'; | import AnswerForm from '../form/answer-form'; | ||||
| import ArXivForm from '../form/arxiv-form'; | import ArXivForm from '../form/arxiv-form'; | ||||
| const FormConfigMap = { | const FormConfigMap = { | ||||
| [Operator.Begin]: { | [Operator.Begin]: { | ||||
| component: BeginForm, | component: BeginForm, | ||||
| defaultValues: { | |||||
| enablePrologue: true, | |||||
| prologue: t('chat.setAnOpenerInitial'), | |||||
| mode: AgentDialogueMode.Conversational, | |||||
| }, | |||||
| defaultValues: {}, | |||||
| schema: z.object({ | schema: z.object({ | ||||
| enablePrologue: z.boolean().optional(), | enablePrologue: z.boolean().optional(), | ||||
| prologue: z | prologue: z | ||||
| }, | }, | ||||
| [Operator.Categorize]: { | [Operator.Categorize]: { | ||||
| component: CategorizeForm, | component: CategorizeForm, | ||||
| defaultValues: { | |||||
| parameter: ModelVariableType.Precise, | |||||
| message_history_window_size: 1, | |||||
| temperatureEnabled: true, | |||||
| topPEnabled: true, | |||||
| presencePenaltyEnabled: true, | |||||
| frequencyPenaltyEnabled: true, | |||||
| maxTokensEnabled: true, | |||||
| items: [], | |||||
| }, | |||||
| defaultValues: {}, | |||||
| schema: z.object({ | schema: z.object({ | ||||
| parameter: z.string().optional(), | parameter: z.string().optional(), | ||||
| ...LlmSettingSchema, | ...LlmSettingSchema, | ||||
| }, | }, | ||||
| [Operator.Message]: { | [Operator.Message]: { | ||||
| component: MessageForm, | component: MessageForm, | ||||
| defaultValues: { | |||||
| content: [], | |||||
| }, | |||||
| defaultValues: {}, | |||||
| schema: z.object({ | schema: z.object({ | ||||
| content: z | content: z | ||||
| .array( | .array( |
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||||
| import { get, isEmpty, isPlainObject, omit } from 'lodash'; | |||||
| import { useMemo, useRef } from 'react'; | |||||
| import { Operator } from '../constant'; | |||||
| import { buildCategorizeListFromObject, convertToObjectArray } from '../utils'; | |||||
| import { useFormConfigMap } from './use-form-config-map'; | |||||
| export function useValues(node?: RAGFlowNodeType, isDirty?: boolean) { | |||||
| const operatorName: Operator = node?.data.label as Operator; | |||||
| const previousId = useRef<string | undefined>(node?.id); | |||||
| const FormConfigMap = useFormConfigMap(); | |||||
| const currentFormMap = FormConfigMap[operatorName]; | |||||
| const values = useMemo(() => { | |||||
| const formData = node?.data?.form; | |||||
| if (operatorName === Operator.Categorize) { | |||||
| const items = buildCategorizeListFromObject( | |||||
| get(node, 'data.form.category_description', {}), | |||||
| ); | |||||
| if (isPlainObject(formData)) { | |||||
| console.info('xxx'); | |||||
| const nextValues = { | |||||
| ...omit(formData, 'category_description'), | |||||
| items, | |||||
| }; | |||||
| return nextValues; | |||||
| } | |||||
| } else if (operatorName === Operator.Message) { | |||||
| return { | |||||
| ...formData, | |||||
| content: convertToObjectArray(formData.content), | |||||
| }; | |||||
| } else { | |||||
| return isEmpty(formData) ? currentFormMap : formData; | |||||
| } | |||||
| }, [currentFormMap, node, operatorName]); | |||||
| return values; | |||||
| } |
| import { Textarea } from '@/components/ui/textarea'; | import { Textarea } from '@/components/ui/textarea'; | ||||
| import { FormTooltip } from '@/components/ui/tooltip'; | import { FormTooltip } from '@/components/ui/tooltip'; | ||||
| import { buildSelectOptions } from '@/utils/component-util'; | import { buildSelectOptions } from '@/utils/component-util'; | ||||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||||
| import { Plus } from 'lucide-react'; | import { Plus } from 'lucide-react'; | ||||
| import { useCallback } from 'react'; | import { useCallback } from 'react'; | ||||
| import { useWatch } from 'react-hook-form'; | |||||
| import { useForm, useWatch } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | |||||
| import { AgentDialogueMode } from '../../constant'; | import { AgentDialogueMode } from '../../constant'; | ||||
| import { INextOperatorForm } from '../../interface'; | import { INextOperatorForm } from '../../interface'; | ||||
| import { ParameterDialog } from './parameter-dialog'; | import { ParameterDialog } from './parameter-dialog'; | ||||
| import { QueryTable } from './query-table'; | import { QueryTable } from './query-table'; | ||||
| import { useEditQueryRecord } from './use-edit-query'; | import { useEditQueryRecord } from './use-edit-query'; | ||||
| import { useValues } from './use-values'; | |||||
| import { useWatchFormChange } from './use-watch-change'; | |||||
| const ModeOptions = buildSelectOptions([ | const ModeOptions = buildSelectOptions([ | ||||
| AgentDialogueMode.Conversational, | AgentDialogueMode.Conversational, | ||||
| AgentDialogueMode.Task, | AgentDialogueMode.Task, | ||||
| ]); | ]); | ||||
| const BeginForm = ({ form, node }: INextOperatorForm) => { | |||||
| const BeginForm = ({ node }: INextOperatorForm) => { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const values = useValues(node); | |||||
| const FormSchema = z.object({ | |||||
| enablePrologue: z.boolean().optional(), | |||||
| prologue: z | |||||
| .string() | |||||
| .min(1, { | |||||
| message: t('common.namePlaceholder'), | |||||
| }) | |||||
| .trim() | |||||
| .optional(), | |||||
| mode: z.string(), | |||||
| query: z | |||||
| .array( | |||||
| z.object({ | |||||
| key: z.string(), | |||||
| type: z.string(), | |||||
| value: z.string(), | |||||
| optional: z.boolean(), | |||||
| name: z.string(), | |||||
| options: z.array(z.union([z.number(), z.string(), z.boolean()])), | |||||
| }), | |||||
| ) | |||||
| .optional(), | |||||
| }); | |||||
| const form = useForm({ | |||||
| defaultValues: values, | |||||
| resolver: zodResolver(FormSchema), | |||||
| }); | |||||
| useWatchFormChange(node?.id, form); | |||||
| const query = useWatch({ control: form.control, name: 'query' }); | const query = useWatch({ control: form.control, name: 'query' }); | ||||
| const mode = useWatch({ control: form.control, name: 'mode' }); | const mode = useWatch({ control: form.control, name: 'mode' }); | ||||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||||
| import { isEmpty } from 'lodash'; | |||||
| import { useMemo } from 'react'; | |||||
| import { useTranslation } from 'react-i18next'; | |||||
| import { AgentDialogueMode } from '../../constant'; | |||||
| export function useValues(node?: RAGFlowNodeType) { | |||||
| const { t } = useTranslation(); | |||||
| const defaultValues = useMemo( | |||||
| () => ({ | |||||
| enablePrologue: true, | |||||
| prologue: t('chat.setAnOpenerInitial'), | |||||
| mode: AgentDialogueMode.Conversational, | |||||
| }), | |||||
| [t], | |||||
| ); | |||||
| const values = useMemo(() => { | |||||
| const formData = node?.data?.form; | |||||
| if (isEmpty(formData)) { | |||||
| return defaultValues; | |||||
| } | |||||
| return formData; | |||||
| }, [defaultValues, node?.data?.form]); | |||||
| return values; | |||||
| } |
| 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; | |||||
| updateNodeForm(id, nextValues); | |||||
| } | |||||
| }, [form?.formState.isDirty, id, updateNodeForm, values]); | |||||
| } |
| import { LargeModelFormField } from '@/components/large-model-form-field'; | import { LargeModelFormField } from '@/components/large-model-form-field'; | ||||
| import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | |||||
| import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item'; | import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item'; | ||||
| import { SelectWithSearch } from '@/components/originui/select-with-search'; | import { SelectWithSearch } from '@/components/originui/select-with-search'; | ||||
| import { | import { | ||||
| FormLabel, | FormLabel, | ||||
| FormMessage, | FormMessage, | ||||
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||||
| import { useForm } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | |||||
| import { INextOperatorForm } from '../../interface'; | import { INextOperatorForm } from '../../interface'; | ||||
| import DynamicCategorize from './dynamic-categorize'; | import DynamicCategorize from './dynamic-categorize'; | ||||
| import { useValues } from './use-values'; | |||||
| import { useWatchFormChange } from './use-watch-change'; | |||||
| const CategorizeForm = ({ form, node }: INextOperatorForm) => { | |||||
| const CategorizeForm = ({ node }: INextOperatorForm) => { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const values = useValues(node); | |||||
| const FormSchema = z.object({ | |||||
| parameter: z.string().optional(), | |||||
| ...LlmSettingSchema, | |||||
| message_history_window_size: z.coerce.number(), | |||||
| items: z.array( | |||||
| z | |||||
| .object({ | |||||
| name: z.string().min(1, t('flow.nameMessage')).trim(), | |||||
| description: z.string().optional(), | |||||
| // examples: z | |||||
| // .array( | |||||
| // z.object({ | |||||
| // value: z.string(), | |||||
| // }), | |||||
| // ) | |||||
| // .optional(), | |||||
| }) | |||||
| .optional(), | |||||
| ), | |||||
| }); | |||||
| const form = useForm({ | |||||
| defaultValues: values, | |||||
| resolver: zodResolver(FormSchema), | |||||
| }); | |||||
| useWatchFormChange(node?.id, form); | |||||
| return ( | return ( | ||||
| <Form {...form}> | <Form {...form}> | ||||
| <form | <form |
| import { ModelVariableType } from '@/constants/knowledge'; | |||||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||||
| import { get, isEmpty, isPlainObject, omit } from 'lodash'; | |||||
| import { useMemo } from 'react'; | |||||
| import { buildCategorizeListFromObject } from '../../utils'; | |||||
| const defaultValues = { | |||||
| parameter: ModelVariableType.Precise, | |||||
| message_history_window_size: 1, | |||||
| temperatureEnabled: true, | |||||
| topPEnabled: true, | |||||
| presencePenaltyEnabled: true, | |||||
| frequencyPenaltyEnabled: true, | |||||
| maxTokensEnabled: true, | |||||
| items: [], | |||||
| }; | |||||
| export function useValues(node?: RAGFlowNodeType) { | |||||
| const values = useMemo(() => { | |||||
| const formData = node?.data?.form; | |||||
| if (isEmpty(formData)) { | |||||
| return defaultValues; | |||||
| } | |||||
| const items = buildCategorizeListFromObject( | |||||
| get(node, 'data.form.category_description', {}), | |||||
| ); | |||||
| if (isPlainObject(formData)) { | |||||
| const nextValues = { | |||||
| ...omit(formData, 'category_description'), | |||||
| items, | |||||
| }; | |||||
| return nextValues; | |||||
| } | |||||
| }, [node]); | |||||
| return values; | |||||
| } |
| import { omit } from 'lodash'; | |||||
| import { useEffect } from 'react'; | |||||
| import { UseFormReturn, useWatch } from 'react-hook-form'; | |||||
| import useGraphStore from '../../store'; | |||||
| import { buildCategorizeObjectFromList } from '../../utils'; | |||||
| 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; | |||||
| const categoryDescription = Array.isArray(values.items) | |||||
| ? buildCategorizeObjectFromList(values.items) | |||||
| : {}; | |||||
| if (categoryDescription) { | |||||
| nextValues = { | |||||
| ...omit(values, 'items'), | |||||
| category_description: categoryDescription, | |||||
| }; | |||||
| } | |||||
| updateNodeForm(id, nextValues); | |||||
| } | |||||
| }, [form?.formState.isDirty, id, updateNodeForm, values]); | |||||
| } |
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { Input } from '@/components/ui/input'; | import { Input } from '@/components/ui/input'; | ||||
| import { RAGFlowSelect } from '@/components/ui/select'; | import { RAGFlowSelect } from '@/components/ui/select'; | ||||
| import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent'; | |||||
| import { ProgrammingLanguage } from '@/constants/agent'; | |||||
| import { ICodeForm } from '@/interfaces/database/flow'; | import { ICodeForm } from '@/interfaces/database/flow'; | ||||
| import { useEffect } from 'react'; | |||||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||||
| import { useForm } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | |||||
| import { | import { | ||||
| DynamicInputVariable, | DynamicInputVariable, | ||||
| TypeOptions, | TypeOptions, | ||||
| VariableTitle, | VariableTitle, | ||||
| } from './next-variable'; | } from './next-variable'; | ||||
| import { useValues } from './use-values'; | |||||
| import { | |||||
| useHandleLanguageChange, | |||||
| useWatchFormChange, | |||||
| } from './use-watch-change'; | |||||
| loader.config({ paths: { vs: '/vs' } }); | loader.config({ paths: { vs: '/vs' } }); | ||||
| ProgrammingLanguage.Javascript, | ProgrammingLanguage.Javascript, | ||||
| ].map((x) => ({ value: x, label: x })); | ].map((x) => ({ value: x, label: x })); | ||||
| const CodeForm = ({ form, node }: INextOperatorForm) => { | |||||
| const CodeForm = ({ node }: INextOperatorForm) => { | |||||
| const formData = node?.data.form as ICodeForm; | const formData = node?.data.form as ICodeForm; | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const values = useValues(node); | |||||
| const FormSchema = z.object({ | |||||
| lang: z.string(), | |||||
| script: z.string(), | |||||
| arguments: z.array( | |||||
| z.object({ name: z.string(), component_id: z.string() }), | |||||
| ), | |||||
| return: z.union([ | |||||
| z | |||||
| .array(z.object({ name: z.string(), component_id: z.string() })) | |||||
| .optional(), | |||||
| z.object({ name: z.string(), component_id: z.string() }), | |||||
| ]), | |||||
| }); | |||||
| const form = useForm({ | |||||
| defaultValues: values, | |||||
| resolver: zodResolver(FormSchema), | |||||
| }); | |||||
| useWatchFormChange(node?.id, form); | |||||
| useEffect(() => { | |||||
| // TODO: Direct operation zustand is more elegant | |||||
| form?.setValue( | |||||
| 'script', | |||||
| CodeTemplateStrMap[formData.lang as ProgrammingLanguage], | |||||
| ); | |||||
| }, [form, formData.lang]); | |||||
| const handleLanguageChange = useHandleLanguageChange(node?.id, form); | |||||
| return ( | return ( | ||||
| <Form {...form}> | <Form {...form}> | ||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | <FormItem> | ||||
| <FormControl> | <FormControl> | ||||
| <RAGFlowSelect {...field} options={options} /> | |||||
| <RAGFlowSelect | |||||
| {...field} | |||||
| onChange={(val) => { | |||||
| field.onChange(val); | |||||
| handleLanguageChange(val); | |||||
| }} | |||||
| options={options} | |||||
| /> | |||||
| </FormControl> | </FormControl> | ||||
| <FormMessage /> | <FormMessage /> | ||||
| </FormItem> | </FormItem> |
| import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent'; | |||||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||||
| import { isEmpty } from 'lodash'; | |||||
| import { useMemo } from 'react'; | |||||
| export function useValues(node?: RAGFlowNodeType) { | |||||
| const defaultValues = useMemo( | |||||
| () => ({ | |||||
| lang: ProgrammingLanguage.Python, | |||||
| script: CodeTemplateStrMap[ProgrammingLanguage.Python], | |||||
| arguments: [], | |||||
| }), | |||||
| [], | |||||
| ); | |||||
| const values = useMemo(() => { | |||||
| const formData = node?.data?.form; | |||||
| if (isEmpty(formData)) { | |||||
| return defaultValues; | |||||
| } | |||||
| return formData; | |||||
| }, [defaultValues, node?.data?.form]); | |||||
| return values; | |||||
| } |
| import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent'; | |||||
| import { useCallback, 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; | |||||
| updateNodeForm(id, nextValues); | |||||
| } | |||||
| }, [form?.formState.isDirty, id, updateNodeForm, values]); | |||||
| } | |||||
| export function useHandleLanguageChange(id?: string, form?: UseFormReturn) { | |||||
| const updateNodeForm = useGraphStore((state) => state.updateNodeForm); | |||||
| const handleLanguageChange = useCallback( | |||||
| (lang: string) => { | |||||
| if (id) { | |||||
| const script = CodeTemplateStrMap[lang as ProgrammingLanguage]; | |||||
| form?.setValue('script', script); | |||||
| updateNodeForm(id, script, ['script']); | |||||
| } | |||||
| }, | |||||
| [form, id, updateNodeForm], | |||||
| ); | |||||
| return handleLanguageChange; | |||||
| } |
| FormLabel, | FormLabel, | ||||
| FormMessage, | FormMessage, | ||||
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||||
| import { X } from 'lucide-react'; | import { X } from 'lucide-react'; | ||||
| import { useFieldArray } from 'react-hook-form'; | |||||
| import { useFieldArray, useForm } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | |||||
| import { INextOperatorForm } from '../../interface'; | import { INextOperatorForm } from '../../interface'; | ||||
| import { useValues } from './use-values'; | |||||
| import { useWatchFormChange } from './use-watch-change'; | |||||
| const MessageForm = ({ form }: INextOperatorForm) => { | |||||
| const MessageForm = ({ node }: INextOperatorForm) => { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const values = useValues(node); | |||||
| const FormSchema = z.object({ | |||||
| content: z | |||||
| .array( | |||||
| z.object({ | |||||
| value: z.string(), | |||||
| }), | |||||
| ) | |||||
| .optional(), | |||||
| }); | |||||
| const form = useForm({ | |||||
| defaultValues: values, | |||||
| resolver: zodResolver(FormSchema), | |||||
| }); | |||||
| useWatchFormChange(node?.id, form); | |||||
| const { fields, append, remove } = useFieldArray({ | const { fields, append, remove } = useFieldArray({ | ||||
| name: 'content', | name: 'content', | ||||
| control: form.control, | control: form.control, |
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||||
| import { isEmpty } from 'lodash'; | |||||
| import { useMemo } from 'react'; | |||||
| import { convertToObjectArray } from '../../utils'; | |||||
| const defaultValues = { | |||||
| content: [], | |||||
| }; | |||||
| export function useValues(node?: RAGFlowNodeType) { | |||||
| const values = useMemo(() => { | |||||
| const formData = node?.data?.form; | |||||
| if (isEmpty(formData)) { | |||||
| return defaultValues; | |||||
| } | |||||
| return { | |||||
| ...formData, | |||||
| content: convertToObjectArray(formData.content), | |||||
| }; | |||||
| }, [node]); | |||||
| return values; | |||||
| } |
| import { useEffect } from 'react'; | |||||
| import { UseFormReturn, useWatch } from 'react-hook-form'; | |||||
| import useGraphStore from '../../store'; | |||||
| import { convertToStringArray } from '../../utils'; | |||||
| 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; | |||||
| nextValues = { | |||||
| ...values, | |||||
| content: convertToStringArray(values.content), | |||||
| }; | |||||
| updateNodeForm(id, nextValues); | |||||
| } | |||||
| }, [form?.formState.isDirty, id, updateNodeForm, values]); | |||||
| } |