@foresthubai/workflow-builder 0.4.3 → 0.4.5

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 (414) hide show
  1. package/LICENSE +661 -661
  2. package/NOTICE +16 -16
  3. package/README.md +110 -110
  4. package/dist/BuilderLayout.d.ts.map +1 -1
  5. package/dist/BuilderLayout.js +52 -61
  6. package/dist/BuilderLayout.js.map +1 -1
  7. package/dist/Canvas.d.ts.map +1 -1
  8. package/dist/Canvas.js +10 -3
  9. package/dist/Canvas.js.map +1 -1
  10. package/dist/CanvasEditor.d.ts.map +1 -1
  11. package/dist/CanvasEditor.js.map +1 -1
  12. package/dist/CanvasTabsToolbar.d.ts.map +1 -1
  13. package/dist/CanvasTabsToolbar.js +3 -0
  14. package/dist/CanvasTabsToolbar.js.map +1 -1
  15. package/dist/RightConfigPanel.d.ts.map +1 -1
  16. package/dist/RightConfigPanel.js +3 -0
  17. package/dist/RightConfigPanel.js.map +1 -1
  18. package/dist/WorkflowBuilder.d.ts +7 -13
  19. package/dist/WorkflowBuilder.d.ts.map +1 -1
  20. package/dist/WorkflowBuilder.js +25 -13
  21. package/dist/WorkflowBuilder.js.map +1 -1
  22. package/dist/cn.d.ts.map +1 -1
  23. package/dist/cn.js +3 -0
  24. package/dist/cn.js.map +1 -1
  25. package/dist/components/ui/add-button.d.ts.map +1 -1
  26. package/dist/components/ui/add-button.js +3 -0
  27. package/dist/components/ui/add-button.js.map +1 -1
  28. package/dist/components/ui/alert-dialog.d.ts.map +1 -1
  29. package/dist/components/ui/alert-dialog.js +3 -0
  30. package/dist/components/ui/alert-dialog.js.map +1 -1
  31. package/dist/components/ui/alert.d.ts.map +1 -1
  32. package/dist/components/ui/alert.js +3 -0
  33. package/dist/components/ui/alert.js.map +1 -1
  34. package/dist/components/ui/badge.d.ts.map +1 -1
  35. package/dist/components/ui/badge.js.map +1 -1
  36. package/dist/components/ui/button.d.ts.map +1 -1
  37. package/dist/components/ui/button.js +3 -0
  38. package/dist/components/ui/button.js.map +1 -1
  39. package/dist/components/ui/card.d.ts.map +1 -1
  40. package/dist/components/ui/card.js +3 -0
  41. package/dist/components/ui/card.js.map +1 -1
  42. package/dist/components/ui/checkbox.d.ts.map +1 -1
  43. package/dist/components/ui/checkbox.js +3 -0
  44. package/dist/components/ui/checkbox.js.map +1 -1
  45. package/dist/components/ui/collapsible.d.ts.map +1 -1
  46. package/dist/components/ui/collapsible.js +3 -0
  47. package/dist/components/ui/collapsible.js.map +1 -1
  48. package/dist/components/ui/command.d.ts +2 -2
  49. package/dist/components/ui/command.d.ts.map +1 -1
  50. package/dist/components/ui/command.js +3 -0
  51. package/dist/components/ui/command.js.map +1 -1
  52. package/dist/components/ui/delete-button.d.ts.map +1 -1
  53. package/dist/components/ui/delete-button.js +3 -0
  54. package/dist/components/ui/delete-button.js.map +1 -1
  55. package/dist/components/ui/dialog.d.ts.map +1 -1
  56. package/dist/components/ui/dialog.js +3 -0
  57. package/dist/components/ui/dialog.js.map +1 -1
  58. package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
  59. package/dist/components/ui/dropdown-menu.js +3 -0
  60. package/dist/components/ui/dropdown-menu.js.map +1 -1
  61. package/dist/components/ui/input.d.ts +1 -1
  62. package/dist/components/ui/input.d.ts.map +1 -1
  63. package/dist/components/ui/input.js +3 -0
  64. package/dist/components/ui/input.js.map +1 -1
  65. package/dist/components/ui/label.d.ts.map +1 -1
  66. package/dist/components/ui/label.js +3 -0
  67. package/dist/components/ui/label.js.map +1 -1
  68. package/dist/components/ui/readonly-banner.d.ts.map +1 -1
  69. package/dist/components/ui/readonly-banner.js +3 -0
  70. package/dist/components/ui/readonly-banner.js.map +1 -1
  71. package/dist/components/ui/resizable.d.ts +1 -1
  72. package/dist/components/ui/resizable.d.ts.map +1 -1
  73. package/dist/components/ui/resizable.js +3 -0
  74. package/dist/components/ui/resizable.js.map +1 -1
  75. package/dist/components/ui/scroll-area.d.ts.map +1 -1
  76. package/dist/components/ui/scroll-area.js +3 -0
  77. package/dist/components/ui/scroll-area.js.map +1 -1
  78. package/dist/components/ui/select.d.ts.map +1 -1
  79. package/dist/components/ui/select.js +3 -0
  80. package/dist/components/ui/select.js.map +1 -1
  81. package/dist/components/ui/separator.d.ts.map +1 -1
  82. package/dist/components/ui/separator.js +3 -0
  83. package/dist/components/ui/separator.js.map +1 -1
  84. package/dist/components/ui/switch.d.ts.map +1 -1
  85. package/dist/components/ui/switch.js +3 -0
  86. package/dist/components/ui/switch.js.map +1 -1
  87. package/dist/components/ui/textarea.d.ts +1 -1
  88. package/dist/components/ui/textarea.d.ts.map +1 -1
  89. package/dist/components/ui/textarea.js +3 -0
  90. package/dist/components/ui/textarea.js.map +1 -1
  91. package/dist/components/ui/toast.d.ts.map +1 -1
  92. package/dist/components/ui/toast.js +3 -0
  93. package/dist/components/ui/toast.js.map +1 -1
  94. package/dist/components/ui/toaster.d.ts.map +1 -1
  95. package/dist/components/ui/toaster.js +3 -0
  96. package/dist/components/ui/toaster.js.map +1 -1
  97. package/dist/components/ui/toggle-group.d.ts.map +1 -1
  98. package/dist/components/ui/toggle-group.js +3 -0
  99. package/dist/components/ui/toggle-group.js.map +1 -1
  100. package/dist/components/ui/toggle.d.ts.map +1 -1
  101. package/dist/components/ui/toggle.js +3 -0
  102. package/dist/components/ui/toggle.js.map +1 -1
  103. package/dist/components/ui/tooltip.d.ts.map +1 -1
  104. package/dist/components/ui/tooltip.js +3 -0
  105. package/dist/components/ui/tooltip.js.map +1 -1
  106. package/dist/dialogs/NodePickerDialog.d.ts.map +1 -1
  107. package/dist/dialogs/NodePickerDialog.js +3 -0
  108. package/dist/dialogs/NodePickerDialog.js.map +1 -1
  109. package/dist/dialogs/ValidationDialog.d.ts.map +1 -1
  110. package/dist/dialogs/ValidationDialog.js +3 -0
  111. package/dist/dialogs/ValidationDialog.js.map +1 -1
  112. package/dist/graph/BaseNode.d.ts.map +1 -1
  113. package/dist/graph/BaseNode.js +21 -16
  114. package/dist/graph/BaseNode.js.map +1 -1
  115. package/dist/graph/CustomEdge.d.ts.map +1 -1
  116. package/dist/graph/CustomEdge.js +4 -1
  117. package/dist/graph/CustomEdge.js.map +1 -1
  118. package/dist/graph/CustomNode.d.ts.map +1 -1
  119. package/dist/graph/CustomNode.js +3 -0
  120. package/dist/graph/CustomNode.js.map +1 -1
  121. package/dist/graph/FunctionCallNode.d.ts.map +1 -1
  122. package/dist/graph/FunctionCallNode.js.map +1 -1
  123. package/dist/graph/PortHandle.d.ts.map +1 -1
  124. package/dist/graph/PortHandle.js.map +1 -1
  125. package/dist/graph/reactFlowRegistry.d.ts.map +1 -1
  126. package/dist/graph/reactFlowRegistry.js +3 -0
  127. package/dist/graph/reactFlowRegistry.js.map +1 -1
  128. package/dist/hooks/use-toast.d.ts.map +1 -1
  129. package/dist/hooks/use-toast.js +3 -0
  130. package/dist/hooks/use-toast.js.map +1 -1
  131. package/dist/hooks/useAvailableVariables.d.ts.map +1 -1
  132. package/dist/hooks/useAvailableVariables.js +3 -0
  133. package/dist/hooks/useAvailableVariables.js.map +1 -1
  134. package/dist/hooks/useCanvasHistory.d.ts.map +1 -1
  135. package/dist/hooks/useCanvasHistory.js +3 -0
  136. package/dist/hooks/useCanvasHistory.js.map +1 -1
  137. package/dist/hooks/useCanvasTabs.d.ts.map +1 -1
  138. package/dist/hooks/useCanvasTabs.js +3 -0
  139. package/dist/hooks/useCanvasTabs.js.map +1 -1
  140. package/dist/hooks/useFunctionDiagnosticsSync.d.ts.map +1 -1
  141. package/dist/hooks/useFunctionDiagnosticsSync.js +3 -0
  142. package/dist/hooks/useFunctionDiagnosticsSync.js.map +1 -1
  143. package/dist/hooks/useFunctionRegistry.d.ts.map +1 -1
  144. package/dist/hooks/useFunctionRegistry.js +3 -0
  145. package/dist/hooks/useFunctionRegistry.js.map +1 -1
  146. package/dist/hooks/useFunctions.d.ts.map +1 -1
  147. package/dist/hooks/useFunctions.js +3 -0
  148. package/dist/hooks/useFunctions.js.map +1 -1
  149. package/dist/hooks/useGraph.d.ts.map +1 -1
  150. package/dist/hooks/useGraph.js +3 -0
  151. package/dist/hooks/useGraph.js.map +1 -1
  152. package/dist/hooks/useNodeDefinitions.d.ts.map +1 -1
  153. package/dist/hooks/useNodeDefinitions.js +3 -0
  154. package/dist/hooks/useNodeDefinitions.js.map +1 -1
  155. package/dist/hooks/useParamErrors.d.ts.map +1 -1
  156. package/dist/hooks/useParamErrors.js +3 -0
  157. package/dist/hooks/useParamErrors.js.map +1 -1
  158. package/dist/hooks/useResolvedTheme.d.ts.map +1 -1
  159. package/dist/hooks/useResolvedTheme.js +3 -0
  160. package/dist/hooks/useResolvedTheme.js.map +1 -1
  161. package/dist/hooks/useResourceDiagnosticsSync.d.ts.map +1 -1
  162. package/dist/hooks/useResourceDiagnosticsSync.js +3 -0
  163. package/dist/hooks/useResourceDiagnosticsSync.js.map +1 -1
  164. package/dist/hooks/useSuppressThemeTransition.d.ts.map +1 -1
  165. package/dist/hooks/useSuppressThemeTransition.js +3 -0
  166. package/dist/hooks/useSuppressThemeTransition.js.map +1 -1
  167. package/dist/hooks/useWorkflowSerialization.d.ts.map +1 -1
  168. package/dist/hooks/useWorkflowSerialization.js +3 -0
  169. package/dist/hooks/useWorkflowSerialization.js.map +1 -1
  170. package/dist/i18n/index.d.ts.map +1 -1
  171. package/dist/i18n/index.js +3 -0
  172. package/dist/i18n/index.js.map +1 -1
  173. package/dist/index.d.ts +1 -1
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +3 -0
  176. package/dist/index.js.map +1 -1
  177. package/dist/inputs/ExpressionInput.d.ts.map +1 -1
  178. package/dist/inputs/ExpressionInput.js +3 -0
  179. package/dist/inputs/ExpressionInput.js.map +1 -1
  180. package/dist/inputs/ParameterEditor.d.ts.map +1 -1
  181. package/dist/inputs/ParameterEditor.js +3 -0
  182. package/dist/inputs/ParameterEditor.js.map +1 -1
  183. package/dist/inputs/PortSection.d.ts.map +1 -1
  184. package/dist/inputs/PortSection.js.map +1 -1
  185. package/dist/mode.d.ts +11 -0
  186. package/dist/mode.d.ts.map +1 -0
  187. package/dist/mode.js +8 -0
  188. package/dist/mode.js.map +1 -0
  189. package/dist/panels/BuilderSidebar.d.ts.map +1 -1
  190. package/dist/panels/BuilderSidebar.js +3 -0
  191. package/dist/panels/BuilderSidebar.js.map +1 -1
  192. package/dist/panels/ChannelConfigPanel.d.ts.map +1 -1
  193. package/dist/panels/ChannelConfigPanel.js +3 -0
  194. package/dist/panels/ChannelConfigPanel.js.map +1 -1
  195. package/dist/panels/ChannelsPanel.d.ts.map +1 -1
  196. package/dist/panels/ChannelsPanel.js +3 -0
  197. package/dist/panels/ChannelsPanel.js.map +1 -1
  198. package/dist/panels/DebugConsolePanel.d.ts.map +1 -1
  199. package/dist/panels/DebugConsolePanel.js +3 -0
  200. package/dist/panels/DebugConsolePanel.js.map +1 -1
  201. package/dist/panels/DebugContextPanel.d.ts.map +1 -1
  202. package/dist/panels/DebugContextPanel.js +3 -0
  203. package/dist/panels/DebugContextPanel.js.map +1 -1
  204. package/dist/panels/DebugExternalIOPanel.d.ts.map +1 -1
  205. package/dist/panels/DebugExternalIOPanel.js +3 -0
  206. package/dist/panels/DebugExternalIOPanel.js.map +1 -1
  207. package/dist/panels/DiagnosticsPanel.d.ts.map +1 -1
  208. package/dist/panels/DiagnosticsPanel.js +3 -0
  209. package/dist/panels/DiagnosticsPanel.js.map +1 -1
  210. package/dist/panels/EdgeConfigPanel.d.ts.map +1 -1
  211. package/dist/panels/EdgeConfigPanel.js +4 -1
  212. package/dist/panels/EdgeConfigPanel.js.map +1 -1
  213. package/dist/panels/FunctionConfigPanel.d.ts.map +1 -1
  214. package/dist/panels/FunctionConfigPanel.js +4 -1
  215. package/dist/panels/FunctionConfigPanel.js.map +1 -1
  216. package/dist/panels/FunctionListPanel.d.ts.map +1 -1
  217. package/dist/panels/FunctionListPanel.js +3 -0
  218. package/dist/panels/FunctionListPanel.js.map +1 -1
  219. package/dist/panels/MemoryConfigPanel.d.ts.map +1 -1
  220. package/dist/panels/MemoryConfigPanel.js +3 -0
  221. package/dist/panels/MemoryConfigPanel.js.map +1 -1
  222. package/dist/panels/MemoryPanel.d.ts.map +1 -1
  223. package/dist/panels/MemoryPanel.js +3 -0
  224. package/dist/panels/MemoryPanel.js.map +1 -1
  225. package/dist/panels/ModelConfigPanel.d.ts.map +1 -1
  226. package/dist/panels/ModelConfigPanel.js +3 -0
  227. package/dist/panels/ModelConfigPanel.js.map +1 -1
  228. package/dist/panels/ModelsPanel.d.ts.map +1 -1
  229. package/dist/panels/ModelsPanel.js +3 -0
  230. package/dist/panels/ModelsPanel.js.map +1 -1
  231. package/dist/panels/NodeConfigPanel.d.ts.map +1 -1
  232. package/dist/panels/NodeConfigPanel.js +4 -1
  233. package/dist/panels/NodeConfigPanel.js.map +1 -1
  234. package/dist/panels/NodeLibrary.d.ts.map +1 -1
  235. package/dist/panels/NodeLibrary.js +4 -1
  236. package/dist/panels/NodeLibrary.js.map +1 -1
  237. package/dist/panels/ResourceConfigPanel.d.ts.map +1 -1
  238. package/dist/panels/ResourceConfigPanel.js +4 -1
  239. package/dist/panels/ResourceConfigPanel.js.map +1 -1
  240. package/dist/panels/ResourceListPanel.d.ts.map +1 -1
  241. package/dist/panels/ResourceListPanel.js +4 -1
  242. package/dist/panels/ResourceListPanel.js.map +1 -1
  243. package/dist/panels/VariableConfigPanel.d.ts.map +1 -1
  244. package/dist/panels/VariableConfigPanel.js +4 -1
  245. package/dist/panels/VariableConfigPanel.js.map +1 -1
  246. package/dist/panels/VariablesPanel.d.ts.map +1 -1
  247. package/dist/panels/VariablesPanel.js +4 -1
  248. package/dist/panels/VariablesPanel.js.map +1 -1
  249. package/dist/stores/canvasStore.d.ts.map +1 -1
  250. package/dist/stores/canvasStore.js +3 -0
  251. package/dist/stores/canvasStore.js.map +1 -1
  252. package/dist/stores/debugStore.d.ts.map +1 -1
  253. package/dist/stores/debugStore.js +3 -0
  254. package/dist/stores/debugStore.js.map +1 -1
  255. package/dist/stores/diagnosticsStore.d.ts.map +1 -1
  256. package/dist/stores/diagnosticsStore.js +3 -0
  257. package/dist/stores/diagnosticsStore.js.map +1 -1
  258. package/dist/stores/editorStore.d.ts +1 -1
  259. package/dist/stores/editorStore.d.ts.map +1 -1
  260. package/dist/stores/editorStore.js +3 -0
  261. package/dist/stores/editorStore.js.map +1 -1
  262. package/dist/utils/categoryConstants.d.ts.map +1 -1
  263. package/dist/utils/categoryConstants.js +3 -0
  264. package/dist/utils/categoryConstants.js.map +1 -1
  265. package/dist/utils/channelOperations.d.ts.map +1 -1
  266. package/dist/utils/channelOperations.js +3 -0
  267. package/dist/utils/channelOperations.js.map +1 -1
  268. package/dist/utils/connectionRules.d.ts +3 -1
  269. package/dist/utils/connectionRules.d.ts.map +1 -1
  270. package/dist/utils/connectionRules.js +11 -16
  271. package/dist/utils/connectionRules.js.map +1 -1
  272. package/dist/utils/functionOperations.d.ts.map +1 -1
  273. package/dist/utils/functionOperations.js +3 -0
  274. package/dist/utils/functionOperations.js.map +1 -1
  275. package/dist/utils/graphOperations.d.ts.map +1 -1
  276. package/dist/utils/graphOperations.js +3 -0
  277. package/dist/utils/graphOperations.js.map +1 -1
  278. package/dist/utils/history.d.ts.map +1 -1
  279. package/dist/utils/history.js +4 -1
  280. package/dist/utils/history.js.map +1 -1
  281. package/dist/utils/memoryOperations.d.ts.map +1 -1
  282. package/dist/utils/memoryOperations.js +3 -0
  283. package/dist/utils/memoryOperations.js.map +1 -1
  284. package/dist/utils/migrateFunctionNodes.d.ts.map +1 -1
  285. package/dist/utils/migrateFunctionNodes.js +3 -0
  286. package/dist/utils/migrateFunctionNodes.js.map +1 -1
  287. package/dist/utils/modelOperations.d.ts.map +1 -1
  288. package/dist/utils/modelOperations.js +3 -0
  289. package/dist/utils/modelOperations.js.map +1 -1
  290. package/dist/utils/paramDisplay.d.ts.map +1 -1
  291. package/dist/utils/paramDisplay.js +3 -0
  292. package/dist/utils/paramDisplay.js.map +1 -1
  293. package/dist/utils/resourceHelpers.d.ts.map +1 -1
  294. package/dist/utils/resourceHelpers.js +3 -0
  295. package/dist/utils/resourceHelpers.js.map +1 -1
  296. package/dist/utils/translation.d.ts.map +1 -1
  297. package/dist/utils/translation.js +3 -0
  298. package/dist/utils/translation.js.map +1 -1
  299. package/dist/utils/variableOperations.d.ts.map +1 -1
  300. package/dist/utils/variableOperations.js +3 -0
  301. package/dist/utils/variableOperations.js.map +1 -1
  302. package/package.json +2 -2
  303. package/src/BuilderLayout.tsx +338 -345
  304. package/src/Canvas.tsx +270 -261
  305. package/src/CanvasEditor.tsx +146 -142
  306. package/src/CanvasTabsToolbar.tsx +180 -176
  307. package/src/RightConfigPanel.tsx +270 -266
  308. package/src/WorkflowBuilder.tsx +423 -408
  309. package/src/cn.ts +10 -6
  310. package/src/components/ui/add-button.tsx +43 -39
  311. package/src/components/ui/alert-dialog.tsx +145 -141
  312. package/src/components/ui/alert.tsx +63 -59
  313. package/src/components/ui/badge.tsx +40 -36
  314. package/src/components/ui/button.tsx +89 -85
  315. package/src/components/ui/card.tsx +83 -79
  316. package/src/components/ui/checkbox.tsx +32 -28
  317. package/src/components/ui/collapsible.tsx +13 -9
  318. package/src/components/ui/command.tsx +157 -153
  319. package/src/components/ui/delete-button.tsx +27 -23
  320. package/src/components/ui/dialog.tsx +129 -125
  321. package/src/components/ui/dropdown-menu.tsx +202 -198
  322. package/src/components/ui/input.tsx +59 -55
  323. package/src/components/ui/label.tsx +28 -24
  324. package/src/components/ui/readonly-banner.tsx +19 -15
  325. package/src/components/ui/resizable.tsx +47 -43
  326. package/src/components/ui/scroll-area.tsx +106 -102
  327. package/src/components/ui/select.tsx +164 -160
  328. package/src/components/ui/separator.tsx +33 -29
  329. package/src/components/ui/switch.tsx +31 -27
  330. package/src/components/ui/textarea.tsx +55 -51
  331. package/src/components/ui/toast.tsx +131 -127
  332. package/src/components/ui/toaster.tsx +37 -33
  333. package/src/components/ui/toggle-group.tsx +63 -59
  334. package/src/components/ui/toggle.tsx +47 -43
  335. package/src/components/ui/tooltip.tsx +36 -32
  336. package/src/dialogs/NodePickerDialog.tsx +88 -84
  337. package/src/dialogs/ValidationDialog.tsx +188 -184
  338. package/src/graph/BaseNode.tsx +563 -557
  339. package/src/graph/CustomEdge.tsx +189 -185
  340. package/src/graph/CustomNode.tsx +20 -16
  341. package/src/graph/FunctionCallNode.tsx +34 -30
  342. package/src/graph/PortHandle.tsx +193 -189
  343. package/src/graph/reactFlowRegistry.ts +30 -26
  344. package/src/hooks/use-toast.ts +129 -125
  345. package/src/hooks/useAvailableVariables.ts +24 -20
  346. package/src/hooks/useCanvasHistory.ts +26 -22
  347. package/src/hooks/useCanvasTabs.ts +172 -168
  348. package/src/hooks/useFunctionDiagnosticsSync.ts +44 -40
  349. package/src/hooks/useFunctionRegistry.ts +30 -26
  350. package/src/hooks/useFunctions.ts +48 -44
  351. package/src/hooks/useGraph.ts +165 -161
  352. package/src/hooks/useNodeDefinitions.ts +86 -82
  353. package/src/hooks/useParamErrors.ts +30 -26
  354. package/src/hooks/useResolvedTheme.ts +34 -30
  355. package/src/hooks/useResourceDiagnosticsSync.ts +62 -58
  356. package/src/hooks/useSuppressThemeTransition.ts +83 -79
  357. package/src/hooks/useWorkflowSerialization.ts +145 -141
  358. package/src/i18n/index.ts +57 -53
  359. package/src/i18n/locales/de.json +501 -501
  360. package/src/i18n/locales/en.json +557 -557
  361. package/src/index.ts +31 -27
  362. package/src/inputs/ExpressionInput.tsx +301 -297
  363. package/src/inputs/ParameterEditor.tsx +519 -515
  364. package/src/inputs/PortSection.tsx +148 -144
  365. package/src/mode.ts +15 -0
  366. package/src/panels/BuilderSidebar.tsx +305 -301
  367. package/src/panels/ChannelConfigPanel.tsx +53 -49
  368. package/src/panels/ChannelsPanel.tsx +32 -28
  369. package/src/panels/DebugConsolePanel.tsx +77 -73
  370. package/src/panels/DebugContextPanel.tsx +81 -77
  371. package/src/panels/DebugExternalIOPanel.tsx +184 -180
  372. package/src/panels/DiagnosticsPanel.tsx +174 -170
  373. package/src/panels/EdgeConfigPanel.tsx +108 -104
  374. package/src/panels/FunctionConfigPanel.tsx +183 -179
  375. package/src/panels/FunctionListPanel.tsx +49 -45
  376. package/src/panels/MemoryConfigPanel.tsx +59 -55
  377. package/src/panels/MemoryPanel.tsx +44 -40
  378. package/src/panels/ModelConfigPanel.tsx +45 -41
  379. package/src/panels/ModelsPanel.tsx +40 -36
  380. package/src/panels/NodeConfigPanel.tsx +634 -630
  381. package/src/panels/NodeLibrary.tsx +292 -288
  382. package/src/panels/ResourceConfigPanel.tsx +136 -132
  383. package/src/panels/ResourceListPanel.tsx +117 -113
  384. package/src/panels/VariableConfigPanel.tsx +165 -161
  385. package/src/panels/VariablesPanel.tsx +149 -145
  386. package/src/stores/canvasStore.test.ts +48 -44
  387. package/src/stores/canvasStore.ts +249 -245
  388. package/src/stores/debugStore.ts +78 -74
  389. package/src/stores/diagnosticsStore.ts +134 -130
  390. package/src/stores/editorStore.ts +194 -190
  391. package/src/styles/index.css +526 -526
  392. package/src/utils/categoryConstants.ts +30 -26
  393. package/src/utils/channelOperations.ts +90 -86
  394. package/src/utils/connectionRules.ts +140 -137
  395. package/src/utils/functionOperations.ts +183 -179
  396. package/src/utils/graphOperations.ts +554 -550
  397. package/src/utils/history.test.ts +92 -0
  398. package/src/utils/history.ts +211 -207
  399. package/src/utils/memoryOperations.ts +61 -57
  400. package/src/utils/migrateFunctionNodes.ts +111 -107
  401. package/src/utils/modelOperations.ts +59 -55
  402. package/src/utils/paramDisplay.ts +75 -71
  403. package/src/utils/resourceHelpers.ts +36 -32
  404. package/src/utils/translation.ts +32 -28
  405. package/src/utils/variableOperations.ts +79 -75
  406. package/tailwind-preset.ts +170 -166
  407. package/dist/lib/utils.d.ts +0 -3
  408. package/dist/lib/utils.d.ts.map +0 -1
  409. package/dist/lib/utils.js +0 -6
  410. package/dist/lib/utils.js.map +0 -1
  411. package/dist/toolbars/CanvasTabsToolbar.d.ts +0 -11
  412. package/dist/toolbars/CanvasTabsToolbar.d.ts.map +0 -1
  413. package/dist/toolbars/CanvasTabsToolbar.js +0 -101
  414. package/dist/toolbars/CanvasTabsToolbar.js.map +0 -1
