import { useIsDarkTheme, useTheme } from '@/components/theme-provider'; import { Tooltip, TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip'; import { useSetModalState } from '@/hooks/common-hooks'; import { cn } from '@/lib/utils'; import { Connection, ConnectionMode, ControlButton, Controls, NodeTypes, OnConnectEnd, Position, ReactFlow, ReactFlowInstance, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import { NotebookPen } from 'lucide-react'; import { memo, useCallback, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { AgentInstanceContext, HandleContext } from '../context'; import FormSheet from '../form-sheet/next'; import { useSelectCanvasData, useValidateConnection } from '../hooks'; import { useAddNode } from '../hooks/use-add-node'; import { useBeforeDelete } from '../hooks/use-before-delete'; import { useMoveNote } from '../hooks/use-move-note'; import { useDropdownManager } from './context'; import { AgentBackground } from '@/components/canvas/background'; import Spotlight from '@/components/spotlight'; import { useRunDataflow } from '../hooks/use-run-dataflow'; import { useHideFormSheetOnNodeDeletion, useShowDrawer, } from '../hooks/use-show-drawer'; import RunSheet from '../run-sheet'; import useGraphStore from '../store'; import { ButtonEdge } from './edge'; import styles from './index.less'; import { RagNode } from './node'; import { BeginNode } from './node/begin-node'; import { NextStepDropdown } from './node/dropdown/next-step-dropdown'; import { ExtractorNode } from './node/extractor-node'; import NoteNode from './node/note-node'; import ParserNode from './node/parser-node'; import { SplitterNode } from './node/splitter-node'; import TokenizerNode from './node/tokenizer-node'; export const nodeTypes: NodeTypes = { ragNode: RagNode, beginNode: BeginNode, noteNode: NoteNode, parserNode: ParserNode, tokenizerNode: TokenizerNode, splitterNode: SplitterNode, contextNode: ExtractorNode, }; const edgeTypes = { buttonEdge: ButtonEdge, }; interface IProps { drawerVisible: boolean; hideDrawer(): void; showLogSheet(): void; } function DataFlowCanvas({ drawerVisible, hideDrawer, showLogSheet }: IProps) { const { t } = useTranslation(); const { nodes, edges, onConnect: originalOnConnect, onEdgesChange, onNodesChange, onSelectionChange, onEdgeMouseEnter, onEdgeMouseLeave, } = useSelectCanvasData(); const isValidConnection = useValidateConnection(); const [reactFlowInstance, setReactFlowInstance] = useState>(); const { onNodeClick, clickedNode, formDrawerVisible, hideFormDrawer, singleDebugDrawerVisible, hideSingleDebugDrawer, showSingleDebugDrawer, chatVisible, runVisible, hideRunOrChatDrawer, showFormDrawer, } = useShowDrawer({ drawerVisible, hideDrawer, }); const { handleBeforeDelete } = useBeforeDelete(); const { addCanvasNode, addNoteNode } = useAddNode(reactFlowInstance); const { ref, showImage, hideImage, imgVisible, mouse } = useMoveNote(); const { theme } = useTheme(); const isDarkTheme = useIsDarkTheme(); 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 { hasChildNode } = useGraphStore((state) => state); const onPaneClick = useCallback(() => { hideFormDrawer(); if (visible && !preventCloseRef.current) { hideModal(); clearActiveDropdown(); } if (imgVisible) { addNoteNode(mouse); hideImage(); } }, [ hideFormDrawer, visible, hideModal, imgVisible, addNoteNode, mouse, hideImage, clearActiveDropdown, ]); const { run, loading: running } = useRunDataflow( showLogSheet!, hideRunOrChatDrawer, ); 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: OnConnectEnd = (event, connectionState) => { const target = event.target as HTMLElement; const nodeId = connectionState.fromNode?.id; // Events triggered by Handle are directly interrupted if ( target?.classList.contains('react-flow__handle') || (nodeId && hasChildNode(nodeId)) ) { return; } 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 (
{t('flow.note')} {visible && ( { hideModal(); clearActiveDropdown(); }} position={dropdownPosition} nodeId={connectionStartRef.current?.nodeId || ''} > )} {formDrawerVisible && ( )} {runVisible && ( )}
); } export default memo(DataFlowCanvas);