UNPKG

n8n-editor-ui

Version:

Workflow Editor UI for n8n

1,764 lines 77.1 kB
import { o as __toESM, t as __commonJSMin } from "./chunk-6z4oVpB-.js";
import { C as computed, G as nextTick, It as ref, zt as shallowRef } from "./vue.runtime.esm-bundler-tP5dCd7J.js";
import { M as useLocalStorage, S as useClipboard, _t as useI18n } from "./_MapCache-ficiegRb.js";
import { T as createEventBus, v as useRoute, y as useRouter } from "./truncate-B1HVeveJ.js";
import { B as PUSH_NODES_OFFSET, Gi as isPresent, Gn as useNodeHelpers, H as doRectsOverlap, I as CONFIGURATION_NODE_SIZE, J as getNewNodePosition, Kn as injectWorkflowState, L as DEFAULT_NODE_SIZE, M as useNDVStore, N as useWorkflowsStore, Nt as useNodeTypesStore, O as useTelemetry, P as CONFIGURABLE_NODE_SIZE, Qr as AddNodeCommand, R as DEFAULT_VIEWPORT_BOUNDARIES, Rn as useWorkflowHelpers, U as generateOffsets, Ur as isValidNodeConnectionType, Vn as useTagsStore, X as getNodesGroupSize, Xi as tryToParseNumber, Xn as useProjectsStore, Xr as useHistoryStore, Yr as CanvasNodeRenderType, Zr as AddConnectionCommand, ai as RenameNodeCommand, b as useExternalHooks, c as useTemplatesStore, fs as v4_default, gn as useCredentialsStore, gr as useCanvasStore, gs as useSettingsStore, ii as RemoveNodeCommand, j as usePostHog, kt as getExecutionErrorToastConfiguration, ni as MoveNodeCommand, oa as useUIStore, oi as ReplaceNodeParametersCommand, qn as useWorkflowState, qr as CanvasConnectionMode, r as useFocusPanelStore, ri as RemoveConnectionCommand, v as useToast, xn as useDataSchema, xt as isChatNode, z as GRID_SIZE } from "./builder.store-sBTWwxRU.js";
import { $i as getNodeParameters, Dn as MCP_TRIGGER_NODE_TYPE, Fs as LOCAL_STORAGE_LOGS_PANEL_OPEN, Is as LOCAL_STORAGE_LOGS_SYNC_SELECTION, Ns as LOCAL_STORAGE_LOGS_PANEL_DETAILS_PANEL, Oi as require__baseUniq, Ps as LOCAL_STORAGE_LOGS_PANEL_DETAILS_PANEL_SUB_NODE, Qi as getNodeOutputs, Ro as VIEWS, Ta as isCommunityPackageName, Ua as NodeConnectionTypes, Xi as getConnectionTypes, Zi as getNodeInputs, bc as EnterpriseEditionFeature, dc as NDV_IN_FOCUS_PANEL_EXPERIMENT, hr as WEBHOOK_NODE_TYPE, ia as getSubworkflowId, pr as UPDATE_WEBHOOK_ID_NODE_TYPES, qi as generateNodesGraph, sc as CANVAS_ZOOMED_VIEW_EXPERIMENT, sr as STICKY_NODE_TYPE, un as FORM_TRIGGER_NODE_TYPE, wa as deepCopy, yi as require_cloneDeep } from "./constants-BbpucWL4.js";
import { k as defineStore, r as useRootStore } from "./_baseOrderBy-B_6CV8x6.js";
import { t as useExecutionsStore } from "./executions.store-CXjGPxs0.js";
import { t as usePinnedData } from "./usePinnedData-CL2qWhwp.js";
import { a as mapCanvasConnectionToLegacyConnection, l as parseCanvasConnectionHandleString, o as mapLegacyConnectionToCanvasConnection, r as createCanvasConnectionHandleString, s as mapLegacyConnectionsToCanvasConnections, t as useNodeCreatorStore } from "./nodeCreator.store-FJrC_Pu6.js";
import { t as useFoldersStore } from "./folders.store-BjAwtSH4.js";
function useUniqueNodeName() {
	function numberSuffixedNames() {
		return useNodeTypesStore().allNodeTypes.reduce((acc, nodeType) => {
			if (typeof nodeType.defaults.name !== "string") throw new Error("Expected node name default to be a string");
			if (/\d$/.test(nodeType.defaults.name)) acc.push(nodeType.defaults.name);
			return acc;
		}, []);
	}
	function uniqueNodeName(originalName, extraNames = []) {
		const { canvasNames } = useWorkflowsStore();
		if (!canvasNames.has(originalName) && !extraNames.includes(originalName)) return originalName;
		const nsn = numberSuffixedNames().find((nsn$1) => originalName.startsWith(nsn$1));
		if (nsn) {
			let unique$1 = "";
			let index$1 = 1;
			const remainder = originalName.split(nsn).pop();
			const lastChar = remainder?.[remainder.length - 1];
			if (lastChar && Number.isInteger(Number(lastChar))) {
				index$1 = parseInt(lastChar, 10);
				originalName = originalName.slice(0, -1);
			}
			unique$1 = originalName;
			while (canvasNames.has(unique$1) || extraNames.includes(unique$1)) unique$1 = originalName + index$1++;
			return unique$1;
		}
		if (/^\d+-?\d*$/.test(originalName)) {
			let unique$1 = "";
			let index$1 = 1;
			const match$1 = originalName.match(/(?<base>\d+)-?(?<suffix>\d*)/);
			if (!match$1?.groups) throw new Error("Failed to find match for unique name");
			if (match$1?.groups?.suffix !== "") index$1 = parseInt(match$1.groups.suffix, 10);
			unique$1 = match$1.groups.base;
			while (canvasNames.has(unique$1) || extraNames.includes(unique$1)) unique$1 = match$1.groups.base + "-" + index$1++;
			return unique$1;
		}
		let unique = "";
		let index = 1;
		const match = originalName.match(/(?<base>.*\D+)(?<suffix>\d*)/);
		if (!match?.groups) throw new Error("Failed to find match for unique name");
		if (match?.groups?.suffix !== "") index = parseInt(match.groups.suffix, 10);
		unique = match.groups.base;
		while (canvasNames.has(unique) || extraNames.includes(unique)) unique = match.groups.base + index++;
		return unique;
	}
	return { uniqueNodeName };
}
function createEventBus$1() {
	const handlers = /* @__PURE__ */ new Map();
	function off(eventName, fn) {
		const eventFns = handlers.get(eventName);
		if (eventFns) eventFns.splice(eventFns.indexOf(fn) >>> 0, 1);
	}
	function on(eventName, fn) {
		let eventFns = handlers.get(eventName);
		if (!eventFns) eventFns = [fn];
		else eventFns.push(fn);
		handlers.set(eventName, eventFns);
		return () => off(eventName, fn);
	}
	function emit(eventName, event) {
		const eventFns = handlers.get(eventName);
		if (eventFns) eventFns.slice().forEach(async (handler) => {
			await handler(event);
		});
	}
	return {
		on,
		off,
		emit
	};
}
const chatEventBus = createEventBus$1();
const LOCAL_STORAGE_PANEL_HEIGHT = "N8N_CANVAS_CHAT_HEIGHT";
const LOCAL_STORAGE_PANEL_WIDTH = "N8N_CANVAS_CHAT_WIDTH";
const LOCAL_STORAGE_OVERVIEW_PANEL_WIDTH = "N8N_LOGS_OVERVIEW_PANEL_WIDTH";
const LOGS_PANEL_STATE = {
	CLOSED: "closed",
	ATTACHED: "attached",
	FLOATING: "floating"
};
const LOG_DETAILS_PANEL_STATE = {
	INPUT: "input",
	OUTPUT: "output",
	BOTH: "both"
};
const useLogsStore = defineStore("logs", () => {
	const isOpen = useLocalStorage(LOCAL_STORAGE_LOGS_PANEL_OPEN, false);
	const preferPoppedOut = ref(false);
	const state = computed(() => isOpen.value ? preferPoppedOut.value ? LOGS_PANEL_STATE.FLOATING : LOGS_PANEL_STATE.ATTACHED : LOGS_PANEL_STATE.CLOSED);
	const height = ref(0);
	const detailsState = useLocalStorage(LOCAL_STORAGE_LOGS_PANEL_DETAILS_PANEL, LOG_DETAILS_PANEL_STATE.OUTPUT, { writeDefaults: false });
	const detailsStateSubNode = useLocalStorage(LOCAL_STORAGE_LOGS_PANEL_DETAILS_PANEL_SUB_NODE, LOG_DETAILS_PANEL_STATE.BOTH, { writeDefaults: false });
	const isLogSelectionSyncedWithCanvas = useLocalStorage(LOCAL_STORAGE_LOGS_SYNC_SELECTION, true, { writeDefaults: false });
	const isSubNodeSelected = ref(false);
	const telemetry = useTelemetry();
	const chatSessionId = ref(getNewSessionId());
	const chatSessionMessages = ref([]);
	function setHeight(value) {
		height.value = value;
	}
	function getNewSessionId() {
		return v4_default().replace(/-/g, "");
	}
	function resetChatSessionId() {
		chatSessionId.value = getNewSessionId();
	}
	function resetMessages() {
		chatSessionMessages.value = [];
	}
	function toggleOpen(value) {
		isOpen.value = value ?? !isOpen.value;
	}
	function setPreferPoppedOut(value) {
		preferPoppedOut.value = value;
	}
	function setSubNodeSelected(value) {
		isSubNodeSelected.value = value;
	}
	function toggleInputOpen(open) {
		const statesWithInput = [LOG_DETAILS_PANEL_STATE.INPUT, LOG_DETAILS_PANEL_STATE.BOTH];
		const stateRef = isSubNodeSelected.value ? detailsStateSubNode : detailsState;
		const wasOpen = statesWithInput.includes(stateRef.value);
		if (open === wasOpen) return;
		stateRef.value = wasOpen ? LOG_DETAILS_PANEL_STATE.OUTPUT : LOG_DETAILS_PANEL_STATE.BOTH;
		telemetry.track("User toggled log view sub pane", {
			pane: "input",
			newState: wasOpen ? "hidden" : "visible",
			isSubNode: isSubNodeSelected.value
		});
	}
	function toggleOutputOpen(open) {
		const statesWithOutput = [LOG_DETAILS_PANEL_STATE.OUTPUT, LOG_DETAILS_PANEL_STATE.BOTH];
		const stateRef = isSubNodeSelected.value ? detailsStateSubNode : detailsState;
		const wasOpen = statesWithOutput.includes(stateRef.value);
		if (open === wasOpen) return;
		stateRef.value = wasOpen ? LOG_DETAILS_PANEL_STATE.INPUT : LOG_DETAILS_PANEL_STATE.BOTH;
		telemetry.track("User toggled log view sub pane", {
			pane: "output",
			newState: wasOpen ? "hidden" : "visible",
			isSubNode: isSubNodeSelected.value
		});
	}
	function toggleLogSelectionSync(value) {
		isLogSelectionSyncedWithCanvas.value = value ?? !isLogSelectionSyncedWithCanvas.value;
	}
	function addChatMessage(message) {
		chatSessionMessages.value.push(message);
	}
	return {
		state,
		isOpen: computed(() => state.value !== LOGS_PANEL_STATE.CLOSED),
		detailsState: computed(() => isSubNodeSelected.value ? detailsStateSubNode.value : detailsState.value),
		height: computed(() => height.value),
		isLogSelectionSyncedWithCanvas: computed(() => isLogSelectionSyncedWithCanvas.value),
		chatSessionId: computed(() => chatSessionId.value),
		chatSessionMessages: computed(() => chatSessionMessages.value),
		addChatMessage,
		setHeight,
		toggleOpen,
		setPreferPoppedOut,
		setSubNodeSelected,
		toggleInputOpen,
		toggleOutputOpen,
		toggleLogSelectionSync,
		resetChatSessionId,
		resetMessages
	};
});
var require_uniq = /* @__PURE__ */ __commonJSMin(((exports, module) => {
	var baseUniq = require__baseUniq();
	function uniq$1(array) {
		return array && array.length ? baseUniq(array) : [];
	}
	module.exports = uniq$1;
}));
const useExperimentalNdvStore = defineStore("experimentalNdv", () => {
	const workflowStore = useWorkflowsStore();
	const postHogStore = usePostHog();
	const isZoomedViewEnabled = computed(() => postHogStore.getVariant(CANVAS_ZOOMED_VIEW_EXPERIMENT.name) === CANVAS_ZOOMED_VIEW_EXPERIMENT.variant);
	const isNdvInFocusPanelEnabled = computed(() => postHogStore.getVariant(NDV_IN_FOCUS_PANEL_EXPERIMENT.name) === NDV_IN_FOCUS_PANEL_EXPERIMENT.variant);
	const maxCanvasZoom = computed(() => isZoomedViewEnabled.value ? 2 : 4);
	const previousViewport = ref();
	const collapsedNodes = shallowRef({});
	const nodeNameToBeFocused = ref();
	const isMapperOpen = ref(false);
	function setNodeExpanded(nodeId, isExpanded) {
		collapsedNodes.value = {
			...collapsedNodes.value,
			[nodeId]: isExpanded === void 0 ? !collapsedNodes.value[nodeId] : !isExpanded
		};
	}
	function collapseAllNodes() {
		collapsedNodes.value = workflowStore.allNodes.reduce((acc, node) => {
			acc[node.id] = true;
			return acc;
		}, {});
	}
	function expandAllNodes() {
		collapsedNodes.value = {};
	}
	function isActive(canvasZoom) {
		return isZoomedViewEnabled.value && Math.abs(canvasZoom - maxCanvasZoom.value) < 1e-6;
	}
	function setNodeNameToBeFocused(nodeName) {
		nodeNameToBeFocused.value = nodeName;
	}
	function setMapperOpen(value) {
		isMapperOpen.value = value;
	}
	function focusNode(node, { canvasDimensions, canvasViewport, setCenter }) {
		collapsedNodes.value = {
			...collapsedNodes.value,
			[node.id]: false
		};
		const topMargin = 80;
		const nodeWidth = node.dimensions.width * (isActive(canvasViewport.zoom) ? 1 : 1.5);
		if (nodeNameToBeFocused.value === node.data.name) nodeNameToBeFocused.value = void 0;
		setCenter(node.position.x + nodeWidth / 2, node.position.y + (canvasDimensions.height * (1 / 2) - topMargin) / maxCanvasZoom.value, {
			duration: 200,
			zoom: maxCanvasZoom.value,
			interpolate: "linear"
		});
	}
	function toggleZoomMode(options) {
		if (isActive(options.canvasViewport.zoom)) {
			if (previousViewport.value === void 0) {
				options.fitView({
					duration: 200,
					interpolate: "linear"
				});
				return;
			}
			options.setViewport(previousViewport.value, {
				duration: 200,
				interpolate: "linear"
			});
			return;
		}
		previousViewport.value = options.canvasViewport;
		const toFocus = options.selectedNodes.filter((node) => node.data.render.type === CanvasNodeRenderType.Default).toSorted((a, b) => a.position.y === b.position.y ? a.position.x - b.position.x : a.position.y - b.position.y)[0];
		if (toFocus) {
			focusNode(toFocus, options);
			return;
		}
		options.zoomTo(maxCanvasZoom.value, {
			duration: 200,
			interpolate: "linear"
		});
	}
	return {
		isZoomedViewEnabled,
		isNdvInFocusPanelEnabled,
		maxCanvasZoom,
		previousZoom: computed(() => previousViewport.value),
		collapsedNodes: computed(() => collapsedNodes.value),
		nodeNameToBeFocused: computed(() => nodeNameToBeFocused.value),
		isMapperOpen: computed(() => isMapperOpen.value),
		isActive,
		setNodeExpanded,
		expandAllNodes,
		collapseAllNodes,
		toggleZoomMode,
		focusNode,
		setNodeNameToBeFocused,
		setMapperOpen
	};
});
const canvasEventBus = createEventBus();
function useParentFolder() {
	const foldersStore = useFoldersStore();
	const projectsStore = useProjectsStore();
	const workflowsStore = useWorkflowsStore();
	const fetchAndSetParentFolder = async (folderId) => {
		if (!folderId) return;
		let parentFolder = foldersStore.getCachedFolder(folderId);
		if (!parentFolder && projectsStore.currentProjectId) {
			await foldersStore.getFolderPath(projectsStore.currentProjectId, folderId);
			parentFolder = foldersStore.getCachedFolder(folderId);
		}
		if (parentFolder) workflowsStore.setParentFolder({
			...parentFolder,
			parentFolderId: parentFolder.parentFolder ?? null
		});
		return parentFolder ?? null;
	};
	return { fetchAndSetParentFolder };
}
var import_cloneDeep = /* @__PURE__ */ __toESM(require_cloneDeep());
var import_uniq = /* @__PURE__ */ __toESM(require_uniq());
function useCanvasOperations() {
	const rootStore = useRootStore();
	const workflowsStore = useWorkflowsStore();
	const workflowState = injectWorkflowState();
	const credentialsStore = useCredentialsStore();
	const historyStore = useHistoryStore();
	const uiStore = useUIStore();
	const ndvStore = useNDVStore();
	const nodeTypesStore = useNodeTypesStore();
	const canvasStore = useCanvasStore();
	const settingsStore = useSettingsStore();
	const tagsStore = useTagsStore();
	const nodeCreatorStore = useNodeCreatorStore();
	const executionsStore = useExecutionsStore();
	const projectsStore = useProjectsStore();
	const logsStore = useLogsStore();
	const experimentalNdvStore = useExperimentalNdvStore();
	const templatesStore = useTemplatesStore();
	const focusPanelStore = useFocusPanelStore();
	const i18n = useI18n();
	const toast = useToast();
	const workflowHelpers = useWorkflowHelpers();
	const nodeHelpers = useNodeHelpers();
	const telemetry = useTelemetry();
	const externalHooks = useExternalHooks();
	const clipboard = useClipboard();
	const { uniqueNodeName } = useUniqueNodeName();
	const { fetchAndSetParentFolder } = useParentFolder();
	const router = useRouter();
	const route = useRoute();
	const lastClickPosition = ref([0, 0]);
	const preventOpeningNDV = !!localStorage.getItem("NodeView.preventOpeningNDV");
	const editableWorkflow = computed(() => workflowsStore.workflow);
	const editableWorkflowObject = computed(() => workflowsStore.workflowObject);
	const triggerNodes = computed(() => {
		return workflowsStore.workflowTriggerNodes;
	});
	function tidyUp({ result, source, target }, { trackEvents = true } = {}) {
		updateNodesPosition(result.nodes.map(({ id, x, y }) => ({
			id,
			position: {
				x,
				y
			}
		})), {
			trackBulk: true,
			trackHistory: true
		});
		if (trackEvents) trackTidyUp({
			result,
			source,
			target
		});
	}
	function trackTidyUp({ result, source, target }) {
		telemetry.track("User tidied up canvas", {
			source,
			target,
			nodes_count: result.nodes.length
		});
	}
	function updateNodesPosition(events, { trackHistory = false, trackBulk = true } = {}) {
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		events.forEach(({ id, position }) => {
			updateNodePosition(id, position, { trackHistory });
		});
		if (trackHistory && trackBulk) historyStore.stopRecordingUndo();
	}
	function updateNodePosition(id, position, { trackHistory = false } = {}) {
		const node = workflowsStore.getNodeById(id);
		if (!node) return;
		const oldPosition = [...node.position];
		const newPosition = [position.x, position.y];
		workflowState.setNodePositionById(id, newPosition);
		if (trackHistory) historyStore.pushCommandToUndo(new MoveNodeCommand(node.name, oldPosition, newPosition, Date.now()));
	}
	function revertUpdateNodePosition(nodeName, position) {
		const node = workflowsStore.getNodeByName(nodeName);
		if (!node) return;
		updateNodePosition(node.id, position);
	}
	function replaceNodeParameters(nodeId, currentParameters, newParameters, { trackHistory = false, trackBulk = true } = {}) {
		const node = workflowsStore.getNodeById(nodeId);
		if (!node) return;
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		workflowState.setNodeParameters({
			name: node.name,
			value: newParameters
		});
		if (trackHistory) historyStore.pushCommandToUndo(new ReplaceNodeParametersCommand(nodeId, currentParameters, newParameters, Date.now()));
		if (trackHistory && trackBulk) historyStore.stopRecordingUndo();
	}
	async function revertReplaceNodeParameters(nodeId, currentParameters, newParameters) {
		replaceNodeParameters(nodeId, newParameters, currentParameters);
	}
	async function renameNode(currentName, newName, { trackHistory = false, trackBulk = true } = {}) {
		if (currentName === newName) return;
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		newName = uniqueNodeName(newName);
		const workflow = workflowsStore.cloneWorkflowObject();
		try {
			workflow.renameNode(currentName, newName);
		} catch (error) {
			toast.showMessage({
				type: "error",
				title: error.message,
				message: error.description
			});
			return;
		}
		if (trackHistory) historyStore.pushCommandToUndo(new RenameNodeCommand(currentName, newName, Date.now()));
		workflowsStore.renameNodeSelectedAndExecution({
			old: currentName,
			new: newName
		});
		workflowsStore.setNodes(Object.values(workflow.nodes));
		workflowsStore.setConnections(workflow.connectionsBySourceNode);
		if (ndvStore.activeNodeName === currentName) ndvStore.setActiveNodeName(newName, "other");
		if (trackHistory && trackBulk) historyStore.stopRecordingUndo();
	}
	async function revertRenameNode(currentName, previousName) {
		await renameNode(currentName, previousName);
	}
	function connectAdjacentNodes(id, { trackHistory = false } = {}) {
		const node = workflowsStore.getNodeById(id);
		if (!node) return;
		const outputConnectionsByType = workflowsStore.outgoingConnectionsByNodeName(node.name);
		const incomingConnectionsByType = workflowsStore.incomingConnectionsByNodeName(node.name);
		for (const [type, incomingConnectionsByInputIndex] of Object.entries(incomingConnectionsByType)) for (const incomingConnection of incomingConnectionsByInputIndex.at(0) ?? []) {
			const incomingNodeId = workflowsStore.getNodeByName(incomingConnection.node)?.id;
			if (!incomingNodeId) continue;
			for (const outgoingConnection of outputConnectionsByType[type]?.at(0) ?? []) {
				const outgoingNodeId = workflowsStore.getNodeByName(outgoingConnection.node)?.id;
				if (!outgoingNodeId) continue;
				if (trackHistory) historyStore.pushCommandToUndo(new AddConnectionCommand([{
					node: incomingConnection.node,
					type,
					index: incomingConnection.index
				}, {
					node: outgoingConnection.node,
					type,
					index: outgoingConnection.index
				}], Date.now()));
				createConnection({
					source: incomingNodeId,
					sourceHandle: createCanvasConnectionHandleString({
						mode: CanvasConnectionMode.Output,
						type,
						index: incomingConnection.index
					}),
					target: outgoingNodeId,
					targetHandle: createCanvasConnectionHandleString({
						mode: CanvasConnectionMode.Input,
						type,
						index: outgoingConnection.index
					})
				});
			}
		}
	}
	function deleteNode(id, { trackHistory = false, trackBulk = true } = {}) {
		const node = workflowsStore.getNodeById(id);
		if (!node) return;
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		if (uiStore.lastInteractedWithNodeId === id) uiStore.lastInteractedWithNodeId = void 0;
		connectAdjacentNodes(id, { trackHistory });
		deleteConnectionsByNodeId(id, {
			trackHistory,
			trackBulk: false
		});
		workflowsStore.removeNodeExecutionDataById(id);
		workflowsStore.removeNodeById(id);
		if (trackHistory) {
			historyStore.pushCommandToUndo(new RemoveNodeCommand(node, Date.now()));
			if (trackBulk) historyStore.stopRecordingUndo();
		}
		trackDeleteNode(id);
	}
	function deleteNodes(ids, { trackHistory = true, trackBulk = true } = {}) {
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		ids.forEach((id) => deleteNode(id, {
			trackHistory,
			trackBulk: false
		}));
		if (trackHistory && trackBulk) historyStore.stopRecordingUndo();
	}
	function revertDeleteNode(node) {
		workflowsStore.addNode(node);
		uiStore.stateIsDirty = true;
	}
	function trackDeleteNode(id) {
		const node = workflowsStore.getNodeById(id);
		if (!node) return;
		if (node.type === "n8n-nodes-base.stickyNote") telemetry.track("User deleted workflow note", { workflow_id: workflowsStore.workflowId });
		else {
			externalHooks.run("node.deleteNode", { node });
			telemetry.track("User deleted node", {
				node_type: node.type,
				workflow_id: workflowsStore.workflowId
			});
		}
	}
	function replaceNodeConnections(previousId, newId, { trackHistory = false, trackBulk = true, replaceInputs = true, replaceOutputs = true } = {}) {
		const previousNode = workflowsStore.getNodeById(previousId);
		const newNode = workflowsStore.getNodeById(newId);
		if (!previousNode || !newNode) return;
		const workflowObject = workflowsStore.workflowObject;
		const inputNodeNames = replaceInputs ? (0, import_uniq.default)(workflowObject.getParentNodes(previousNode.name, "ALL", 1)) : [];
		const outputNodeNames = replaceOutputs ? (0, import_uniq.default)(workflowObject.getChildNodes(previousNode.name, "ALL", 1)) : [];
		const connectionPairs = [...workflowObject.getConnectionsBetweenNodes(inputNodeNames, [previousNode.name]), ...workflowObject.getConnectionsBetweenNodes([previousNode.name], outputNodeNames)];
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		for (const pair of connectionPairs) {
			const sourceNode = workflowsStore.getNodeByName(pair[0].node);
			const targetNode = workflowsStore.getNodeByName(pair[1].node);
			if (!sourceNode || !targetNode) continue;
			deleteConnection(mapLegacyConnectionToCanvasConnection(sourceNode, targetNode, pair), {
				trackHistory,
				trackBulk: false
			});
			const newSourceIConnection = {
				...pair[0],
				node: pair[0].node === previousNode.name ? newNode.name : pair[0].node
			};
			const newTargetIConnection = {
				...pair[1],
				node: pair[1].node === previousNode.name ? newNode.name : pair[1].node
			};
			const newSourceNode = sourceNode.name === previousNode.name ? newNode : sourceNode;
			const newTargetNode = targetNode.name === previousNode.name ? newNode : targetNode;
			if (!isConnectionAllowed(newSourceNode, newTargetNode, newSourceIConnection, newTargetIConnection)) continue;
			const newCanvasConnection = mapLegacyConnectionToCanvasConnection(newSourceNode, newTargetNode, [newSourceIConnection, newTargetIConnection]);
			createConnection(newCanvasConnection, { trackHistory });
			revalidateNodeInputConnections(newCanvasConnection.target);
			revalidateNodeOutputConnections(newCanvasConnection.source);
		}
		if (trackHistory && trackBulk) historyStore.stopRecordingUndo();
	}
	function setNodeActive(id, source) {
		const node = workflowsStore.getNodeById(id);
		if (!node) return;
		workflowsStore.setNodePristine(node.name, false);
		setNodeActiveByName(node.name, source);
	}
	function setNodeActiveByName(name, source) {
		ndvStore.setActiveNodeName(name, source);
	}
	function clearNodeActive() {
		ndvStore.unsetActiveNodeName();
	}
	function setNodeParameters(id, parameters) {
		const node = workflowsStore.getNodeById(id);
		if (!node) return;
		workflowState.setNodeParameters({
			name: node.name,
			value: parameters
		}, true);
	}
	function setNodeSelected(id) {
		if (!id) {
			uiStore.lastInteractedWithNodeId = void 0;
			uiStore.lastSelectedNode = "";
			return;
		}
		const node = workflowsStore.getNodeById(id);
		if (!node) return;
		uiStore.lastInteractedWithNodeId = id;
		uiStore.lastSelectedNode = node.name;
	}
	function toggleNodesDisabled(ids, { trackHistory = true, trackBulk = true } = {}) {
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		const nodes = workflowsStore.getNodesByIds(ids);
		nodeHelpers.disableNodes(nodes, {
			trackHistory,
			trackBulk: false
		});
		if (trackHistory && trackBulk) historyStore.stopRecordingUndo();
	}
	function revertToggleNodeDisabled(nodeName) {
		const node = workflowsStore.getNodeByName(nodeName);
		if (node) nodeHelpers.disableNodes([node]);
	}
	function toggleNodesPinned(ids, source, { trackHistory = true, trackBulk = true } = {}) {
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		const pinnableNodes = workflowsStore.getNodesByIds(ids).filter((node) => {
			return usePinnedData(node).canPinNode(true);
		});
		const nextStatePinned = pinnableNodes.some((node) => !workflowsStore.pinDataByNodeName(node.name));
		for (const node of pinnableNodes) {
			const pinnedDataForNode = usePinnedData(node);
			if (nextStatePinned) {
				const dataToPin = useDataSchema().getInputDataWithPinned(node);
				if (dataToPin.length !== 0) pinnedDataForNode.setData(dataToPin, source);
			} else pinnedDataForNode.unsetData(source);
		}
		if (trackHistory && trackBulk) historyStore.stopRecordingUndo();
	}
	function requireNodeTypeDescription(type, version) {
		return nodeTypesStore.getNodeType(type, version) ?? nodeTypesStore.communityNodeType(type)?.nodeDescription ?? {
			properties: [],
			displayName: type,
			name: type,
			group: [],
			description: "",
			version: version ?? 1,
			defaults: {},
			inputs: [],
			outputs: []
		};
	}
	async function addNodes(nodes, { viewport,...options } = {}) {
		let insertPosition = options.position;
		let lastAddedNode;
		const addedNodes = [];
		const nodesWithTypeVersion = nodes.map((node) => {
			const typeVersion = node.typeVersion ?? resolveNodeVersion(requireNodeTypeDescription(node.type));
			return {
				...node,
				typeVersion
			};
		});
		await loadNodeTypesProperties(nodesWithTypeVersion);
		if (options.trackHistory && options.trackBulk) historyStore.startRecordingUndo();
		for (const [index, nodeAddData] of nodesWithTypeVersion.entries()) {
			const { isAutoAdd, openDetail: openNDV, actionName, positionOffset,...node } = nodeAddData;
			const rawPosition = node.position ?? insertPosition;
			const position = rawPosition && positionOffset ? [rawPosition[0] + positionOffset[0], rawPosition[1] + positionOffset[1]] : rawPosition;
			const nodeTypeDescription = requireNodeTypeDescription(node.type, node.typeVersion);
			try {
				const newNode = addNode({
					...node,
					position
				}, nodeTypeDescription, {
					...options,
					...index === 0 ? { viewport } : {},
					openNDV,
					isAutoAdd,
					actionName
				});
				lastAddedNode = newNode;
				addedNodes.push(newNode);
			} catch (error) {
				toast.showError(error, i18n.baseText("error"));
				console.error(error);
				continue;
			}
			insertPosition = [lastAddedNode.position[0] + DEFAULT_NODE_SIZE[0] * 2 + 16, lastAddedNode.position[1]];
		}
		if (lastAddedNode) updatePositionForNodeWithMultipleInputs(lastAddedNode);
		if (options.trackHistory && options.trackBulk) historyStore.stopRecordingUndo();
		if (!options.keepPristine) uiStore.stateIsDirty = true;
		return addedNodes;
	}
	function updatePositionForNodeWithMultipleInputs(node) {
		const inputNodes = editableWorkflowObject.value.getParentNodesByDepth(node.name, 1);
		if (inputNodes.length > 1) inputNodes.slice(1).forEach((inputNode, index) => {
			const nodeUi = workflowsStore.getNodeByName(inputNode.name);
			if (!nodeUi) return;
			updateNodePosition(nodeUi.id, {
				x: nodeUi.position[0],
				y: nodeUi.position[1] + 100 * (index + 1)
			});
		});
	}
	function checkMaxNodesOfTypeReached(nodeTypeDescription) {
		if (nodeTypeDescription.maxNodes !== void 0 && workflowHelpers.getNodeTypeCount(nodeTypeDescription.name) >= nodeTypeDescription.maxNodes) throw new Error(i18n.baseText("nodeView.showMessage.showMaxNodeTypeError.message", {
			adjustToNumber: nodeTypeDescription.maxNodes,
			interpolate: { nodeTypeDataDisplayName: nodeTypeDescription.displayName }
		}));
	}
	function addNode(node, nodeTypeDescription, options = {}) {
		checkMaxNodesOfTypeReached(nodeTypeDescription);
		const nodeData = resolveNodeData(node, nodeTypeDescription, { viewport: options.viewport });
		if (!nodeData) throw new Error(i18n.baseText("nodeViewV2.showError.failedToCreateNode"));
		workflowsStore.addNode(nodeData);
		if (options.trackHistory) historyStore.pushCommandToUndo(new AddNodeCommand(nodeData, Date.now()));
		if (!options.isAutoAdd) createConnectionToLastInteractedWithNode(nodeData, options);
		nextTick(() => {
			if (!options.keepPristine) uiStore.stateIsDirty = true;
			workflowsStore.setNodePristine(nodeData.name, true);
			nodeHelpers.matchCredentials(nodeData);
			nodeHelpers.updateNodeParameterIssues(nodeData);
			nodeHelpers.updateNodeCredentialIssues(nodeData);
			nodeHelpers.updateNodeInputIssues(nodeData);
			const isStickyNode = nodeData.type === STICKY_NODE_TYPE;
			const nextView = isStickyNode || !options.openNDV || preventOpeningNDV ? void 0 : experimentalNdvStore.isNdvInFocusPanelEnabled && focusPanelStore.focusPanelActive && focusPanelStore.resolvedParameter === void 0 ? "focus_panel" : experimentalNdvStore.isZoomedViewEnabled ? "zoomed_view" : "ndv";
			if (options.telemetry) trackAddNode(nodeData, options, nextView);
			if (!isStickyNode) {
				externalHooks.run("nodeView.addNodeButton", { nodeTypeName: nodeData.type });
				if (nextView === "focus_panel") {} else if (nextView === "zoomed_view") experimentalNdvStore.setNodeNameToBeFocused(nodeData.name);
				else if (nextView === "ndv") ndvStore.setActiveNodeName(nodeData.name, "added_new_node");
			}
		});
		return nodeData;
	}
	async function revertAddNode(nodeName) {
		const node = workflowsStore.getNodeByName(nodeName);
		if (!node) return;
		deleteNode(node.id);
	}
	function createConnectionToLastInteractedWithNode(node, options = {}) {
		const lastInteractedWithNode = uiStore.lastInteractedWithNode;
		if (!lastInteractedWithNode) return;
		const lastInteractedWithNodeId = lastInteractedWithNode.id;
		const lastInteractedWithNodeConnection = uiStore.lastInteractedWithNodeConnection;
		const lastInteractedWithNodeHandle = uiStore.lastInteractedWithNodeHandle;
		if (lastInteractedWithNodeHandle) {
			const { type: connectionType, mode } = parseCanvasConnectionHandleString(lastInteractedWithNodeHandle);
			const nodeId = node.id;
			const nodeHandle = createCanvasConnectionHandleString({
				mode: CanvasConnectionMode.Input,
				type: connectionType,
				index: 0
			});
			if (mode === CanvasConnectionMode.Input) createConnection({
				source: nodeId,
				sourceHandle: nodeHandle,
				target: lastInteractedWithNodeId,
				targetHandle: lastInteractedWithNodeHandle
			});
			else createConnection({
				source: lastInteractedWithNodeId,
				sourceHandle: lastInteractedWithNodeHandle,
				target: nodeId,
				targetHandle: nodeHandle
			});
		} else createConnection({
			source: lastInteractedWithNodeId,
			sourceHandle: createCanvasConnectionHandleString({
				mode: CanvasConnectionMode.Output,
				type: NodeConnectionTypes.Main,
				index: 0
			}),
			target: node.id,
			targetHandle: createCanvasConnectionHandleString({
				mode: CanvasConnectionMode.Input,
				type: NodeConnectionTypes.Main,
				index: 0
			})
		});
		if (lastInteractedWithNodeConnection) {
			deleteConnection(lastInteractedWithNodeConnection, { trackHistory: options.trackHistory });
			if (workflowsStore.getNodeById(lastInteractedWithNodeConnection.target)) createConnection({
				source: node.id,
				sourceHandle: createCanvasConnectionHandleString({
					mode: CanvasConnectionMode.Input,
					type: NodeConnectionTypes.Main,
					index: 0
				}),
				target: lastInteractedWithNodeConnection.target,
				targetHandle: lastInteractedWithNodeConnection.targetHandle
			});
		}
	}
	function trackAddNode(nodeData, options, nextView) {
		switch (nodeData.type) {
			case STICKY_NODE_TYPE:
				trackAddStickyNoteNode();
				break;
			default: trackAddDefaultNode(nodeData, options, nextView);
		}
	}
	function trackAddStickyNoteNode() {
		telemetry.track("User inserted workflow note", { workflow_id: workflowsStore.workflowId });
	}
	function trackAddDefaultNode(nodeData, options, nextView) {
		const nodeParameters = nodeData.parameters;
		const resource = typeof nodeParameters?.resource === "string" ? nodeParameters.resource : void 0;
		const operation = typeof nodeParameters?.operation === "string" ? nodeParameters.operation : void 0;
		nodeCreatorStore.onNodeAddedToCanvas({
			node_id: nodeData.id,
			node_type: nodeData.type,
			node_version: nodeData.typeVersion,
			is_auto_add: options.isAutoAdd,
			workflow_id: workflowsStore.workflowId,
			drag_and_drop: options.dragAndDrop,
			input_node_type: uiStore.lastInteractedWithNode ? uiStore.lastInteractedWithNode.type : void 0,
			resource,
			operation,
			action: options.actionName,
			next_view_shown: nextView
		});
	}
	function resolveNodeData(node, nodeTypeDescription, options = {}) {
		const id = node.id ?? nodeHelpers.assignNodeId(node);
		const name = node.name ?? nodeHelpers.getDefaultNodeName(node) ?? nodeTypeDescription.defaults.name;
		const type = nodeTypeDescription.name;
		const typeVersion = node.typeVersion;
		const position = options.forcePosition && node.position ? node.position : resolveNodePosition(node, nodeTypeDescription, { viewport: options.viewport });
		const disabled = node.disabled ?? false;
		const parameters = node.parameters ?? {};
		const nodeData = {
			...node,
			id,
			name,
			type,
			typeVersion,
			position,
			disabled,
			parameters
		};
		resolveNodeName(nodeData);
		resolveNodeParameters(nodeData, nodeTypeDescription);
		resolveNodeWebhook(nodeData, nodeTypeDescription);
		return nodeData;
	}
	async function loadNodeTypesProperties(nodes) {
		const allNodeTypeDescriptions = nodeTypesStore.allNodeTypes;
		const nodesToBeFetched = [];
		allNodeTypeDescriptions.forEach((nodeTypeDescription) => {
			const nodeVersions = Array.isArray(nodeTypeDescription.version) ? nodeTypeDescription.version : [nodeTypeDescription.version];
			if (!!nodes.find((n) => n.type === nodeTypeDescription.name && nodeVersions.includes(n.typeVersion)) && !nodeTypeDescription.hasOwnProperty("properties")) nodesToBeFetched.push({
				name: nodeTypeDescription.name,
				version: Array.isArray(nodeTypeDescription.version) ? nodeTypeDescription.version.slice(-1)[0] : nodeTypeDescription.version
			});
		});
		if (nodesToBeFetched.length > 0) await nodeTypesStore.getNodesInformation(nodesToBeFetched);
	}
	function resolveNodeVersion(nodeTypeDescription) {
		let nodeVersion = nodeTypeDescription.defaultVersion;
		if (typeof nodeVersion === "undefined") nodeVersion = Array.isArray(nodeTypeDescription.version) ? nodeTypeDescription.version.slice(-1)[0] : nodeTypeDescription.version;
		return nodeVersion;
	}
	function resolveNodeParameters(node, nodeTypeDescription) {
		node.parameters = getNodeParameters(nodeTypeDescription?.properties ?? [], node.parameters, true, false, node, nodeTypeDescription) ?? {};
	}
	function resolveNodePosition(node, nodeTypeDescription, options = {}) {
		const lastInteractedWithNode = uiStore.lastInteractedWithNode;
		const lastInteractedWithNodeConnection = uiStore.lastInteractedWithNodeConnection;
		const lastInteractedWithNodeHandle = uiStore.lastInteractedWithNodeHandle;
		const { type: connectionType, index: connectionIndex } = parseCanvasConnectionHandleString(lastInteractedWithNodeHandle ?? lastInteractedWithNodeConnection?.sourceHandle ?? "");
		const nodeSize = connectionType === NodeConnectionTypes.Main ? DEFAULT_NODE_SIZE : CONFIGURATION_NODE_SIZE;
		const pushOffsets = [nodeSize[0] / 2, nodeSize[1] / 2];
		let position = node.position;
		if (position) return getNewNodePosition(workflowsStore.allNodes, position, {
			offset: pushOffsets,
			size: nodeSize,
			viewport: options.viewport,
			normalize: false
		});
		if (lastInteractedWithNode) {
			const lastInteractedWithNodeTypeDescription = nodeTypesStore.getNodeType(lastInteractedWithNode.type, lastInteractedWithNode.typeVersion);
			const lastInteractedWithNodeObject = editableWorkflowObject.value.getNode(lastInteractedWithNode.name);
			const newNodeInsertPosition = uiStore.lastCancelledConnectionPosition;
			if (newNodeInsertPosition) {
				const xOffset = connectionType === NodeConnectionTypes.Main ? 0 : -nodeSize[0] / 2;
				const yOffset = connectionType === NodeConnectionTypes.Main ? -nodeSize[1] / 2 : 0;
				position = [newNodeInsertPosition[0] + xOffset, newNodeInsertPosition[1] + yOffset];
				uiStore.lastCancelledConnectionPosition = void 0;
			} else if (lastInteractedWithNodeTypeDescription && lastInteractedWithNodeObject) {
				const lastInteractedWithNodeInputTypes = getConnectionTypes(getNodeInputs(editableWorkflowObject.value, lastInteractedWithNodeObject, lastInteractedWithNodeTypeDescription));
				const lastInteractedWithNodeScopedInputTypes = (lastInteractedWithNodeInputTypes || []).filter((input) => input !== NodeConnectionTypes.Main);
				const lastInteractedWithNodeMainOutputs = getConnectionTypes(getNodeOutputs(editableWorkflowObject.value, lastInteractedWithNodeObject, lastInteractedWithNodeTypeDescription)).filter((output) => output === NodeConnectionTypes.Main);
				let yOffset = 0;
				if (lastInteractedWithNodeConnection) {
					let isNewNodeConfigurable = false;
					if (typeof nodeTypeDescription.inputs === "string") isNewNodeConfigurable = true;
					else if (Array.isArray(nodeTypeDescription.inputs)) isNewNodeConfigurable = getConnectionTypes(nodeTypeDescription.inputs).filter((input) => input !== NodeConnectionTypes.Main).length > 0;
					const newNodeSize = isNewNodeConfigurable ? CONFIGURABLE_NODE_SIZE : DEFAULT_NODE_SIZE;
					const shiftMargin = PUSH_NODES_OFFSET + (isNewNodeConfigurable ? CONFIGURABLE_NODE_SIZE[0] - DEFAULT_NODE_SIZE[0] : 0);
					shiftDownstreamNodesPosition(lastInteractedWithNode.name, shiftMargin, {
						trackHistory: true,
						nodeSize: newNodeSize
					});
				}
				if (lastInteractedWithNodeMainOutputs.length > 1) yOffset = generateOffsets(lastInteractedWithNodeMainOutputs.length, DEFAULT_NODE_SIZE[1], 16)[connectionIndex];
				let outputs = [];
				try {
					outputs = getNodeOutputs(editableWorkflowObject.value, node, nodeTypeDescription);
				} catch (e) {}
				const outputTypes = getConnectionTypes(outputs);
				const customOffset = {
					[NodeConnectionTypes.AiLanguageModel]: nodeSize[0] * 2,
					[NodeConnectionTypes.AiMemory]: nodeSize[0]
				}[connectionType] ?? 0;
				if (outputTypes.length > 0 && outputTypes.every((outputName) => outputName !== NodeConnectionTypes.Main)) {
					const scopedConnectionIndex = lastInteractedWithNodeScopedInputTypes.findIndex((inputType) => outputs[0] === inputType);
					const lastInteractedWithNodeWidthDivisions = Math.max(lastInteractedWithNodeScopedInputTypes.length + 1, 1);
					position = [lastInteractedWithNode.position[0] + CONFIGURABLE_NODE_SIZE[0] / lastInteractedWithNodeWidthDivisions * (scopedConnectionIndex + 1) - nodeSize[0] / 2 - customOffset, lastInteractedWithNode.position[1] + PUSH_NODES_OFFSET];
				} else {
					let pushOffset = PUSH_NODES_OFFSET;
					if (lastInteractedWithNodeInputTypes.find((input) => input !== NodeConnectionTypes.Main)) pushOffset += 140;
					position = [lastInteractedWithNode.position[0] + pushOffset, lastInteractedWithNode.position[1] + yOffset];
					if (lastInteractedWithNodeConnection) {
						const adjustedY = getYPositionForStickyOverlap(position, nodeSize);
						if (adjustedY !== null) position = [position[0], adjustedY];
					}
				}
			}
		}
		if (!position) if (nodeTypesStore.isTriggerNode(node.type) && triggerNodes.value.length === 0) position = [0, 0];
		else position = lastClickPosition.value;
		return getNewNodePosition(workflowsStore.allNodes, position, {
			offset: pushOffsets,
			size: nodeSize,
			viewport: options.viewport
		});
	}
	function resolveNodeName(node) {
		node.name = uniqueNodeName(i18n.localizeNodeName(rootStore.defaultLocale, node.name, node.type));
	}
	function resolveNodeWebhook(node, nodeTypeDescription) {
		if (nodeTypeDescription.webhooks?.length && !node.webhookId) nodeHelpers.assignWebhookId(node);
		if ([
			"n8n-nodes-base.webhook",
			"n8n-nodes-base.formTrigger",
			"@n8n/n8n-nodes-langchain.mcpTrigger"
		].includes(node.type) && node.parameters.path === "") node.parameters.path = node.webhookId;
	}
	function getNodeRect(node) {
		if (node.type === "n8n-nodes-base.stickyNote") return {
			x: node.position[0],
			y: node.position[1],
			width: node.parameters.width || DEFAULT_NODE_SIZE[0],
			height: node.parameters.height || DEFAULT_NODE_SIZE[1]
		};
		return {
			x: node.position[0],
			y: node.position[1],
			width: DEFAULT_NODE_SIZE[0],
			height: DEFAULT_NODE_SIZE[1]
		};
	}
	function hasSpaceForInsertion(insertPosition, nodeSize, nodesToCheck) {
		const insertRect = {
			x: insertPosition[0],
			y: insertPosition[1],
			width: nodeSize[0] + 16,
			height: nodeSize[1]
		};
		for (const node of nodesToCheck) {
			if (node.type === "n8n-nodes-base.stickyNote") continue;
			if (doRectsOverlap(insertRect, getNodeRect(node))) return false;
		}
		return true;
	}
	function getNodesToShift(insertPosition, sourceNodeName) {
		const allNodes = Object.values(workflowsStore.nodesByName);
		const insertX = insertPosition[0];
		const insertY = insertPosition[1];
		const yTolerance = DEFAULT_NODE_SIZE[1] * 2;
		const initialCandidates = allNodes.filter((node) => {
			if (node.type === "n8n-nodes-base.stickyNote") return false;
			if (node.name === sourceNodeName) return false;
			const isSimilarY = Math.abs(node.position[1] - insertY) <= yTolerance;
			const overlapsOrIsToTheRight = node.position[0] + DEFAULT_NODE_SIZE[0] > insertX || node.position[0] >= insertX;
			return isSimilarY && overlapsOrIsToTheRight;
		});
		const candidateNames = new Set(initialCandidates.map((n) => n.name));
		for (const candidate of initialCandidates) workflowHelpers.getConnectedNodes("downstream", editableWorkflowObject.value, candidate.name).forEach((name) => candidateNames.add(name));
		const regularNodesToMove = allNodes.filter((node) => {
			if (node.type === "n8n-nodes-base.stickyNote") return false;
			return candidateNames.has(node.name);
		});
		const stickiesToStretch = [];
		const stickiesToMove = [];
		const stickyNodes = allNodes.filter((node) => node.type === STICKY_NODE_TYPE);
		const affectedMinY = Math.min(insertY, ...regularNodesToMove.map((n) => n.position[1]));
		const affectedMaxY = Math.max(insertY + DEFAULT_NODE_SIZE[1], ...regularNodesToMove.map((n) => n.position[1] + DEFAULT_NODE_SIZE[1]));
		for (const sticky of stickyNodes) {
			const stickyRect = getNodeRect(sticky);
			const stickyLeftEdge = sticky.position[0];
			const stickyRightEdge = stickyLeftEdge + stickyRect.width;
			const stickyTop = sticky.position[1];
			const overlapsVertically = !(stickyTop + stickyRect.height <= affectedMinY || stickyTop >= affectedMaxY);
			if (stickyLeftEdge >= insertX && overlapsVertically) stickiesToMove.push(sticky);
			else if (stickyLeftEdge < insertX && stickyRightEdge >= insertX && overlapsVertically) stickiesToStretch.push(sticky);
		}
		return {
			nodesToMove: [...regularNodesToMove, ...stickiesToMove],
			stickiesToStretch
		};
	}
	function getYPositionForStickyOverlap(insertPosition, nodeSize) {
		const allNodes = Object.values(workflowsStore.nodesByName);
		const stickyNodes = allNodes.filter((node) => node.type === STICKY_NODE_TYPE);
		const regularNodes = allNodes.filter((node) => node.type !== STICKY_NODE_TYPE);
		const insertRect = {
			x: insertPosition[0],
			y: insertPosition[1],
			width: nodeSize[0] + 16,
			height: nodeSize[1]
		};
		for (const node of regularNodes) if (doRectsOverlap(insertRect, getNodeRect(node))) return null;
		for (const sticky of stickyNodes) {
			const stickyRect = getNodeRect(sticky);
			if (doRectsOverlap(insertRect, stickyRect)) {
				const stickyContentTop = sticky.position[1] + 40;
				const centeredY = stickyContentTop + (sticky.position[1] + stickyRect.height - 16 - stickyContentTop - nodeSize[1]) / 2;
				return Math.round(centeredY / 16) * 16;
			}
		}
		return null;
	}
	function stretchStickyNote(sticky, stretchAmount, { trackHistory = false }) {
		const newWidth = (sticky.parameters.width || DEFAULT_NODE_SIZE[0]) + stretchAmount;
		const newParameters = {
			...sticky.parameters,
			width: newWidth
		};
		replaceNodeParameters(sticky.id, sticky.parameters, newParameters, {
			trackHistory,
			trackBulk: false
		});
	}
	function shiftDownstreamNodesPosition(sourceNodeName, margin, { trackHistory = false, nodeSize = DEFAULT_NODE_SIZE }) {
		const sourceNode = workflowsStore.nodesByName[sourceNodeName];
		if (!sourceNode) return;
		const insertPosition = [sourceNode.position[0] + PUSH_NODES_OFFSET, sourceNode.position[1]];
		if (hasSpaceForInsertion(insertPosition, nodeSize, Object.values(workflowsStore.nodesByName).filter((n) => n.name !== sourceNodeName && n.type !== "n8n-nodes-base.stickyNote"))) return;
		const { nodesToMove, stickiesToStretch } = getNodesToShift(insertPosition, sourceNodeName);
		for (const node of nodesToMove) updateNodePosition(node.id, {
			x: node.position[0] + margin,
			y: node.position[1]
		}, { trackHistory });
		for (const sticky of stickiesToStretch) stretchStickyNote(sticky, margin, { trackHistory });
	}
	function createConnection(connection, { trackHistory = false, keepPristine = false } = {}) {
		const sourceNode = workflowsStore.getNodeById(connection.source);
		const targetNode = workflowsStore.getNodeById(connection.target);
		if (!sourceNode || !targetNode) return;
		if (trackHistory) historyStore.pushCommandToUndo(new AddConnectionCommand(mapCanvasConnectionToLegacyConnection(sourceNode, targetNode, connection), Date.now()));
		const mappedConnection = mapCanvasConnectionToLegacyConnection(sourceNode, targetNode, connection);
		if (!isConnectionAllowed(sourceNode, targetNode, mappedConnection[0], mappedConnection[1])) return;
		workflowsStore.addConnection({ connection: mappedConnection });
		nextTick(() => {
			nodeHelpers.updateNodeInputIssues(sourceNode);
			nodeHelpers.updateNodeInputIssues(targetNode);
		});
		if (!keepPristine) uiStore.stateIsDirty = true;
	}
	function revertCreateConnection(connection) {
		const sourceNodeName = connection[0].node;
		const sourceNode = workflowsStore.getNodeByName(sourceNodeName);
		const targetNodeName = connection[1].node;
		const targetNode = workflowsStore.getNodeByName(targetNodeName);
		if (!sourceNode || !targetNode) return;
		deleteConnection(mapLegacyConnectionToCanvasConnection(sourceNode, targetNode, connection));
	}
	function deleteConnectionsByNodeId(targetNodeId, { trackHistory = false, trackBulk = true } = {}) {
		const targetNode = workflowsStore.getNodeById(targetNodeId);
		if (!targetNode) return;
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		const connections = (0, import_cloneDeep.default)(workflowsStore.workflow.connections);
		for (const nodeName of Object.keys(connections)) {
			const node = workflowsStore.getNodeByName(nodeName);
			if (!node) continue;
			for (const type of Object.keys(connections[nodeName])) for (const index of Object.keys(connections[nodeName][type])) {
				const connectionsToDelete = connections[nodeName][type][parseInt(index, 10)] ?? [];
				for (const connectionIndex of Object.keys(connectionsToDelete)) {
					const connectionData = connectionsToDelete[parseInt(connectionIndex, 10)];
					if (!connectionData) continue;
					const connectionDataNode = workflowsStore.getNodeByName(connectionData.node);
					if (connectionDataNode && (connectionDataNode.id === targetNode.id || node.name === targetNode.name)) deleteConnection({
						source: node.id,
						sourceHandle: createCanvasConnectionHandleString({
							mode: CanvasConnectionMode.Output,
							type,
							index: parseInt(index, 10)
						}),
						target: connectionDataNode.id,
						targetHandle: createCanvasConnectionHandleString({
							mode: CanvasConnectionMode.Input,
							type: connectionData.type,
							index: connectionData.index
						})
					}, {
						trackHistory,
						trackBulk: false
					});
				}
			}
		}
		delete workflowsStore.workflow.connections[targetNode.name];
		if (trackHistory && trackBulk) historyStore.stopRecordingUndo();
	}
	function deleteConnection(connection, { trackHistory = false, trackBulk = true } = {}) {
		const sourceNode = workflowsStore.getNodeById(connection.source);
		const targetNode = workflowsStore.getNodeById(connection.target);
		if (!sourceNode || !targetNode) return;
		const mappedConnection = mapCanvasConnectionToLegacyConnection(sourceNode, targetNode, connection);
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		workflowsStore.removeConnection({ connection: mappedConnection });
		if (trackHistory) {
			historyStore.pushCommandToUndo(new RemoveConnectionCommand(mappedConnection, Date.now()));
			if (trackBulk) historyStore.stopRecordingUndo();
		}
	}
	function revertDeleteConnection(connection) {
		workflowsStore.addConnection({ connection });
	}
	function revalidateNodeConnections(id, connectionMode) {
		const node = workflowsStore.getNodeById(id);
		const isInput = connectionMode === CanvasConnectionMode.Input;
		if (!node) return;
		if (!nodeTypesStore.getNodeType(node.type, node.typeVersion)) return;
		mapLegacyConnectionsToCanvasConnections(workflowsStore.workflow.connections, workflowsStore.workflow.nodes).forEach((connection) => {
			if (isInput ? connection.target === id : connection.source === id) {
				const otherNodeId = isInput ? connection.source : connection.target;
				const otherNode = workflowsStore.getNodeById(otherNodeId);
				if (!otherNode || !connection.data) return;
				const [firstNode, secondNode] = isInput ? [otherNode, node] : [node, otherNode];
				if (!isConnectionAllowed(firstNode, secondNode, connection.data.source, connection.data.target)) nextTick(() => deleteConnection(connection));
			}
		});
	}
	function revalidateNodeInputConnections(id) {
		return revalidateNodeConnections(id, CanvasConnectionMode.Input);
	}
	function revalidateNodeOutputConnections(id) {
		return revalidateNodeConnections(id, CanvasConnectionMode.Output);
	}
	function isConnectionAllowed(sourceNode, targetNode, sourceConnection, targetConnection) {
		const blocklist = [STICKY_NODE_TYPE];
		const checkIsNotInstalledCommunityNode = (node) => isCommunityPackageName(node.type) && !useNodeTypesStore().getIsNodeInstalled(node.type);
		const isSourceNotInstalled = checkIsNotInstalledCommunityNode(sourceNode);
		const isTargetNotInstalled = checkIsNotInstalledCommunityNode(targetNode);
		const getNodeType = (node) => {
			return nodeTypesStore.getNodeType(node.type, node.typeVersion) ?? nodeTypesStore.communityNodeType(node.type)?.nodeDescription;
		};
		const filterConnectionsByType = (connections, type) => connections.filter((connection) => {
			return (typeof connection === "string" ? connection : connection.type) === type;
		});
		const getInputFilter = (connection) => {
			if (connection && typeof connection === "object" && "filter" in connection) return connection.filter;
		};
		if (sourceConnection.type !== targetConnection.type) return false;
		if (blocklist.includes(sourceNode.type) || blocklist.includes(targetNode.type)) return false;
		const sourceNodeType = getNodeType(sourceNode);
		const sourceWorkflowNode = editableWorkflowObject.value.getNode(sourceNode.name);
		if (!sourceWorkflowNode) return false;
		let sourceNodeOutputs = [];
		if (sourceNodeType) sourceNodeOutputs = getNodeOutputs(editableWorkflowObject.value, sourceWorkflowNode, sourceNodeType) || [];
		const sourceOutputsOfType = filterConnectionsByType(sourceNodeOutputs, sourceConnection.type);
		const sourceNodeHasOutputConnectionOfType = sourceOutputsOfType.length > 0;
		const sourceNodeHasOutputConnectionPortOfType = sourceConnection.index < sourceOutputsOfType.length;
		if ((!sourceNodeHasOutputConnectionOfType || !sourceNodeHasOutputConnectionPortOfType) && !isSourceNotInstalled) return false;
		const targetNodeType = getNodeType(targetNode);
		const targetWorkflowNode = editableWorkflowObject.value.getNode(targetNode.name);
		if (!targetWorkflowNode) return false;
		let targetNodeInputs = [];
		if (targetNodeType) targetNodeInputs = getNodeInputs(editableWorkflowObject.value, targetWorkflowNode, targetNodeType) || [];
		const targetInputsOfType = filterConnectionsByType(targetNodeInputs, targetConnection.type);
		const targetNodeHasInputConnectionOfType = targetInputsOfType.length > 0;
		const targetNodeHasInputConnectionPortOfType = targetConnection.index < targetInputsOfType.length;
		const targetConnectionFilter = getInputFilter(targetNodeHasInputConnectionPortOfType ? targetInputsOfType[targetConnection.index] : void 0);
		if (targetConnectionFilter?.nodes?.length && !targetConnectionFilter.nodes.includes(sourceNode.type) || targetConnectionFilter?.excludedNodes?.length && targetConnectionFilter.excludedNodes.includes(sourceNode.type)) {
			toast.showToast({
				title: i18n.baseText("nodeView.showError.nodeNodeCompatible.title"),
				message: i18n.baseText("nodeView.showError.nodeNodeCompatible.message", { interpolate: {
					sourceNodeName: sourceNode.name,
					targetNodeName: targetNode.name
				} }),
				type: "error",
				duration: 5e3
			});
			return false;
		}
		if ((!targetNodeHasInputConnectionOfType || !targetNodeHasInputConnectionPortOfType) && !isTargetNotInstalled) return false;
		return true;
	}
	async function addConnections(connections, { trackBulk = true, trackHistory = false, keepPristine = false } = {}) {
		await nextTick();
		if (trackBulk && trackHistory) historyStore.startRecordingUndo();
		for (const connection of connections) createConnection(connection, {
			trackHistory,
			keepPristine
		});
		if (trackBulk && trackHistory) historyStore.stopRecordingUndo();
		if (!keepPristine) uiStore.stateIsDirty = true;
	}
	function resetWorkspace() {
		nodeCreatorStore.setNodeCreatorState({ createNodeActive: false });
		nodeCreatorStore.setShowScrim(false);
		if (workflowsStore.executionWaitingForWebhook) try {
			workflowsStore.removeTestWebhook(workflowsStore.workflowId);
		} catch (error) {}
		workflowsStore.resetWorkflow();
		workflowState.resetState();
		workflowsStore.currentWorkflowExecutions = [];
		workflowState.setActiveExecutionId(void 0);
		workflowsStore.lastSuccessfulExecution = null;
		uiStore.resetLastInteractedWith();
		uiStore.stateIsDirty = false;
		executionsStore.activeExecution = null;
		nodeHelpers.credentialsUpdated.value = false;
	}
	async function initializeWorkspace(data) {
		await workflowHelpers.initState(data, useWorkflowState());
		data.nodes.forEach((node) => {
			const nodeTypeDescription = requireNodeTypeDescription(node.type, node.typeVersion);
			const isUnknownNode = !nodeTypesStore.getNodeType(node.type, node.typeVersion) && !nodeTypesStore.communityNodeType(node.type)?.nodeDescription;
			nodeHelpers.matchCredentials(node);
			if (!isUnknownNode) {
				resolveNodeParameters(node, nodeTypeDescription);
				resolveNodeWebhook(node, nodeTypeDescription);
			}
		});
		workflowsStore.setNodes(data.nodes);
		workflowsStore.setConnections(data.connections);
		workflowState.setWorkflowProperty("createdAt", data.createdAt);
		workflowState.setWorkflowProperty("updatedAt", data.updatedAt);
	}
	const initializeUnknownNodes = (nodes) => {
		nodes.forEach((node) => {
			const nodeTypeDescription = requireNodeTypeDescription(node.type, node.typeVersion);
			nodeHelpers.matchCredentials(node);
			resolveNodeParameters(node, nodeTypeDescription);
			resolveNodeWebhook(node, nodeTypeDescription);
			const nodeIndex = workflowsStore.workflow.nodes.findIndex((n) => {
				return n.name === node.name;
			});
			workflowState.updateNodeAtIndex(nodeIndex, node);
		});
	};
	function removeUnknownCredentials(workflow) {
		if (!workflow?.nodes) return;
		for (const node of workflow.nodes) {
			if (!node.credentials) continue;
			for (const [name, credential] of Object.entries(node.credentials)) {
				if (typeof credential === "string" || credential.id === null) continue;
				if (!credentialsStore.getCredentialById(credential.id)) delete node.credentials[name];
			}
		}
	}
	async function addImportedNodesToWorkflow(data, { trackBulk = true, trackHistory = false, viewport = DEFAULT_VIEWPORT_BOUNDARIES, setStateDirty = true } = {}) {
		const nodeNameTable = {};
		const newNodeNames = new Set((data.nodes ?? []).map((node) => node.name));
		if (!data.nodes) throw new Error(i18n.baseText("nodeView.noNodesGivenToAdd"));
		const nodeTypesCount = workflowHelpers.getNodeTypesMaxCount();
		let oldName;
		let newName;
		const createNodes = [];
		await nodeHelpers.loadNodesProperties(data.nodes.map((node) => ({
			name: node.type,
			version: node.typeVersion
		})));
		data.nodes.forEach((node) => {
			if (nodeTypesCount[node.type] !== void 0) if (nodeTypesCount[node.type].exist >= nodeTypesCount[node.type].max) {
				nodeNameTable[node.name] = nodeTypesCount[node.type].nodeNames[0];
				return;
			} else nodeTypesCount[node.type].exist += 1;
			oldName = node.name;
			const localized = i18n.localizeNodeName(rootStore.defaultLocale, node.name, node.type);
			newNodeNames.delete(oldName);
			newName = uniqueNodeName(localized, Array.from(newNodeNames));
			newNodeNames.add(newName);
			nodeNameTable[oldName] = newName;
			createNodes.push(node);
		});
		const newConnections = {};
		const currentConnections = data.connections ?? {};
		const createNodeNames = createNodes.map((node) => node.name);
		let sourceNode, type, sourceIndex, connectionIndex, connectionData;
		for (sourceNode of Object.keys(currentConnections)) {
			if (!createNodeNames.includes(sourceNode)) continue;
			const connection = {};
			for (type of Object.keys(currentConnections[sourceNode])) {
				connection[type] = [];
				for (sourceIndex = 0; sourceIndex < currentConnections[sourceNode][type].length; sourceIndex++) {
					const nodeSourceConnections = [];
					const connectionsToCheck = currentConnections[sourceNode][type][sourceIndex];
					if (connectionsToCheck) for (connectionIndex = 0; connectionIndex < connectionsToCheck.length; connectionIndex++) {
						connectionData = connectionsToCheck[connectionIndex];
						if (!createNodeNames.includes(connectionData.node)) continue;
						nodeSourceConnections.push(connectionData);
					}
					connection[type].push(nodeSourceConnections);
				}
			}
			newConnections[sourceNode] = connection;
		}
		const tempWorkflow = workflowsStore.createWorkflowObject(createNodes, newConnections);
		for (oldName in nodeNameTable) {
			const nameToChangeTo = nodeNameTable[oldName];
			if (!nameToChangeTo || oldName === nameToChangeTo) continue;
			tempWorkflow.renameNode(oldName, nameToChangeTo);
		}
		if (data.pinData) {
			let pinDataSuccess = true;
			for (const nodeName of Object.keys(data.pinData)) {
				if (!pinDataSuccess) {
					toast.showError(new Error(i18n.baseText("ndv.pinData.error.tooLarge.description")), i18n.baseText("ndv.pinData.error.tooLarge.title"));
					continue;
				}
				const node = tempWorkflow.nodes[nodeNameTable[nodeName] ?? nodeName];
				if (node) try {
					usePinnedData(node).setData(data.pinData[nodeName], "add-nodes");
					pinDataSuccess = true;
				} catch (error) {
					pinDataSuccess = false;
					console.error(error);
				}
			}
		}
		if (trackBulk && trackHistory) historyStore.startRecordingUndo();
		await addNodes(Object.values(tempWorkflow.nodes), {
			trackBulk: false,
			trackHistory,
			viewport
		});
		await addConnections(mapLegacyConnectionsToCanvasConnections(tempWorkflow.connectionsBySourceNode, Object.values(tempWorkflow.nodes)), {
			trackBulk: false,
			trackHistory
		});
		if (trackBulk && trackHistory) historyStore.stopRecordingUndo();
		uiStore.stateIsDirty = setStateDirty;
		return {
			nodes: Object.values(tempWorkflow.nodes),
			connections: tempWorkflow.connectionsBySourceNode
		};
	}
	async function importWorkflowData(workflowData, source, { importTags = true, trackBulk = true, trackHistory = true, viewport, regenerateIds = true, trackEvents = true, setStateDirty = true } = {}) {
		uiStore.resetLastInteractedWith();
		if (!workflowData.hasOwnProperty("nodes") || !workflowData.hasOwnProperty("connections")) return {};
		try {
			const nodeIdMap = {};
			if (workflowData.nodes) {
				const nodeNames = new Set(workflowData.nodes.map((node) => node.name));
				workflowData.nodes.forEach((node) => {
					if (!node.name) {
						const newName = uniqueNodeName(nodeTypesStore.getNodeType(node.type)?.displayName ?? node.type, Array.from(nodeNames));
						node.name = newName;
						nodeNames.add(newName);
					}
					if (node.webhookId && UPDATE_WEBHOOK_ID_NODE_TYPES.includes(node.type)) {
						if (Object.values(workflowsStore.workflowObject.nodes).some((n) => n.webhookId === node.webhookId)) {
							nodeHelpers.assignWebhookId(node);
							if (node.parameters.path) node.parameters.path = node.webhookId;
							else if (node.parameters.options.path) node.parameters.options.path = node.webhookId;
						}
					}
					if (node.id) {
						const previousId = node.id;
						if (regenerateIds) {
							const newId = nodeHelpers.assignNodeId(node);
							nodeIdMap[newId] = previousId;
						}
					} else nodeHelpers.assignNodeId(node);
				});
			}
			removeUnknownCredentials(workflowData);
			try {
				if (trackEvents) {
					const nodeGraph = JSON.stringify(generateNodesGraph(workflowData, workflowHelpers.getNodeTypes(), {
						nodeIdMap,
						sourceInstanceId: workflowData.meta && workflowData.meta.instanceId !== rootStore.instanceId ? workflowData.meta.instanceId : "",
						isCloudDeployment: settingsStore.isCloudDeployment
					}).nodeGraph);
					if (source === "paste") telemetry.track("User pasted nodes", {
						workflow_id: workflowsStore.workflowId,
						node_graph_string: nodeGraph
					});
					else if (source === "duplicate") telemetry.track("User duplicated nodes", {
						workflow_id: workflowsStore.workflowId,
						node_graph_string: nodeGraph
					});
					else telemetry.track("User imported workflow", {
						source,
						workflow_id: workflowsStore.workflowId,
						node_graph_string: nodeGraph
					});
				}
			} catch {}
			workflowHelpers.updateNodePositions(workflowData, getNewNodePosition(editableWorkflow.value.nodes, lastClickPosition.value, {
				...workflowData.nodes && workflowData.nodes.length > 1 ? { size: getNodesGroupSize(workflowData.nodes) } : {},
				viewport
			}));
			await addImportedNodesToWorkflow(workflowData, {
				trackBulk,
				trackHistory,
				viewport,
				setStateDirty
			});
			if (importTags && settingsStore.areTagsEnabled && Array.isArray(workflowData.tags)) await importWorkflowTags(workflowData);
			if (workflowData.name) workflowState.setWorkflowName({
				newName: workflowData.name,
				setStateDirty
			});
			return workflowData;
		} catch (error) {
			console.error(error);
			toast.showError(error, i18n.baseText("nodeView.showError.importWorkflowData.title"));
			return {};
		}
	}
	async function importWorkflowTags(workflowData) {
		const allTags = await tagsStore.fetchAll();
		const tagNames = new Set(allTags.map((tag) => tag.name));
		const workflowTags = workflowData.tags;
		const notFound = workflowTags.filter((tag) => !tagNames.has(tag.name));
		const creatingTagPromises = [];
		for (const tag of notFound) {
			const creationPromise = tagsStore.create(tag.name).then((newTag) => {
				allTags.push(newTag);
				return newTag;
			});
			creatingTagPromises.push(creationPromise);
		}
		await Promise.all(creatingTagPromises);
		const tagIds = workflowTags.reduce((accu, imported) => {
			const tag = allTags.find((t) => t.name === imported.name);
			if (tag) accu.push(tag.id);
			return accu;
		}, []);
		workflowsStore.addWorkflowTagIds(tagIds);
	}
	async function fetchWorkflowDataFromUrl(url) {
		let workflowData;
		const projectId = projectsStore.currentProjectId ?? projectsStore.personalProject?.id;
		if (!projectId) throw new Error("No project selected");
		canvasStore.startLoading();
		try {
			workflowData = await workflowsStore.getWorkflowFromUrl(url, projectId);
		} catch (error) {
			toast.showError(error, i18n.baseText("nodeView.showError.getWorkflowDataFromUrl.title"));
			return;
		} finally {
			canvasStore.stopLoading();
		}
		return workflowData;
	}
	function getNodesToSave(nodes) {
		const data = {
			nodes: [],
			connections: {},
			pinData: {}
		};
		const exportedNodeNames = /* @__PURE__ */ new Set();
		for (const node of nodes) {
			const nodeSaveData = workflowHelpers.getNodeDataToSave(node);
			const pinDataForNode = workflowsStore.pinDataByNodeName(node.name);
			if (pinDataForNode) data.pinData[node.name] = pinDataForNode;
			if (nodeSaveData.credentials && settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) nodeSaveData.credentials = filterAllowedCredentials(nodeSaveData.credentials, workflowsStore.usedCredentials);
			data.nodes.push(nodeSaveData);
			exportedNodeNames.add(node.name);
		}
		data.connections = getConnectionsForNodes(data.nodes, exportedNodeNames);
		workflowHelpers.removeForeignCredentialsFromWorkflow(data, credentialsStore.allCredentials);
		return data;
	}
	function filterAllowedCredentials(credentials, usedCredentials) {
		return Object.fromEntries(Object.entries(credentials).filter(([, credential]) => {
			return credential.id && (!usedCredentials[credential.id] || usedCredentials[credential.id]?.currentUserHasAccess);
		}));
	}
	function getConnectionsForNodes(nodes, includeNodeNames) {
		const connections = {};
		for (const node of nodes) {
			const outgoingConnections = workflowsStore.outgoingConnectionsByNodeName(node.name);
			if (!Object.keys(outgoingConnections).length) continue;
			const filteredConnections = filterConnectionsByNodes(outgoingConnections, includeNodeNames);
			if (Object.keys(filteredConnections).length) connections[node.name] = filteredConnections;
		}
		return connections;
	}
	function filterConnectionsByNodes(connections, includeNodeNames) {
		const filteredConnections = {};
		for (const [type, typeConnections] of Object.entries(connections)) {
			const validConnections = typeConnections.map((sourceConnections) => (sourceConnections ?? []).filter((connection) => includeNodeNames.has(connection.node)));
			if (validConnections.length) filteredConnections[type] = validConnections;
		}
		return filteredConnections;
	}
	async function duplicateNodes(ids, options = {}) {
		return (await importWorkflowData(deepCopy(getNodesToSave(workflowsStore.getNodesByIds(ids))), "duplicate", {
			viewport: options.viewport,
			importTags: false
		})).nodes?.map((node) => node.id).filter(isPresent) ?? [];
	}
	async function copyNodes(ids) {
		const workflowData = deepCopy(getNodesToSave(workflowsStore.getNodesByIds(ids)));
		workflowData.meta = {
			...workflowData.meta,
			...workflowsStore.workflow.meta,
			instanceId: rootStore.instanceId
		};
		await clipboard.copy(JSON.stringify(workflowData, null, 2));
		telemetry.track("User copied nodes", {
			node_types: workflowData.nodes.map((node) => node.type),
			workflow_id: workflowsStore.workflowId
		});
	}
	async function cutNodes(ids) {
		await copyNodes(ids);
		deleteNodes(ids);
	}
	async function openExecution(executionId, nodeId) {
		let data;
		try {
			data = await workflowsStore.getExecution(executionId);
		} catch (error) {
			toast.showError(error, i18n.baseText("nodeView.showError.openExecution.title"));
			return;
		}
		if (data === void 0) throw new Error(`Execution with id "${executionId}" could not be found!`);
		if (data.status === "error" && data.data?.resultData.error) {
			const { title, message } = getExecutionErrorToastConfiguration({
				error: data.data.resultData.error,
				lastNodeExecuted: data.data.resultData.lastNodeExecuted
			});
			toast.showMessage({
				title,
				message,
				type: "error",
				duration: 0
			});
		}
		await initializeWorkspace(data.workflowData);
		workflowState.setWorkflowExecutionData(data);
		if (!["manual", "evaluation"].includes(data.mode)) workflowsStore.setWorkflowPinData({});
		if (nodeId) {
			const node = workflowsStore.getNodeById(nodeId);
			if (node) ndvStore.setActiveNodeName(node.name, "other");
			else toast.showError(/* @__PURE__ */ new Error(`Node with id "${nodeId}" could not be found!`), i18n.baseText("nodeView.showError.openExecution.node"));
		}
		uiStore.stateIsDirty = false;
		return data;
	}
	function startChat(source) {
		if (!workflowsStore.allNodes.some(isChatNode)) return;
		const workflowObject = workflowsStore.workflowObject;
		logsStore.toggleOpen(true);
		const payload = {
			workflow_id: workflowObject.id,
			button_type: source
		};
		externalHooks.run("nodeView.onOpenChat", payload);
		telemetry.track("User clicked chat open button", payload);
		setTimeout(() => {
			chatEventBus.emit("focusInput");
		}, 0);
	}
	async function importTemplate({ id, name, workflow }) {
		const convertedNodes = workflow.nodes?.map(workflowsStore.convertTemplateNodeToNodeUi);
		if (workflow.connections) workflowsStore.setConnections(workflow.connections);
		await addNodes(convertedNodes ?? []);
		await workflowState.getNewWorkflowDataAndMakeShareable(name, projectsStore.currentProjectId);
		workflowsStore.addToWorkflowMetadata({ templateId: `${id}` });
	}
	function tryToOpenSubworkflowInNewTab(nodeId) {
		const node = workflowsStore.getNodeById(nodeId);
		if (!node) return false;
		const subWorkflowId = getSubworkflowId(node);
		if (!subWorkflowId) return false;
		window.open(`${rootStore.baseUrl}workflow/${subWorkflowId}`, "_blank");
		return true;
	}
	function replaceNode(previousId, newId, { trackHistory = true, trackBulk = true } = {}) {
		const previousNode = workflowsStore.getNodeById(previousId);
		const newNode = workflowsStore.getNodeById(newId);
		if (!previousNode || !newNode) return;
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		const [x, y] = previousNode.position;
		updateNodePosition(newId, {
			x,
			y
		}, { trackHistory });
		replaceNodeConnections(previousId, newId, {
			trackBulk: false,
			trackHistory
		});
		deleteNode(previousId, {
			trackHistory,
			trackBulk: false
		});
		uiStore.stateIsDirty = true;
		if (trackHistory && trackBulk) historyStore.stopRecordingUndo();
	}
	async function addNodesAndConnections(nodes, addedConnections, { trackBulk = true, trackHistory = true,...options }) {
		if (trackHistory && trackBulk) historyStore.startRecordingUndo();
		const addedNodes = await addNodes(nodes, {
			...options,
			trackHistory,
			trackBulk: false,
			telemetry: true
		});
		const offsetIndex = editableWorkflow.value.nodes.length - nodes.length;
		await addConnections(addedConnections.map(({ from, to }) => {
			const fromNode = editableWorkflow.value.nodes[offsetIndex + from.nodeIndex];
			const toNode = editableWorkflow.value.nodes[offsetIndex + to.nodeIndex];
			const type = from.type ?? to.type ?? NodeConnectionTypes.Main;
			return {
				source: fromNode.id,
				sourceHandle: createCanvasConnectionHandleString({
					mode: CanvasConnectionMode.Output,
					type: isValidNodeConnectionType(type) ? type : NodeConnectionTypes.Main,
					index: from.outputIndex ?? 0
				}),
				target: toNode.id,
				targetHandle: createCanvasConnectionHandleString({
					mode: CanvasConnectionMode.Input,
					type: isValidNodeConnectionType(type) ? type : NodeConnectionTypes.Main,
					index: to.inputIndex ?? 0
				}),
				data: {
					source: {
						index: from.outputIndex ?? 0,
						type
					},
					target: {
						index: to.inputIndex ?? 0,
						type
					}
				}
			};
		}), {
			trackHistory,
			trackBulk: false
		});
		uiStore.resetLastInteractedWith();
		if (addedNodes.length > 0 && options.replaceNodeId) {
			const lastAddedNodeId = addedNodes[addedNodes.length - 1].id;
			replaceNode(options.replaceNodeId, lastAddedNodeId, {
				trackHistory,
				trackBulk: false
			});
		}
		if (trackHistory && trackBulk) historyStore.stopRecordingUndo();
		return { addedNodes };
	}
	function fitView() {
		setTimeout(() => canvasEventBus.emit("fitView"));
	}
	function trackOpenWorkflowTemplate(templateId) {
		telemetry.track("User inserted workflow template", {
			source: "workflow",
			template_id: tryToParseNumber(templateId),
			wf_template_repo_session_id: templatesStore.previousSessionId
		});
	}
	async function openWorkflowTemplate(templateId) {
		resetWorkspace();
		canvasStore.startLoading();
		canvasStore.setLoadingText(i18n.baseText("nodeView.loadingTemplate"));
		workflowsStore.currentWorkflowExecutions = [];
		executionsStore.activeExecution = null;
		let data;
		try {
			externalHooks.run("template.requested", { templateId });
			data = await templatesStore.getFixedWorkflowTemplate(templateId);
			if (!data) throw new Error(i18n.baseText("nodeView.workflowTemplateWithIdCouldNotBeFound", { interpolate: { templateId } }));
		} catch (error) {
			toast.showError(error, i18n.baseText("nodeView.couldntImportWorkflow"));
			await router.replace({ name: VIEWS.NEW_WORKFLOW });
			return;
		}
		trackOpenWorkflowTemplate(templateId);
		uiStore.isBlankRedirect = true;
		await router.replace({
			name: VIEWS.NEW_WORKFLOW,
			query: { templateId }
		});
		await importTemplate({
			id: templateId,
			name: data.name,
			workflow: data.workflow
		});
		if (typeof route.params.name === "string") workflowState.setWorkflowId(route.params.name);
		uiStore.stateIsDirty = true;
		canvasStore.stopLoading();
		externalHooks.run("template.open", {
			templateId,
			templateName: data.name,
			workflow: data.workflow
		});
		fitView();
	}
	async function openWorkflowTemplateFromJSON(workflow) {
		if (!workflow.nodes || !workflow.connections) {
			toast.showError(new Error(i18n.baseText("nodeView.couldntLoadWorkflow.invalidWorkflowObject")), i18n.baseText("nodeView.couldntImportWorkflow"));
			await router.replace({ name: VIEWS.NEW_WORKFLOW });
			return;
		}
		resetWorkspace();
		canvasStore.startLoading();
		canvasStore.setLoadingText(i18n.baseText("nodeView.loadingTemplate"));
		workflowsStore.currentWorkflowExecutions = [];
		executionsStore.activeExecution = null;
		uiStore.isBlankRedirect = true;
		const templateId = workflow.meta.templateId;
		const parentFolderId = route.query.parentFolderId;
		await projectsStore.refreshCurrentProject();
		await fetchAndSetParentFolder(parentFolderId);
		await router.replace({
			name: VIEWS.NEW_WORKFLOW,
			query: {
				templateId,
				parentFolderId,
				projectId: projectsStore.currentProjectId
			}
		});
		await importTemplate({
			id: templateId,
			name: workflow.name,
			workflow
		});
		uiStore.stateIsDirty = true;
		canvasStore.stopLoading();
		fitView();
	}
	return {
		lastClickPosition,
		editableWorkflow,
		editableWorkflowObject,
		triggerNodes,
		requireNodeTypeDescription,
		addNodes,
		addNode,
		resolveNodePosition,
		revertAddNode,
		updateNodesPosition,
		updateNodePosition,
		tidyUp,
		revertUpdateNodePosition,
		setNodeActive,
		setNodeActiveByName,
		clearNodeActive,
		setNodeSelected,
		toggleNodesDisabled,
		revertToggleNodeDisabled,
		toggleNodesPinned,
		setNodeParameters,
		renameNode,
		revertRenameNode,
		replaceNodeParameters,
		revertReplaceNodeParameters,
		deleteNode,
		deleteNodes,
		copyNodes,
		cutNodes,
		duplicateNodes,
		getNodesToSave,
		revertDeleteNode,
		addConnections,
		createConnection,
		revertCreateConnection,
		deleteConnection,
		revertDeleteConnection,
		deleteConnectionsByNodeId,
		revalidateNodeInputConnections,
		revalidateNodeOutputConnections,
		isConnectionAllowed,
		filterConnectionsByNodes,
		connectAdjacentNodes,
		importWorkflowData,
		fetchWorkflowDataFromUrl,
		resetWorkspace,
		initializeWorkspace,
		resolveNodeWebhook,
		openExecution,
		startChat,
		importTemplate,
		replaceNodeConnections,
		tryToOpenSubworkflowInNewTab,
		initializeUnknownNodes,
		replaceNode,
		addNodesAndConnections,
		fitView,
		openWorkflowTemplate,
		openWorkflowTemplateFromJSON
	};
}
export { useLogsStore as a, LOCAL_STORAGE_PANEL_WIDTH as c, chatEventBus as d, useExperimentalNdvStore as i, LOGS_PANEL_STATE as l, useParentFolder as n, LOCAL_STORAGE_OVERVIEW_PANEL_WIDTH as o, canvasEventBus as r, LOCAL_STORAGE_PANEL_HEIGHT as s, useCanvasOperations as t, LOG_DETAILS_PANEL_STATE as u };