@@ -1,297 +1,301 @@
1
- import { useState, useRef, useEffect, useCallback, useMemo } from "react";
2
- import { useTranslation } from "react-i18next";
3
- import { NodeRegistry } from "@foresthubai/workflow-core/node";
4
- import type { DataType, Expression, Reference } from "@foresthubai/workflow-core";
5
- import { ResolvedExpr, resolveExpression } from "@foresthubai/workflow-core/expression";
6
- import { varKey, type Variable } from "@foresthubai/workflow-core/variable";
7
- import { useEditorStore } from "../stores/editorStore";
8
- import { getOrCreateCanvasStore } from "../stores/canvasStore";
9
- import { cn } from "../cn";
10
-
11
- interface ExpressionInputProps {
12
- value: Expression;
13
- onChange: (value: Expression) => void;
14
- expressionType: DataType;
15
- availableVariables: Record<string, Variable>;
16
- placeholder?: string;
17
- }
18
-
19
- const ExpressionInput = ({
20
- value: apiValue,
21
- onChange,
22
- expressionType,
23
- availableVariables,
24
- placeholder,
25
- }: ExpressionInputProps) => {
26
- const { t } = useTranslation();
27
- const resolvedPlaceholder = placeholder ?? "${var1}";
28
- // Convert to resolved expression for internal use
29
- const value = useMemo((): ResolvedExpr => {
30
- return resolveExpression(apiValue, availableVariables);
31
- }, [apiValue, availableVariables]);
32
-
33
- // Convert Record to array for iteration
34
- const variableList = useMemo(() => Object.values(availableVariables), [availableVariables]);
35
-
36
- const [inputValue, setInputValue] = useState(value?.expression ?? "");
37
- const [showDropdown, setShowDropdown] = useState(false);
38
- const [dropdownPosition, setDropdownPosition] = useState(0);
39
- const [filterText, setFilterText] = useState("");
40
- const [selectedIndex, setSelectedIndex] = useState(0);
41
- const inputRef = useRef<HTMLInputElement>(null);
42
- const containerRef = useRef<HTMLDivElement>(null);
43
- const isEditingRef = useRef(false);
44
-
45
- // Filter variables based on what user typed after $
46
- const filteredVariables = useMemo(
47
- () => variableList.filter((v) => v.name.toLowerCase().includes(filterText.toLowerCase())),
48
- [variableList, filterText],
49
- );
50
-
51
- // nodeId user-facing display name for the active canvas, used to label
52
- // node-output variables in the dropdown. Falls back to the node definition's
53
- // human label, never the schema-internal `type`. Resolved lazily when the
54
- // dropdown opens (labels can't change while it's open) so typing in an
55
- // expression doesn't subscribe this input to every node mutation.
56
- const activeCanvasId = useEditorStore((s) => s.activeCanvasId);
57
- const nodeLabelById = useMemo(() => {
58
- const map: Record<string, string> = {};
59
- if (!showDropdown) return map;
60
- for (const n of getOrCreateCanvasStore(activeCanvasId).getState().nodes) {
61
- map[n.id] = n.data.label ?? NodeRegistry.getByType(n.data.type)?.label ?? n.data.type;
62
- }
63
- return map;
64
- }, [showDropdown, activeCanvasId]);
65
-
66
- // Find a variable by name from variableList
67
- const findVariableByName = useCallback(
68
- (name: string): Variable | undefined => {
69
- return variableList.find((v) => v.name === name);
70
- },
71
- [variableList],
72
- );
73
-
74
- // Parse display expression to API expression format
75
- // Converts ${name} syntax to ${} placeholders with references array
76
- const parseToApiExpression = useCallback(
77
- (displayExpr: string): Expression => {
78
- const references: Reference[] = [];
79
- const regex = /\$\{([^}]+)\}/g;
80
- let match;
81
- let expressionWithPlaceholders = displayExpr;
82
-
83
- // First pass: collect all variable names and their positions
84
- const matches: { name: string; start: number; end: number }[] = [];
85
- while ((match = regex.exec(displayExpr)) !== null) {
86
- matches.push({ name: match[1] ?? "", start: match.index, end: match.index + match[0].length });
87
- }
88
-
89
- // Build expression with empty placeholders and collect references
90
- let offset = 0;
91
- for (const m of matches) {
92
- const availableVar = findVariableByName(m.name);
93
- if (availableVar) {
94
- if (availableVar.kind === "node") {
95
- references.push({ srcId: availableVar.nodeId, varId: availableVar.outputId });
96
- } else if (availableVar.kind === "fnarg") {
97
- references.push({ srcId: "fnarg", varId: availableVar.uid });
98
- } else {
99
- references.push({ srcId: "declared", varId: availableVar.uid });
100
- }
101
- } else {
102
- // Variable not found - use invalid reference
103
- references.push({ srcId: "", varId: "" });
104
- }
105
- // Replace ${name} with ${}
106
- const before = expressionWithPlaceholders.slice(0, m.start + offset);
107
- const after = expressionWithPlaceholders.slice(m.end + offset);
108
- expressionWithPlaceholders = before + "${}" + after;
109
- // Adjust offset: original was ${name} (3 + name.length), new is ${} (3)
110
- offset -= m.name.length;
111
- }
112
-
113
- return { expression: expressionWithPlaceholders, references, dataType: expressionType };
114
- },
115
- [findVariableByName, expressionType],
116
- );
117
-
118
- // Resolve expression display by filling ${} placeholders with variable names
119
- const resolveExpressionDisplay = useCallback((expr: ResolvedExpr | undefined): string => {
120
- if (!expr) return "";
121
-
122
- let result = expr.expression;
123
- let varIndex = 0;
124
-
125
- // Replace each ${} with the corresponding variable's name
126
- result = result.replace(/\$\{\}/g, () => {
127
- if (varIndex < expr.variables.length) {
128
- const variable = expr.variables[varIndex];
129
- varIndex++;
130
- const name = variable?.name ?? "unknown";
131
- return `\${${name}}`;
132
- }
133
- return "${}"; // No variable for this placeholder
134
- });
135
-
136
- return result;
137
- }, []);
138
-
139
- // Only sync from external value when NOT actively editing
140
- useEffect(() => {
141
- if (isEditingRef.current) return;
142
- const resolved = resolveExpressionDisplay(value);
143
- if (resolved !== inputValue) {
144
- setInputValue(resolved);
145
- }
146
- }, [value, resolveExpressionDisplay, inputValue]);
147
-
148
- const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
149
- isEditingRef.current = true;
150
- const newValue = e.target.value;
151
- const cursorPos = e.target.selectionStart ?? 0;
152
- setInputValue(newValue);
153
-
154
- // Check if user just typed $ or is mid-variable reference
155
- const textBeforeCursor = newValue.slice(0, cursorPos);
156
- const dollarIndex = textBeforeCursor.lastIndexOf("$");
157
-
158
- if (dollarIndex !== -1) {
159
- const textAfterDollar = textBeforeCursor.slice(dollarIndex + 1);
160
- // Skip if this $ is part of an already-closed ${...} reference
161
- const isClosedRef = textAfterDollar.startsWith("{") && textAfterDollar.includes("}");
162
- if (
163
- !isClosedRef &&
164
- (textAfterDollar === "" || textAfterDollar.startsWith("{") || /^[a-zA-Z_]/.test(textAfterDollar))
165
- ) {
166
- const filter = textAfterDollar.replace(/^\{?/, "");
167
- setFilterText(filter);
168
- setDropdownPosition(dollarIndex);
169
- setShowDropdown(true);
170
- setSelectedIndex(0);
171
- return;
172
- }
173
- }
174
-
175
- setShowDropdown(false);
176
- onChange(parseToApiExpression(newValue));
177
- };
178
-
179
- // Handle blur - mark editing as finished and commit final value
180
- const handleBlur = () => {
181
- isEditingRef.current = false;
182
- onChange(parseToApiExpression(inputValue));
183
- };
184
-
185
- const selectVariable = (variable: Variable) => {
186
- const beforeDollar = inputValue.slice(0, dropdownPosition);
187
- const afterCursor = inputValue.slice(inputRef.current?.selectionStart ?? inputValue.length);
188
-
189
- // Remove any partial variable name after $
190
- const cleanAfter = afterCursor.replace(/^[{]?[a-zA-Z_]*[}]?/, "");
191
-
192
- const newValue = `${beforeDollar}\${${variable.name}}${cleanAfter}`;
193
- setInputValue(newValue);
194
- setShowDropdown(false);
195
- onChange(parseToApiExpression(newValue));
196
-
197
- // Focus back to input
198
- setTimeout(() => {
199
- inputRef.current?.focus();
200
- const newCursorPos = beforeDollar.length + variable.name.length + 3;
201
- inputRef.current?.setSelectionRange(newCursorPos, newCursorPos);
202
- }, 0);
203
- };
204
-
205
- // Handle keyboard navigation in dropdown
206
- const handleKeyDown = (e: React.KeyboardEvent) => {
207
- if (!showDropdown) return;
208
-
209
- switch (e.key) {
210
- case "ArrowDown":
211
- e.preventDefault();
212
- setSelectedIndex((prev) => Math.min(prev + 1, filteredVariables.length - 1));
213
- break;
214
- case "ArrowUp":
215
- e.preventDefault();
216
- setSelectedIndex((prev) => Math.max(prev - 1, 0));
217
- break;
218
- case "Enter":
219
- case "Tab":
220
- e.preventDefault();
221
- if (filteredVariables[selectedIndex]) {
222
- selectVariable(filteredVariables[selectedIndex]);
223
- }
224
- break;
225
- case "Escape":
226
- setShowDropdown(false);
227
- break;
228
- }
229
- };
230
-
231
- // Close dropdown when clicking outside
232
- useEffect(() => {
233
- const handleClickOutside = (e: MouseEvent) => {
234
- if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
235
- setShowDropdown(false);
236
- }
237
- };
238
- document.addEventListener("mousedown", handleClickOutside);
239
- return () => document.removeEventListener("mousedown", handleClickOutside);
240
- }, []);
241
-
242
- return (
243
- <div ref={containerRef}>
244
- <div className="relative">
245
- <input
246
- ref={inputRef}
247
- type="text"
248
- value={inputValue}
249
- onChange={handleInputChange}
250
- onKeyDown={handleKeyDown}
251
- onBlur={handleBlur}
252
- placeholder={resolvedPlaceholder}
253
- className="w-full h-9 px-3 py-1 text-sm bg-field border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring font-mono"
254
- />
255
-
256
- {/* Variable dropdown */}
257
- {showDropdown && filteredVariables.length > 0 && (
258
- <div className="absolute z-50 mt-1 w-full bg-popover border border-border rounded-md shadow-lg max-h-48 overflow-auto">
259
- {filteredVariables.map((variable, index) => (
260
- <button
261
- key={varKey(variable)}
262
- onClick={() => selectVariable(variable)}
263
- className={cn(
264
- "w-full px-3 py-2 text-left text-sm flex items-center justify-between hover:bg-accent",
265
- index === selectedIndex && "bg-accent",
266
- )}
267
- >
268
- <div className="flex flex-col">
269
- <span className="font-mono">
270
- ${"{"}
271
- {variable.name}
272
- {"}"}
273
- </span>
274
- <span className="text-xs text-muted-foreground truncate max-w-[150px]">
275
- {variable.kind === "node"
276
- ? t("fromNode", { node: nodeLabelById[variable.nodeId] ?? variable.nodeId })
277
- : variable.kind === "declared"
278
- ? t("globalVariable")
279
- : t("fnarg")}
280
- </span>
281
- </div>
282
- <span className="text-xs text-muted-foreground">{variable.dataType}</span>
283
- </button>
284
- ))}
285
- </div>
286
- )}
287
-
288
- {/* Type indicator */}
289
- <div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-2 pointer-events-none">
290
- <span className="text-xs text-muted-foreground">→ {expressionType}</span>
291
- </div>
292
- </div>
293
- </div>
294
- );
295
- };
296
-
297
- export default ExpressionInput;
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026 ForestHub. All rights reserved.
3
+ // For commercial licensing, contact root@foresthub.ai
4
+
5
+ import { useState, useRef, useEffect, useCallback, useMemo } from "react";
6
+ import { useTranslation } from "react-i18next";
7
+ import { NodeRegistry } from "@foresthubai/workflow-core/node";
8
+ import type { DataType, Expression, Reference } from "@foresthubai/workflow-core";
9
+ import { ResolvedExpr, resolveExpression } from "@foresthubai/workflow-core/expression";
10
+ import { varKey, type Variable } from "@foresthubai/workflow-core/variable";
11
+ import { useEditorStore } from "../stores/editorStore";
12
+ import { getOrCreateCanvasStore } from "../stores/canvasStore";
13
+ import { cn } from "../cn";
14
+
15
+ interface ExpressionInputProps {
16
+ value: Expression;
17
+ onChange: (value: Expression) => void;
18
+ expressionType: DataType;
19
+ availableVariables: Record<string, Variable>;
20
+ placeholder?: string;
21
+ }
22
+
23
+ const ExpressionInput = ({
24
+ value: apiValue,
25
+ onChange,
26
+ expressionType,
27
+ availableVariables,
28
+ placeholder,
29
+ }: ExpressionInputProps) => {
30
+ const { t } = useTranslation();
31
+ const resolvedPlaceholder = placeholder ?? "${var1}";
32
+ // Convert to resolved expression for internal use
33
+ const value = useMemo((): ResolvedExpr => {
34
+ return resolveExpression(apiValue, availableVariables);
35
+ }, [apiValue, availableVariables]);
36
+
37
+ // Convert Record to array for iteration
38
+ const variableList = useMemo(() => Object.values(availableVariables), [availableVariables]);
39
+
40
+ const [inputValue, setInputValue] = useState(value?.expression ?? "");
41
+ const [showDropdown, setShowDropdown] = useState(false);
42
+ const [dropdownPosition, setDropdownPosition] = useState(0);
43
+ const [filterText, setFilterText] = useState("");
44
+ const [selectedIndex, setSelectedIndex] = useState(0);
45
+ const inputRef = useRef<HTMLInputElement>(null);
46
+ const containerRef = useRef<HTMLDivElement>(null);
47
+ const isEditingRef = useRef(false);
48
+
49
+ // Filter variables based on what user typed after $
50
+ const filteredVariables = useMemo(
51
+ () => variableList.filter((v) => v.name.toLowerCase().includes(filterText.toLowerCase())),
52
+ [variableList, filterText],
53
+ );
54
+
55
+ // nodeId user-facing display name for the active canvas, used to label
56
+ // node-output variables in the dropdown. Falls back to the node definition's
57
+ // human label, never the schema-internal `type`. Resolved lazily when the
58
+ // dropdown opens (labels can't change while it's open) so typing in an
59
+ // expression doesn't subscribe this input to every node mutation.
60
+ const activeCanvasId = useEditorStore((s) => s.activeCanvasId);
61
+ const nodeLabelById = useMemo(() => {
62
+ const map: Record<string, string> = {};
63
+ if (!showDropdown) return map;
64
+ for (const n of getOrCreateCanvasStore(activeCanvasId).getState().nodes) {
65
+ map[n.id] = n.data.label ?? NodeRegistry.getByType(n.data.type)?.label ?? n.data.type;
66
+ }
67
+ return map;
68
+ }, [showDropdown, activeCanvasId]);
69
+
70
+ // Find a variable by name from variableList
71
+ const findVariableByName = useCallback(
72
+ (name: string): Variable | undefined => {
73
+ return variableList.find((v) => v.name === name);
74
+ },
75
+ [variableList],
76
+ );
77
+
78
+ // Parse display expression to API expression format
79
+ // Converts ${name} syntax to ${} placeholders with references array
80
+ const parseToApiExpression = useCallback(
81
+ (displayExpr: string): Expression => {
82
+ const references: Reference[] = [];
83
+ const regex = /\$\{([^}]+)\}/g;
84
+ let match;
85
+ let expressionWithPlaceholders = displayExpr;
86
+
87
+ // First pass: collect all variable names and their positions
88
+ const matches: { name: string; start: number; end: number }[] = [];
89
+ while ((match = regex.exec(displayExpr)) !== null) {
90
+ matches.push({ name: match[1] ?? "", start: match.index, end: match.index + match[0].length });
91
+ }
92
+
93
+ // Build expression with empty placeholders and collect references
94
+ let offset = 0;
95
+ for (const m of matches) {
96
+ const availableVar = findVariableByName(m.name);
97
+ if (availableVar) {
98
+ if (availableVar.kind === "node") {
99
+ references.push({ srcId: availableVar.nodeId, varId: availableVar.outputId });
100
+ } else if (availableVar.kind === "fnarg") {
101
+ references.push({ srcId: "fnarg", varId: availableVar.uid });
102
+ } else {
103
+ references.push({ srcId: "declared", varId: availableVar.uid });
104
+ }
105
+ } else {
106
+ // Variable not found - use invalid reference
107
+ references.push({ srcId: "", varId: "" });
108
+ }
109
+ // Replace ${name} with ${}
110
+ const before = expressionWithPlaceholders.slice(0, m.start + offset);
111
+ const after = expressionWithPlaceholders.slice(m.end + offset);
112
+ expressionWithPlaceholders = before + "${}" + after;
113
+ // Adjust offset: original was ${name} (3 + name.length), new is ${} (3)
114
+ offset -= m.name.length;
115
+ }
116
+
117
+ return { expression: expressionWithPlaceholders, references, dataType: expressionType };
118
+ },
119
+ [findVariableByName, expressionType],
120
+ );
121
+
122
+ // Resolve expression display by filling ${} placeholders with variable names
123
+ const resolveExpressionDisplay = useCallback((expr: ResolvedExpr | undefined): string => {
124
+ if (!expr) return "";
125
+
126
+ let result = expr.expression;
127
+ let varIndex = 0;
128
+
129
+ // Replace each ${} with the corresponding variable's name
130
+ result = result.replace(/\$\{\}/g, () => {
131
+ if (varIndex < expr.variables.length) {
132
+ const variable = expr.variables[varIndex];
133
+ varIndex++;
134
+ const name = variable?.name ?? "unknown";
135
+ return `\${${name}}`;
136
+ }
137
+ return "${}"; // No variable for this placeholder
138
+ });
139
+
140
+ return result;
141
+ }, []);
142
+
143
+ // Only sync from external value when NOT actively editing
144
+ useEffect(() => {
145
+ if (isEditingRef.current) return;
146
+ const resolved = resolveExpressionDisplay(value);
147
+ if (resolved !== inputValue) {
148
+ setInputValue(resolved);
149
+ }
150
+ }, [value, resolveExpressionDisplay, inputValue]);
151
+
152
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
153
+ isEditingRef.current = true;
154
+ const newValue = e.target.value;
155
+ const cursorPos = e.target.selectionStart ?? 0;
156
+ setInputValue(newValue);
157
+
158
+ // Check if user just typed $ or is mid-variable reference
159
+ const textBeforeCursor = newValue.slice(0, cursorPos);
160
+ const dollarIndex = textBeforeCursor.lastIndexOf("$");
161
+
162
+ if (dollarIndex !== -1) {
163
+ const textAfterDollar = textBeforeCursor.slice(dollarIndex + 1);
164
+ // Skip if this $ is part of an already-closed ${...} reference
165
+ const isClosedRef = textAfterDollar.startsWith("{") && textAfterDollar.includes("}");
166
+ if (
167
+ !isClosedRef &&
168
+ (textAfterDollar === "" || textAfterDollar.startsWith("{") || /^[a-zA-Z_]/.test(textAfterDollar))
169
+ ) {
170
+ const filter = textAfterDollar.replace(/^\{?/, "");
171
+ setFilterText(filter);
172
+ setDropdownPosition(dollarIndex);
173
+ setShowDropdown(true);
174
+ setSelectedIndex(0);
175
+ return;
176
+ }
177
+ }
178
+
179
+ setShowDropdown(false);
180
+ onChange(parseToApiExpression(newValue));
181
+ };
182
+
183
+ // Handle blur - mark editing as finished and commit final value
184
+ const handleBlur = () => {
185
+ isEditingRef.current = false;
186
+ onChange(parseToApiExpression(inputValue));
187
+ };
188
+
189
+ const selectVariable = (variable: Variable) => {
190
+ const beforeDollar = inputValue.slice(0, dropdownPosition);
191
+ const afterCursor = inputValue.slice(inputRef.current?.selectionStart ?? inputValue.length);
192
+
193
+ // Remove any partial variable name after $
194
+ const cleanAfter = afterCursor.replace(/^[{]?[a-zA-Z_]*[}]?/, "");
195
+
196
+ const newValue = `${beforeDollar}\${${variable.name}}${cleanAfter}`;
197
+ setInputValue(newValue);
198
+ setShowDropdown(false);
199
+ onChange(parseToApiExpression(newValue));
200
+
201
+ // Focus back to input
202
+ setTimeout(() => {
203
+ inputRef.current?.focus();
204
+ const newCursorPos = beforeDollar.length + variable.name.length + 3;
205
+ inputRef.current?.setSelectionRange(newCursorPos, newCursorPos);
206
+ }, 0);
207
+ };
208
+
209
+ // Handle keyboard navigation in dropdown
210
+ const handleKeyDown = (e: React.KeyboardEvent) => {
211
+ if (!showDropdown) return;
212
+
213
+ switch (e.key) {
214
+ case "ArrowDown":
215
+ e.preventDefault();
216
+ setSelectedIndex((prev) => Math.min(prev + 1, filteredVariables.length - 1));
217
+ break;
218
+ case "ArrowUp":
219
+ e.preventDefault();
220
+ setSelectedIndex((prev) => Math.max(prev - 1, 0));
221
+ break;
222
+ case "Enter":
223
+ case "Tab":
224
+ e.preventDefault();
225
+ if (filteredVariables[selectedIndex]) {
226
+ selectVariable(filteredVariables[selectedIndex]);
227
+ }
228
+ break;
229
+ case "Escape":
230
+ setShowDropdown(false);
231
+ break;
232
+ }
233
+ };
234
+
235
+ // Close dropdown when clicking outside
236
+ useEffect(() => {
237
+ const handleClickOutside = (e: MouseEvent) => {
238
+ if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
239
+ setShowDropdown(false);
240
+ }
241
+ };
242
+ document.addEventListener("mousedown", handleClickOutside);
243
+ return () => document.removeEventListener("mousedown", handleClickOutside);
244
+ }, []);
245
+
246
+ return (
247
+ <div ref={containerRef}>
248
+ <div className="relative">
249
+ <input
250
+ ref={inputRef}
251
+ type="text"
252
+ value={inputValue}
253
+ onChange={handleInputChange}
254
+ onKeyDown={handleKeyDown}
255
+ onBlur={handleBlur}
256
+ placeholder={resolvedPlaceholder}
257
+ className="w-full h-9 px-3 py-1 text-sm bg-field border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring font-mono"
258
+ />
259
+
260
+ {/* Variable dropdown */}
261
+ {showDropdown && filteredVariables.length > 0 && (
262
+ <div className="absolute z-50 mt-1 w-full bg-popover border border-border rounded-md shadow-lg max-h-48 overflow-auto">
263
+ {filteredVariables.map((variable, index) => (
264
+ <button
265
+ key={varKey(variable)}
266
+ onClick={() => selectVariable(variable)}
267
+ className={cn(
268
+ "w-full px-3 py-2 text-left text-sm flex items-center justify-between hover:bg-accent",
269
+ index === selectedIndex && "bg-accent",
270
+ )}
271
+ >
272
+ <div className="flex flex-col">
273
+ <span className="font-mono">
274
+ ${"{"}
275
+ {variable.name}
276
+ {"}"}
277
+ </span>
278
+ <span className="text-xs text-muted-foreground truncate max-w-[150px]">
279
+ {variable.kind === "node"
280
+ ? t("fromNode", { node: nodeLabelById[variable.nodeId] ?? variable.nodeId })
281
+ : variable.kind === "declared"
282
+ ? t("globalVariable")
283
+ : t("fnarg")}
284
+ </span>
285
+ </div>
286
+ <span className="text-xs text-muted-foreground">{variable.dataType}</span>
287
+ </button>
288
+ ))}
289
+ </div>
290
+ )}
291
+
292
+ {/* Type indicator */}
293
+ <div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-2 pointer-events-none">
294
+ <span className="text-xs text-muted-foreground">→ {expressionType}</span>
295
+ </div>
296
+ </div>
297
+ </div>
298
+ );
299
+ };
300
+
301
+ export default ExpressionInput;