From 42a570a64d9b39b8bf9f6b32d643b611f0f8d76d Mon Sep 17 00:00:00 2001 From: balibabu Date: Thu, 26 Jun 2025 14:55:51 +0800 Subject: [PATCH] Feat: Add UserFillUpForm component #3221 (#8508) ### What problem does this PR solve? Feat: Add UserFillUpForm component #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- .../node/dropdown/next-step-dropdown.tsx | 4 +- web/src/pages/agent/constant.tsx | 8 + .../agent/form-sheet/use-form-config-map.tsx | 6 + .../agent/form/user-fill-up-form/index.tsx | 155 ++++++++++++++++++ .../form/user-fill-up-form/use-values.ts | 21 +++ .../user-fill-up-form/use-watch-change.ts | 31 ++++ web/src/pages/agent/hooks/use-add-node.ts | 2 + web/src/pages/agent/operator-icon.tsx | 3 +- 8 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 web/src/pages/agent/form/user-fill-up-form/index.tsx create mode 100644 web/src/pages/agent/form/user-fill-up-form/use-values.ts create mode 100644 web/src/pages/agent/form/user-fill-up-form/use-watch-change.ts diff --git a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx index 3a18233a1..dfb6b12f1 100644 --- a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx +++ b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx @@ -67,7 +67,9 @@ function AccordionOperators() { Dialogue - + diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx index 26b028825..dd8a882d3 100644 --- a/web/src/pages/agent/constant.tsx +++ b/web/src/pages/agent/constant.tsx @@ -86,6 +86,7 @@ export enum Operator { Agent = 'Agent', Tool = 'Tool', TavilySearch = 'TavilySearch', + UserFillUp = 'UserFillUp', } export const SwitchLogicOperatorOptions = ['and', 'or']; @@ -696,6 +697,12 @@ export const initialAgentValues = { }, }; +export const initialUserFillUpValues = { + enable_tips: true, + tips: '', + inputs: [], +}; + export enum TavilySearchDepth { Basic = 'basic', Advanced = 'advanced', @@ -860,6 +867,7 @@ export const NodeMap = { [Operator.Agent]: 'agentNode', [Operator.Tool]: 'toolNode', [Operator.TavilySearch]: 'ragNode', + [Operator.UserFillUp]: 'ragNode', }; export enum BeginQueryType { diff --git a/web/src/pages/agent/form-sheet/use-form-config-map.tsx b/web/src/pages/agent/form-sheet/use-form-config-map.tsx index 08e2a125a..175205f64 100644 --- a/web/src/pages/agent/form-sheet/use-form-config-map.tsx +++ b/web/src/pages/agent/form-sheet/use-form-config-map.tsx @@ -38,6 +38,7 @@ import TavilyForm from '../form/tavily-form'; import TemplateForm from '../form/template-form'; import ToolForm from '../form/tool-form'; import TuShareForm from '../form/tushare-form'; +import UserFillUpForm from '../form/user-fill-up-form'; import WenCaiForm from '../form/wencai-form'; import WikipediaForm from '../form/wikipedia-form'; import YahooFinanceForm from '../form/yahoo-finance-form'; @@ -382,6 +383,11 @@ export function useFormConfigMap() { defaultValues: {}, schema: z.object({}), }, + [Operator.UserFillUp]: { + component: UserFillUpForm, + defaultValues: {}, + schema: z.object({}), + }, }; return FormConfigMap; diff --git a/web/src/pages/agent/form/user-fill-up-form/index.tsx b/web/src/pages/agent/form/user-fill-up-form/index.tsx new file mode 100644 index 000000000..240eed0e7 --- /dev/null +++ b/web/src/pages/agent/form/user-fill-up-form/index.tsx @@ -0,0 +1,155 @@ +import { Collapse } from '@/components/collapse'; +import { Button } from '@/components/ui/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Switch } from '@/components/ui/switch'; +import { Textarea } from '@/components/ui/textarea'; +import { FormTooltip } from '@/components/ui/tooltip'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Plus } from 'lucide-react'; +import { useForm, useWatch } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { z } from 'zod'; +import { INextOperatorForm } from '../../interface'; +import { ParameterDialog } from '../begin-form/parameter-dialog'; +import { QueryTable } from '../begin-form/query-table'; +import { useEditQueryRecord } from '../begin-form/use-edit-query'; +import { useValues } from './use-values'; +import { useWatchFormChange } from './use-watch-change'; + +const UserFillUpForm = ({ node }: INextOperatorForm) => { + const { t } = useTranslation(); + + const values = useValues(node); + + const FormSchema = z.object({ + enable_tips: z.boolean().optional(), + tips: z.string().trim().optional(), + inputs: 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 inputs = useWatch({ control: form.control, name: 'inputs' }); + + const { + ok, + currentRecord, + visible, + hideModal, + showModal, + otherThanCurrentQuery, + handleDeleteRecord, + } = useEditQueryRecord({ + form, + node, + }); + + return ( +
+
+ ( + + + Guiding Question + + + + + + + )} + /> + + ( + + Message + + + + + + )} + /> + + {/* Create a hidden field to make Form instance record this */} +
} + /> + + {t('flow.input')} + + + } + rightContent={ + + } + > + + + + {visible && ( + + )} + +
+ ); +}; + +export default UserFillUpForm; diff --git a/web/src/pages/agent/form/user-fill-up-form/use-values.ts b/web/src/pages/agent/form/user-fill-up-form/use-values.ts new file mode 100644 index 000000000..0af1c78c3 --- /dev/null +++ b/web/src/pages/agent/form/user-fill-up-form/use-values.ts @@ -0,0 +1,21 @@ +import { RAGFlowNodeType } from '@/interfaces/database/flow'; +import { isEmpty } from 'lodash'; +import { useMemo } from 'react'; +import { initialUserFillUpValues } from '../../constant'; +import { buildBeginInputListFromObject } from '../begin-form/utils'; + +export function useValues(node?: RAGFlowNodeType) { + const values = useMemo(() => { + const formData = node?.data?.form; + + if (isEmpty(formData)) { + return initialUserFillUpValues; + } + + const inputs = buildBeginInputListFromObject(formData?.inputs); + + return { ...(formData || {}), inputs }; + }, [node?.data?.form]); + + return values; +} diff --git a/web/src/pages/agent/form/user-fill-up-form/use-watch-change.ts b/web/src/pages/agent/form/user-fill-up-form/use-watch-change.ts new file mode 100644 index 000000000..3dc451265 --- /dev/null +++ b/web/src/pages/agent/form/user-fill-up-form/use-watch-change.ts @@ -0,0 +1,31 @@ +import { omit } from 'lodash'; +import { useEffect } from 'react'; +import { UseFormReturn, useWatch } from 'react-hook-form'; +import { BeginQuery } from '../../interface'; +import useGraphStore from '../../store'; + +function transferInputsArrayToObject(inputs: BeginQuery[] = []) { + return inputs.reduce>>((pre, cur) => { + pre[cur.key] = omit(cur, 'key'); + + return pre; + }, {}); +} + +export function useWatchFormChange(id?: string, form?: UseFormReturn) { + let values = useWatch({ control: form?.control }); + const updateNodeForm = useGraphStore((state) => state.updateNodeForm); + + useEffect(() => { + if (id && form?.formState.isDirty) { + values = form?.getValues(); + + const nextValues = { + ...values, + inputs: transferInputsArrayToObject(values.inputs), + }; + + updateNodeForm(id, nextValues); + } + }, [form?.formState.isDirty, id, updateNodeForm, values]); +} diff --git a/web/src/pages/agent/hooks/use-add-node.ts b/web/src/pages/agent/hooks/use-add-node.ts index 25f75b383..c243f9773 100644 --- a/web/src/pages/agent/hooks/use-add-node.ts +++ b/web/src/pages/agent/hooks/use-add-node.ts @@ -43,6 +43,7 @@ import { initialTavilyValues, initialTemplateValues, initialTuShareValues, + initialUserFillUpValues, initialWaitingDialogueValues, initialWenCaiValues, initialWikipediaValues, @@ -106,6 +107,7 @@ export const useInitializeOperatorParams = () => { [Operator.Agent]: { ...initialAgentValues, llm_id: llmId }, [Operator.Tool]: {}, [Operator.TavilySearch]: initialTavilyValues, + [Operator.UserFillUp]: initialUserFillUpValues, }; }, [llmId]); diff --git a/web/src/pages/agent/operator-icon.tsx b/web/src/pages/agent/operator-icon.tsx index 3c2479492..1547b6ca7 100644 --- a/web/src/pages/agent/operator-icon.tsx +++ b/web/src/pages/agent/operator-icon.tsx @@ -1,6 +1,6 @@ import { IconFont } from '@/components/icon-font'; import { cn } from '@/lib/utils'; -import { CirclePlay } from 'lucide-react'; +import { CirclePlay, MessageSquareMore } from 'lucide-react'; import { Operator } from './constant'; interface IProps { @@ -19,6 +19,7 @@ export const OperatorIconMap = { [Operator.Switch]: 'condition', [Operator.Code]: 'code-set', [Operator.Agent]: 'agent-ai', + [Operator.UserFillUp]: MessageSquareMore, // [Operator.Relevant]: BranchesOutlined, // [Operator.RewriteQuestion]: FormOutlined, // [Operator.KeywordExtract]: KeywordIcon,