@invect/ui 0.0.1

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 (419) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/dist/Invect-CWpIwZ5F.js +92738 -0
  4. package/dist/Invect.d.ts +25 -0
  5. package/dist/InvectShell.d.ts +14 -0
  6. package/dist/api/agent-tools.api.d.ts +1 -0
  7. package/dist/api/client.d.ts +207 -0
  8. package/dist/api/credentials.api.d.ts +47 -0
  9. package/dist/api/executions.api.d.ts +43 -0
  10. package/dist/api/flows.api.d.ts +100 -0
  11. package/dist/api/index.d.ts +9 -0
  12. package/dist/api/node-data.api.d.ts +66 -0
  13. package/dist/api/query-keys.d.ts +22 -0
  14. package/dist/api/triggers.api.d.ts +44 -0
  15. package/dist/api/types.d.ts +147 -0
  16. package/dist/api/use-flow-run-stream.d.ts +12 -0
  17. package/dist/assets/invect-branding.d.ts +4 -0
  18. package/dist/assets/provider-icons/index.d.ts +8 -0
  19. package/dist/babel-C9OtljFZ.js +9721 -0
  20. package/dist/components/PageLayout.d.ts +17 -0
  21. package/dist/components/chat/ChatInput.d.ts +17 -0
  22. package/dist/components/chat/ChatMessageList.d.ts +14 -0
  23. package/dist/components/chat/ChatModelSelector.d.ts +11 -0
  24. package/dist/components/chat/ChatPanel.d.ts +19 -0
  25. package/dist/components/chat/ChatPromptOverlay.d.ts +13 -0
  26. package/dist/components/chat/ChatProviderSelector.d.ts +9 -0
  27. package/dist/components/chat/ChatSettingsPanel.d.ts +11 -0
  28. package/dist/components/chat/InlineCredentialSetup.d.ts +9 -0
  29. package/dist/components/chat/MarkdownRenderer.d.ts +7 -0
  30. package/dist/components/chat/chat-memory.d.ts +21 -0
  31. package/dist/components/chat/chat.store.d.ts +416 -0
  32. package/dist/components/chat/index.d.ts +5 -0
  33. package/dist/components/chat/use-chat.d.ts +28 -0
  34. package/dist/components/credentials/CreateCredentialModal.d.ts +13 -0
  35. package/dist/components/credentials/CredentialDetailDialog.d.ts +17 -0
  36. package/dist/components/credentials/EditCredentialModal.d.ts +11 -0
  37. package/dist/components/credentials/OAuth2ConnectButton.d.ts +38 -0
  38. package/dist/components/credentials/OAuth2ProviderSelector.d.ts +15 -0
  39. package/dist/components/credentials/credential-utils.d.ts +12 -0
  40. package/dist/components/credentials/index.d.ts +9 -0
  41. package/dist/components/dashboard/FailedRunsAlert.d.ts +3 -0
  42. package/dist/components/dashboard/FlowCard.d.ts +7 -0
  43. package/dist/components/dashboard/RecentActivityTable.d.ts +9 -0
  44. package/dist/components/dashboard/StatCard.d.ts +10 -0
  45. package/dist/components/dashboard/index.d.ts +5 -0
  46. package/dist/components/dashboard/status-helpers.d.ts +5 -0
  47. package/dist/components/flow-editor/ActionsSidebar.d.ts +2 -0
  48. package/dist/components/flow-editor/FlowEditor.d.ts +21 -0
  49. package/dist/components/flow-editor/FlowHeader.d.ts +9 -0
  50. package/dist/components/flow-editor/FlowLayout.d.ts +24 -0
  51. package/dist/components/flow-editor/ModeSwitcher.d.ts +7 -0
  52. package/dist/components/flow-editor/NodeSidebar.d.ts +24 -0
  53. package/dist/components/flow-editor/RunControls.d.ts +12 -0
  54. package/dist/components/flow-editor/ToolConfigPanel.d.ts +16 -0
  55. package/dist/components/flow-editor/ValidationPanel.d.ts +5 -0
  56. package/dist/components/flow-editor/flow-editor.store.d.ts +1 -0
  57. package/dist/components/flow-editor/index.d.ts +6 -0
  58. package/dist/components/flow-editor/inline-edit.d.ts +10 -0
  59. package/dist/components/flow-editor/node-config-panel/ConfigFieldWithTemplate.d.ts +26 -0
  60. package/dist/components/flow-editor/node-config-panel/CredentialCombobox.d.ts +21 -0
  61. package/dist/components/flow-editor/node-config-panel/CredentialsSection.d.ts +19 -0
  62. package/dist/components/flow-editor/node-config-panel/DroppableInput.d.ts +20 -0
  63. package/dist/components/flow-editor/node-config-panel/DynamicSelectField.d.ts +22 -0
  64. package/dist/components/flow-editor/node-config-panel/JsonPreviewPanel.d.ts +25 -0
  65. package/dist/components/flow-editor/node-config-panel/NodeConfigPanel.d.ts +14 -0
  66. package/dist/components/flow-editor/node-config-panel/NodeConfigPanelHeader.d.ts +15 -0
  67. package/dist/components/flow-editor/node-config-panel/ParametersSection.d.ts +16 -0
  68. package/dist/components/flow-editor/node-config-panel/SearchableSelectField.d.ts +17 -0
  69. package/dist/components/flow-editor/node-config-panel/SwitchCasesField.d.ts +18 -0
  70. package/dist/components/flow-editor/node-config-panel/hooks/index.d.ts +2 -0
  71. package/dist/components/flow-editor/node-config-panel/hooks/use-node-config-panel-state.d.ts +24 -0
  72. package/dist/components/flow-editor/node-config-panel/hooks/use-node-execution.d.ts +46 -0
  73. package/dist/components/flow-editor/node-config-panel/hooks/use-upstream-slots.d.ts +16 -0
  74. package/dist/components/flow-editor/node-config-panel/panels/AgentToolsPanel.d.ts +18 -0
  75. package/dist/components/flow-editor/node-config-panel/panels/ConfigurationPanel.d.ts +49 -0
  76. package/dist/components/flow-editor/node-config-panel/panels/DataMapperPane.d.ts +40 -0
  77. package/dist/components/flow-editor/node-config-panel/panels/InputPanel.d.ts +49 -0
  78. package/dist/components/flow-editor/node-config-panel/panels/OutputPanel.d.ts +7 -0
  79. package/dist/components/flow-editor/node-config-panel/panels/index.d.ts +4 -0
  80. package/dist/components/flow-editor/node-config-panel/types.d.ts +19 -0
  81. package/dist/components/flow-editor/node-config-panel/use-node-config-panel-store.d.ts +49 -0
  82. package/dist/components/flow-editor/node-config-panel/use-node-config-state.d.ts +26 -0
  83. package/dist/components/flow-editor/node-config-panel/use-run-node.d.ts +16 -0
  84. package/dist/components/flow-editor/node-config-panel/utils.d.ts +9 -0
  85. package/dist/components/flow-editor/serialize-to-sdk.d.ts +20 -0
  86. package/dist/components/flow-editor/toolbar-context.d.ts +2 -0
  87. package/dist/components/flow-editor/use-copy-paste.d.ts +7 -0
  88. package/dist/components/flow-editor/use-copy-paste.types.d.ts +38 -0
  89. package/dist/components/flow-editor/use-flow-editor.d.ts +44 -0
  90. package/dist/components/flow-runs-table/FlowRunsTable.d.ts +6 -0
  91. package/dist/components/flow-runs-table/index.d.ts +1 -0
  92. package/dist/components/flow-viewer/FlowRunsView.d.ts +7 -0
  93. package/dist/components/flow-viewer/FlowStatusView.d.ts +21 -0
  94. package/dist/components/flow-viewer/RunSelector.d.ts +13 -0
  95. package/dist/components/flow-viewer/RunsSidebar.d.ts +14 -0
  96. package/dist/components/flow-viewer/agent-tool-executions-list.d.ts +7 -0
  97. package/dist/components/flow-viewer/index.d.ts +1 -0
  98. package/dist/components/flow-viewer/logs-panel.d.ts +18 -0
  99. package/dist/components/flow-viewer/use-execution-log-data.d.ts +113 -0
  100. package/dist/components/graph/BatchFlowEdge.d.ts +33 -0
  101. package/dist/components/graph/LayoutSelector.d.ts +9 -0
  102. package/dist/components/graph/index.d.ts +47 -0
  103. package/dist/components/graph/styleUtils.d.ts +124 -0
  104. package/dist/components/nodes/AgentConfigPanel.d.ts +24 -0
  105. package/dist/components/nodes/AgentNode.d.ts +8 -0
  106. package/dist/components/nodes/AgentToolsBox.d.ts +41 -0
  107. package/dist/components/nodes/NodeAppendix.d.ts +19 -0
  108. package/dist/components/nodes/NodeStatusIndicator.d.ts +30 -0
  109. package/dist/components/nodes/NodeViewContext.d.ts +18 -0
  110. package/dist/components/nodes/ToolParamField.d.ts +28 -0
  111. package/dist/components/nodes/ToolSelectorModal.d.ts +80 -0
  112. package/dist/components/nodes/ToolSelectorParts.d.ts +30 -0
  113. package/dist/components/nodes/UniversalNode.d.ts +2 -0
  114. package/dist/components/nodes/createContextAwareNodes.d.ts +6 -0
  115. package/dist/components/nodes/index.d.ts +22 -0
  116. package/dist/components/nodes/nodeRegistry.d.ts +13 -0
  117. package/dist/components/nodes/withNodeContext.d.ts +7 -0
  118. package/dist/components/shared/InvectLoader.d.ts +8 -0
  119. package/dist/components/shared/InvectLogo.d.ts +9 -0
  120. package/dist/components/shared/ProviderIcon.d.ts +23 -0
  121. package/dist/components/side-menu/side-menu.d.ts +4 -0
  122. package/dist/components/sidebar/BaseSidebar.d.ts +17 -0
  123. package/dist/components/sidebar/index.d.ts +1 -0
  124. package/dist/components/triggers/CronPreview.d.ts +12 -0
  125. package/dist/components/triggers/index.d.ts +1 -0
  126. package/dist/components/ui/alert-dialog.d.ts +18 -0
  127. package/dist/components/ui/badge.d.ts +9 -0
  128. package/dist/components/ui/button.d.ts +13 -0
  129. package/dist/components/ui/card.d.ts +9 -0
  130. package/dist/components/ui/codemirror-js-editor.d.ts +25 -0
  131. package/dist/components/ui/codemirror-json-editor.d.ts +18 -0
  132. package/dist/components/ui/codemirror-nunjucks-editor.d.ts +13 -0
  133. package/dist/components/ui/codemirror-vscode-theme.d.ts +24 -0
  134. package/dist/components/ui/collapsible.d.ts +6 -0
  135. package/dist/components/ui/command.d.ts +18 -0
  136. package/dist/components/ui/dialog.d.ts +18 -0
  137. package/dist/components/ui/dropdown-menu.d.ts +25 -0
  138. package/dist/components/ui/empty-state.d.ts +21 -0
  139. package/dist/components/ui/input.d.ts +3 -0
  140. package/dist/components/ui/label.d.ts +4 -0
  141. package/dist/components/ui/popover.d.ts +10 -0
  142. package/dist/components/ui/resizable.d.ts +8 -0
  143. package/dist/components/ui/scroll-area.d.ts +5 -0
  144. package/dist/components/ui/select.d.ts +18 -0
  145. package/dist/components/ui/separator.d.ts +4 -0
  146. package/dist/components/ui/slider.d.ts +4 -0
  147. package/dist/components/ui/switch.d.ts +3 -0
  148. package/dist/components/ui/table.d.ts +10 -0
  149. package/dist/components/ui/textarea.d.ts +3 -0
  150. package/dist/components/ui/tooltip.d.ts +7 -0
  151. package/dist/components/ui/tree-view.d.ts +107 -0
  152. package/dist/contexts/AgentToolCallbacksContext.d.ts +23 -0
  153. package/dist/contexts/ApiContext.d.ts +11 -0
  154. package/dist/contexts/FlowDataContext.d.ts +9 -0
  155. package/dist/contexts/NodeRegistryContext.d.ts +14 -0
  156. package/dist/contexts/PluginRegistryContext.d.ts +39 -0
  157. package/dist/contexts/ThemeProvider.d.ts +18 -0
  158. package/dist/contexts/ValidationContext.d.ts +22 -0
  159. package/dist/demo/DemoInvect.d.ts +11 -0
  160. package/dist/demo/FlowViewer.d.ts +31 -0
  161. package/dist/demo/demo-api-client.d.ts +33 -0
  162. package/dist/demo/index.d.ts +6 -0
  163. package/dist/demo/sample-data.d.ts +1538 -0
  164. package/dist/demo.d.ts +2 -0
  165. package/dist/demo.js +2774 -0
  166. package/dist/estree-ClbRfS-1.js +7076 -0
  167. package/dist/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
  168. package/dist/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
  169. package/dist/fonts/geist-latin-wght-normal.woff2 +0 -0
  170. package/dist/fonts/iosevka-latin-400-normal.woff2 +0 -0
  171. package/dist/hooks/index.d.ts +1 -0
  172. package/dist/hooks/use-document-title.d.ts +1 -0
  173. package/dist/hooks/use-flow-data.d.ts +22 -0
  174. package/dist/hooks/use-invect-portal-class.d.ts +21 -0
  175. package/dist/hooks/useFlowEditorStore.d.ts +1 -0
  176. package/dist/index.css +3 -0
  177. package/dist/index.d.ts +22 -0
  178. package/dist/index.js +717 -0
  179. package/dist/lib/utils.d.ts +2 -0
  180. package/dist/prettier.d.ts +13 -0
  181. package/dist/routes/all-flow-runs.d.ts +5 -0
  182. package/dist/routes/credentials.d.ts +5 -0
  183. package/dist/routes/flow-route-layout.d.ts +19 -0
  184. package/dist/routes/flow-runs.d.ts +5 -0
  185. package/dist/routes/flow.d.ts +5 -0
  186. package/dist/routes/home.d.ts +5 -0
  187. package/dist/services/index.d.ts +1 -0
  188. package/dist/standalone-C3Df7W52.js +3463 -0
  189. package/dist/stores/executionViewStore.d.ts +64 -0
  190. package/dist/stores/flow-editor.store.d.ts +137 -0
  191. package/dist/stores/flowEditorStore.d.ts +1 -0
  192. package/dist/stores/index.d.ts +2 -0
  193. package/dist/stores/uiStore.d.ts +45 -0
  194. package/dist/types/agent-tools.types.d.ts +53 -0
  195. package/dist/types/index.d.ts +2 -0
  196. package/dist/types/node-definition.types.d.ts +85 -0
  197. package/dist/types/plugin.types.d.ts +100 -0
  198. package/dist/utils/credentialBranding.d.ts +8 -0
  199. package/dist/utils/credentialFiltering.d.ts +20 -0
  200. package/dist/utils/flowTransformations.d.ts +16 -0
  201. package/dist/utils/layoutUtils.d.ts +23 -0
  202. package/dist/utils/nodeReferenceUtils.d.ts +37 -0
  203. package/dist/vendor.d.ts +5 -0
  204. package/package.json +130 -0
  205. package/src/.DS_Store +0 -0
  206. package/src/Invect.tsx +229 -0
  207. package/src/InvectShell.tsx +55 -0
  208. package/src/api/agent-tools.api.ts +23 -0
  209. package/src/api/client.ts +899 -0
  210. package/src/api/credentials.api.ts +197 -0
  211. package/src/api/executions.api.ts +228 -0
  212. package/src/api/flows.api.ts +195 -0
  213. package/src/api/index.ts +17 -0
  214. package/src/api/node-data.api.ts +167 -0
  215. package/src/api/query-keys.ts +44 -0
  216. package/src/api/triggers.api.ts +120 -0
  217. package/src/api/types.ts +212 -0
  218. package/src/api/use-flow-run-stream.ts +206 -0
  219. package/src/app.css +560 -0
  220. package/src/assets/.DS_Store +0 -0
  221. package/src/assets/favicon.ico +0 -0
  222. package/src/assets/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
  223. package/src/assets/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
  224. package/src/assets/fonts/geist-latin-wght-normal.woff2 +0 -0
  225. package/src/assets/fonts/iosevka-latin-400-normal.woff2 +0 -0
  226. package/src/assets/invect-branding.ts +51 -0
  227. package/src/assets/provider-icons/anthropic.svg +1 -0
  228. package/src/assets/provider-icons/anthropic_light.svg +1 -0
  229. package/src/assets/provider-icons/github.svg +1 -0
  230. package/src/assets/provider-icons/github_light.svg +1 -0
  231. package/src/assets/provider-icons/gmail.svg +1 -0
  232. package/src/assets/provider-icons/google_calendar.svg +1 -0
  233. package/src/assets/provider-icons/google_docs.svg +1 -0
  234. package/src/assets/provider-icons/google_drive.svg +1 -0
  235. package/src/assets/provider-icons/google_sheets.svg +1 -0
  236. package/src/assets/provider-icons/index.ts +55 -0
  237. package/src/assets/provider-icons/linear.svg +1 -0
  238. package/src/assets/provider-icons/openai.svg +1 -0
  239. package/src/assets/provider-icons/postgres.svg +1 -0
  240. package/src/assets/provider-icons/slack.svg +1 -0
  241. package/src/assets/small-loader-dark.svg +22 -0
  242. package/src/assets/small-loader-light.svg +22 -0
  243. package/src/assets/small.svg +7 -0
  244. package/src/components/.DS_Store +0 -0
  245. package/src/components/PageLayout.tsx +55 -0
  246. package/src/components/chat/ChatInput.tsx +115 -0
  247. package/src/components/chat/ChatMessageList.tsx +788 -0
  248. package/src/components/chat/ChatModelSelector.tsx +208 -0
  249. package/src/components/chat/ChatPanel.tsx +243 -0
  250. package/src/components/chat/ChatPromptOverlay.tsx +150 -0
  251. package/src/components/chat/ChatProviderSelector.tsx +135 -0
  252. package/src/components/chat/ChatSettingsPanel.tsx +277 -0
  253. package/src/components/chat/InlineCredentialSetup.tsx +343 -0
  254. package/src/components/chat/MarkdownRenderer.tsx +140 -0
  255. package/src/components/chat/chat-memory.ts +88 -0
  256. package/src/components/chat/chat.store.ts +479 -0
  257. package/src/components/chat/index.ts +5 -0
  258. package/src/components/chat/use-chat.ts +473 -0
  259. package/src/components/credentials/CreateCredentialModal.tsx +609 -0
  260. package/src/components/credentials/CredentialDetailDialog.tsx +882 -0
  261. package/src/components/credentials/EditCredentialModal.tsx +399 -0
  262. package/src/components/credentials/OAuth2ConnectButton.tsx +288 -0
  263. package/src/components/credentials/OAuth2ProviderSelector.tsx +360 -0
  264. package/src/components/credentials/credential-utils.ts +99 -0
  265. package/src/components/credentials/index.ts +10 -0
  266. package/src/components/dashboard/FailedRunsAlert.tsx +67 -0
  267. package/src/components/dashboard/FlowCard.tsx +64 -0
  268. package/src/components/dashboard/RecentActivityTable.tsx +92 -0
  269. package/src/components/dashboard/StatCard.tsx +32 -0
  270. package/src/components/dashboard/index.ts +5 -0
  271. package/src/components/dashboard/status-helpers.tsx +102 -0
  272. package/src/components/flow-editor/ActionsSidebar.tsx +503 -0
  273. package/src/components/flow-editor/FlowEditor.tsx +1002 -0
  274. package/src/components/flow-editor/FlowHeader.tsx +87 -0
  275. package/src/components/flow-editor/FlowLayout.tsx +117 -0
  276. package/src/components/flow-editor/ModeSwitcher.tsx +49 -0
  277. package/src/components/flow-editor/NodeSidebar.tsx +343 -0
  278. package/src/components/flow-editor/RunControls.tsx +109 -0
  279. package/src/components/flow-editor/ToolConfigPanel.tsx +434 -0
  280. package/src/components/flow-editor/ValidationPanel.tsx +167 -0
  281. package/src/components/flow-editor/flow-editor.store.ts +2 -0
  282. package/src/components/flow-editor/index.ts +6 -0
  283. package/src/components/flow-editor/inline-edit.tsx +111 -0
  284. package/src/components/flow-editor/node-config-panel/ConfigFieldWithTemplate.tsx +334 -0
  285. package/src/components/flow-editor/node-config-panel/CredentialCombobox.tsx +217 -0
  286. package/src/components/flow-editor/node-config-panel/CredentialsSection.tsx +154 -0
  287. package/src/components/flow-editor/node-config-panel/DroppableInput.tsx +45 -0
  288. package/src/components/flow-editor/node-config-panel/DynamicSelectField.tsx +223 -0
  289. package/src/components/flow-editor/node-config-panel/JsonPreviewPanel.tsx +134 -0
  290. package/src/components/flow-editor/node-config-panel/NodeConfigPanel.tsx +650 -0
  291. package/src/components/flow-editor/node-config-panel/NodeConfigPanelHeader.tsx +91 -0
  292. package/src/components/flow-editor/node-config-panel/ParametersSection.tsx +144 -0
  293. package/src/components/flow-editor/node-config-panel/SearchableSelectField.tsx +126 -0
  294. package/src/components/flow-editor/node-config-panel/SwitchCasesField.tsx +212 -0
  295. package/src/components/flow-editor/node-config-panel/hooks/index.ts +2 -0
  296. package/src/components/flow-editor/node-config-panel/hooks/use-node-config-panel-state.ts +284 -0
  297. package/src/components/flow-editor/node-config-panel/hooks/use-node-execution.ts +287 -0
  298. package/src/components/flow-editor/node-config-panel/hooks/use-upstream-slots.ts +310 -0
  299. package/src/components/flow-editor/node-config-panel/panels/AgentToolsPanel.tsx +837 -0
  300. package/src/components/flow-editor/node-config-panel/panels/ConfigurationPanel.tsx +383 -0
  301. package/src/components/flow-editor/node-config-panel/panels/DataMapperPane.tsx +456 -0
  302. package/src/components/flow-editor/node-config-panel/panels/InputPanel.tsx +338 -0
  303. package/src/components/flow-editor/node-config-panel/panels/OutputPanel.tsx +109 -0
  304. package/src/components/flow-editor/node-config-panel/panels/index.ts +4 -0
  305. package/src/components/flow-editor/node-config-panel/types.ts +20 -0
  306. package/src/components/flow-editor/node-config-panel/use-node-config-panel-store.ts +283 -0
  307. package/src/components/flow-editor/node-config-panel/use-node-config-state.ts +172 -0
  308. package/src/components/flow-editor/node-config-panel/use-run-node.ts +147 -0
  309. package/src/components/flow-editor/node-config-panel/utils.ts +73 -0
  310. package/src/components/flow-editor/serialize-to-sdk.ts +204 -0
  311. package/src/components/flow-editor/toolbar-context.ts +9 -0
  312. package/src/components/flow-editor/use-copy-paste.ts +575 -0
  313. package/src/components/flow-editor/use-copy-paste.types.ts +35 -0
  314. package/src/components/flow-editor/use-flow-editor.ts +241 -0
  315. package/src/components/flow-runs-table/FlowRunsTable.tsx +631 -0
  316. package/src/components/flow-runs-table/index.ts +1 -0
  317. package/src/components/flow-viewer/FlowRunsView.tsx +268 -0
  318. package/src/components/flow-viewer/FlowStatusView.tsx +351 -0
  319. package/src/components/flow-viewer/RunSelector.tsx +422 -0
  320. package/src/components/flow-viewer/RunsSidebar.tsx +125 -0
  321. package/src/components/flow-viewer/agent-tool-executions-list.tsx +298 -0
  322. package/src/components/flow-viewer/index.ts +1 -0
  323. package/src/components/flow-viewer/logs-panel.tsx +567 -0
  324. package/src/components/flow-viewer/use-execution-log-data.ts +374 -0
  325. package/src/components/graph/BatchFlowEdge.tsx +229 -0
  326. package/src/components/graph/LayoutSelector.tsx +42 -0
  327. package/src/components/graph/index.ts +61 -0
  328. package/src/components/graph/styleUtils.ts +375 -0
  329. package/src/components/nodes/.DS_Store +0 -0
  330. package/src/components/nodes/AgentConfigPanel.tsx +1033 -0
  331. package/src/components/nodes/AgentNode.tsx +298 -0
  332. package/src/components/nodes/AgentToolsBox.tsx +193 -0
  333. package/src/components/nodes/NodeAppendix.tsx +98 -0
  334. package/src/components/nodes/NodeStatusIndicator.tsx +74 -0
  335. package/src/components/nodes/NodeViewContext.tsx +45 -0
  336. package/src/components/nodes/ToolParamField.tsx +282 -0
  337. package/src/components/nodes/ToolSelectorModal.tsx +648 -0
  338. package/src/components/nodes/ToolSelectorParts.tsx +505 -0
  339. package/src/components/nodes/UniversalNode.tsx +356 -0
  340. package/src/components/nodes/createContextAwareNodes.ts +19 -0
  341. package/src/components/nodes/index.ts +45 -0
  342. package/src/components/nodes/nodeRegistry.ts +50 -0
  343. package/src/components/nodes/withNodeContext.tsx +55 -0
  344. package/src/components/shared/InvectLoader.tsx +59 -0
  345. package/src/components/shared/InvectLogo.tsx +59 -0
  346. package/src/components/shared/ProviderIcon.tsx +115 -0
  347. package/src/components/side-menu/side-menu.tsx +267 -0
  348. package/src/components/sidebar/BaseSidebar.tsx +148 -0
  349. package/src/components/sidebar/index.ts +1 -0
  350. package/src/components/triggers/CronPreview.tsx +243 -0
  351. package/src/components/triggers/index.ts +1 -0
  352. package/src/components/ui/alert-dialog.tsx +152 -0
  353. package/src/components/ui/badge.tsx +39 -0
  354. package/src/components/ui/button.tsx +58 -0
  355. package/src/components/ui/card.tsx +75 -0
  356. package/src/components/ui/codemirror-js-editor.tsx +432 -0
  357. package/src/components/ui/codemirror-json-editor.tsx +816 -0
  358. package/src/components/ui/codemirror-nunjucks-editor.tsx +451 -0
  359. package/src/components/ui/codemirror-vscode-theme.ts +243 -0
  360. package/src/components/ui/collapsible.tsx +12 -0
  361. package/src/components/ui/command.tsx +162 -0
  362. package/src/components/ui/dialog.tsx +140 -0
  363. package/src/components/ui/dropdown-menu.tsx +232 -0
  364. package/src/components/ui/empty-state.tsx +93 -0
  365. package/src/components/ui/input.tsx +26 -0
  366. package/src/components/ui/label.tsx +19 -0
  367. package/src/components/ui/popover.tsx +53 -0
  368. package/src/components/ui/resizable.tsx +61 -0
  369. package/src/components/ui/scroll-area.tsx +56 -0
  370. package/src/components/ui/select.tsx +179 -0
  371. package/src/components/ui/separator.tsx +26 -0
  372. package/src/components/ui/slider.tsx +58 -0
  373. package/src/components/ui/switch.tsx +22 -0
  374. package/src/components/ui/table.tsx +90 -0
  375. package/src/components/ui/textarea.tsx +23 -0
  376. package/src/components/ui/tooltip.tsx +54 -0
  377. package/src/components/ui/tree-view.tsx +574 -0
  378. package/src/contexts/AgentToolCallbacksContext.tsx +31 -0
  379. package/src/contexts/ApiContext.tsx +51 -0
  380. package/src/contexts/FlowDataContext.tsx +21 -0
  381. package/src/contexts/NodeRegistryContext.tsx +54 -0
  382. package/src/contexts/PluginRegistryContext.tsx +182 -0
  383. package/src/contexts/ThemeProvider.tsx +106 -0
  384. package/src/contexts/ValidationContext.tsx +122 -0
  385. package/src/demo/DemoInvect.tsx +42 -0
  386. package/src/demo/FlowViewer.tsx +294 -0
  387. package/src/demo/demo-api-client.ts +246 -0
  388. package/src/demo/index.ts +28 -0
  389. package/src/demo/sample-data.ts +1980 -0
  390. package/src/hooks/index.ts +1 -0
  391. package/src/hooks/use-document-title.ts +8 -0
  392. package/src/hooks/use-flow-data.ts +144 -0
  393. package/src/hooks/use-invect-portal-class.ts +27 -0
  394. package/src/hooks/useFlowEditorStore.ts +2 -0
  395. package/src/index.ts +70 -0
  396. package/src/lib/utils.ts +6 -0
  397. package/src/prettier.d.ts +13 -0
  398. package/src/routes/all-flow-runs.tsx +27 -0
  399. package/src/routes/credentials.tsx +362 -0
  400. package/src/routes/flow-route-layout.tsx +113 -0
  401. package/src/routes/flow-runs.tsx +22 -0
  402. package/src/routes/flow.tsx +22 -0
  403. package/src/routes/home.tsx +282 -0
  404. package/src/services/index.ts +6 -0
  405. package/src/stores/executionViewStore.ts +211 -0
  406. package/src/stores/flow-editor.store.ts +738 -0
  407. package/src/stores/flowEditorStore.ts +2 -0
  408. package/src/stores/index.ts +10 -0
  409. package/src/stores/uiStore.ts +189 -0
  410. package/src/types/agent-tools.types.ts +64 -0
  411. package/src/types/index.ts +5 -0
  412. package/src/types/node-definition.types.ts +104 -0
  413. package/src/types/plugin.types.ts +123 -0
  414. package/src/utils/credentialBranding.ts +116 -0
  415. package/src/utils/credentialFiltering.ts +68 -0
  416. package/src/utils/flowTransformations.ts +137 -0
  417. package/src/utils/layoutUtils.ts +127 -0
  418. package/src/utils/nodeReferenceUtils.ts +135 -0
  419. package/src/vendor.d.ts +7 -0
