diff --git a/packages/global/core/workflow/type/node.d.ts b/packages/global/core/workflow/type/node.d.ts
index a5f121418..239408f12 100644
--- a/packages/global/core/workflow/type/node.d.ts
+++ b/packages/global/core/workflow/type/node.d.ts
@@ -104,6 +104,7 @@ export type FlowNodeItemType = FlowNodeTemplateType & {
response?: ChatHistoryItemResType;
isExpired?: boolean;
};
+ isFolded?: boolean;
};
// store node type
diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts
index abcc7400d..0696b6b7f 100644
--- a/packages/web/components/common/Icon/constants.ts
+++ b/packages/web/components/common/Icon/constants.ts
@@ -123,6 +123,7 @@ export const iconPaths = {
'core/chat/chatLight': () => import('./icons/core/chat/chatLight.svg'),
'core/chat/chatModelTag': () => import('./icons/core/chat/chatModelTag.svg'),
'core/chat/chevronDown': () => import('./icons/core/chat/chevronDown.svg'),
+ 'core/chat/chevronRight': () => import('./icons/core/chat/chevronRight.svg'),
'core/chat/chevronSelector': () => import('./icons/core/chat/chevronSelector.svg'),
'core/chat/chevronUp': () => import('./icons/core/chat/chevronUp.svg'),
'core/chat/export/pdf': () => import('./icons/core/chat/export/pdf.svg'),
diff --git a/packages/web/components/common/Icon/icons/core/chat/chevronRight.svg b/packages/web/components/common/Icon/icons/core/chat/chevronRight.svg
new file mode 100644
index 000000000..b6724706e
--- /dev/null
+++ b/packages/web/components/common/Icon/icons/core/chat/chevronRight.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx
index 0f0e956f0..bf4032dbd 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx
@@ -1,6 +1,6 @@
import React, { useCallback, useMemo } from 'react';
import { BezierEdge, getBezierPath, EdgeLabelRenderer, EdgeProps } from 'reactflow';
-import { Flex } from '@chakra-ui/react';
+import { Box, Flex } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
@@ -27,6 +27,16 @@ const ButtonEdge = (props: EdgeProps) => {
targetHandleId,
style
} = props;
+
+ const parentNode = useMemo(() => {
+ for (const node of nodeList) {
+ if ((node.nodeId === source || node.nodeId === target) && node.parentNodeId) {
+ return nodeList.find((parent) => parent.nodeId === node.parentNodeId);
+ }
+ }
+ return undefined;
+ }, [nodeList, source, target]);
+
const defaultZIndex = useMemo(
() => (nodeList.find((node) => node.nodeId === source && node.parentNodeId) ? 1001 : 0),
[nodeList, source]
@@ -127,55 +137,59 @@ const ButtonEdge = (props: EdgeProps) => {
})();
return (
- onDelConnect(id)}
- >
-
-
- {!isToolEdge && (
+
onDelConnect(id)}
>
-
+
- )}
+ {!isToolEdge && (
+
+
+
+ )}
+
);
}, [
+ parentNode?.isFolded,
isHover,
highlightEdge,
labelX,
labelY,
isToolEdge,
+ defaultZIndex,
edgeColor,
targetPosition,
newTargetX,
@@ -214,7 +228,8 @@ const ButtonEdge = (props: EdgeProps) => {
targetY={newTargetY}
style={{
...edgeStyle,
- stroke: edgeColor
+ stroke: edgeColor,
+ display: parentNode?.isFolded ? 'none' : 'block'
}}
/>
);
@@ -227,7 +242,8 @@ const ButtonEdge = (props: EdgeProps) => {
source,
target,
style,
- highlightEdge
+ highlightEdge,
+ parentNode?.isFolded
]);
return (
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx
index 3685d99a7..d46f9dd3f 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx
@@ -1,5 +1,13 @@
-import React, { useMemo } from 'react';
-import { Background, ControlButton, MiniMap, Panel, useReactFlow, useViewport } from 'reactflow';
+import React, { useCallback, useMemo } from 'react';
+import {
+ Background,
+ ControlButton,
+ MiniMap,
+ MiniMapNodeProps,
+ Panel,
+ useReactFlow,
+ useViewport
+} from 'reactflow';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
@@ -26,7 +34,8 @@ const FlowController = React.memo(function FlowController() {
canUndo,
workflowControlMode,
setWorkflowControlMode,
- mouseInCanvas
+ mouseInCanvas,
+ nodeList
} = useContextSelector(WorkflowContext, (v) => v);
const { t } = useTranslation();
@@ -52,6 +61,20 @@ const FlowController = React.memo(function FlowController() {
zoomOut();
});
+ const MiniMapNode = useCallback(
+ ({ x, y, width, height, color, id }: MiniMapNodeProps) => {
+ const node = nodeList.find((node) => node.nodeId === id);
+ const parentNode = nodeList.find((n) => n.nodeId === node?.parentNodeId);
+
+ if (parentNode?.isFolded) {
+ return null;
+ }
+
+ return ;
+ },
+ [nodeList]
+ );
+
const Render = useMemo(() => {
return (
<>
@@ -64,6 +87,7 @@ const FlowController = React.memo(function FlowController() {
boxShadow: '0px 0px 1px rgba(19, 51, 107, 0.10), 0px 4px 10px rgba(19, 51, 107, 0.10)'
}}
pannable
+ nodeComponent={MiniMapNode}
/>
);
}, [
+ MiniMapNode,
workflowControlMode,
- isMac,
t,
+ isMac,
undo,
canUndo,
redo,
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx
index cc77eea98..69818409c 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx
@@ -13,7 +13,8 @@ import {
getNodesBounds,
Rect,
NodeRemoveChange,
- NodeSelectionChange
+ NodeSelectionChange,
+ Position
} from 'reactflow';
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import 'reactflow/dist/style.css';
@@ -30,6 +31,8 @@ import {
Input_Template_Node_Width
} from '@fastgpt/global/core/workflow/template/input';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
+import { getHandleId } from '@fastgpt/global/core/workflow/utils';
+import { IfElseResultEnum } from '@fastgpt/global/core/workflow/template/system/ifElse/constant';
/*
Compute helper lines for snapping nodes to each other
@@ -367,8 +370,8 @@ export const useWorkflow = () => {
const checkNodeOverLoopNode = useMemoizedFn((node: Node) => {
if (!node) return;
- // 获取所有与当前节点相交的节点
- const intersections = getIntersectingNodes(node);
+ // 获取所有与当前节点相交的节点,不包含折叠的节点
+ const intersections = getIntersectingNodes(node).filter((node) => !node.data.isFolded);
// 获取所有与当前节点相交的节点中,类型为 loop 的节点
const parentNode = intersections.find((item) => item.type === FlowNodeTypeEnum.loop);
@@ -544,9 +547,19 @@ export const useWorkflow = () => {
/* connect */
const onConnectStart = useCallback(
(event: any, params: OnConnectStartParams) => {
+ if (!params.nodeId) return;
+ const sourceNode = nodes.find((node) => node.id === params.nodeId);
+ if (sourceNode?.data.isFolded) {
+ return onChangeNode({
+ nodeId: params.nodeId,
+ type: 'attr',
+ key: 'isFolded',
+ value: false
+ });
+ }
setConnectingEdge(params);
},
- [setConnectingEdge]
+ [nodes, setConnectingEdge, onChangeNode]
);
const onConnectEnd = useCallback(() => {
setConnectingEdge(undefined);
@@ -581,7 +594,7 @@ export const useWorkflow = () => {
connect
});
},
- [onConnect, t, toast]
+ [onConnect, t, toast, nodes]
);
/* edge */
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx
index 17503589e..3c70c8a68 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx
@@ -22,7 +22,7 @@ import { WorkflowContext } from '../../../context';
const NodeLoop = ({ data, selected }: NodeProps) => {
const { t } = useTranslation();
- const { nodeId, inputs, outputs } = data;
+ const { nodeId, inputs, outputs, isFolded } = data;
const { onChangeNode, nodeList } = useContextSelector(WorkflowContext, (v) => v);
const { nodeWidth, nodeHeight } = useMemo(() => {
@@ -53,14 +53,14 @@ const NodeLoop = ({ data, selected }: NodeProps) => {
return (
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoopStart.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoopStart.tsx
index 4d39ccf8f..4c4d4f564 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoopStart.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoopStart.tsx
@@ -94,17 +94,15 @@ const NodeLoopStart = ({ data, selected }: NodeProps) => {
-
+
{!loopItemInputType ? (
-
+
) : (
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx
index 750d9211a..7004cdf92 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx
@@ -56,11 +56,6 @@ const NodeUserSelect = ({ data, selected }: NodeProps) => {
value: options.filter((input) => input.key !== item.key)
}
});
- onChangeNode({
- nodeId,
- type: 'delOutput',
- key: item.key
- });
}}
/>
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx
index f98720bf7..0b53e7f63 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx
@@ -6,7 +6,13 @@ import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../../context';
-export const ConnectionSourceHandle = ({ nodeId }: { nodeId: string }) => {
+export const ConnectionSourceHandle = ({
+ nodeId,
+ isFoldNode
+}: {
+ nodeId: string;
+ isFoldNode?: boolean;
+}) => {
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
@@ -26,8 +32,7 @@ export const ConnectionSourceHandle = ({ nodeId }: { nodeId: string }) => {
const rightTargetConnected = edges.some(
(edge) => edge.targetHandle === getHandleId(nodeId, 'target', Position.Right)
);
-
- if (!node || !node?.sourceHandle?.right || rightTargetConnected) return null;
+ if (!isFoldNode && (!node || !node?.sourceHandle?.right || rightTargetConnected)) return null;
return (
{
TopHandlee,
BottomHandlee
};
- }, [connectingEdge, edges, nodeId, nodeList]);
+ }, [connectingEdge, edges, nodeId, nodeList, isFoldNode]);
return showSourceHandle ? (
<>
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx
index f70a8820d..32cb584d8 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx
@@ -32,8 +32,8 @@ const MySourceHandle = React.memo(function MySourceHandle({
const node = useMemo(() => nodes.find((node) => node.data.nodeId === nodeId), [nodes, nodeId]);
const connected = edges.some((edge) => edge.sourceHandle === handleId);
+ const nodeFolded = node?.data.isFolded && edges.some((edge) => edge.source === nodeId);
const nodeIsHover = hoverNodeId === nodeId;
-
const active = useMemo(
() => nodeIsHover || node?.selected || connectingEdge?.handleId === handleId,
[nodeIsHover, node?.selected, connectingEdge, handleId]
@@ -73,7 +73,7 @@ const MySourceHandle = React.memo(function MySourceHandle({
};
}
- if (connected) {
+ if (connected || nodeFolded) {
return {
styles: {
...connectedStyle,
@@ -89,7 +89,7 @@ const MySourceHandle = React.memo(function MySourceHandle({
styles: undefined,
showAddIcon: false
};
- }, [active, connected, highlightStyle, translateStr, transform, connectedStyle]);
+ }, [active, connected, nodeFolded, highlightStyle, translateStr, transform, connectedStyle]);
const RenderHandle = useMemo(() => {
return (
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx
index 27557c001..a96e08a9c 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import React, { useCallback, useMemo } from 'react';
import { Box, Button, Card, Flex, Image } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
@@ -61,7 +61,8 @@ const NodeCard = (props: Props) => {
menuForbid,
isTool = false,
isError = false,
- debugResult
+ debugResult,
+ isFolded
} = props;
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
@@ -81,7 +82,12 @@ const NodeCard = (props: Props) => {
[isTool, nodeList]
);
- const node = nodeList.find((node) => node.nodeId === nodeId);
+ const { node, parentNode } = useMemo(() => {
+ const node = nodeList.find((node) => node.nodeId === nodeId);
+ const parentNode = nodeList.find((n) => n.nodeId === node?.parentNodeId);
+ return { node, parentNode };
+ }, [nodeList, nodeId]);
+
const { openConfirm: onOpenConfirmSync, ConfirmModal: ConfirmSyncModal } = useConfirm({
content: t('app:module.Confirm Sync')
});
@@ -155,6 +161,31 @@ const NodeCard = (props: Props) => {
{/* avatar and name */}
+ {
+ onChangeNode({
+ nodeId,
+ type: 'attr',
+ key: 'isFolded',
+ value: !isFolded
+ });
+ }}
+ >
+ {!isFolded ? (
+
+ ) : (
+
+ )}
+
{t(name as any)}
@@ -250,19 +281,21 @@ const NodeCard = (props: Props) => {
ConfirmSyncModal,
onOpenCustomTitleModal,
onChangeNode,
- toast
+ toast,
+ isFolded
]);
const RenderHandle = useMemo(() => {
return (
<>
-
+
>
);
- }, [nodeId]);
+ }, [nodeId, isFolded]);
return (
{
>
{Header}
- {children}
+ {!isFolded && children}
{RenderHandle}
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx
index 13dea0acd..13f909306 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx
@@ -561,9 +561,9 @@ const WorkflowContextProvider = ({
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
if (!checkResults) {
- const storeNodes = uiWorkflow2StoreWorkflow({ nodes, edges });
+ const storeWorkflow = uiWorkflow2StoreWorkflow({ nodes, edges });
- return storeNodes;
+ return storeWorkflow;
} else if (!hideTip) {
checkResults.forEach((nodeId) => onUpdateNodeError(nodeId, true));
toast({
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/utils.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/utils.tsx
index 91592d1bc..53239bfa5 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/utils.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/utils.tsx
@@ -25,7 +25,8 @@ export const uiWorkflow2StoreWorkflow = ({
version: item.data.version,
inputs: item.data.inputs,
outputs: item.data.outputs,
- pluginId: item.data.pluginId
+ pluginId: item.data.pluginId,
+ isFolded: item.data.isFolded
}));
// get all handle
@@ -49,6 +50,8 @@ export const uiWorkflow2StoreWorkflow = ({
(item) => {
// Not in react flow page
if (!reactFlowViewport) return true;
+ const currentSourceNode = nodes.find((node) => node.data.nodeId === item.source);
+ if (currentSourceNode?.data.isFolded) return true;
return handleIdList.includes(item.sourceHandle) && handleIdList.includes(item.targetHandle);
}
);
diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts
index 959eb343c..b752da524 100644
--- a/projects/app/src/web/core/workflow/utils.ts
+++ b/projects/app/src/web/core/workflow/utils.ts
@@ -532,7 +532,8 @@ export const compareSnapshot = (
name: node.data.name,
intro: node.data.intro,
avatar: node.data.avatar,
- version: node.data.version
+ version: node.data.version,
+ isFolded: node.data.isFolded
}
}));
};