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

View file

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

View file

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

View file

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

View file

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

View file

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