…e connecting line (#9226) ### What problem does this PR solve? Can directly generate an agent node by dragging and dropping the connecting line (#9226) ### Type of change - [ ] Bug Fix (non-breaking change which fixes an issue) - [x] New Feature (non-breaking change which adds functionality) - [ ] Documentation Update - [ ] Refactoring - [ ] Performance Improvement - [ ] Other (please describe):tags/v0.20.2
| @@ -0,0 +1,56 @@ | |||
| import { | |||
| createContext, | |||
| ReactNode, | |||
| useCallback, | |||
| useContext, | |||
| useRef, | |||
| } from 'react'; | |||
| interface DropdownContextType { | |||
| canShowDropdown: () => boolean; | |||
| setActiveDropdown: (type: 'handle' | 'drag') => void; | |||
| clearActiveDropdown: () => void; | |||
| } | |||
| const DropdownContext = createContext<DropdownContextType | null>(null); | |||
| export const useDropdownManager = () => { | |||
| const context = useContext(DropdownContext); | |||
| if (!context) { | |||
| throw new Error('useDropdownManager must be used within DropdownProvider'); | |||
| } | |||
| return context; | |||
| }; | |||
| interface DropdownProviderProps { | |||
| children: ReactNode; | |||
| } | |||
| export const DropdownProvider = ({ children }: DropdownProviderProps) => { | |||
| const activeDropdownRef = useRef<'handle' | 'drag' | null>(null); | |||
| const canShowDropdown = useCallback(() => { | |||
| const current = activeDropdownRef.current; | |||
| return !current; | |||
| }, []); | |||
| const setActiveDropdown = useCallback((type: 'handle' | 'drag') => { | |||
| activeDropdownRef.current = type; | |||
| }, []); | |||
| const clearActiveDropdown = useCallback(() => { | |||
| activeDropdownRef.current = null; | |||
| }, []); | |||
| const value: DropdownContextType = { | |||
| canShowDropdown, | |||
| setActiveDropdown, | |||
| clearActiveDropdown, | |||
| }; | |||
| return ( | |||
| <DropdownContext.Provider value={value}> | |||
| {children} | |||
| </DropdownContext.Provider> | |||
| ); | |||
| }; | |||
| @@ -4,17 +4,20 @@ import { | |||
| TooltipContent, | |||
| TooltipTrigger, | |||
| } from '@/components/ui/tooltip'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { | |||
| Connection, | |||
| ConnectionMode, | |||
| ControlButton, | |||
| Controls, | |||
| NodeTypes, | |||
| Position, | |||
| ReactFlow, | |||
| } from '@xyflow/react'; | |||
| import '@xyflow/react/dist/style.css'; | |||
| import { NotebookPen } from 'lucide-react'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { ChatSheet } from '../chat/chat-sheet'; | |||
| import { AgentBackground } from '../components/background'; | |||
| @@ -22,7 +25,9 @@ import { | |||
| AgentChatContext, | |||
| AgentChatLogContext, | |||
| AgentInstanceContext, | |||
| HandleContext, | |||
| } from '../context'; | |||
| import FormSheet from '../form-sheet/next'; | |||
| import { | |||
| useHandleDrop, | |||
| @@ -33,6 +38,8 @@ import { useAddNode } from '../hooks/use-add-node'; | |||
| import { useBeforeDelete } from '../hooks/use-before-delete'; | |||
| import { useCacheChatLog } from '../hooks/use-cache-chat-log'; | |||
| import { useMoveNote } from '../hooks/use-move-note'; | |||
| import { useDropdownManager } from './context'; | |||
| import { | |||
| useHideFormSheetOnNodeDeletion, | |||
| useShowDrawer, | |||
| @@ -46,6 +53,7 @@ import { RagNode } from './node'; | |||
| import { AgentNode } from './node/agent-node'; | |||
| import { BeginNode } from './node/begin-node'; | |||
| import { CategorizeNode } from './node/categorize-node'; | |||
| import { InnerNextStepDropdown } from './node/dropdown/next-step-dropdown'; | |||
| import { GenerateNode } from './node/generate-node'; | |||
| import { InvokeNode } from './node/invoke-node'; | |||
| import { IterationNode, IterationStartNode } from './node/iteration-node'; | |||
| @@ -96,7 +104,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| const { | |||
| nodes, | |||
| edges, | |||
| onConnect, | |||
| onConnect: originalOnConnect, | |||
| onEdgesChange, | |||
| onNodesChange, | |||
| onSelectionChange, | |||
| @@ -147,14 +155,6 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| const { theme } = useTheme(); | |||
| const onPaneClick = useCallback(() => { | |||
| hideFormDrawer(); | |||
| if (imgVisible) { | |||
| addNoteNode(mouse); | |||
| hideImage(); | |||
| } | |||
| }, [addNoteNode, hideFormDrawer, hideImage, imgVisible, mouse]); | |||
| useEffect(() => { | |||
| if (!chatVisible) { | |||
| clearEventList(); | |||
| @@ -172,6 +172,73 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| useHideFormSheetOnNodeDeletion({ hideFormDrawer }); | |||
| const { visible, hideModal, showModal } = useSetModalState(); | |||
| const [dropdownPosition, setDropdownPosition] = useState({ x: 0, y: 0 }); | |||
| const isConnectedRef = useRef(false); | |||
| const connectionStartRef = useRef<{ | |||
| nodeId: string; | |||
| handleId: string; | |||
| } | null>(null); | |||
| const preventCloseRef = useRef(false); | |||
| const { setActiveDropdown, clearActiveDropdown } = useDropdownManager(); | |||
| const onPaneClick = useCallback(() => { | |||
| hideFormDrawer(); | |||
| if (visible && !preventCloseRef.current) { | |||
| hideModal(); | |||
| clearActiveDropdown(); | |||
| } | |||
| if (imgVisible) { | |||
| addNoteNode(mouse); | |||
| hideImage(); | |||
| } | |||
| }, [ | |||
| hideFormDrawer, | |||
| visible, | |||
| hideModal, | |||
| imgVisible, | |||
| addNoteNode, | |||
| mouse, | |||
| hideImage, | |||
| clearActiveDropdown, | |||
| ]); | |||
| const onConnect = (connection: Connection) => { | |||
| originalOnConnect(connection); | |||
| isConnectedRef.current = true; | |||
| }; | |||
| const OnConnectStart = (event: any, params: any) => { | |||
| isConnectedRef.current = false; | |||
| if (params && params.nodeId && params.handleId) { | |||
| connectionStartRef.current = { | |||
| nodeId: params.nodeId, | |||
| handleId: params.handleId, | |||
| }; | |||
| } else { | |||
| connectionStartRef.current = null; | |||
| } | |||
| }; | |||
| const OnConnectEnd = (event: MouseEvent | TouchEvent) => { | |||
| if ('clientX' in event && 'clientY' in event) { | |||
| const { clientX, clientY } = event; | |||
| setDropdownPosition({ x: clientX, y: clientY }); | |||
| if (!isConnectedRef.current) { | |||
| setActiveDropdown('drag'); | |||
| showModal(); | |||
| preventCloseRef.current = true; | |||
| setTimeout(() => { | |||
| preventCloseRef.current = false; | |||
| }, 300); | |||
| } | |||
| } | |||
| }; | |||
| return ( | |||
| <div className={styles.canvasWrapper}> | |||
| <svg | |||
| @@ -206,6 +273,8 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| nodeTypes={nodeTypes} | |||
| edgeTypes={edgeTypes} | |||
| onDrop={onDrop} | |||
| onConnectStart={OnConnectStart} | |||
| onConnectEnd={OnConnectEnd} | |||
| onDragOver={onDragOver} | |||
| onNodeClick={onNodeClick} | |||
| onPaneClick={onPaneClick} | |||
| @@ -243,6 +312,27 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| </ControlButton> | |||
| </Controls> | |||
| </ReactFlow> | |||
| {visible && ( | |||
| <HandleContext.Provider | |||
| value={{ | |||
| nodeId: connectionStartRef.current?.nodeId || '', | |||
| id: connectionStartRef.current?.handleId || '', | |||
| type: 'source', | |||
| position: Position.Right, | |||
| isFromConnectionDrag: true, | |||
| }} | |||
| > | |||
| <InnerNextStepDropdown | |||
| hideModal={() => { | |||
| hideModal(); | |||
| clearActiveDropdown(); | |||
| }} | |||
| position={dropdownPosition} | |||
| > | |||
| <span></span> | |||
| </InnerNextStepDropdown> | |||
| </HandleContext.Provider> | |||
| )} | |||
| </AgentInstanceContext.Provider> | |||
| <NotebookPen | |||
| className={cn('hidden absolute size-6', { block: imgVisible })} | |||
| @@ -20,55 +20,111 @@ import { IModalProps } from '@/interfaces/common'; | |||
| import { Operator } from '@/pages/agent/constant'; | |||
| import { AgentInstanceContext, HandleContext } from '@/pages/agent/context'; | |||
| import OperatorIcon from '@/pages/agent/operator-icon'; | |||
| import { Position } from '@xyflow/react'; | |||
| import { lowerFirst } from 'lodash'; | |||
| import { PropsWithChildren, createContext, memo, useContext } from 'react'; | |||
| import { | |||
| PropsWithChildren, | |||
| createContext, | |||
| memo, | |||
| useContext, | |||
| useEffect, | |||
| useRef, | |||
| } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| type OperatorItemProps = { operators: Operator[] }; | |||
| type OperatorItemProps = { | |||
| operators: Operator[]; | |||
| isCustomDropdown?: boolean; | |||
| mousePosition?: { x: number; y: number }; | |||
| }; | |||
| const HideModalContext = createContext<IModalProps<any>['showModal']>(() => {}); | |||
| const OnNodeCreatedContext = createContext< | |||
| ((newNodeId: string) => void) | undefined | |||
| >(undefined); | |||
| function OperatorItemList({ operators }: OperatorItemProps) { | |||
| function OperatorItemList({ | |||
| operators, | |||
| isCustomDropdown = false, | |||
| mousePosition, | |||
| }: OperatorItemProps) { | |||
| const { addCanvasNode } = useContext(AgentInstanceContext); | |||
| const { nodeId, id, position } = useContext(HandleContext); | |||
| const handleContext = useContext(HandleContext); | |||
| const hideModal = useContext(HideModalContext); | |||
| const onNodeCreated = useContext(OnNodeCreatedContext); | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <ul className="space-y-2"> | |||
| {operators.map((x) => { | |||
| return ( | |||
| <Tooltip key={x}> | |||
| <TooltipTrigger asChild> | |||
| <DropdownMenuItem | |||
| key={x} | |||
| className="hover:bg-bg-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start" | |||
| onClick={addCanvasNode(x, { | |||
| nodeId, | |||
| id, | |||
| position, | |||
| })} | |||
| onSelect={() => hideModal?.()} | |||
| > | |||
| <OperatorIcon name={x}></OperatorIcon> | |||
| {t(`flow.${lowerFirst(x)}`)} | |||
| </DropdownMenuItem> | |||
| </TooltipTrigger> | |||
| <TooltipContent side="right"> | |||
| <p>{t(`flow.${lowerFirst(x)}Description`)}</p> | |||
| </TooltipContent> | |||
| </Tooltip> | |||
| ); | |||
| })} | |||
| </ul> | |||
| ); | |||
| const handleClick = (operator: Operator) => { | |||
| const contextData = handleContext || { | |||
| nodeId: '', | |||
| id: '', | |||
| type: 'source' as const, | |||
| position: Position.Right, | |||
| isFromConnectionDrag: true, | |||
| }; | |||
| const mockEvent = mousePosition | |||
| ? { | |||
| clientX: mousePosition.x, | |||
| clientY: mousePosition.y, | |||
| } | |||
| : undefined; | |||
| const newNodeId = addCanvasNode(operator, contextData)(mockEvent); | |||
| if (onNodeCreated && newNodeId) { | |||
| onNodeCreated(newNodeId); | |||
| } | |||
| hideModal?.(); | |||
| }; | |||
| const renderOperatorItem = (operator: Operator) => { | |||
| const commonContent = ( | |||
| <div className="hover:bg-background-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start"> | |||
| <OperatorIcon name={operator} /> | |||
| {t(`flow.${lowerFirst(operator)}`)} | |||
| </div> | |||
| ); | |||
| return ( | |||
| <Tooltip key={operator}> | |||
| <TooltipTrigger asChild> | |||
| {isCustomDropdown ? ( | |||
| <li onClick={() => handleClick(operator)}>{commonContent}</li> | |||
| ) : ( | |||
| <DropdownMenuItem | |||
| key={operator} | |||
| className="hover:bg-background-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start" | |||
| onClick={() => handleClick(operator)} | |||
| onSelect={() => hideModal?.()} | |||
| > | |||
| <OperatorIcon name={operator} /> | |||
| {t(`flow.${lowerFirst(operator)}`)} | |||
| </DropdownMenuItem> | |||
| )} | |||
| </TooltipTrigger> | |||
| <TooltipContent side="right"> | |||
| <p>{t(`flow.${lowerFirst(operator)}Description`)}</p> | |||
| </TooltipContent> | |||
| </Tooltip> | |||
| ); | |||
| }; | |||
| return <ul className="space-y-2">{operators.map(renderOperatorItem)}</ul>; | |||
| } | |||
| function AccordionOperators() { | |||
| function AccordionOperators({ | |||
| isCustomDropdown = false, | |||
| mousePosition, | |||
| }: { | |||
| isCustomDropdown?: boolean; | |||
| mousePosition?: { x: number; y: number }; | |||
| }) { | |||
| return ( | |||
| <Accordion | |||
| type="multiple" | |||
| className="px-2 text-text-primary max-h-[45vh] overflow-auto" | |||
| className="px-2 text-text-title max-h-[45vh] overflow-auto" | |||
| defaultValue={['item-1', 'item-2', 'item-3', 'item-4', 'item-5']} | |||
| > | |||
| <AccordionItem value="item-1"> | |||
| @@ -76,6 +132,8 @@ function AccordionOperators() { | |||
| <AccordionContent className="flex flex-col gap-4 text-balance"> | |||
| <OperatorItemList | |||
| operators={[Operator.Agent, Operator.Retrieval]} | |||
| isCustomDropdown={isCustomDropdown} | |||
| mousePosition={mousePosition} | |||
| ></OperatorItemList> | |||
| </AccordionContent> | |||
| </AccordionItem> | |||
| @@ -84,6 +142,8 @@ function AccordionOperators() { | |||
| <AccordionContent className="flex flex-col gap-4 text-balance"> | |||
| <OperatorItemList | |||
| operators={[Operator.Message, Operator.UserFillUp]} | |||
| isCustomDropdown={isCustomDropdown} | |||
| mousePosition={mousePosition} | |||
| ></OperatorItemList> | |||
| </AccordionContent> | |||
| </AccordionItem> | |||
| @@ -96,6 +156,8 @@ function AccordionOperators() { | |||
| Operator.Iteration, | |||
| Operator.Categorize, | |||
| ]} | |||
| isCustomDropdown={isCustomDropdown} | |||
| mousePosition={mousePosition} | |||
| ></OperatorItemList> | |||
| </AccordionContent> | |||
| </AccordionItem> | |||
| @@ -106,6 +168,8 @@ function AccordionOperators() { | |||
| <AccordionContent className="flex flex-col gap-4 text-balance"> | |||
| <OperatorItemList | |||
| operators={[Operator.Code, Operator.StringTransform]} | |||
| isCustomDropdown={isCustomDropdown} | |||
| mousePosition={mousePosition} | |||
| ></OperatorItemList> | |||
| </AccordionContent> | |||
| </AccordionItem> | |||
| @@ -129,6 +193,8 @@ function AccordionOperators() { | |||
| Operator.Invoke, | |||
| Operator.WenCai, | |||
| ]} | |||
| isCustomDropdown={isCustomDropdown} | |||
| mousePosition={mousePosition} | |||
| ></OperatorItemList> | |||
| </AccordionContent> | |||
| </AccordionItem> | |||
| @@ -139,9 +205,69 @@ function AccordionOperators() { | |||
| export function InnerNextStepDropdown({ | |||
| children, | |||
| hideModal, | |||
| }: PropsWithChildren & IModalProps<any>) { | |||
| position, | |||
| onNodeCreated, | |||
| }: PropsWithChildren & | |||
| IModalProps<any> & { | |||
| position?: { x: number; y: number }; | |||
| onNodeCreated?: (newNodeId: string) => void; | |||
| }) { | |||
| const dropdownRef = useRef<HTMLDivElement>(null); | |||
| useEffect(() => { | |||
| if (position && hideModal) { | |||
| const handleKeyDown = (event: KeyboardEvent) => { | |||
| if (event.key === 'Escape') { | |||
| hideModal(); | |||
| } | |||
| }; | |||
| document.addEventListener('keydown', handleKeyDown); | |||
| return () => { | |||
| document.removeEventListener('keydown', handleKeyDown); | |||
| }; | |||
| } | |||
| }, [position, hideModal]); | |||
| if (position) { | |||
| return ( | |||
| <div | |||
| ref={dropdownRef} | |||
| style={{ | |||
| position: 'fixed', | |||
| left: position.x, | |||
| top: position.y + 10, | |||
| zIndex: 1000, | |||
| }} | |||
| onClick={(e) => e.stopPropagation()} | |||
| > | |||
| <div className="w-[300px] font-semibold bg-white border border-border rounded-md shadow-lg"> | |||
| <div className="px-3 py-2 border-b border-border"> | |||
| <div className="text-sm font-medium">Next Step</div> | |||
| </div> | |||
| <HideModalContext.Provider value={hideModal}> | |||
| <OnNodeCreatedContext.Provider value={onNodeCreated}> | |||
| <AccordionOperators | |||
| isCustomDropdown={true} | |||
| mousePosition={position} | |||
| ></AccordionOperators> | |||
| </OnNodeCreatedContext.Provider> | |||
| </HideModalContext.Provider> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| return ( | |||
| <DropdownMenu open onOpenChange={hideModal}> | |||
| <DropdownMenu | |||
| open={true} | |||
| onOpenChange={(open) => { | |||
| if (!open && hideModal) { | |||
| hideModal(); | |||
| } | |||
| }} | |||
| > | |||
| <DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger> | |||
| <DropdownMenuContent | |||
| onClick={(e) => e.stopPropagation()} | |||
| @@ -4,6 +4,7 @@ import { Handle, HandleProps } from '@xyflow/react'; | |||
| import { Plus } from 'lucide-react'; | |||
| import { useMemo } from 'react'; | |||
| import { HandleContext } from '../../context'; | |||
| import { useDropdownManager } from '../context'; | |||
| import { InnerNextStepDropdown } from './dropdown/next-step-dropdown'; | |||
| export function CommonHandle({ | |||
| @@ -13,12 +14,16 @@ export function CommonHandle({ | |||
| }: HandleProps & { nodeId: string }) { | |||
| const { visible, hideModal, showModal } = useSetModalState(); | |||
| const { canShowDropdown, setActiveDropdown, clearActiveDropdown } = | |||
| useDropdownManager(); | |||
| const value = useMemo( | |||
| () => ({ | |||
| nodeId, | |||
| id: props.id, | |||
| id: props.id || undefined, | |||
| type: props.type, | |||
| position: props.position, | |||
| isFromConnectionDrag: false, | |||
| }), | |||
| [nodeId, props.id, props.position, props.type], | |||
| ); | |||
| @@ -33,12 +38,23 @@ export function CommonHandle({ | |||
| )} | |||
| onClick={(e) => { | |||
| e.stopPropagation(); | |||
| if (!canShowDropdown()) { | |||
| return; | |||
| } | |||
| setActiveDropdown('handle'); | |||
| showModal(); | |||
| }} | |||
| > | |||
| <Plus className="size-3 pointer-events-none text-text-title-invert" /> | |||
| {visible && ( | |||
| <InnerNextStepDropdown hideModal={hideModal}> | |||
| <InnerNextStepDropdown | |||
| hideModal={() => { | |||
| hideModal(); | |||
| clearActiveDropdown(); | |||
| }} | |||
| > | |||
| <span></span> | |||
| </InnerNextStepDropdown> | |||
| )} | |||
| @@ -42,6 +42,7 @@ export type HandleContextType = { | |||
| id?: string; | |||
| type: HandleType; | |||
| position: Position; | |||
| isFromConnectionDrag: boolean; | |||
| }; | |||
| export const HandleContext = createContext<HandleContextType>( | |||
| @@ -208,7 +208,7 @@ function useAddToolNode() { | |||
| ); | |||
| const addToolNode = useCallback( | |||
| (newNode: Node<any>, nodeId?: string) => { | |||
| (newNode: Node<any>, nodeId?: string): boolean => { | |||
| const agentNode = getNode(nodeId); | |||
| if (agentNode) { | |||
| @@ -222,7 +222,7 @@ function useAddToolNode() { | |||
| childToolNodeIds.length > 0 && | |||
| nodes.some((x) => x.id === childToolNodeIds[0]) | |||
| ) { | |||
| return; | |||
| return false; | |||
| } | |||
| newNode.position = { | |||
| @@ -239,7 +239,9 @@ function useAddToolNode() { | |||
| targetHandle: NodeHandleId.End, | |||
| }); | |||
| } | |||
| return true; | |||
| } | |||
| return false; | |||
| }, | |||
| [addEdge, addNode, edges, getNode, nodes], | |||
| ); | |||
| @@ -295,13 +297,17 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) { | |||
| const addCanvasNode = useCallback( | |||
| ( | |||
| type: string, | |||
| params: { nodeId?: string; position: Position; id?: string } = { | |||
| params: { | |||
| nodeId?: string; | |||
| position: Position; | |||
| id?: string; | |||
| isFromConnectionDrag?: boolean; | |||
| } = { | |||
| position: Position.Right, | |||
| }, | |||
| ) => | |||
| (event?: CanvasMouseEvent) => { | |||
| (event?: CanvasMouseEvent): string | undefined => { | |||
| const nodeId = params.nodeId; | |||
| const node = getNode(nodeId); | |||
| // reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition | |||
| @@ -312,7 +318,11 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) { | |||
| y: event?.clientY || 0, | |||
| }); | |||
| if (params.position === Position.Right && type !== Operator.Note) { | |||
| if ( | |||
| params.position === Position.Right && | |||
| type !== Operator.Note && | |||
| !params.isFromConnectionDrag | |||
| ) { | |||
| position = calculateNewlyBackChildPosition(nodeId, params.id); | |||
| } | |||
| @@ -371,6 +381,7 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) { | |||
| targetHandle: NodeHandleId.End, | |||
| }); | |||
| } | |||
| return newNode.id; | |||
| } else if ( | |||
| type === Operator.Agent && | |||
| params.position === Position.Bottom | |||
| @@ -406,8 +417,10 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) { | |||
| targetHandle: NodeHandleId.AgentTop, | |||
| }); | |||
| } | |||
| return newNode.id; | |||
| } else if (type === Operator.Tool) { | |||
| addToolNode(newNode, params.nodeId); | |||
| const toolNodeAdded = addToolNode(newNode, params.nodeId); | |||
| return toolNodeAdded ? newNode.id : undefined; | |||
| } else { | |||
| addNode(newNode); | |||
| addChildEdge(params.position, { | |||
| @@ -416,6 +429,8 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) { | |||
| sourceHandle: params.id, | |||
| }); | |||
| } | |||
| return newNode.id; | |||
| }, | |||
| [ | |||
| addChildEdge, | |||
| @@ -34,6 +34,7 @@ import { ComponentPropsWithoutRef, useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { useParams } from 'umi'; | |||
| import AgentCanvas from './canvas'; | |||
| import { DropdownProvider } from './canvas/context'; | |||
| import EmbedDialog from './embed-dialog'; | |||
| import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; | |||
| import { useFetchDataOnMount } from './hooks/use-fetch-data'; | |||
| @@ -185,10 +186,12 @@ export default function Agent() { | |||
| </div> | |||
| </PageHeader> | |||
| <ReactFlowProvider> | |||
| <AgentCanvas | |||
| drawerVisible={chatDrawerVisible} | |||
| hideDrawer={hideChatDrawer} | |||
| ></AgentCanvas> | |||
| <DropdownProvider> | |||
| <AgentCanvas | |||
| drawerVisible={chatDrawerVisible} | |||
| hideDrawer={hideChatDrawer} | |||
| ></AgentCanvas> | |||
| </DropdownProvider> | |||
| </ReactFlowProvider> | |||
| {fileUploadVisible && ( | |||
| <UploadAgentDialog | |||