From b1bcace64fff1e5bddb2758ea4ff2050e8de7eb4 Mon Sep 17 00:00:00 2001 From: Camiel van Schoonhoven Date: Mon, 24 Nov 2025 17:38:27 -0800 Subject: [PATCH] POC Unpack Subgraphs onto empty Canvas --- .../ReactFlow/FlowCanvas/FlowCanvas.tsx | 86 +++++++++++++++++++ .../ReactFlow/FlowCanvas/utils/addTask.ts | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/components/shared/ReactFlow/FlowCanvas/FlowCanvas.tsx b/src/components/shared/ReactFlow/FlowCanvas/FlowCanvas.tsx index c5fc5c2a7..03a35f692 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/FlowCanvas.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/FlowCanvas.tsx @@ -42,8 +42,10 @@ import { import { loadComponentAsRefFromText } from "@/utils/componentStore"; import { deselectAllNodes } from "@/utils/flowUtils"; import createNodesFromComponentSpec from "@/utils/nodes/createNodesFromComponentSpec"; +import { extractPositionFromAnnotations } from "@/utils/nodes/extractPositionFromAnnotations"; import { getSubgraphComponentSpec, + isSubgraph, updateSubgraphSpec, } from "@/utils/subgraphUtils"; @@ -632,6 +634,90 @@ const FlowCanvas = ({ } } + // Drop a subgraph onto an empty canvas + if ( + nodes.length === 0 && + taskType === "task" && + droppedTask && + isSubgraph(droppedTask) && + droppedTask.componentRef.spec && + isGraphImplementation(droppedTask.componentRef.spec.implementation) + ) { + const dialogDetails = { + title: "Import Subgraph as Pipeline", + description: `Dropping the subgraph "${droppedTask.componentRef.spec.name}" onto an empty canvas will unpack its internal components. Do you want to proceed?`, + }; + + const confirmed = await triggerConfirmation(dialogDetails); + + if (!confirmed) { + return; + } + + // Todo: copy over IO values + // Todo: output node connections + // Todo: Move logic into a utility + console.log("Unpacking subgraph onto empty canvas"); + console.log("Dropped Task:", droppedTask); + + const graphSpec = droppedTask.componentRef.spec?.implementation.graph; + + let updatedSubgraphSpec = { ...currentSubgraphSpec }; + + Object.entries(graphSpec.tasks).forEach(([_, task]) => { + const { spec } = addTask( + "task", + task, + extractPositionFromAnnotations(task.annotations), + updatedSubgraphSpec, + ); + + updatedSubgraphSpec = spec; + }); + + droppedTask.componentRef.spec.inputs?.forEach((input) => { + const { spec } = addTask( + "input", + null, + extractPositionFromAnnotations(input.annotations), + updatedSubgraphSpec, + { + name: input.name, + type: input.type, + description: input.description, + }, + ); + + updatedSubgraphSpec = spec; + }); + + droppedTask.componentRef.spec.outputs?.forEach((output) => { + const { spec } = addTask( + "output", + null, + extractPositionFromAnnotations(output.annotations), + updatedSubgraphSpec, + { + name: output.name, + type: output.type, + description: output.description, + }, + ); + + updatedSubgraphSpec = spec; + }); + + const newRootSpec = updateSubgraphSpec( + componentSpec, + currentSubgraphPath, + updatedSubgraphSpec, + ); + + setComponentSpec(newRootSpec); + + return; + } + // Replacing an existing node if (replaceTarget) { if (!droppedTask) { diff --git a/src/components/shared/ReactFlow/FlowCanvas/utils/addTask.ts b/src/components/shared/ReactFlow/FlowCanvas/utils/addTask.ts index 0728d9ee0..f8fbd5fe9 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/utils/addTask.ts +++ b/src/components/shared/ReactFlow/FlowCanvas/utils/addTask.ts @@ -26,7 +26,7 @@ interface AddTaskResult { * Options for creating input/output nodes. * Omits position-related fields (annotations) which are automatically set. */ -type IONodeOptions = Omit, "annotations">; +type IONodeOptions = Omit, "annotations">; /** * Creates a task, input, or output node and adds it to the component specification.