@flowdrop/flowdrop 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (441) hide show
  1. package/README.md +94 -51
  2. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  3. package/dist/adapters/WorkflowAdapter.js +27 -47
  4. package/dist/adapters/agentspec/AgentSpecAdapter.d.ts +2 -2
  5. package/dist/adapters/agentspec/AgentSpecAdapter.js +122 -133
  6. package/dist/adapters/agentspec/agentAdapter.d.ts +2 -2
  7. package/dist/adapters/agentspec/agentAdapter.js +10 -10
  8. package/dist/adapters/agentspec/autoLayout.d.ts +52 -6
  9. package/dist/adapters/agentspec/autoLayout.js +118 -23
  10. package/dist/adapters/agentspec/componentTypeDefaults.d.ts +1 -1
  11. package/dist/adapters/agentspec/componentTypeDefaults.js +120 -120
  12. package/dist/adapters/agentspec/defaultNodeTypes.d.ts +2 -2
  13. package/dist/adapters/agentspec/defaultNodeTypes.js +307 -307
  14. package/dist/adapters/agentspec/index.d.ts +10 -10
  15. package/dist/adapters/agentspec/index.js +6 -6
  16. package/dist/adapters/agentspec/validator.d.ts +2 -2
  17. package/dist/adapters/agentspec/validator.js +20 -22
  18. package/dist/api/enhanced-client.d.ts +3 -3
  19. package/dist/api/enhanced-client.js +72 -73
  20. package/dist/chat/commandClassifier.d.ts +19 -0
  21. package/dist/chat/commandClassifier.js +30 -0
  22. package/dist/chat/index.d.ts +27 -0
  23. package/dist/chat/index.js +32 -0
  24. package/dist/chat/responseParser.d.ts +21 -0
  25. package/dist/chat/responseParser.js +91 -0
  26. package/dist/commands/batch.d.ts +18 -0
  27. package/dist/commands/batch.js +54 -0
  28. package/dist/commands/executor.d.ts +37 -0
  29. package/dist/commands/executor.js +1133 -0
  30. package/dist/commands/index.d.ts +14 -0
  31. package/dist/commands/index.js +17 -0
  32. package/dist/commands/parser.d.ts +16 -0
  33. package/dist/commands/parser.js +295 -0
  34. package/dist/commands/positioner.d.ts +19 -0
  35. package/dist/commands/positioner.js +33 -0
  36. package/dist/commands/storeIntegration.svelte.d.ts +16 -0
  37. package/dist/commands/storeIntegration.svelte.js +67 -0
  38. package/dist/commands/types.d.ts +343 -0
  39. package/dist/commands/types.js +45 -0
  40. package/dist/components/App.svelte +522 -237
  41. package/dist/components/App.svelte.d.ts +11 -8
  42. package/dist/components/CanvasBanner.stories.svelte +10 -16
  43. package/dist/components/CanvasBanner.stories.svelte.d.ts +1 -1
  44. package/dist/components/CanvasBanner.svelte +2 -2
  45. package/dist/components/CanvasBanner.svelte.d.ts +1 -1
  46. package/dist/components/CanvasController.svelte +37 -0
  47. package/dist/components/CanvasController.svelte.d.ts +32 -0
  48. package/dist/components/ConfigForm.svelte +118 -256
  49. package/dist/components/ConfigForm.svelte.d.ts +2 -2
  50. package/dist/components/ConfigMappingRow.svelte +128 -0
  51. package/dist/components/ConfigMappingRow.svelte.d.ts +8 -0
  52. package/dist/components/ConfigModal.svelte +3 -3
  53. package/dist/components/ConfigModal.svelte.d.ts +1 -1
  54. package/dist/components/ConfigPanel.stories.svelte +19 -19
  55. package/dist/components/ConfigPanel.stories.svelte.d.ts +1 -1
  56. package/dist/components/ConfigPanel.svelte +57 -19
  57. package/dist/components/ConfigPanel.svelte.d.ts +3 -1
  58. package/dist/components/ConnectionLine.svelte +4 -4
  59. package/dist/components/EdgeRefresher.svelte +1 -1
  60. package/dist/components/FlowDropEdge.stories.svelte +110 -110
  61. package/dist/components/FlowDropEdge.svelte +11 -19
  62. package/dist/components/FlowDropEdge.svelte.d.ts +1 -1
  63. package/dist/components/FlowDropZone.svelte +6 -9
  64. package/dist/components/FlowDropZone.svelte.d.ts +1 -1
  65. package/dist/components/LoadingSpinner.stories.svelte +13 -13
  66. package/dist/components/LoadingSpinner.stories.svelte.d.ts +1 -1
  67. package/dist/components/LoadingSpinner.svelte +3 -3
  68. package/dist/components/LoadingSpinner.svelte.d.ts +1 -1
  69. package/dist/components/Logo.stories.svelte +4 -4
  70. package/dist/components/Logo.stories.svelte.d.ts +1 -1
  71. package/dist/components/Logo.svelte +3 -9
  72. package/dist/components/LogsSidebar.svelte +46 -53
  73. package/dist/components/LogsSidebar.svelte.d.ts +1 -1
  74. package/dist/components/MarkdownDisplay.stories.svelte +10 -14
  75. package/dist/components/MarkdownDisplay.stories.svelte.d.ts +1 -1
  76. package/dist/components/MarkdownDisplay.svelte +4 -6
  77. package/dist/components/Navbar.stories.svelte +19 -19
  78. package/dist/components/Navbar.stories.svelte.d.ts +1 -1
  79. package/dist/components/Navbar.svelte +28 -49
  80. package/dist/components/Navbar.svelte.d.ts +2 -2
  81. package/dist/components/NodeSidebar.svelte +55 -135
  82. package/dist/components/NodeSidebar.svelte.d.ts +1 -1
  83. package/dist/components/NodeStatusOverlay.stories.svelte +19 -31
  84. package/dist/components/NodeStatusOverlay.stories.svelte.d.ts +1 -1
  85. package/dist/components/NodeStatusOverlay.svelte +40 -55
  86. package/dist/components/NodeStatusOverlay.svelte.d.ts +3 -3
  87. package/dist/components/NodeSwapPicker.svelte +493 -0
  88. package/dist/components/NodeSwapPicker.svelte.d.ts +16 -0
  89. package/dist/components/PipelineStatus.svelte +63 -89
  90. package/dist/components/PipelineStatus.svelte.d.ts +4 -4
  91. package/dist/components/PortCoordinateTracker.svelte +5 -7
  92. package/dist/components/PortCoordinateTracker.svelte.d.ts +1 -1
  93. package/dist/components/PortMappingRow.svelte +205 -0
  94. package/dist/components/PortMappingRow.svelte.d.ts +12 -0
  95. package/dist/components/ReadOnlyDetails.svelte +1 -1
  96. package/dist/components/SchemaForm.stories.svelte +53 -53
  97. package/dist/components/SchemaForm.stories.svelte.d.ts +1 -1
  98. package/dist/components/SchemaForm.svelte +24 -51
  99. package/dist/components/SchemaForm.svelte.d.ts +2 -2
  100. package/dist/components/SettingsModal.svelte +6 -9
  101. package/dist/components/SettingsModal.svelte.d.ts +1 -1
  102. package/dist/components/SettingsPanel.svelte +138 -158
  103. package/dist/components/SettingsPanel.svelte.d.ts +1 -1
  104. package/dist/components/StatusIcon.stories.svelte +16 -29
  105. package/dist/components/StatusIcon.stories.svelte.d.ts +1 -1
  106. package/dist/components/StatusIcon.svelte +19 -19
  107. package/dist/components/StatusIcon.svelte.d.ts +2 -2
  108. package/dist/components/StatusLabel.stories.svelte +8 -8
  109. package/dist/components/StatusLabel.stories.svelte.d.ts +1 -1
  110. package/dist/components/SwapMappingEditor.svelte +529 -0
  111. package/dist/components/SwapMappingEditor.svelte.d.ts +12 -0
  112. package/dist/components/ThemeToggle.stories.svelte +10 -10
  113. package/dist/components/ThemeToggle.stories.svelte.d.ts +1 -1
  114. package/dist/components/ThemeToggle.svelte +22 -33
  115. package/dist/components/ThemeToggle.svelte.d.ts +1 -1
  116. package/dist/components/UniversalNode.svelte +29 -41
  117. package/dist/components/UniversalNode.svelte.d.ts +3 -3
  118. package/dist/components/WorkflowEditor.svelte +210 -170
  119. package/dist/components/WorkflowEditor.svelte.d.ts +12 -4
  120. package/dist/components/chat/AIChatPanel.svelte +797 -0
  121. package/dist/components/chat/AIChatPanel.svelte.d.ts +13 -0
  122. package/dist/components/chat/CommandPreview.svelte +234 -0
  123. package/dist/components/chat/CommandPreview.svelte.d.ts +9 -0
  124. package/dist/components/console/CommandConsole.stories.svelte +111 -0
  125. package/dist/components/console/CommandConsole.stories.svelte.d.ts +27 -0
  126. package/dist/components/console/CommandConsole.svelte +263 -0
  127. package/dist/components/console/CommandConsole.svelte.d.ts +11 -0
  128. package/dist/components/console/ConsoleAutocomplete.svelte +142 -0
  129. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +21 -0
  130. package/dist/components/console/ConsoleInput.svelte +771 -0
  131. package/dist/components/console/ConsoleInput.svelte.d.ts +16 -0
  132. package/dist/components/console/ConsoleOutput.svelte +116 -0
  133. package/dist/components/console/ConsoleOutput.svelte.d.ts +11 -0
  134. package/dist/components/console/formatters.d.ts +26 -0
  135. package/dist/components/console/formatters.js +116 -0
  136. package/dist/components/form/FormArray.svelte +75 -132
  137. package/dist/components/form/FormArray.svelte.d.ts +1 -1
  138. package/dist/components/form/FormAutocomplete.svelte +65 -108
  139. package/dist/components/form/FormAutocomplete.svelte.d.ts +1 -1
  140. package/dist/components/form/FormCheckboxGroup.stories.svelte +13 -16
  141. package/dist/components/form/FormCheckboxGroup.stories.svelte.d.ts +1 -1
  142. package/dist/components/form/FormCheckboxGroup.svelte +2 -2
  143. package/dist/components/form/FormCodeEditor.svelte +42 -56
  144. package/dist/components/form/FormField.svelte +79 -90
  145. package/dist/components/form/FormField.svelte.d.ts +2 -2
  146. package/dist/components/form/FormFieldLight.svelte +72 -88
  147. package/dist/components/form/FormFieldLight.svelte.d.ts +1 -1
  148. package/dist/components/form/FormFieldWrapper.stories.svelte +14 -14
  149. package/dist/components/form/FormFieldWrapper.stories.svelte.d.ts +1 -1
  150. package/dist/components/form/FormFieldWrapper.svelte +2 -9
  151. package/dist/components/form/FormFieldWrapper.svelte.d.ts +1 -1
  152. package/dist/components/form/FormFieldset.svelte +3 -3
  153. package/dist/components/form/FormFieldset.svelte.d.ts +2 -2
  154. package/dist/components/form/FormMarkdownEditor.svelte +123 -156
  155. package/dist/components/form/FormNumberField.stories.svelte +18 -18
  156. package/dist/components/form/FormNumberField.stories.svelte.d.ts +1 -1
  157. package/dist/components/form/FormNumberField.svelte +6 -6
  158. package/dist/components/form/FormRangeField.stories.svelte +13 -13
  159. package/dist/components/form/FormRangeField.stories.svelte.d.ts +1 -1
  160. package/dist/components/form/FormRangeField.svelte +4 -12
  161. package/dist/components/form/FormSelect.stories.svelte +21 -21
  162. package/dist/components/form/FormSelect.stories.svelte.d.ts +1 -1
  163. package/dist/components/form/FormSelect.svelte +5 -5
  164. package/dist/components/form/FormSelect.svelte.d.ts +1 -1
  165. package/dist/components/form/FormTemplateEditor.svelte +126 -175
  166. package/dist/components/form/FormTemplateEditor.svelte.d.ts +1 -1
  167. package/dist/components/form/FormTextField.stories.svelte +17 -23
  168. package/dist/components/form/FormTextField.stories.svelte.d.ts +1 -1
  169. package/dist/components/form/FormTextField.svelte +4 -4
  170. package/dist/components/form/FormTextarea.stories.svelte +18 -21
  171. package/dist/components/form/FormTextarea.stories.svelte.d.ts +1 -1
  172. package/dist/components/form/FormTextarea.svelte +4 -4
  173. package/dist/components/form/FormToggle.stories.svelte +13 -16
  174. package/dist/components/form/FormToggle.stories.svelte.d.ts +1 -1
  175. package/dist/components/form/FormToggle.svelte +3 -3
  176. package/dist/components/form/FormUISchemaRenderer.svelte +12 -19
  177. package/dist/components/form/FormUISchemaRenderer.svelte.d.ts +3 -3
  178. package/dist/components/form/index.d.ts +19 -19
  179. package/dist/components/form/index.js +18 -18
  180. package/dist/components/form/templateAutocomplete.d.ts +2 -2
  181. package/dist/components/form/templateAutocomplete.js +55 -64
  182. package/dist/components/form/types.d.ts +6 -6
  183. package/dist/components/form/types.js +4 -9
  184. package/dist/components/icons/AlertCircleIcon.svelte +1 -6
  185. package/dist/components/icons/CogIcon.svelte +1 -6
  186. package/dist/components/interrupt/ChoicePrompt.stories.svelte +27 -27
  187. package/dist/components/interrupt/ChoicePrompt.stories.svelte.d.ts +1 -1
  188. package/dist/components/interrupt/ChoicePrompt.svelte +17 -41
  189. package/dist/components/interrupt/ChoicePrompt.svelte.d.ts +1 -1
  190. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte +17 -17
  191. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte.d.ts +1 -1
  192. package/dist/components/interrupt/ConfirmationPrompt.svelte +10 -16
  193. package/dist/components/interrupt/ConfirmationPrompt.svelte.d.ts +1 -1
  194. package/dist/components/interrupt/FormPrompt.svelte +10 -15
  195. package/dist/components/interrupt/FormPrompt.svelte.d.ts +1 -1
  196. package/dist/components/interrupt/InterruptBubble.svelte +87 -121
  197. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +2 -2
  198. package/dist/components/interrupt/ReviewPrompt.stories.svelte +37 -37
  199. package/dist/components/interrupt/ReviewPrompt.stories.svelte.d.ts +1 -1
  200. package/dist/components/interrupt/ReviewPrompt.svelte +55 -75
  201. package/dist/components/interrupt/ReviewPrompt.svelte.d.ts +1 -1
  202. package/dist/components/interrupt/TextInputPrompt.stories.svelte +16 -17
  203. package/dist/components/interrupt/TextInputPrompt.stories.svelte.d.ts +1 -1
  204. package/dist/components/interrupt/TextInputPrompt.svelte +13 -18
  205. package/dist/components/interrupt/TextInputPrompt.svelte.d.ts +1 -1
  206. package/dist/components/interrupt/index.d.ts +6 -5
  207. package/dist/components/interrupt/index.js +6 -5
  208. package/dist/components/layouts/MainLayout.svelte +46 -84
  209. package/dist/components/layouts/MainLayout.svelte.d.ts +6 -6
  210. package/dist/components/nodes/GatewayNode.stories.svelte +64 -65
  211. package/dist/components/nodes/GatewayNode.svelte +37 -70
  212. package/dist/components/nodes/GatewayNode.svelte.d.ts +3 -3
  213. package/dist/components/nodes/IdeaNode.stories.svelte +25 -26
  214. package/dist/components/nodes/IdeaNode.svelte +22 -36
  215. package/dist/components/nodes/IdeaNode.svelte.d.ts +1 -1
  216. package/dist/components/nodes/NotesNode.stories.svelte +37 -38
  217. package/dist/components/nodes/NotesNode.svelte +28 -39
  218. package/dist/components/nodes/NotesNode.svelte.d.ts +1 -1
  219. package/dist/components/nodes/SimpleNode.stories.svelte +137 -138
  220. package/dist/components/nodes/SimpleNode.svelte +44 -74
  221. package/dist/components/nodes/SimpleNode.svelte.d.ts +1 -1
  222. package/dist/components/nodes/SquareNode.stories.svelte +75 -75
  223. package/dist/components/nodes/SquareNode.svelte +42 -68
  224. package/dist/components/nodes/SquareNode.svelte.d.ts +1 -1
  225. package/dist/components/nodes/TerminalNode.stories.svelte +10 -10
  226. package/dist/components/nodes/TerminalNode.svelte +74 -112
  227. package/dist/components/nodes/TerminalNode.svelte.d.ts +1 -1
  228. package/dist/components/nodes/ToolNode.stories.svelte +115 -116
  229. package/dist/components/nodes/ToolNode.svelte +31 -64
  230. package/dist/components/nodes/ToolNode.svelte.d.ts +1 -1
  231. package/dist/components/nodes/WorkflowNode.stories.svelte +84 -89
  232. package/dist/components/nodes/WorkflowNode.svelte +50 -103
  233. package/dist/components/nodes/WorkflowNode.svelte.d.ts +3 -3
  234. package/dist/components/playground/ChatPanel.svelte +47 -103
  235. package/dist/components/playground/ExecutionLogs.svelte +45 -68
  236. package/dist/components/playground/InputCollector.svelte +32 -51
  237. package/dist/components/playground/MessageBubble.stories.svelte +25 -25
  238. package/dist/components/playground/MessageBubble.stories.svelte.d.ts +1 -1
  239. package/dist/components/playground/MessageBubble.svelte +54 -70
  240. package/dist/components/playground/MessageBubble.svelte.d.ts +1 -1
  241. package/dist/components/playground/Playground.svelte +60 -91
  242. package/dist/components/playground/Playground.svelte.d.ts +3 -3
  243. package/dist/components/playground/PlaygroundModal.svelte +8 -12
  244. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -3
  245. package/dist/components/playground/SessionManager.svelte +34 -40
  246. package/dist/components/playground/SessionManager.svelte.d.ts +1 -1
  247. package/dist/config/agentSpecEndpoints.d.ts +1 -1
  248. package/dist/config/agentSpecEndpoints.js +20 -20
  249. package/dist/config/constants.js +2 -2
  250. package/dist/config/defaultCategories.d.ts +1 -1
  251. package/dist/config/defaultCategories.js +86 -86
  252. package/dist/config/defaultPortConfig.d.ts +1 -1
  253. package/dist/config/defaultPortConfig.js +144 -144
  254. package/dist/config/endpoints.d.ts +12 -4
  255. package/dist/config/endpoints.js +70 -65
  256. package/dist/config/runtimeConfig.d.ts +2 -2
  257. package/dist/config/runtimeConfig.js +8 -8
  258. package/dist/core/index.d.ts +68 -63
  259. package/dist/core/index.js +44 -35
  260. package/dist/display/index.d.ts +2 -2
  261. package/dist/display/index.js +2 -2
  262. package/dist/editor/index.d.ts +64 -62
  263. package/dist/editor/index.js +57 -55
  264. package/dist/form/code.d.ts +5 -5
  265. package/dist/form/code.js +14 -14
  266. package/dist/form/fieldRegistry.d.ts +3 -3
  267. package/dist/form/fieldRegistry.js +9 -11
  268. package/dist/form/full.d.ts +8 -8
  269. package/dist/form/full.js +9 -9
  270. package/dist/form/index.d.ts +18 -18
  271. package/dist/form/index.js +16 -16
  272. package/dist/form/markdown.d.ts +4 -4
  273. package/dist/form/markdown.js +8 -8
  274. package/dist/helpers/proximityConnect.d.ts +3 -3
  275. package/dist/helpers/proximityConnect.js +40 -35
  276. package/dist/helpers/workflowEditorHelper.d.ts +8 -58
  277. package/dist/helpers/workflowEditorHelper.js +73 -292
  278. package/dist/index.d.ts +6 -6
  279. package/dist/index.js +6 -6
  280. package/dist/mocks/app-environment.js +2 -2
  281. package/dist/mocks/app-forms.js +1 -1
  282. package/dist/mocks/app-navigation.js +2 -2
  283. package/dist/mocks/app-stores.js +3 -3
  284. package/dist/playground/index.d.ts +19 -19
  285. package/dist/playground/index.js +16 -16
  286. package/dist/playground/mount.d.ts +3 -3
  287. package/dist/playground/mount.js +24 -24
  288. package/dist/registry/builtinFormats.js +13 -13
  289. package/dist/registry/builtinNodes.d.ts +2 -2
  290. package/dist/registry/builtinNodes.js +77 -77
  291. package/dist/registry/index.d.ts +4 -4
  292. package/dist/registry/index.js +4 -4
  293. package/dist/registry/nodeComponentRegistry.d.ts +8 -8
  294. package/dist/registry/nodeComponentRegistry.js +9 -11
  295. package/dist/registry/plugin.d.ts +2 -2
  296. package/dist/registry/plugin.js +11 -11
  297. package/dist/registry/workflowFormatRegistry.d.ts +3 -3
  298. package/dist/registry/workflowFormatRegistry.js +2 -2
  299. package/dist/schema/index.d.ts +1 -1
  300. package/dist/schema/index.js +2 -2
  301. package/dist/schemas/v1/workflow.schema.json +107 -22
  302. package/dist/services/agentSpecExecutionService.d.ts +3 -3
  303. package/dist/services/agentSpecExecutionService.js +55 -56
  304. package/dist/services/api.d.ts +2 -2
  305. package/dist/services/api.js +37 -37
  306. package/dist/services/apiVariableService.d.ts +1 -1
  307. package/dist/services/apiVariableService.js +34 -41
  308. package/dist/services/autoSaveService.js +8 -8
  309. package/dist/services/categoriesApi.d.ts +2 -2
  310. package/dist/services/categoriesApi.js +8 -8
  311. package/dist/services/chatService.d.ts +65 -0
  312. package/dist/services/chatService.js +131 -0
  313. package/dist/services/draftStorage.d.ts +1 -1
  314. package/dist/services/draftStorage.js +11 -11
  315. package/dist/services/dynamicSchemaService.d.ts +1 -1
  316. package/dist/services/dynamicSchemaService.js +39 -41
  317. package/dist/services/globalSave.d.ts +2 -2
  318. package/dist/services/globalSave.js +38 -41
  319. package/dist/services/historyService.d.ts +7 -5
  320. package/dist/services/historyService.js +29 -14
  321. package/dist/services/interruptService.d.ts +1 -1
  322. package/dist/services/interruptService.js +29 -35
  323. package/dist/services/nodeExecutionService.d.ts +1 -1
  324. package/dist/services/nodeExecutionService.js +44 -45
  325. package/dist/services/playgroundService.d.ts +1 -1
  326. package/dist/services/playgroundService.js +29 -29
  327. package/dist/services/portConfigApi.d.ts +2 -2
  328. package/dist/services/portConfigApi.js +8 -8
  329. package/dist/services/settingsService.d.ts +2 -2
  330. package/dist/services/settingsService.js +19 -25
  331. package/dist/services/toastService.d.ts +4 -4
  332. package/dist/services/toastService.js +33 -33
  333. package/dist/services/variableService.d.ts +1 -1
  334. package/dist/services/variableService.js +36 -36
  335. package/dist/services/workflowStorage.d.ts +2 -2
  336. package/dist/services/workflowStorage.js +13 -13
  337. package/dist/settings/index.d.ts +7 -7
  338. package/dist/settings/index.js +6 -6
  339. package/dist/skins/default.d.ts +1 -1
  340. package/dist/skins/default.js +1 -1
  341. package/dist/skins/index.d.ts +3 -3
  342. package/dist/skins/index.js +7 -7
  343. package/dist/skins/slate.d.ts +1 -1
  344. package/dist/skins/slate.js +69 -69
  345. package/dist/stores/categoriesStore.svelte.d.ts +1 -1
  346. package/dist/stores/categoriesStore.svelte.js +5 -5
  347. package/dist/stores/editorStateMachine.svelte.d.ts +2 -2
  348. package/dist/stores/editorStateMachine.svelte.js +34 -34
  349. package/dist/stores/historyStore.svelte.d.ts +4 -4
  350. package/dist/stores/historyStore.svelte.js +4 -4
  351. package/dist/stores/interruptStore.svelte.d.ts +3 -3
  352. package/dist/stores/interruptStore.svelte.js +27 -22
  353. package/dist/stores/playgroundStore.svelte.d.ts +3 -3
  354. package/dist/stores/playgroundStore.svelte.js +29 -23
  355. package/dist/stores/portCoordinateStore.svelte.d.ts +6 -2
  356. package/dist/stores/portCoordinateStore.svelte.js +30 -39
  357. package/dist/stores/settingsStore.svelte.d.ts +2 -2
  358. package/dist/stores/settingsStore.svelte.js +57 -62
  359. package/dist/stores/workflowStore.svelte.d.ts +34 -5
  360. package/dist/stores/workflowStore.svelte.js +127 -108
  361. package/dist/stories/CanvasDecorator.svelte +7 -10
  362. package/dist/stories/CanvasDecorator.svelte.d.ts +2 -2
  363. package/dist/stories/EdgeDecorator.svelte +28 -31
  364. package/dist/stories/EdgeDecorator.svelte.d.ts +1 -1
  365. package/dist/stories/NodeDecorator.svelte +14 -20
  366. package/dist/stories/NodeDecorator.svelte.d.ts +1 -1
  367. package/dist/stories/utils.d.ts +2 -2
  368. package/dist/stories/utils.js +89 -93
  369. package/dist/styles/base.css +16 -50
  370. package/dist/styles/tokens.css +10 -28
  371. package/dist/svelte-app.d.ts +10 -10
  372. package/dist/svelte-app.js +39 -39
  373. package/dist/themes/default.d.ts +1 -1
  374. package/dist/themes/default.js +4 -4
  375. package/dist/themes/index.d.ts +3 -3
  376. package/dist/themes/index.js +11 -11
  377. package/dist/themes/minimal.d.ts +1 -1
  378. package/dist/themes/minimal.js +5 -5
  379. package/dist/types/agentspec.d.ts +18 -18
  380. package/dist/types/agentspec.js +2 -2
  381. package/dist/types/auth.d.ts +1 -1
  382. package/dist/types/auth.js +6 -6
  383. package/dist/types/chat.d.ts +63 -0
  384. package/dist/types/chat.js +9 -0
  385. package/dist/types/config.d.ts +6 -6
  386. package/dist/types/events.d.ts +28 -2
  387. package/dist/types/events.js +2 -1
  388. package/dist/types/index.d.ts +40 -32
  389. package/dist/types/index.js +6 -6
  390. package/dist/types/interrupt.d.ts +6 -6
  391. package/dist/types/interrupt.js +21 -21
  392. package/dist/types/interruptState.d.ts +12 -12
  393. package/dist/types/interruptState.js +66 -66
  394. package/dist/types/playground.d.ts +7 -7
  395. package/dist/types/playground.js +14 -14
  396. package/dist/types/settings.d.ts +12 -4
  397. package/dist/types/settings.js +21 -23
  398. package/dist/types/skin.d.ts +1 -1
  399. package/dist/types/theme.d.ts +2 -2
  400. package/dist/types/uischema.d.ts +4 -4
  401. package/dist/types/uischema.js +3 -3
  402. package/dist/utils/colors.d.ts +1 -1
  403. package/dist/utils/colors.js +95 -97
  404. package/dist/utils/config.d.ts +2 -2
  405. package/dist/utils/config.js +48 -48
  406. package/dist/utils/connections.d.ts +2 -2
  407. package/dist/utils/connections.js +15 -15
  408. package/dist/utils/edgeStyling.d.ts +42 -0
  409. package/dist/utils/edgeStyling.js +173 -0
  410. package/dist/utils/errors.js +3 -3
  411. package/dist/utils/fetchWithAuth.d.ts +1 -1
  412. package/dist/utils/fetchWithAuth.js +2 -2
  413. package/dist/utils/handleIds.d.ts +2 -2
  414. package/dist/utils/handleIds.js +8 -8
  415. package/dist/utils/handlePositioning.d.ts +1 -1
  416. package/dist/utils/handlePositioning.js +2 -2
  417. package/dist/utils/icons.d.ts +1 -1
  418. package/dist/utils/icons.js +74 -74
  419. package/dist/utils/logger.d.ts +1 -1
  420. package/dist/utils/logger.js +7 -7
  421. package/dist/utils/nodeIds.d.ts +31 -0
  422. package/dist/utils/nodeIds.js +42 -0
  423. package/dist/utils/nodeStatus.d.ts +1 -1
  424. package/dist/utils/nodeStatus.js +48 -48
  425. package/dist/utils/nodeSwap.d.ts +221 -0
  426. package/dist/utils/nodeSwap.js +680 -0
  427. package/dist/utils/nodeTypes.d.ts +1 -1
  428. package/dist/utils/nodeTypes.js +20 -21
  429. package/dist/utils/nodeWrapper.d.ts +7 -7
  430. package/dist/utils/nodeWrapper.js +19 -21
  431. package/dist/utils/performanceUtils.d.ts +1 -1
  432. package/dist/utils/performanceUtils.js +1 -2
  433. package/dist/utils/portUtils.d.ts +2 -2
  434. package/dist/utils/portUtils.js +1 -1
  435. package/dist/utils/sanitize.js +1 -1
  436. package/dist/utils/uischema.d.ts +2 -2
  437. package/dist/utils/uischema.js +8 -8
  438. package/dist/utils/validation.js +8 -8
  439. package/package.json +12 -11
  440. package/dist/helpers/nodeLayoutHelper.d.ts +0 -14
  441. package/dist/helpers/nodeLayoutHelper.js +0 -19
