@kiberon-labs/behave-graph-flow 1.0.0 → 3.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.
- package/.fallowrc.json +16 -0
- package/.storybook/main.ts +32 -0
- package/.storybook/manager.ts +6 -0
- package/.storybook/preview.ts +64 -0
- package/.storybook/styles.css +16 -0
- package/.turbo/turbo-build.log +7 -0
- package/CHANGELOG.md +368 -0
- package/LICENSE +6 -0
- package/README.md +2 -2
- package/data/Polynomial.json +510 -0
- package/data/sequence.json +337 -0
- package/data/trigger-event.json +241 -0
- package/data/variable-change.json +210 -0
- package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
- package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
- package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
- package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
- package/dist/entry.css +4 -0
- package/dist/index.css +42 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +3597 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18009 -0
- package/dist/index.js.map +1 -0
- package/dist/noteImpl-KkrrWgJd.js +242 -0
- package/dist/noteImpl-KkrrWgJd.js.map +1 -0
- package/dist/styles.module-CvmpDkZj.css +3 -0
- package/dist/styles.module-CvmpDkZj.css.map +1 -0
- package/dist/styles.module-DZxg8aW9.js +271 -0
- package/dist/styles.module-DZxg8aW9.js.map +1 -0
- package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
- package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
- package/docs/notifications.md +246 -0
- package/docs/protocol.md +702 -0
- package/docs/specifics.md +191 -0
- package/package.json +82 -22
- package/postcss.config.ts +3 -4
- package/src/annotations/index.ts +32 -0
- package/src/components/FloatingToolbar/index.module.css +37 -0
- package/src/components/FloatingToolbar/index.tsx +256 -0
- package/src/components/Flow.tsx +287 -75
- package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
- package/src/components/contextMenus/NodePicker.module.css +274 -0
- package/src/components/contextMenus/NodePicker.tsx +481 -0
- package/src/components/contextMenus/edge.tsx +22 -0
- package/src/components/contextMenus/node.tsx +15 -0
- package/src/components/contextMenus/selection.tsx +11 -0
- package/src/components/controls/any/AnyControlImpl.tsx +14 -0
- package/src/components/controls/any/index.tsx +19 -0
- package/src/components/controls/boolean/index.tsx +13 -0
- package/src/components/controls/colorPicker/InputPopover.module.css +100 -0
- package/src/components/controls/colorPicker/InputPopover.tsx +31 -0
- package/src/components/controls/colorPicker/index.module.css +18 -0
- package/src/components/controls/colorPicker/index.tsx +61 -0
- package/src/components/controls/number/index.tsx +35 -0
- package/src/components/controls/string/index.tsx +16 -0
- package/src/components/edges/index.tsx +475 -0
- package/src/components/edges/offsetBezier.ts +134 -0
- package/src/components/hotKeys.tsx +20 -0
- package/src/components/layoutController/index.module.css +13 -0
- package/src/components/layoutController/index.tsx +140 -0
- package/src/components/layoutController/utils.ts +248 -0
- package/src/components/menubar/defaults.tsx +516 -0
- package/src/components/menubar/index.tsx +49 -0
- package/src/components/menubar/menuItem.module.css +31 -0
- package/src/components/menubar/menuItem.tsx +65 -0
- package/src/components/nodes/behave/Node.module.css +23 -0
- package/src/components/nodes/behave/Node.tsx +176 -0
- package/src/components/nodes/behave/NodeContainer.module.css +88 -0
- package/src/components/nodes/behave/NodeContainer.tsx +46 -0
- package/src/components/nodes/behave/index.tsx +14 -0
- package/src/components/nodes/group/index.tsx +109 -0
- package/src/components/nodes/wrapper/index.tsx +73 -0
- package/src/components/nodes/wrapper/styles.module.css +87 -0
- package/src/components/notifications/NotificationProvider.tsx +81 -0
- package/src/components/notifications/index.ts +2 -0
- package/src/components/notifications/utils.ts +71 -0
- package/src/components/panels/alignment/index.module.css +10 -0
- package/src/components/panels/alignment/index.tsx +244 -0
- package/src/components/panels/base/index.tsx +5 -0
- package/src/components/panels/base/styles.module.css +12 -0
- package/src/components/panels/common/PanelHeader.module.css +24 -0
- package/src/components/panels/common/PanelHeader.tsx +22 -0
- package/src/components/panels/common/SectionTitle.module.css +13 -0
- package/src/components/panels/common/SectionTitle.tsx +10 -0
- package/src/components/panels/events/EditEventPanel.tsx +324 -0
- package/src/components/panels/events/ManageEventsPanel.tsx +101 -0
- package/src/components/panels/events/index.tsx +23 -0
- package/src/components/panels/events/styles.module.css +178 -0
- package/src/components/panels/graphProperties/index.tsx +125 -0
- package/src/components/panels/history/index.tsx +92 -0
- package/src/components/panels/history/styles.module.css +97 -0
- package/src/components/panels/keymaps/index.module.css +68 -0
- package/src/components/panels/keymaps/index.tsx +166 -0
- package/src/components/panels/layers/index.tsx +245 -0
- package/src/components/panels/layers/styles.module.css +107 -0
- package/src/components/panels/legend/index.module.css +6 -0
- package/src/components/panels/legend/index.tsx +76 -0
- package/src/components/panels/logs/index.module.css +218 -0
- package/src/components/panels/logs/index.tsx +288 -0
- package/src/components/panels/nodeInputs/InputControl.tsx +63 -0
- package/src/components/panels/nodeInputs/InputsGroup.tsx +65 -0
- package/src/components/panels/nodeInputs/MultipleNodesView.tsx +37 -0
- package/src/components/panels/nodeInputs/NodeSettings.tsx +92 -0
- package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +125 -0
- package/src/components/panels/nodeInputs/OutputsGroup.tsx +55 -0
- package/src/components/panels/nodeInputs/SocketGenerators.tsx +32 -0
- package/src/components/panels/nodeInputs/index.module.css +308 -0
- package/src/components/panels/nodeInputs/index.tsx +349 -0
- package/src/components/panels/nodeInputs/useNodeHandlers.ts +76 -0
- package/src/components/panels/nodeInputs/useNodeInputsData.ts +153 -0
- package/src/components/panels/nodePicker/index.tsx +115 -0
- package/src/components/panels/panel/index.module.css +66 -0
- package/src/components/panels/panel/index.tsx +88 -0
- package/src/components/panels/search/index.module.css +16 -0
- package/src/components/panels/search/index.tsx +215 -0
- package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
- package/src/components/panels/systemSettings/index.tsx +251 -0
- package/src/components/panels/systemSettings/styles.module.css +138 -0
- package/src/components/panels/traces/GridLines.tsx +38 -0
- package/src/components/panels/traces/TimeGrid.tsx +48 -0
- package/src/components/panels/traces/TraceLane.tsx +62 -0
- package/src/components/panels/traces/TraceTooltip.tsx +22 -0
- package/src/components/panels/traces/TracesHeader.tsx +56 -0
- package/src/components/panels/traces/index.module.css +159 -0
- package/src/components/panels/traces/index.tsx +298 -0
- package/src/components/panels/traces/types.ts +48 -0
- package/src/components/panels/traces/useDerivedSpans.ts +307 -0
- package/src/components/panels/traces/utils.ts +33 -0
- package/src/components/panels/variables/CreateVariableScreen.tsx +162 -0
- package/src/components/panels/variables/ManageVariablesScreen.tsx +147 -0
- package/src/components/panels/variables/index.tsx +125 -0
- package/src/components/panels/variables/styles.module.css +149 -0
- package/src/components/primitives/icon.module.css +45 -0
- package/src/components/primitives/icon.tsx +38 -0
- package/src/components/sockets/input/index.tsx +83 -0
- package/src/components/sockets/input/styles.module.css +26 -0
- package/src/components/sockets/output/index.tsx +68 -0
- package/src/components/sockets/output/styles.module.css +22 -0
- package/src/css/notes.css +135 -0
- package/src/css/prosemirror.css +57 -0
- package/src/css/rc-dock.css +212 -0
- package/src/css/rc-menu.css +101 -0
- package/src/css/themes/kiberon.css +127 -0
- package/src/css/vars.css +198 -0
- package/src/css/vscode-elements.css +124 -0
- package/src/entry.css +4 -0
- package/src/generators/CallSubgraphGenerator.tsx +136 -0
- package/src/generators/CustomEventOnTriggeredGenerator.tsx +85 -0
- package/src/generators/GraphBoundaryGenerator.module.css +32 -0
- package/src/generators/GraphBoundaryGenerator.tsx +193 -0
- package/src/generators/SequenceGenerator.tsx +104 -0
- package/src/generators/SwitchOnIntegerGenerator.tsx +256 -0
- package/src/generators/SwitchOnStringGenerator.tsx +263 -0
- package/src/generators/callSubgraphSync.ts +126 -0
- package/src/generators/registerDefaultGenerators.ts +55 -0
- package/src/generators/registerDefaults.ts +26 -0
- package/src/hooks/useBehaveGraphFlow.ts +17 -16
- package/src/hooks/useFlowHandlers.ts +154 -30
- package/src/hooks/useWasdPan.ts +210 -0
- package/src/index.css +134 -0
- package/src/index.ts +53 -18
- package/src/manifest/contributionRegistry.ts +93 -0
- package/src/manifest/index.ts +4 -0
- package/src/manifest/loadManifest.ts +82 -0
- package/src/manifest/manifestPlugin.ts +29 -0
- package/src/manifest/passthroughValueType.ts +40 -0
- package/src/plugin/alignment/index.ts +91 -0
- package/src/plugin/autosave/controller.ts +366 -0
- package/src/plugin/autosave/index.tsx +114 -0
- package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
- package/src/plugin/autosave/panel/index.tsx +1 -0
- package/src/plugin/autosave/panel/styles.module.css +56 -0
- package/src/plugin/autosave/settings.ts +65 -0
- package/src/plugin/autosave/storage.ts +147 -0
- package/src/plugin/docs/index.tsx +297 -0
- package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
- package/src/plugin/docs/panel/index.tsx +21 -0
- package/src/plugin/docs/panel/styles.module.css +174 -0
- package/src/plugin/graphrunner/actions.ts +326 -0
- package/src/plugin/graphrunner/buttons.tsx +95 -0
- package/src/plugin/graphrunner/client.ts +707 -0
- package/src/plugin/graphrunner/index.tsx +184 -0
- package/src/plugin/graphrunner/panel.tsx +386 -0
- package/src/plugin/graphrunner/runController.ts +283 -0
- package/src/plugin/graphrunner/runner.ts +187 -0
- package/src/plugin/graphrunner/session.ts +243 -0
- package/src/plugin/graphrunner/store.ts +196 -0
- package/src/plugin/graphrunner/styles.module.css +171 -0
- package/src/plugin/graphrunner/transport.ts +250 -0
- package/src/plugin/graphrunner/types.ts +693 -0
- package/src/plugin/graphrunner-local/execution-utils.ts +637 -0
- package/src/plugin/graphrunner-local/index.tsx +172 -0
- package/src/plugin/graphrunner-local/panel.tsx +187 -0
- package/src/plugin/graphrunner-local/store.ts +41 -0
- package/src/plugin/graphrunner-local/styles.module.css +82 -0
- package/src/plugin/graphrunner-local/transport.ts +1339 -0
- package/src/plugin/graphrunner-local/types.ts +10 -0
- package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +635 -0
- package/src/plugin/graphrunner-webworker/index.tsx +140 -0
- package/src/plugin/graphrunner-webworker/panel.tsx +173 -0
- package/src/plugin/graphrunner-webworker/store.ts +98 -0
- package/src/plugin/graphrunner-webworker/worker-transport.ts +123 -0
- package/src/plugin/kitchen-sink/index.ts +38 -0
- package/src/plugin/layout/dagre.ts +131 -0
- package/src/plugin/layout/elk.ts +216 -0
- package/src/plugin/layout/index.ts +80 -0
- package/src/plugin/notes/FormatToolbar.tsx +200 -0
- package/src/plugin/notes/index.tsx +191 -0
- package/src/plugin/notes/nodeActions.ts +100 -0
- package/src/plugin/notes/note.tsx +20 -0
- package/src/plugin/notes/noteImpl.tsx +89 -0
- package/src/plugin/realtime/realtimeRunner.ts +624 -0
- package/src/specifics/CustomEventOnTriggeredSpecific.tsx +92 -0
- package/src/specifics/CustomEventTriggerSpecific.tsx +141 -0
- package/src/specifics/VariableGetSpecific.tsx +110 -0
- package/src/specifics/VariableSetSpecific.tsx +110 -0
- package/src/store/actions.tsx +698 -0
- package/src/store/commands.ts +278 -0
- package/src/store/contextMenu.ts +192 -0
- package/src/store/controls.tsx +62 -0
- package/src/store/conversions.ts +47 -0
- package/src/store/documentation.tsx +69 -0
- package/src/store/events.tsx +116 -0
- package/src/store/flow.tsx +230 -0
- package/src/store/graphMeta.ts +39 -0
- package/src/store/hotKeys.tsx +364 -0
- package/src/store/layers.ts +259 -0
- package/src/store/legend.tsx +76 -0
- package/src/store/logs.ts +28 -0
- package/src/store/menubar.ts +41 -0
- package/src/store/refs.ts +84 -0
- package/src/store/registry.ts +51 -0
- package/src/store/selection.ts +22 -0
- package/src/store/settings.ts +99 -0
- package/src/store/settingsSchema.ts +210 -0
- package/src/store/socketGenerator.tsx +54 -0
- package/src/store/specific.tsx +75 -0
- package/src/store/specs.tsx +35 -0
- package/src/store/tabs.ts +282 -0
- package/src/store/toolbar.tsx +45 -0
- package/src/store/traces.ts +240 -0
- package/src/store/variables.ts +37 -0
- package/src/system/graph.ts +131 -0
- package/src/system/graphSession.ts +172 -0
- package/src/system/index.ts +6 -0
- package/src/system/notifications.ts +111 -0
- package/src/system/persistence.ts +82 -0
- package/src/system/plugin.ts +55 -0
- package/src/system/provider.tsx +86 -0
- package/src/system/pubsub.ts +323 -0
- package/src/system/system.ts +653 -0
- package/src/system/tabLoader.tsx +303 -0
- package/src/system/undoRedo.ts +103 -0
- package/src/transformers/Uigraph.ts +61 -0
- package/src/transformers/behaveToFlow.ts +16 -4
- package/src/transformers/contract.ts +87 -0
- package/src/transformers/flowToBehave.ts +40 -12
- package/src/types/NodeMetadata.ts +27 -0
- package/src/types/graph.ts +49 -0
- package/src/types/nodes.ts +50 -0
- package/src/types.ts +18 -0
- package/src/util/autoConvert.ts +200 -0
- package/src/util/colors.ts +1 -29
- package/src/util/downloadJson.ts +18 -0
- package/src/util/extractNodeMetadata.ts +16 -0
- package/src/util/getPickerFilters.ts +1 -1
- package/src/util/isBehaveNode.ts +6 -0
- package/src/util/isValidConnection.ts +51 -17
- package/src/util/mergeSockets.ts +29 -0
- package/src/util/serializeVariables.ts +66 -0
- package/src/util/sockets.ts +43 -0
- package/stories/apex/layoutController/example-graph.worker.ts +39 -0
- package/stories/apex/layoutController/index.stories.tsx +48 -0
- package/stories/apex/layoutController/webworker.stories.tsx +103 -0
- package/stories/apex/menubar/menubar.stories.tsx +19 -0
- package/stories/components/colorpicker/index.stories.tsx +20 -0
- package/stories/components/contextMenus/edge.stories.tsx +32 -0
- package/stories/components/contextMenus/node.stories.tsx +26 -0
- package/stories/components/contextMenus/nodePicker.stories.tsx +115 -0
- package/stories/components/controls/any/index.stories.tsx +19 -0
- package/stories/components/controls/boolean/index.stories.tsx +19 -0
- package/stories/components/controls/colorPicker/index.stories.tsx +49 -0
- package/stories/components/controls/number/index.stories.tsx +19 -0
- package/stories/components/controls/string/index.stories.tsx +19 -0
- package/stories/components/nodes/behaveNode.stories.tsx +108 -0
- package/stories/components/panels/alignment.stories.tsx +24 -0
- package/stories/components/panels/events.stories.tsx +38 -0
- package/stories/components/panels/graphRunner.stories.tsx +317 -0
- package/stories/components/panels/history.stories.tsx +37 -0
- package/stories/components/panels/keymaps.stories.tsx +21 -0
- package/stories/components/panels/legend.stories.tsx +37 -0
- package/stories/components/panels/logs.stories.tsx +24 -0
- package/stories/components/panels/nodeInputs.stories.tsx +21 -0
- package/stories/components/panels/nodePicker.stories.tsx +37 -0
- package/stories/components/panels/panel.stories.tsx +39 -0
- package/stories/components/panels/search.stories.tsx +24 -0
- package/stories/components/panels/systemSettings.stories.tsx +26 -0
- package/stories/components/panels/traces.stories.tsx +225 -0
- package/stories/components/panels/variables.stories.tsx +24 -0
- package/stories/defaults/defaultStoryProvider.tsx +170 -0
- package/stories/defaults/systemGenerator.ts +43 -0
- package/stories/plugins/notes.stories.tsx +100 -0
- package/tests/autoConvert.test.ts +329 -0
- package/tests/autosavePlugin.test.ts +204 -0
- package/tests/callSubgraphSync.test.ts +148 -0
- package/tests/commandRegistry.test.ts +137 -0
- package/tests/components/edges/offsetBezier.test.ts +51 -0
- package/tests/components/layoutController/utils.test.ts +68 -0
- package/tests/components/panels/traces/utils.test.ts +52 -0
- package/tests/contract.test.ts +51 -0
- package/tests/contractSerialize.test.ts +62 -0
- package/tests/deriveSpans.test.ts +71 -0
- package/tests/flowToBehave.test.ts +27 -4
- package/tests/hotkeys.test.ts +79 -0
- package/tests/keepAliveLifecycle.test.ts +167 -0
- package/tests/loadManifest.test.ts +113 -0
- package/tests/noteMarkdown.test.ts +65 -0
- package/tests/notesPlugin.test.ts +162 -0
- package/tests/notifications.test.ts +87 -0
- package/tests/persistence.test.ts +51 -0
- package/tests/saveLoad.test.ts +373 -0
- package/tests/settings.test.ts +178 -0
- package/tests/traceStore.test.ts +46 -0
- package/tests/util/calculateNewEdge.test.ts +98 -0
- package/tests/util/getSocketsByNodeTypeAndHandleType.test.ts +31 -0
- package/tests/util/hasPositionMetaData.test.ts +33 -0
- package/tests/util/isBehaveNode.test.ts +22 -0
- package/tests/util/isHandleConnected.test.ts +37 -0
- package/tests/util/mergeSockets.test.ts +43 -0
- package/tests/visual/README.md +64 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-alignment-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-traces-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
- package/tests/visual/panels.visual.test.tsx +76 -0
- package/tests/wasdPan.test.ts +71 -0
- package/tsconfig.base.json +39 -0
- package/tsconfig.json +18 -59
- package/tsconfig.prod.json +23 -0
- package/tsdown.config.ts +15 -3
- package/typedoc.json +7 -7
- package/vite.config.js +7 -0
- package/vitest.config.ts +5 -2
- package/vitest.visual.config.ts +55 -0
- package/src/components/AutoSizeInput.tsx +0 -65
- package/src/components/Controls.tsx +0 -87
- package/src/components/InputSocket.tsx +0 -142
- package/src/components/Node.tsx +0 -68
- package/src/components/NodeContainer.tsx +0 -46
- package/src/components/NodePicker.tsx +0 -77
- package/src/components/OutputSocket.tsx +0 -58
- package/src/components/modals/ClearModal.tsx +0 -40
- package/src/components/modals/HelpModal.tsx +0 -36
- package/src/components/modals/LoadModal.tsx +0 -96
- package/src/components/modals/Modal.tsx +0 -64
- package/src/components/modals/SaveModal.tsx +0 -60
- package/src/hooks/useCustomNodeTypes.tsx +0 -31
- package/src/hooks/useGraphRunner.ts +0 -104
- package/src/hooks/useMergeMap.ts +0 -14
- package/src/hooks/useNodeSpecJson.ts +0 -20
- package/src/hooks/useQueriableDefinitions.ts +0 -22
- package/src/styles.css +0 -8
- package/tailwind.config.ts +0 -19
- package/tests/tsconfig.json +0 -10
- /package/src/{types.d.ts → types-declarations.d.ts} +0 -0
|
@@ -0,0 +1,624 @@
|
|
|
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
|
+
await this.evaluateNodeForPreview(nodeId);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Evaluate every node that a UI consumer is actively watching.
|
|
434
|
+
*
|
|
435
|
+
* Pure function graphs (e.g. the image nodes) never run during
|
|
436
|
+
* `executeAllSync()` because nothing pushes a flow fiber through them. They
|
|
437
|
+
* are normally pulled lazily when a downstream flow/event node reads their
|
|
438
|
+
* value. In the editor preview there is no such consumer, so the act of a
|
|
439
|
+
* component calling `watchNodeOutput()` is what drives evaluation: we resolve
|
|
440
|
+
* the node's upstream function graph and execute the node itself so its
|
|
441
|
+
* sockets hold a fresh value for `publishWatchedOutputs()` to read.
|
|
442
|
+
*
|
|
443
|
+
* This intentionally does not depend on the `ui.realtime` annotation , any
|
|
444
|
+
* watched output is evaluated.
|
|
445
|
+
*/
|
|
446
|
+
private async recalculateWatchedOutputs(): Promise<void> {
|
|
447
|
+
if (!this.engine) return;
|
|
448
|
+
if (this.watched.size === 0) return;
|
|
449
|
+
|
|
450
|
+
for (const nodeId of this.watched.keys()) {
|
|
451
|
+
await this.evaluateNodeForPreview(nodeId);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Resolve a single function node's inputs (recursively executing its upstream
|
|
457
|
+
* function graph) and execute it. Non-function nodes keep whatever value their
|
|
458
|
+
* last real execution produced. Resilient: a failure on one node is logged and
|
|
459
|
+
* does not abort evaluation of the others.
|
|
460
|
+
*/
|
|
461
|
+
private async evaluateNodeForPreview(nodeId: string): Promise<void> {
|
|
462
|
+
if (!this.engine) return;
|
|
463
|
+
const node = this.engine.nodes?.[nodeId];
|
|
464
|
+
if (!node) return;
|
|
465
|
+
if (!isFunctionNode(node)) return;
|
|
466
|
+
|
|
467
|
+
try {
|
|
468
|
+
let executionSteps = 0;
|
|
469
|
+
for (const inputSocket of node.inputs) {
|
|
470
|
+
executionSteps += await this.resolveSocketValueForPreview(inputSocket);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
this.engine.onNodeExecutionStart.emit(node);
|
|
474
|
+
await node.exec(node);
|
|
475
|
+
executionSteps++;
|
|
476
|
+
this.engine.onNodeExecutionEnd.emit(node);
|
|
477
|
+
|
|
478
|
+
this.engine.executionSteps += executionSteps;
|
|
479
|
+
} catch (err) {
|
|
480
|
+
console.error(
|
|
481
|
+
`RealtimeRunner: failed to evaluate watched node ${nodeId}:`,
|
|
482
|
+
err
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private rebuildEngine(): void {
|
|
488
|
+
const specJson = this.system.specStore.getState().specs;
|
|
489
|
+
if (!specJson || specJson.length === 0) return;
|
|
490
|
+
|
|
491
|
+
const nodes = this.system.nodeStore.getState().nodes;
|
|
492
|
+
const edges = this.system.edgeStore.getState().edges;
|
|
493
|
+
|
|
494
|
+
const rawGraphJson = flowToBehave(
|
|
495
|
+
this.system.session!,
|
|
496
|
+
nodes,
|
|
497
|
+
edges,
|
|
498
|
+
specJson
|
|
499
|
+
);
|
|
500
|
+
const graphWithAnnotations = this.mergeNodeAnnotationsIntoMetadata(
|
|
501
|
+
rawGraphJson,
|
|
502
|
+
nodes
|
|
503
|
+
);
|
|
504
|
+
const graphJson = this.stripNonSemanticMetadata(graphWithAnnotations);
|
|
505
|
+
|
|
506
|
+
const signature = this.computeGraphSignature(graphJson);
|
|
507
|
+
if (this.lastGraphSignature === signature && this.engine) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
this.lastGraphSignature = signature;
|
|
511
|
+
|
|
512
|
+
if (!this.executionRegistry) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
this.graphInstance = readGraphFromJSON({
|
|
517
|
+
graphJson,
|
|
518
|
+
registry: this.executionRegistry
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
if (this.engine) {
|
|
522
|
+
this.engine.dispose();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
this.engine = new Engine(this.graphInstance, this.executionRegistry);
|
|
526
|
+
|
|
527
|
+
this.engine.onNodeExecutionError.addListener(({ error }) => {
|
|
528
|
+
console.error(error);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
this.store.getState().setEngine(this.engine);
|
|
532
|
+
|
|
533
|
+
this.rebuildAnnotatedOutputNodeCache();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private publishWatchedOutputs(): void {
|
|
537
|
+
if (!this.engine) return;
|
|
538
|
+
if (this.watched.size === 0) return;
|
|
539
|
+
|
|
540
|
+
const updates: Array<{
|
|
541
|
+
nodeId: string;
|
|
542
|
+
outputName: string;
|
|
543
|
+
value: unknown;
|
|
544
|
+
}> = [];
|
|
545
|
+
|
|
546
|
+
for (const [nodeId, outputs] of this.watched.entries()) {
|
|
547
|
+
const node = this.engine.nodes?.[nodeId];
|
|
548
|
+
if (!node) continue;
|
|
549
|
+
|
|
550
|
+
let lastForNode = this.lastPublished.get(nodeId);
|
|
551
|
+
if (!lastForNode) {
|
|
552
|
+
lastForNode = new Map<string, unknown>();
|
|
553
|
+
this.lastPublished.set(nodeId, lastForNode);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
for (const outputName of outputs.values()) {
|
|
557
|
+
const outputSocket = node.outputs?.find(
|
|
558
|
+
(s: any) => s.name === outputName
|
|
559
|
+
);
|
|
560
|
+
const inputSocket = outputSocket
|
|
561
|
+
? undefined
|
|
562
|
+
: node.inputs?.find((s: any) => s.name === outputName);
|
|
563
|
+
|
|
564
|
+
const value = (outputSocket ?? inputSocket)?.value;
|
|
565
|
+
const prev = lastForNode.get(outputName);
|
|
566
|
+
if (Object.is(prev, value)) continue;
|
|
567
|
+
|
|
568
|
+
lastForNode.set(outputName, value);
|
|
569
|
+
updates.push({ nodeId, outputName, value });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (updates.length > 0) {
|
|
574
|
+
this.store.getState().setOutputs(updates);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private async evaluate(): Promise<void> {
|
|
579
|
+
try {
|
|
580
|
+
this.rebuildEngine();
|
|
581
|
+
if (!this.engine) return;
|
|
582
|
+
|
|
583
|
+
// Compute function/flow nodes immediately.
|
|
584
|
+
await this.engine.executeAllSync();
|
|
585
|
+
|
|
586
|
+
// Recalculate annotated output nodes to force evaluation of upstream function graphs.
|
|
587
|
+
await this.recalculateAnnotatedOutputNodes();
|
|
588
|
+
|
|
589
|
+
// Pull every watched output (e.g. live image node previews) so pure
|
|
590
|
+
// function graphs are evaluated even without a flow consumer.
|
|
591
|
+
await this.recalculateWatchedOutputs();
|
|
592
|
+
|
|
593
|
+
this.publishWatchedOutputs();
|
|
594
|
+
} catch (err) {
|
|
595
|
+
// Keep preview runner resilient; don't crash the editor.
|
|
596
|
+
console.error('RealtimeRunner evaluate failed:', err);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private async tick(): Promise<void> {
|
|
601
|
+
if (!this.engine) return;
|
|
602
|
+
if (this.watched.size === 0) return;
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
const eventEmitter = this.executionRegistry?.dependencies
|
|
606
|
+
?.ILifecycleEventEmitter as ILifecycleEventEmitter | undefined;
|
|
607
|
+
|
|
608
|
+
// Tick time-based graphs.
|
|
609
|
+
eventEmitter?.tickEvent?.emit();
|
|
610
|
+
|
|
611
|
+
await this.engine.executeAllSync();
|
|
612
|
+
// Recalculate annotated output nodes to force evaluation of upstream function graphs.
|
|
613
|
+
await this.recalculateAnnotatedOutputNodes();
|
|
614
|
+
|
|
615
|
+
// Pull every watched output (e.g. live image node previews) so pure
|
|
616
|
+
// function graphs are evaluated even without a flow consumer.
|
|
617
|
+
await this.recalculateWatchedOutputs();
|
|
618
|
+
|
|
619
|
+
this.publishWatchedOutputs();
|
|
620
|
+
} catch {
|
|
621
|
+
// ignore
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
@@ -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 { useGraph } 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 = useGraph();
|
|
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
|
+
};
|