mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 21:13:50 +00:00
feat: add context menu & comment node (#2834)
* feat: add comment node * useMemo
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
|
||||
import { CommentNode } from '@fastgpt/global/core/workflow/template/system/comment';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import { useReactFlow } from 'reactflow';
|
||||
|
||||
type ContextMenuProps = {
|
||||
top: number;
|
||||
left: number;
|
||||
};
|
||||
|
||||
const ContextMenu = ({ top, left }: ContextMenuProps) => {
|
||||
const { t } = useTranslation();
|
||||
const setNodes = useContextSelector(WorkflowContext, (ctx) => ctx.setNodes);
|
||||
const setMenu = useContextSelector(WorkflowContext, (ctx) => ctx.setMenu);
|
||||
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
const newNode = nodeTemplate2FlowNode({
|
||||
template: CommentNode,
|
||||
position: screenToFlowPosition({ x: left, y: top }),
|
||||
t
|
||||
});
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<Box
|
||||
position="absolute"
|
||||
top={`${top - 6}px`}
|
||||
left={`${left + 10}px`}
|
||||
width={0}
|
||||
height={0}
|
||||
borderLeft="6px solid transparent"
|
||||
borderRight="6px solid transparent"
|
||||
borderBottom="6px solid white"
|
||||
zIndex={2}
|
||||
filter="drop-shadow(0px -1px 2px rgba(0, 0, 0, 0.1))"
|
||||
/>
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
top={top}
|
||||
left={left}
|
||||
bg={'white'}
|
||||
w={'120px'}
|
||||
height={9}
|
||||
p={1}
|
||||
rounded={'md'}
|
||||
boxShadow={'0px 2px 4px 0px #A1A7B340'}
|
||||
className="context-menu"
|
||||
alignItems={'center'}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'primary.500'
|
||||
}}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
setNodes((state) => {
|
||||
const newState = state
|
||||
.map((node) => ({
|
||||
...node,
|
||||
selected: false
|
||||
}))
|
||||
// @ts-ignore
|
||||
.concat(newNode);
|
||||
return newState;
|
||||
});
|
||||
}}
|
||||
zIndex={1}
|
||||
>
|
||||
<MyIcon name="comment" w={'16px'} h={'16px'} ml={1} />
|
||||
<Box fontSize={'12px'} fontWeight={'500'} ml={1.5}>
|
||||
{t('workflow:context_menu.add_comment')}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ContextMenu);
|
@@ -282,7 +282,8 @@ export const useWorkflow = () => {
|
||||
setEdges,
|
||||
onChangeNode,
|
||||
onEdgesChange,
|
||||
setHoverEdgeId
|
||||
setHoverEdgeId,
|
||||
setMenu
|
||||
} = useContextSelector(WorkflowContext, (v) => v);
|
||||
|
||||
const { getIntersectingNodes } = useReactFlow();
|
||||
@@ -599,7 +600,7 @@ export const useWorkflow = () => {
|
||||
connect
|
||||
});
|
||||
},
|
||||
[onConnect, t, toast, nodes]
|
||||
[onConnect, t, toast]
|
||||
);
|
||||
|
||||
/* edge */
|
||||
@@ -613,6 +614,24 @@ export const useWorkflow = () => {
|
||||
setHoverEdgeId(undefined);
|
||||
}, [setHoverEdgeId]);
|
||||
|
||||
// context menu
|
||||
const onPaneContextMenu = useCallback(
|
||||
(e: any) => {
|
||||
// Prevent native context menu from showing
|
||||
e.preventDefault();
|
||||
|
||||
setMenu({
|
||||
top: e.clientY - 64,
|
||||
left: e.clientX - 12
|
||||
});
|
||||
},
|
||||
[setMenu]
|
||||
);
|
||||
|
||||
const onPaneClick = useCallback(() => {
|
||||
setMenu(null);
|
||||
}, [setMenu]);
|
||||
|
||||
return {
|
||||
handleNodesChange,
|
||||
handleEdgeChange,
|
||||
@@ -624,7 +643,9 @@ export const useWorkflow = () => {
|
||||
onEdgeMouseLeave,
|
||||
helperLineHorizontal,
|
||||
helperLineVertical,
|
||||
onNodeDragStop
|
||||
onNodeDragStop,
|
||||
onPaneContextMenu,
|
||||
onPaneClick
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -17,6 +17,7 @@ import { WorkflowContext } from '../context';
|
||||
import { useWorkflow } from './hooks/useWorkflow';
|
||||
import HelperLines from './components/HelperLines';
|
||||
import FlowController from './components/FlowController';
|
||||
import ContextMenu from './components/ContextMenu';
|
||||
|
||||
export const minZoom = 0.1;
|
||||
export const maxZoom = 1.5;
|
||||
@@ -57,14 +58,15 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = {
|
||||
[FlowNodeTypeEnum.loop]: dynamic(() => import('./nodes/Loop/NodeLoop')),
|
||||
[FlowNodeTypeEnum.loopStart]: dynamic(() => import('./nodes/Loop/NodeLoopStart')),
|
||||
[FlowNodeTypeEnum.loopEnd]: dynamic(() => import('./nodes/Loop/NodeLoopEnd')),
|
||||
[FlowNodeTypeEnum.formInput]: dynamic(() => import('./nodes/NodeFormInput'))
|
||||
[FlowNodeTypeEnum.formInput]: dynamic(() => import('./nodes/NodeFormInput')),
|
||||
[FlowNodeTypeEnum.comment]: dynamic(() => import('./nodes/NodeComment'))
|
||||
};
|
||||
const edgeTypes = {
|
||||
[EDGE_TYPE]: ButtonEdge
|
||||
};
|
||||
|
||||
const Workflow = () => {
|
||||
const { nodes, edges, reactFlowWrapper, workflowControlMode } = useContextSelector(
|
||||
const { nodes, edges, menu, reactFlowWrapper, workflowControlMode } = useContextSelector(
|
||||
WorkflowContext,
|
||||
(v) => v
|
||||
);
|
||||
@@ -79,7 +81,9 @@ const Workflow = () => {
|
||||
onEdgeMouseLeave,
|
||||
helperLineHorizontal,
|
||||
helperLineVertical,
|
||||
onNodeDragStop
|
||||
onNodeDragStop,
|
||||
onPaneContextMenu,
|
||||
onPaneClick
|
||||
} = useWorkflow();
|
||||
|
||||
const {
|
||||
@@ -121,6 +125,7 @@ const Workflow = () => {
|
||||
<NodeTemplatesModal isOpen={isOpenTemplate} onClose={onCloseTemplate} />
|
||||
</>
|
||||
|
||||
{menu && <ContextMenu {...menu} />}
|
||||
<ReactFlow
|
||||
ref={reactFlowWrapper}
|
||||
fitView
|
||||
@@ -142,6 +147,8 @@ const Workflow = () => {
|
||||
onEdgeMouseEnter={onEdgeMouseEnter}
|
||||
onEdgeMouseLeave={onEdgeMouseLeave}
|
||||
panOnScrollSpeed={2}
|
||||
onPaneContextMenu={onPaneContextMenu}
|
||||
onPaneClick={onPaneClick}
|
||||
{...(workflowControlMode === 'select'
|
||||
? {
|
||||
selectionMode: SelectionMode.Full,
|
||||
|
@@ -0,0 +1,128 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { Box, Textarea } from '@chakra-ui/react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const NodeComment = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { nodeId, inputs } = data;
|
||||
const { commentText, commentSize } = useMemo(
|
||||
() => ({
|
||||
commentText: inputs.find((item) => item.key === NodeInputKeyEnum.commentText),
|
||||
commentSize: inputs.find((item) => item.key === NodeInputKeyEnum.commentSize)
|
||||
}),
|
||||
[inputs]
|
||||
);
|
||||
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (ctx) => ctx.onChangeNode);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [size, setSize] = useState<{
|
||||
width: number;
|
||||
height: number;
|
||||
}>(commentSize?.value);
|
||||
|
||||
const initialY = useRef(0);
|
||||
const initialX = useRef(0);
|
||||
|
||||
const handleMouseDown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
initialY.current = e.clientY;
|
||||
initialX.current = e.clientX;
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
const deltaY = e.clientY - initialY.current;
|
||||
const deltaX = e.clientX - initialX.current;
|
||||
setSize((prevSize) => ({
|
||||
width: prevSize.width + deltaX < 240 ? 240 : prevSize.width + deltaX,
|
||||
height: prevSize.height + deltaY < 140 ? 140 : prevSize.height + deltaY
|
||||
}));
|
||||
initialY.current = e.clientY;
|
||||
initialX.current = e.clientX;
|
||||
commentSize &&
|
||||
onChangeNode({
|
||||
nodeId: nodeId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.commentSize,
|
||||
value: {
|
||||
...commentSize,
|
||||
value: {
|
||||
width: size.width + deltaX,
|
||||
height: size.height + deltaY
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
},
|
||||
[commentSize, nodeId, onChangeNode, size.height, size.width]
|
||||
);
|
||||
|
||||
return (
|
||||
<NodeCard
|
||||
selected={false}
|
||||
{...data}
|
||||
minW={`${size.width}px`}
|
||||
minH={`${size.height}px`}
|
||||
menuForbid={{
|
||||
debug: true
|
||||
}}
|
||||
border={'none'}
|
||||
rounded={'none'}
|
||||
bg={'#D8E9FF'}
|
||||
boxShadow={
|
||||
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)'
|
||||
}
|
||||
>
|
||||
<Box w={'full'} h={'full'} position={'relative'}>
|
||||
<Box
|
||||
position={'absolute'}
|
||||
right={'0'}
|
||||
bottom={'-2'}
|
||||
zIndex={9}
|
||||
cursor={'nwse-resize'}
|
||||
px={'2px'}
|
||||
className="nodrag"
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
<MyIcon name={'common/editor/resizer'} width={'14px'} height={'14px'} />
|
||||
</Box>
|
||||
<Textarea
|
||||
value={commentText?.value}
|
||||
border={'none'}
|
||||
rounded={'none'}
|
||||
minH={`${size.height}px`}
|
||||
minW={`${size.width}px`}
|
||||
resize={'none'}
|
||||
placeholder={t('workflow:enter_comment')}
|
||||
onChange={(e) => {
|
||||
commentText &&
|
||||
onChangeNode({
|
||||
nodeId: nodeId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.commentText,
|
||||
value: {
|
||||
...commentText,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodeComment);
|
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, Button, Card, Flex, Image } from '@chakra-ui/react';
|
||||
import { Box, Button, Card, Flex, FlexProps, Image } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
@@ -39,7 +39,7 @@ type Props = FlowNodeItemType & {
|
||||
copy?: boolean;
|
||||
delete?: boolean;
|
||||
};
|
||||
};
|
||||
} & Omit<FlexProps, 'children'>;
|
||||
|
||||
const NodeCard = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -62,7 +62,8 @@ const NodeCard = (props: Props) => {
|
||||
isTool = false,
|
||||
isError = false,
|
||||
debugResult,
|
||||
isFolded
|
||||
isFolded,
|
||||
...customStyle
|
||||
} = props;
|
||||
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
@@ -156,120 +157,136 @@ const NodeCard = (props: Props) => {
|
||||
|
||||
/* Node header */
|
||||
const Header = useMemo(() => {
|
||||
const showHeader = node?.flowNodeType !== FlowNodeTypeEnum.comment;
|
||||
|
||||
return (
|
||||
<Box position={'relative'}>
|
||||
{/* debug */}
|
||||
<Box px={4} py={3}>
|
||||
{/* tool target handle */}
|
||||
<ToolTargetHandle show={showToolHandle} nodeId={nodeId} />
|
||||
{showHeader && (
|
||||
<Box px={4} py={3}>
|
||||
{/* tool target handle */}
|
||||
<ToolTargetHandle show={showToolHandle} nodeId={nodeId} />
|
||||
|
||||
{/* avatar and name */}
|
||||
<Flex alignItems={'center'}>
|
||||
{node?.flowNodeType !== FlowNodeTypeEnum.stopTool && (
|
||||
<Box
|
||||
mr={2}
|
||||
cursor={'pointer'}
|
||||
rounded={'sm'}
|
||||
_hover={{ bg: 'myGray.200' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'attr',
|
||||
key: 'isFolded',
|
||||
value: !isFolded
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon
|
||||
name={!isFolded ? 'core/chat/chevronDown' : 'core/chat/chevronRight'}
|
||||
w={'24px'}
|
||||
h={'24px'}
|
||||
color={'myGray.500'}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Avatar src={avatar} borderRadius={'sm'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||
<Box ml={3} fontSize={'md'} fontWeight={'medium'}>
|
||||
{t(name as any)}
|
||||
</Box>
|
||||
<MyIcon
|
||||
className="controller-rename"
|
||||
display={'none'}
|
||||
name={'edit'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={1}
|
||||
color={'myGray.500'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => {
|
||||
onOpenCustomTitleModal({
|
||||
defaultVal: name,
|
||||
onSuccess: (e) => {
|
||||
if (!e) {
|
||||
return toast({
|
||||
title: t('app:modules.Title is required'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
{/* avatar and name */}
|
||||
<Flex alignItems={'center'}>
|
||||
{node?.flowNodeType !== FlowNodeTypeEnum.stopTool && (
|
||||
<Box
|
||||
mr={2}
|
||||
cursor={'pointer'}
|
||||
rounded={'sm'}
|
||||
_hover={{ bg: 'myGray.200' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'attr',
|
||||
key: 'name',
|
||||
value: e
|
||||
key: 'isFolded',
|
||||
value: !isFolded
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box flex={1} />
|
||||
{hasNewVersion && (
|
||||
<MyTooltip label={t('app:app.modules.click to update')}>
|
||||
<Button
|
||||
bg={'yellow.50'}
|
||||
color={'yellow.600'}
|
||||
variant={'ghost'}
|
||||
h={8}
|
||||
px={2}
|
||||
rounded={'6px'}
|
||||
fontSize={'xs'}
|
||||
fontWeight={'medium'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'yellow.100' }}
|
||||
onClick={onOpenConfirmSync(onClickSyncVersion)}
|
||||
}}
|
||||
>
|
||||
<Box>{t('app:app.modules.has new version')}</Box>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{!!nodeTemplate?.diagram && !hasNewVersion && (
|
||||
<MyTooltip
|
||||
label={
|
||||
<Image src={nodeTemplate?.diagram} w={'100%'} minH={['auto', '200px']} alt={''} />
|
||||
}
|
||||
>
|
||||
<Box
|
||||
fontSize={'sm'}
|
||||
color={'primary.700'}
|
||||
p={1}
|
||||
rounded={'sm'}
|
||||
cursor={'default'}
|
||||
_hover={{ bg: 'rgba(17, 24, 36, 0.05)' }}
|
||||
>
|
||||
{t('common:core.module.Diagram')}
|
||||
<MyIcon
|
||||
name={!isFolded ? 'core/chat/chevronDown' : 'core/chat/chevronRight'}
|
||||
w={'24px'}
|
||||
h={'24px'}
|
||||
color={'myGray.500'}
|
||||
/>
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
<MenuRender nodeId={nodeId} menuForbid={menuForbid} nodeList={nodeList} />
|
||||
<NodeIntro nodeId={nodeId} intro={intro} />
|
||||
</Box>
|
||||
)}
|
||||
<Avatar
|
||||
src={avatar}
|
||||
borderRadius={'sm'}
|
||||
objectFit={'contain'}
|
||||
w={'30px'}
|
||||
h={'30px'}
|
||||
/>
|
||||
<Box ml={3} fontSize={'md'} fontWeight={'medium'}>
|
||||
{t(name as any)}
|
||||
</Box>
|
||||
<MyIcon
|
||||
className="controller-rename"
|
||||
display={'none'}
|
||||
name={'edit'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={1}
|
||||
color={'myGray.500'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => {
|
||||
onOpenCustomTitleModal({
|
||||
defaultVal: name,
|
||||
onSuccess: (e) => {
|
||||
if (!e) {
|
||||
return toast({
|
||||
title: t('app:modules.Title is required'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'attr',
|
||||
key: 'name',
|
||||
value: e
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box flex={1} />
|
||||
{hasNewVersion && (
|
||||
<MyTooltip label={t('app:app.modules.click to update')}>
|
||||
<Button
|
||||
bg={'yellow.50'}
|
||||
color={'yellow.600'}
|
||||
variant={'ghost'}
|
||||
h={8}
|
||||
px={2}
|
||||
rounded={'6px'}
|
||||
fontSize={'xs'}
|
||||
fontWeight={'medium'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'yellow.100' }}
|
||||
onClick={onOpenConfirmSync(onClickSyncVersion)}
|
||||
>
|
||||
<Box>{t('app:app.modules.has new version')}</Box>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{!!nodeTemplate?.diagram && !hasNewVersion && (
|
||||
<MyTooltip
|
||||
label={
|
||||
<Image
|
||||
src={nodeTemplate?.diagram}
|
||||
w={'100%'}
|
||||
minH={['auto', '200px']}
|
||||
alt={''}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
fontSize={'sm'}
|
||||
color={'primary.700'}
|
||||
p={1}
|
||||
rounded={'sm'}
|
||||
cursor={'default'}
|
||||
_hover={{ bg: 'rgba(17, 24, 36, 0.05)' }}
|
||||
>
|
||||
{t('common:core.module.Diagram')}
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
<NodeIntro nodeId={nodeId} intro={intro} />
|
||||
</Box>
|
||||
)}
|
||||
<MenuRender nodeId={nodeId} menuForbid={menuForbid} nodeList={nodeList} />
|
||||
<ConfirmSyncModal />
|
||||
</Box>
|
||||
);
|
||||
}, [
|
||||
node?.flowNodeType,
|
||||
showToolHandle,
|
||||
nodeId,
|
||||
node?.flowNodeType,
|
||||
isFolded,
|
||||
avatar,
|
||||
t,
|
||||
@@ -278,9 +295,10 @@ const NodeCard = (props: Props) => {
|
||||
onOpenConfirmSync,
|
||||
onClickSyncVersion,
|
||||
nodeTemplate?.diagram,
|
||||
intro,
|
||||
menuForbid,
|
||||
nodeList,
|
||||
intro,
|
||||
ConfirmSyncModal,
|
||||
onChangeNode,
|
||||
onOpenCustomTitleModal,
|
||||
toast
|
||||
@@ -334,6 +352,7 @@ const NodeCard = (props: Props) => {
|
||||
: {
|
||||
borderColor: selected ? 'primary.600' : 'borderColor.base'
|
||||
})}
|
||||
{...customStyle}
|
||||
>
|
||||
<NodeDebugResponse nodeId={nodeId} debugResult={debugResult} />
|
||||
{Header}
|
||||
|
@@ -174,6 +174,11 @@ type WorkflowContextType = {
|
||||
//
|
||||
workflowControlMode?: 'drag' | 'select';
|
||||
setWorkflowControlMode: (value?: SetState<'drag' | 'select'> | undefined) => void;
|
||||
menu: {
|
||||
top: number;
|
||||
left: number;
|
||||
} | null;
|
||||
setMenu: (value: React.SetStateAction<{ top: number; left: number } | null>) => void;
|
||||
};
|
||||
|
||||
type DebugDataType = {
|
||||
@@ -313,6 +318,10 @@ export const WorkflowContext = createContext<WorkflowContextType>({
|
||||
},
|
||||
onSwitchCloudVersion: function (appVersion: AppVersionSchemaType): boolean {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
menu: null,
|
||||
setMenu: function (value: React.SetStateAction<{ top: number; left: number } | null>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -973,6 +982,8 @@ const WorkflowContextProvider = ({
|
||||
};
|
||||
}, [edges, nodes]);
|
||||
|
||||
const [menu, setMenu] = useState<{ top: number; left: number } | null>(null);
|
||||
|
||||
const value = {
|
||||
appId,
|
||||
reactFlowWrapper,
|
||||
@@ -1032,7 +1043,10 @@ const WorkflowContextProvider = ({
|
||||
setShowHistoryModal,
|
||||
|
||||
// chat test
|
||||
setWorkflowTestData
|
||||
setWorkflowTestData,
|
||||
|
||||
menu,
|
||||
setMenu
|
||||
};
|
||||
|
||||
return (
|
||||
|
Reference in New Issue
Block a user