@@ -5,58 +5,65 @@
5
5
  -->
6
6
 
7
7
  <script lang="ts">
8
- import { onMount } from "svelte";
9
- import MainLayout from "./layouts/MainLayout.svelte";
10
- import WorkflowEditor from "./WorkflowEditor.svelte";
11
- import NodeSidebar from "./NodeSidebar.svelte";
12
- import Icon from "@iconify/svelte";
13
- import ConfigForm from "./ConfigForm.svelte";
14
- import ConfigPanel from "./ConfigPanel.svelte";
15
- import Navbar from "./Navbar.svelte";
16
- import { api, setEndpointConfig } from "../services/api.js";
17
- import { EnhancedFlowDropApiClient } from "../api/enhanced-client.js";
8
+ import { onMount, tick } from 'svelte';
9
+ import MainLayout from './layouts/MainLayout.svelte';
10
+ import WorkflowEditor from './WorkflowEditor.svelte';
11
+ import NodeSidebar from './NodeSidebar.svelte';
12
+ import Icon from '@iconify/svelte';
13
+ import ConfigForm from './ConfigForm.svelte';
14
+ import ConfigPanel from './ConfigPanel.svelte';
15
+ import CommandConsole from './console/CommandConsole.svelte';
16
+ import AIChatPanel from './chat/AIChatPanel.svelte';
17
+ import type { UIAction } from '../commands/index.js';
18
+ import NodeSwapPicker from './NodeSwapPicker.svelte';
19
+ import SwapMappingEditor from './SwapMappingEditor.svelte';
20
+ import Navbar from './Navbar.svelte';
21
+ import { api, setEndpointConfig } from '../services/api.js';
22
+ import { EnhancedFlowDropApiClient } from '../api/enhanced-client.js';
18
23
  import type {
19
24
  NodeMetadata,
20
25
  Workflow,
21
26
  WorkflowNode,
22
27
  ConfigSchema,
23
- NodeUIExtensions,
24
- } from "../types/index.js";
25
- import { DEFAULT_WORKFLOW_FORMAT } from "../types/index.js";
26
- import { createEndpointConfig } from "../config/endpoints.js";
27
- import type { EndpointConfig } from "../config/endpoints.js";
28
- import type { AuthProvider } from "../types/auth.js";
29
- import type {
30
- FlowDropEventHandlers,
31
- FlowDropFeatures,
32
- } from "../types/events.js";
33
- import { mergeFeatures } from "../types/events.js";
34
- import type { FlowDropTheme, FlowDropThemeName } from "../types/theme.js";
35
- import type { FlowDropSkinTokens } from "../types/skin.js";
36
- import { resolveTheme } from "../themes/index.js";
28
+ NodeUIExtensions
29
+ } from '../types/index.js';
30
+ import type { InteractiveSwapState, SwapEventContext } from '../utils/nodeSwap.js';
31
+ import {
32
+ computeInteractiveState,
33
+ buildSwapPreviewFromState,
34
+ executeSwap,
35
+ validateSwapResult
36
+ } from '../utils/nodeSwap.js';
37
+ import type { SwapStrategy } from '../utils/nodeSwap.js';
38
+ import { DEFAULT_WORKFLOW_FORMAT } from '../types/index.js';
39
+ import { createEndpointConfig } from '../config/endpoints.js';
40
+ import type { EndpointConfig } from '../config/endpoints.js';
41
+ import type { AuthProvider } from '../types/auth.js';
42
+ import type { FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
43
+ import { mergeFeatures } from '../types/events.js';
44
+ import type { FlowDropTheme, FlowDropThemeName } from '../types/theme.js';
45
+ import type { FlowDropSkinTokens } from '../types/skin.js';
46
+ import { resolveTheme } from '../themes/index.js';
37
47
  import {
38
48
  getWorkflowStore,
39
49
  workflowActions,
40
50
  getWorkflowName,
41
51
  getWorkflowFormat,
42
- markAsSaved,
43
- } from "../stores/workflowStore.svelte.js";
44
- import {
45
- globalSaveWorkflow,
46
- globalExportWorkflow,
47
- } from "../services/globalSave.js";
48
- import { apiToasts, dismissToast } from "../services/toastService.js";
49
- import { initAutoSave } from "../services/autoSaveService.js";
52
+ markAsSaved
53
+ } from '../stores/workflowStore.svelte.js';
54
+ import { globalSaveWorkflow, globalExportWorkflow } from '../services/globalSave.js';
55
+ import { apiToasts, dismissToast } from '../services/toastService.js';
56
+ import { initAutoSave } from '../services/autoSaveService.js';
57
+ import { getUiSettings, updateSettings } from '../stores/settingsStore.svelte.js';
50
58
  import {
51
- getUiSettings,
52
- updateSettings,
53
- } from "../stores/settingsStore.svelte.js";
54
- import { initializePortCompatibility } from "../utils/connections.js";
55
- import { DEFAULT_PORT_CONFIG } from "../config/defaultPortConfig.js";
56
- import { workflowFormatRegistry } from "../registry/workflowFormatRegistry.js";
57
- import { logger } from "../utils/logger.js";
58
- import { validateWorkflowData } from "../utils/validation.js";
59
- import type { SettingsCategory } from "../types/settings.js";
59
+ initializePortCompatibility,
60
+ getPortCompatibilityChecker
61
+ } from '../utils/connections.js';
62
+ import { DEFAULT_PORT_CONFIG } from '../config/defaultPortConfig.js';
63
+ import { workflowFormatRegistry } from '../registry/workflowFormatRegistry.js';
64
+ import { logger } from '../utils/logger.js';
65
+ import { validateWorkflowData } from '../utils/validation.js';
66
+ import type { SettingsCategory } from '../types/settings.js';
60
67
 
