@kiberon-labs/behave-graph-flow 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/.fallowrc.json +16 -0
  2. package/.storybook/main.ts +32 -0
  3. package/.storybook/preview.ts +16 -0
  4. package/.storybook/styles.css +10 -0
  5. package/.storybook/vscode.css +814 -0
  6. package/.turbo/turbo-build.log +7 -0
  7. package/LICENSE +6 -0
  8. package/README.md +2 -2
  9. package/data/Polynomial.json +510 -0
  10. package/data/sequence.json +337 -0
  11. package/data/trigger-event.json +241 -0
  12. package/data/variable-change.json +210 -0
  13. package/dist/entry.css +4 -0
  14. package/dist/index.css +39 -0
  15. package/dist/index.css.map +1 -0
  16. package/dist/index.d.ts +2282 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +14873 -0
  19. package/dist/index.js.map +1 -0
  20. package/docs/notifications.md +246 -0
  21. package/docs/protocol.md +679 -0
  22. package/docs/specifics.md +191 -0
  23. package/package.json +85 -21
  24. package/postcss.config.ts +3 -4
  25. package/src/annotations/index.ts +32 -0
  26. package/src/components/FloatingToolbar/index.module.css +45 -0
  27. package/src/components/FloatingToolbar/index.tsx +256 -0
  28. package/src/components/Flow.tsx +276 -75
  29. package/src/components/contextMenus/NodePicker.module.css +274 -0
  30. package/src/components/contextMenus/NodePicker.tsx +481 -0
  31. package/src/components/contextMenus/edge.tsx +108 -0
  32. package/src/components/contextMenus/node.tsx +155 -0
  33. package/src/components/contextMenus/selection.tsx +77 -0
  34. package/src/components/controls/any/index.tsx +8 -0
  35. package/src/components/controls/boolean/index.tsx +13 -0
  36. package/src/components/controls/colorPicker/InputPopover.module.css +100 -0
  37. package/src/components/controls/colorPicker/InputPopover.tsx +31 -0
  38. package/src/components/controls/colorPicker/index.module.css +18 -0
  39. package/src/components/controls/colorPicker/index.tsx +61 -0
  40. package/src/components/controls/number/index.tsx +35 -0
  41. package/src/components/controls/string/index.tsx +16 -0
  42. package/src/components/edges/index.tsx +469 -0
  43. package/src/components/edges/offsetBezier.ts +134 -0
  44. package/src/components/hotKeys.tsx +20 -0
  45. package/src/components/layoutController/index.module.css +10 -0
  46. package/src/components/layoutController/index.tsx +117 -0
  47. package/src/components/layoutController/utils.ts +205 -0
  48. package/src/components/menubar/defaults.tsx +480 -0
  49. package/src/components/menubar/index.tsx +49 -0
  50. package/src/components/menubar/menuItem.module.css +16 -0
  51. package/src/components/menubar/menuItem.tsx +32 -0
  52. package/src/components/nodes/behave/Node.module.css +23 -0
  53. package/src/components/nodes/behave/Node.tsx +176 -0
  54. package/src/components/nodes/behave/NodeContainer.module.css +87 -0
  55. package/src/components/nodes/behave/NodeContainer.tsx +46 -0
  56. package/src/components/nodes/behave/index.tsx +14 -0
  57. package/src/components/nodes/comment/FormatToolbar.tsx +118 -0
  58. package/src/components/nodes/comment/comment.tsx +103 -0
  59. package/src/components/nodes/comment/styles.module.css +150 -0
  60. package/src/components/nodes/group/index.tsx +109 -0
  61. package/src/components/nodes/wrapper/index.tsx +73 -0
  62. package/src/components/nodes/wrapper/styles.module.css +113 -0
  63. package/src/components/notifications/NotificationProvider.tsx +81 -0
  64. package/src/components/notifications/index.ts +2 -0
  65. package/src/components/notifications/utils.ts +71 -0
  66. package/src/components/panels/alignment/index.module.css +20 -0
  67. package/src/components/panels/alignment/index.tsx +244 -0
  68. package/src/components/panels/base/index.tsx +5 -0
  69. package/src/components/panels/base/styles.module.css +12 -0
  70. package/src/components/panels/conversation/index.module.css +151 -0
  71. package/src/components/panels/conversation/index.tsx +162 -0
  72. package/src/components/panels/events/CustomEventsEditor.tsx +384 -0
  73. package/src/components/panels/events/EditEventPanel.tsx +315 -0
  74. package/src/components/panels/events/ManageEventsPanel.tsx +98 -0
  75. package/src/components/panels/events/index.tsx +23 -0
  76. package/src/components/panels/events/styles.module.css +236 -0
  77. package/src/components/panels/history/index.tsx +92 -0
  78. package/src/components/panels/history/styles.module.css +106 -0
  79. package/src/components/panels/keymaps/index.module.css +78 -0
  80. package/src/components/panels/keymaps/index.tsx +167 -0
  81. package/src/components/panels/layers/index.tsx +240 -0
  82. package/src/components/panels/layers/styles.module.css +110 -0
  83. package/src/components/panels/legend/index.module.css +6 -0
  84. package/src/components/panels/legend/index.tsx +76 -0
  85. package/src/components/panels/logs/index.module.css +212 -0
  86. package/src/components/panels/logs/index.tsx +288 -0
  87. package/src/components/panels/nodeInputs/InputControl.tsx +63 -0
  88. package/src/components/panels/nodeInputs/InputsGroup.tsx +64 -0
  89. package/src/components/panels/nodeInputs/MultipleNodesView.tsx +37 -0
  90. package/src/components/panels/nodeInputs/NodeSettings.tsx +92 -0
  91. package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +125 -0
  92. package/src/components/panels/nodeInputs/OutputsGroup.tsx +65 -0
  93. package/src/components/panels/nodeInputs/SocketGenerators.tsx +32 -0
  94. package/src/components/panels/nodeInputs/index.module.css +284 -0
  95. package/src/components/panels/nodeInputs/index.tsx +339 -0
  96. package/src/components/panels/nodeInputs/useNodeHandlers.ts +76 -0
  97. package/src/components/panels/nodeInputs/useNodeInputsData.ts +173 -0
  98. package/src/components/panels/nodePicker/index.tsx +115 -0
  99. package/src/components/panels/panel/index.module.css +66 -0
  100. package/src/components/panels/panel/index.tsx +88 -0
  101. package/src/components/panels/search/index.module.css +66 -0
  102. package/src/components/panels/search/index.tsx +215 -0
  103. package/src/components/panels/systemSettings/index.tsx +206 -0
  104. package/src/components/panels/systemSettings/styles.module.css +11 -0
  105. package/src/components/panels/traces/GridLines.tsx +38 -0
  106. package/src/components/panels/traces/TimeGrid.tsx +48 -0
  107. package/src/components/panels/traces/TraceLane.tsx +62 -0
  108. package/src/components/panels/traces/TraceTooltip.tsx +22 -0
  109. package/src/components/panels/traces/TracesHeader.tsx +56 -0
  110. package/src/components/panels/traces/index.module.css +166 -0
  111. package/src/components/panels/traces/index.tsx +294 -0
  112. package/src/components/panels/traces/types.ts +48 -0
  113. package/src/components/panels/traces/useDerivedSpans.ts +212 -0
  114. package/src/components/panels/traces/utils.ts +25 -0
  115. package/src/components/panels/variables/CreateVariableScreen.tsx +162 -0
  116. package/src/components/panels/variables/ManageVariablesScreen.tsx +144 -0
  117. package/src/components/panels/variables/index.tsx +125 -0
  118. package/src/components/panels/variables/styles.module.css +236 -0
  119. package/src/components/primitives/icon.module.css +45 -0
  120. package/src/components/primitives/icon.tsx +38 -0
  121. package/src/components/sockets/input/index.tsx +76 -0
  122. package/src/components/sockets/input/styles.module.css +27 -0
  123. package/src/components/sockets/output/index.tsx +61 -0
  124. package/src/components/sockets/output/styles.module.css +27 -0
  125. package/src/css/prosemirror.css +57 -0
  126. package/src/css/rc-dock.css +112 -0
  127. package/src/css/rc-menu.css +100 -0
  128. package/src/css/vars.css +14 -0
  129. package/src/css/vscode.css +13 -0
  130. package/src/entry.css +4 -0
  131. package/src/generators/CustomEventOnTriggeredGenerator.tsx +85 -0
  132. package/src/generators/SequenceGenerator.tsx +104 -0
  133. package/src/generators/SwitchOnIntegerGenerator.tsx +256 -0
  134. package/src/generators/SwitchOnStringGenerator.tsx +263 -0
  135. package/src/generators/registerDefaultGenerators.ts +34 -0
  136. package/src/hooks/useBehaveGraphFlow.ts +17 -16
  137. package/src/hooks/useDetachNodes.ts +39 -0
  138. package/src/hooks/useFlowHandlers.ts +115 -29
  139. package/src/hooks/useWasdPan.ts +188 -0
  140. package/src/index.css +146 -0
  141. package/src/index.ts +36 -18
  142. package/src/layout/dagre.tsx +119 -0
  143. package/src/layout/elk.ts +200 -0
  144. package/src/plugin/alignment/index.ts +81 -0
  145. package/src/plugin/docs/index.tsx +299 -0
  146. package/src/plugin/docs/panel/index.tsx +200 -0
  147. package/src/plugin/docs/panel/styles.module.css +174 -0
  148. package/src/plugin/graphrunner/actions.ts +253 -0
  149. package/src/plugin/graphrunner/buttons.tsx +87 -0
  150. package/src/plugin/graphrunner/client.ts +704 -0
  151. package/src/plugin/graphrunner/index.tsx +255 -0
  152. package/src/plugin/graphrunner/panel.tsx +386 -0
  153. package/src/plugin/graphrunner/runner.ts +358 -0
  154. package/src/plugin/graphrunner/session.ts +243 -0
  155. package/src/plugin/graphrunner/store.ts +206 -0
  156. package/src/plugin/graphrunner/styles.module.css +211 -0
  157. package/src/plugin/graphrunner/transport.ts +224 -0
  158. package/src/plugin/graphrunner/types.ts +672 -0
  159. package/src/plugin/graphrunner-local/execution-utils.ts +457 -0
  160. package/src/plugin/graphrunner-local/index.tsx +166 -0
  161. package/src/plugin/graphrunner-local/panel.tsx +231 -0
  162. package/src/plugin/graphrunner-local/store.ts +41 -0
  163. package/src/plugin/graphrunner-local/styles.module.css +101 -0
  164. package/src/plugin/graphrunner-local/transport.ts +1372 -0
  165. package/src/plugin/graphrunner-local/types.ts +10 -0
  166. package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +633 -0
  167. package/src/plugin/graphrunner-webworker/index.tsx +146 -0
  168. package/src/plugin/graphrunner-webworker/panel.tsx +173 -0
  169. package/src/plugin/graphrunner-webworker/store.ts +89 -0
  170. package/src/plugin/graphrunner-webworker/types.ts +17 -0
  171. package/src/plugin/graphrunner-webworker/worker-transport.ts +123 -0
  172. package/src/plugin/realtime/realtimeRunner.ts +570 -0
  173. package/src/specifics/CustomEventOnTriggeredSpecific.tsx +92 -0
  174. package/src/specifics/CustomEventTriggerSpecific.tsx +141 -0
  175. package/src/specifics/VariableGetSpecific.tsx +110 -0
  176. package/src/specifics/VariableSetSpecific.tsx +110 -0
  177. package/src/specifics/registerDefaultSpecifics.ts +5 -0
  178. package/src/store/actions.tsx +698 -0
  179. package/src/store/chat.ts +73 -0
  180. package/src/store/controls.tsx +62 -0
  181. package/src/store/documentation.tsx +69 -0
  182. package/src/store/events.tsx +116 -0
  183. package/src/store/flow.tsx +245 -0
  184. package/src/store/graphRunnerClient.ts +110 -0
  185. package/src/store/hotKeys.tsx +323 -0
  186. package/src/store/layers.ts +259 -0
  187. package/src/store/legend.tsx +76 -0
  188. package/src/store/logs.ts +28 -0
  189. package/src/store/menubar.ts +41 -0
  190. package/src/store/refs.ts +84 -0
  191. package/src/store/registry.ts +43 -0
  192. package/src/store/selection.ts +22 -0
  193. package/src/store/settings.ts +99 -0
  194. package/src/store/socketGenerator.tsx +54 -0
  195. package/src/store/specific.tsx +75 -0
  196. package/src/store/specs.tsx +35 -0
  197. package/src/store/tabs.ts +278 -0
  198. package/src/store/toolbar.tsx +45 -0
  199. package/src/store/traces.ts +240 -0
  200. package/src/store/variables.ts +37 -0
  201. package/src/system/graph.ts +134 -0
  202. package/src/system/index.ts +3 -0
  203. package/src/system/notifications.ts +98 -0
  204. package/src/system/plugin.ts +27 -0
  205. package/src/system/provider.tsx +22 -0
  206. package/src/system/pubsub.ts +323 -0
  207. package/src/system/system.ts +223 -0
  208. package/src/system/tabLoader.tsx +265 -0
  209. package/src/system/undoRedo.ts +103 -0
  210. package/src/transformers/Uigraph.ts +60 -0
  211. package/src/transformers/behaveToFlow.ts +16 -4
  212. package/src/transformers/flowToBehave.ts +32 -12
  213. package/src/types/NodeMetadata.ts +27 -0
  214. package/src/types/graph.ts +49 -0
  215. package/src/types/nodes.ts +45 -0
  216. package/src/types.ts +16 -0
  217. package/src/util/colors.ts +1 -29
  218. package/src/util/downloadJson.ts +18 -0
  219. package/src/util/extractNodeMetadata.ts +16 -0
  220. package/src/util/getPickerFilters.ts +1 -1
  221. package/src/util/isBehaveNode.ts +6 -0
  222. package/src/util/isValidConnection.ts +28 -15
  223. package/src/util/mergeSockets.ts +29 -0
  224. package/src/util/serializeVariables.ts +66 -0
  225. package/src/util/sockets.ts +43 -0
  226. package/stories/apex/layoutController/example-graph.worker.ts +39 -0
  227. package/stories/apex/layoutController/index.stories.tsx +48 -0
  228. package/stories/apex/layoutController/webworker.stories.tsx +103 -0
  229. package/stories/apex/menubar/menubar.stories.tsx +19 -0
  230. package/stories/components/colorpicker/index.stories.tsx +20 -0
  231. package/stories/components/contextMenus/edge.stories.tsx +32 -0
  232. package/stories/components/contextMenus/node.stories.tsx +26 -0
  233. package/stories/components/contextMenus/nodePicker.stories.tsx +115 -0
  234. package/stories/components/controls/any/index.stories.tsx +19 -0
  235. package/stories/components/controls/boolean/index.stories.tsx +19 -0
  236. package/stories/components/controls/colorPicker/index.stories.tsx +49 -0
  237. package/stories/components/controls/number/index.stories.tsx +19 -0
  238. package/stories/components/controls/string/index.stories.tsx +19 -0
  239. package/stories/components/nodes/behaveNode.stories.tsx +108 -0
  240. package/stories/components/nodes/comment.stories.tsx +106 -0
  241. package/stories/components/panels/alignment.stories.tsx +24 -0
  242. package/stories/components/panels/events.stories.tsx +38 -0
  243. package/stories/components/panels/graphRunner.stories.tsx +317 -0
  244. package/stories/components/panels/history.stories.tsx +37 -0
  245. package/stories/components/panels/keymaps.stories.tsx +21 -0
  246. package/stories/components/panels/legend.stories.tsx +37 -0
  247. package/stories/components/panels/logs.stories.tsx +24 -0
  248. package/stories/components/panels/nodeInputs.stories.tsx +21 -0
  249. package/stories/components/panels/nodePicker.stories.tsx +37 -0
  250. package/stories/components/panels/panel.stories.tsx +39 -0
  251. package/stories/components/panels/search.stories.tsx +24 -0
  252. package/stories/components/panels/systemSettings.stories.tsx +26 -0
  253. package/stories/components/panels/traces.stories.tsx +225 -0
  254. package/stories/components/panels/variables.stories.tsx +24 -0
  255. package/stories/defaults/defaultStoryProvider.tsx +167 -0
  256. package/stories/defaults/systemGenerator.ts +38 -0
  257. package/tests/components/edges/offsetBezier.test.ts +51 -0
  258. package/tests/components/layoutController/utils.test.ts +68 -0
  259. package/tests/components/panels/traces/utils.test.ts +52 -0
  260. package/tests/flowToBehave.test.ts +26 -4
  261. package/tests/notifications.test.ts +87 -0
  262. package/tests/saveLoad.test.ts +372 -0
  263. package/tests/util/calculateNewEdge.test.ts +98 -0
  264. package/tests/util/getSocketsByNodeTypeAndHandleType.test.ts +31 -0
  265. package/tests/util/hasPositionMetaData.test.ts +33 -0
  266. package/tests/util/isBehaveNode.test.ts +22 -0
  267. package/tests/util/isHandleConnected.test.ts +37 -0
  268. package/tests/util/mergeSockets.test.ts +43 -0
  269. package/tests/visual/README.md +64 -0
  270. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-alignment-chromium-win32.png +0 -0
  271. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
  272. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
  273. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
  274. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
  275. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
  276. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
  277. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
  278. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
  279. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
  280. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
  281. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
  282. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
  283. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-traces-chromium-win32.png +0 -0
  284. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
  285. package/tests/visual/panels.visual.test.tsx +76 -0
  286. package/tsconfig.base.json +39 -0
  287. package/tsconfig.json +18 -59
  288. package/tsconfig.prod.json +23 -0
  289. package/tsdown.config.ts +15 -3
  290. package/typedoc.json +7 -7
  291. package/vite.config.js +7 -0
  292. package/vitest.config.ts +5 -2
  293. package/vitest.visual.config.ts +48 -0
  294. package/src/components/AutoSizeInput.tsx +0 -65
  295. package/src/components/Controls.tsx +0 -87
  296. package/src/components/InputSocket.tsx +0 -142
  297. package/src/components/Node.tsx +0 -68
  298. package/src/components/NodeContainer.tsx +0 -46
  299. package/src/components/NodePicker.tsx +0 -77
  300. package/src/components/OutputSocket.tsx +0 -58
  301. package/src/components/modals/ClearModal.tsx +0 -40
  302. package/src/components/modals/HelpModal.tsx +0 -36
  303. package/src/components/modals/LoadModal.tsx +0 -96
  304. package/src/components/modals/Modal.tsx +0 -64
  305. package/src/components/modals/SaveModal.tsx +0 -60
  306. package/src/hooks/useCustomNodeTypes.tsx +0 -31
  307. package/src/hooks/useGraphRunner.ts +0 -104
  308. package/src/hooks/useMergeMap.ts +0 -14
  309. package/src/hooks/useNodeSpecJson.ts +0 -20
  310. package/src/hooks/useQueriableDefinitions.ts +0 -22
  311. package/src/styles.css +0 -8
  312. package/tailwind.config.ts +0 -19
  313. package/tests/tsconfig.json +0 -10
  314. /package/src/{types.d.ts → types-declarations.d.ts} +0 -0
