@flowdrop/flowdrop 1.0.0 → 1.1.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 (388) hide show
  1. package/README.md +50 -50
  2. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  3. package/dist/adapters/WorkflowAdapter.js +25 -25
  4. package/dist/adapters/agentspec/AgentSpecAdapter.d.ts +2 -2
  5. package/dist/adapters/agentspec/AgentSpecAdapter.js +133 -122
  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 +1 -1
  9. package/dist/adapters/agentspec/autoLayout.js +2 -2
  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 +22 -20
  18. package/dist/api/enhanced-client.d.ts +3 -3
  19. package/dist/api/enhanced-client.js +73 -72
  20. package/dist/components/App.svelte +1081 -961
  21. package/dist/components/App.svelte.d.ts +9 -6
  22. package/dist/components/CanvasBanner.stories.svelte +23 -20
  23. package/dist/components/CanvasBanner.stories.svelte.d.ts +1 -1
  24. package/dist/components/CanvasBanner.svelte +46 -46
  25. package/dist/components/ConfigForm.svelte +1164 -1065
  26. package/dist/components/ConfigForm.svelte.d.ts +2 -2
  27. package/dist/components/ConfigModal.svelte +180 -180
  28. package/dist/components/ConfigModal.svelte.d.ts +1 -1
  29. package/dist/components/ConfigPanel.stories.svelte +35 -35
  30. package/dist/components/ConfigPanel.stories.svelte.d.ts +1 -1
  31. package/dist/components/ConfigPanel.svelte +178 -167
  32. package/dist/components/ConfigPanel.svelte.d.ts +1 -1
  33. package/dist/components/ConnectionLine.svelte +25 -25
  34. package/dist/components/EdgeRefresher.svelte +26 -26
  35. package/dist/components/FlowDropEdge.stories.svelte +197 -0
  36. package/dist/components/FlowDropEdge.stories.svelte.d.ts +26 -0
  37. package/dist/components/FlowDropEdge.svelte +168 -0
  38. package/dist/components/FlowDropEdge.svelte.d.ts +4 -0
  39. package/dist/components/FlowDropZone.svelte +63 -60
  40. package/dist/components/FlowDropZone.svelte.d.ts +1 -1
  41. package/dist/components/LoadingSpinner.stories.svelte +19 -19
  42. package/dist/components/LoadingSpinner.stories.svelte.d.ts +1 -1
  43. package/dist/components/LoadingSpinner.svelte +21 -21
  44. package/dist/components/LoadingSpinner.svelte.d.ts +1 -1
  45. package/dist/components/Logo.stories.svelte +13 -13
  46. package/dist/components/Logo.stories.svelte.d.ts +1 -1
  47. package/dist/components/Logo.svelte +101 -95
  48. package/dist/components/LogsSidebar.svelte +553 -546
  49. package/dist/components/LogsSidebar.svelte.d.ts +1 -1
  50. package/dist/components/MarkdownDisplay.stories.svelte +29 -23
  51. package/dist/components/MarkdownDisplay.stories.svelte.d.ts +1 -1
  52. package/dist/components/MarkdownDisplay.svelte +16 -14
  53. package/dist/components/Navbar.stories.svelte +43 -38
  54. package/dist/components/Navbar.stories.svelte.d.ts +1 -1
  55. package/dist/components/Navbar.svelte +760 -706
  56. package/dist/components/Navbar.svelte.d.ts +1 -1
  57. package/dist/components/NodeSidebar.svelte +900 -746
  58. package/dist/components/NodeSidebar.svelte.d.ts +3 -1
  59. package/dist/components/NodeStatusOverlay.stories.svelte +82 -70
  60. package/dist/components/NodeStatusOverlay.stories.svelte.d.ts +1 -1
  61. package/dist/components/NodeStatusOverlay.svelte +295 -280
  62. package/dist/components/NodeStatusOverlay.svelte.d.ts +3 -3
  63. package/dist/components/PipelineStatus.svelte +326 -300
  64. package/dist/components/PipelineStatus.svelte.d.ts +4 -4
  65. package/dist/components/PortCoordinateTracker.svelte +49 -47
  66. package/dist/components/PortCoordinateTracker.svelte.d.ts +1 -1
  67. package/dist/components/ReadOnlyDetails.svelte +156 -156
  68. package/dist/components/SchemaForm.stories.svelte +106 -98
  69. package/dist/components/SchemaForm.stories.svelte.d.ts +1 -1
  70. package/dist/components/SchemaForm.svelte +490 -463
  71. package/dist/components/SchemaForm.svelte.d.ts +2 -2
  72. package/dist/components/SettingsModal.svelte +226 -223
  73. package/dist/components/SettingsModal.svelte.d.ts +1 -1
  74. package/dist/components/SettingsPanel.svelte +637 -601
  75. package/dist/components/SettingsPanel.svelte.d.ts +1 -1
  76. package/dist/components/StatusIcon.stories.svelte +62 -49
  77. package/dist/components/StatusIcon.stories.svelte.d.ts +1 -1
  78. package/dist/components/StatusIcon.svelte +87 -87
  79. package/dist/components/StatusIcon.svelte.d.ts +2 -2
  80. package/dist/components/StatusLabel.stories.svelte +12 -12
  81. package/dist/components/StatusLabel.stories.svelte.d.ts +1 -1
  82. package/dist/components/StatusLabel.svelte +19 -19
  83. package/dist/components/ThemeToggle.stories.svelte +16 -16
  84. package/dist/components/ThemeToggle.stories.svelte.d.ts +1 -1
  85. package/dist/components/ThemeToggle.svelte +180 -169
  86. package/dist/components/ThemeToggle.svelte.d.ts +1 -1
  87. package/dist/components/UniversalNode.svelte +150 -138
  88. package/dist/components/UniversalNode.svelte.d.ts +3 -3
  89. package/dist/components/WorkflowEditor.svelte +1069 -1007
  90. package/dist/components/WorkflowEditor.svelte.d.ts +4 -4
  91. package/dist/components/form/FormArray.svelte +1034 -973
  92. package/dist/components/form/FormArray.svelte.d.ts +1 -1
  93. package/dist/components/form/FormAutocomplete.svelte +1021 -978
  94. package/dist/components/form/FormAutocomplete.svelte.d.ts +1 -1
  95. package/dist/components/form/FormCheckboxGroup.stories.svelte +23 -20
  96. package/dist/components/form/FormCheckboxGroup.stories.svelte.d.ts +1 -1
  97. package/dist/components/form/FormCheckboxGroup.svelte +136 -136
  98. package/dist/components/form/FormCodeEditor.svelte +452 -434
  99. package/dist/components/form/FormField.svelte +366 -355
  100. package/dist/components/form/FormField.svelte.d.ts +2 -2
  101. package/dist/components/form/FormFieldLight.svelte +400 -384
  102. package/dist/components/form/FormFieldLight.svelte.d.ts +1 -1
  103. package/dist/components/form/FormFieldWrapper.stories.svelte +42 -42
  104. package/dist/components/form/FormFieldWrapper.stories.svelte.d.ts +1 -1
  105. package/dist/components/form/FormFieldWrapper.svelte +100 -93
  106. package/dist/components/form/FormFieldWrapper.svelte.d.ts +1 -1
  107. package/dist/components/form/FormFieldset.svelte +108 -108
  108. package/dist/components/form/FormFieldset.svelte.d.ts +2 -2
  109. package/dist/components/form/FormMarkdownEditor.svelte +758 -725
  110. package/dist/components/form/FormNumberField.stories.svelte +25 -25
  111. package/dist/components/form/FormNumberField.stories.svelte.d.ts +1 -1
  112. package/dist/components/form/FormNumberField.svelte +88 -88
  113. package/dist/components/form/FormRangeField.stories.svelte +20 -20
  114. package/dist/components/form/FormRangeField.stories.svelte.d.ts +1 -1
  115. package/dist/components/form/FormRangeField.svelte +234 -226
  116. package/dist/components/form/FormSelect.stories.svelte +38 -38
  117. package/dist/components/form/FormSelect.stories.svelte.d.ts +1 -1
  118. package/dist/components/form/FormSelect.svelte +101 -101
  119. package/dist/components/form/FormSelect.svelte.d.ts +1 -1
  120. package/dist/components/form/FormTemplateEditor.svelte +847 -798
  121. package/dist/components/form/FormTemplateEditor.svelte.d.ts +1 -1
  122. package/dist/components/form/FormTextField.stories.svelte +29 -23
  123. package/dist/components/form/FormTextField.stories.svelte.d.ts +1 -1
  124. package/dist/components/form/FormTextField.svelte +68 -68
  125. package/dist/components/form/FormTextarea.stories.svelte +28 -25
  126. package/dist/components/form/FormTextarea.stories.svelte.d.ts +1 -1
  127. package/dist/components/form/FormTextarea.svelte +74 -74
  128. package/dist/components/form/FormToggle.stories.svelte +23 -20
  129. package/dist/components/form/FormToggle.stories.svelte.d.ts +1 -1
  130. package/dist/components/form/FormToggle.svelte +98 -98
  131. package/dist/components/form/FormUISchemaRenderer.svelte +120 -113
  132. package/dist/components/form/FormUISchemaRenderer.svelte.d.ts +3 -3
  133. package/dist/components/form/index.d.ts +19 -19
  134. package/dist/components/form/index.js +18 -18
  135. package/dist/components/form/templateAutocomplete.d.ts +2 -2
  136. package/dist/components/form/templateAutocomplete.js +64 -55
  137. package/dist/components/form/types.d.ts +6 -6
  138. package/dist/components/form/types.js +9 -4
  139. package/dist/components/icons/AlertCircleIcon.svelte +11 -0
  140. package/dist/components/icons/AlertCircleIcon.svelte.d.ts +26 -0
  141. package/dist/components/icons/CogIcon.svelte +11 -0
  142. package/dist/components/icons/CogIcon.svelte.d.ts +26 -0
  143. package/dist/components/interrupt/ChoicePrompt.stories.svelte +54 -38
  144. package/dist/components/interrupt/ChoicePrompt.stories.svelte.d.ts +1 -1
  145. package/dist/components/interrupt/ChoicePrompt.svelte +407 -383
  146. package/dist/components/interrupt/ChoicePrompt.svelte.d.ts +1 -1
  147. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte +48 -48
  148. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte.d.ts +1 -1
  149. package/dist/components/interrupt/ConfirmationPrompt.svelte +280 -274
  150. package/dist/components/interrupt/ConfirmationPrompt.svelte.d.ts +1 -1
  151. package/dist/components/interrupt/FormPrompt.svelte +223 -218
  152. package/dist/components/interrupt/FormPrompt.svelte.d.ts +1 -1
  153. package/dist/components/interrupt/InterruptBubble.svelte +617 -583
  154. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +2 -2
  155. package/dist/components/interrupt/ReviewPrompt.stories.svelte +66 -56
  156. package/dist/components/interrupt/ReviewPrompt.stories.svelte.d.ts +1 -1
  157. package/dist/components/interrupt/ReviewPrompt.svelte +861 -841
  158. package/dist/components/interrupt/ReviewPrompt.svelte.d.ts +1 -1
  159. package/dist/components/interrupt/TextInputPrompt.stories.svelte +38 -33
  160. package/dist/components/interrupt/TextInputPrompt.stories.svelte.d.ts +1 -1
  161. package/dist/components/interrupt/TextInputPrompt.svelte +333 -328
  162. package/dist/components/interrupt/TextInputPrompt.svelte.d.ts +1 -1
  163. package/dist/components/interrupt/index.d.ts +5 -5
  164. package/dist/components/interrupt/index.js +5 -5
  165. package/dist/components/layouts/MainLayout.svelte +724 -691
  166. package/dist/components/layouts/MainLayout.svelte.d.ts +6 -6
  167. package/dist/components/nodes/GatewayNode.stories.svelte +100 -99
  168. package/dist/components/nodes/GatewayNode.svelte +605 -571
  169. package/dist/components/nodes/GatewayNode.svelte.d.ts +3 -3
  170. package/dist/components/nodes/IdeaNode.stories.svelte +44 -43
  171. package/dist/components/nodes/IdeaNode.svelte +451 -437
  172. package/dist/components/nodes/IdeaNode.svelte.d.ts +1 -1
  173. package/dist/components/nodes/NotesNode.stories.svelte +65 -64
  174. package/dist/components/nodes/NotesNode.svelte +380 -369
  175. package/dist/components/nodes/NotesNode.svelte.d.ts +1 -1
  176. package/dist/components/nodes/SimpleNode.stories.svelte +145 -144
  177. package/dist/components/nodes/SimpleNode.svelte +486 -424
  178. package/dist/components/nodes/SimpleNode.svelte.d.ts +1 -1
  179. package/dist/components/nodes/SquareNode.stories.svelte +73 -73
  180. package/dist/components/nodes/SquareNode.svelte +439 -380
  181. package/dist/components/nodes/SquareNode.svelte.d.ts +1 -1
  182. package/dist/components/nodes/TerminalNode.stories.svelte +13 -13
  183. package/dist/components/nodes/TerminalNode.svelte +709 -670
  184. package/dist/components/nodes/TerminalNode.svelte.d.ts +1 -1
  185. package/dist/components/nodes/ToolNode.stories.svelte +181 -180
  186. package/dist/components/nodes/ToolNode.svelte +505 -447
  187. package/dist/components/nodes/ToolNode.svelte.d.ts +1 -1
  188. package/dist/components/nodes/WorkflowNode.stories.svelte +70 -46
  189. package/dist/components/nodes/WorkflowNode.svelte +621 -551
  190. package/dist/components/nodes/WorkflowNode.svelte.d.ts +3 -3
  191. package/dist/components/playground/ChatPanel.svelte +945 -889
  192. package/dist/components/playground/ExecutionLogs.svelte +495 -472
  193. package/dist/components/playground/InputCollector.svelte +449 -428
  194. package/dist/components/playground/MessageBubble.stories.svelte +47 -47
  195. package/dist/components/playground/MessageBubble.stories.svelte.d.ts +1 -1
  196. package/dist/components/playground/MessageBubble.svelte +626 -610
  197. package/dist/components/playground/MessageBubble.svelte.d.ts +1 -1
  198. package/dist/components/playground/Playground.svelte +1088 -1057
  199. package/dist/components/playground/Playground.svelte.d.ts +3 -3
  200. package/dist/components/playground/PlaygroundModal.svelte +208 -204
  201. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -3
  202. package/dist/components/playground/SessionManager.svelte +527 -521
  203. package/dist/components/playground/SessionManager.svelte.d.ts +1 -1
  204. package/dist/config/agentSpecEndpoints.d.ts +1 -1
  205. package/dist/config/agentSpecEndpoints.js +20 -20
  206. package/dist/config/constants.d.ts +8 -0
  207. package/dist/config/constants.js +10 -2
  208. package/dist/config/defaultCategories.d.ts +1 -1
  209. package/dist/config/defaultCategories.js +86 -86
  210. package/dist/config/defaultPortConfig.d.ts +1 -1
  211. package/dist/config/defaultPortConfig.js +144 -144
  212. package/dist/config/endpoints.d.ts +4 -4
  213. package/dist/config/endpoints.js +65 -65
  214. package/dist/config/runtimeConfig.d.ts +2 -2
  215. package/dist/config/runtimeConfig.js +8 -8
  216. package/dist/core/index.d.ts +63 -59
  217. package/dist/core/index.js +35 -33
  218. package/dist/display/index.d.ts +2 -2
  219. package/dist/display/index.js +2 -2
  220. package/dist/editor/index.d.ts +62 -62
  221. package/dist/editor/index.js +53 -53
  222. package/dist/form/code.d.ts +5 -5
  223. package/dist/form/code.js +14 -14
  224. package/dist/form/fieldRegistry.d.ts +3 -3
  225. package/dist/form/fieldRegistry.js +11 -9
  226. package/dist/form/full.d.ts +8 -8
  227. package/dist/form/full.js +9 -9
  228. package/dist/form/index.d.ts +18 -18
  229. package/dist/form/index.js +16 -16
  230. package/dist/form/markdown.d.ts +4 -4
  231. package/dist/form/markdown.js +8 -8
  232. package/dist/helpers/proximityConnect.d.ts +3 -3
  233. package/dist/helpers/proximityConnect.js +34 -32
  234. package/dist/helpers/workflowEditorHelper.d.ts +5 -5
  235. package/dist/helpers/workflowEditorHelper.js +108 -96
  236. package/dist/index.d.ts +6 -6
  237. package/dist/index.js +6 -6
  238. package/dist/mocks/app-environment.js +2 -2
  239. package/dist/mocks/app-forms.js +9 -9
  240. package/dist/mocks/app-navigation.js +11 -11
  241. package/dist/mocks/app-stores.js +8 -8
  242. package/dist/playground/index.d.ts +19 -19
  243. package/dist/playground/index.js +16 -16
  244. package/dist/playground/mount.d.ts +3 -3
  245. package/dist/playground/mount.js +24 -24
  246. package/dist/registry/builtinFormats.js +13 -13
  247. package/dist/registry/builtinNodes.d.ts +2 -2
  248. package/dist/registry/builtinNodes.js +77 -77
  249. package/dist/registry/index.d.ts +4 -4
  250. package/dist/registry/index.js +4 -4
  251. package/dist/registry/nodeComponentRegistry.d.ts +8 -8
  252. package/dist/registry/nodeComponentRegistry.js +11 -9
  253. package/dist/registry/plugin.d.ts +2 -2
  254. package/dist/registry/plugin.js +11 -11
  255. package/dist/registry/workflowFormatRegistry.d.ts +3 -3
  256. package/dist/registry/workflowFormatRegistry.js +2 -2
  257. package/dist/schema/index.d.ts +1 -1
  258. package/dist/schema/index.js +2 -2
  259. package/dist/schemas/v1/workflow.schema.json +22 -107
  260. package/dist/services/agentSpecExecutionService.d.ts +3 -3
  261. package/dist/services/agentSpecExecutionService.js +59 -55
  262. package/dist/services/api.d.ts +18 -4
  263. package/dist/services/api.js +46 -43
  264. package/dist/services/apiVariableService.d.ts +1 -1
  265. package/dist/services/apiVariableService.js +41 -34
  266. package/dist/services/autoSaveService.js +8 -8
  267. package/dist/services/categoriesApi.d.ts +2 -2
  268. package/dist/services/categoriesApi.js +8 -8
  269. package/dist/services/draftStorage.d.ts +1 -1
  270. package/dist/services/draftStorage.js +11 -11
  271. package/dist/services/dynamicSchemaService.d.ts +1 -1
  272. package/dist/services/dynamicSchemaService.js +41 -39
  273. package/dist/services/globalSave.d.ts +2 -2
  274. package/dist/services/globalSave.js +53 -42
  275. package/dist/services/historyService.d.ts +1 -1
  276. package/dist/services/historyService.js +8 -8
  277. package/dist/services/interruptService.d.ts +1 -1
  278. package/dist/services/interruptService.js +35 -29
  279. package/dist/services/nodeExecutionService.d.ts +1 -1
  280. package/dist/services/nodeExecutionService.js +45 -44
  281. package/dist/services/playgroundService.d.ts +1 -1
  282. package/dist/services/playgroundService.js +29 -29
  283. package/dist/services/portConfigApi.d.ts +2 -2
  284. package/dist/services/portConfigApi.js +8 -8
  285. package/dist/services/settingsService.d.ts +2 -2
  286. package/dist/services/settingsService.js +25 -19
  287. package/dist/services/toastService.d.ts +4 -4
  288. package/dist/services/toastService.js +33 -33
  289. package/dist/services/variableService.d.ts +1 -1
  290. package/dist/services/variableService.js +36 -36
  291. package/dist/services/workflowStorage.d.ts +2 -2
  292. package/dist/services/workflowStorage.js +13 -13
  293. package/dist/settings/index.d.ts +7 -7
  294. package/dist/settings/index.js +6 -6
  295. package/dist/skins/default.d.ts +2 -0
  296. package/dist/skins/default.js +1 -0
  297. package/dist/skins/index.d.ts +13 -0
  298. package/dist/skins/index.js +30 -0
  299. package/dist/skins/slate.d.ts +2 -0
  300. package/dist/skins/slate.js +78 -0
  301. package/dist/stores/categoriesStore.svelte.d.ts +1 -1
  302. package/dist/stores/categoriesStore.svelte.js +5 -5
  303. package/dist/stores/editorStateMachine.svelte.d.ts +2 -2
  304. package/dist/stores/editorStateMachine.svelte.js +65 -33
  305. package/dist/stores/historyStore.svelte.d.ts +4 -4
  306. package/dist/stores/historyStore.svelte.js +4 -4
  307. package/dist/stores/interruptStore.svelte.d.ts +3 -3
  308. package/dist/stores/interruptStore.svelte.js +21 -21
  309. package/dist/stores/playgroundStore.svelte.d.ts +2 -2
  310. package/dist/stores/playgroundStore.svelte.js +25 -18
  311. package/dist/stores/portCoordinateStore.svelte.d.ts +2 -2
  312. package/dist/stores/portCoordinateStore.svelte.js +15 -8
  313. package/dist/stores/settingsStore.svelte.d.ts +2 -2
  314. package/dist/stores/settingsStore.svelte.js +62 -57
  315. package/dist/stores/workflowStore.svelte.d.ts +3 -3
  316. package/dist/stores/workflowStore.svelte.js +50 -47
  317. package/dist/stories/CanvasDecorator.svelte +35 -32
  318. package/dist/stories/CanvasDecorator.svelte.d.ts +2 -2
  319. package/dist/stories/EdgeDecorator.svelte +125 -0
  320. package/dist/stories/EdgeDecorator.svelte.d.ts +17 -0
  321. package/dist/stories/NodeDecorator.svelte +59 -53
  322. package/dist/stories/NodeDecorator.svelte.d.ts +1 -1
  323. package/dist/stories/utils.d.ts +2 -2
  324. package/dist/stories/utils.js +105 -67
  325. package/dist/styles/base.css +599 -595
  326. package/dist/styles/toast.css +14 -14
  327. package/dist/styles/tokens.css +409 -378
  328. package/dist/svelte-app.d.ts +9 -9
  329. package/dist/svelte-app.js +39 -39
  330. package/dist/themes/default.d.ts +2 -0
  331. package/dist/themes/default.js +9 -0
  332. package/dist/themes/index.d.ts +13 -0
  333. package/dist/themes/index.js +44 -0
  334. package/dist/themes/minimal.d.ts +2 -0
  335. package/dist/themes/minimal.js +11 -0
  336. package/dist/types/agentspec.d.ts +18 -18
  337. package/dist/types/agentspec.js +2 -2
  338. package/dist/types/auth.d.ts +1 -1
  339. package/dist/types/auth.js +6 -6
  340. package/dist/types/config.d.ts +6 -6
  341. package/dist/types/events.d.ts +2 -2
  342. package/dist/types/events.js +2 -2
  343. package/dist/types/index.d.ts +32 -32
  344. package/dist/types/index.js +6 -6
  345. package/dist/types/interrupt.d.ts +6 -6
  346. package/dist/types/interrupt.js +21 -21
  347. package/dist/types/interruptState.d.ts +12 -12
  348. package/dist/types/interruptState.js +66 -66
  349. package/dist/types/playground.d.ts +7 -7
  350. package/dist/types/playground.js +14 -14
  351. package/dist/types/settings.d.ts +5 -3
  352. package/dist/types/settings.js +25 -18
  353. package/dist/types/skin.d.ts +31 -0
  354. package/dist/types/skin.js +1 -0
  355. package/dist/types/theme.d.ts +35 -0
  356. package/dist/types/theme.js +1 -0
  357. package/dist/types/uischema.d.ts +4 -4
  358. package/dist/types/uischema.js +3 -3
  359. package/dist/utils/colors.d.ts +1 -1
  360. package/dist/utils/colors.js +97 -95
  361. package/dist/utils/config.d.ts +2 -2
  362. package/dist/utils/config.js +48 -48
  363. package/dist/utils/connections.d.ts +2 -2
  364. package/dist/utils/connections.js +15 -15
  365. package/dist/utils/errors.js +3 -3
  366. package/dist/utils/fetchWithAuth.d.ts +1 -1
  367. package/dist/utils/fetchWithAuth.js +2 -2
  368. package/dist/utils/handleIds.d.ts +2 -2
  369. package/dist/utils/handleIds.js +8 -8
  370. package/dist/utils/handlePositioning.d.ts +1 -1
  371. package/dist/utils/handlePositioning.js +2 -2
  372. package/dist/utils/icons.d.ts +1 -1
  373. package/dist/utils/icons.js +74 -74
  374. package/dist/utils/logger.d.ts +1 -1
  375. package/dist/utils/logger.js +7 -7
  376. package/dist/utils/nodeStatus.d.ts +1 -1
  377. package/dist/utils/nodeStatus.js +48 -48
  378. package/dist/utils/nodeTypes.d.ts +1 -1
  379. package/dist/utils/nodeTypes.js +21 -20
  380. package/dist/utils/nodeWrapper.d.ts +7 -7
  381. package/dist/utils/nodeWrapper.js +21 -19
  382. package/dist/utils/performanceUtils.d.ts +1 -1
  383. package/dist/utils/performanceUtils.js +2 -1
  384. package/dist/utils/sanitize.js +1 -1
  385. package/dist/utils/uischema.d.ts +2 -2
  386. package/dist/utils/uischema.js +8 -8
  387. package/dist/utils/validation.js +20 -8
  388. package/package.json +296 -291