61
68
  /**
62
69
  * Configuration props for runtime customization
@@ -79,10 +86,7 @@
79
86
  /** Read-only mode */
80
87
  readOnly?: boolean;
81
88
  /** Node execution statuses */
82
- nodeStatuses?: Record<
83
- string,
84
- "pending" | "running" | "completed" | "error"
85
- >;
89
+ nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
86
90
  /** Pipeline ID for fetching node execution info */
87
91
  pipelineId?: string;
88
92
  /** Custom navbar title */
@@ -92,7 +96,7 @@
92
96
  label: string;
93
97
  href: string;
94
98
  icon?: string;
95
- variant?: "primary" | "secondary" | "outline";
99
+ variant?: 'primary' | 'secondary' | 'outline';
96
100
  onclick?: (event: Event) => void;
97
101
  }>;
98
102
  /** Show settings gear icon in navbar */
@@ -115,13 +119,15 @@
115
119
  showSettingsSyncButton?: boolean;
116
120
  /** Show the reset buttons in the settings modal */
117
121
  showSettingsResetButton?: boolean;
122
+ /** Pluggable swap strategies — instance-scoped, checked in order */
123
+ swapStrategies?: SwapStrategy[];
118
124
  }
119
125
 
120
126
  let {
121
127
  workflow: initialWorkflow,
122
128
  nodes: propNodes,
123
- height = "100vh",
124
- width = "100%",
129
+ height = '100vh',
130
+ width = '100%',
125
131
  showNavbar = false,
126
132
  disableSidebar = false,
127
133
  lockWorkflow = false,
@@ -140,6 +146,7 @@
140
146
  settingsCategories,
141
147
  showSettingsSyncButton,
142
148
  showSettingsResetButton,
149
+ swapStrategies
143
150
  }: Props = $props();
