### What problem does this PR solve? Feat: Convert the arguments parameter of the code operator to a dictionary #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| <Flex vertical gap={8} flex={1}> | <Flex vertical gap={8} flex={1}> | ||||
| <Space> | <Space> | ||||
| {isAssistant ? ( | {isAssistant ? ( | ||||
| index !== 0 && ( | |||||
| <AssistantGroupButton | |||||
| messageId={item.id} | |||||
| content={item.content} | |||||
| prompt={item.prompt} | |||||
| showLikeButton={showLikeButton} | |||||
| audioBinary={item.audio_binary} | |||||
| showLoudspeaker={showLoudspeaker} | |||||
| ></AssistantGroupButton> | |||||
| ) | |||||
| <AssistantGroupButton | |||||
| messageId={item.id} | |||||
| content={item.content} | |||||
| prompt={item.prompt} | |||||
| showLikeButton={showLikeButton} | |||||
| audioBinary={item.audio_binary} | |||||
| showLoudspeaker={showLoudspeaker} | |||||
| ></AssistantGroupButton> | |||||
| ) : ( | ) : ( | ||||
| <UserGroupButton | <UserGroupButton | ||||
| content={item.content} | content={item.content} | 
| export interface IAnswer { | export interface IAnswer { | ||||
| answer: string; | answer: string; | ||||
| reference: IReference; | |||||
| reference?: IReference; | |||||
| conversationId?: string; | conversationId?: string; | ||||
| prompt?: string; | prompt?: string; | ||||
| id?: string; | id?: string; | 
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import NodeHeader from './node-header'; | import NodeHeader from './node-header'; | ||||
| import { NodeWrapper } from './node-wrapper'; | import { NodeWrapper } from './node-wrapper'; | ||||
| import { ToolBar } from './toolbar'; | |||||
| function ResizeIcon() { | function ResizeIcon() { | ||||
| return ( | return ( | ||||
| return ( | return ( | ||||
| <section | <section | ||||
| className={cn('h-full bg-transparent rounded-b-md', { | |||||
| className={cn('h-full bg-transparent rounded-b-md relative', { | |||||
| [styles.selectedHeader]: selected, | [styles.selectedHeader]: selected, | ||||
| })} | })} | ||||
| > | > | ||||
| <ToolBar selected={selected} id={id} label={data.label}> | |||||
| <div className="h-full w-full"></div> | |||||
| </ToolBar> | |||||
| <NodeResizeControl style={controlStyle} minWidth={100} minHeight={50}> | <NodeResizeControl style={controlStyle} minWidth={100} minHeight={50}> | ||||
| <ResizeIcon /> | <ResizeIcon /> | ||||
| </NodeResizeControl> | </NodeResizeControl> | 
| import { v4 as uuid } from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||
| import { BeginId } from '../constant'; | import { BeginId } from '../constant'; | ||||
| import { AgentChatLogContext } from '../context'; | import { AgentChatLogContext } from '../context'; | ||||
| import { transferInputsArrayToObject } from '../form/begin-form/use-watch-change'; | |||||
| import { useGetBeginNodeDataQuery } from '../hooks/use-get-begin-query'; | |||||
| import { BeginQuery } from '../interface'; | import { BeginQuery } from '../interface'; | ||||
| import useGraphStore from '../store'; | import useGraphStore from '../store'; | ||||
| import { receiveMessageError } from '../utils'; | import { receiveMessageError } from '../utils'; | ||||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | ||||
| const { refetch } = useFetchAgent(); | const { refetch } = useFetchAgent(); | ||||
| const { addEventList } = useContext(AgentChatLogContext); | const { addEventList } = useContext(AgentChatLogContext); | ||||
| const getBeginNodeDataQuery = useGetBeginNodeDataQuery(); | |||||
| const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE( | const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE( | ||||
| api.runCanvas, | api.runCanvas, | ||||
| ); | ); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (prologue) { | |||||
| const query = getBeginNodeDataQuery(); | |||||
| if (query.length > 0) { | |||||
| send({ id: agentId, inputs: transferInputsArrayToObject(query) }); | |||||
| } else if (prologue) { | |||||
| addNewestOneAnswer({ | addNewestOneAnswer({ | ||||
| answer: prologue, | answer: prologue, | ||||
| }); | }); | ||||
| } | } | ||||
| }, [addNewestOneAnswer, prologue]); | |||||
| }, [ | |||||
| addNewestOneAnswer, | |||||
| agentId, | |||||
| getBeginNodeDataQuery, | |||||
| prologue, | |||||
| send, | |||||
| sendFormMessage, | |||||
| ]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| addEventList(answerList); | addEventList(answerList); | 
| 'use client'; | |||||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||||
| import { useForm } from 'react-hook-form'; | |||||
| import { toast } from 'sonner'; | |||||
| import { z } from 'zod'; | |||||
| import { Button } from '@/components/ui/button'; | |||||
| import { | |||||
| Form, | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| FormMessage, | |||||
| } from '@/components/ui/form'; | |||||
| import { Input } from '@/components/ui/input'; | |||||
| import { Message } from '@/interfaces/database/chat'; | |||||
| import { get } from 'lodash'; | |||||
| import { useParams } from 'umi'; | |||||
| import { useSendNextMessage } from './hooks'; | |||||
| const FormSchema = z.object({ | |||||
| username: z.string().min(2, { | |||||
| message: 'Username must be at least 2 characters.', | |||||
| }), | |||||
| }); | |||||
| type InputFormProps = Pick<ReturnType<typeof useSendNextMessage>, 'send'> & { | |||||
| message: Message; | |||||
| }; | |||||
| export function InputForm({ send, message }: InputFormProps) { | |||||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||||
| resolver: zodResolver(FormSchema), | |||||
| defaultValues: { | |||||
| username: '', | |||||
| }, | |||||
| }); | |||||
| const { id: canvasId } = useParams(); | |||||
| function onSubmit(data: z.infer<typeof FormSchema>) { | |||||
| const inputs = get(message, 'data.inputs', {}); | |||||
| const nextInputs = Object.entries(inputs).reduce((pre, [key, val]) => { | |||||
| pre[key] = { ...val, value: data.username }; | |||||
| return pre; | |||||
| }, {}); | |||||
| send({ | |||||
| inputs: nextInputs, | |||||
| id: canvasId, | |||||
| }); | |||||
| toast('You submitted the following values', { | |||||
| description: ( | |||||
| <pre className="mt-2 w-[320px] rounded-md bg-neutral-950 p-4"> | |||||
| <code className="text-white">{JSON.stringify(data, null, 2)}</code> | |||||
| </pre> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| return ( | |||||
| <Form {...form}> | |||||
| <form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6"> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="username" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>Username</FormLabel> | |||||
| <FormControl> | |||||
| <Input placeholder="shadcn" {...field} /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <Button type="submit">Submit</Button> | |||||
| </form> | |||||
| </Form> | |||||
| ); | |||||
| } | 
| }; | }; | ||||
| export const initialCodeValues = { | export const initialCodeValues = { | ||||
| lang: 'python', | |||||
| lang: ProgrammingLanguage.Python, | |||||
| script: CodeTemplateStrMap[ProgrammingLanguage.Python], | script: CodeTemplateStrMap[ProgrammingLanguage.Python], | ||||
| arguments: [ | |||||
| { | |||||
| name: 'arg1', | |||||
| }, | |||||
| { | |||||
| name: 'arg2', | |||||
| }, | |||||
| ], | |||||
| arguments: { | |||||
| arg1: '', | |||||
| arg2: '', | |||||
| }, | |||||
| }; | }; | ||||
| export const initialWaitingDialogueValues = {}; | export const initialWaitingDialogueValues = {}; | 
| import { BeginQuery } from '../../interface'; | import { BeginQuery } from '../../interface'; | ||||
| import useGraphStore from '../../store'; | import useGraphStore from '../../store'; | ||||
| function transferInputsArrayToObject(inputs: BeginQuery[] = []) { | |||||
| export function transferInputsArrayToObject(inputs: BeginQuery[] = []) { | |||||
| return inputs.reduce<Record<string, Omit<BeginQuery, 'key'>>>((pre, cur) => { | return inputs.reduce<Record<string, Omit<BeginQuery, 'key'>>>((pre, cur) => { | ||||
| pre[cur.key] = omit(cur, 'key'); | pre[cur.key] = omit(cur, 'key'); | ||||
| import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent'; | |||||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | import { RAGFlowNodeType } from '@/interfaces/database/flow'; | ||||
| import { isEmpty } from 'lodash'; | import { isEmpty } from 'lodash'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import { initialCodeValues } from '../../constant'; | |||||
| export function useValues(node?: RAGFlowNodeType) { | |||||
| const defaultValues = useMemo( | |||||
| () => ({ | |||||
| lang: ProgrammingLanguage.Python, | |||||
| script: CodeTemplateStrMap[ProgrammingLanguage.Python], | |||||
| arguments: [], | |||||
| }), | |||||
| [], | |||||
| ); | |||||
| function convertToArray(args: Record<string, string>) { | |||||
| return Object.entries(args).map(([key, value]) => ({ | |||||
| name: key, | |||||
| component_id: value, | |||||
| })); | |||||
| } | |||||
| export function useValues(node?: RAGFlowNodeType) { | |||||
| const values = useMemo(() => { | const values = useMemo(() => { | ||||
| const formData = node?.data?.form; | const formData = node?.data?.form; | ||||
| if (isEmpty(formData)) { | if (isEmpty(formData)) { | ||||
| return defaultValues; | |||||
| return initialCodeValues; | |||||
| } | } | ||||
| return formData; | |||||
| }, [defaultValues, node?.data?.form]); | |||||
| return { ...formData, arguments: convertToArray(formData.arguments) }; | |||||
| }, [node?.data?.form]); | |||||
| return values; | return values; | ||||
| } | } | 
| import { UseFormReturn, useWatch } from 'react-hook-form'; | import { UseFormReturn, useWatch } from 'react-hook-form'; | ||||
| import useGraphStore from '../../store'; | import useGraphStore from '../../store'; | ||||
| function convertToObject(list: Array<{ name: string; component_id: string }>) { | |||||
| return list.reduce<Record<string, string>>((pre, cur) => { | |||||
| pre[cur.name] = cur.component_id; | |||||
| return pre; | |||||
| }, {}); | |||||
| } | |||||
| export function useWatchFormChange(id?: string, form?: UseFormReturn) { | export function useWatchFormChange(id?: string, form?: UseFormReturn) { | ||||
| let values = useWatch({ control: form?.control }); | let values = useWatch({ control: form?.control }); | ||||
| const updateNodeForm = useGraphStore((state) => state.updateNodeForm); | const updateNodeForm = useGraphStore((state) => state.updateNodeForm); | ||||
| // Manually triggered form updates are synchronized to the canvas | // Manually triggered form updates are synchronized to the canvas | ||||
| if (id) { | if (id) { | ||||
| values = form?.getValues() || {}; | values = form?.getValues() || {}; | ||||
| let nextValues: any = values; | |||||
| let nextValues: any = { | |||||
| ...values, | |||||
| arguments: convertToObject(values.arguments), | |||||
| }; | |||||
| updateNodeForm(id, nextValues); | updateNodeForm(id, nextValues); | ||||
| } | } | 
| let content: ReactNode = ( | let content: ReactNode = ( | ||||
| <span className="text-blue-600">{this.__label}</span> | <span className="text-blue-600">{this.__label}</span> | ||||
| ); | ); | ||||
| if (this.__value.startsWith(prefix)) { | |||||
| if (this.__value?.startsWith(prefix)) { | |||||
| content = ( | content = ( | ||||
| <div> | <div> | ||||
| <span>{i18n.t(`flow.begin`)}</span> / {content} | <span>{i18n.t(`flow.begin`)}</span> / {content} | 
| } | } | ||||
| $getRoot().clear().append(paragraph); | $getRoot().clear().append(paragraph); | ||||
| if ($isRangeSelection($getSelection())) { | |||||
| $getRoot().selectEnd(); | |||||
| } | |||||
| }, | }, | ||||
| [findLabelByValue], | [findLabelByValue], | ||||
| ); | ); |