refactor: Optimize GenericNode and other nearby components to improve performance especially on larger flows (#8053)

* Improve render preformance for nodes

* update component optimization

* [autofix.ci] apply automated fixes

* shard increase

* nodeoutput check

* restore

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Carlos Coelho <80289056+carlosrcoelho@users.noreply.github.com>
This commit is contained in:
Mike Fortman 2025-05-19 13:14:17 -05:00 committed by GitHub
commit 4b580d1569
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 245 additions and 243 deletions

View file

@ -9,6 +9,7 @@ import {
import useAuthStore from "@/stores/authStore";
import { cn } from "@/utils/utils";
import { useEffect, useMemo, useRef } from "react";
import { useShallow } from "zustand/react/shallow";
import { default as IconComponent } from "../../../../components/common/genericIconComponent";
import ShadTooltip from "../../../../components/common/shadTooltipComponent";
import {
@ -20,7 +21,6 @@ import {
import useFlowStore from "../../../../stores/flowStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { NodeInputFieldComponentType } from "../../../../types/components";
import { scapedJSONStringfy } from "../../../../utils/reactflowUtils";
import useFetchDataOnMount from "../../../hooks/use-fetch-data-on-mount";
import useHandleOnNewValue from "../../../hooks/use-handle-new-value";
import NodeInputInfo from "../NodeInputInfo";
@ -44,10 +44,13 @@ export default function NodeInputField({
isToolMode = false,
}: NodeInputFieldComponentType): JSX.Element {
const ref = useRef<HTMLDivElement>(null);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const isAuth = useAuthStore((state) => state.isAuthenticated);
const currentFlow = useFlowStore((state) => state.currentFlow);
const { currentFlowId, currentFlowName } = useFlowStore(
useShallow((state) => ({
currentFlowId: state.currentFlow?.id,
currentFlowName: state.currentFlow?.name,
})),
);
const myData = useTypesStore((state) => state.data);
const postTemplateValue = usePostTemplateValue({
node: data.node!,
@ -56,11 +59,6 @@ export default function NodeInputField({
});
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const { handleNodeClass } = useHandleNodeClass(data.id);
let disabled =
edges.some(
(edge) =>
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id),
) || isToolMode;
const { handleOnNewValue } = useHandleOnNewValue({
node: data.node!,
@ -74,9 +72,9 @@ export default function NodeInputField({
const nodeInformationMetadata: NodeInfoType = useMemo(() => {
return {
flowId: currentFlow?.id ?? "",
flowId: currentFlowId ?? "",
nodeType: data?.type?.toLowerCase() ?? "",
flowName: currentFlow?.name ?? "",
flowName: currentFlowName ?? "",
isAuth,
variableName: name,
};
@ -107,12 +105,10 @@ export default function NodeInputField({
const Handle = (
<HandleRenderComponent
left={true}
nodes={nodes}
tooltipTitle={tooltipTitle}
proxy={proxy}
id={id}
title={title}
edges={edges}
myData={myData}
colors={colors}
setFilterEdge={setFilterEdge}
@ -207,12 +203,12 @@ export default function NodeInputField({
handleOnNewValue={handleOnNewValue}
name={name}
nodeId={data.id}
inputId={id}
templateData={data.node?.template[name]!}
templateValue={data.node?.template[name].value ?? ""}
editNode={false}
handleNodeClass={handleNodeClass}
nodeClass={data.node!}
disabled={disabled}
placeholder={
isToolMode
? DEFAULT_TOOLSET_PLACEHOLDER
@ -220,6 +216,7 @@ export default function NodeInputField({
}
isToolMode={isToolMode}
nodeInformationMetadata={nodeInformationMetadata}
proxy={proxy}
/>
)}
</div>

View file

@ -0,0 +1,39 @@
// NodeOutputs.tsx
import { OutputParameter } from ".";
export default function NodeOutputs({
outputs,
keyPrefix,
data,
types,
selected,
showNode,
isToolMode,
showHiddenOutputs,
}) {
if (!outputs?.length) return null;
return outputs?.map((output, idx) => (
<OutputParameter
key={`${keyPrefix}-${output.name}-${idx}`}
output={output}
idx={
data.node!.outputs?.findIndex((out) => out.name === output.name) ?? idx
}
lastOutput={idx === outputs.length - 1}
data={data}
types={types}
selected={selected}
showNode={showNode}
isToolMode={isToolMode}
showHiddenOutputs={showHiddenOutputs}
hidden={
keyPrefix === "hidden"
? showHiddenOutputs
? output.hidden
: true
: false
}
/>
));
}

View file

@ -177,7 +177,6 @@ function NodeOutputField({
const updateNodeInternals = useUpdateNodeInternals();
// Use selective store subscriptions
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setNode = useFlowStore((state) => state.setNode);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
@ -316,11 +315,9 @@ function NodeOutputField({
return (
<HandleRenderComponent
left={true}
nodes={nodes}
tooltipTitle={tooltipTitle}
id={id}
title={title}
edges={edges}
nodeId={data.id}
myData={myData}
colors={colors}
@ -332,11 +329,9 @@ function NodeOutputField({
);
}
}, [
nodes,
tooltipTitle,
id,
title,
edges,
data.id,
myData,
colors,
@ -350,11 +345,9 @@ function NodeOutputField({
() => (
<HandleRenderComponent
left={false}
nodes={nodes}
tooltipTitle={tooltipTitle}
id={id}
title={title}
edges={edges}
nodeId={data.id}
myData={myData}
colors={colors}
@ -365,11 +358,9 @@ function NodeOutputField({
/>
),
[
nodes,
tooltipTitle,
id,
title,
edges,
data.id,
myData,
colors,

View file

@ -151,12 +151,10 @@ const HandleContent = memo(function HandleContent({
const HandleRenderComponent = memo(function HandleRenderComponent({
left,
nodes,
tooltipTitle = "",
proxy,
id,
title,
edges,
myData,
colors,
setFilterEdge,
@ -166,12 +164,10 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
colorName,
}: {
left: boolean;
nodes: any;
tooltipTitle?: string;
proxy?: any;
id: any;
title: string;
edges: any;
myData: any;
colors: string[];
setFilterEdge: (edges: any) => void;
@ -209,20 +205,17 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
[id, proxy],
);
const getConnection = useCallback(
(semiConnection: {
source?: string;
sourceHandle?: string;
target?: string;
targetHandle?: string;
}) => ({
source: semiConnection.source ?? nodeId,
sourceHandle: semiConnection.sourceHandle ?? myId,
target: semiConnection.target ?? nodeId,
targetHandle: semiConnection.targetHandle ?? myId,
}),
[nodeId, myId],
);
const getConnection = (semiConnection: {
source?: string;
sourceHandle?: string;
target?: string;
targetHandle?: string;
}) => ({
source: semiConnection.source ?? nodeId,
sourceHandle: semiConnection.sourceHandle ?? myId,
target: semiConnection.target ?? nodeId,
targetHandle: semiConnection.targetHandle ?? myId,
});
const {
sameNode,
@ -255,25 +248,26 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
handleDragging &&
(left ? handleDragging.source : handleDragging.target) &&
!ownDraggingHandle
? isValidConnection(getConnection(handleDragging), nodes, edges)
? isValidConnection(getConnection(handleDragging))
: false;
const filterOpenHandle =
filterType &&
(left ? filterType.source : filterType.target) &&
!ownFilterHandle
? isValidConnection(getConnection(filterType), nodes, edges)
? isValidConnection(getConnection(filterType))
: false;
const openHandle = filterOpenHandle || draggingOpenHandle;
const filterPresent = handleDragging || filterType;
const connectedEdge = edges.find(
(edge) => edge.target === nodeId && edge.targetHandle === myId,
);
const connectedColor =
nodeColorsName[connectedEdge?.data?.sourceHandle?.output_types[0]] ||
"gray";
const connectedEdge = useFlowStore
.getState()
.edges.find(
(edge) => edge.target === nodeId && edge.targetHandle === myId,
);
const outputType = connectedEdge?.data?.sourceHandle?.output_types?.[0];
const connectedColor = outputType ? nodeColorsName[outputType] : "gray";
const isNullHandle =
filterPresent && !(openHandle || ownDraggingHandle || ownFilterHandle);
@ -341,9 +335,6 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
filterType,
nodeId,
myId,
nodes,
edges,
getConnection,
dark,
colors,
colorName,
@ -365,6 +356,7 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
);
const handleClick = useCallback(() => {
const nodes = useFlowStore.getState().nodes;
setFilterEdge(groupByFamily(myData, tooltipTitle!, left, nodes!));
setFilterType(currentFilter);
if (filterOpenHandle && filterType) {
@ -376,14 +368,12 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
myData,
tooltipTitle,
left,
nodes,
setFilterEdge,
setFilterType,
currentFilter,
filterOpenHandle,
filterType,
onConnect,
getConnection,
]);
const handleMouseEnter = useCallback(() => setIsHovered(true), []);
@ -396,8 +386,8 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
// Memoize the validation function
const validateConnection = useCallback(
(connection: any) => isValidConnection(connection, nodes, edges),
[nodes, edges],
(connection: any) => isValidConnection(connection),
[],
);
return (
@ -424,7 +414,7 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
position={left ? Position.Left : Position.Right}
id={myId}
isValidConnection={(connection) =>
isValidConnection(connection as Connection, nodes, edges)
isValidConnection(connection as Connection)
}
className={cn(
`group/handle z-50 transition-all`,

View file

@ -24,25 +24,24 @@ import { useShortcutsStore } from "../../stores/shortcuts";
import { useTypesStore } from "../../stores/typesStore";
import { VertexBuildTypeAPI } from "../../types/api";
import { NodeDataType } from "../../types/flow";
import { checkHasToolMode } from "../../utils/reactflowUtils";
import { classNames, cn } from "../../utils/utils";
import { processNodeAdvancedFields } from "../helpers/process-node-advanced-fields";
import useUpdateNodeCode from "../hooks/use-update-node-code";
import NodeDescription from "./components/NodeDescription";
import NodeName from "./components/NodeName";
import { OutputParameter } from "./components/NodeOutputParameter";
import NodeOutputs from "./components/NodeOutputParameter/NodeOutputs";
import NodeStatus from "./components/NodeStatus";
import NodeUpdateComponent from "./components/NodeUpdateComponent";
import RenderInputParameters from "./components/RenderInputParameters";
import { NodeIcon } from "./components/nodeIcon";
import { useBuildStatus } from "./hooks/use-get-build-status";
const MemoizedOutputParameter = memo(OutputParameter);
const MemoizedRenderInputParameters = memo(RenderInputParameters);
const MemoizedNodeIcon = memo(NodeIcon);
const MemoizedNodeName = memo(NodeName);
const MemoizedNodeStatus = memo(CustomNodeStatus);
const MemoizedNodeDescription = memo(NodeDescription);
const MemoizedNodeOutputs = memo(NodeOutputs);
const HiddenOutputsButton = memo(
({
@ -104,10 +103,10 @@ function GenericNode({
const showNode = data.showNode ?? true;
const getValidationStatus = (data) => {
const getValidationStatus = useCallback((data) => {
setValidationStatus(data);
return null;
};
}, []);
const { mutate: validateComponentCode } = usePostValidateComponentCode();
@ -246,45 +245,18 @@ function GenericNode({
callback: toggleEditNameDescription,
});
const renderOutputs = useCallback(
(outputs, key?: string) => {
return outputs?.map((output, idx) => (
<MemoizedOutputParameter
key={`${key}-${output.name}-${idx}`}
output={output}
idx={
data.node!.outputs?.findIndex((out) => out.name === output.name) ??
idx
}
lastOutput={idx === outputs.length - 1}
data={data}
types={types}
selected={selected}
showNode={showNode}
isToolMode={isToolMode}
showHiddenOutputs={showHiddenOutputs}
hidden={
key === "hidden"
? showHiddenOutputs
? output.hidden
: true
: false
}
/>
));
},
[data, types, selected, showNode, isToolMode, showHiddenOutputs],
);
const { shownOutputs, hiddenOutputs } = useMemo(
() => ({
shownOutputs:
data.node?.outputs?.filter((output) => !output.hidden) ?? [],
hiddenOutputs:
data.node?.outputs?.filter((output) => output.hidden) ?? [],
}),
[data.node?.outputs],
);
const { shownOutputs, hiddenOutputs } = useMemo(() => {
const shownOutputs: typeof data.node.outputs = [];
const hiddenOutputs: typeof data.node.outputs = [];
(data.node?.outputs ?? []).forEach((output) => {
if (output.hidden) {
hiddenOutputs.push(output);
} else {
shownOutputs.push(output);
}
});
return { shownOutputs, hiddenOutputs };
}, [data.node?.outputs]);
const [hasChangedNodeDescription, setHasChangedNodeDescription] =
useState(false);
@ -391,114 +363,26 @@ function GenericNode({
toggleEditNameDescription,
selectedNodesCount,
]);
useEffect(() => {
if (hiddenOutputs && hiddenOutputs.length === 0) {
setShowHiddenOutputs(false);
}
}, [hiddenOutputs]);
const renderNodeIcon = useCallback(() => {
return (
<MemoizedNodeIcon
dataType={data.type}
showNode={showNode}
icon={data.node?.icon}
isGroup={!!data.node?.flow}
/>
);
}, [data.type, showNode, data.node?.icon, data.node?.flow]);
const handleToggleHiddenOutputs = useCallback(
() => setShowHiddenOutputs((prev) => !prev),
[],
);
const renderNodeName = useCallback(() => {
return (
<MemoizedNodeName
display_name={data.node?.display_name}
nodeId={data.id}
selected={selected}
showNode={showNode}
validationStatus={validationStatus}
isOutdated={isOutdated}
beta={data.node?.beta || false}
editNameDescription={editNameDescription}
toggleEditNameDescription={toggleEditNameDescription}
setHasChangedNodeDescription={setHasChangedNodeDescription}
/>
);
}, [
data.node?.display_name,
data.id,
selected,
showNode,
validationStatus,
isOutdated,
data.node?.beta,
editNameDescription,
toggleEditNameDescription,
setHasChangedNodeDescription,
]);
const renderNodeStatus = useCallback(() => {
return (
<MemoizedNodeStatus
data={data}
frozen={data.node?.frozen}
showNode={showNode}
display_name={data.node?.display_name!}
nodeId={data.id}
selected={selected}
setBorderColor={setBorderColor}
buildStatus={buildStatus}
dismissAll={dismissAll}
isOutdated={isOutdated}
isUserEdited={isUserEdited}
isBreakingChange={hasBreakingChange}
getValidationStatus={getValidationStatus}
/>
);
}, [
data,
showNode,
selected,
buildStatus,
isOutdated,
isUserEdited,
getValidationStatus,
dismissAll,
handleUpdateCode,
]);
const renderDescription = useCallback(() => {
return (
<MemoizedNodeDescription
description={data.node?.description}
mdClassName={"dark:prose-invert"}
nodeId={data.id}
selected={selected}
editNameDescription={editNameDescription}
setEditNameDescription={set}
setHasChangedNodeDescription={setHasChangedNodeDescription}
/>
);
}, [
data.node?.description,
data.id,
selected,
editNameDescription,
toggleEditNameDescription,
setHasChangedNodeDescription,
]);
const renderInputParameters = useCallback(() => {
return (
<MemoizedRenderInputParameters
data={data}
types={types}
isToolMode={isToolMode}
showNode={showNode}
shownOutputs={shownOutputs}
showHiddenOutputs={showHiddenOutputs}
/>
);
}, [data, types, isToolMode, showNode, shownOutputs, showHiddenOutputs]);
const memoizedOnUpdateNode = useCallback(
() => handleUpdateCode(true),
[handleUpdateCode],
);
const memoizedSetDismissAll = useCallback(
() => addDismissedNodes([data.id]),
[addDismissedNodes, data.id],
);
return (
<div className={cn(shouldShowUpdateComponent ? "relative -mt-10" : "")}>
@ -510,20 +394,22 @@ function GenericNode({
!hasOutputs && "pb-4",
)}
>
<UpdateComponentModal
open={openUpdateModal}
setOpen={setOpenUpdateModal}
onUpdateNode={() => handleUpdateCode(true)}
components={componentUpdate ? [componentUpdate] : []}
/>
{openUpdateModal && (
<UpdateComponentModal
open={openUpdateModal}
setOpen={setOpenUpdateModal}
onUpdateNode={memoizedOnUpdateNode}
components={componentUpdate ? [componentUpdate] : []}
/>
)}
{memoizedNodeToolbarComponent}
{shouldShowUpdateComponent && (
<NodeUpdateComponent
hasBreakingChange={hasBreakingChange}
showNode={showNode}
handleUpdateCode={() => handleUpdateCode()}
handleUpdateCode={handleUpdateCode}
loadingUpdate={loadingUpdate}
setDismissAll={() => addDismissedNodes([data.id])}
setDismissAll={memoizedSetDismissAll}
/>
)}
<div
@ -546,28 +432,92 @@ function GenericNode({
className={"generic-node-title-arrangement"}
data-testid="generic-node-title-arrangement"
>
{renderNodeIcon()}
<MemoizedNodeIcon
dataType={data.type}
showNode={showNode}
icon={data.node?.icon}
isGroup={!!data.node?.flow}
/>
<div className="generic-node-tooltip-div truncate">
{renderNodeName()}
<MemoizedNodeName
display_name={data.node?.display_name}
nodeId={data.id}
selected={selected}
showNode={showNode}
validationStatus={validationStatus}
isOutdated={isOutdated}
beta={data.node?.beta || false}
editNameDescription={editNameDescription}
toggleEditNameDescription={toggleEditNameDescription}
setHasChangedNodeDescription={setHasChangedNodeDescription}
/>
</div>
</div>
<div data-testid={`${showNode ? "show" : "hide"}-node-content`}>
{!showNode && (
<>
{renderInputParameters()}
{shownOutputs.length > 0 &&
renderOutputs(shownOutputs, "render-outputs")}
<MemoizedRenderInputParameters
data={data}
types={types}
isToolMode={isToolMode}
showNode={showNode}
shownOutputs={shownOutputs}
showHiddenOutputs={showHiddenOutputs}
/>
<MemoizedNodeOutputs
outputs={shownOutputs}
keyPrefix="render-outputs"
data={data}
types={types}
selected={selected}
showNode={showNode}
isToolMode={isToolMode}
showHiddenOutputs={showHiddenOutputs}
/>
</>
)}
</div>
{renderNodeStatus()}
<MemoizedNodeStatus
data={data}
frozen={data.node?.frozen}
showNode={showNode}
display_name={data.node?.display_name!}
nodeId={data.id}
selected={selected}
setBorderColor={setBorderColor}
buildStatus={buildStatus}
dismissAll={dismissAll}
isOutdated={isOutdated}
isUserEdited={isUserEdited}
isBreakingChange={hasBreakingChange}
getValidationStatus={getValidationStatus}
/>
</div>
{showNode && <div>{renderDescription()}</div>}
{showNode && (
<div>
<MemoizedNodeDescription
description={data.node?.description}
mdClassName={"dark:prose-invert"}
nodeId={data.id}
selected={selected}
editNameDescription={editNameDescription}
setEditNameDescription={set}
setHasChangedNodeDescription={setHasChangedNodeDescription}
/>
</div>
)}
</div>
{showNode && (
<div className="nopan nodelete nodrag noflow relative cursor-auto">
<>
{renderInputParameters()}
<MemoizedRenderInputParameters
data={data}
types={types}
isToolMode={isToolMode}
showNode={showNode}
shownOutputs={shownOutputs}
showHiddenOutputs={showHiddenOutputs}
/>
<div
className={classNames(
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
@ -576,15 +526,33 @@ function GenericNode({
>
{" "}
</div>
{!showHiddenOutputs &&
shownOutputs &&
renderOutputs(shownOutputs, "shown")}
{!showHiddenOutputs && shownOutputs && (
<MemoizedNodeOutputs
outputs={shownOutputs}
keyPrefix="shown"
data={data}
types={types}
selected={selected}
showNode={showNode}
isToolMode={isToolMode}
showHiddenOutputs={showHiddenOutputs}
/>
)}
<div
className={cn(showHiddenOutputs ? "" : "h-0 overflow-hidden")}
>
<div className="block">
{renderOutputs(data.node!.outputs, "hidden")}
<MemoizedNodeOutputs
outputs={data.node!.outputs}
keyPrefix="hidden"
data={data}
types={types}
selected={selected}
showNode={showNode}
isToolMode={isToolMode}
showHiddenOutputs={showHiddenOutputs}
/>
</div>
</div>
{hiddenOutputs && hiddenOutputs.length > 0 && (
@ -606,7 +574,7 @@ function GenericNode({
>
<HiddenOutputsButton
showHiddenOutputs={showHiddenOutputs}
onClick={() => setShowHiddenOutputs(!showHiddenOutputs)}
onClick={handleToggleHiddenOutputs}
/>
</div>
</ShadTooltip>

View file

@ -1,36 +1,50 @@
import { ParameterRenderComponent } from "@/components/core/parameterRenderComponent";
import { NodeInfoType } from "@/components/core/parameterRenderComponent/types";
import { handleOnNewValueType } from "@/CustomNodes/hooks/use-handle-new-value";
import useFlowStore from "@/stores/flowStore";
import { APIClassType, InputFieldType } from "@/types/api";
import { targetHandleType } from "@/types/flow";
import { scapedJSONStringfy } from "@/utils/reactflowUtils";
import { cn } from "@/utils/utils";
export function CustomParameterComponent({
handleOnNewValue,
name,
nodeId,
inputId,
templateData,
templateValue,
editNode,
handleNodeClass,
nodeClass,
disabled,
placeholder,
isToolMode,
isToolMode = false,
nodeInformationMetadata,
proxy,
}: {
handleOnNewValue: handleOnNewValueType;
name: string;
nodeId: string;
inputId: targetHandleType;
templateData: Partial<InputFieldType>;
templateValue: any;
editNode: boolean;
handleNodeClass: (value: any, code?: string, type?: string) => void;
nodeClass: APIClassType;
disabled: boolean;
placeholder?: string;
isToolMode?: boolean;
nodeInformationMetadata?: NodeInfoType;
proxy: { field: string; id: string } | undefined;
}) {
const edges = useFlowStore((state) => state.edges);
let disabled =
edges.some(
(edge) =>
edge.targetHandle ===
scapedJSONStringfy(proxy ? { ...inputId, proxy } : inputId),
) || isToolMode;
return (
<ParameterRenderComponent
handleOnNewValue={handleOnNewValue}

View file

@ -79,8 +79,6 @@ const NodeToolbarComponent = memo(
const updateNodeInternals = useUpdateNodeInternals();
const paste = useFlowStore((state) => state.paste);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setNodes = useFlowStore((state) => state.setNodes);
const setEdges = useFlowStore((state) => state.setEdges);
const getNodePosition = useFlowStore((state) => state.getNodePosition);
@ -200,8 +198,6 @@ const NodeToolbarComponent = memo(
data.id,
updateFlowPosition(getNodePosition(data.id), data.node?.flow!),
data.node!.template,
nodes,
edges,
setNodes,
setEdges,
data.node?.outputs,
@ -213,8 +209,6 @@ const NodeToolbarComponent = memo(
data.node?.flow,
data.node?.template,
data.node?.outputs,
nodes,
edges,
setNodes,
setEdges,
takeSnapshot,
@ -307,6 +301,7 @@ const NodeToolbarComponent = memo(
const handleSelectChange = useCallback(
(event) => {
let nodes;
setSelectedValue(event);
switch (event) {
@ -356,10 +351,12 @@ const NodeToolbarComponent = memo(
updateNode();
break;
case "copy":
nodes = useFlowStore.getState().nodes;
const node = nodes.filter((node) => node.id === data.id);
setLastCopiedSelection({ nodes: _.cloneDeep(node), edges: [] });
break;
case "duplicate":
nodes = useFlowStore.getState().nodes;
paste(
{
nodes: [nodes.find((node) => node.id === data.id)!],

View file

@ -17,6 +17,7 @@ import {
} from "@/CustomNodes/utils/get-handle-id";
import { INCOMPLETE_LOOP_ERROR_ALERT } from "@/constants/alerts_constants";
import { customDownloadFlow } from "@/customization/utils/custom-reactFlowUtils";
import useFlowStore from "@/stores/flowStore";
import {
Connection,
Edge,
@ -320,12 +321,16 @@ export function unselectAllNodesEdges(nodes: Node[], edges: Edge[]) {
export function isValidConnection(
{ source, target, sourceHandle, targetHandle }: Connection,
nodes: AllNodeType[],
edges: EdgeType[],
nodes?: AllNodeType[],
edges?: EdgeType[],
): boolean {
if (source === target) {
return false;
}
const nodesArray = nodes || useFlowStore.getState().nodes;
const edgesArray = edges || useFlowStore.getState().edges;
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
if (
@ -345,20 +350,20 @@ export function isValidConnection(
t === targetHandleObject.type,
)
) {
let targetNode = nodes.find((node) => node.id === target!)?.data?.node;
let targetNode = nodesArray.find((node) => node.id === target!)?.data?.node;
if (!targetNode) {
if (!edges.find((e) => e.targetHandle === targetHandle)) {
if (!edgesArray.find((e) => e.targetHandle === targetHandle)) {
return true;
}
} else if (
targetHandleObject.output_types &&
!edges.find((e) => e.targetHandle === targetHandle)
!edgesArray.find((e) => e.targetHandle === targetHandle)
) {
return true;
} else if (
!targetHandleObject.output_types &&
((!targetNode.template[targetHandleObject.fieldName].list &&
!edges.find((e) => e.targetHandle === targetHandle)) ||
!edgesArray.find((e) => e.targetHandle === targetHandle)) ||
targetNode.template[targetHandleObject.fieldName].list)
) {
return true;
@ -1476,8 +1481,6 @@ export function expandGroupNode(
id: string,
flow: FlowType,
template: APITemplateType,
nodes: AllNodeType[],
edges: EdgeType[],
setNodes: (
update: AllNodeType[] | ((oldState: AllNodeType[]) => AllNodeType[]),
) => void,
@ -1488,7 +1491,7 @@ export function expandGroupNode(
) {
const idsMap = updateIds(flow!.data!);
updateProxyIdsOnTemplate(template, idsMap);
let flowEdges = edges;
let flowEdges = useFlowStore.getState().edges;
updateEdgesIds(flowEdges, idsMap);
const gNodes: AllNodeType[] = cloneDeep(flow?.data?.nodes!);
const gEdges = cloneDeep(flow!.data!.edges);
@ -1588,9 +1591,12 @@ export function expandGroupNode(
}
}
});
const filteredNodes = [...nodes.filter((n) => n.id !== id), ...gNodes];
const filteredNodes = [
...useFlowStore.getState().nodes.filter((n) => n.id !== id),
...gNodes,
];
const filteredEdges = [
...edges.filter((e) => e.target !== id && e.source !== id),
...flowEdges.filter((e) => e.target !== id && e.source !== id),
...gEdges,
];
setNodes(filteredNodes);