144
151
 
145
152
  // svelte-ignore state_referenced_locally — feature flags don't change at runtime
@@ -147,9 +154,7 @@
147
154
 
148
155
  // Theme system — resolve named theme or custom object, inject CSS tokens from skin
149
156
  // Explicit prop wins; falls back to user's persisted theme preference from settings
150
- let resolvedTheme = $derived(
151
- resolveTheme(themeProp ?? getUiSettings().theme),
152
- );
157
+ let resolvedTheme = $derived(resolveTheme(themeProp ?? getUiSettings().theme));
153
158
  let themeConfig = $derived(resolvedTheme.config);
154
159
 
155
160
  // Inject skin tokens as a style tag so light/dark palettes can coexist.
@@ -160,19 +165,19 @@
160
165
  const skin = resolvedTheme.skin;
161
166
  const tokens = skin?.tokens;
162
167
  const darkTokens = skin?.darkTokens;
163
- if ((!tokens && !darkTokens) || typeof document === "undefined") return;
168
+ if ((!tokens && !darkTokens) || typeof document === 'undefined') return;
164
169
 
165
170
  const toRules = (dict: FlowDropSkinTokens) =>
166
171
  Object.entries(dict)
167
172
  .map(([k, v]) => ` --fd-${k}: ${v};`)
168
- .join("\n");
173
+ .join('\n');
169
174
 
170
- let css = "";
175
+ let css = '';
171
176
  if (tokens) css += `:root {\n${toRules(tokens)}\n}\n`;
172
177
  if (darkTokens) css += `[data-theme='dark'] {\n${toRules(darkTokens)}\n}\n`;
173
178
 
174
- const style = document.createElement("style");
175
- style.id = "fd-skin-tokens";
179
+ const style = document.createElement('style');
180
+ style.id = 'fd-skin-tokens';
176
181
  document.head.appendChild(style);
177
182
  style.textContent = css;
178
183
 
@@ -180,15 +185,15 @@
180
185
  });
181
186
 
182
187
  // Create breadcrumb-style title - at top level to avoid store subscription issues