@@ -5,977 +5,1097 @@
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 ConfigForm from './ConfigForm.svelte';
13
- import ConfigPanel from './ConfigPanel.svelte';
14
- import Navbar from './Navbar.svelte';
15
- import { api, setEndpointConfig } from '../services/api.js';
16
- import { EnhancedFlowDropApiClient } from '../api/enhanced-client.js';
17
- import type {
18
- NodeMetadata,
19
- Workflow,
20
- WorkflowNode,
21
- ConfigSchema,
22
- NodeUIExtensions
23
- } from '../types/index.js';
24
- import { DEFAULT_WORKFLOW_FORMAT } from '../types/index.js';
25
- import { createEndpointConfig } from '../config/endpoints.js';
26
- import type { EndpointConfig } from '../config/endpoints.js';
27
- import type { AuthProvider } from '../types/auth.js';
28
- import type { FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
29
- import { mergeFeatures } from '../types/events.js';
30
- import {
31
- getWorkflowStore,
32
- workflowActions,
33
- getWorkflowName,
34
- getWorkflowFormat,
35
- markAsSaved
36
- } from '../stores/workflowStore.svelte.js';
37
- import { globalSaveWorkflow, globalExportWorkflow } from '../services/globalSave.js';
38
- import { apiToasts, dismissToast } from '../services/toastService.js';
39
- import { initAutoSave } from '../services/autoSaveService.js';
40
- import { getUiSettings } from '../stores/settingsStore.svelte.js';
41
- import { initializePortCompatibility } from '../utils/connections.js';
42
- import { DEFAULT_PORT_CONFIG } from '../config/defaultPortConfig.js';
43
- import { workflowFormatRegistry } from '../registry/workflowFormatRegistry.js';
44
- import { logger } from '../utils/logger.js';
45
- import { validateWorkflowData } from '../utils/validation.js';
46
-
47
- /**
48
- * Configuration props for runtime customization
49
- */
50
- interface Props {
51
- /** Initial workflow to load */
52
- workflow?: Workflow;
53
- /** Pre-loaded node types (if provided, skips API fetch) */
54
- nodes?: NodeMetadata[];
55
- /** Editor height */
56
- height?: string | number;
57
- /** Editor width */
58
- width?: string | number;
59
- /** Show the navbar */
60
- showNavbar?: boolean;
61
- /** Disable the node sidebar */
62
- disableSidebar?: boolean;
63
- /** Lock the workflow (prevent changes) */
64
- lockWorkflow?: boolean;
65
- /** Read-only mode */
66
- readOnly?: boolean;
67
- /** Node execution statuses */
68
- nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
69
- /** Pipeline ID for fetching node execution info */
70
- pipelineId?: string;
71
- /** Custom navbar title */
72
- navbarTitle?: string;
73
- /** Custom navbar actions */
74
- navbarActions?: Array<{
75
- label: string;
76
- href: string;
77
- icon?: string;
78
- variant?: 'primary' | 'secondary' | 'outline';
79
- onclick?: (event: Event) => void;
80
- }>;
81
- /** Show settings gear icon in navbar */
82
- showSettings?: boolean;
83
- /** API base URL */
84
- apiBaseUrl?: string;
85
- /** Endpoint configuration */
86
- endpointConfig?: EndpointConfig;
87
- /** Authentication provider */
88
- authProvider?: AuthProvider;
89
- /** Event handlers */
90
- eventHandlers?: FlowDropEventHandlers;
91
- /** Feature configuration */
92
- features?: FlowDropFeatures;
93
- }
94
-
95
- let {
96
- workflow: initialWorkflow,
97
- nodes: propNodes,
98
- height = '100vh',
99
- width = '100%',
100
- showNavbar = false,
101
- disableSidebar = false,
102
- lockWorkflow = false,
103
- readOnly = false,
104
- nodeStatuses = {},
105
- pipelineId,
106
- navbarTitle,
107
- navbarActions = [],
108
- showSettings = true,
109
- apiBaseUrl,
110
- endpointConfig: propEndpointConfig,
111
- authProvider,
112
- eventHandlers,
113
- features: propFeatures
114
- }: Props = $props();
115
-
116
- // svelte-ignore state_referenced_locally — feature flags don't change at runtime
117
- const features = mergeFeatures(propFeatures);
118
-
119
- // Create breadcrumb-style title - at top level to avoid store subscription issues
120
- let breadcrumbTitle = $derived(() => {
121
- // Use custom navbar title if provided
122
- if (navbarTitle) {
123
- return navbarTitle;
124
- }
125
- // Default workflow title logic
126
- const wfName = getWorkflowName();
127
- if (!wfName || wfName === 'Untitled Workflow') {
128
- return 'Workflow / New Workflow';
129
- }
130
- return `Workflow / ${wfName}`;
131
- });
132
-
133
- let nodes = $state<NodeMetadata[]>([]);
134
- // Remove workflow prop - use global store directly
135
- // let workflow = $derived($workflowStore || initialWorkflow);
136
- let error = $state<string | null>(null);
137
- let endpointConfig = $state<EndpointConfig | null>(null);
138
-
139
- /**
140
- * Enhanced API client with authProvider support
141
- * Used when authProvider is provided; otherwise falls back to legacy api service
142
- */
143
- let apiClient = $state<EnhancedFlowDropApiClient | null>(null);
144
-
145
- // ConfigSidebar state
146
- let isConfigSidebarOpen = $state(false);
147
- let selectedNodeId = $state<string | null>(null);
148
-
149
- // Workflow settings sidebar state
150
- let isWorkflowSettingsOpen = $state(false);
151
-
152
- // Workflow configuration schema (derived to pick up dynamic format options)
153
- let workflowConfigSchema: ConfigSchema = $derived({
154
- type: 'object' as const,
155
- properties: {
156
- name: {
157
- type: 'string',
158
- title: 'Workflow Name',
159
- description: 'The name of the workflow',
160
- default: ''
161
- },
162
- description: {
163
- type: 'string',
164
- title: 'Description',
165
- description: 'A description of the workflow',
166
- format: 'multiline',
167
- default: ''
168
- },
169
- format: {
170
- type: 'string',
171
- title: 'Workflow Format',
172
- description: 'The specification format for this workflow',
173
- oneOf: workflowFormatRegistry.getOneOfOptions(),
174
- default: 'flowdrop'
175
- }
176
- },
177
- required: ['name']
178
- });
179
-
180
- // Workflow configuration values
181
- let workflowConfigValues = $derived({
182
- name: getWorkflowName() || '',
183
- description: getWorkflowStore()?.description || '',
184
- format: getWorkflowStore()?.metadata?.format || 'flowdrop'
185
- });
186
-
187
- // Get the current node from the workflow store
188
- let selectedNodeForConfig = $derived(() => {
189
- const wf = getWorkflowStore();
190
- if (!selectedNodeId || !wf) return null;
191
- return wf.nodes.find((node) => node.id === selectedNodeId) || null;
192
- });
193
-
194
- // WorkflowEditor reference for save functionality
195
- let workflowEditorRef: WorkflowEditor | null = null;
196
-
197
- /**
198
- * Fetch node types from the server
199
- *
200
- * If propNodes is provided, uses those instead of fetching from API.
201
- * Uses enhanced API client with authProvider support when available.
202
- */
203
- async function fetchNodeTypes(): Promise<void> {
204
- // If nodes were provided as props, use them directly (skip API fetch)
205
- if (propNodes && propNodes.length > 0) {
206
- // Merge format-provided nodes with prop nodes (deduplicate by ID, props take priority)
207
- const formatNodes = workflowFormatRegistry.getAllFormatNodes();
208
- const existingIds = new Set(propNodes.map((n) => n.id));
209
- const uniqueFormatNodes = formatNodes.filter((n) => !existingIds.has(n.id));
210
- nodes = [...propNodes, ...uniqueFormatNodes];
211
- return;
212
- }
213
-
214
- // Show loading toast (if toasts are enabled)
215
- const loadingToast = features.showToasts ? apiToasts.loading('Loading node types') : null;
216
- try {
217
- error = null;
218
-
219
- // Use enhanced client with authProvider if available, otherwise fall back to legacy api
220
- let fetchedNodes: NodeMetadata[];
221
- if (apiClient) {
222
- fetchedNodes = await apiClient.getAvailableNodes();
223
- } else {
224
- fetchedNodes = await api.nodes.getNodes();
225
- }
226
-
227
- // Merge format-provided nodes with API nodes (deduplicate by ID, API takes priority)
228
- const formatNodes = workflowFormatRegistry.getAllFormatNodes();
229
- const existingIds = new Set(fetchedNodes.map((n) => n.id));
230
- const uniqueFormatNodes = formatNodes.filter((n) => !existingIds.has(n.id));
231
- nodes = [...fetchedNodes, ...uniqueFormatNodes];
232
- error = null;
233
-
234
- // Dismiss loading toast
235
- if (loadingToast) {
236
- dismissToast(loadingToast);
237
- }
238
- } catch (err) {
239
- // Dismiss loading toast and show error toast
240
- if (loadingToast) {
241
- dismissToast(loadingToast);
242
- }
243
-
244
- const errorMessage = err instanceof Error ? err.message : 'Unknown error';
245
-
246
- // Notify parent via event handler
247
- if (eventHandlers?.onApiError) {
248
- const suppressToast = eventHandlers.onApiError(
249
- err instanceof Error ? err : new Error(errorMessage),
250
- 'fetchNodes'
251
- );
252
- if (suppressToast) {
253
- // Parent handled the error, keep nodes empty
254
- nodes = [];
255
- return;
256
- }
257
- }
258
-
259
- // Show error and set empty nodes array (no fallback to sample data)
260
- error = `API Error: ${errorMessage}. No node types available.`;
261
- if (features.showToasts) {
262
- apiToasts.error('Load node types', errorMessage);
263
- }
264
-
265
- // Set empty nodes array instead of fallback data
266
- nodes = [];
267
- }
268
- }
269
-
270
- /**
271
- * Retry loading node types
272
- */
273
- function retryLoad(): void {
274
- fetchNodeTypes();
275
- }
276
-
277
- /**
278
- * Test API connection
279
- */
280
- async function testApiConnection(): Promise<void> {
281
- try {
282
- const baseUrl = endpointConfig?.baseUrl || apiBaseUrl || '/api/flowdrop';
283
- const testUrl = `${baseUrl}/nodes`;
284
-
285
- const response = await fetch(testUrl);
286
- const data = await response.json();
287
-
288
- if (response.ok && data.success) {
289
- apiToasts.success('API connection test', 'Connection successful');
290
- } else {
291
- apiToasts.error('API connection test', 'Connection failed');
292
- }
293
- } catch (err) {
294
- apiToasts.error('API connection test', err instanceof Error ? err.message : 'Unknown error');
295
- }
296
- }
297
-
298
- /**
299
- * Initialize API endpoints and create enhanced client if authProvider is available
300
- * Priority: propEndpointConfig > existingConfig > apiBaseUrl > default
301
- */
302
- async function initializeApiEndpoints(): Promise<void> {
303
- // First priority: Use endpointConfig prop if provided (from mountFlowDropApp)
304
- if (propEndpointConfig) {
305
- setEndpointConfig(propEndpointConfig);
306
- endpointConfig = propEndpointConfig;
307
-
308
- // Create enhanced API client with authProvider support if provided
309
- if (authProvider) {
310
- apiClient = new EnhancedFlowDropApiClient(propEndpointConfig, authProvider);
311
- }
312
- return;
313
- }
314
-
315
- // Second priority: Check if endpoint config is already set (e.g., by parent layout)
316
- const { getEndpointConfig } = await import('../services/api.js');
317
- const existingConfig = getEndpointConfig();
318
-
319
- // If config already exists and no override provided, use existing
320
- if (existingConfig && !apiBaseUrl) {
321
- endpointConfig = existingConfig;
322
-
323
- // Create enhanced API client with authProvider support if provided
324
- if (authProvider) {
325
- apiClient = new EnhancedFlowDropApiClient(existingConfig, authProvider);
326
- }
327
- return;
328
- }
329
-
330
- // Third priority: Use provided apiBaseUrl or default
331
- const baseUrl = apiBaseUrl || '/api/flowdrop';
332
-
333
- const config = createEndpointConfig(baseUrl, {
334
- auth: {
335
- type: 'none' // No authentication for now
336
- },
337
- timeout: 10000, // 10 second timeout
338
- retry: {
339
- enabled: true,
340
- maxAttempts: 2,
341
- delay: 1000,
342
- backoff: 'exponential'
343
- }
344
- });
345
-
346
- setEndpointConfig(config);
347
- // Store the configuration for passing to WorkflowEditor
348
- endpointConfig = config;
349
-
350
- // Create enhanced API client with authProvider support if provided
351
- if (authProvider) {
352
- apiClient = new EnhancedFlowDropApiClient(config, authProvider);
353
- }
354
- }
355
-
356
- /**
357
- * ConfigSidebar functions
358
- */
359
- function openConfigSidebar(node: WorkflowNode): void {
360
- // Close if already open for the same node
361
- if (isConfigSidebarOpen && selectedNodeId === node.id) {
362
- closeConfigSidebar();
363
- return;
364
- }
365
- selectedNodeId = node.id;
366
- isConfigSidebarOpen = true;
367
- }
368
-
369
- function closeConfigSidebar(): void {
370
- isConfigSidebarOpen = false;
371
- selectedNodeId = null;
372
- }
373
-
374
- /**
375
- * Toggle workflow settings sidebar
376
- */
377
- function toggleWorkflowSettings(): void {
378
- isWorkflowSettingsOpen = !isWorkflowSettingsOpen;
379
- // Close config sidebar if opening workflow settings
380
- if (isWorkflowSettingsOpen) {
381
- closeConfigSidebar();
382
- }
383
- }
384
-
385
- /**
386
- * Handle workflow configuration save
387
- */
388
- async function handleWorkflowSave(config: Record<string, unknown>): Promise<void> {
389
- // Update the workflow store
390
- if (getWorkflowStore()) {
391
- workflowActions.batchUpdate({
392
- name: config.name as string | undefined,
393
- description: config.description as string | undefined
394
- });
395
- }
396
-
397
- // Close the sidebar
398
- isWorkflowSettingsOpen = false;
399
-
400
- // Also save the workflow to the backend
401
- try {
402
- await saveWorkflow();
403
- } catch (error) {
404
- logger.error('Failed to save workflow to backend:', error);
405
- // Note: We don't throw the error here to avoid breaking the UI flow
406
- // The user can still manually save via the main Save button if needed
407
- }
408
- }
409
-
410
- /**
411
- * Save workflow - thin wrapper that delegates to globalSaveWorkflow().
412
- *
413
- * All save logic (blur flush, metadata construction, API call, event hooks,
414
- * toast notifications) lives in globalSave.ts — the single source of truth.
415
- */
416
- async function saveWorkflow(): Promise<void> {
417
- await globalSaveWorkflow({
418
- apiClient: apiClient ?? undefined,
419
- eventHandlers,
420
- features,
421
- onMarkAsSaved: markAsSaved
422
- });
423
- }
424
-
425
- /**
426
- * Export workflow - thin wrapper that delegates to globalExportWorkflow().
427
- *
428
- * All export logic (flush, metadata construction, file download) lives
429
- * in globalSave.ts — the single source of truth.
430
- */
431
- async function exportWorkflow(): Promise<void> {
432
- await globalExportWorkflow({ features });
433
- }
434
-
435
- /**
436
- * Import workflow from a JSON file
437
- *
438
- * Reads the selected file, validates its structure, and loads it into the workflow store.
439
- */
440
- function importWorkflow(file: File): void {
441
- const reader = new FileReader();
442
- reader.onload = (event) => {
443
- try {
444
- const text = event.target?.result;
445
- if (typeof text !== 'string') {
446
- throw new Error('Could not read file contents.');
447
- }
448
- const data = JSON.parse(text);
449
- const validation = validateWorkflowData(data);
450
- if (!validation.valid) {
451
- if (features.showToasts) {
452
- apiToasts.error('Import workflow', validation.error ?? 'Invalid workflow JSON');
453
- }
454
- logger.warn('Workflow import validation failed:', validation.error);
455
- return;
456
- }
457
- workflowActions.initialize(data as Workflow);
458
- if (features.showToasts) {
459
- apiToasts.success('Import workflow', 'Workflow imported successfully');
460
- }
461
- if (eventHandlers?.onWorkflowLoad) {
462
- eventHandlers.onWorkflowLoad(data as Workflow);
463
- }
464
- } catch (error) {
465
- const errorObj = error instanceof Error ? error : new Error('Unknown error occurred');
466
- logger.error('Workflow import failed:', errorObj);
467
- if (features.showToasts) {
468
- apiToasts.error('Import workflow', errorObj.message);
469
- }
470
- }
471
- };
472
- reader.onerror = () => {
473
- const message = 'Failed to read the selected file.';
474
- logger.error(message);
475
- if (features.showToasts) {
476
- apiToasts.error('Import workflow', message);
477
- }
478
- };
479
- reader.readAsText(file);
480
- }
481
-
482
- /**
483
- * Handle file input change event for workflow import
484
- */
485
- function handleImportFileChange(event: Event): void {
486
- const input = event.target as HTMLInputElement;
487
- const file = input.files?.[0];
488
- if (file) {
489
- importWorkflow(file);
490
- }
491
- // Reset input so same file can be re-imported
492
- input.value = '';
493
- }
494
-
495
- // Function to handle clicks outside the sidebar
496
- function handleCanvasClick(event: MouseEvent): void {
497
- // Check if the click is outside the right sidebar
498
- const rightSidebar = document.querySelector('.flowdrop-main-layout__sidebar--right');
499
- if (rightSidebar && !rightSidebar.contains(event.target as Node)) {
500
- // Close sidebar when clicking outside of it
501
- if (isConfigSidebarOpen) {
502
- closeConfigSidebar();
503
- }
504
- }
505
- }
506
-
507
- // Load node types on mount
508
- onMount(() => {
509
- (async () => {
510
- try {
511
- await initializeApiEndpoints();
512
-
513
- // Ensure port compatibility checker is initialized (needed for proximity connect, etc.)
514
- // mountFlowDropApp initializes this before mounting, but SvelteKit routes need it here.
515
- initializePortCompatibility(DEFAULT_PORT_CONFIG);
516
-
517
- await fetchNodeTypes();
518
-
519
- // Initialize the workflow store
520
- if (initialWorkflow) {
521
- workflowActions.initialize(initialWorkflow);
522
-
523
- // Emit onWorkflowLoad event
524
- if (eventHandlers?.onWorkflowLoad) {
525
- eventHandlers.onWorkflowLoad(initialWorkflow);
526
- }
527
- } else {
528
- // Initialize with a default empty workflow so the editor is functional
529
- // (e.g., drag-and-drop requires a non-null workflow in the store)
530
- const defaultWorkflow: Workflow = {
531
- id: '',
532
- name: 'Untitled Workflow',
533
- nodes: [],
534
- edges: [],
535
- metadata: {
536
- version: '1.0.0',
537
- format: DEFAULT_WORKFLOW_FORMAT,
538
- createdAt: new Date().toISOString(),
539
- updatedAt: new Date().toISOString()
540
- }
541
- };
542
- workflowActions.initialize(defaultWorkflow);
543
- }
544
- } catch (error) {
545
- logger.error('Failed to initialize editor:', error);
546
- }
547
- })();
548
-
549
- // Listen for workflow settings toggle from main navbar
550
- const handleWorkflowSettingsToggle = () => {
551
- toggleWorkflowSettings();
552
- };
553
-
554
- window.addEventListener('workflow-settings-toggle', handleWorkflowSettingsToggle);
555
-
556
- // Initialize auto-save based on user settings
557
- const cleanupAutoSave = initAutoSave({
558
- onSave: async () => {
559
- await saveWorkflow();
560
- },
561
- onError: (error) => {
562
- // Don't show toast for auto-save errors to avoid noise
563
- logger.warn('Auto-save failed:', error);
564
- },
565
- onSuccess: () => {
566
- logger.debug('Auto-saved workflow');
567
- }
568
- });
569
-
570
- return () => {
571
- window.removeEventListener('workflow-settings-toggle', handleWorkflowSettingsToggle);
572
- cleanupAutoSave();
573
- };
574
- });
575
-
576
- /**
577
- * Derived value for showing the right config panel
578
- * Config panel always appears on the right side
579
- */
580
- const hasConfigPanelOpen = $derived(isWorkflowSettingsOpen || !!selectedNodeForConfig());
581
- const showRightPanel = $derived(!disableSidebar && hasConfigPanelOpen);
582
-
583
- /**
584
- * Calculate left sidebar width based on collapsed state
585
- * When collapsed, use 48px; otherwise use user-configured width
586
- */
587
- const leftSidebarWidth = $derived(
588
- getUiSettings().sidebarCollapsed ? 48 : getUiSettings().sidebarWidth
589
- );
590
-
591
- // File input reference for workflow import
592
- let fileInputRef = $state<HTMLInputElement | null>(null);
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 ConfigForm from "./ConfigForm.svelte";
13
+ import ConfigPanel from "./ConfigPanel.svelte";
14
+ import Navbar from "./Navbar.svelte";
15
+ import { api, setEndpointConfig } from "../services/api.js";
16
+ import { EnhancedFlowDropApiClient } from "../api/enhanced-client.js";
17
+ import type {
18
+ NodeMetadata,
19
+ Workflow,
20
+ WorkflowNode,
21
+ ConfigSchema,
22
+ NodeUIExtensions,
23
+ } from "../types/index.js";
24
+ import { DEFAULT_WORKFLOW_FORMAT } from "../types/index.js";
25
+ import { createEndpointConfig } from "../config/endpoints.js";
26
+ import type { EndpointConfig } from "../config/endpoints.js";
27
+ import type { AuthProvider } from "../types/auth.js";
28
+ import type {
29
+ FlowDropEventHandlers,
30
+ FlowDropFeatures,
31
+ } from "../types/events.js";
32
+ import { mergeFeatures } from "../types/events.js";
33
+ import type { FlowDropTheme, FlowDropThemeName } from "../types/theme.js";
34
+ import type { FlowDropSkinTokens } from "../types/skin.js";
35
+ import { resolveTheme } from "../themes/index.js";
36
+ import {
37
+ getWorkflowStore,
38
+ workflowActions,
39
+ getWorkflowName,
40
+ getWorkflowFormat,
41
+ markAsSaved,
42
+ } from "../stores/workflowStore.svelte.js";
43
+ import {
44
+ globalSaveWorkflow,
45
+ globalExportWorkflow,
46
+ } from "../services/globalSave.js";
47
+ import { apiToasts, dismissToast } from "../services/toastService.js";
48
+ import { initAutoSave } from "../services/autoSaveService.js";
49
+ import { getUiSettings } from "../stores/settingsStore.svelte.js";
50
+ import { initializePortCompatibility } from "../utils/connections.js";
51
+ import { DEFAULT_PORT_CONFIG } from "../config/defaultPortConfig.js";
52
+ import { workflowFormatRegistry } from "../registry/workflowFormatRegistry.js";
53
+ import { logger } from "../utils/logger.js";
54
+ import { validateWorkflowData } from "../utils/validation.js";
55
+
56
+ /**
57
+ * Configuration props for runtime customization
58
+ */
59
+ interface Props {
60
+ /** Initial workflow to load */
61
+ workflow?: Workflow;
62
+ /** Pre-loaded node types (if provided, skips API fetch) */
63
+ nodes?: NodeMetadata[];
64
+ /** Editor height */
65
+ height?: string | number;
66
+ /** Editor width */
67
+ width?: string | number;
68
+ /** Show the navbar */
69
+ showNavbar?: boolean;
70
+ /** Disable the node sidebar */
71
+ disableSidebar?: boolean;
72
+ /** Lock the workflow (prevent changes) */
73
+ lockWorkflow?: boolean;
74
+ /** Read-only mode */
75
+ readOnly?: boolean;
76
+ /** Node execution statuses */
77
+ nodeStatuses?: Record<
78
+ string,
79
+ "pending" | "running" | "completed" | "error"
80
+ >;
81
+ /** Pipeline ID for fetching node execution info */
82
+ pipelineId?: string;
83
+ /** Custom navbar title */
84
+ navbarTitle?: string;
85
+ /** Custom navbar actions */
86
+ navbarActions?: Array<{
87
+ label: string;
88
+ href: string;
89
+ icon?: string;
90
+ variant?: "primary" | "secondary" | "outline";
91
+ onclick?: (event: Event) => void;
92
+ }>;
93
+ /** Show settings gear icon in navbar */
94
+ showSettings?: boolean;
95
+ /** API base URL */
96
+ apiBaseUrl?: string;
97
+ /** Endpoint configuration */
98
+ endpointConfig?: EndpointConfig;
99
+ /** Authentication provider */
100
+ authProvider?: AuthProvider;
101
+ /** Event handlers */
102
+ eventHandlers?: FlowDropEventHandlers;
103
+ /** Feature configuration */
104
+ features?: FlowDropFeatures;
105
+ /** Visual theme — named built-in ('default' | 'minimal') or custom theme object */
106
+ theme?: FlowDropTheme | FlowDropThemeName;
107
+ }
108
+
109
+ let {
110
+ workflow: initialWorkflow,
111
+ nodes: propNodes,
112
+ height = "100vh",
113
+ width = "100%",
114
+ showNavbar = false,
115
+ disableSidebar = false,
116
+ lockWorkflow = false,
117
+ readOnly = false,
118
+ nodeStatuses = {},
119
+ pipelineId,
120
+ navbarTitle,
121
+ navbarActions = [],
122
+ showSettings = true,
123
+ apiBaseUrl,
124
+ endpointConfig: propEndpointConfig,
125
+ authProvider,
126
+ eventHandlers,
127
+ features: propFeatures,
128
+ theme: themeProp,
129
+ }: Props = $props();
130
+
131
+ // svelte-ignore state_referenced_locally — feature flags don't change at runtime
132
+ const features = mergeFeatures(propFeatures);
133
+
134
+ // Theme system resolve named theme or custom object, inject CSS tokens from skin
135
+ // Explicit prop wins; falls back to user's persisted theme preference from settings
136
+ let resolvedTheme = $derived(
137
+ resolveTheme(themeProp ?? getUiSettings().theme),
138
+ );
139
+ let themeConfig = $derived(resolvedTheme.config);
140
+
141
+ // Inject skin tokens as a style tag so light/dark palettes can coexist.
142
+ // tokens → :root { ... } (light mode / base)
143
+ // darkTokens → [data-theme='dark'] { ... } (dark mode override)
144
+ // The tag is appended after tokens.css so it wins via source order.
145
+ $effect(() => {
146
+ const skin = resolvedTheme.skin;
147
+ const tokens = skin?.tokens;
148
+ const darkTokens = skin?.darkTokens;
149
+ if ((!tokens && !darkTokens) || typeof document === "undefined") return;
150
+
151
+ const toRules = (dict: FlowDropSkinTokens) =>
152
+ Object.entries(dict)
153
+ .map(([k, v]) => ` --fd-${k}: ${v};`)
154
+ .join("\n");
155
+
156
+ let css = "";
157
+ if (tokens) css += `:root {\n${toRules(tokens)}\n}\n`;
158
+ if (darkTokens) css += `[data-theme='dark'] {\n${toRules(darkTokens)}\n}\n`;
159
+
160
+ const style = document.createElement("style");
161
+ style.id = "fd-skin-tokens";
162
+ document.head.appendChild(style);
163
+ style.textContent = css;
164
+
165
+ return () => style.remove();
166
+ });
167
+
168
+ // Create breadcrumb-style title - at top level to avoid store subscription issues
169
+ let breadcrumbTitle = $derived(() => {
170
+ // Use custom navbar title if provided
171
+ if (navbarTitle) {
172
+ return navbarTitle;
173
+ }
174
+ // Default workflow title logic
175
+ const wfName = getWorkflowName();
176
+ if (!wfName || wfName === "Untitled Workflow") {
177
+ return "Workflow / New Workflow";
178
+ }
179
+ return `Workflow / ${wfName}`;
180
+ });
181
+
182
+ let nodes = $state<NodeMetadata[]>([]);
183
+ // Remove workflow prop - use global store directly
184
+ // let workflow = $derived($workflowStore || initialWorkflow);
185
+ let error = $state<string | null>(null);
186
+ let endpointConfig = $state<EndpointConfig | null>(null);
187
+
188
+ /**
189
+ * Enhanced API client with authProvider support
190
+ * Used when authProvider is provided; otherwise falls back to legacy api service
191
+ */
192
+ let apiClient = $state<EnhancedFlowDropApiClient | null>(null);
193
+
194
+ // ConfigSidebar state
195
+ let isConfigSidebarOpen = $state(false);
196
+ let selectedNodeId = $state<string | null>(null);
197
+
198
+ // Workflow settings sidebar state
199
+ let isWorkflowSettingsOpen = $state(false);
200
+
201
+ // Workflow configuration schema (derived to pick up dynamic format options)
202
+ let workflowConfigSchema: ConfigSchema = $derived({
203
+ type: "object" as const,
204
+ properties: {
205
+ name: {
206
+ type: "string",
207
+ title: "Workflow Name",
208
+ description: "The name of the workflow",
209
+ default: "",
210
+ },
211
+ description: {
212
+ type: "string",
213
+ title: "Description",
214
+ description: "A description of the workflow",
215
+ format: "multiline",
216
+ default: "",
217
+ },
218
+ format: {
219
+ type: "string",
220
+ title: "Workflow Format",
221
+ description: "The specification format for this workflow",
222
+ oneOf: workflowFormatRegistry.getOneOfOptions(),
223
+ default: "flowdrop",
224
+ },
225
+ },
226
+ required: ["name"],
227
+ });
228
+
229
+ // Workflow configuration values
230
+ let workflowConfigValues = $derived({
231
+ name: getWorkflowName() || "",
232
+ description: getWorkflowStore()?.description || "",
233
+ format: getWorkflowStore()?.metadata?.format || "flowdrop",
234
+ });
235
+
236
+ // Get the current node from the workflow store
237
+ let selectedNodeForConfig = $derived(() => {
238
+ const wf = getWorkflowStore();
239
+ if (!selectedNodeId || !wf) return null;
240
+ return wf.nodes.find((node) => node.id === selectedNodeId) || null;
241
+ });
242
+
243
+ // WorkflowEditor reference for save functionality
244
+ let workflowEditorRef: WorkflowEditor | null = null;
245
+
246
+ /**
247
+ * Fetch node types from the server
248
+ *
249
+ * If propNodes is provided, uses those instead of fetching from API.
250
+ * Uses enhanced API client with authProvider support when available.
251
+ */
252
+ async function fetchNodeTypes(): Promise<void> {
253
+ // If nodes were provided as props, use them directly (skip API fetch)
254
+ if (propNodes && propNodes.length > 0) {
255
+ // Merge format-provided nodes with prop nodes (deduplicate by ID, props take priority)
256
+ const formatNodes = workflowFormatRegistry.getAllFormatNodes();
257
+ const existingIds = new Set(propNodes.map((n) => n.id));
258
+ const uniqueFormatNodes = formatNodes.filter(
259
+ (n) => !existingIds.has(n.id),
260
+ );
261
+ nodes = [...propNodes, ...uniqueFormatNodes];
262
+ return;
263
+ }
264
+
265
+ // Show loading toast (if toasts are enabled)
266
+ const loadingToast = features.showToasts
267
+ ? apiToasts.loading("Loading node types")
268
+ : null;
269
+ try {
270
+ error = null;
271
+
272
+ // Use enhanced client with authProvider if available, otherwise fall back to legacy api
273
+ let fetchedNodes: NodeMetadata[];
274
+ if (apiClient) {
275
+ fetchedNodes = await apiClient.getAvailableNodes();
276
+ } else {
277
+ fetchedNodes = await api.nodes.getNodes();
278
+ }
279
+
280
+ // Merge format-provided nodes with API nodes (deduplicate by ID, API takes priority)
281
+ const formatNodes = workflowFormatRegistry.getAllFormatNodes();
282
+ const existingIds = new Set(fetchedNodes.map((n) => n.id));
283
+ const uniqueFormatNodes = formatNodes.filter(
284
+ (n) => !existingIds.has(n.id),
285
+ );
286
+ nodes = [...fetchedNodes, ...uniqueFormatNodes];
287
+ error = null;
288
+
289
+ // Dismiss loading toast
290
+ if (loadingToast) {
291
+ dismissToast(loadingToast);
292
+ }
293
+ } catch (err) {
294
+ // Dismiss loading toast and show error toast
295
+ if (loadingToast) {
296
+ dismissToast(loadingToast);
297
+ }
298
+
299
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
300
+
301
+ // Notify parent via event handler
302
+ if (eventHandlers?.onApiError) {
303
+ const suppressToast = eventHandlers.onApiError(
304
+ err instanceof Error ? err : new Error(errorMessage),
305
+ "fetchNodes",
306
+ );
307
+ if (suppressToast) {
308
+ // Parent handled the error, keep nodes empty
309
+ nodes = [];
310
+ return;
311
+ }
312
+ }
313
+
314
+ // Show error and set empty nodes array (no fallback to sample data)
315
+ error = `API Error: ${errorMessage}. No node types available.`;
316
+ if (features.showToasts) {
317
+ apiToasts.error("Load node types", errorMessage);
318
+ }
319
+
320
+ // Set empty nodes array instead of fallback data
321
+ nodes = [];
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Retry loading node types
327
+ */
328
+ function retryLoad(): void {
329
+ fetchNodeTypes();
330
+ }
331
+
332
+ /**
333
+ * Test API connection
334
+ */
335
+ async function testApiConnection(): Promise<void> {
336
+ try {
337
+ const baseUrl = endpointConfig?.baseUrl || apiBaseUrl || "/api/flowdrop";
338
+ const testUrl = `${baseUrl}/nodes`;
339
+
340
+ const response = await fetch(testUrl);
341
+ const data = await response.json();
342
+
343
+ if (response.ok && data.success) {
344
+ apiToasts.success("API connection test", "Connection successful");
345
+ } else {
346
+ apiToasts.error("API connection test", "Connection failed");
347
+ }
348
+ } catch (err) {
349
+ apiToasts.error(
350
+ "API connection test",
351
+ err instanceof Error ? err.message : "Unknown error",
352
+ );
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Initialize API endpoints and create enhanced client if authProvider is available
358
+ * Priority: propEndpointConfig > existingConfig > apiBaseUrl > default
359
+ */
360
+ async function initializeApiEndpoints(): Promise<void> {
361
+ // First priority: Use endpointConfig prop if provided (from mountFlowDropApp)
362
+ if (propEndpointConfig) {
363
+ setEndpointConfig(propEndpointConfig);
364
+ endpointConfig = propEndpointConfig;
365
+
366
+ // Create enhanced API client with authProvider support if provided
367
+ if (authProvider) {
368
+ apiClient = new EnhancedFlowDropApiClient(
369
+ propEndpointConfig,
370
+ authProvider,
371
+ );
372
+ }
373
+ return;
374
+ }
375
+
376
+ // Second priority: Check if endpoint config is already set (e.g., by parent layout)
377
+ const { getEndpointConfig } = await import("../services/api.js");
378
+ const existingConfig = getEndpointConfig();
379
+
380
+ // If config already exists and no override provided, use existing
381
+ if (existingConfig && !apiBaseUrl) {
382
+ endpointConfig = existingConfig;
383
+
384
+ // Create enhanced API client with authProvider support if provided
385
+ if (authProvider) {
386
+ apiClient = new EnhancedFlowDropApiClient(existingConfig, authProvider);
387
+ }
388
+ return;
389
+ }
390
+
391
+ // Third priority: Use provided apiBaseUrl or default
392
+ const baseUrl = apiBaseUrl || "/api/flowdrop";
393
+
394
+ const config = createEndpointConfig(baseUrl, {
395
+ auth: {
396
+ type: "none", // No authentication for now
397
+ },
398
+ timeout: 10000, // 10 second timeout
399
+ retry: {
400
+ enabled: true,
401
+ maxAttempts: 2,
402
+ delay: 1000,
403
+ backoff: "exponential",
404
+ },
405
+ });
406
+
407
+ setEndpointConfig(config);
408
+ // Store the configuration for passing to WorkflowEditor
409
+ endpointConfig = config;
410
+
411
+ // Create enhanced API client with authProvider support if provided
412
+ if (authProvider) {
413
+ apiClient = new EnhancedFlowDropApiClient(config, authProvider);
414
+ }
415
+ }
416
+
417
+ /**
418
+ * ConfigSidebar functions
419
+ */
420
+ function openConfigSidebar(node: WorkflowNode): void {
421
+ // Close if already open for the same node
422
+ if (isConfigSidebarOpen && selectedNodeId === node.id) {
423
+ closeConfigSidebar();
424
+ return;
425
+ }
426
+ selectedNodeId = node.id;
427
+ isConfigSidebarOpen = true;
428
+ }
429
+
430
+ function closeConfigSidebar(): void {
431
+ isConfigSidebarOpen = false;
432
+ selectedNodeId = null;
433
+ }
434
+
435
+ /**
436
+ * Toggle workflow settings sidebar
437
+ */
438
+ function toggleWorkflowSettings(): void {
439
+ isWorkflowSettingsOpen = !isWorkflowSettingsOpen;
440
+ // Close config sidebar if opening workflow settings
441
+ if (isWorkflowSettingsOpen) {
442
+ closeConfigSidebar();
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Handle workflow configuration save
448
+ */
449
+ async function handleWorkflowSave(
450
+ config: Record<string, unknown>,
451
+ ): Promise<void> {
452
+ // Update the workflow store
453
+ if (getWorkflowStore()) {
454
+ workflowActions.batchUpdate({
455
+ name: config.name as string | undefined,
456
+ description: config.description as string | undefined,
457
+ });
458
+ }
459
+
460
+ // Close the sidebar
461
+ isWorkflowSettingsOpen = false;
462
+
463
+ // Also save the workflow to the backend
464
+ try {
465
+ await saveWorkflow();
466
+ } catch (error) {
467
+ logger.error("Failed to save workflow to backend:", error);
468
+ // Note: We don't throw the error here to avoid breaking the UI flow
469
+ // The user can still manually save via the main Save button if needed
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Save workflow - thin wrapper that delegates to globalSaveWorkflow().
475
+ *
476
+ * All save logic (blur flush, metadata construction, API call, event hooks,
477
+ * toast notifications) lives in globalSave.ts — the single source of truth.
478
+ */
479
+ async function saveWorkflow(): Promise<void> {
480
+ await globalSaveWorkflow({
481
+ apiClient: apiClient ?? undefined,
482
+ eventHandlers,
483
+ features,
484
+ onMarkAsSaved: markAsSaved,
485
+ });
486
+ }
487
+
488
+ /**
489
+ * Export workflow - thin wrapper that delegates to globalExportWorkflow().
490
+ *
491
+ * All export logic (flush, metadata construction, file download) lives
492
+ * in globalSave.ts the single source of truth.
493
+ */
494
+ async function exportWorkflow(): Promise<void> {
495
+ await globalExportWorkflow({ features });
496
+ }
497
+
498
+ /**
499
+ * Import workflow from a JSON file
500
+ *
501
+ * Reads the selected file, validates its structure, and loads it into the workflow store.
502
+ */
503
+ function importWorkflow(file: File): void {
504
+ const reader = new FileReader();
505
+ reader.onload = (event) => {
506
+ try {
507
+ const text = event.target?.result;
508
+ if (typeof text !== "string") {
509
+ throw new Error("Could not read file contents.");
510
+ }
511
+ const data = JSON.parse(text);
512
+ const validation = validateWorkflowData(data);
513
+ if (!validation.valid) {
514
+ if (features.showToasts) {
515
+ apiToasts.error(
516
+ "Import workflow",
517
+ validation.error ?? "Invalid workflow JSON",
518
+ );
519
+ }
520
+ logger.warn("Workflow import validation failed:", validation.error);
521
+ return;
522
+ }
523
+ workflowActions.initialize(data as Workflow);
524
+ if (features.showToasts) {
525
+ apiToasts.success(
526
+ "Import workflow",
527
+ "Workflow imported successfully",
528
+ );
529
+ }
530
+ if (eventHandlers?.onWorkflowLoad) {
531
+ eventHandlers.onWorkflowLoad(data as Workflow);
532
+ }
533
+ } catch (error) {
534
+ const errorObj =
535
+ error instanceof Error ? error : new Error("Unknown error occurred");
536
+ logger.error("Workflow import failed:", errorObj);
537
+ if (features.showToasts) {
538
+ apiToasts.error("Import workflow", errorObj.message);
539
+ }
540
+ }
541
+ };
542
+ reader.onerror = () => {
543
+ const message = "Failed to read the selected file.";
544
+ logger.error(message);
545
+ if (features.showToasts) {
546
+ apiToasts.error("Import workflow", message);
547
+ }
548
+ };
549
+ reader.readAsText(file);
550
+ }
551
+
552
+ /**
553
+ * Handle file input change event for workflow import
554
+ */
555
+ function handleImportFileChange(event: Event): void {
556
+ const input = event.target as HTMLInputElement;
557
+ const file = input.files?.[0];
558
+ if (file) {
559
+ importWorkflow(file);
560
+ }
561
+ // Reset input so same file can be re-imported
562
+ input.value = "";
563
+ }
564
+
565
+ // Function to handle clicks outside the sidebar
566
+ function handleCanvasClick(event: MouseEvent): void {
567
+ // Check if the click is outside the right sidebar
568
+ const rightSidebar = document.querySelector(
569
+ ".flowdrop-main-layout__sidebar--right",
570
+ );
571
+ if (rightSidebar && !rightSidebar.contains(event.target as Node)) {
572
+ // Close sidebar when clicking outside of it
573
+ if (isConfigSidebarOpen) {
574
+ closeConfigSidebar();
575
+ }
576
+ }
577
+ }
578
+
579
+ // Load node types on mount
580
+ onMount(() => {
581
+ (async () => {
582
+ try {
583
+ await initializeApiEndpoints();
584
+
585
+ // Ensure port compatibility checker is initialized (needed for proximity connect, etc.)
586
+ // mountFlowDropApp initializes this before mounting, but SvelteKit routes need it here.
587
+ initializePortCompatibility(DEFAULT_PORT_CONFIG);
588
+
589
+ await fetchNodeTypes();
590
+
591
+ // Initialize the workflow store
592
+ if (initialWorkflow) {
593
+ workflowActions.initialize(initialWorkflow);
594
+
595
+ // Emit onWorkflowLoad event
596
+ if (eventHandlers?.onWorkflowLoad) {
597
+ eventHandlers.onWorkflowLoad(initialWorkflow);
598
+ }
599
+ } else {
600
+ // Initialize with a default empty workflow so the editor is functional
601
+ // (e.g., drag-and-drop requires a non-null workflow in the store)
602
+ const defaultWorkflow: Workflow = {
603
+ id: "",
604
+ name: "Untitled Workflow",
605
+ nodes: [],
606
+ edges: [],
607
+ metadata: {
608
+ version: "1.0.0",
609
+ format: DEFAULT_WORKFLOW_FORMAT,
610
+ createdAt: new Date().toISOString(),
611
+ updatedAt: new Date().toISOString(),
612
+ },
613
+ };
614
+ workflowActions.initialize(defaultWorkflow);
615
+ }
616
+ } catch (error) {
617
+ logger.error("Failed to initialize editor:", error);
618
+ }
619
+ })();
620
+
621
+ // Listen for workflow settings toggle from main navbar
622
+ const handleWorkflowSettingsToggle = () => {
623
+ toggleWorkflowSettings();
624
+ };
625
+
626
+ window.addEventListener(
627
+ "workflow-settings-toggle",
628
+ handleWorkflowSettingsToggle,
629
+ );
630
+
631
+ // Initialize auto-save based on user settings
632
+ const cleanupAutoSave = initAutoSave({
633
+ onSave: async () => {
634
+ await saveWorkflow();
635
+ },
636
+ onError: (error) => {
637
+ // Don't show toast for auto-save errors to avoid noise
638
+ logger.warn("Auto-save failed:", error);
639
+ },
640
+ onSuccess: () => {
641
+ logger.debug("Auto-saved workflow");
642
+ },
643
+ });
644
+
645
+ return () => {
646
+ window.removeEventListener(
647
+ "workflow-settings-toggle",
648
+ handleWorkflowSettingsToggle,
649
+ );
650
+ cleanupAutoSave();
651
+ };
652
+ });
653
+
654
+ /**
655
+ * Derived value for showing the right config panel
656
+ * Config panel always appears on the right side
657
+ */
658
+ const hasConfigPanelOpen = $derived(
659
+ isWorkflowSettingsOpen || !!selectedNodeForConfig(),
660
+ );
661
+ const showRightPanel = $derived(!disableSidebar && hasConfigPanelOpen);
662
+
663
+ /**
664
+ * Calculate left sidebar width based on collapsed state
665
+ * When collapsed, use 48px; otherwise use user-configured width
666
+ */
667
+ const leftSidebarWidth = $derived(
668
+ getUiSettings().sidebarCollapsed ? 48 : getUiSettings().sidebarWidth,
669
+ );
670
+
671
+ // File input reference for workflow import
672
+ let fileInputRef = $state<HTMLInputElement | null>(null);
593
673
  </script>
594
674
 
595
675
  <svelte:head>
596
- <title>FlowDrop - Visual Workflow Manager</title>
597
- <meta name="description" content="A modern drag-and-drop workflow editor for LLM applications" />
676
+ <title>FlowDrop - Visual Workflow Manager</title>
677
+ <meta
678
+ name="description"
679
+ content="A modern drag-and-drop workflow editor for LLM applications"
680
+ />
598
681
  </svelte:head>
599
682
 
600
683
  <!-- Hidden file input for workflow JSON import -->
601
684
  <input
602
- bind:this={fileInputRef}
603
- type="file"
604
- accept=".json,application/json"
605
- style="display: none;"
606
- onchange={handleImportFileChange}
685
+ bind:this={fileInputRef}
686
+ type="file"
687
+ accept=".json,application/json"
688
+ style="display: none;"
689
+ onchange={handleImportFileChange}
607
690
  />
608
691
 
609
692
  <!-- MainLayout wrapper for workflow editor -->
610
- <MainLayout
611
- showHeader={showNavbar}
612
- showLeftSidebar={!disableSidebar}
613
- showRightSidebar={showRightPanel}
614
- showBottomPanel={false}
615
- showFooter={false}
616
- headerHeight={60}
617
- {leftSidebarWidth}
618
- rightSidebarWidth={400}
619
- leftSidebarMinWidth={getUiSettings().sidebarCollapsed ? 48 : 280}
620
- leftSidebarMaxWidth={getUiSettings().sidebarCollapsed ? 48 : 450}
621
- rightSidebarMinWidth={320}
622
- rightSidebarMaxWidth={550}
623
- enableLeftSplitPane={false}
624
- enableRightSplitPane={true}
625
- class="flowdrop-app-layout"
626
- >
627
- <!-- Header: Navbar -->
628
- {#snippet header()}
629
- <Navbar
630
- title={breadcrumbTitle()}
631
- primaryActions={navbarActions.length > 0
632
- ? navbarActions
633
- : [
634
- {
635
- label: 'Save',
636
- href: '#save',
637
- icon: 'heroicons:document-arrow-down',
638
- variant: 'primary',
639
- onclick: (e) => {
640
- e.preventDefault();
641
- saveWorkflow();
642
- }
643
- },
644
- {
645
- label: 'Export',
646
- href: '#export',
647
- icon: 'heroicons:arrow-down-tray',
648
- variant: 'outline',
649
- onclick: (e) => {
650
- e.preventDefault();
651
- exportWorkflow();
652
- }
653
- },
654
- {
655
- label: 'Import',
656
- href: '#import',
657
- icon: 'heroicons:arrow-up-tray',
658
- variant: 'outline',
659
- onclick: (e) => {
660
- e.preventDefault();
661
- fileInputRef?.click();
662
- }
663
- },
664
- {
665
- label: 'Workflow Settings',
666
- href: '#settings',
667
- icon: 'heroicons:cog-6-tooth',
668
- variant: 'outline',
669
- onclick: (e) => {
670
- e.preventDefault();
671
- toggleWorkflowSettings();
672
- }
673
- }
674
- ]}
675
- showStatus={true}
676
- {showSettings}
677
- />
678
- {/snippet}
679
-
680
- <!-- Left Sidebar: Node Components -->
681
- {#snippet leftSidebar()}
682
- <NodeSidebar {nodes} activeFormat={getWorkflowFormat()} />
683
- {/snippet}
684
-
685
- <!-- Right Sidebar: Configuration or Workflow Settings -->
686
- {#snippet rightSidebar()}
687
- {#if isWorkflowSettingsOpen}
688
- <ConfigPanel
689
- title="Workflow Settings"
690
- id={getWorkflowStore()?.id}
691
- details={[
692
- { label: 'Nodes', value: String(getWorkflowStore()?.nodes?.length ?? 0) },
693
- { label: 'Connections', value: String(getWorkflowStore()?.edges?.length ?? 0) }
694
- ]}
695
- configTitle="Settings"
696
- onClose={() => (isWorkflowSettingsOpen = false)}
697
- >
698
- <ConfigForm
699
- {authProvider}
700
- schema={workflowConfigSchema}
701
- values={workflowConfigValues}
702
- showUIExtensions={false}
703
- onChange={(config) => {
704
- // Sync workflow settings changes immediately on field blur
705
- const wf = getWorkflowStore();
706
- if (wf) {
707
- const newFormat = (config.format as string) || DEFAULT_WORKFLOW_FORMAT;
708
- const currentFormat = wf.metadata?.format || DEFAULT_WORKFLOW_FORMAT;
709
-
710
- // Warn about incompatible nodes when format changes
711
- if (newFormat !== currentFormat) {
712
- const incompatibleNodes = wf.nodes?.filter((node) => {
713
- const formats = node.data?.metadata?.formats;
714
- return formats && formats.length > 0 && !formats.includes(newFormat);
715
- });
716
- if (incompatibleNodes && incompatibleNodes.length > 0) {
717
- logger.warn(
718
- `Format changed to '${newFormat}'. ${incompatibleNodes.length} node(s) are not compatible with this format and may not export correctly:`,
719
- incompatibleNodes.map((n) => n.data?.label || n.type)
720
- );
721
- }
722
- }
723
-
724
- workflowActions.batchUpdate({
725
- name: config.name as string,
726
- description: config.description as string | undefined,
727
- metadata: {
728
- ...wf.metadata,
729
- format: newFormat
730
- }
731
- });
732
- }
733
- }}
734
- />
735
- </ConfigPanel>
736
- {:else if selectedNodeForConfig()}
737
- {@const currentNode = selectedNodeForConfig()!}
738
- <ConfigPanel
739
- title={currentNode.data.label}
740
- id={currentNode.id}
741
- description={currentNode.data.metadata?.description || 'Node configuration'}
742
- details={[
743
- { label: 'Type', value: currentNode.data.metadata?.type || currentNode.type },
744
- { label: 'Category', value: currentNode.data.metadata?.category || 'general' }
745
- ]}
746
- onClose={closeConfigSidebar}
747
- >
748
- <ConfigForm
749
- {authProvider}
750
- node={currentNode}
751
- workflowId={getWorkflowStore()?.id}
752
- workflowNodes={getWorkflowStore()?.nodes}
753
- workflowEdges={getWorkflowStore()?.edges}
754
- onChange={async (updatedConfig, uiExtensions) => {
755
- // Sync config changes to workflow immediately on field blur
756
- if (selectedNodeId && currentNode) {
757
- // Build the updated node data
758
- const updatedData = {
759
- ...currentNode.data,
760
- config: updatedConfig
761
- };
762
-
763
- // Include UI extensions if provided
764
- if (uiExtensions) {
765
- updatedData.extensions = {
766
- ...currentNode.data.extensions,
767
- ui: uiExtensions
768
- };
769
- }
770
-
771
- // Update the node in the workflow store
772
- const nodeUpdates: Record<string, unknown> = {
773
- data: updatedData
774
- };
775
-
776
- workflowActions.updateNode(selectedNodeId, nodeUpdates);
777
-
778
- // Update the local editor state to reflect config changes immediately
779
- // This is needed for nodeType changes to take effect visually
780
- workflowEditorRef?.updateNodeData(selectedNodeId, updatedData);
781
-
782
- // Refresh edge positions in case config changes affect handles
783
- await workflowEditorRef?.refreshEdgePositions(selectedNodeId);
784
- }
785
- }}
786
- />
787
- </ConfigPanel>
788
- {/if}
789
- {/snippet}
790
-
791
- <!-- Main Content: Workflow Editor with Error Status -->
792
- <!-- Status Display: aria-live announces API errors dynamically without requiring focus -->
793
- {#if error}
794
- <div class="flowdrop-status flowdrop-status--error" aria-live="polite" aria-atomic="true">
795
- <div class="flowdrop-status__content">
796
- <div class="flowdrop-flex flowdrop-gap--3">
797
- <div class="flowdrop-status__indicator flowdrop-status__indicator--error"></div>
798
- <span class="flowdrop-text--sm flowdrop-font--medium">Error: {error}</span>
799
- </div>
800
- <div class="flowdrop-flex flowdrop-gap--2">
801
- <button
802
- class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--primary"
803
- onclick={retryLoad}
804
- type="button"
805
- >
806
- Retry
807
- </button>
808
- <button
809
- class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline"
810
- onclick={() => {
811
- const defaultUrl = '/api/flowdrop';
812
- const newUrl = prompt('Enter Backend API URL:', defaultUrl);
813
- if (newUrl) {
814
- const endpointConfig = createEndpointConfig(newUrl);
815
- setEndpointConfig(endpointConfig);
816
- fetchNodeTypes();
817
- }
818
- }}
819
- type="button"
820
- >
821
- Set API URL
822
- </button>
823
- <button
824
- class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline"
825
- onclick={testApiConnection}
826
- type="button"
827
- >
828
- Test API
829
- </button>
830
- <button
831
- class="flowdrop-btn flowdrop-btn--ghost flowdrop-btn--sm"
832
- onclick={() => (error = null)}
833
- type="button"
834
- >
835
-
836
- </button>
837
- </div>
838
- </div>
839
- </div>
840
- {/if}
841
-
842
- <!-- Main Editor Area -->
843
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions — interactive workflow canvas region with keyboard support -->
844
- <div
845
- class="flowdrop-editor-main"
846
- class:pipeline-view={!!pipelineId}
847
- onclick={handleCanvasClick}
848
- onkeydown={(e) => e.key === 'Escape' && closeConfigSidebar()}
849
- role="region"
850
- aria-label="Workflow canvas"
851
- >
852
- <WorkflowEditor
853
- bind:this={workflowEditorRef}
854
- {nodes}
855
- {height}
856
- {width}
857
- endpointConfig={endpointConfig ?? undefined}
858
- {isConfigSidebarOpen}
859
- selectedNodeForConfig={selectedNodeForConfig()}
860
- {openConfigSidebar}
861
- {closeConfigSidebar}
862
- {lockWorkflow}
863
- {readOnly}
864
- {nodeStatuses}
865
- {pipelineId}
866
- />
867
- </div>
868
- </MainLayout>
693
+ <div class="flowdrop-root">
694
+ <MainLayout
695
+ showHeader={showNavbar}
696
+ showLeftSidebar={!disableSidebar}
697
+ showRightSidebar={showRightPanel}
698
+ showBottomPanel={false}
699
+ showFooter={false}
700
+ headerHeight={60}
701
+ {leftSidebarWidth}
702
+ rightSidebarWidth={400}
703
+ leftSidebarMinWidth={getUiSettings().sidebarCollapsed ? 48 : 280}
704
+ leftSidebarMaxWidth={getUiSettings().sidebarCollapsed ? 48 : 450}
705
+ rightSidebarMinWidth={320}
706
+ rightSidebarMaxWidth={550}
707
+ enableLeftSplitPane={false}
708
+ enableRightSplitPane={true}
709
+ class="flowdrop-app-layout"
710
+ >
711
+ <!-- Header: Navbar -->
712
+ {#snippet header()}
713
+ <Navbar
714
+ title={breadcrumbTitle()}
715
+ primaryActions={navbarActions.length > 0
716
+ ? navbarActions
717
+ : [
718
+ {
719
+ label: "Save",
720
+ href: "#save",
721
+ icon: "heroicons:document-arrow-down",
722
+ variant: "primary",
723
+ onclick: (e) => {
724
+ e.preventDefault();
725
+ saveWorkflow();
726
+ },
727
+ },
728
+ {
729
+ label: "Export",
730
+ href: "#export",
731
+ icon: "heroicons:arrow-down-tray",
732
+ variant: "outline",
733
+ onclick: (e) => {
734
+ e.preventDefault();
735
+ exportWorkflow();
736
+ },
737
+ },
738
+ {
739
+ label: "Import",
740
+ href: "#import",
741
+ icon: "heroicons:arrow-up-tray",
742
+ variant: "outline",
743
+ onclick: (e) => {
744
+ e.preventDefault();
745
+ fileInputRef?.click();
746
+ },
747
+ },
748
+ {
749
+ label: "Workflow Settings",
750
+ href: "#settings",
751
+ icon: "heroicons:cog-6-tooth",
752
+ variant: "outline",
753
+ onclick: (e) => {
754
+ e.preventDefault();
755
+ toggleWorkflowSettings();
756
+ },
757
+ },
758
+ ]}
759
+ showStatus={true}
760
+ {showSettings}
761
+ />
762
+ {/snippet}
763
+
764
+ <!-- Left Sidebar: Node Components -->
765
+ {#snippet leftSidebar()}
766
+ <NodeSidebar
767
+ {nodes}
768
+ activeFormat={getWorkflowFormat()}
769
+ categoriesDefaultOpen={themeConfig?.sidebar?.categoriesDefaultOpen ??
770
+ false}
771
+ />
772
+ {/snippet}
773
+
774
+ <!-- Right Sidebar: Configuration or Workflow Settings -->
775
+ {#snippet rightSidebar()}
776
+ {#if isWorkflowSettingsOpen}
777
+ <ConfigPanel
778
+ title="Workflow Settings"
779
+ id={getWorkflowStore()?.id}
780
+ details={[
781
+ {
782
+ label: "Nodes",
783
+ value: String(getWorkflowStore()?.nodes?.length ?? 0),
784
+ },
785
+ {
786
+ label: "Connections",
787
+ value: String(getWorkflowStore()?.edges?.length ?? 0),
788
+ },
789
+ ]}
790
+ configTitle="Settings"
791
+ onClose={() => (isWorkflowSettingsOpen = false)}
792
+ >
793
+ <ConfigForm
794
+ {authProvider}
795
+ schema={workflowConfigSchema}
796
+ values={workflowConfigValues}
797
+ showUIExtensions={false}
798
+ onChange={(config) => {
799
+ // Sync workflow settings changes immediately on field blur
800
+ const wf = getWorkflowStore();
801
+ if (wf) {
802
+ const newFormat =
803
+ (config.format as string) || DEFAULT_WORKFLOW_FORMAT;
804
+ const currentFormat =
805
+ wf.metadata?.format || DEFAULT_WORKFLOW_FORMAT;
806
+
807
+ // Warn about incompatible nodes when format changes
808
+ if (newFormat !== currentFormat) {
809
+ const incompatibleNodes = wf.nodes?.filter((node) => {
810
+ const formats = node.data?.metadata?.formats;
811
+ return (
812
+ formats &&
813
+ formats.length > 0 &&
814
+ !formats.includes(newFormat)
815
+ );
816
+ });
817
+ if (incompatibleNodes && incompatibleNodes.length > 0) {
818
+ logger.warn(
819
+ `Format changed to '${newFormat}'. ${incompatibleNodes.length} node(s) are not compatible with this format and may not export correctly:`,
820
+ incompatibleNodes.map((n) => n.data?.label || n.type),
821
+ );
822
+ }
823
+ }
824
+
825
+ workflowActions.batchUpdate({
826
+ name: config.name as string,
827
+ description: config.description as string | undefined,
828
+ metadata: {
829
+ ...wf.metadata,
830
+ format: newFormat,
831
+ },
832
+ });
833
+ }
834
+ }}
835
+ />
836
+ </ConfigPanel>
837
+ {:else if selectedNodeForConfig()}
838
+ {@const currentNode = selectedNodeForConfig()!}
839
+ <ConfigPanel
840
+ title={currentNode.data.label}
841
+ id={currentNode.id}
842
+ description={currentNode.data.metadata?.description ||
843
+ "Node configuration"}
844
+ details={[
845
+ {
846
+ label: "Type",
847
+ value: currentNode.data.metadata?.type || currentNode.type,
848
+ },
849
+ {
850
+ label: "Category",
851
+ value: currentNode.data.metadata?.category || "general",
852
+ },
853
+ ]}
854
+ onClose={closeConfigSidebar}
855
+ >
856
+ <ConfigForm
857
+ {authProvider}
858
+ node={currentNode}
859
+ workflowId={getWorkflowStore()?.id}
860
+ workflowNodes={getWorkflowStore()?.nodes}
861
+ workflowEdges={getWorkflowStore()?.edges}
862
+ onChange={async (updatedConfig, uiExtensions) => {
863
+ // Sync config changes to workflow immediately on field blur
864
+ if (selectedNodeId && currentNode) {
865
+ // Build the updated node data
866
+ const updatedData = {
867
+ ...currentNode.data,
868
+ config: updatedConfig,
869
+ };
870
+
871
+ // Include UI extensions if provided
872
+ if (uiExtensions) {
873
+ updatedData.extensions = {
874
+ ...currentNode.data.extensions,
875
+ ui: uiExtensions,
876
+ };
877
+ }
878
+
879
+ // Update the node in the workflow store
880
+ const nodeUpdates: Record<string, unknown> = {
881
+ data: updatedData,
882
+ };
883
+
884
+ workflowActions.updateNode(selectedNodeId, nodeUpdates);
885
+
886
+ // Update the local editor state to reflect config changes immediately
887
+ // This is needed for nodeType changes to take effect visually
888
+ workflowEditorRef?.updateNodeData(selectedNodeId, updatedData);
889
+
890
+ // Refresh edge positions in case config changes affect handles
891
+ await workflowEditorRef?.refreshEdgePositions(selectedNodeId);
892
+ }
893
+ }}
894
+ />
895
+ </ConfigPanel>
896
+ {/if}
897
+ {/snippet}
898
+
899
+ <!-- Main Content: Workflow Editor with Error Status -->
900
+ <!-- Status Display: aria-live announces API errors dynamically without requiring focus -->
901
+ {#if error}
902
+ <div
903
+ class="flowdrop-status flowdrop-status--error"
904
+ aria-live="polite"
905
+ aria-atomic="true"
906
+ >
907
+ <div class="flowdrop-status__content">
908
+ <div class="flowdrop-flex flowdrop-gap--3">
909
+ <div
910
+ class="flowdrop-status__indicator flowdrop-status__indicator--error"
911
+ ></div>
912
+ <span class="flowdrop-text--sm flowdrop-font--medium"
913
+ >Error: {error}</span
914
+ >
915
+ </div>
916
+ <div class="flowdrop-flex flowdrop-gap--2">
917
+ <button
918
+ class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--primary"
919
+ onclick={retryLoad}
920
+ type="button"
921
+ >
922
+ Retry
923
+ </button>
924
+ <button
925
+ class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline"
926
+ onclick={() => {
927
+ const defaultUrl = "/api/flowdrop";
928
+ const newUrl = prompt("Enter Backend API URL:", defaultUrl);
929
+ if (newUrl) {
930
+ const endpointConfig = createEndpointConfig(newUrl);
931
+ setEndpointConfig(endpointConfig);
932
+ fetchNodeTypes();
933
+ }
934
+ }}
935
+ type="button"
936
+ >
937
+ Set API URL
938
+ </button>
939
+ <button
940
+ class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline"
941
+ onclick={testApiConnection}
942
+ type="button"
943
+ >
944
+ Test API
945
+ </button>
946
+ <button
947
+ class="flowdrop-btn flowdrop-btn--ghost flowdrop-btn--sm"
948
+ onclick={() => (error = null)}
949
+ type="button"
950
+ >
951
+
952
+ </button>
953
+ </div>
954
+ </div>
955
+ </div>
956
+ {/if}
957
+
958
+ <!-- Main Editor Area -->
959
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions — interactive workflow canvas region with keyboard support -->
960
+ <div
961
+ class="flowdrop-editor-main"
962
+ class:pipeline-view={!!pipelineId}
963
+ onclick={handleCanvasClick}
964
+ onkeydown={(e) => e.key === "Escape" && closeConfigSidebar()}
965
+ role="region"
966
+ aria-label="Workflow canvas"
967
+ >
968
+ <WorkflowEditor
969
+ bind:this={workflowEditorRef}
970
+ {nodes}
971
+ {height}
972
+ {width}
973
+ endpointConfig={endpointConfig ?? undefined}
974
+ {isConfigSidebarOpen}
975
+ selectedNodeForConfig={selectedNodeForConfig()}
976
+ {openConfigSidebar}
977
+ {closeConfigSidebar}
978
+ {lockWorkflow}
979
+ {readOnly}
980
+ {nodeStatuses}
981
+ {pipelineId}
982
+ />
983
+ </div>
984
+ </MainLayout>
985
+ </div>
869
986
 
870
987
  <style>
871
- /* Status bar styles */
872
- .flowdrop-status {
873
- background-color: var(--fd-info-muted);
874
- border-bottom: 1px solid var(--fd-info);
875
- padding: 1rem;
876
- }
877
-
878
- .flowdrop-status--error {
879
- background-color: var(--fd-error-muted);
880
- border-bottom: 1px solid var(--fd-error);
881
- }
882
-
883
- .flowdrop-status__content {
884
- max-width: 80rem;
885
- margin: 0 auto;
886
- display: flex;
887
- align-items: center;
888
- justify-content: space-between;
889
- }
890
-
891
- .flowdrop-status__indicator {
892
- width: 0.5rem;
893
- height: 0.5rem;
894
- border-radius: 50%;
895
- }
896
-
897
- .flowdrop-status__indicator--error {
898
- background-color: var(--fd-error);
899
- }
900
-
901
- /* Button styles */
902
- .flowdrop-btn {
903
- padding: 0.375rem 0.75rem;
904
- border-radius: var(--fd-radius-md);
905
- font-size: 0.75rem;
906
- font-weight: 500;
907
- cursor: pointer;
908
- border: 1px solid transparent;
909
- transition: all var(--fd-transition-fast);
910
- }
911
-
912
- .flowdrop-btn--sm {
913
- padding: 0.25rem 0.5rem;
914
- font-size: 0.625rem;
915
- }
916
-
917
- .flowdrop-btn--outline {
918
- background-color: transparent;
919
- border-color: var(--fd-border);
920
- color: var(--fd-foreground);
921
- }
922
-
923
- .flowdrop-btn--outline:hover {
924
- background-color: var(--fd-muted);
925
- border-color: var(--fd-border-strong);
926
- }
927
-
928
- .flowdrop-btn--primary {
929
- background-color: var(--fd-primary);
930
- border-color: var(--fd-primary);
931
- color: var(--fd-primary-foreground);
932
- }
933
-
934
- .flowdrop-btn--primary:hover {
935
- background-color: var(--fd-primary-hover);
936
- border-color: var(--fd-primary-hover);
937
- }
938
-
939
- .flowdrop-btn--ghost {
940
- background-color: transparent;
941
- border-color: transparent;
942
- color: var(--fd-muted-foreground);
943
- }
944
-
945
- .flowdrop-btn--ghost:hover {
946
- background-color: var(--fd-muted);
947
- color: var(--fd-foreground);
948
- }
949
-
950
- /* Utility classes */
951
- .flowdrop-flex {
952
- display: flex;
953
- }
954
-
955
- .flowdrop-gap--2 {
956
- gap: 0.5rem;
957
- }
958
-
959
- .flowdrop-gap--3 {
960
- gap: 0.75rem;
961
- }
962
-
963
- .flowdrop-text--sm {
964
- font-size: 0.875rem;
965
- line-height: 1.25rem;
966
- }
967
-
968
- .flowdrop-font--medium {
969
- font-weight: 500;
970
- }
971
-
972
- /* Main editor area */
973
- .flowdrop-editor-main {
974
- flex: 1;
975
- position: relative;
976
- min-width: 0;
977
- height: 100%;
978
- overflow: hidden;
979
- background: var(--fd-layout-background);
980
- }
988
+ .flowdrop-root {
989
+ display: contents;
990
+ }
991
+ /* Status bar styles */
992
+ .flowdrop-status {
993
+ background-color: var(--fd-info-muted);
994
+ border-bottom: 1px solid var(--fd-info);
995
+ padding: 1rem;
996
+ }
997
+
998
+ .flowdrop-status--error {
999
+ background-color: var(--fd-error-muted);
1000
+ border-bottom: 1px solid var(--fd-error);
1001
+ }
1002
+
1003
+ .flowdrop-status__content {
1004
+ max-width: 80rem;
1005
+ margin: 0 auto;
1006
+ display: flex;
1007
+ align-items: center;
1008
+ justify-content: space-between;
1009
+ }
1010
+
1011
+ .flowdrop-status__indicator {
1012
+ width: 0.5rem;
1013
+ height: 0.5rem;
1014
+ border-radius: 50%;
1015
+ }
1016
+
1017
+ .flowdrop-status__indicator--error {
1018
+ background-color: var(--fd-error);
1019
+ }
1020
+
1021
+ /* Button styles */
1022
+ .flowdrop-btn {
1023
+ padding: 0.375rem 0.75rem;
1024
+ border-radius: var(--fd-radius-md);
1025
+ font-size: 0.75rem;
1026
+ font-weight: 500;
1027
+ cursor: pointer;
1028
+ border: 1px solid transparent;
1029
+ transition: all var(--fd-transition-fast);
1030
+ }
1031
+
1032
+ .flowdrop-btn--sm {
1033
+ padding: 0.25rem 0.5rem;
1034
+ font-size: 0.625rem;
1035
+ }
1036
+
1037
+ .flowdrop-btn--outline {
1038
+ background-color: transparent;
1039
+ border-color: var(--fd-border);
1040
+ color: var(--fd-foreground);
1041
+ }
1042
+
1043
+ .flowdrop-btn--outline:hover {
1044
+ background-color: var(--fd-muted);
1045
+ border-color: var(--fd-border-strong);
1046
+ }
1047
+
1048
+ .flowdrop-btn--primary {
1049
+ background-color: var(--fd-primary);
1050
+ border-color: var(--fd-primary);
1051
+ color: var(--fd-primary-foreground);
1052
+ }
1053
+
1054
+ .flowdrop-btn--primary:hover {
1055
+ background-color: var(--fd-primary-hover);
1056
+ border-color: var(--fd-primary-hover);
1057
+ }
1058
+
1059
+ .flowdrop-btn--ghost {
1060
+ background-color: transparent;
1061
+ border-color: transparent;
1062
+ color: var(--fd-muted-foreground);
1063
+ }
1064
+
1065
+ .flowdrop-btn--ghost:hover {
1066
+ background-color: var(--fd-muted);
1067
+ color: var(--fd-foreground);
1068
+ }
1069
+
1070
+ /* Utility classes */
1071
+ .flowdrop-flex {
1072
+ display: flex;
1073
+ }
1074
+
1075
+ .flowdrop-gap--2 {
1076
+ gap: 0.5rem;
1077
+ }
1078
+
1079
+ .flowdrop-gap--3 {
1080
+ gap: 0.75rem;
1081
+ }
1082
+
1083
+ .flowdrop-text--sm {
1084
+ font-size: 0.875rem;
1085
+ line-height: 1.25rem;
1086
+ }
1087
+
1088
+ .flowdrop-font--medium {
1089
+ font-weight: 500;
1090
+ }
1091
+
1092
+ /* Main editor area */
1093
+ .flowdrop-editor-main {
1094
+ flex: 1;
1095
+ position: relative;
1096
+ min-width: 0;
1097
+ height: 100%;
1098
+ overflow: hidden;
1099
+ background: var(--fd-layout-background);
1100
+ }
981
1101
  </style>