project-graph-mcp 2.2.6 → 2.3.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/ARCHITECTURE.md +81 -0
- package/CHANGELOG.md +57 -0
- package/README.md +9 -4
- package/package.json +6 -13
- package/src/compact/expand.js +1 -1
- package/src/core/graph-builder.js +2 -2
- package/src/core/parser.js +2 -2
- package/src/network/server.js +1 -2
- package/vendor/symbiote-node/CHANGELOG.md +31 -0
- package/vendor/symbiote-node/LICENSE +21 -0
- package/vendor/symbiote-node/README.md +206 -0
- package/vendor/symbiote-node/canvas/AutoLayout.js +725 -0
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +73 -0
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +93 -0
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +9 -0
- package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +962 -0
- package/vendor/symbiote-node/canvas/ConnectionRenderer.js +1468 -0
- package/vendor/symbiote-node/canvas/FlowSimulator.js +323 -0
- package/vendor/symbiote-node/canvas/ForceLayout.js +189 -0
- package/vendor/symbiote-node/canvas/ForceWorker.js +1325 -0
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +97 -0
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +176 -0
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +12 -0
- package/vendor/symbiote-node/canvas/LODManager.js +88 -0
- package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +71 -0
- package/vendor/symbiote-node/canvas/Minimap/Minimap.js +207 -0
- package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +9 -0
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +261 -0
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +1840 -0
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +22 -0
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +97 -0
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +132 -0
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +21 -0
- package/vendor/symbiote-node/canvas/NodeViewManager.js +584 -0
- package/vendor/symbiote-node/canvas/PinExpansion.js +131 -0
- package/vendor/symbiote-node/canvas/PseudoConnection.js +80 -0
- package/vendor/symbiote-node/canvas/SubgraphManager.js +201 -0
- package/vendor/symbiote-node/canvas/SubgraphRouter.js +443 -0
- package/vendor/symbiote-node/canvas/ViewportActions.js +446 -0
- package/vendor/symbiote-node/core/Connection.js +45 -0
- package/vendor/symbiote-node/core/Editor.js +451 -0
- package/vendor/symbiote-node/core/Frame.js +31 -0
- package/vendor/symbiote-node/core/GraphMermaid.js +348 -0
- package/vendor/symbiote-node/core/GraphText.js +210 -0
- package/vendor/symbiote-node/core/Node.js +143 -0
- package/vendor/symbiote-node/core/Portal.js +104 -0
- package/vendor/symbiote-node/core/Socket.js +185 -0
- package/vendor/symbiote-node/core/SubgraphNode.js +125 -0
- package/vendor/symbiote-node/index.js +103 -0
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +361 -0
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +332 -0
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +96 -0
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +104 -0
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +133 -0
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +33 -0
- package/vendor/symbiote-node/interactions/ConnectFlow.js +307 -0
- package/vendor/symbiote-node/interactions/Drag.js +102 -0
- package/vendor/symbiote-node/interactions/Selector.js +132 -0
- package/vendor/symbiote-node/interactions/SnapGrid.js +65 -0
- package/vendor/symbiote-node/interactions/Zoom.js +140 -0
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +88 -0
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +254 -0
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +11 -0
- package/vendor/symbiote-node/layout/Layout/Layout.css.js +88 -0
- package/vendor/symbiote-node/layout/Layout/Layout.js +622 -0
- package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +25 -0
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +293 -0
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +467 -0
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +33 -0
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +46 -0
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +102 -0
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +6 -0
- package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +156 -0
- package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +250 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +379 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +263 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +20 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +183 -0
- package/vendor/symbiote-node/layout/LayoutTree.js +246 -0
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +43 -0
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +89 -0
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +14 -0
- package/vendor/symbiote-node/layout/index.js +16 -0
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +61 -0
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +79 -0
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +19 -0
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +41 -0
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +24 -0
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +16 -0
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +65 -0
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +29 -0
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +13 -0
- package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +683 -0
- package/vendor/symbiote-node/node/GraphNode/GraphNode.js +92 -0
- package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +17 -0
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +25 -0
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +7 -0
- package/vendor/symbiote-node/node/PortItem/PortItem.css.js +90 -0
- package/vendor/symbiote-node/node/PortItem/PortItem.js +87 -0
- package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +10 -0
- package/vendor/symbiote-node/package.json +59 -0
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +143 -0
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +131 -0
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +16 -0
- package/vendor/symbiote-node/plugins/History.js +384 -0
- package/vendor/symbiote-node/plugins/Readonly.js +59 -0
- package/vendor/symbiote-node/shapes/CircleShape.js +80 -0
- package/vendor/symbiote-node/shapes/CommentShape.js +35 -0
- package/vendor/symbiote-node/shapes/DiamondShape.js +115 -0
- package/vendor/symbiote-node/shapes/NodeShape.js +80 -0
- package/vendor/symbiote-node/shapes/PillShape.js +91 -0
- package/vendor/symbiote-node/shapes/RectShape.js +72 -0
- package/vendor/symbiote-node/shapes/SVGShape.js +494 -0
- package/vendor/symbiote-node/shapes/index.js +53 -0
- package/vendor/symbiote-node/themes/Palette.js +32 -0
- package/vendor/symbiote-node/themes/Skin.js +113 -0
- package/vendor/symbiote-node/themes/Theme.js +84 -0
- package/vendor/symbiote-node/themes/carbon.js +137 -0
- package/vendor/symbiote-node/themes/dark.js +137 -0
- package/vendor/symbiote-node/themes/ebook.js +138 -0
- package/vendor/symbiote-node/themes/grey.js +137 -0
- package/vendor/symbiote-node/themes/light.js +137 -0
- package/vendor/symbiote-node/themes/neon.js +138 -0
- package/vendor/symbiote-node/themes/pcb.js +273 -0
- package/vendor/symbiote-node/themes/synthwave.js +137 -0
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +86 -0
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +128 -0
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +29 -0
- package/web/app.js +6 -5
- package/web/components/canvas-graph.js +1666 -0
- package/web/components/event-feed/CodeWidget.js +32 -0
- package/web/components/event-feed/EventWidget.js +97 -0
- package/web/components/event-feed/ListWidget.js +57 -0
- package/web/components/event-feed/MiniGraphWidget.js +69 -0
- package/web/dashboard.js +1 -1
- package/web/index.html +4 -0
- package/web/panels/ActionBoard/ActionBoard.js +1 -1
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -1
- package/web/panels/code-viewer.js +50 -15
- package/web/panels/dep-graph.js +2712 -7
- package/web/panels/file-tree.js +5 -2
- package/web/panels/live-monitor.js +75 -3
- package/web/style.css +33 -0
- package/docs/img/explorer-compact.jpg +0 -0
- package/docs/img/explorer-expanded.jpg +0 -0
- package/src/.contextignore +0 -22
- package/src/.project-graph-cache.json +0 -1
- package/src/compact/.project-graph-cache.json +0 -1
- package/web/.project-graph-cache.json +0 -1
- package/web/panels/SettingsPanel/.project-graph-cache.json +0 -1
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NodeEditor — central graph manager with event system
|
|
3
|
+
*
|
|
4
|
+
* Provides CRUD operations for nodes and connections with
|
|
5
|
+
* pre/post event hooks via callback-based event emitter.
|
|
6
|
+
* Replaces Rete.js Scope/Signal with simpler emit pattern.
|
|
7
|
+
*
|
|
8
|
+
* @module symbiote-node/core/Editor
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Connection } from './Connection.js';
|
|
12
|
+
import { Node } from './Node.js';
|
|
13
|
+
import { Socket, Input, Output, InputControl } from './Socket.js';
|
|
14
|
+
import { Frame } from './Frame.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {'nodecreate'|'nodecreated'|'noderemove'|'noderemoved'|
|
|
18
|
+
* 'connectioncreate'|'connectioncreated'|'connectionremove'|'connectionremoved'|
|
|
19
|
+
* 'framecreate'|'framecreated'|'frameremove'|'frameremoved'|
|
|
20
|
+
* 'clear'|'cleared'|'nodeselect'|'nodedeselect'} EditorEvent
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export class NodeEditor {
|
|
24
|
+
constructor() {
|
|
25
|
+
/** @type {Map<string, import('./Node.js').Node>} */
|
|
26
|
+
this.nodes = new Map();
|
|
27
|
+
|
|
28
|
+
/** @type {Map<string, Connection>} */
|
|
29
|
+
this.connections = new Map();
|
|
30
|
+
|
|
31
|
+
/** @type {Map<string, import('./Frame.js').Frame>} */
|
|
32
|
+
this.frames = new Map();
|
|
33
|
+
|
|
34
|
+
/** @type {Object<string, Set<function>>} */
|
|
35
|
+
this._listeners = {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Event System ---
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Subscribe to editor event
|
|
42
|
+
* @param {EditorEvent} event
|
|
43
|
+
* @param {function} handler
|
|
44
|
+
* @returns {function} Unsubscribe function
|
|
45
|
+
*/
|
|
46
|
+
on(event, handler) {
|
|
47
|
+
if (!this._listeners[event]) {
|
|
48
|
+
this._listeners[event] = new Set();
|
|
49
|
+
}
|
|
50
|
+
this._listeners[event].add(handler);
|
|
51
|
+
return () => this._listeners[event].delete(handler);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Emit event to all listeners
|
|
56
|
+
* @param {EditorEvent} event
|
|
57
|
+
* @param {*} data
|
|
58
|
+
* @returns {boolean} - false if any listener returned false (cancel)
|
|
59
|
+
*/
|
|
60
|
+
emit(event, data) {
|
|
61
|
+
const handlers = this._listeners[event];
|
|
62
|
+
if (!handlers) return true;
|
|
63
|
+
for (const handler of handlers) {
|
|
64
|
+
if (handler(data) === false) return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Remove all event listeners (for clean teardown)
|
|
71
|
+
*/
|
|
72
|
+
removeAllListeners() {
|
|
73
|
+
this._listeners = {};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- Node CRUD ---
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get node by ID
|
|
80
|
+
* @param {string} id
|
|
81
|
+
* @returns {import('./Node.js').Node|undefined}
|
|
82
|
+
*/
|
|
83
|
+
getNode(id) {
|
|
84
|
+
return this.nodes.get(id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get all nodes
|
|
89
|
+
* @returns {import('./Node.js').Node[]}
|
|
90
|
+
*/
|
|
91
|
+
getNodes() {
|
|
92
|
+
return [...this.nodes.values()];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Add node to editor
|
|
97
|
+
* @param {import('./Node.js').Node} node
|
|
98
|
+
* @returns {boolean}
|
|
99
|
+
*/
|
|
100
|
+
addNode(node) {
|
|
101
|
+
if (this.nodes.has(node.id)) throw new Error('node already added');
|
|
102
|
+
if (!this.emit('nodecreate', node)) return false;
|
|
103
|
+
this.nodes.set(node.id, node);
|
|
104
|
+
this.emit('nodecreated', node);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Remove node and all its connections
|
|
110
|
+
* @param {string} id
|
|
111
|
+
* @returns {boolean}
|
|
112
|
+
*/
|
|
113
|
+
removeNode(id) {
|
|
114
|
+
const node = this.nodes.get(id);
|
|
115
|
+
if (!node) throw new Error('node not found');
|
|
116
|
+
if (!this.emit('noderemove', node)) return false;
|
|
117
|
+
|
|
118
|
+
// Remove all connections to/from this node
|
|
119
|
+
for (const [connId, conn] of this.connections) {
|
|
120
|
+
if (conn.from === id || conn.to === id) {
|
|
121
|
+
this.removeConnection(connId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.nodes.delete(id);
|
|
126
|
+
this.emit('noderemoved', node);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- Connection CRUD ---
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get connection by ID
|
|
134
|
+
* @param {string} id
|
|
135
|
+
* @returns {Connection|undefined}
|
|
136
|
+
*/
|
|
137
|
+
getConnection(id) {
|
|
138
|
+
return this.connections.get(id);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get all connections
|
|
143
|
+
* @returns {Connection[]}
|
|
144
|
+
*/
|
|
145
|
+
getConnections() {
|
|
146
|
+
return [...this.connections.values()];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get connections for a specific node
|
|
151
|
+
* @param {string} nodeId
|
|
152
|
+
* @returns {Connection[]}
|
|
153
|
+
*/
|
|
154
|
+
getNodeConnections(nodeId) {
|
|
155
|
+
return this.getConnections().filter(c => c.from === nodeId || c.to === nodeId);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Add connection
|
|
160
|
+
* @param {Connection} connection
|
|
161
|
+
* @returns {boolean}
|
|
162
|
+
*/
|
|
163
|
+
addConnection(connection) {
|
|
164
|
+
if (this.connections.has(connection.id)) throw new Error('connection already added');
|
|
165
|
+
if (!this.emit('connectioncreate', connection)) return false;
|
|
166
|
+
this.connections.set(connection.id, connection);
|
|
167
|
+
this.emit('connectioncreated', connection);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Remove connection
|
|
173
|
+
* @param {string} id
|
|
174
|
+
* @returns {boolean}
|
|
175
|
+
*/
|
|
176
|
+
removeConnection(id) {
|
|
177
|
+
const conn = this.connections.get(id);
|
|
178
|
+
if (!conn) return false;
|
|
179
|
+
if (!this.emit('connectionremove', conn)) return false;
|
|
180
|
+
this.connections.delete(id);
|
|
181
|
+
this.emit('connectionremoved', conn);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- Bulk Operations ---
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Clear all nodes and connections
|
|
189
|
+
* @returns {boolean}
|
|
190
|
+
*/
|
|
191
|
+
clear() {
|
|
192
|
+
if (!this.emit('clear', null)) return false;
|
|
193
|
+
for (const id of [...this.connections.keys()]) {
|
|
194
|
+
this.removeConnection(id);
|
|
195
|
+
}
|
|
196
|
+
for (const id of [...this.nodes.keys()]) {
|
|
197
|
+
this.removeNode(id);
|
|
198
|
+
}
|
|
199
|
+
this.emit('cleared', null);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// --- Frame CRUD ---
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get frame by ID
|
|
207
|
+
* @param {string} id
|
|
208
|
+
* @returns {import('./Frame.js').Frame|undefined}
|
|
209
|
+
*/
|
|
210
|
+
getFrame(id) {
|
|
211
|
+
return this.frames.get(id);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get all frames
|
|
216
|
+
* @returns {import('./Frame.js').Frame[]}
|
|
217
|
+
*/
|
|
218
|
+
getFrames() {
|
|
219
|
+
return [...this.frames.values()];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Add frame
|
|
224
|
+
* @param {import('./Frame.js').Frame} frame
|
|
225
|
+
* @returns {boolean}
|
|
226
|
+
*/
|
|
227
|
+
addFrame(frame) {
|
|
228
|
+
if (this.frames.has(frame.id)) throw new Error('frame already added');
|
|
229
|
+
if (!this.emit('framecreate', frame)) return false;
|
|
230
|
+
this.frames.set(frame.id, frame);
|
|
231
|
+
this.emit('framecreated', frame);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Remove frame
|
|
237
|
+
* @param {string} id
|
|
238
|
+
* @returns {boolean}
|
|
239
|
+
*/
|
|
240
|
+
removeFrame(id) {
|
|
241
|
+
const frame = this.frames.get(id);
|
|
242
|
+
if (!frame) return false;
|
|
243
|
+
if (!this.emit('frameremove', frame)) return false;
|
|
244
|
+
this.frames.delete(id);
|
|
245
|
+
this.emit('frameremoved', frame);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// --- Serialization (agi-graph isomorphic bridge) ---
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Serialize editor state to agi-graph workflow JSON format.
|
|
253
|
+
* Output is directly compatible with engine/Graph.fromJSON().
|
|
254
|
+
* @param {Object<string, number[]>} [positions] - Node positions {nodeId: [x, y]}
|
|
255
|
+
* @returns {object} Workflow JSON
|
|
256
|
+
*/
|
|
257
|
+
toJSON(positions = {}) {
|
|
258
|
+
return {
|
|
259
|
+
version: 1,
|
|
260
|
+
nodes: this.getNodes().map((n) => {
|
|
261
|
+
const obj = {
|
|
262
|
+
id: n.id,
|
|
263
|
+
type: n.type,
|
|
264
|
+
name: n.label,
|
|
265
|
+
params: { ...n.params },
|
|
266
|
+
};
|
|
267
|
+
if (n.category && n.category !== 'default') obj.category = n.category;
|
|
268
|
+
if (n.shape && n.shape !== 'rect') obj.shape = n.shape;
|
|
269
|
+
if (n.icon) obj.icon = n.icon;
|
|
270
|
+
if (n.cacheMode && n.cacheMode !== 'auto') obj.cacheMode = n.cacheMode;
|
|
271
|
+
|
|
272
|
+
// Serialize port definitions for round-trip
|
|
273
|
+
const inputs = Object.entries(n.inputs);
|
|
274
|
+
if (inputs.length > 0) {
|
|
275
|
+
obj.inputs = inputs.map(([key, inp]) => ({
|
|
276
|
+
name: key,
|
|
277
|
+
type: inp.socket?.name || 'any',
|
|
278
|
+
label: inp.label || '',
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
const outputs = Object.entries(n.outputs);
|
|
282
|
+
if (outputs.length > 0) {
|
|
283
|
+
obj.outputs = outputs.map(([key, out]) => ({
|
|
284
|
+
name: key,
|
|
285
|
+
type: out.socket?.name || 'any',
|
|
286
|
+
label: out.label || '',
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
return obj;
|
|
290
|
+
}),
|
|
291
|
+
connections: this.getConnections().map((c) => ({
|
|
292
|
+
id: c.id,
|
|
293
|
+
from: c.from,
|
|
294
|
+
out: c.out,
|
|
295
|
+
to: c.to,
|
|
296
|
+
in: c.in,
|
|
297
|
+
})),
|
|
298
|
+
frames: this.getFrames().map((f) => ({
|
|
299
|
+
id: f.id,
|
|
300
|
+
label: f.label,
|
|
301
|
+
x: f.x,
|
|
302
|
+
y: f.y,
|
|
303
|
+
width: f.width,
|
|
304
|
+
height: f.height,
|
|
305
|
+
color: f.color,
|
|
306
|
+
})),
|
|
307
|
+
ui: {
|
|
308
|
+
positions: { ...positions },
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Reconstruct editor state from agi-graph workflow JSON.
|
|
315
|
+
* Enables round-trip: Editor → toJSON → fromJSON → Editor.
|
|
316
|
+
* Also accepts output from engine/Graph.toJSON().
|
|
317
|
+
* @param {object} data - Workflow JSON
|
|
318
|
+
* @param {Object<string, number[]>} [positionsOut] - Optional object to populate with positions
|
|
319
|
+
* @returns {NodeEditor} this
|
|
320
|
+
*/
|
|
321
|
+
fromJSON(data, positionsOut) {
|
|
322
|
+
// Clear existing state (bypass events for bulk load)
|
|
323
|
+
this.nodes.clear();
|
|
324
|
+
this.connections.clear();
|
|
325
|
+
this.frames.clear();
|
|
326
|
+
|
|
327
|
+
// Restore nodes
|
|
328
|
+
for (const nd of (data.nodes || [])) {
|
|
329
|
+
const node = new Node(nd.name || nd.type, {
|
|
330
|
+
id: nd.id,
|
|
331
|
+
type: nd.type,
|
|
332
|
+
category: nd.category || 'default',
|
|
333
|
+
shape: nd.shape || 'rect',
|
|
334
|
+
icon: nd.icon || '',
|
|
335
|
+
});
|
|
336
|
+
node.params = { ...nd.params };
|
|
337
|
+
if (nd.cacheMode) node.cacheMode = nd.cacheMode;
|
|
338
|
+
|
|
339
|
+
// Auto-create InputControls from params for Inspector display
|
|
340
|
+
// Merge driver defaults into params (fills missing keys from handler definitions)
|
|
341
|
+
const driverParams = nd.driver?.params;
|
|
342
|
+
if (driverParams && !nd.params) nd.params = {};
|
|
343
|
+
if (driverParams) {
|
|
344
|
+
for (const [key, def] of Object.entries(driverParams)) {
|
|
345
|
+
if (!(key in nd.params) && def.default !== undefined) {
|
|
346
|
+
nd.params[key] = def.default;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (nd.params) {
|
|
352
|
+
for (const [key, value] of Object.entries(nd.params)) {
|
|
353
|
+
/** @type {'text'|'number'|'textarea'|'select'|'boolean'} */
|
|
354
|
+
let controlType = 'text';
|
|
355
|
+
let displayValue = value;
|
|
356
|
+
let controlOptions = [];
|
|
357
|
+
|
|
358
|
+
// Use driver metadata for richer control type detection
|
|
359
|
+
const paramMeta = driverParams?.[key];
|
|
360
|
+
if (paramMeta?.type === 'boolean' || typeof value === 'boolean') {
|
|
361
|
+
controlType = 'boolean';
|
|
362
|
+
} else if (paramMeta?.type === 'number' || typeof value === 'number') {
|
|
363
|
+
controlType = 'number';
|
|
364
|
+
} else if (typeof value === 'string' && value.includes('\n')) {
|
|
365
|
+
controlType = 'textarea';
|
|
366
|
+
} else if (typeof value === 'object') {
|
|
367
|
+
controlType = 'textarea';
|
|
368
|
+
displayValue = JSON.stringify(value, null, 2);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Select type from driver options
|
|
372
|
+
if (paramMeta?.options) {
|
|
373
|
+
controlType = 'select';
|
|
374
|
+
controlOptions = paramMeta.options;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
node.addControl(key, new InputControl(controlType, {
|
|
378
|
+
initial: displayValue,
|
|
379
|
+
label: paramMeta?.description || key,
|
|
380
|
+
options: controlOptions,
|
|
381
|
+
}));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Restore ports from serialized definitions
|
|
386
|
+
if (nd.inputs) {
|
|
387
|
+
for (const inp of nd.inputs) {
|
|
388
|
+
node.addInput(inp.name, new Input(new Socket(inp.type || 'any'), inp.label || ''));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (nd.outputs) {
|
|
392
|
+
for (const out of nd.outputs) {
|
|
393
|
+
node.addOutput(out.name, new Output(new Socket(out.type || 'any'), out.label || ''));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
this.nodes.set(node.id, node);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Restore connections
|
|
401
|
+
for (const cd of (data.connections || [])) {
|
|
402
|
+
const srcNode = this.nodes.get(cd.from);
|
|
403
|
+
const tgtNode = this.nodes.get(cd.to);
|
|
404
|
+
if (!srcNode || !tgtNode) continue;
|
|
405
|
+
|
|
406
|
+
// Ensure ports exist (auto-create if coming from engine format without port defs)
|
|
407
|
+
if (!srcNode.outputs[cd.out]) {
|
|
408
|
+
srcNode.addOutput(cd.out, new Output(new Socket('any'), cd.out));
|
|
409
|
+
}
|
|
410
|
+
if (!tgtNode.inputs[cd.in]) {
|
|
411
|
+
tgtNode.addInput(cd.in, new Input(new Socket('any'), cd.in));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const conn = new Connection(srcNode, cd.out, tgtNode, cd.in);
|
|
415
|
+
if (cd.id) conn.id = cd.id;
|
|
416
|
+
this.connections.set(conn.id, conn);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Restore frames
|
|
420
|
+
for (const fd of (data.frames || [])) {
|
|
421
|
+
const frame = new Frame(fd.label, {
|
|
422
|
+
id: fd.id,
|
|
423
|
+
x: fd.x,
|
|
424
|
+
y: fd.y,
|
|
425
|
+
width: fd.width,
|
|
426
|
+
height: fd.height,
|
|
427
|
+
color: fd.color,
|
|
428
|
+
});
|
|
429
|
+
this.frames.set(frame.id, frame);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Extract positions
|
|
433
|
+
if (positionsOut && data.ui?.positions) {
|
|
434
|
+
Object.assign(positionsOut, data.ui.positions);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return this;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Convert editor state to an engine Graph instance for server-side execution.
|
|
442
|
+
* The Graph can be passed directly to Executor.run().
|
|
443
|
+
* @param {Object<string, number[]>} [positions] - Node positions
|
|
444
|
+
* @returns {import('../engine/Graph.js').Graph}
|
|
445
|
+
*/
|
|
446
|
+
async toGraph(positions = {}) {
|
|
447
|
+
const { Graph } = await import('../engine/Graph.js');
|
|
448
|
+
const json = this.toJSON(positions);
|
|
449
|
+
return new Graph(json);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame — visual grouping rectangle for nodes
|
|
3
|
+
*
|
|
4
|
+
* Pure data class (like Node). Stores position, size, color, label.
|
|
5
|
+
* Frame does not own nodes — containment is determined by spatial overlap.
|
|
6
|
+
*
|
|
7
|
+
* @module symbiote-node/core/Frame
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { uid } from './Socket.js';
|
|
11
|
+
|
|
12
|
+
export class Frame {
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} label - Display label
|
|
15
|
+
* @param {object} [options]
|
|
16
|
+
* @param {string} [options.color='#4a9eff'] - Border/header color
|
|
17
|
+
* @param {number} [options.x=0] - X position
|
|
18
|
+
* @param {number} [options.y=0] - Y position
|
|
19
|
+
* @param {number} [options.width=400] - Width
|
|
20
|
+
* @param {number} [options.height=300] - Height
|
|
21
|
+
*/
|
|
22
|
+
constructor(label, options = {}) {
|
|
23
|
+
this.id = options.id || uid();
|
|
24
|
+
this.label = label;
|
|
25
|
+
this.color = options.color || '#4a9eff';
|
|
26
|
+
this.x = options.x || 0;
|
|
27
|
+
this.y = options.y || 0;
|
|
28
|
+
this.width = options.width || 400;
|
|
29
|
+
this.height = options.height || 300;
|
|
30
|
+
}
|
|
31
|
+
}
|