183
- let breadcrumbTitle = $derived(() => {
188
+ let breadcrumbTitle = $derived.by(() => {
184
189
  // Use custom navbar title if provided
185
190
  if (navbarTitle) {
186
191
  return navbarTitle;
187
192
  }
188
193
  // Default workflow title logic
189
194
  const wfName = getWorkflowName();
190
- if (!wfName || wfName === "Untitled Workflow") {
191
- return "Workflow / New Workflow";
195
+ if (!wfName || wfName === 'Untitled Workflow') {
196
+ return 'Workflow / New Workflow';
192
197
  }
193
198
  return `Workflow / ${wfName}`;
194
199
  });
@@ -213,43 +218,48 @@
213
218
  // Workflow settings sidebar state
214
219
  let isWorkflowSettingsOpen = $state(false);
215
220
 
221
+ // Node swap state
222
+ let swapMode = $state<'idle' | 'picking' | 'mapping'>('idle');
223
+ let swapTargetMetadata = $state<NodeMetadata | null>(null);
224
+ let swapInteractiveState = $state<InteractiveSwapState | null>(null);
225
+
216
226
  // Workflow configuration schema (derived to pick up dynamic format options)
217
227
  let workflowConfigSchema: ConfigSchema = $derived({
218
- type: "object" as const,
228
+ type: 'object' as const,
219
229
  properties: {
220
230
  name: {
221
- type: "string",
222
- title: "Workflow Name",
223
- description: "The name of the workflow",
224
- default: "",
231
+ type: 'string',
232
+ title: 'Workflow Name',
233
+ description: 'The name of the workflow',
234
+ default: ''
225
235
  },
226
236
  description: {
227
- type: "string",
228
- title: "Description",
229
- description: "A description of the workflow",
230
- format: "multiline",
231
- default: "",
237
+ type: 'string',
238
+ title: 'Description',
239
+ description: 'A description of the workflow',
240
+ format: 'multiline',
241
+ default: ''
232
242
  },
233
243
  format: {
234
- type: "string",
235
- title: "Workflow Format",
236
- description: "The specification format for this workflow",
244
+ type: 'string',
245
+ title: 'Workflow Format',
246
+ description: 'The specification format for this workflow',
237
247
  oneOf: workflowFormatRegistry.getOneOfOptions(),
238
- default: "flowdrop",
239
- },
248
+ default: 'flowdrop'
249
+ }
240
250
  },
241
- required: ["name"],
251
+ required: ['name']
242
252
  });
243
253
 
244
254
  // Workflow configuration values
245
255
  let workflowConfigValues = $derived({
246
- name: getWorkflowName() || "",
247
- description: getWorkflowStore()?.description || "",
248
- format: getWorkflowStore()?.metadata?.format || "flowdrop",
256
+ name: getWorkflowName() || '',
257
+ description: getWorkflowStore()?.description || '',
258
+ format: getWorkflowStore()?.metadata?.format || 'flowdrop'
249
259
  });
250
260
 
251
261
  // Get the current node from the workflow store
252
- let selectedNodeForConfig = $derived(() => {
262
+ let selectedNodeForConfig = $derived.by(() => {
253
263
  const wf = getWorkflowStore();
254
264
  if (!selectedNodeId || !wf) return null;
255
265
  return wf.nodes.find((node) => node.id === selectedNodeId) || null;
@@ -270,18 +280,14 @@
270
280
  // Merge format-provided nodes with prop nodes (deduplicate by ID, props take priority)
271
281
  const formatNodes = workflowFormatRegistry.getAllFormatNodes();
272
282
  const existingIds = new Set(propNodes.map((n) => n.id));
273
- const uniqueFormatNodes = formatNodes.filter(
274
- (n) => !existingIds.has(n.id),
275
- );
283
+ const uniqueFormatNodes = formatNodes.filter((n) => !existingIds.has(n.id));
276
284
  nodes = [...propNodes, ...uniqueFormatNodes];
277
285
  nodeTypesLoading = false;
278
286
  return;
279
287
  }
280
288
 
281
289
  // Show loading toast (if toasts are enabled)
282
- const loadingToast = features.showToasts
283
- ? apiToasts.loading("Loading node types")
284
- : null;
290
+ const loadingToast = features.showToasts ? apiToasts.loading('Loading node types') : null;
285
291
  try {
286
292
  error = null;
287
293
 
@@ -296,9 +302,7 @@
296
302
  // Merge format-provided nodes with API nodes (deduplicate by ID, API takes priority)
297
303
  const formatNodes = workflowFormatRegistry.getAllFormatNodes();
298
304
  const existingIds = new Set(fetchedNodes.map((n) => n.id));
299
- const uniqueFormatNodes = formatNodes.filter(
300
- (n) => !existingIds.has(n.id),
301
- );
305
+ const uniqueFormatNodes = formatNodes.filter((n) => !existingIds.has(n.id));
302
306
  nodes = [...fetchedNodes, ...uniqueFormatNodes];
303
307
  error = null;
304
308
  nodeTypesLoading = false;
@@ -313,13 +317,13 @@
313
317
  dismissToast(loadingToast);
314
318
  }
315
319
 
316
- const errorMessage = err instanceof Error ? err.message : "Unknown error";
320
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
317
321
 
318
322
  // Notify parent via event handler
319
323
  if (eventHandlers?.onApiError) {
320
324
  const suppressToast = eventHandlers.onApiError(
321
325
  err instanceof Error ? err : new Error(errorMessage),
322
- "fetchNodes",
326
+ 'fetchNodes'
323
327
  );
324
328
  if (suppressToast) {
325
329
  // Parent handled the error, keep nodes empty
@@ -332,7 +336,7 @@
332
336
  // Show error and set empty nodes array (no fallback to sample data)
333
337
  error = `API Error: ${errorMessage}. No node types available.`;
334
338
  if (features.showToasts) {
335
- apiToasts.error("Load node types", errorMessage);
339
+ apiToasts.error('Load node types', errorMessage);
336
340
  }
337
341
 
338
342
  // Set empty nodes array instead of fallback data
@@ -353,22 +357,19 @@
353
357
  */
354
358
  async function testApiConnection(): Promise<void> {
355
359
  try {
356
- const baseUrl = endpointConfig?.baseUrl || apiBaseUrl || "/api/flowdrop";
360
+ const baseUrl = endpointConfig?.baseUrl || apiBaseUrl || '/api/flowdrop';
357
361
  const testUrl = `${baseUrl}/nodes`;
358
362
 
359
363
  const response = await fetch(testUrl);
360
364
  const data = await response.json();
361
365
 
362
366
  if (response.ok && data.success) {
363
- apiToasts.success("API connection test", "Connection successful");
367
+ apiToasts.success('API connection test', 'Connection successful');
364
368
  } else {
365
- apiToasts.error("API connection test", "Connection failed");
369
+ apiToasts.error('API connection test', 'Connection failed');
366
370
  }
367
371
  } catch (err) {
368
- apiToasts.error(
369
- "API connection test",
370
- err instanceof Error ? err.message : "Unknown error",
371
- );
372
+ apiToasts.error('API connection test', err instanceof Error ? err.message : 'Unknown error');
372
373
  }
373
374
  }
374
375
 
@@ -384,16 +385,13 @@
384
385
 
385
386
  // Create enhanced API client with authProvider support if provided
386
387
  if (authProvider) {
387
- apiClient = new EnhancedFlowDropApiClient(
388
- propEndpointConfig,
389
- authProvider,
390
- );
388
+ apiClient = new EnhancedFlowDropApiClient(propEndpointConfig, authProvider);
391
389
  }
392
390
  return;
393
391
  }
394
392
 
395
393
  // Second priority: Check if endpoint config is already set (e.g., by parent layout)
396
- const { getEndpointConfig } = await import("../services/api.js");
394
+ const { getEndpointConfig } = await import('../services/api.js');
397
395
  const existingConfig = getEndpointConfig();
398
396
 
399
397
  // If config already exists and no override provided, use existing
@@ -408,19 +406,19 @@
408
406
  }
409
407
 
410
408
  // Third priority: Use provided apiBaseUrl or default
411
- const baseUrl = apiBaseUrl || "/api/flowdrop";
409
+ const baseUrl = apiBaseUrl || '/api/flowdrop';
412
410
 
413
411
  const config = createEndpointConfig(baseUrl, {
414
412
  auth: {
415
- type: "none", // No authentication for now
413
+ type: 'none' // No authentication for now
416
414
  },
417
415
  timeout: 10000, // 10 second timeout
418
416
  retry: {
419
417
  enabled: true,
420
418
  maxAttempts: 2,
421
419
  delay: 1000,
422
- backoff: "exponential",
423
- },
420
+ backoff: 'exponential'
421
+ }
424
422
  });
425
423
 
426
424
  setEndpointConfig(config);
@@ -444,11 +442,19 @@
444
442
  }
445
443
  selectedNodeId = node.id;
446
444
  isConfigSidebarOpen = true;
445
+ // Reset swap state when switching nodes
446
+ swapMode = 'idle';
447
+ swapTargetMetadata = null;
448
+ swapInteractiveState = null;
447
449
  }
448
450
 
449
451
  function closeConfigSidebar(): void {
450
452
  isConfigSidebarOpen = false;
451
453
  selectedNodeId = null;
454
+ // Reset swap state when closing
455
+ swapMode = 'idle';
456
+ swapTargetMetadata = null;
457
+ swapInteractiveState = null;
452
458
  }
453
459
 
454
460
  /**
@@ -462,17 +468,144 @@
462
468
  }
463
469
  }
464
470
 
471
+ /**
472
+ * Start swap mode — transitions the right sidebar to the node picker
473
+ */
474
+ function startSwap(): void {
475
+ swapMode = 'picking';
476
+ swapTargetMetadata = null;
477
+ swapInteractiveState = null;
478
+ }
479
+
480
+ /**
481
+ * Handle selection of a target node type for swap
482
+ */
483
+ function handleSwapSelect(metadata: NodeMetadata): void {
484
+ const node = selectedNodeForConfig;
485
+ if (!node) return;
486
+
487
+ const wf = getWorkflowStore();
488
+ if (!wf) return;
489
+
490
+ // Format compatibility guard — defence-in-depth behind picker's own filter
491
+ const currentFormat = getWorkflowFormat();
492
+ if (metadata.formats?.length && !metadata.formats.includes(currentFormat)) {
493
+ return;
494
+ }
495
+
496
+ // Get port compatibility checker (may be null if not initialized)
497
+ let checker: import('../utils/connections.js').PortCompatibilityChecker | null = null;
498
+ try {
499
+ checker = getPortCompatibilityChecker();
500
+ } catch {
501
+ // Checker not initialized — computeSwapPreview will use exact dataType matching
502
+ }
503
+
504
+ const interactive = computeInteractiveState(node, metadata, wf.edges, wf.nodes, {
505
+ checker,
506
+ strategies: swapStrategies
507
+ });
508
+
509
+ swapTargetMetadata = metadata;
510
+ swapInteractiveState = interactive;
511
+ swapMode = 'mapping';
512
+ }
513
+
514
+ /**
515
+ * Execute the confirmed node swap
516
+ */
517
+ async function executeNodeSwap(finalState?: InteractiveSwapState): Promise<void> {
518
+ const state = finalState ?? swapInteractiveState;
519
+ if (!state) return;
520
+
521
+ const wf = getWorkflowStore();
522
+ if (!wf) return;
523
+
524
+ const oldLabel = state.oldNode.data.label;
525
+ const newLabel = state.newMetadata.name;
526
+
527
+ // Convert interactive state to swap preview
528
+ const preview = buildSwapPreviewFromState(state, wf.edges);
529
+
530
+ // Execute the swap
531
+ const result = executeSwap(state.oldNode, state.newMetadata, preview, wf.nodes, wf.edges);
532
+
533
+ // Post-swap validation
534
+ const validation = validateSwapResult(result);
535
+ if (!validation.valid) {
536
+ logger.error('Swap validation failed:', validation.error);
537
+ return;
538
+ }
539
+
540
+ // onBeforeSwap hook — abort if returns false
541
+ if (eventHandlers?.onBeforeSwap) {
542
+ const swapEventCtx: SwapEventContext = {
543
+ oldNode: state.oldNode,
544
+ newMetadata: state.newMetadata,
545
+ preview,
546
+ portOverrides: [],
547
+ configOverrides: []
548
+ };
549
+ const shouldProceed = await eventHandlers.onBeforeSwap(swapEventCtx);
550
+ if (shouldProceed === false) return;
551
+ }
552
+
553
+ // Apply as a single atomic swap with descriptive history entry
554
+ workflowActions.swapNode({
555
+ nodes: result.updatedNodes,
556
+ edges: result.updatedEdges,
557
+ description: `Swap node: ${oldLabel} → ${newLabel}`
558
+ });
559
+
560
+ // onAfterSwap hook (fire-and-forget — swap is already applied)
561
+ if (eventHandlers?.onAfterSwap) {
562
+ try {
563
+ eventHandlers.onAfterSwap(result, state.oldNode, state.newNodeId);
564
+ } catch (err) {
565
+ logger.error('onAfterSwap hook error:', err);
566
+ }
567
+ }
568
+
569
+ // Select the new node in the sidebar
570
+ const newNodeId = state.newNodeId;
571
+ selectedNodeId = newNodeId;
572
+
573
+ // Reset swap state
574
+ swapMode = 'idle';
575
+ swapTargetMetadata = null;
576
+ swapInteractiveState = null;
577
+
578
+ // Wait for SvelteFlow to process the new node before updating visual state
579
+ await tick();
580
+
581
+ // Refresh the editor visual state
582
+ if (workflowEditorRef) {
583
+ const newNode = result.updatedNodes.find((n) => n.id === newNodeId);
584
+ if (newNode) {
585
+ workflowEditorRef.updateNodeData(newNodeId, newNode.data);
586
+ await workflowEditorRef.refreshEdgePositions(newNodeId);
587
+ }
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Cancel swap and return to normal config view
593
+ */
594
+ function cancelSwap(): void {
595
+ swapMode = 'idle';
596
+ swapTargetMetadata = null;
597
+ swapInteractiveState = null;
598
+ }
599
+
465
600
  /**
466
601
  * Handle workflow configuration save
467
602
  */
468
- async function handleWorkflowSave(
469
- config: Record<string, unknown>,
470
- ): Promise<void> {
603
+ async function handleWorkflowSave(config: Record<string, unknown>): Promise<void> {
471
604
  // Update the workflow store
472
605
  if (getWorkflowStore()) {
473
606
  workflowActions.batchUpdate({
474
607
  name: config.name as string | undefined,
475
- description: config.description as string | undefined,
608
+ description: config.description as string | undefined
476
609
  });
477
610
  }
478
611
 
@@ -483,7 +616,7 @@
483
616
  try {
484
617
  await saveWorkflow();
485
618
  } catch (error) {
486
- logger.error("Failed to save workflow to backend:", error);
619
+ logger.error('Failed to save workflow to backend:', error);
487
620
  // Note: We don't throw the error here to avoid breaking the UI flow
488
621
  // The user can still manually save via the main Save button if needed
489
622
  }
@@ -500,7 +633,7 @@
500
633
  apiClient: apiClient ?? undefined,
501
634
  eventHandlers,
502
635
  features,
503
- onMarkAsSaved: markAsSaved,
636
+ onMarkAsSaved: markAsSaved
504
637
  });
505
638
  }
506
639
 
@@ -524,45 +657,38 @@
524
657
  reader.onload = (event) => {
525
658
  try {
526
659
  const text = event.target?.result;
527
- if (typeof text !== "string") {
528
- throw new Error("Could not read file contents.");
660
+ if (typeof text !== 'string') {
661
+ throw new Error('Could not read file contents.');
529
662
  }
530
663
  const data = JSON.parse(text);
531
664
  const validation = validateWorkflowData(data);
532
665
  if (!validation.valid) {
533
666
  if (features.showToasts) {
534
- apiToasts.error(
535
- "Import workflow",
536
- validation.error ?? "Invalid workflow JSON",
537
- );
667
+ apiToasts.error('Import workflow', validation.error ?? 'Invalid workflow JSON');
538
668
  }
539
- logger.warn("Workflow import validation failed:", validation.error);
669
+ logger.warn('Workflow import validation failed:', validation.error);
540
670
  return;
541
671
  }
542
672
  workflowActions.initialize(data as Workflow);
543
673
  if (features.showToasts) {
544
- apiToasts.success(
545
- "Import workflow",
546
- "Workflow imported successfully",
547
- );
674
+ apiToasts.success('Import workflow', 'Workflow imported successfully');
548
675
  }
549
676
  if (eventHandlers?.onWorkflowLoad) {
550
677
  eventHandlers.onWorkflowLoad(data as Workflow);
551
678
  }
552
679
  } catch (error) {
553
- const errorObj =
554
- error instanceof Error ? error : new Error("Unknown error occurred");
555
- logger.error("Workflow import failed:", errorObj);
680
+ const errorObj = error instanceof Error ? error : new Error('Unknown error occurred');
681
+ logger.error('Workflow import failed:', errorObj);
556
682
  if (features.showToasts) {
557
- apiToasts.error("Import workflow", errorObj.message);
683
+ apiToasts.error('Import workflow', errorObj.message);
558
684
  }
559
685
  }
560
686
  };
561
687
  reader.onerror = () => {
562
- const message = "Failed to read the selected file.";
688
+ const message = 'Failed to read the selected file.';
563
689
  logger.error(message);
564
690
  if (features.showToasts) {
565
- apiToasts.error("Import workflow", message);
691
+ apiToasts.error('Import workflow', message);
566
692
  }
567
693
  };
568
694
  reader.readAsText(file);
@@ -578,15 +704,13 @@
578
704
  importWorkflow(file);
579
705
  }
580
706
  // Reset input so same file can be re-imported
581
- input.value = "";
707
+ input.value = '';
582
708
  }
583
709
 
584
710
  // Function to handle clicks outside the sidebar
585
711
  function handleCanvasClick(event: MouseEvent): void {
586
712
  // Check if the click is outside the right sidebar
587
- const rightSidebar = document.querySelector(
588
- ".flowdrop-main-layout__sidebar--right",
589
- );
713
+ const rightSidebar = document.querySelector('.flowdrop-main-layout__sidebar--right');
590
714
  if (rightSidebar && !rightSidebar.contains(event.target as Node)) {
591
715
  // Close sidebar when clicking outside of it
592
716
  if (isConfigSidebarOpen) {
@@ -619,21 +743,21 @@
619
743
  // Initialize with a default empty workflow so the editor is functional
620
744
  // (e.g., drag-and-drop requires a non-null workflow in the store)
621
745
  const defaultWorkflow: Workflow = {
622
- id: "",
623
- name: "Untitled Workflow",
746
+ id: '',
747
+ name: 'Untitled Workflow',
624
748
  nodes: [],
625
749
  edges: [],
626
750
  metadata: {
627
- version: "1.0.0",
751
+ version: '1.0.0',
628
752
  format: DEFAULT_WORKFLOW_FORMAT,
629
753
  createdAt: new Date().toISOString(),
630
- updatedAt: new Date().toISOString(),
631
- },
754
+ updatedAt: new Date().toISOString()
755
+ }
632
756
  };
633
757
  workflowActions.initialize(defaultWorkflow);
634
758
  }
635
759
  } catch (error) {
636
- logger.error("Failed to initialize editor:", error);
760
+ logger.error('Failed to initialize editor:', error);
637
761
  }
638
762
  })();
