### 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
| @@ -13,7 +13,7 @@ import { Input } from '@/components/ui/input'; | |||
| import { Separator } from '@/components/ui/separator'; | |||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||
| import { X } from 'lucide-react'; | |||
| import { ReactNode } from 'react'; | |||
| import { ReactNode, useCallback, useMemo } from 'react'; | |||
| import { useFieldArray, useFormContext } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { useBuildSubNodeOutputOptions } from './use-build-options'; | |||
| @@ -28,6 +28,26 @@ export function DynamicOutputForm({ node }: IProps) { | |||
| const options = useBuildSubNodeOutputOptions(node?.id); | |||
| 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({ | |||
| name: name, | |||
| control: form.control, | |||
| @@ -36,12 +56,13 @@ export function DynamicOutputForm({ node }: IProps) { | |||
| return ( | |||
| <div className="space-y-5"> | |||
| {fields.map((field, index) => { | |||
| const typeField = `${name}.${index}.name`; | |||
| const nameField = `${name}.${index}.name`; | |||
| const typeField = `${name}.${index}.type`; | |||
| return ( | |||
| <div key={field.id} className="flex items-center gap-2"> | |||
| <FormField | |||
| control={form.control} | |||
| name={typeField} | |||
| name={nameField} | |||
| render={({ field }) => ( | |||
| <FormItem className="flex-1"> | |||
| <FormControl> | |||
| @@ -64,12 +85,21 @@ export function DynamicOutputForm({ node }: IProps) { | |||
| <SelectWithSearch | |||
| options={options} | |||
| {...field} | |||
| onChange={(val) => { | |||
| form.setValue(typeField, findType(val)); | |||
| field.onChange(val); | |||
| }} | |||
| ></SelectWithSearch> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name={typeField} | |||
| render={() => <div></div>} | |||
| /> | |||
| <Button variant={'ghost'} onClick={() => remove(index)}> | |||
| <X className="text-text-sub-title-invert " /> | |||
| </Button> | |||
| @@ -1,2 +1,2 @@ | |||
| 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 }>; | |||
| @@ -8,6 +8,7 @@ function convertToArray(outputObject: OutputObject) { | |||
| return Object.entries(outputObject).map(([key, value]) => ({ | |||
| name: key, | |||
| ref: value.ref, | |||
| type: value.type, | |||
| })); | |||
| } | |||
| @@ -5,7 +5,7 @@ import { OutputArray, OutputObject } from './interface'; | |||
| function transferToObject(list: OutputArray) { | |||
| return list.reduce<OutputObject>((pre, cur) => { | |||
| pre[cur.name] = { ref: cur.ref }; | |||
| pre[cur.name] = { ref: cur.ref, type: cur.type }; | |||
| return pre; | |||
| }, {}); | |||
| } | |||
| @@ -18,6 +18,7 @@ export function useWatchFormChange(id?: string, form?: UseFormReturn) { | |||
| // Manually triggered form updates are synchronized to the canvas | |||
| if (id && form?.formState.isDirty) { | |||
| values = form?.getValues(); | |||
| console.log('🚀 ~ useEffect ~ values:', values); | |||
| let nextValues: any = { | |||
| ...values, | |||
| outputs: transferToObject(values.outputs), | |||
| @@ -7,14 +7,13 @@ import { | |||
| DropdownMenuSeparator, | |||
| DropdownMenuTrigger, | |||
| } 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 { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { ReactFlowProvider } from '@xyflow/react'; | |||
| import { CodeXml, EllipsisVertical, Forward, Import, Key } from 'lucide-react'; | |||
| import { ComponentPropsWithoutRef, useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { AgentSidebar } from './agent-sidebar'; | |||
| import AgentCanvas from './canvas'; | |||
| import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; | |||
| import { useFetchDataOnMount } from './hooks/use-fetch-data'; | |||
| @@ -119,9 +118,7 @@ export default function Agent() { | |||
| <ReactFlowProvider> | |||
| <div> | |||
| <SidebarProvider> | |||
| <AgentSidebar /> | |||
| <div className="w-full"> | |||
| <SidebarTrigger /> | |||
| <div className="w-full h-full"> | |||
| <AgentCanvas | |||
| drawerVisible={chatDrawerVisible} | |||