### What problem does this PR solve? Feat: Add SwitchForm component #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.1
| import { MinusCircleOutlined } from '@ant-design/icons'; | import { MinusCircleOutlined } from '@ant-design/icons'; | ||||
| import { Form, Input, Select } from 'antd'; | import { Form, Input, Select } from 'antd'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; | |||||
| import { useBuildVariableOptions } from '../../hooks/use-get-begin-query'; | |||||
| import { FormCollapse } from '../components/dynamic-input-variable'; | import { FormCollapse } from '../components/dynamic-input-variable'; | ||||
| type DynamicInputVariableProps = { | type DynamicInputVariableProps = { | ||||
| }: DynamicInputVariableProps) => { | }: DynamicInputVariableProps) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const valueOptions = useBuildComponentIdSelectOptions( | |||||
| node?.id, | |||||
| node?.parentId, | |||||
| ); | |||||
| const valueOptions = useBuildVariableOptions(node?.id, node?.parentId); | |||||
| return ( | return ( | ||||
| <FormCollapse title={t('flow.inputVariables')}> | <FormCollapse title={t('flow.inputVariables')}> | 
| import { ReactNode } from 'react'; | import { ReactNode } 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 { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; | |||||
| import { useBuildVariableOptions } from '../../hooks/use-get-begin-query'; | |||||
| interface IProps { | interface IProps { | ||||
| node?: RAGFlowNodeType; | node?: RAGFlowNodeType; | ||||
| control: form.control, | control: form.control, | ||||
| }); | }); | ||||
| const valueOptions = useBuildComponentIdSelectOptions( | |||||
| node?.id, | |||||
| node?.parentId, | |||||
| ); | |||||
| const valueOptions = useBuildVariableOptions(node?.id, node?.parentId); | |||||
| return ( | return ( | ||||
| <div className="space-y-5"> | <div className="space-y-5"> | 
| import { Button, Collapse, Flex, Form, Input, Select } from 'antd'; | import { Button, Collapse, Flex, Form, Input, Select } from 'antd'; | ||||
| import { PropsWithChildren, useCallback } from 'react'; | import { PropsWithChildren, useCallback } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; | |||||
| import { useBuildVariableOptions } from '../../hooks/use-get-begin-query'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const DynamicVariableForm = ({ node }: IProps) => { | const DynamicVariableForm = ({ node }: IProps) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const valueOptions = useBuildComponentIdSelectOptions( | |||||
| node?.id, | |||||
| node?.parentId, | |||||
| ); | |||||
| const valueOptions = useBuildVariableOptions(node?.id, node?.parentId); | |||||
| const form = Form.useFormInstance(); | const form = Form.useFormInstance(); | ||||
| const options = [ | const options = [ | 
| import { Plus, Trash2 } from 'lucide-react'; | import { Plus, Trash2 } from 'lucide-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 { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; | |||||
| import { useBuildVariableOptions } from '../../hooks/use-get-begin-query'; | |||||
| interface IProps { | interface IProps { | ||||
| node?: RAGFlowNodeType; | node?: RAGFlowNodeType; | ||||
| control: form.control, | control: form.control, | ||||
| }); | }); | ||||
| const valueOptions = useBuildComponentIdSelectOptions( | |||||
| node?.id, | |||||
| node?.parentId, | |||||
| ); | |||||
| const valueOptions = useBuildVariableOptions(node?.id, node?.parentId); | |||||
| const options = [ | const options = [ | ||||
| { value: VariableType.Reference, label: t('flow.reference') }, | { value: VariableType.Reference, label: t('flow.reference') }, | 
| import { DeleteOutlined } from '@ant-design/icons'; | import { DeleteOutlined } from '@ant-design/icons'; | ||||
| import { Button, Collapse, Flex, Input, Select, Table, TableProps } from 'antd'; | import { Button, Collapse, Flex, Input, Select, Table, TableProps } from 'antd'; | ||||
| import { trim } from 'lodash'; | import { trim } from 'lodash'; | ||||
| import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; | |||||
| import { useBuildVariableOptions } from '../../hooks/use-get-begin-query'; | |||||
| import { IInvokeVariable } from '../../interface'; | import { IInvokeVariable } from '../../interface'; | ||||
| import { useHandleOperateParameters } from './hooks'; | import { useHandleOperateParameters } from './hooks'; | ||||
| const nodeId = node?.id; | const nodeId = node?.id; | ||||
| const { t } = useTranslate('flow'); | const { t } = useTranslate('flow'); | ||||
| const options = useBuildComponentIdSelectOptions(nodeId, node?.parentId); | |||||
| const options = useBuildVariableOptions(nodeId, node?.parentId); | |||||
| const { | const { | ||||
| dataSource, | dataSource, | ||||
| handleAdd, | handleAdd, | 
| @lightBackgroundColor: rgba(150, 150, 150, 0.07); | |||||
| @darkBackgroundColor: rgba(150, 150, 150, 0.12); | |||||
| .caseCard { | |||||
| background-color: @lightBackgroundColor; | |||||
| } | |||||
| .conditionCard { | |||||
| background-color: @darkBackgroundColor; | |||||
| } | |||||
| .elseCase { | |||||
| background-color: @lightBackgroundColor; | |||||
| padding: 12px; | |||||
| border-radius: 8px; | |||||
| } | |||||
| .addButton { | |||||
| color: rgb(22, 119, 255); | |||||
| font-weight: 600; | |||||
| } | 
| import { CloseOutlined } from '@ant-design/icons'; | |||||
| import { Button, Card, Divider, Form, Input, Select } from 'antd'; | |||||
| import { FormContainer } from '@/components/form-container'; | |||||
| import { BlockButton, Button } from '@/components/ui/button'; | |||||
| import { Card, CardContent } from '@/components/ui/card'; | |||||
| import { | |||||
| Form, | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormMessage, | |||||
| } from '@/components/ui/form'; | |||||
| import { RAGFlowSelect } from '@/components/ui/select'; | |||||
| import { Textarea } from '@/components/ui/textarea'; | |||||
| import { ISwitchForm } from '@/interfaces/database/flow'; | |||||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||||
| import { X } from 'lucide-react'; | |||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import { useFieldArray, useForm, useFormContext } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | |||||
| import { | import { | ||||
| Operator, | Operator, | ||||
| SwitchElseTo, | |||||
| SwitchLogicOperatorOptions, | SwitchLogicOperatorOptions, | ||||
| SwitchOperatorOptions, | SwitchOperatorOptions, | ||||
| } from '../../constant'; | } from '../../constant'; | ||||
| import { useBuildFormSelectOptions } from '../../form-hooks'; | import { useBuildFormSelectOptions } from '../../form-hooks'; | ||||
| import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; | |||||
| import { | |||||
| useBuildComponentIdAndBeginOptions, | |||||
| useBuildVariableOptions, | |||||
| } from '../../hooks/use-get-begin-query'; | |||||
| import { IOperatorForm } from '../../interface'; | import { IOperatorForm } from '../../interface'; | ||||
| import { getOtherFieldValues } from '../../utils'; | |||||
| import { useValues } from './use-values'; | |||||
| import { ISwitchForm } from '@/interfaces/database/flow'; | |||||
| import styles from './index.less'; | |||||
| const ConditionKey = 'conditions'; | |||||
| const ItemKey = 'items'; | |||||
| type ConditionCardsProps = { | |||||
| name: string; | |||||
| } & IOperatorForm; | |||||
| function ConditionCards({ name: parentName, node }: ConditionCardsProps) { | |||||
| const form = useFormContext(); | |||||
| const { t } = useTranslation(); | |||||
| const componentIdOptions = useBuildComponentIdAndBeginOptions( | |||||
| node?.id, | |||||
| node?.parentId, | |||||
| ); | |||||
| const switchOperatorOptions = useMemo(() => { | |||||
| return SwitchOperatorOptions.map((x) => ({ | |||||
| value: x.value, | |||||
| label: t(`flow.switchOperatorOptions.${x.label}`), | |||||
| })); | |||||
| }, [t]); | |||||
| const name = `${parentName}.${ItemKey}`; | |||||
| const { fields, remove, append } = useFieldArray({ | |||||
| name: name, | |||||
| control: form.control, | |||||
| }); | |||||
| const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => { | |||||
| return ( | |||||
| <section className="flex-1 space-y-2.5"> | |||||
| {fields.map((field, index) => { | |||||
| return ( | |||||
| <div key={field.id} className="flex"> | |||||
| <Card className="bg-transparent border-input-border border flex-1"> | |||||
| <section className="p-2 bg-background-card flex justify-between"> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={`${name}.${index}.cpn_id`} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormControl> | |||||
| <RAGFlowSelect | |||||
| {...field} | |||||
| options={componentIdOptions} | |||||
| triggerClassName="w-30 text-background-checked" | |||||
| /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={`${name}.${index}.operator`} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormControl> | |||||
| <RAGFlowSelect | |||||
| {...field} | |||||
| options={switchOperatorOptions} | |||||
| triggerClassName="w-30" | |||||
| /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| </section> | |||||
| <CardContent className="p-4 "> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={`${name}.${index}.value`} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormControl> | |||||
| <Textarea {...field} className="bg-transparent" /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| </CardContent> | |||||
| </Card> | |||||
| <Button variant={'ghost'} onClick={() => remove(index)}> | |||||
| <X /> | |||||
| </Button> | |||||
| </div> | |||||
| ); | |||||
| })} | |||||
| <div className="pr-9"> | |||||
| <BlockButton | |||||
| className="mt-6" | |||||
| onClick={() => append({ operator: switchOperatorOptions[0].value })} | |||||
| > | |||||
| add | |||||
| </BlockButton> | |||||
| </div> | |||||
| </section> | |||||
| ); | |||||
| } | |||||
| const SwitchForm = ({ node }: IOperatorForm) => { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const values = useValues(); | |||||
| const FormSchema = z.object({ | |||||
| conditions: z.array( | |||||
| z | |||||
| .object({ | |||||
| logical_operator: z.string(), | |||||
| items: z | |||||
| .array( | |||||
| z.object({ | |||||
| cpn_id: z.string(), | |||||
| operator: z.string(), | |||||
| value: z.string().optional(), | |||||
| }), | |||||
| ) | |||||
| .optional(), | |||||
| to: z.array(z.string()).optional(), | |||||
| }) | |||||
| .optional(), | |||||
| ), | |||||
| }); | |||||
| const form = useForm({ | |||||
| defaultValues: values, | |||||
| resolver: zodResolver(FormSchema), | |||||
| }); | |||||
| const { fields, remove, append } = useFieldArray({ | |||||
| name: ConditionKey, | |||||
| control: form.control, | |||||
| }); | |||||
| const buildCategorizeToOptions = useBuildFormSelectOptions( | const buildCategorizeToOptions = useBuildFormSelectOptions( | ||||
| Operator.Switch, | Operator.Switch, | ||||
| node?.id, | node?.id, | ||||
| ); | ); | ||||
| const getSelectedConditionTos = () => { | const getSelectedConditionTos = () => { | ||||
| const conditions: ISwitchForm['conditions'] = | |||||
| form?.getFieldValue('conditions'); | |||||
| const conditions: ISwitchForm['conditions'] = form?.getValues('conditions'); | |||||
| return conditions?.filter((x) => !!x).map((x) => x?.to) ?? []; | return conditions?.filter((x) => !!x).map((x) => x?.to) ?? []; | ||||
| }; | }; | ||||
| })); | })); | ||||
| }, [t]); | }, [t]); | ||||
| const componentIdOptions = useBuildComponentIdSelectOptions( | |||||
| node?.id, | |||||
| node?.parentId, | |||||
| ); | |||||
| const componentIdOptions = useBuildVariableOptions(node?.id, node?.parentId); | |||||
| return ( | return ( | ||||
| <Form | |||||
| form={form} | |||||
| name="dynamic_form_complex" | |||||
| autoComplete="off" | |||||
| initialValues={{ conditions: [{}] }} | |||||
| onValuesChange={onValuesChange} | |||||
| layout={'vertical'} | |||||
| > | |||||
| <Form.List name="conditions"> | |||||
| {(fields, { add, remove }) => ( | |||||
| <div style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}> | |||||
| {fields.map((field) => { | |||||
| return ( | |||||
| <Card | |||||
| size="small" | |||||
| title={`Case ${field.name + 1}`} | |||||
| key={field.key} | |||||
| className={styles.caseCard} | |||||
| extra={ | |||||
| <CloseOutlined | |||||
| onClick={() => { | |||||
| remove(field.name); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Form.Item noStyle dependencies={[field.name, 'items']}> | |||||
| {({ getFieldValue }) => | |||||
| getFieldValue(['conditions', field.name, 'items']) | |||||
| ?.length > 1 && ( | |||||
| <Form.Item | |||||
| label={t('flow.logicalOperator')} | |||||
| name={[field.name, 'logical_operator']} | |||||
| > | |||||
| <Select options={switchLogicOperatorOptions} /> | |||||
| </Form.Item> | |||||
| ) | |||||
| } | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label={t('flow.nextStep')} | |||||
| name={[field.name, 'to']} | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| options={buildCategorizeToOptions([ | |||||
| form?.getFieldValue(SwitchElseTo), | |||||
| ...getOtherFieldValues( | |||||
| form!, | |||||
| 'conditions', | |||||
| field, | |||||
| 'to', | |||||
| ), | |||||
| ])} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item label="Condition"> | |||||
| <Form.List name={[field.name, 'items']}> | |||||
| {(subFields, subOpt) => ( | |||||
| <div | |||||
| style={{ | |||||
| display: 'flex', | |||||
| flexDirection: 'column', | |||||
| rowGap: 16, | |||||
| }} | |||||
| > | |||||
| {subFields.map((subField) => ( | |||||
| <Card | |||||
| key={subField.key} | |||||
| title={null} | |||||
| size="small" | |||||
| className={styles.conditionCard} | |||||
| bordered | |||||
| extra={ | |||||
| <CloseOutlined | |||||
| onClick={() => { | |||||
| subOpt.remove(subField.name); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Form.Item | |||||
| label={t('flow.componentId')} | |||||
| name={[subField.name, 'cpn_id']} | |||||
| > | |||||
| <Select | |||||
| placeholder={t('flow.componentId')} | |||||
| options={componentIdOptions} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label={t('flow.operator')} | |||||
| name={[subField.name, 'operator']} | |||||
| > | |||||
| <Select | |||||
| placeholder={t('flow.operator')} | |||||
| options={switchOperatorOptions} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label={t('flow.value')} | |||||
| name={[subField.name, 'value']} | |||||
| > | |||||
| <Input placeholder={t('flow.value')} /> | |||||
| </Form.Item> | |||||
| </Card> | |||||
| ))} | |||||
| <Button | |||||
| onClick={() => { | |||||
| form?.setFieldValue( | |||||
| ['conditions', field.name, 'logical_operator'], | |||||
| SwitchLogicOperatorOptions[0], | |||||
| ); | |||||
| subOpt.add({ | |||||
| operator: SwitchOperatorOptions[0].value, | |||||
| }); | |||||
| }} | |||||
| block | |||||
| className={styles.addButton} | |||||
| > | |||||
| + Add Condition | |||||
| </Button> | |||||
| </div> | |||||
| )} | |||||
| </Form.List> | |||||
| </Form.Item> | |||||
| </Card> | |||||
| ); | |||||
| })} | |||||
| <Button onClick={() => add()} block className={styles.addButton}> | |||||
| + Add Case | |||||
| </Button> | |||||
| </div> | |||||
| )} | |||||
| </Form.List> | |||||
| <Divider /> | |||||
| <Form.Item | |||||
| label={'ELSE'} | |||||
| name={[SwitchElseTo]} | |||||
| className={styles.elseCase} | |||||
| <Form {...form}> | |||||
| <form | |||||
| className="space-y-6 p-5 " | |||||
| onSubmit={(e) => { | |||||
| e.preventDefault(); | |||||
| }} | |||||
| > | > | ||||
| <Select | |||||
| allowClear | |||||
| options={buildCategorizeToOptions(getSelectedConditionTos())} | |||||
| /> | |||||
| </Form.Item> | |||||
| {fields.map((field, index) => { | |||||
| return ( | |||||
| <FormContainer key={field.id} className=""> | |||||
| <div>IF</div> | |||||
| <section className="flex items-center gap-2 !mt-2"> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={`${ConditionKey}.${index}.logical_operator`} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormControl> | |||||
| <RAGFlowSelect | |||||
| {...field} | |||||
| options={switchLogicOperatorOptions} | |||||
| triggerClassName="w-18" | |||||
| /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <ConditionCards | |||||
| name={`${ConditionKey}.${index}`} | |||||
| ></ConditionCards> | |||||
| </section> | |||||
| </FormContainer> | |||||
| ); | |||||
| })} | |||||
| <BlockButton | |||||
| onClick={() => | |||||
| append({ logical_operator: SwitchLogicOperatorOptions[0] }) | |||||
| } | |||||
| > | |||||
| add | |||||
| </BlockButton> | |||||
| </form> | |||||
| </Form> | </Form> | ||||
| ); | ); | ||||
| }; | }; | 
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||||
| import { isEmpty } from 'lodash'; | |||||
| import { useMemo } from 'react'; | |||||
| const defaultValues = { | |||||
| conditions: [], | |||||
| }; | |||||
| export function useValues(node?: RAGFlowNodeType) { | |||||
| const values = useMemo(() => { | |||||
| const formData = node?.data?.form; | |||||
| if (isEmpty(formData)) { | |||||
| return defaultValues; | |||||
| } | |||||
| return formData; | |||||
| }, [node]); | |||||
| return values; | |||||
| } | 
| Operator.Note, | Operator.Note, | ||||
| ]; | ]; | ||||
| export const useBuildComponentIdSelectOptions = ( | |||||
| nodeId?: string, | |||||
| parentId?: string, | |||||
| ) => { | |||||
| const nodes = useGraphStore((state) => state.nodes); | |||||
| export function useBuildBeginVariableOptions() { | |||||
| const getBeginNodeDataQuery = useGetBeginNodeDataQuery(); | const getBeginNodeDataQuery = useGetBeginNodeDataQuery(); | ||||
| const nodeOutputOptions = useBuildNodeOutputOptions(nodeId); | |||||
| // Limit the nodes inside iteration to only reference peer nodes with the same parentId and other external nodes other than their parent nodes | |||||
| const filterChildNodesToSameParentOrExternal = useCallback( | |||||
| (node: RAGFlowNodeType) => { | |||||
| // Node inside iteration | |||||
| if (parentId) { | |||||
| return ( | |||||
| (node.parentId === parentId || node.parentId === undefined) && | |||||
| node.id !== parentId | |||||
| ); | |||||
| } | |||||
| return node.parentId === undefined; // The outermost node | |||||
| }, | |||||
| [parentId], | |||||
| ); | |||||
| const componentIdOptions = useMemo(() => { | |||||
| return nodes | |||||
| .filter( | |||||
| (x) => | |||||
| x.id !== nodeId && | |||||
| !ExcludedNodes.some((y) => y === x.data.label) && | |||||
| filterChildNodesToSameParentOrExternal(x), | |||||
| ) | |||||
| .map((x) => ({ label: x.data.name, value: x.id })); | |||||
| }, [nodes, nodeId, filterChildNodesToSameParentOrExternal]); | |||||
| const options = useMemo(() => { | const options = useMemo(() => { | ||||
| const query: BeginQuery[] = getBeginNodeDataQuery(); | const query: BeginQuery[] = getBeginNodeDataQuery(); | ||||
| return [ | return [ | ||||
| value: `begin@${x.key}`, | value: `begin@${x.key}`, | ||||
| })), | })), | ||||
| }, | }, | ||||
| ...nodeOutputOptions, | |||||
| ]; | ]; | ||||
| }, [getBeginNodeDataQuery, nodeOutputOptions]); | |||||
| }, [getBeginNodeDataQuery]); | |||||
| return options; | |||||
| } | |||||
| export const useBuildVariableOptions = (nodeId?: string) => { | |||||
| const nodeOutputOptions = useBuildNodeOutputOptions(nodeId); | |||||
| const beginOptions = useBuildBeginVariableOptions(); | |||||
| const options = useMemo(() => { | |||||
| return [...beginOptions, ...nodeOutputOptions]; | |||||
| }, [beginOptions, nodeOutputOptions]); | |||||
| return options; | return options; | ||||
| }; | }; | ||||
| export const useGetComponentLabelByValue = (nodeId: string) => { | export const useGetComponentLabelByValue = (nodeId: string) => { | ||||
| const options = useBuildComponentIdSelectOptions(nodeId); | |||||
| const options = useBuildVariableOptions(nodeId); | |||||
| const flattenOptions = useMemo(() => { | const flattenOptions = useMemo(() => { | ||||
| return options.reduce<DefaultOptionType[]>((pre, cur) => { | return options.reduce<DefaultOptionType[]>((pre, cur) => { | ||||
| export function useBuildQueryVariableOptions() { | export function useBuildQueryVariableOptions() { | ||||
| const { data } = useFetchAgent(); | const { data } = useFetchAgent(); | ||||
| const node = useContext(AgentFormContext); | const node = useContext(AgentFormContext); | ||||
| const options = useBuildComponentIdSelectOptions(node?.id, node?.parentId); | |||||
| const options = useBuildVariableOptions(node?.id); | |||||
| const nextOptions = useMemo(() => { | const nextOptions = useMemo(() => { | ||||
| const globalOptions = Object.keys(data?.dsl?.globals ?? {}).map((x) => ({ | const globalOptions = Object.keys(data?.dsl?.globals ?? {}).map((x) => ({ | ||||
| return nextOptions; | return nextOptions; | ||||
| } | } | ||||
| export function useBuildComponentIdOptions(nodeId?: string, parentId?: string) { | |||||
| const nodes = useGraphStore((state) => state.nodes); | |||||
| // Limit the nodes inside iteration to only reference peer nodes with the same parentId and other external nodes other than their parent nodes | |||||
| const filterChildNodesToSameParentOrExternal = useCallback( | |||||
| (node: RAGFlowNodeType) => { | |||||
| // Node inside iteration | |||||
| if (parentId) { | |||||
| return ( | |||||
| (node.parentId === parentId || node.parentId === undefined) && | |||||
| node.id !== parentId | |||||
| ); | |||||
| } | |||||
| return node.parentId === undefined; // The outermost node | |||||
| }, | |||||
| [parentId], | |||||
| ); | |||||
| const componentIdOptions = useMemo(() => { | |||||
| return nodes | |||||
| .filter( | |||||
| (x) => | |||||
| x.id !== nodeId && | |||||
| !ExcludedNodes.some((y) => y === x.data.label) && | |||||
| filterChildNodesToSameParentOrExternal(x), | |||||
| ) | |||||
| .map((x) => ({ label: x.data.name, value: x.id })); | |||||
| }, [nodes, nodeId, filterChildNodesToSameParentOrExternal]); | |||||
| return [ | |||||
| { | |||||
| label: <span>Component Output</span>, | |||||
| title: 'Component Output', | |||||
| options: componentIdOptions, | |||||
| }, | |||||
| ]; | |||||
| } | |||||
| export function useBuildComponentIdAndBeginOptions( | |||||
| nodeId?: string, | |||||
| parentId?: string, | |||||
| ) { | |||||
| const componentIdOptions = useBuildComponentIdOptions(nodeId, parentId); | |||||
| const beginOptions = useBuildBeginVariableOptions(); | |||||
| return [...beginOptions, ...componentIdOptions]; | |||||
| } |