639
763
 
@@ -642,10 +766,7 @@
642
766
  toggleWorkflowSettings();
643
767
  };
644
768
 
645
- window.addEventListener(
646
- "workflow-settings-toggle",
647
- handleWorkflowSettingsToggle,
648
- );
769
+ window.addEventListener('workflow-settings-toggle', handleWorkflowSettingsToggle);
649
770
 
650
771
  // Initialize auto-save based on user settings
651
772
  const cleanupAutoSave = initAutoSave({
@@ -654,18 +775,15 @@
654
775
  },
655
776
  onError: (error) => {
656
777
  // Don't show toast for auto-save errors to avoid noise
657
- logger.warn("Auto-save failed:", error);
778
+ logger.warn('Auto-save failed:', error);
658
779
  },
659
780
  onSuccess: () => {
660
- logger.debug("Auto-saved workflow");
661
- },
781
+ logger.debug('Auto-saved workflow');
782
+ }
662
783
  });
663
784
 
664
785
  return () => {
665
- window.removeEventListener(
666
- "workflow-settings-toggle",
667
- handleWorkflowSettingsToggle,
668
- );
786
+ window.removeEventListener('workflow-settings-toggle', handleWorkflowSettingsToggle);
669
787
  cleanupAutoSave();
670
788
  };
671
789
  });
@@ -675,7 +793,7 @@
675
793
  * Config panel always appears on the right side
676
794
  */
677
795
  const hasConfigPanelOpen = $derived(
678
- isWorkflowSettingsOpen || !!selectedNodeForConfig(),
796
+ isWorkflowSettingsOpen || !!selectedNodeForConfig || swapMode !== 'idle'
679
797
  );
680
798
  const showRightPanel = $derived(!disableSidebar && hasConfigPanelOpen);
681
799
 
@@ -684,7 +802,7 @@
684
802
  * When collapsed, use 0; otherwise use user-configured width
685
803
  */
686
804
  const leftSidebarWidth = $derived(
687
- getUiSettings().sidebarCollapsed ? 0 : getUiSettings().sidebarWidth,
805
+ getUiSettings().sidebarCollapsed ? 0 : getUiSettings().sidebarWidth
688
806
  );
689
807
 
690
808
  /** Whether the sidebar is collapsed */
@@ -693,20 +811,82 @@
693
811
  /** Toggle sidebar collapsed state */
694
812
  function toggleSidebar(): void {
695
813
  updateSettings({
696
- ui: { sidebarCollapsed: !getUiSettings().sidebarCollapsed },
814
+ ui: { sidebarCollapsed: !getUiSettings().sidebarCollapsed }
697
815
  });
698
816
  }
699
817
 
700
818
  // File input reference for workflow import
701
819
  let fileInputRef = $state<HTMLInputElement | null>(null);