@@ -0,0 +1,1002 @@
1
+ import React, { useRef, useCallback, useState, useMemo } from 'react';
2
+ import { useNavigate, useSearchParams } from 'react-router';
3
+ import { FlowLayout } from './FlowLayout';
4
+ import { ModeSwitcher } from './ModeSwitcher';
5
+ import { RunControls } from './RunControls';
6
+ import { NodeSidebar } from './NodeSidebar';
7
+ import { ValidationPanel } from './ValidationPanel';
8
+ import { LayoutSelector } from '../graph/LayoutSelector';
9
+ import { BatchFlowEdge, defaultEdgeOptions } from '../graph';
10
+ import { applyLayout, type LayoutAlgorithm } from '~/utils/layoutUtils';
11
+ import { generateUniqueDisplayName, generateUniqueReferenceId } from '~/utils/nodeReferenceUtils';
12
+ import { GraphNodeType } from '@invect/core/types';
13
+ import {
14
+ NodeTypes,
15
+ EdgeTypes,
16
+ ReactFlow,
17
+ Controls,
18
+ Background,
19
+ BackgroundVariant,
20
+ SelectionMode,
21
+ useReactFlow,
22
+ useNodesInitialized,
23
+ type Node,
24
+ } from '@xyflow/react';
25
+ import { UniversalNode, AgentNode, type ToolDefinition, type AddedToolInstance } from '../nodes';
26
+ import { getNodeComponent } from '../nodes/nodeRegistry';
27
+ import { NodeConfigPanel } from './node-config-panel/NodeConfigPanel';
28
+ import { ToolConfigPanel } from './ToolConfigPanel';
29
+ import { ChatPanel, ChatToggleButton, ChatPromptOverlay } from '~/components/chat';
30
+ import { useNodeRegistry } from '~/contexts/NodeRegistryContext';
31
+ import { useFlowEditorStore, useIsLoading } from './flow-editor.store';
32
+ import { useFlowActions } from '../../routes/flow-route-layout';
33
+ import { useUIStore } from '~/stores/uiStore';
34
+ import { useTheme } from '~/contexts/ThemeProvider';
35
+ import {
36
+ AgentToolCallbacksProvider,
37
+ type AgentToolCallbacks,
38
+ } from '~/contexts/AgentToolCallbacksContext';
39
+ import { InvectLoader } from '../shared/InvectLoader';
40
+ import { useAgentTools } from '~/api/agent-tools.api';
41
+ import { useNodeExecutions } from '~/api/executions.api';
42
+ import { extractOutputValue } from './node-config-panel/utils';
43
+ import { nanoid } from 'nanoid';
44
+ import { useCopyPaste } from './use-copy-paste';
45
+
46
+ // Stable references for React Flow - defined at module scope to avoid re-renders
47
+ const EDGE_TYPES: EdgeTypes = {
48
+ default: BatchFlowEdge,
49
+ };
50
+
51
+ const FIT_VIEW_OPTIONS = {
52
+ duration: 0,
53
+ padding: 0.2,
54
+ } as const;
55
+
56
+ // Stable empty array to avoid re-render cascades when no tool panel node is selected
57
+ const EMPTY_TOOLS: AddedToolInstance[] = [];
58
+
59
+ // Node dimensions (must match max-w/h in UniversalNode / AgentNode)
60
+ const NODE_WIDTH = 240;
61
+ const NODE_HEIGHT = 60;
62
+ const PLACEMENT_OFFSET = 140; // Larger than max possible node height (128px for 5-output switch)
63
+
64
+ /**
65
+ * Finds a position that doesn't overlap any existing node, stepping down-right
66
+ * by PLACEMENT_OFFSET until a clear spot is found.
67
+ */
68
+ function findNonOverlappingPosition(
69
+ startX: number,
70
+ startY: number,
71
+ existingNodes: Node[],
72
+ ): { x: number; y: number } {
73
+ let x = Math.round(startX);
74
+ let y = Math.round(startY);
75
+
76
+ const overlaps = (cx: number, cy: number) =>
77
+ existingNodes.some(
78
+ (n) =>
79
+ Math.abs(n.position.x - cx) < NODE_WIDTH &&
80
+ Math.abs(n.position.y - cy) < (n.height ?? NODE_HEIGHT),
81
+ );
82
+
83
+ while (overlaps(x, y)) {
84
+ x += PLACEMENT_OFFSET;
85
+ y += PLACEMENT_OFFSET;
86
+ }
87
+
88
+ return { x, y };
89
+ }
90
+
91
+ export interface FlowEditorProps {
92
+ flowId: string;
93
+ flowVersion?: string;
94
+ basePath?: string;
95
+ initialName?: string;
96
+ }
97
+
98
+ // Edit view shell - displays the flow editor
99
+ export function FlowEditor({ flowId, flowVersion, basePath = '' }: FlowEditorProps) {
100
+ const viewportRef = useRef<HTMLDivElement>(null);
101
+ const navigate = useNavigate();
102
+ const addNodeFnRef = useRef<(type: string) => void>(() => {
103
+ // Default implementation
104
+ });
105
+ const [showValidation] = useState(false);
106
+ const [layoutSelector, setLayoutSelector] = useState<React.ReactNode>(null);
107
+
108
+ // Use Zustand store for loading state
109
+ const loading = useIsLoading();
110
+
111
+ // Sidebar visibility (persisted)
112
+ const nodeSidebarOpen = useUIStore((s) => s.nodeSidebarOpen);
113
+ const toggleNodeSidebar = useUIStore((s) => s.toggleNodeSidebar);
114
+
115
+ // Flow actions from parent layout context (execute, active state)
116
+ const flowActions = useFlowActions();
117
+
118
+ const handleModeChange = (newMode: 'edit' | 'runs') => {
119
+ if (newMode === 'runs') {
120
+ const runsPath = flowVersion
121
+ ? `${basePath}/flow/${flowId}/runs/version/${flowVersion}`
122
+ : `${basePath}/flow/${flowId}/runs`;
123
+ navigate(runsPath);
124
+ }
125
+ };
126
+
127
+ const handleRegisterAddNode = useCallback((fn: (type: string) => void) => {
128
+ addNodeFnRef.current = fn;
129
+ }, []);
130
+
131
+ const handleAddNode = useCallback((type: string) => {
132
+ addNodeFnRef.current(type);
133
+ }, []);
134
+
135
+ // Sidebar & right panel render state (lifted from FlowWorkbenchView)
136
+ const [sidebarElement, setSidebarElement] = useState<React.ReactNode>(null);
137
+ const [rightPanelElement, setRightPanelElement] = useState<React.ReactNode>(null);
138
+
139
+ if (loading) {
140
+ return <InvectLoader className="w-full h-full" iconClassName="h-16" label="Loading flow..." />;
141
+ }
142
+
143
+ return (
144
+ <div className="flex h-full min-h-0">
145
+ <div className="flex flex-col flex-1 min-h-0 bg-background text-foreground">
146
+ <FlowLayout
147
+ modeSwitcher={<ModeSwitcher mode="edit" onModeChange={handleModeChange} />}
148
+ layoutSelector={layoutSelector}
149
+ viewportRef={viewportRef}
150
+ sidebar={
151
+ sidebarElement ?? (
152
+ <NodeSidebar mode="nodes" onAddNode={handleAddNode} onCollapse={toggleNodeSidebar} />
153
+ )
154
+ }
155
+ sidebarOpen={nodeSidebarOpen}
156
+ onToggleSidebar={toggleNodeSidebar}
157
+ rightPanel={rightPanelElement}
158
+ chatToggle={<ChatToggleButton />}
159
+ chatPanel={<ChatPanel flowId={flowId} basePath={basePath} />}
160
+ chatOverlay={<ChatPromptOverlay />}
161
+ toolbarExtra={
162
+ <RunControls
163
+ onExecute={flowActions?.onExecute}
164
+ isExecuting={flowActions?.isExecuting}
165
+ isActive={flowActions?.isActive}
166
+ isTogglingActive={flowActions?.isTogglingActive}
167
+ onToggleActive={flowActions?.onToggleActive}
168
+ />
169
+ }
170
+ viewport={
171
+ <FlowWorkbenchView
172
+ flowId={flowId}
173
+ onRegisterAddNode={handleRegisterAddNode}
174
+ onLayoutSelectorRender={setLayoutSelector}
175
+ onAddNode={handleAddNode}
176
+ onSidebarRender={setSidebarElement}
177
+ onRightPanelRender={setRightPanelElement}
178
+ />
179
+ }
180
+ />
181
+ </div>
182
+
183
+ {/* Validation panel slides in from right */}
184
+ {showValidation && <ValidationPanel />}
185
+ </div>
186
+ );
187
+ }
188
+
189
+ interface FlowWorkbenchViewProps {
190
+ flowId: string;
191
+ onRegisterAddNode?: (fn: (type: string) => void) => void;
192
+ onLayoutSelectorRender?: (layoutSelector: React.ReactNode) => void;
193
+ /** Callback to add a node (passed from shell, used for sidebar) */
194
+ onAddNode: (type: string) => void;
195
+ /** Renders the sidebar element (NodeSidebar with current mode + props) */
196
+ onSidebarRender: (sidebar: React.ReactNode) => void;
197
+ /** Renders the right panel element (ToolConfigPanel when active) */
198
+ onRightPanelRender: (rightPanel: React.ReactNode | null) => void;
199
+ }
200
+
201
+ export function FlowWorkbenchView({
202
+ flowId,
203
+ onRegisterAddNode,
204
+ onLayoutSelectorRender,
205
+ onAddNode,
206
+ onSidebarRender,
207
+ onRightPanelRender,
208
+ }: FlowWorkbenchViewProps) {
209
+ // Get theme for React Flow colorMode
210
+ const { resolvedTheme } = useTheme();
211
+
212
+ // === Zustand store: Fine-grained selectors ===
213
+ // Each selector only re-renders when its specific value changes.
214
+ // Previously useFlowEditorStore() without a selector re-rendered on ANY state change
215
+ // (isDirty, flowName, activeFlowRunId, etc.) — even during unrelated updates.
216
+
217
+ // Reactive state slices
218
+ const storeNodes = useFlowEditorStore((s) => s.nodes);
219
+ const storeEdges = useFlowEditorStore((s) => s.edges);
220
+ const edgesReady = useFlowEditorStore((s) => s.edgesReady);
221
+ const loading = useFlowEditorStore((s) => s.isLoading);
222
+ const queryError = useFlowEditorStore((s) => s.error);
223
+ const currentLayout = useFlowEditorStore((s) => s.currentLayout);
224
+ const currentDirection = useFlowEditorStore((s) => s.layoutDirection);
225
+ const configNodeId = useFlowEditorStore((s) => s.selectedNodeId);
226
+ const configPanelOpen = useFlowEditorStore((s) => s.configPanelOpen);
227
+
228
+ // Actions — stable function references, never trigger re-renders
229
+ const onNodesChange = useFlowEditorStore((s) => s.applyNodeChanges);
230
+ const onEdgesChange = useFlowEditorStore((s) => s.applyEdgeChanges);
231
+ const setNodes = useFlowEditorStore((s) => s.setNodes);
232
+ const onConnect = useFlowEditorStore((s) => s.onConnect);
233
+ const setLayout = useFlowEditorStore((s) => s.setLayout);
234
+ const setLayoutedNodes = useFlowEditorStore((s) => s.setLayoutedNodes);
235
+ const openConfigPanel = useFlowEditorStore((s) => s.openConfigPanel);
236
+ const closeConfigPanel = useFlowEditorStore((s) => s.closeConfigPanel);
237
+ const selectNode = useFlowEditorStore((s) => s.selectNode);
238
+ const markInitialLayoutApplied = useFlowEditorStore((s) => s.markInitialLayoutApplied);
239
+ const needsInitialLayout = useFlowEditorStore((s) => s.needsInitialLayout);
240
+ const setRegistryLoading = useFlowEditorStore((s) => s.setRegistryLoading);
241
+ const setNodesInitialized = useFlowEditorStore((s) => s.setNodesInitialized);
242
+ const setAllNodesHaveDefinitions = useFlowEditorStore((s) => s.setAllNodesHaveDefinitions);
243
+ const updateNodeData = useFlowEditorStore((s) => s.updateNodeData);
244
+ const addNodeToStore = useFlowEditorStore((s) => s.addNode);
245
+ const populateFromRunData = useFlowEditorStore((s) => s.populateFromRunData);
246
+
247
+ const { getNodeDefinition, isLoading: registryLoading } = useNodeRegistry();
248
+ const reactFlowInstance = useReactFlow();
249
+ const { fitView } = reactFlowInstance;
250
+
251
+ // Copy/paste/cut/duplicate/delete keyboard shortcuts
252
+ useCopyPaste({ flowId, reactFlowInstance });
253
+
254
+ // Use React Flow's built-in hook to detect when nodes have been initialized
255
+ // This is true when all nodes have been measured and their handles registered
256
+ const nodesInitializedFromHook = useNodesInitialized();
257
+
258
+ // Check that all nodes have their definitions loaded (handles depend on definitions)
259
+ const allNodesHaveDefinitions = useMemo(() => {
260
+ if (storeNodes.length === 0) {
261
+ return false;
262
+ }
263
+ return storeNodes.every((node) => {
264
+ const nodeType = (node.data as { type?: string })?.type;
265
+ return nodeType && getNodeDefinition(nodeType);
266
+ });
267
+ }, [storeNodes, getNodeDefinition]);
268
+
269
+ // Sync React Flow / registry state to Zustand for edge readiness tracking
270
+ React.useEffect(() => {
271
+ setRegistryLoading(registryLoading);
272
+ }, [registryLoading, setRegistryLoading]);
273
+
274
+ React.useEffect(() => {
275
+ setNodesInitialized(nodesInitializedFromHook);
276
+ }, [nodesInitializedFromHook, setNodesInitialized]);
277
+
278
+ React.useEffect(() => {
279
+ setAllNodesHaveDefinitions(allNodesHaveDefinitions);
280
+ }, [allNodesHaveDefinitions, setAllNodesHaveDefinitions]);
281
+
282
+ // Handle openNode + fromRunId query params (e.g. navigated from runs view "Edit" button)
283
+ const [searchParams, setSearchParams] = useSearchParams();
284
+ // Capture fromRunId in a ref so it survives URL param cleanup
285
+ const fromRunIdRef = useRef<string | null>(null);
286
+ if (searchParams.get('fromRunId') && !fromRunIdRef.current) {
287
+ fromRunIdRef.current = searchParams.get('fromRunId');
288
+ }
289
+
290
+ React.useEffect(() => {
291
+ const openNodeId = searchParams.get('openNode');
292
+ if (openNodeId && storeNodes.length > 0) {
293
+ const nodeExists = storeNodes.some((n) => n.id === openNodeId);
294
+ if (nodeExists) {
295
+ openConfigPanel(openNodeId);
296
+ }
297
+ // Clear the params to avoid re-opening on subsequent renders
298
+ setSearchParams(
299
+ (prev) => {
300
+ const next = new URLSearchParams(prev);
301
+ next.delete('openNode');
302
+ next.delete('fromRunId');
303
+ return next;
304
+ },
305
+ { replace: true },
306
+ );
307
+ }
308
+ }, [searchParams, storeNodes, openConfigPanel, setSearchParams]);
309
+
310
+ // Populate node preview data from a specific flow run (when navigating from runs view)
311
+ const { data: fromRunNodeExecutions } = useNodeExecutions(fromRunIdRef.current ?? '');
312
+ const fromRunPopulatedRef = useRef(false);
313
+ React.useEffect(() => {
314
+ if (
315
+ !fromRunIdRef.current ||
316
+ !fromRunNodeExecutions?.length ||
317
+ storeNodes.length === 0 ||
318
+ fromRunPopulatedRef.current
319
+ ) {
320
+ return;
321
+ }
322
+ fromRunPopulatedRef.current = true;
323
+
324
+ const nodeExecutionMap: Record<string, { inputs?: unknown; outputs?: unknown }> = {};
325
+ for (const exec of fromRunNodeExecutions) {
326
+ const extracted = extractOutputValue(exec.outputs);
327
+ nodeExecutionMap[exec.nodeId] = {
328
+ inputs: exec.inputs,
329
+ outputs: extracted ?? undefined,
330
+ };
331
+ }
332
+ populateFromRunData(nodeExecutionMap);
333
+ }, [fromRunNodeExecutions, storeNodes, populateFromRunData]);
334
+
335
+ const dialogContainerRef = useRef<HTMLDivElement | null>(null);
336
+ const isDraggingNodeRef = useRef(false);
337
+ const isShiftKeyHeldRef = useRef(false);
338
+ const dragEndTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
339
+
340
+ // Tool panel state — lives in Zustand store (survives unmount, accessible from anywhere)
341
+ const toolSelectorPanelOpen = useFlowEditorStore((s) => s.toolSelectorOpen);
342
+ const toolConfigPanelOpen = useFlowEditorStore((s) => s.toolConfigOpen);
343
+ const toolPanelNodeId = useFlowEditorStore((s) => s.toolPanelNodeId);
344
+ const selectedToolInstanceId = useFlowEditorStore((s) => s.selectedToolInstanceId);
345
+ const configPanelToolInstanceId = useFlowEditorStore((s) => s.configPanelToolInstanceId);
346
+ const closeToolSelector = useFlowEditorStore((s) => s.closeToolSelector);
347
+ const closeToolConfig = useFlowEditorStore((s) => s.closeToolConfig);
348
+ const setConfigPanelToolInstanceId = useFlowEditorStore((s) => s.setConfigPanelToolInstanceId);
349
+
350
+ // Sidebar mode: "nodes" (default) or "actions" (when editing agent tools)
351
+ const sidebarMode = toolSelectorPanelOpen ? ('actions' as const) : ('nodes' as const);
352
+
353
+ // Fetch available tools from API using React Query
354
+ const { data: agentToolsData } = useAgentTools();
355
+
356
+ // Transform API response to ToolDefinition format
357
+ const availableTools: ToolDefinition[] = useMemo(() => {
358
+ if (!agentToolsData) {
359
+ return [];
360
+ }
361
+ return agentToolsData
362
+ .filter((tool) => tool.provider?.id !== 'triggers' && !tool.id.startsWith('trigger.'))
363
+ .map((tool) => ({
364
+ id: tool.id,
365
+ name: tool.name,
366
+ description: tool.description,
367
+ category: tool.category as ToolDefinition['category'],
368
+ tags: tool.tags,
369
+ inputSchema: tool.inputSchema,
370
+ nodeType: tool.nodeType, // Include nodeType for fetching node definition params
371
+ docsUrl: undefined, // API doesn't provide this yet
372
+ provider: tool.provider, // Provider info for grouping and branding
373
+ }));
374
+ }, [agentToolsData]);
375
+
376
+ // Create stable callback refs for tool selector (to avoid re-renders when passing to nodes)
377
+ const openToolSelectorRef = useRef<(nodeId: string) => void>(() => {
378
+ // noop
379
+ });
380
+ const showMoreToolsRef = useRef<(nodeId: string) => void>(() => {
381
+ // noop
382
+ });
383
+ const removeToolRef = useRef<(nodeId: string, toolId: string) => void>(() => {
384
+ // noop
385
+ });
386
+ const toolClickRef = useRef<(nodeId: string, instanceId: string) => void>(() => {
387
+ // noop
388
+ });
389
+
390
+ // Track shift key state to prevent config panel opening during drag selection
391
+ React.useEffect(() => {
392
+ const handleKeyDown = (e: KeyboardEvent) => {
393
+ if (e.key === 'Shift') {
394
+ isShiftKeyHeldRef.current = true;
395
+ }
396
+ };
397
+ const handleKeyUp = (e: KeyboardEvent) => {
398
+ if (e.key === 'Shift') {
399
+ isShiftKeyHeldRef.current = false;
400
+ }
401
+ };
402
+ window.addEventListener('keydown', handleKeyDown);
403
+ window.addEventListener('keyup', handleKeyUp);
404
+ return () => {
405
+ window.removeEventListener('keydown', handleKeyDown);
406
+ window.removeEventListener('keyup', handleKeyUp);
407
+ };
408
+ }, []);
409
+
410
+ // Agent tool callbacks provided via context so AgentNode can read them directly.
411
+ // This avoids the expensive pattern of remapping ALL nodes on every render just to
412
+ // inject callbacks into Agent node data — which created new object references for
413
+ // every node on every drag, defeating React Flow's internal shallow comparison.
414
+ const agentToolCallbacks = useMemo<AgentToolCallbacks>(
415
+ () => ({
416
+ onOpenToolSelector: (nodeId: string) => openToolSelectorRef.current(nodeId),
417
+ onShowMoreTools: (nodeId: string) => showMoreToolsRef.current(nodeId),
418
+ onRemoveTool: (nodeId: string, instanceId: string) =>
419
+ removeToolRef.current(nodeId, instanceId),
420
+ onToolClick: (nodeId: string, instanceId: string) => toolClickRef.current(nodeId, instanceId),
421
+ availableTools,
422
+ selectedToolNodeId: toolPanelNodeId,
423
+ selectedToolInstanceId,
424
+ }),
425
+ [availableTools, toolPanelNodeId, selectedToolInstanceId],
426
+ );
427
+
428
+ // Only pass edges to React Flow after edgesReady is true (from Zustand store)
429
+ // This prevents "Couldn't create edge for source handle" errors on initial load
430
+ const edges = edgesReady ? storeEdges : [];
431
+
432
+ // Handle layout changes (user-triggered)
433
+ // Reads nodes via getState() at call-time so callback doesn't recreate on every drag
434
+ const handleLayoutChange = useCallback(
435
+ async (algorithm: LayoutAlgorithm, direction: 'TB' | 'BT' | 'LR' | 'RL' = 'LR') => {
436
+ setLayout(algorithm, direction);
437
+ const { nodes: currentNodes, edges: currentEdges } = useFlowEditorStore.getState();
438
+ const { nodes: layoutedNodes } = await applyLayout(
439
+ currentNodes,
440
+ currentEdges,
441
+ algorithm,
442
+ direction,
443
+ );
444
+ setLayoutedNodes(layoutedNodes);
445
+ // Fit view after layout change - use animation for user-triggered changes
446
+ setTimeout(() => {
447
+ fitView({ padding: 0.2, duration: 200 });
448
+ }, 50);
449
+ },
450
+ [setLayout, setLayoutedNodes, fitView],
451
+ );
452
+
453
+ // Apply initial layout when nodes first load (using Zustand state)
454
+ React.useEffect(() => {
455
+ if (needsInitialLayout()) {
456
+ markInitialLayoutApplied();
457
+ const { nodes: currentNodes, edges: currentEdges } = useFlowEditorStore.getState();
458
+ applyLayout(currentNodes, currentEdges, currentLayout, currentDirection).then(
459
+ ({ nodes: layoutedNodes }) => {
460
+ setNodes(layoutedNodes);
461
+ },
462
+ );
463
+ }
464
+ }, [
465
+ needsInitialLayout,
466
+ markInitialLayoutApplied,
467
+ storeNodes,
468
+ storeEdges,
469
+ currentLayout,
470
+ currentDirection,
471
+ setNodes,
472
+ ]);
473
+
474
+ // Fit view once after initial render - triggered by onInit
475
+ // Uses getState() instead of depending on nodes to avoid recreating on every position change
476
+ const handleReactFlowInit = useCallback(() => {
477
+ const currentNodes = useFlowEditorStore.getState().nodes;
478
+ if (currentNodes.length > 0) {
479
+ const hasValidPositions = currentNodes.some(
480
+ (node) => node.position.x !== 0 || node.position.y !== 0,
481
+ );
482
+
483
+ if (hasValidPositions) {
484
+ fitView({ padding: 0.2, duration: 0 });
485
+ } else {
486
+ // Layout is still being applied, wait and retry
487
+ setTimeout(() => {
488
+ fitView({ padding: 0.2, duration: 0 });
489
+ }, 100);
490
+ }
491
+ }
492
+ }, [fitView]);
493
+
494
+ // Close config panel if the selected node is deleted (already handled by removeNode in store,
495
+ // but we keep this as a safety check for edge cases)
496
+ React.useEffect(() => {
497
+ if (configNodeId && !storeNodes.some((candidate) => candidate.id === configNodeId)) {
498
+ closeConfigPanel();
499
+ selectNode(null);
500
+ }
501
+ }, [configNodeId, storeNodes, closeConfigPanel, selectNode]);
502
+
503
+ const handleNodeDoubleClick = useCallback(
504
+ (event: React.MouseEvent, clickedNode: Node) => {
505
+ if (isDraggingNodeRef.current || isShiftKeyHeldRef.current) {
506
+ return;
507
+ }
508
+ setConfigPanelToolInstanceId(null); // reset tool pre-selection
509
+ openConfigPanel(clickedNode.id);
510
+ },
511
+ [openConfigPanel],
512
+ );
513
+
514
+ const handleSelectionChange = useCallback<
515
+ NonNullable<React.ComponentProps<typeof ReactFlow>['onSelectionChange']>
516
+ >(
517
+ ({ nodes: selectedNodes }) => {
518
+ if (isDraggingNodeRef.current || isShiftKeyHeldRef.current) {
519
+ return;
520
+ }
521
+ // Only close the panel when selection is cleared, don't open on selection
522
+ // Opening is handled by double-click only
523
+ if (selectedNodes.length === 0) {
524
+ closeConfigPanel();
525
+ selectNode(null);
526
+ }
527
+ },
528
+ [closeConfigPanel, selectNode],
529
+ );
530
+
531
+ const handlePanelOpenChange = useCallback(
532
+ (open: boolean) => {
533
+ if (open) {
534
+ // Panel is being opened - this shouldn't happen via this callback
535
+ } else {
536
+ closeConfigPanel();
537
+ selectNode(null);
538
+ setConfigPanelToolInstanceId(null);
539
+ }
540
+ },
541
+ [closeConfigPanel, selectNode],
542
+ );
543
+
544
+ // Tool panel handlers for Agent nodes
545
+ const _setNodeSidebarOpen = useUIStore((s) => s.setNodeSidebarOpen);
546
+
547
+ const handleOpenToolSelector = useCallback(
548
+ (nodeId: string) => {
549
+ // Open NodeConfigPanel for this node — the Tools tab will show
550
+ setConfigPanelToolInstanceId(null);
551
+ openConfigPanel(nodeId);
552
+ },
553
+ [openConfigPanel],
554
+ );
555
+
556
+ // Opens tool selector panel (show more from agent tools box)
557
+ const handleShowMoreTools = useCallback(
558
+ (nodeId: string) => {
559
+ setConfigPanelToolInstanceId(null);
560
+ openConfigPanel(nodeId);
561
+ },
562
+ [openConfigPanel],
563
+ );
564
+
565
+ // Opens tool config panel when a tool instance is clicked
566
+ const handleToolClick = useCallback(
567
+ (nodeId: string, instanceId: string) => {
568
+ setConfigPanelToolInstanceId(instanceId);
569
+ openConfigPanel(nodeId);
570
+ },
571
+ [openConfigPanel],
572
+ );
573
+
574
+ // Close tool selector (returns sidebar to nodes mode)
575
+ const handleCloseToolSelector = useCallback(() => {
576
+ closeToolSelector();
577
+ }, [closeToolSelector]);
578
+
579
+ // Close tool config panel
580
+ const handleCloseToolConfig = useCallback(() => {
581
+ closeToolConfig();
582
+ }, [closeToolConfig]);
583
+
584
+ // Handle selecting a tool instance for configuration (from selector panel)
585
+ const openToolConfig = useFlowEditorStore((s) => s.openToolConfig);
586
+ const handleSelectToolInstance = useCallback(
587
+ (instance: AddedToolInstance) => {
588
+ if (toolPanelNodeId) {
589
+ openToolConfig(toolPanelNodeId, instance.instanceId);
590
+ }
591
+ },
592
+ [toolPanelNodeId, openToolConfig],
593
+ );
594
+
595
+ // Keep refs in sync with callbacks
596
+ openToolSelectorRef.current = handleOpenToolSelector;
597
+ showMoreToolsRef.current = handleShowMoreTools;
598
+ toolClickRef.current = handleToolClick;
599
+
600
+ // Get added tools for the currently selected node
601
+ // Reads nodes via getState() at call-time — stable callback, no storeNodes dependency
602
+ const getAddedToolsForNode = useCallback((nodeId: string): AddedToolInstance[] => {
603
+ const node = useFlowEditorStore.getState().nodes.find((n) => n.id === nodeId);
604
+ if (!node) {
605
+ return [];
606
+ }
607
+ const params = (node.data as Record<string, unknown>)?.params as Record<string, unknown>;
608
+ return (params?.addedTools as AddedToolInstance[]) || [];
609
+ }, []);
610
+
611
+ const handleAddToolToNode = useCallback(
612
+ (toolId: string): string => {
613
+ if (!toolPanelNodeId) {
614
+ return '';
615
+ }
616
+ const node = useFlowEditorStore.getState().nodes.find((n) => n.id === toolPanelNodeId);
617
+ if (!node) {
618
+ return '';
619
+ }
620
+
621
+ // Find the tool definition to get default name/description
622
+ const toolDef = availableTools.find((t) => t.id === toolId);
623
+ if (!toolDef) {
624
+ return '';
625
+ }
626
+
627
+ // Create a new tool instance with unique ID
628
+ const instanceId = nanoid();
629
+ const newInstance: AddedToolInstance = {
630
+ instanceId,
631
+ toolId: toolId,
632
+ name: toolDef.name,
633
+ description: toolDef.description,
634
+ params: {},
635
+ };
636
+
637
+ const currentTools = getAddedToolsForNode(toolPanelNodeId);
638
+ updateNodeData(toolPanelNodeId, {
639
+ params: {
640
+ ...((node.data as Record<string, unknown>)?.params as Record<string, unknown>),
641
+ addedTools: [...currentTools, newInstance],
642
+ },
643
+ });
644
+
645
+ return instanceId;
646
+ },
647
+ [toolPanelNodeId, availableTools, getAddedToolsForNode, updateNodeData],
648
+ );
649
+
650
+ const handleRemoveToolFromNode = useCallback(
651
+ (instanceId: string) => {
652
+ if (!toolPanelNodeId) {
653
+ return;
654
+ }
655
+ const node = useFlowEditorStore.getState().nodes.find((n) => n.id === toolPanelNodeId);
656
+ if (!node) {
657
+ return;
658
+ }
659
+
660
+ const currentTools = getAddedToolsForNode(toolPanelNodeId);
661
+ updateNodeData(toolPanelNodeId, {
662
+ params: {
663
+ ...((node.data as Record<string, unknown>)?.params as Record<string, unknown>),
664
+ addedTools: currentTools.filter((t) => t.instanceId !== instanceId),
665
+ },
666
+ });
667
+ // If we just removed the tool that's being configured, close config panel
668
+ if (selectedToolInstanceId === instanceId) {
669
+ closeToolConfig();
670
+ }
671
+ },
672
+ [
673
+ toolPanelNodeId,
674
+ getAddedToolsForNode,
675
+ selectedToolInstanceId,
676
+ updateNodeData,
677
+ closeToolConfig,
678
+ ],
679
+ );
680
+
681
+ const handleUpdateToolInNode = useCallback(
682
+ (instanceId: string, updates: Partial<Omit<AddedToolInstance, 'instanceId' | 'toolId'>>) => {
683
+ if (!toolPanelNodeId) {
684
+ return;
685
+ }
686
+ const node = useFlowEditorStore.getState().nodes.find((n) => n.id === toolPanelNodeId);
687
+ if (!node) {
688
+ return;
689
+ }
690
+
691
+ const currentTools = getAddedToolsForNode(toolPanelNodeId);
692
+ updateNodeData(toolPanelNodeId, {
693
+ params: {
694
+ ...((node.data as Record<string, unknown>)?.params as Record<string, unknown>),
695
+ addedTools: currentTools.map((t) =>
696
+ t.instanceId === instanceId ? { ...t, ...updates } : t,
697
+ ),
698
+ },
699
+ });
700
+ },
701
+ [toolPanelNodeId, getAddedToolsForNode, updateNodeData],
702
+ );
703
+
704
+ // Helper to remove tool from a specific node (used by AgentNode directly)
705
+ const handleRemoveToolFromSpecificNode = useCallback(
706
+ (nodeId: string, instanceId: string) => {
707
+ const node = useFlowEditorStore.getState().nodes.find((n) => n.id === nodeId);
708
+ if (!node) {
709
+ return;
710
+ }
711
+
712
+ const params = (node.data as Record<string, unknown>)?.params as Record<string, unknown>;
713
+ const currentTools = (params?.addedTools as AddedToolInstance[]) || [];
714
+ updateNodeData(nodeId, {
715
+ params: {
716
+ ...params,
717
+ addedTools: currentTools.filter((t) => t.instanceId !== instanceId),
718
+ },
719
+ });
720
+ },
721
+ [updateNodeData],
722
+ );
723
+
724
+ // Keep ref in sync with callback
725
+ removeToolRef.current = handleRemoveToolFromSpecificNode;
726
+
727
+ const handleNodeDragStart = useCallback(() => {
728
+ if (dragEndTimeoutRef.current) {
729
+ clearTimeout(dragEndTimeoutRef.current);
730
+ dragEndTimeoutRef.current = null;
731
+ }
732
+ isDraggingNodeRef.current = true;
733
+ }, []);
734
+
735
+ const handleNodeDragStop = useCallback(() => {
736
+ if (dragEndTimeoutRef.current) {
737
+ clearTimeout(dragEndTimeoutRef.current);
738
+ }
739
+ dragEndTimeoutRef.current = setTimeout(() => {
740
+ isDraggingNodeRef.current = false;
741
+ dragEndTimeoutRef.current = null;
742
+ }, 150);
743
+ }, []);
744
+
745
+ React.useEffect(() => {
746
+ return () => {
747
+ if (dragEndTimeoutRef.current) {
748
+ clearTimeout(dragEndTimeoutRef.current);
749
+ dragEndTimeoutRef.current = null;
750
+ }
751
+ };
752
+ }, []);
753
+
754
+ // Register add-node function for sidebar
755
+ // Reads nodes via getState() at call-time — stable callback, doesn't recreate on every drag
756
+ const createNewNode = useCallback(
757
+ (type: string) => {
758
+ const definition = getNodeDefinition(type);
759
+
760
+ // Enforce maxInstances: check if adding this node would exceed the limit
761
+ if (definition?.maxInstances !== null && definition?.maxInstances !== undefined) {
762
+ const currentNodes = useFlowEditorStore.getState().nodes;
763
+ const existingCount = currentNodes.filter(
764
+ (n) => (n.data as Record<string, unknown>)?.type === type,
765
+ ).length;
766
+ if (existingCount >= definition.maxInstances) {
767
+ console.warn(
768
+ `[Node Limit] Cannot add another "${definition.label}" — only ${definition.maxInstances} allowed per flow.`,
769
+ );
770
+ return;
771
+ }
772
+ }
773
+
774
+ const id = `${type}-${Date.now()}`;
775
+
776
+ const fieldDefaults = (definition?.paramFields || []).reduce<Record<string, unknown>>(
777
+ (acc, field) => {
778
+ if (field.defaultValue !== undefined) {
779
+ acc[field.name] = field.defaultValue;
780
+ }
781
+ return acc;
782
+ },
783
+ {},
784
+ );
785
+
786
+ const defaultParams = {
787
+ ...definition?.defaultParams,
788
+ ...fieldDefaults,
789
+ };
790
+
791
+ const baseDisplayName = definition?.label || type;
792
+ const currentNodes = useFlowEditorStore.getState().nodes;
793
+ const displayName = generateUniqueDisplayName(baseDisplayName, currentNodes);
794
+ const referenceId = generateUniqueReferenceId(displayName, currentNodes);
795
+
796
+ // Determine starting position for placement:
797
+ // - No nodes: use viewport center so the first node appears where the user is looking
798
+ // - Has nodes: cascade from the last added node to guarantee visible separation
799
+ let startX: number;
800
+ let startY: number;
801
+ if (currentNodes.length === 0) {
802
+ const viewportCenter = reactFlowInstance.screenToFlowPosition({
803
+ x: window.innerWidth / 2,
804
+ y: window.innerHeight / 2,
805
+ });
806
+ startX = Math.round(viewportCenter.x - NODE_WIDTH / 2);
807
+ startY = Math.round(viewportCenter.y - NODE_HEIGHT / 2);
808
+ } else {
809
+ const lastNode = currentNodes[currentNodes.length - 1];
810
+ startX = lastNode.position.x + PLACEMENT_OFFSET;
811
+ startY = lastNode.position.y + PLACEMENT_OFFSET;
812
+ }
813
+
814
+ const position = findNonOverlappingPosition(startX, startY, currentNodes);
815
+
816
+ const newNode: Node = {
817
+ id,
818
+ type,
819
+ position,
820
+ data: {
821
+ display_name: displayName,
822
+ reference_id: referenceId,
823
+ type,
824
+ params: defaultParams,
825
+ },
826
+ };
827
+
828
+ addNodeToStore(newNode);
829
+ },
830
+ [getNodeDefinition, addNodeToStore, reactFlowInstance],
831
+ );
832
+
833
+ React.useEffect(() => {
834
+ if (onRegisterAddNode) {
835
+ onRegisterAddNode(createNewNode);
836
+ }
837
+ }, [onRegisterAddNode, createNewNode]);
838
+
839
+ // Render layout selector
840
+ React.useEffect(() => {
841
+ if (onLayoutSelectorRender) {
842
+ onLayoutSelectorRender(
843
+ <LayoutSelector currentLayout={currentLayout} onLayoutChange={handleLayoutChange} />,
844
+ );
845
+ }
846
+ }, [currentLayout, handleLayoutChange, onLayoutSelectorRender]);
847
+
848
+ // Compute current added tools and selected tool instance for panels.
849
+ // IMPORTANT: useMemo to avoid creating a new [] reference every render,
850
+ // which would cascade into the onSidebarRender effect → setSidebarElement → re-render loop.
851
+ const currentNodeAddedTools = useMemo(() => {
852
+ if (!toolPanelNodeId) {
853
+ return EMPTY_TOOLS;
854
+ }
855
+ return getAddedToolsForNode(toolPanelNodeId);
856
+ }, [toolPanelNodeId, getAddedToolsForNode, storeNodes]);
857
+ const selectedToolInstance = selectedToolInstanceId
858
+ ? (currentNodeAddedTools.find((t) => t.instanceId === selectedToolInstanceId) ?? null)
859
+ : null;
860
+ const selectedToolDef = selectedToolInstance
861
+ ? (availableTools.find((t) => t.id === selectedToolInstance.toolId) ?? null)
862
+ : null;
863
+
864
+ // Push sidebar element to shell (switches between nodes / actions mode)
865
+ const toggleNodeSidebar = useUIStore((s) => s.toggleNodeSidebar);
866
+ React.useEffect(() => {
867
+ onSidebarRender(
868
+ <NodeSidebar
869
+ mode={sidebarMode}
870
+ onAddNode={onAddNode}
871
+ onCollapse={toggleNodeSidebar}
872
+ onClose={sidebarMode === 'actions' ? handleCloseToolSelector : undefined}
873
+ availableTools={availableTools}
874
+ addedTools={currentNodeAddedTools}
875
+ onAddTool={handleAddToolToNode}
876
+ onRemoveTool={handleRemoveToolFromNode}
877
+ onSelectTool={handleSelectToolInstance}
878
+ selectedInstanceId={selectedToolInstanceId}
879
+ />,
880
+ );
881
+ }, [
882
+ sidebarMode,
883
+ onAddNode,
884
+ toggleNodeSidebar,
885
+ handleCloseToolSelector,
886
+ availableTools,
887
+ currentNodeAddedTools,
888
+ handleAddToolToNode,
889
+ handleRemoveToolFromNode,
890
+ handleSelectToolInstance,
891
+ selectedToolInstanceId,
892
+ onSidebarRender,
893
+ ]);
894
+
895
+ // Push right panel element to shell (tool config panel when active)
896
+ React.useEffect(() => {
897
+ if (toolConfigPanelOpen && selectedToolDef && selectedToolInstance) {
898
+ onRightPanelRender(
899
+ <ToolConfigPanel
900
+ open={toolConfigPanelOpen}
901
+ onClose={handleCloseToolConfig}
902
+ tool={selectedToolDef}
903
+ instance={selectedToolInstance}
904
+ onUpdate={handleUpdateToolInNode}
905
+ onRemove={handleRemoveToolFromNode}
906
+ portalContainer={dialogContainerRef.current}
907
+ />,
908
+ );
909
+ } else {
910
+ onRightPanelRender(null);
911
+ }
912
+ }, [
913
+ toolConfigPanelOpen,
914
+ selectedToolDef,
915
+ selectedToolInstance,
916
+ handleCloseToolConfig,
917
+ handleUpdateToolInNode,
918
+ handleRemoveToolFromNode,
919
+ onRightPanelRender,
920
+ ]);
921
+
922
+ // Node types registry — AGENT gets a custom component, everything else
923
+ // renders as UniversalNode. Known action types are registered explicitly
924
+ // from nodeDefinitions to avoid ReactFlow's fallback CSS class
925
+ // (.react-flow__node-default) which adds unwanted border/padding.
926
+ // The "default" key catches any truly unknown types (e.g. pasted from SDK).
927
+ const { nodeDefinitions } = useNodeRegistry();
928
+ const nodeTypes: NodeTypes = useMemo(() => {
929
+ // @ts-ignore React 19 vs 18 type mismatch in @xyflow/react
930
+ // eslint-disable-next-line typescript/no-explicit-any -- React node components require generic any props
931
+ const mapping: Record<string, React.ComponentType<any>> = {
932
+ [GraphNodeType.AGENT]: AgentNode,
933
+ default: UniversalNode,
934
+ };
935
+ for (const def of nodeDefinitions) {
936
+ if (!(def.type in mapping)) {
937
+ mapping[def.type] = getNodeComponent(def.type);
938
+ }
939
+ }
940
+ return mapping;
941
+ }, [nodeDefinitions]);
942
+
943
+ if (loading) {
944
+ return <InvectLoader className="w-full h-full" iconClassName="h-16" label="Loading flow..." />;
945
+ }
946
+
947
+ if (queryError) {
948
+ return (
949
+ <div className="flex items-center justify-center w-full h-full">
950
+ <div className="p-4 text-red-800 border border-red-200 rounded bg-red-50">
951
+ Error: {queryError}
952
+ </div>
953
+ </div>
954
+ );
955
+ }
956
+
957
+ return (
958
+ <>
959
+ <div
960
+ style={{ width: '100%', height: '100%', background: 'var(--canvas-background)' }}
961
+ ref={dialogContainerRef}
962
+ >
963
+ <AgentToolCallbacksProvider value={agentToolCallbacks}>
964
+ <ReactFlow
965
+ nodes={storeNodes}
966
+ edges={edges}
967
+ onNodesChange={onNodesChange}
968
+ onEdgesChange={onEdgesChange}
969
+ onConnect={onConnect}
970
+ onNodeDoubleClick={handleNodeDoubleClick}
971
+ onSelectionChange={handleSelectionChange}
972
+ onNodeDragStart={handleNodeDragStart}
973
+ onNodeDragStop={handleNodeDragStop}
974
+ nodeTypes={nodeTypes}
975
+ edgeTypes={EDGE_TYPES}
976
+ defaultEdgeOptions={defaultEdgeOptions}
977
+ colorMode={resolvedTheme}
978
+ fitView
979
+ fitViewOptions={FIT_VIEW_OPTIONS}
980
+ onInit={handleReactFlowInit}
981
+ panOnDrag={[1, 2]}
982
+ selectionOnDrag
983
+ selectionMode={SelectionMode.Partial}
984
+ panOnScroll
985
+ >
986
+ <Controls />
987
+ <Background variant={BackgroundVariant.Dots} gap={20} size={1.2} />
988
+ </ReactFlow>
989
+ </AgentToolCallbacksProvider>
990
+ </div>
991
+ <NodeConfigPanel
992
+ open={configPanelOpen}
993
+ nodeId={configNodeId}
994
+ flowId={flowId}
995
+ onOpenChange={handlePanelOpenChange}
996
+ portalContainer={dialogContainerRef.current}
997
+ availableTools={availableTools}
998
+ initialToolInstanceId={configPanelToolInstanceId}
999
+ />
1000
+ </>
1001
+ );
1002
+ }