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