820
+
821
+ /**
822
+ * Handle global keyboard shortcut for console toggle.
823
+ * Backtick (`) toggles the console open/closed unless user is typing in an input.
824
+ */
825
+ function handleGlobalKeydown(event: KeyboardEvent): void {
826
+ // Dead key on international keyboards — do not intercept
827
+ if (event.key === 'Dead') return;
828
+
829
+ if (event.key !== '`') return;
830
+
831
+ // Don't intercept when user is typing in an input, textarea, or contenteditable
832
+ const target = event.target as HTMLElement;
833
+ const isInputElement =
834
+ target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
835
+
836
+ if (isInputElement) return;
837
+
838
+ event.preventDefault();
839
+ toggleConsole();
840
+ }
841
+
842
+ function handleConsoleUIAction(action: UIAction): void {
843
+ if (action.type === 'open_config') {
844
+ const wf = getWorkflowStore();
845
+ if (!wf) return;
846
+ const node = wf.nodes.find((n) => n.id === action.nodeId);
847
+ if (node) openConfigSidebar(node);
848
+ } else if (action.type === 'select_node') {
849
+ selectedNodeId = action.nodeId;
850
+ } else if (action.type === 'canvas_fit_view') {
851
+ workflowEditorRef?.canvasFitView();
852
+ } else if (action.type === 'canvas_zoom_in') {
853
+ workflowEditorRef?.canvasZoomIn();
854
+ } else if (action.type === 'canvas_zoom_out') {
855
+ workflowEditorRef?.canvasZoomOut();
856
+ } else if (action.type === 'canvas_zoom_to') {
857
+ workflowEditorRef?.canvasZoomTo(action.level);
858
+ } else if (action.type === 'canvas_pan_to') {
859
+ workflowEditorRef?.canvasPanTo(action.position.x, action.position.y);
860
+ } else if (action.type === 'canvas_reset_view') {
861
+ workflowEditorRef?.canvasResetView();
862
+ }
863
+ }
864
+
865
+ function toggleConsole(): void {
866
+ const currentOpen = getUiSettings().consoleOpen;
867
+ updateSettings({ ui: { consoleOpen: !currentOpen } });
868
+
869
+ // Focus management after DOM update
870
+ tick().then(() => {
871
+ if (currentOpen) {
872
+ // Console was open, now closing — focus the canvas
873
+ const canvas = document.querySelector<HTMLElement>('.flowdrop-editor-main');
874
+ canvas?.focus();
875
+ } else {
876
+ // Console was closed, now opening — focus first focusable element inside console
877
+ const consoleEl = document.querySelector<HTMLElement>('.command-console');
878
+ const focusTarget = consoleEl?.querySelector<HTMLElement>('input, button, [tabindex]');
879
+ focusTarget?.focus();
880
+ }
881
+ });
882
+ }
702
883
  </script>
703
884
 
885
+ <svelte:window onkeydown={handleGlobalKeydown} />
886
+
704
887
  <svelte:head>
705
888
  <title>FlowDrop - Visual Workflow Manager</title>
706
- <meta
707
- name="description"
708
- content="A modern drag-and-drop workflow editor for LLM applications"
709
- />
889
+ <meta name="description" content="A modern drag-and-drop workflow editor for LLM applications" />
710
890
  </svelte:head>
711
891
 
712
892
  <!-- Hidden file input for workflow JSON import -->
@@ -724,7 +904,8 @@
724
904
  showHeader={showNavbar}
725
905
  showLeftSidebar={!disableSidebar}
726
906
  showRightSidebar={showRightPanel}
727
- showBottomPanel={false}
907
+ showBottomPanel={getUiSettings().consoleOpen && !readOnly && !lockWorkflow}
908
+ bottomPanelHeight={getUiSettings().consoleHeight}
728
909
  showFooter={false}
729
910
  headerHeight={60}
730
911
  {leftSidebarWidth}
@@ -740,50 +921,50 @@
740
921
  <!-- Header: Navbar -->
741
922
  {#snippet header()}
742
923
  <Navbar
743
- title={breadcrumbTitle()}
924
+ title={breadcrumbTitle}
744
925
  primaryActions={navbarActions.length > 0
745
926
  ? navbarActions
746
927
  : [
747
928
  {
748
- label: "Save",
749
- href: "#save",
750
- icon: "heroicons:document-arrow-down",
751
- variant: "primary",
929
+ label: 'Save',
930
+ href: '#save',
931
+ icon: 'heroicons:document-arrow-down',
932
+ variant: 'primary',
752
933
  onclick: (e) => {
753
934
  e.preventDefault();
754
935
  saveWorkflow();
755
- },
936
+ }
756
937
  },
757
938
  {
758
- label: "Export",
759
- href: "#export",
760
- icon: "heroicons:arrow-down-tray",
761
- variant: "outline",
939
+ label: 'Export',
940
+ href: '#export',
941
+ icon: 'heroicons:arrow-down-tray',
942
+ variant: 'outline',
762
943
  onclick: (e) => {
763
944
  e.preventDefault();
764
945
  exportWorkflow();
765
- },
946
+ }
766
947
  },
767
948
  {
768
- label: "Import",
769
- href: "#import",
770
- icon: "heroicons:arrow-up-tray",
771
- variant: "outline",
949
+ label: 'Import',
950
+ href: '#import',
951
+ icon: 'heroicons:arrow-up-tray',
952
+ variant: 'outline',
772
953
  onclick: (e) => {
773
954
  e.preventDefault();
774
955
  fileInputRef?.click();
775
- },
956
+ }
776
957
  },
777
958
  {
778
- label: "Workflow Settings",
779
- href: "#settings",
780
- icon: "heroicons:cog-6-tooth",
781
- variant: "outline",
959
+ label: 'Workflow Settings',
960
+ href: '#settings',
961
+ icon: 'heroicons:cog-6-tooth',
962
+ variant: 'outline',
782
963
  onclick: (e) => {
783
964
  e.preventDefault();
784
965
  toggleWorkflowSettings();
785
- },
786
- },
966
+ }
967
+ }
787
968
  ]}
788
969
  showStatus={true}
789
970
  {showSettings}
@@ -799,26 +980,51 @@
799
980
  {nodes}
800
981
  loading={nodeTypesLoading}
801
982
  activeFormat={getWorkflowFormat()}
802
- categoriesDefaultOpen={themeConfig?.sidebar?.categoriesDefaultOpen ??
803
- false}
983
+ categoriesDefaultOpen={themeConfig?.sidebar?.categoriesDefaultOpen ?? false}
804
984
  />
805
985
  {/snippet}
806
986
 
807
- <!-- Right Sidebar: Configuration or Workflow Settings -->
987
+ <!-- Right Sidebar: Configuration, Swap, or Workflow Settings -->
808
988
  {#snippet rightSidebar()}
809
- {#if isWorkflowSettingsOpen}
989
+ {#if swapMode === 'mapping' && swapInteractiveState && selectedNodeForConfig}
990
+ {@const swapChecker = (() => {
991
+ try {
992
+ return getPortCompatibilityChecker();
993
+ } catch {
994
+ return null;
995
+ }
996
+ })()}
997
+ <SwapMappingEditor
998
+ interactiveState={swapInteractiveState}
999
+ checker={swapChecker}
1000
+ onConfirm={executeNodeSwap}
1001
+ onCancel={cancelSwap}
1002
+ onBack={() => {
1003
+ swapMode = 'picking';
1004
+ swapInteractiveState = null;
1005
+ }}
1006
+ />
1007
+ {:else if swapMode === 'picking' && selectedNodeForConfig}
1008
+ <NodeSwapPicker
1009
+ currentNode={selectedNodeForConfig}
1010
+ availableNodes={nodes}
1011
+ activeFormat={getWorkflowFormat()}
1012
+ onSelect={handleSwapSelect}
1013
+ onCancel={cancelSwap}
1014
+ />
1015
+ {:else if isWorkflowSettingsOpen}
810
1016
  <ConfigPanel
811
1017
  title="Workflow Settings"
812
1018
  id={getWorkflowStore()?.id}
813
1019
  details={[
814
1020
  {
815
- label: "Nodes",
816
- value: String(getWorkflowStore()?.nodes?.length ?? 0),
1021
+ label: 'Nodes',
1022
+ value: String(getWorkflowStore()?.nodes?.length ?? 0)
817
1023
  },
818
1024
  {
819
- label: "Connections",
820
- value: String(getWorkflowStore()?.edges?.length ?? 0),
821
- },
1025
+ label: 'Connections',
1026
+ value: String(getWorkflowStore()?.edges?.length ?? 0)
1027
+ }
822
1028
  ]}
823
1029
  configTitle="Settings"
824
1030
  onClose={() => (isWorkflowSettingsOpen = false)}
@@ -832,25 +1038,19 @@
832
1038
  // Sync workflow settings changes immediately on field blur
833
1039
  const wf = getWorkflowStore();
834
1040
  if (wf) {
835
- const newFormat =
836
- (config.format as string) || DEFAULT_WORKFLOW_FORMAT;
837
- const currentFormat =
838
- wf.metadata?.format || DEFAULT_WORKFLOW_FORMAT;
1041
+ const newFormat = (config.format as string) || DEFAULT_WORKFLOW_FORMAT;
1042
+ const currentFormat = wf.metadata?.format || DEFAULT_WORKFLOW_FORMAT;
839
1043
 
840
1044
  // Warn about incompatible nodes when format changes
841
1045
  if (newFormat !== currentFormat) {
842
1046
  const incompatibleNodes = wf.nodes?.filter((node) => {
843
1047
  const formats = node.data?.metadata?.formats;
844
- return (
845
- formats &&
846
- formats.length > 0 &&
847
- !formats.includes(newFormat)
848
- );
1048
+ return formats && formats.length > 0 && !formats.includes(newFormat);
849
1049
  });
850
1050
  if (incompatibleNodes && incompatibleNodes.length > 0) {
851
1051
  logger.warn(
852
1052
  `Format changed to '${newFormat}'. ${incompatibleNodes.length} node(s) are not compatible with this format and may not export correctly:`,
853
- incompatibleNodes.map((n) => n.data?.label || n.type),
1053
+ incompatibleNodes.map((n) => n.data?.label || n.type)
854
1054
  );
855
1055
  }
856
1056
  }
@@ -860,31 +1060,31 @@
860
1060
  description: config.description as string | undefined,
861
1061
  metadata: {
862
1062
  ...wf.metadata,
863
- format: newFormat,
864
- },
1063
+ format: newFormat
1064
+ }
865
1065
  });
