### What problem does this PR solve? Feat: Add UserFillUpForm component #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| <AccordionItem value="item-2"> | <AccordionItem value="item-2"> | ||||
| <AccordionTrigger className="text-xl">Dialogue </AccordionTrigger> | <AccordionTrigger className="text-xl">Dialogue </AccordionTrigger> | ||||
| <AccordionContent className="flex flex-col gap-4 text-balance"> | <AccordionContent className="flex flex-col gap-4 text-balance"> | ||||
| <OperatorItemList operators={[Operator.Message]}></OperatorItemList> | |||||
| <OperatorItemList | |||||
| operators={[Operator.Message, Operator.UserFillUp]} | |||||
| ></OperatorItemList> | |||||
| </AccordionContent> | </AccordionContent> | ||||
| </AccordionItem> | </AccordionItem> | ||||
| <AccordionItem value="item-3"> | <AccordionItem value="item-3"> |
| Agent = 'Agent', | Agent = 'Agent', | ||||
| Tool = 'Tool', | Tool = 'Tool', | ||||
| TavilySearch = 'TavilySearch', | TavilySearch = 'TavilySearch', | ||||
| UserFillUp = 'UserFillUp', | |||||
| } | } | ||||
| export const SwitchLogicOperatorOptions = ['and', 'or']; | export const SwitchLogicOperatorOptions = ['and', 'or']; | ||||
| }, | }, | ||||
| }; | }; | ||||
| export const initialUserFillUpValues = { | |||||
| enable_tips: true, | |||||
| tips: '', | |||||
| inputs: [], | |||||
| }; | |||||
| export enum TavilySearchDepth { | export enum TavilySearchDepth { | ||||
| Basic = 'basic', | Basic = 'basic', | ||||
| Advanced = 'advanced', | Advanced = 'advanced', | ||||
| [Operator.Agent]: 'agentNode', | [Operator.Agent]: 'agentNode', | ||||
| [Operator.Tool]: 'toolNode', | [Operator.Tool]: 'toolNode', | ||||
| [Operator.TavilySearch]: 'ragNode', | [Operator.TavilySearch]: 'ragNode', | ||||
| [Operator.UserFillUp]: 'ragNode', | |||||
| }; | }; | ||||
| export enum BeginQueryType { | export enum BeginQueryType { |
| import TemplateForm from '../form/template-form'; | import TemplateForm from '../form/template-form'; | ||||
| import ToolForm from '../form/tool-form'; | import ToolForm from '../form/tool-form'; | ||||
| import TuShareForm from '../form/tushare-form'; | import TuShareForm from '../form/tushare-form'; | ||||
| import UserFillUpForm from '../form/user-fill-up-form'; | |||||
| import WenCaiForm from '../form/wencai-form'; | import WenCaiForm from '../form/wencai-form'; | ||||
| import WikipediaForm from '../form/wikipedia-form'; | import WikipediaForm from '../form/wikipedia-form'; | ||||
| import YahooFinanceForm from '../form/yahoo-finance-form'; | import YahooFinanceForm from '../form/yahoo-finance-form'; | ||||
| defaultValues: {}, | defaultValues: {}, | ||||
| schema: z.object({}), | schema: z.object({}), | ||||
| }, | }, | ||||
| [Operator.UserFillUp]: { | |||||
| component: UserFillUpForm, | |||||
| defaultValues: {}, | |||||
| schema: z.object({}), | |||||
| }, | |||||
| }; | }; | ||||
| return FormConfigMap; | return FormConfigMap; |
| 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 ( | |||||
| <section className="px-5 space-y-5"> | |||||
| <Form {...form}> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={'enable_tips'} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel tooltip={t('flow.openingSwitchTip')}> | |||||
| Guiding Question | |||||
| </FormLabel> | |||||
| <FormControl> | |||||
| <Switch | |||||
| checked={field.value} | |||||
| onCheckedChange={field.onChange} | |||||
| /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={'tips'} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel tooltip={t('chat.setAnOpenerTip')}>Message</FormLabel> | |||||
| <FormControl> | |||||
| <Textarea | |||||
| rows={5} | |||||
| {...field} | |||||
| placeholder={t('common.pleaseInput')} | |||||
| ></Textarea> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| {/* Create a hidden field to make Form instance record this */} | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={'inputs'} | |||||
| render={() => <div></div>} | |||||
| /> | |||||
| <Collapse | |||||
| title={ | |||||
| <div> | |||||
| {t('flow.input')} | |||||
| <FormTooltip tooltip={t('flow.beginInputTip')}></FormTooltip> | |||||
| </div> | |||||
| } | |||||
| rightContent={ | |||||
| <Button | |||||
| variant={'ghost'} | |||||
| onClick={(e) => { | |||||
| e.preventDefault(); | |||||
| showModal(); | |||||
| }} | |||||
| > | |||||
| <Plus /> | |||||
| </Button> | |||||
| } | |||||
| > | |||||
| <QueryTable | |||||
| data={inputs} | |||||
| showModal={showModal} | |||||
| deleteRecord={handleDeleteRecord} | |||||
| ></QueryTable> | |||||
| </Collapse> | |||||
| {visible && ( | |||||
| <ParameterDialog | |||||
| hideModal={hideModal} | |||||
| initialValue={currentRecord} | |||||
| otherThanCurrentQuery={otherThanCurrentQuery} | |||||
| submit={ok} | |||||
| ></ParameterDialog> | |||||
| )} | |||||
| </Form> | |||||
| </section> | |||||
| ); | |||||
| }; | |||||
| export default UserFillUpForm; |
| 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; | |||||
| } |
| 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<Record<string, Omit<BeginQuery, 'key'>>>((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]); | |||||
| } |
| initialTavilyValues, | initialTavilyValues, | ||||
| initialTemplateValues, | initialTemplateValues, | ||||
| initialTuShareValues, | initialTuShareValues, | ||||
| initialUserFillUpValues, | |||||
| initialWaitingDialogueValues, | initialWaitingDialogueValues, | ||||
| initialWenCaiValues, | initialWenCaiValues, | ||||
| initialWikipediaValues, | initialWikipediaValues, | ||||
| [Operator.Agent]: { ...initialAgentValues, llm_id: llmId }, | [Operator.Agent]: { ...initialAgentValues, llm_id: llmId }, | ||||
| [Operator.Tool]: {}, | [Operator.Tool]: {}, | ||||
| [Operator.TavilySearch]: initialTavilyValues, | [Operator.TavilySearch]: initialTavilyValues, | ||||
| [Operator.UserFillUp]: initialUserFillUpValues, | |||||
| }; | }; | ||||
| }, [llmId]); | }, [llmId]); | ||||
| import { IconFont } from '@/components/icon-font'; | import { IconFont } from '@/components/icon-font'; | ||||
| import { cn } from '@/lib/utils'; | import { cn } from '@/lib/utils'; | ||||
| import { CirclePlay } from 'lucide-react'; | |||||
| import { CirclePlay, MessageSquareMore } from 'lucide-react'; | |||||
| import { Operator } from './constant'; | import { Operator } from './constant'; | ||||
| interface IProps { | interface IProps { | ||||
| [Operator.Switch]: 'condition', | [Operator.Switch]: 'condition', | ||||
| [Operator.Code]: 'code-set', | [Operator.Code]: 'code-set', | ||||
| [Operator.Agent]: 'agent-ai', | [Operator.Agent]: 'agent-ai', | ||||
| [Operator.UserFillUp]: MessageSquareMore, | |||||
| // [Operator.Relevant]: BranchesOutlined, | // [Operator.Relevant]: BranchesOutlined, | ||||
| // [Operator.RewriteQuestion]: FormOutlined, | // [Operator.RewriteQuestion]: FormOutlined, | ||||
| // [Operator.KeywordExtract]: KeywordIcon, | // [Operator.KeywordExtract]: KeywordIcon, |