### What problem does this PR solve? Feat: Displays the output variable type selected by the loop operator #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| import { Separator } from '@/components/ui/separator'; | import { Separator } from '@/components/ui/separator'; | ||||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | import { RAGFlowNodeType } from '@/interfaces/database/flow'; | ||||
| import { X } from 'lucide-react'; | import { X } from 'lucide-react'; | ||||
| import { ReactNode } from 'react'; | |||||
| import { ReactNode, useCallback, useMemo } from 'react'; | |||||
| import { useFieldArray, useFormContext } from 'react-hook-form'; | import { useFieldArray, useFormContext } from 'react-hook-form'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { useBuildSubNodeOutputOptions } from './use-build-options'; | import { useBuildSubNodeOutputOptions } from './use-build-options'; | ||||
| const options = useBuildSubNodeOutputOptions(node?.id); | const options = useBuildSubNodeOutputOptions(node?.id); | ||||
| const name = 'outputs'; | const name = 'outputs'; | ||||
| const flatOptions = useMemo(() => { | |||||
| return options.reduce<{ label: string; value: string; type: string }[]>( | |||||
| (pre, cur) => { | |||||
| pre.push(...cur.options); | |||||
| return pre; | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| }, [options]); | |||||
| const findType = useCallback( | |||||
| (val: string) => { | |||||
| const type = flatOptions.find((x) => x.value === val)?.type; | |||||
| if (type) { | |||||
| return `Array<${type}>`; | |||||
| } | |||||
| }, | |||||
| [flatOptions], | |||||
| ); | |||||
| const { fields, remove, append } = useFieldArray({ | const { fields, remove, append } = useFieldArray({ | ||||
| name: name, | name: name, | ||||
| control: form.control, | control: form.control, | ||||
| return ( | return ( | ||||
| <div className="space-y-5"> | <div className="space-y-5"> | ||||
| {fields.map((field, index) => { | {fields.map((field, index) => { | ||||
| const typeField = `${name}.${index}.name`; | |||||
| const nameField = `${name}.${index}.name`; | |||||
| const typeField = `${name}.${index}.type`; | |||||
| return ( | return ( | ||||
| <div key={field.id} className="flex items-center gap-2"> | <div key={field.id} className="flex items-center gap-2"> | ||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| name={typeField} | |||||
| name={nameField} | |||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem className="flex-1"> | <FormItem className="flex-1"> | ||||
| <FormControl> | <FormControl> | ||||
| <SelectWithSearch | <SelectWithSearch | ||||
| options={options} | options={options} | ||||
| {...field} | {...field} | ||||
| onChange={(val) => { | |||||
| form.setValue(typeField, findType(val)); | |||||
| field.onChange(val); | |||||
| }} | |||||
| ></SelectWithSearch> | ></SelectWithSearch> | ||||
| </FormControl> | </FormControl> | ||||
| <FormMessage /> | <FormMessage /> | ||||
| </FormItem> | </FormItem> | ||||
| )} | )} | ||||
| /> | /> | ||||
| <FormField | |||||
| control={form.control} | |||||
| name={typeField} | |||||
| render={() => <div></div>} | |||||
| /> | |||||
| <Button variant={'ghost'} onClick={() => remove(index)}> | <Button variant={'ghost'} onClick={() => remove(index)}> | ||||
| <X className="text-text-sub-title-invert " /> | <X className="text-text-sub-title-invert " /> | ||||
| </Button> | </Button> |
| export type OutputArray = Array<{ name: string; ref: string; type?: string }>; | export type OutputArray = Array<{ name: string; ref: string; type?: string }>; | ||||
| export type OutputObject = Record<string, { ref: string }>; | |||||
| export type OutputObject = Record<string, { ref: string; type?: string }>; |
| return Object.entries(outputObject).map(([key, value]) => ({ | return Object.entries(outputObject).map(([key, value]) => ({ | ||||
| name: key, | name: key, | ||||
| ref: value.ref, | ref: value.ref, | ||||
| type: value.type, | |||||
| })); | })); | ||||
| } | } | ||||
| function transferToObject(list: OutputArray) { | function transferToObject(list: OutputArray) { | ||||
| return list.reduce<OutputObject>((pre, cur) => { | return list.reduce<OutputObject>((pre, cur) => { | ||||
| pre[cur.name] = { ref: cur.ref }; | |||||
| pre[cur.name] = { ref: cur.ref, type: cur.type }; | |||||
| return pre; | return pre; | ||||
| }, {}); | }, {}); | ||||
| } | } | ||||
| // Manually triggered form updates are synchronized to the canvas | // Manually triggered form updates are synchronized to the canvas | ||||
| if (id && form?.formState.isDirty) { | if (id && form?.formState.isDirty) { | ||||
| values = form?.getValues(); | values = form?.getValues(); | ||||
| console.log('🚀 ~ useEffect ~ values:', values); | |||||
| let nextValues: any = { | let nextValues: any = { | ||||
| ...values, | ...values, | ||||
| outputs: transferToObject(values.outputs), | outputs: transferToObject(values.outputs), |
| DropdownMenuSeparator, | DropdownMenuSeparator, | ||||
| DropdownMenuTrigger, | DropdownMenuTrigger, | ||||
| } from '@/components/ui/dropdown-menu'; | } from '@/components/ui/dropdown-menu'; | ||||
| import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'; | |||||
| import { SidebarProvider } from '@/components/ui/sidebar'; | |||||
| import { useSetModalState } from '@/hooks/common-hooks'; | import { useSetModalState } from '@/hooks/common-hooks'; | ||||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | ||||
| import { ReactFlowProvider } from '@xyflow/react'; | import { ReactFlowProvider } from '@xyflow/react'; | ||||
| import { CodeXml, EllipsisVertical, Forward, Import, Key } from 'lucide-react'; | import { CodeXml, EllipsisVertical, Forward, Import, Key } from 'lucide-react'; | ||||
| import { ComponentPropsWithoutRef, useCallback } from 'react'; | import { ComponentPropsWithoutRef, useCallback } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { AgentSidebar } from './agent-sidebar'; | |||||
| import AgentCanvas from './canvas'; | import AgentCanvas from './canvas'; | ||||
| import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; | import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; | ||||
| import { useFetchDataOnMount } from './hooks/use-fetch-data'; | import { useFetchDataOnMount } from './hooks/use-fetch-data'; | ||||
| <ReactFlowProvider> | <ReactFlowProvider> | ||||
| <div> | <div> | ||||
| <SidebarProvider> | <SidebarProvider> | ||||
| <AgentSidebar /> | |||||
| <div className="w-full"> | <div className="w-full"> | ||||
| <SidebarTrigger /> | |||||
| <div className="w-full h-full"> | <div className="w-full h-full"> | ||||
| <AgentCanvas | <AgentCanvas | ||||
| drawerVisible={chatDrawerVisible} | drawerVisible={chatDrawerVisible} |