866
1066
  }
867
1067
  }}
868
1068
  />
869
1069
  </ConfigPanel>
870
- {:else if selectedNodeForConfig()}
871
- {@const currentNode = selectedNodeForConfig()!}
1070
+ {:else if selectedNodeForConfig}
1071
+ {@const currentNode = selectedNodeForConfig}
872
1072
  <ConfigPanel
873
1073
  title={currentNode.data.label}
874
1074
  id={currentNode.id}
875
- description={currentNode.data.metadata?.description ||
876
- "Node configuration"}
1075
+ description={currentNode.data.metadata?.description || 'Node configuration'}
877
1076
  details={[
878
1077
  {
879
- label: "Type",
880
- value: currentNode.data.metadata?.type || currentNode.type,
1078
+ label: 'Type',
1079
+ value: currentNode.data.metadata?.type || currentNode.type
881
1080
  },
882
1081
  {
883
- label: "Category",
884
- value: currentNode.data.metadata?.category || "general",
885
- },
1082
+ label: 'Category',
1083
+ value: currentNode.data.metadata?.category || 'general'
1084
+ }
886
1085
  ]}
887
1086
  onClose={closeConfigSidebar}
1087
+ onSwap={!readOnly && !lockWorkflow && features.enableNodeSwap ? startSwap : undefined}
888
1088
  >
889
1089
  <ConfigForm
890
1090
  {authProvider}
@@ -898,20 +1098,20 @@
898
1098
  // Build the updated node data
899
1099
  const updatedData = {
900
1100
  ...currentNode.data,
901
- config: updatedConfig,
1101
+ config: updatedConfig
902
1102
  };
903
1103
 
904
1104
  // Include UI extensions if provided
905
1105
  if (uiExtensions) {
906
1106
  updatedData.extensions = {
907
1107
  ...currentNode.data.extensions,
908
- ui: uiExtensions,
1108
+ ui: uiExtensions
909
1109
  };
910
1110
  }
911
1111
 
912
1112
  // Update the node in the workflow store
913
1113
  const nodeUpdates: Record<string, unknown> = {
914
- data: updatedData,
1114
+ data: updatedData
915
1115
  };
916
1116
 
917
1117
  workflowActions.updateNode(selectedNodeId, nodeUpdates);
@@ -929,22 +1129,57 @@
929
1129
  {/if}
930
1130
  {/snippet}
931
1131
 
1132
+ <!-- Bottom Panel: Tabbed Console / AI Chat -->
1133
+ {#snippet bottomPanel()}
1134
+ <div class="bottom-panel-tabs">
1135
+ <div class="bottom-panel-tabs__bar">
1136
+ <button
1137
+ class="bottom-panel-tabs__tab {getUiSettings().bottomPanelTab === 'console'
1138
+ ? 'bottom-panel-tabs__tab--active'
1139
+ : ''}"
1140
+ onclick={() => updateSettings({ ui: { bottomPanelTab: 'console' } })}
1141
+ >
1142
+ Console
1143
+ </button>
1144
+ <button
1145
+ class="bottom-panel-tabs__tab {getUiSettings().bottomPanelTab === 'chat'
1146
+ ? 'bottom-panel-tabs__tab--active'
1147
+ : ''}"
1148
+ onclick={() => updateSettings({ ui: { bottomPanelTab: 'chat' } })}
1149
+ >
1150
+ AI Chat
1151
+ </button>
1152
+ </div>
1153
+ <div class="bottom-panel-tabs__content">
1154
+ <div
1155
+ class="bottom-panel-tabs__panel"
1156
+ style:display={getUiSettings().bottomPanelTab === 'console' ? 'contents' : 'none'}
1157
+ >
1158
+ <CommandConsole nodeTypes={nodes} onUIAction={handleConsoleUIAction} />
1159
+ </div>
1160
+ <div
1161
+ class="bottom-panel-tabs__panel"
1162
+ style:display={getUiSettings().bottomPanelTab === 'chat' ? 'flex' : 'none'}
1163
+ >
1164
+ <AIChatPanel
1165
+ nodeTypes={nodes}
1166
+ workflowId={getWorkflowStore()?.id}
1167
+ onUIAction={handleConsoleUIAction}
1168
+ {endpointConfig}
1169
+ />
1170
+ </div>
1171
+ </div>
1172
+ </div>
1173
+ {/snippet}
1174
+
932
1175
  <!-- Main Content: Workflow Editor with Error Status -->
933
1176
  <!-- Status Display: aria-live announces API errors dynamically without requiring focus -->
934
1177
  {#if error}
935
- <div
936
- class="flowdrop-status flowdrop-status--error"
937
- aria-live="polite"
938
- aria-atomic="true"
939
- >
1178
+ <div class="flowdrop-status flowdrop-status--error" aria-live="polite" aria-atomic="true">
940
1179
  <div class="flowdrop-status__content">
941
1180
  <div class="flowdrop-flex flowdrop-gap--3">
942
- <div
943
- class="flowdrop-status__indicator flowdrop-status__indicator--error"
944
- ></div>
945
- <span class="flowdrop-text--sm flowdrop-font--medium"
946
- >Error: {error}</span
947
- >
1181
+ <div class="flowdrop-status__indicator flowdrop-status__indicator--error"></div>
1182
+ <span class="flowdrop-text--sm flowdrop-font--medium">Error: {error}</span>
948
1183
  </div>
949
1184
  <div class="flowdrop-flex flowdrop-gap--2">
950
1185
  <button
@@ -957,8 +1192,8 @@
957
1192
  <button
958
1193
  class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline"
959
1194
  onclick={() => {
960
- const defaultUrl = "/api/flowdrop";
961
- const newUrl = prompt("Enter Backend API URL:", defaultUrl);
1195
+ const defaultUrl = '/api/flowdrop';
1196
+ const newUrl = prompt('Enter Backend API URL:', defaultUrl);
962
1197
  if (newUrl) {
963
1198
  const endpointConfig = createEndpointConfig(newUrl);
964
1199
  setEndpointConfig(endpointConfig);
@@ -993,11 +1228,9 @@
993
1228
  <div
994
1229
  class="flowdrop-editor-main"
995
1230
  class:pipeline-view={!!pipelineId}
996
- style="--fd-canvas-left-offset: {!disableSidebar
997
- ? leftSidebarWidth + 'px'
998
- : '0px'}"
1231
+ style="--fd-canvas-left-offset: {!disableSidebar ? leftSidebarWidth + 'px' : '0px'}"
999
1232
  onclick={handleCanvasClick}
1000
- onkeydown={(e) => e.key === "Escape" && closeConfigSidebar()}
1233
+ onkeydown={(e) => e.key === 'Escape' && closeConfigSidebar()}
1001
1234
  role="region"
1002
1235
  aria-label="Workflow canvas"
1003
1236
  >
@@ -1006,12 +1239,10 @@
1006
1239
  <button
1007
1240
  class="flowdrop-sidebar-fab"
1008
1241
  onclick={toggleSidebar}
1009
- aria-label={isSidebarCollapsed
1010
- ? "Expand sidebar"
1011
- : "Collapse sidebar"}
1012
- title={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
1242
+ aria-label={isSidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
1243
+ title={isSidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
1013
1244
  >
1014
- <Icon icon={isSidebarCollapsed ? "mdi:menu" : "mdi:menu-open"} />
1245
+ <Icon icon={isSidebarCollapsed ? 'mdi:menu' : 'mdi:menu-open'} />
1015
1246
  </button>
1016
1247
  {/if}
1017
1248
 
@@ -1022,13 +1253,15 @@
1022
1253
  {width}
1023
1254
  endpointConfig={endpointConfig ?? undefined}
1024
1255
  {isConfigSidebarOpen}
1025
- selectedNodeForConfig={selectedNodeForConfig()}
1256
+ {selectedNodeForConfig}
1026
1257
  {openConfigSidebar}
1027
1258
  {closeConfigSidebar}
1028
1259
  {lockWorkflow}
1029
1260
  {readOnly}
1030
1261
  {nodeStatuses}
1031
1262
  {pipelineId}
1263
+ consoleOpen={getUiSettings().consoleOpen}
1264
+ onToggleConsole={toggleConsole}
1032
1265
  />
1033
1266
  </div>
1034
1267
  </MainLayout>
@@ -1182,4 +1415,56 @@
1182
1415
  overflow: hidden;
1183
1416
  background: var(--fd-layout-background);
1184
1417
  }
1418
+
1419
+ /* Bottom panel tab system */
1420
+ .bottom-panel-tabs {
1421
+ display: flex;
1422
+ flex-direction: column;
1423
+ height: 100%;
1424
+ overflow: hidden;
1425
+ }
1426
+
1427
+ .bottom-panel-tabs__bar {
1428
+ display: flex;
1429
+ gap: 0;
1430
+ background: var(--fd-muted);
1431
+ border-bottom: 1px solid var(--fd-border);
1432
+ flex-shrink: 0;
1433
+ }
1434
+
1435
+ .bottom-panel-tabs__tab {
1436
+ padding: 0.375rem 0.75rem;
1437
+ font-size: 0.75rem;
1438
+ font-weight: 500;
1439
+ cursor: pointer;
1440
+ border: none;
1441
+ border-bottom: 2px solid transparent;
1442
+ background: transparent;
1443
+ color: var(--fd-muted-foreground);
1444
+ transition: all var(--fd-transition-fast);
1445
+ }
1446
+
1447
+ .bottom-panel-tabs__tab:hover {
1448
+ color: var(--fd-foreground);
1449
+ background: var(--fd-background);
1450
+ }
1451
+
1452
+ .bottom-panel-tabs__tab--active {
1453
+ color: var(--fd-foreground);
1454
+ border-bottom-color: var(--fd-primary);
1455
+ background: var(--fd-background);
1456
+ }
1457
+
1458
+ .bottom-panel-tabs__content {
1459
+ flex: 1;
1460
+ overflow: hidden;
1461
+ display: flex;
1462
+ flex-direction: column;
1463
+ }
1464
+
1465
+ .bottom-panel-tabs__panel {
1466
+ flex: 1;
1467
+ overflow: hidden;
1468
+ flex-direction: column;
1469
+ }
1185
1470
  </style>