@@ -0,0 +1,570 @@
1
+ import {
2
+ Engine,
3
+ type GraphInstance,
4
+ type ILifecycleEventEmitter,
5
+ type IRegistry,
6
+ isFunctionNode,
7
+ readGraphFromJSON
8
+ } from '@kiberon-labs/behave-graph';
9
+ import { create, type StoreApi } from 'zustand';
10
+
11
+ import type { System } from '@/system/system.js';
12
+ import { flowToBehave } from '@/transformers/flowToBehave.js';
13
+ import { realtime } from '@/annotations';
14
+
15
+ export type RealtimeRunnerStore = {
16
+ engine?: Engine;
17
+ /** Live output values published for UI consumers (keyed by nodeId -> outputName) */
18
+ outputs: Record<string, Record<string, unknown>>;
19
+ setEngine: (engine?: Engine) => void;
20
+ setOutputs: (
21
+ updates: Array<{ nodeId: string; outputName: string; value: unknown }>
22
+ ) => void;
23
+ };
24
+
25
+ const realtimeRunnerStoreFactory = () =>
26
+ create<RealtimeRunnerStore>((set) => ({
27
+ engine: undefined,
28
+ outputs: {},
29
+ setEngine: (engine) => set({ engine }),
30
+ setOutputs: (updates) =>
31
+ set((state) => {
32
+ if (updates.length === 0) return state;
33
+
34
+ let changed = false;
35
+ const nextOutputs: Record<string, Record<string, unknown>> = {
36
+ ...state.outputs
37
+ };
38
+
39
+ for (const { nodeId, outputName, value } of updates) {
40
+ const prevNode = nextOutputs[nodeId] ?? state.outputs[nodeId];
41
+ const prevVal = prevNode?.[outputName];
42
+ if (Object.is(prevVal, value)) continue;
43
+
44
+ const base = prevNode ?? {};
45
+ // Copy-on-write per node
46
+ const nextNode = base === prevNode ? { ...base } : { ...base };
47
+ nextNode[outputName] = value;
48
+ nextOutputs[nodeId] = nextNode;
49
+ changed = true;
50
+ }
51
+
52
+ if (!changed) return state;
53
+ return { outputs: nextOutputs };
54
+ })
55
+ }));
56
+
57
+ /**
58
+ * RealtimeRunner keeps a small preview Engine up-to-date with the current graph.
59
+ * It is designed for UI previews (e.g. live node output thumbnails) and does not
60
+ * replace the existing `GraphRunner`.
61
+ */
62
+ export class RealtimeRunner {
63
+ private system: System;
64
+ private executionRegistry?: IRegistry;
65
+ private graphInstance?: GraphInstance;
66
+ private engine?: Engine;
67
+ private scheduled = false;
68
+ private tickRafHandle?: number;
69
+ private tickTimeoutHandle?: number;
70
+ private tickLoopActive = false;
71
+ private tickInProgress = false;
72
+ private lastTickAtMs = 0;
73
+ private readonly tickIntervalMs = 50;
74
+ private unsubscribers: Array<() => void> = [];
75
+ private watched = new Map<string, Set<string>>();
76
+ private lastPublished = new Map<string, Map<string, unknown>>();
77
+ private lastGraphSignature?: string;
78
+ private annotatedOutputNodeIds: string[] = [];
79
+
80
+ public readonly store: StoreApi<RealtimeRunnerStore>;
81
+
82
+ constructor(system: System, executionRegistry?: IRegistry) {
83
+ this.system = system;
84
+ this.executionRegistry = executionRegistry;
85
+ this.store = realtimeRunnerStoreFactory();
86
+
87
+ // Start in a low-impact mode; actual ticking only happens when something is watched.
88
+ this.start();
89
+ }
90
+
91
+ getEngine(): Engine | undefined {
92
+ return this.engine;
93
+ }
94
+
95
+ /**
96
+ * Get a value from a node's output socket, if available.
97
+ */
98
+ getNodeOutputValue(nodeId: string, outputName: string): unknown {
99
+ const node = this.engine?.nodes?.[nodeId];
100
+ const socket = node?.outputs?.find((s: any) => s.name === outputName);
101
+ return socket?.value;
102
+ }
103
+
104
+ /**
105
+ * Register interest in a particular node output. Used to keep the preview runner efficient.
106
+ */
107
+ watchNodeOutput(nodeId: string, outputName: string): () => void {
108
+ const existing = this.watched.get(nodeId);
109
+ if (existing) {
110
+ existing.add(outputName);
111
+ } else {
112
+ this.watched.set(nodeId, new Set([outputName]));
113
+ }
114
+
115
+ this.ensureTicking();
116
+ this.scheduleEvaluate();
117
+
118
+ return () => {
119
+ const set = this.watched.get(nodeId);
120
+ if (!set) return;
121
+ set.delete(outputName);
122
+ if (set.size === 0) this.watched.delete(nodeId);
123
+ this.ensureTicking();
124
+ };
125
+ }
126
+
127
+ start(): void {
128
+ this.stop();
129
+
130
+ // Rebuild/re-evaluate when graph inputs change.
131
+ this.unsubscribers.push(
132
+ this.system.nodeStore.subscribe(() => this.scheduleEvaluate())
133
+ );
134
+ this.unsubscribers.push(
135
+ this.system.edgeStore.subscribe(() => this.scheduleEvaluate())
136
+ );
137
+ this.unsubscribers.push(
138
+ this.system.variableStore.subscribe(() => this.scheduleEvaluate())
139
+ );
140
+ this.unsubscribers.push(
141
+ this.system.specStore.subscribe(() => this.scheduleEvaluate())
142
+ );
143
+ this.unsubscribers.push(
144
+ this.system.registry.subscribe(() => this.scheduleEvaluate())
145
+ );
146
+
147
+ this.scheduleEvaluate();
148
+ }
149
+
150
+ stop(): void {
151
+ this.unsubscribers.forEach((u) => u());
152
+ this.unsubscribers = [];
153
+
154
+ this.stopTickLoop();
155
+
156
+ if (this.engine) {
157
+ this.engine.dispose();
158
+ this.engine = undefined;
159
+ this.graphInstance = undefined;
160
+ this.store.getState().setEngine(undefined);
161
+ }
162
+ }
163
+
164
+ triggerNode(nodeId: string): void {
165
+ const node = this.engine?.nodes?.[nodeId];
166
+ if (!node) {
167
+ throw new Error(`node not found: ${nodeId}`);
168
+ }
169
+ this.engine?.trigger(node, 'flow');
170
+ // this.engine?.executeAllSync(100,1);
171
+ // this.startTickLoop();
172
+ }
173
+
174
+ private ensureTicking(): void {
175
+ const hasWatchers = this.watched.size > 0;
176
+
177
+ if (hasWatchers) {
178
+ this.startTickLoop();
179
+ return;
180
+ }
181
+
182
+ this.stopTickLoop();
183
+ }
184
+
185
+ private nowMs(): number {
186
+ return typeof performance !== 'undefined' && performance.now
187
+ ? performance.now()
188
+ : Date.now();
189
+ }
190
+
191
+ private startTickLoop(): void {
192
+ if (
193
+ this.tickRafHandle !== undefined ||
194
+ this.tickTimeoutHandle !== undefined
195
+ ) {
196
+ return;
197
+ }
198
+
199
+ this.tickLoopActive = true;
200
+
201
+ this.lastTickAtMs = this.nowMs();
202
+ this.scheduleNextTick();
203
+ }
204
+
205
+ private stopTickLoop(): void {
206
+ this.tickLoopActive = false;
207
+ if (this.tickRafHandle !== undefined) {
208
+ window.cancelAnimationFrame(this.tickRafHandle);
209
+ this.tickRafHandle = undefined;
210
+ }
211
+ if (this.tickTimeoutHandle !== undefined) {
212
+ window.clearTimeout(this.tickTimeoutHandle);
213
+ this.tickTimeoutHandle = undefined;
214
+ }
215
+ }
216
+
217
+ private scheduleNextTick(): void {
218
+ if (!this.tickLoopActive) return;
219
+ //TODO REadd
220
+ // if (this.watched.size === 0) return;
221
+ if (
222
+ this.tickRafHandle !== undefined ||
223
+ this.tickTimeoutHandle !== undefined
224
+ )
225
+ return;
226
+
227
+ const raf = window.requestAnimationFrame;
228
+ if (typeof raf === 'function') {
229
+ this.tickRafHandle = raf((timestampMs) => {
230
+ this.tickRafHandle = undefined;
231
+
232
+ if (!this.tickLoopActive) return;
233
+ // if (this.watched.size === 0) return;
234
+
235
+ const now =
236
+ typeof timestampMs === 'number' ? timestampMs : this.nowMs();
237
+ if (now - this.lastTickAtMs >= this.tickIntervalMs) {
238
+ if (this.tickInProgress) return;
239
+
240
+ this.tickInProgress = true;
241
+ this.lastTickAtMs = now;
242
+ void this.tick().finally(() => {
243
+ this.tickInProgress = false;
244
+ this.scheduleNextTick();
245
+ });
246
+ return;
247
+ }
248
+
249
+ this.scheduleNextTick();
250
+ });
251
+ return;
252
+ }
253
+
254
+ // Fallback (non-browser/test environments).
255
+ this.tickTimeoutHandle = window.setTimeout(() => {
256
+ this.tickTimeoutHandle = undefined;
257
+ if (!this.tickLoopActive) return;
258
+ // if (this.watched.size === 0) return;
259
+ if (this.tickInProgress) return;
260
+
261
+ this.tickInProgress = true;
262
+ this.lastTickAtMs = this.nowMs();
263
+ void this.tick().finally(() => {
264
+ this.tickInProgress = false;
265
+ this.scheduleNextTick();
266
+ });
267
+ }, this.tickIntervalMs);
268
+ }
269
+
270
+ private scheduleEvaluate(): void {
271
+ if (this.scheduled) return;
272
+ this.scheduled = true;
273
+
274
+ // Batch rapid changes (dragging, typing) into a single eval.
275
+ window.setTimeout(() => {
276
+ this.scheduled = false;
277
+ void this.evaluate();
278
+ }, 50);
279
+ }
280
+
281
+ private stripNonSemanticMetadata(graphJson: any): any {
282
+ // We want previews to react to semantic changes (types/ports/edges/vars),
283
+ // not editor-only movement/selection changes.
284
+ if (!graphJson?.nodes) return graphJson;
285
+ const nodes = graphJson.nodes.map((n: any) => {
286
+ if (!n || typeof n !== 'object') return n;
287
+ if (!n.metadata) return n;
288
+
289
+ const meta = { ...n.metadata };
290
+ delete meta.positionX;
291
+ delete meta.positionY;
292
+
293
+ const hasMeta = Object.keys(meta).length > 0;
294
+ return hasMeta ? { ...n, metadata: meta } : { ...n, metadata: undefined };
295
+ });
296
+ return { ...graphJson, nodes };
297
+ }
298
+
299
+ private computeGraphSignature(graphJson: any): string {
300
+ // Stable enough for our preview use-case.
301
+ return JSON.stringify(graphJson);
302
+ }
303
+
304
+ private mergeNodeAnnotationsIntoMetadata(
305
+ graphJson: any,
306
+ flowNodes: any[]
307
+ ): any {
308
+ // The editor stores node annotations in ReactFlow `node.data.annotations`.
309
+ // The execution engine only sees behave-graph `node.metadata` (string map),
310
+ // so we merge annotations into metadata here.
311
+ if (!graphJson?.nodes) return graphJson;
312
+
313
+ const byId = new Map<string, any>();
314
+ for (const n of flowNodes) {
315
+ if (n?.id) byId.set(n.id, n);
316
+ }
317
+
318
+ const nodes = graphJson.nodes.map((nodeJson: any) => {
319
+ const flowNode = byId.get(nodeJson?.id);
320
+ const annotations = flowNode?.data?.annotations;
321
+ if (!annotations || typeof annotations !== 'object') return nodeJson;
322
+
323
+ const meta: Record<string, string> = {
324
+ ...(nodeJson.metadata as Record<string, string> | undefined)
325
+ };
326
+ for (const [key, value] of Object.entries(annotations)) {
327
+ if (value === undefined) continue;
328
+ if (typeof value === 'string') {
329
+ meta[key] = value;
330
+ continue;
331
+ }
332
+ if (typeof value === 'number' || typeof value === 'boolean') {
333
+ meta[key] = String(value);
334
+ continue;
335
+ }
336
+ try {
337
+ meta[key] = JSON.stringify(value);
338
+ } catch {
339
+ meta[key] = String(value);
340
+ }
341
+ }
342
+
343
+ return { ...nodeJson, metadata: meta };
344
+ });
345
+
346
+ return { ...graphJson, nodes };
347
+ }
348
+
349
+ private isRealtimeOutputNode(node: any): boolean {
350
+ const meta = node?.metadata;
351
+ if (!meta || typeof meta !== 'object') return false;
352
+
353
+ const rawFlag = meta[realtime];
354
+
355
+ return !(rawFlag === undefined || rawFlag === null);
356
+ }
357
+
358
+ private rebuildAnnotatedOutputNodeCache(): void {
359
+ if (!this.engine?.nodes) {
360
+ this.annotatedOutputNodeIds = [];
361
+ return;
362
+ }
363
+
364
+ const ids: string[] = [];
365
+ for (const node of Object.values(this.engine.nodes)) {
366
+ if (this.isRealtimeOutputNode(node)) {
367
+ ids.push(node.id);
368
+ }
369
+ }
370
+ this.annotatedOutputNodeIds = ids;
371
+ }
372
+
373
+ private async resolveSocketValueForPreview(
374
+ inputSocket: any
375
+ ): Promise<number> {
376
+ if (!this.engine) return 0;
377
+ if (!inputSocket?.links || inputSocket.links.length === 0) return 0;
378
+
379
+ const nodes = this.engine.nodes;
380
+
381
+ // Safe because links.length > 0
382
+ const upstreamLink = inputSocket.links[0]!;
383
+
384
+ if (
385
+ upstreamLink._targetNode === undefined ||
386
+ upstreamLink._targetSocket === undefined
387
+ ) {
388
+ upstreamLink._targetNode = nodes[upstreamLink.nodeId]!;
389
+ upstreamLink._targetSocket = upstreamLink._targetNode.outputs.find(
390
+ (socket: any) => socket.name === upstreamLink.socketName
391
+ );
392
+ if (upstreamLink._targetSocket === undefined) {
393
+ throw new Error(
394
+ `can not find socket with the name ${upstreamLink.socketName}`
395
+ );
396
+ }
397
+ }
398
+
399
+ const upstreamNode = upstreamLink._targetNode;
400
+ const upstreamOutputSocket = upstreamLink._targetSocket;
401
+
402
+ // If upstream is a flow/event/async node, use its existing output value.
403
+ if (!isFunctionNode(upstreamNode)) {
404
+ inputSocket.value = upstreamOutputSocket.value;
405
+ return 0;
406
+ }
407
+
408
+ let executionSteps = 0;
409
+ for (const upstreamInputSocket of upstreamNode.inputs) {
410
+ executionSteps +=
411
+ await this.resolveSocketValueForPreview(upstreamInputSocket);
412
+ }
413
+
414
+ this.engine.onNodeExecutionStart.emit(upstreamNode);
415
+ await upstreamNode.exec(upstreamNode);
416
+ executionSteps++;
417
+ this.engine.onNodeExecutionEnd.emit(upstreamNode);
418
+
419
+ inputSocket.value = upstreamOutputSocket.value;
420
+ return executionSteps;
421
+ }
422
+
423
+ private async recalculateAnnotatedOutputNodes(): Promise<void> {
424
+ if (!this.engine) return;
425
+ if (this.annotatedOutputNodeIds.length === 0) return;
426
+
427
+ for (const nodeId of this.annotatedOutputNodeIds) {
428
+ const node = this.engine.nodes?.[nodeId];
429
+ if (!node) continue;
430
+ if (!isFunctionNode(node)) continue;
431
+
432
+ let executionSteps = 0;
433
+ for (const inputSocket of node.inputs) {
434
+ executionSteps += await this.resolveSocketValueForPreview(inputSocket);
435
+ }
436
+
437
+ this.engine.onNodeExecutionStart.emit(node);
438
+ await node.exec(node);
439
+ executionSteps++;
440
+ this.engine.onNodeExecutionEnd.emit(node);
441
+
442
+ this.engine.executionSteps += executionSteps;
443
+ }
444
+ }
445
+
446
+ private rebuildEngine(): void {
447
+ const specJson = this.system.specStore.getState().specs;
448
+ if (!specJson || specJson.length === 0) return;
449
+
450
+ const nodes = this.system.nodeStore.getState().nodes;
451
+ const edges = this.system.edgeStore.getState().edges;
452
+
453
+ const rawGraphJson = flowToBehave(this.system, nodes, edges, specJson);
454
+ const graphWithAnnotations = this.mergeNodeAnnotationsIntoMetadata(
455
+ rawGraphJson,
456
+ nodes
457
+ );
458
+ const graphJson = this.stripNonSemanticMetadata(graphWithAnnotations);
459
+
460
+ const signature = this.computeGraphSignature(graphJson);
461
+ if (this.lastGraphSignature === signature && this.engine) {
462
+ return;
463
+ }
464
+ this.lastGraphSignature = signature;
465
+
466
+ if (!this.executionRegistry) {
467
+ return;
468
+ }
469
+
470
+ this.graphInstance = readGraphFromJSON({
471
+ graphJson,
472
+ registry: this.executionRegistry
473
+ });
474
+
475
+ if (this.engine) {
476
+ this.engine.dispose();
477
+ }
478
+
479
+ this.engine = new Engine(this.graphInstance, this.executionRegistry);
480
+
481
+ this.engine.onNodeExecutionError.addListener(({ error }) => {
482
+ console.error(error);
483
+ });
484
+
485
+ this.store.getState().setEngine(this.engine);
486
+
487
+ this.rebuildAnnotatedOutputNodeCache();
488
+ }
489
+
490
+ private publishWatchedOutputs(): void {
491
+ if (!this.engine) return;
492
+ if (this.watched.size === 0) return;
493
+
494
+ const updates: Array<{
495
+ nodeId: string;
496
+ outputName: string;
497
+ value: unknown;
498
+ }> = [];
499
+
500
+ for (const [nodeId, outputs] of this.watched.entries()) {
501
+ const node = this.engine.nodes?.[nodeId];
502
+ if (!node) continue;
503
+
504
+ let lastForNode = this.lastPublished.get(nodeId);
505
+ if (!lastForNode) {
506
+ lastForNode = new Map<string, unknown>();
507
+ this.lastPublished.set(nodeId, lastForNode);
508
+ }
509
+
510
+ for (const outputName of outputs.values()) {
511
+ const outputSocket = node.outputs?.find(
512
+ (s: any) => s.name === outputName
513
+ );
514
+ const inputSocket = outputSocket
515
+ ? undefined
516
+ : node.inputs?.find((s: any) => s.name === outputName);
517
+
518
+ const value = (outputSocket ?? inputSocket)?.value;
519
+ const prev = lastForNode.get(outputName);
520
+ if (Object.is(prev, value)) continue;
521
+
522
+ lastForNode.set(outputName, value);
523
+ updates.push({ nodeId, outputName, value });
524
+ }
525
+ }
526
+
527
+ if (updates.length > 0) {
528
+ this.store.getState().setOutputs(updates);
529
+ }
530
+ }
531
+
532
+ private async evaluate(): Promise<void> {
533
+ try {
534
+ this.rebuildEngine();
535
+ if (!this.engine) return;
536
+
537
+ // Compute function/flow nodes immediately.
538
+ await this.engine.executeAllSync();
539
+
540
+ // Recalculate annotated output nodes to force evaluation of upstream function graphs.
541
+ await this.recalculateAnnotatedOutputNodes();
542
+
543
+ this.publishWatchedOutputs();
544
+ } catch (err) {
545
+ // Keep preview runner resilient; don't crash the editor.
546
+ console.error('RealtimeRunner evaluate failed:', err);
547
+ }
548
+ }
549
+
550
+ private async tick(): Promise<void> {
551
+ if (!this.engine) return;
552
+ if (this.watched.size === 0) return;
553
+
554
+ try {
555
+ const eventEmitter = this.executionRegistry?.dependencies
556
+ ?.ILifecycleEventEmitter as ILifecycleEventEmitter | undefined;
557
+
558
+ // Tick time-based graphs.
559
+ eventEmitter?.tickEvent?.emit();
560
+
561
+ await this.engine.executeAllSync();
562
+ // Recalculate annotated output nodes to force evaluation of upstream function graphs.
563
+ await this.recalculateAnnotatedOutputNodes();
564
+
565
+ this.publishWatchedOutputs();
566
+ } catch {
567
+ // ignore
568
+ }
569
+ }
570
+ }
@@ -0,0 +1,92 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import { useStore } from 'zustand';
3
+ import {
4
+ VscodeOption,
5
+ VscodeSingleSelect
6
+ } from '@vscode-elements/react-elements';
7
+
8
+ import { useSystem } from '@/system/provider';
9
+ import type { SpecificRenderProps } from '@/store/specific';
10
+
11
+ const NAME = 'customEvent/onTriggered.customEventId';
12
+
13
+ export function getCustomEventOnTriggeredSpecific() {
14
+ return {
15
+ name: NAME,
16
+ check: (spec: any) => spec?.type === 'customEvent/onTriggered',
17
+ render: CustomEventOnTriggeredSpecific
18
+ };
19
+ }
20
+
21
+ const CustomEventOnTriggeredSpecific: React.FC<SpecificRenderProps> = ({
22
+ node
23
+ }) => {
24
+ const system = useSystem();
25
+ const customEvents = useStore(system.eventsStore, (s) => s.customEvents);
26
+
27
+ const options = useMemo(() => {
28
+ return Object.values(customEvents)
29
+ .map((evt) => ({
30
+ id: evt.id === undefined || evt.id === null ? '' : String(evt.id),
31
+ name:
32
+ evt.name === undefined || evt.name === null ? '' : String(evt.name)
33
+ }))
34
+ .filter((x) => x.id);
35
+ }, [customEvents]);
36
+
37
+ const value = useMemo(() => {
38
+ const v = node.data?.configuration?.customEventId;
39
+ return v === undefined || v === null ? '' : String(v);
40
+ }, [node.data]);
41
+
42
+ const setNodeConfigValue = (nextValue: string) => {
43
+ system.nodeStore.getState().setNodes((prev) =>
44
+ prev.map((n) => {
45
+ if (n.id !== node.id) return n;
46
+ return {
47
+ ...n,
48
+ data: {
49
+ ...n.data,
50
+ configuration: {
51
+ ...n.data?.configuration,
52
+ customEventId: nextValue
53
+ }
54
+ }
55
+ };
56
+ })
57
+ );
58
+ };
59
+
60
+ useEffect(() => {
61
+ if (value) return;
62
+ const first = options[0];
63
+ if (!first) return;
64
+ setNodeConfigValue(first.id);
65
+ // eslint-disable-next-line react-hooks/exhaustive-deps
66
+ }, [options.length, node.id]);
67
+
68
+ return (
69
+ <div style={{ paddingLeft: 8, paddingRight: 8, paddingBottom: 8 }}>
70
+ <div style={{ fontSize: 12, opacity: 0.9, marginBottom: 4 }}>
71
+ customEventId
72
+ </div>
73
+ <VscodeSingleSelect
74
+ value={value}
75
+ onChange={(e: any) =>
76
+ setNodeConfigValue(String(e?.target?.value ?? ''))
77
+ }
78
+ disabled={options.length === 0}
79
+ >
80
+ {options.length === 0 ? (
81
+ <VscodeOption value="">No custom events</VscodeOption>
82
+ ) : (
83
+ options.map((opt) => (
84
+ <VscodeOption key={opt.id} value={opt.id}>
85
+ {opt.name ? `${opt.name} (${opt.id})` : opt.id}
86
+ </VscodeOption>
87
+ ))
88
+ )}
89
+ </VscodeSingleSelect>
90
+ </div>
91
+ );
92
+ };