@pipelex/mthds-ui 0.5.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/LICENSE +21 -0
- package/README.md +299 -0
- package/dist/chunk-CCUSQM3E.js +1155 -0
- package/dist/chunk-CCUSQM3E.js.map +1 -0
- package/dist/chunk-DDAAVRWG.js +25 -0
- package/dist/chunk-DDAAVRWG.js.map +1 -0
- package/dist/chunk-IX35IG2I.js +1 -0
- package/dist/chunk-IX35IG2I.js.map +1 -0
- package/dist/graph/index.d.ts +95 -0
- package/dist/graph/index.js +73 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/react/detail/DetailPanel.css +525 -0
- package/dist/graph/react/graph-core.css +394 -0
- package/dist/graph/react/index.css +753 -0
- package/dist/graph/react/index.css.map +1 -0
- package/dist/graph/react/index.d.ts +252 -0
- package/dist/graph/react/index.js +2207 -0
- package/dist/graph/react/index.js.map +1 -0
- package/dist/graph/react/stuff/StuffViewer.css +284 -0
- package/dist/graph/react/viewer/GraphToolbar.css +61 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/shiki/index.d.ts +13 -0
- package/dist/shiki/index.js +1333 -0
- package/dist/shiki/index.js.map +1 -0
- package/dist/types-bV8F_WoM.d.ts +366 -0
- package/package.json +109 -0
|
@@ -0,0 +1,1155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__spreadProps,
|
|
3
|
+
__spreadValues
|
|
4
|
+
} from "./chunk-DDAAVRWG.js";
|
|
5
|
+
|
|
6
|
+
// src/graph/types.ts
|
|
7
|
+
var NODE_TYPE_PIPE_CARD = "pipeCard";
|
|
8
|
+
var NODE_TYPE_STUFF = "default";
|
|
9
|
+
var NODE_TYPE_CONTROLLER = "controllerGroup";
|
|
10
|
+
var STUFF_ID_PREFIX = "stuff_";
|
|
11
|
+
function stuffNodeId(digest) {
|
|
12
|
+
return STUFF_ID_PREFIX + digest;
|
|
13
|
+
}
|
|
14
|
+
function isStuffNodeId(id) {
|
|
15
|
+
return id.startsWith(STUFF_ID_PREFIX);
|
|
16
|
+
}
|
|
17
|
+
function stuffDigestFromId(id) {
|
|
18
|
+
return id.slice(STUFF_ID_PREFIX.length);
|
|
19
|
+
}
|
|
20
|
+
var GRAPH_DIRECTION = {
|
|
21
|
+
TB: "TB",
|
|
22
|
+
BT: "BT",
|
|
23
|
+
LR: "LR",
|
|
24
|
+
RL: "RL"
|
|
25
|
+
};
|
|
26
|
+
var EDGE_TYPE = {
|
|
27
|
+
/** Bezier curve — ReactFlow v12 renamed this type from "bezier" to "default". */
|
|
28
|
+
DEFAULT: "default",
|
|
29
|
+
STEP: "step",
|
|
30
|
+
STRAIGHT: "straight",
|
|
31
|
+
SMOOTH_STEP: "smoothstep"
|
|
32
|
+
};
|
|
33
|
+
var CONTROLLER_PADDING_X = 40;
|
|
34
|
+
var CONTROLLER_PADDING_TOP = 48;
|
|
35
|
+
var CONTROLLER_PADDING_BOTTOM = 20;
|
|
36
|
+
var ARROW_CLOSED_MARKER = "arrowclosed";
|
|
37
|
+
function nodeWidth(n) {
|
|
38
|
+
var _a;
|
|
39
|
+
const raw = (_a = n.style) == null ? void 0 : _a.width;
|
|
40
|
+
if (raw == null) return 200;
|
|
41
|
+
const w = typeof raw === "number" ? raw : parseFloat(raw);
|
|
42
|
+
return isNaN(w) || w <= 0 ? 200 : w;
|
|
43
|
+
}
|
|
44
|
+
function nodeHeight(n) {
|
|
45
|
+
var _a, _b;
|
|
46
|
+
const raw = (_a = n.style) == null ? void 0 : _a.height;
|
|
47
|
+
if (raw != null) {
|
|
48
|
+
const h = typeof raw === "number" ? raw : parseFloat(raw);
|
|
49
|
+
if (!isNaN(h) && h > 0) return h;
|
|
50
|
+
}
|
|
51
|
+
return ((_b = n.data) == null ? void 0 : _b.isStuff) ? 60 : 70;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/graph/graphAnalysis.ts
|
|
55
|
+
function buildDataflowAnalysis(graphspec) {
|
|
56
|
+
if (!graphspec) return null;
|
|
57
|
+
const stuffRegistry = {};
|
|
58
|
+
const stuffProducers = {};
|
|
59
|
+
const stuffConsumers = {};
|
|
60
|
+
const containmentTree = {};
|
|
61
|
+
const childNodeIds = /* @__PURE__ */ new Set();
|
|
62
|
+
for (const edge of graphspec.edges) {
|
|
63
|
+
if (edge.kind === "contains") {
|
|
64
|
+
if (!containmentTree[edge.source]) containmentTree[edge.source] = [];
|
|
65
|
+
containmentTree[edge.source].push(edge.target);
|
|
66
|
+
childNodeIds.add(edge.target);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const controllerNodeIds = new Set(Object.keys(containmentTree));
|
|
70
|
+
for (const node of graphspec.nodes) {
|
|
71
|
+
const nodeIo = node.io || {};
|
|
72
|
+
const isController = controllerNodeIds.has(node.id);
|
|
73
|
+
for (const output of nodeIo.outputs || []) {
|
|
74
|
+
if (output.digest && !stuffRegistry[output.digest]) {
|
|
75
|
+
stuffRegistry[output.digest] = {
|
|
76
|
+
name: output.name,
|
|
77
|
+
concept: output.concept,
|
|
78
|
+
contentType: output.content_type
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (output.digest && !isController) {
|
|
82
|
+
stuffProducers[output.digest] = node.id;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const input of nodeIo.inputs || []) {
|
|
86
|
+
if (input.digest && !stuffRegistry[input.digest]) {
|
|
87
|
+
stuffRegistry[input.digest] = {
|
|
88
|
+
name: input.name,
|
|
89
|
+
concept: input.concept,
|
|
90
|
+
contentType: input.content_type
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (input.digest && !isController) {
|
|
94
|
+
if (!stuffConsumers[input.digest]) stuffConsumers[input.digest] = [];
|
|
95
|
+
stuffConsumers[input.digest].push(node.id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
stuffRegistry,
|
|
101
|
+
stuffProducers,
|
|
102
|
+
stuffConsumers,
|
|
103
|
+
controllerNodeIds,
|
|
104
|
+
childNodeIds,
|
|
105
|
+
containmentTree
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function buildChildToControllerMap(graphspec, analysis) {
|
|
109
|
+
var _a;
|
|
110
|
+
const childToController = {};
|
|
111
|
+
for (const [ctrlId, children] of Object.entries(analysis.containmentTree)) {
|
|
112
|
+
for (const childId of children) {
|
|
113
|
+
childToController[childId] = ctrlId;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const [digest, producerId] of Object.entries(analysis.stuffProducers)) {
|
|
117
|
+
const stuffId = "stuff_" + digest;
|
|
118
|
+
const ctrlId = childToController[producerId];
|
|
119
|
+
if (ctrlId) {
|
|
120
|
+
childToController[stuffId] = ctrlId;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const node of graphspec.nodes) {
|
|
124
|
+
if (!analysis.controllerNodeIds.has(node.id)) continue;
|
|
125
|
+
const parentCtrlId = childToController[node.id];
|
|
126
|
+
if (!parentCtrlId) continue;
|
|
127
|
+
for (const output of ((_a = node.io) == null ? void 0 : _a.outputs) || []) {
|
|
128
|
+
if (!output.digest) continue;
|
|
129
|
+
const stuffId = "stuff_" + output.digest;
|
|
130
|
+
if (!childToController[stuffId]) {
|
|
131
|
+
childToController[stuffId] = parentCtrlId;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const edge of graphspec.edges) {
|
|
136
|
+
if (edge.kind === "batch_item" && edge.target_stuff_digest) {
|
|
137
|
+
const stuffId = "stuff_" + edge.target_stuff_digest;
|
|
138
|
+
if (analysis.controllerNodeIds.has(edge.source)) {
|
|
139
|
+
childToController[stuffId] = edge.source;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const stuffInStuffEdges = /* @__PURE__ */ new Set();
|
|
144
|
+
for (const edge of graphspec.edges) {
|
|
145
|
+
if (edge.kind === "batch_item" || edge.kind === "batch_aggregate" || edge.kind === "parallel_combine") {
|
|
146
|
+
if (edge.source_stuff_digest) stuffInStuffEdges.add(edge.source_stuff_digest);
|
|
147
|
+
if (edge.target_stuff_digest) stuffInStuffEdges.add(edge.target_stuff_digest);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const stuffPrefix = "stuff_";
|
|
151
|
+
const stuffEntries = Object.keys(childToController).filter((id) => id.startsWith(stuffPrefix));
|
|
152
|
+
for (const stuffId of stuffEntries) {
|
|
153
|
+
const digest = stuffId.slice(stuffPrefix.length);
|
|
154
|
+
let assignedCtrl = childToController[stuffId];
|
|
155
|
+
if (!assignedCtrl) continue;
|
|
156
|
+
const consumers = analysis.stuffConsumers[digest] || [];
|
|
157
|
+
if (consumers.length === 0) {
|
|
158
|
+
if (!stuffInStuffEdges.has(digest)) {
|
|
159
|
+
delete childToController[stuffId];
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
while (assignedCtrl) {
|
|
164
|
+
const ctrl = assignedCtrl;
|
|
165
|
+
const hasConsumerInside = consumers.some(
|
|
166
|
+
(consumerId) => isDescendantOf(consumerId, ctrl, childToController)
|
|
167
|
+
);
|
|
168
|
+
if (hasConsumerInside) break;
|
|
169
|
+
const parentCtrl = childToController[assignedCtrl];
|
|
170
|
+
if (parentCtrl) {
|
|
171
|
+
childToController[stuffId] = parentCtrl;
|
|
172
|
+
assignedCtrl = parentCtrl;
|
|
173
|
+
} else {
|
|
174
|
+
delete childToController[stuffId];
|
|
175
|
+
assignedCtrl = void 0;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return childToController;
|
|
180
|
+
}
|
|
181
|
+
function isDescendantOf(nodeId, ancestorCtrlId, childToController) {
|
|
182
|
+
let current = childToController[nodeId];
|
|
183
|
+
while (current) {
|
|
184
|
+
if (current === ancestorCtrlId) return true;
|
|
185
|
+
current = childToController[current];
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
function getPipeBlueprint(spec, pipeRef) {
|
|
190
|
+
var _a;
|
|
191
|
+
return (_a = spec.pipe_registry) == null ? void 0 : _a[pipeRef];
|
|
192
|
+
}
|
|
193
|
+
function getConceptInfo(spec, conceptRef) {
|
|
194
|
+
var _a;
|
|
195
|
+
return (_a = spec.concept_registry) == null ? void 0 : _a[conceptRef];
|
|
196
|
+
}
|
|
197
|
+
function resolveConceptRef(spec, codeOrRef) {
|
|
198
|
+
if (!spec.concept_registry) return void 0;
|
|
199
|
+
const direct = spec.concept_registry[codeOrRef];
|
|
200
|
+
if (direct) return direct;
|
|
201
|
+
for (const info of Object.values(spec.concept_registry)) {
|
|
202
|
+
if (info.code === codeOrRef) return info;
|
|
203
|
+
}
|
|
204
|
+
return void 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/graph/graphBuilders.ts
|
|
208
|
+
function defaultDescription(pipeType, pipeCode) {
|
|
209
|
+
const code = pipeCode || "this step";
|
|
210
|
+
const verb = {
|
|
211
|
+
PipeLLM: "Analyze and generate output using",
|
|
212
|
+
PipeExtract: "Extract content from",
|
|
213
|
+
PipeCompose: "Compose output using",
|
|
214
|
+
PipeImgGen: "Generate image for",
|
|
215
|
+
PipeSearch: "Search the web for",
|
|
216
|
+
PipeFunc: "Process data in"
|
|
217
|
+
};
|
|
218
|
+
return `${verb[pipeType || ""] || "Execute"} ${code.replace(/_/g, " ")}`;
|
|
219
|
+
}
|
|
220
|
+
var STUFF_CHAR_WIDTH_PX = 7;
|
|
221
|
+
var STUFF_LABEL_PADDING = 48;
|
|
222
|
+
var MIN_STUFF_WIDTH = 140;
|
|
223
|
+
function buildDataflowGraph(graphspec, analysis, edgeType) {
|
|
224
|
+
var _a, _b, _c, _d, _e;
|
|
225
|
+
const nodes = [];
|
|
226
|
+
const edges = [];
|
|
227
|
+
const participatingPipes = /* @__PURE__ */ new Set();
|
|
228
|
+
for (const producer of Object.values(analysis.stuffProducers)) {
|
|
229
|
+
participatingPipes.add(producer);
|
|
230
|
+
}
|
|
231
|
+
for (const consumers of Object.values(analysis.stuffConsumers)) {
|
|
232
|
+
for (const consumer of consumers) {
|
|
233
|
+
participatingPipes.add(consumer);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
for (const node of graphspec.nodes) {
|
|
237
|
+
if (!participatingPipes.has(node.id)) continue;
|
|
238
|
+
const isFailed = node.status === "failed";
|
|
239
|
+
const label = node.pipe_code || node.id.split(":").pop() || node.id;
|
|
240
|
+
const inputs = ((_b = (_a = node.io) == null ? void 0 : _a.inputs) != null ? _b : []).map((i) => {
|
|
241
|
+
var _a2, _b2;
|
|
242
|
+
return {
|
|
243
|
+
name: (_a2 = i.name) != null ? _a2 : "",
|
|
244
|
+
concept: (_b2 = i.concept) != null ? _b2 : ""
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
const outputs = ((_d = (_c = node.io) == null ? void 0 : _c.outputs) != null ? _d : []).map((o) => {
|
|
248
|
+
var _a2, _b2;
|
|
249
|
+
return {
|
|
250
|
+
name: (_a2 = o.name) != null ? _a2 : "",
|
|
251
|
+
concept: (_b2 = o.concept) != null ? _b2 : ""
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
const operatorType = node.pipe_type || "PipeFunc";
|
|
255
|
+
nodes.push({
|
|
256
|
+
id: node.id,
|
|
257
|
+
type: NODE_TYPE_PIPE_CARD,
|
|
258
|
+
data: {
|
|
259
|
+
labelDescriptor: { kind: "pipe", label, isFailed },
|
|
260
|
+
nodeData: node,
|
|
261
|
+
isPipe: true,
|
|
262
|
+
isStuff: false,
|
|
263
|
+
labelText: label,
|
|
264
|
+
pipeCode: node.pipe_code || label,
|
|
265
|
+
pipeType: node.pipe_type,
|
|
266
|
+
pipeCardData: {
|
|
267
|
+
pipeCode: node.pipe_code || label,
|
|
268
|
+
pipeType: operatorType,
|
|
269
|
+
description: node.description || defaultDescription(node.pipe_type, node.pipe_code),
|
|
270
|
+
status: node.status || "scheduled",
|
|
271
|
+
inputs,
|
|
272
|
+
outputs
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
position: { x: 0, y: 0 }
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
for (const [digest, stuffInfo] of Object.entries(analysis.stuffRegistry)) {
|
|
279
|
+
const stuffId = stuffNodeId(digest);
|
|
280
|
+
const label = stuffInfo.name || "data";
|
|
281
|
+
const concept = stuffInfo.concept || "";
|
|
282
|
+
const textWidth = Math.max(label.length, concept.length) * STUFF_CHAR_WIDTH_PX + STUFF_LABEL_PADDING;
|
|
283
|
+
const stuffWidth = Math.max(MIN_STUFF_WIDTH, textWidth);
|
|
284
|
+
const isInput = !analysis.stuffProducers[digest];
|
|
285
|
+
const isOutput = !isInput && !((_e = analysis.stuffConsumers[digest]) == null ? void 0 : _e.length);
|
|
286
|
+
const stuffRole = isInput ? "input" : isOutput ? "output" : void 0;
|
|
287
|
+
const borderColor = isInput ? "var(--color-stuff-input-border, #50FA7B)" : isOutput ? "var(--color-stuff-output-border, #a78bfa)" : "var(--color-stuff-border)";
|
|
288
|
+
nodes.push({
|
|
289
|
+
id: stuffId,
|
|
290
|
+
type: NODE_TYPE_STUFF,
|
|
291
|
+
data: {
|
|
292
|
+
labelDescriptor: { kind: "stuff", label, concept },
|
|
293
|
+
isStuff: true,
|
|
294
|
+
isPipe: false,
|
|
295
|
+
labelText: label,
|
|
296
|
+
stuffRole,
|
|
297
|
+
stuffDigest: digest
|
|
298
|
+
},
|
|
299
|
+
position: { x: 0, y: 0 },
|
|
300
|
+
style: {
|
|
301
|
+
background: "var(--color-stuff-bg)",
|
|
302
|
+
border: `2px solid ${borderColor}`,
|
|
303
|
+
borderRadius: "999px",
|
|
304
|
+
padding: "0",
|
|
305
|
+
width: stuffWidth + "px",
|
|
306
|
+
boxShadow: "var(--shadow-md)"
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
let edgeId = 0;
|
|
311
|
+
for (const [digest, producerNodeId] of Object.entries(analysis.stuffProducers)) {
|
|
312
|
+
const stuffId = stuffNodeId(digest);
|
|
313
|
+
edges.push({
|
|
314
|
+
id: "edge_" + edgeId++,
|
|
315
|
+
source: producerNodeId,
|
|
316
|
+
target: stuffId,
|
|
317
|
+
type: edgeType,
|
|
318
|
+
animated: false,
|
|
319
|
+
style: { stroke: "var(--color-edge)", strokeWidth: 2 },
|
|
320
|
+
markerEnd: {
|
|
321
|
+
type: ARROW_CLOSED_MARKER,
|
|
322
|
+
color: "var(--color-edge)"
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
for (const [digest, consumers] of Object.entries(analysis.stuffConsumers)) {
|
|
327
|
+
const stuffId = stuffNodeId(digest);
|
|
328
|
+
for (const consumerNodeId of consumers) {
|
|
329
|
+
edges.push({
|
|
330
|
+
id: "edge_" + edgeId++,
|
|
331
|
+
source: stuffId,
|
|
332
|
+
target: consumerNodeId,
|
|
333
|
+
type: edgeType,
|
|
334
|
+
animated: false,
|
|
335
|
+
style: { stroke: "var(--color-edge)", strokeWidth: 2 },
|
|
336
|
+
markerEnd: {
|
|
337
|
+
type: ARROW_CLOSED_MARKER,
|
|
338
|
+
color: "var(--color-edge)"
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
for (const edge of graphspec.edges) {
|
|
344
|
+
if (edge.kind !== "parallel_combine") continue;
|
|
345
|
+
if (!edge.source_stuff_digest || !edge.target_stuff_digest) continue;
|
|
346
|
+
if (!analysis.stuffRegistry[edge.source_stuff_digest] || !analysis.stuffRegistry[edge.target_stuff_digest])
|
|
347
|
+
continue;
|
|
348
|
+
const sourceId = stuffNodeId(edge.source_stuff_digest);
|
|
349
|
+
const targetId = stuffNodeId(edge.target_stuff_digest);
|
|
350
|
+
edges.push({
|
|
351
|
+
id: edge.id || "edge_" + edgeId++,
|
|
352
|
+
source: sourceId,
|
|
353
|
+
target: targetId,
|
|
354
|
+
type: "smoothstep",
|
|
355
|
+
animated: false,
|
|
356
|
+
style: {
|
|
357
|
+
stroke: "var(--color-parallel-combine)",
|
|
358
|
+
strokeWidth: 2,
|
|
359
|
+
strokeDasharray: "5,5"
|
|
360
|
+
},
|
|
361
|
+
markerEnd: {
|
|
362
|
+
type: ARROW_CLOSED_MARKER,
|
|
363
|
+
color: "var(--color-parallel-combine)"
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
for (const edge of graphspec.edges) {
|
|
368
|
+
if (edge.kind !== "batch_item" && edge.kind !== "batch_aggregate") continue;
|
|
369
|
+
if (!edge.source_stuff_digest || !edge.target_stuff_digest) continue;
|
|
370
|
+
if (!analysis.stuffRegistry[edge.source_stuff_digest] || !analysis.stuffRegistry[edge.target_stuff_digest])
|
|
371
|
+
continue;
|
|
372
|
+
const sourceId = stuffNodeId(edge.source_stuff_digest);
|
|
373
|
+
const targetId = stuffNodeId(edge.target_stuff_digest);
|
|
374
|
+
const isBatchItem = edge.kind === "batch_item";
|
|
375
|
+
edges.push({
|
|
376
|
+
id: edge.id || "edge_" + edgeId++,
|
|
377
|
+
source: sourceId,
|
|
378
|
+
target: targetId,
|
|
379
|
+
type: edgeType,
|
|
380
|
+
animated: false,
|
|
381
|
+
_batchEdge: true,
|
|
382
|
+
label: edge.label || "",
|
|
383
|
+
labelStyle: {
|
|
384
|
+
fontSize: "10px",
|
|
385
|
+
fontFamily: "var(--font-mono)",
|
|
386
|
+
fill: isBatchItem ? "var(--color-batch-item)" : "var(--color-batch-aggregate)"
|
|
387
|
+
},
|
|
388
|
+
labelBgStyle: { fill: "var(--color-bg)", fillOpacity: 0.9 },
|
|
389
|
+
style: {
|
|
390
|
+
stroke: isBatchItem ? "var(--color-batch-item)" : "var(--color-batch-aggregate)",
|
|
391
|
+
strokeWidth: 2,
|
|
392
|
+
strokeDasharray: "5,5"
|
|
393
|
+
},
|
|
394
|
+
markerEnd: {
|
|
395
|
+
type: ARROW_CLOSED_MARKER,
|
|
396
|
+
color: isBatchItem ? "var(--color-batch-item)" : "var(--color-batch-aggregate)"
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
const childToCtrl = buildChildToControllerMap(graphspec, analysis);
|
|
401
|
+
for (const edge of edges) {
|
|
402
|
+
const srcCtrl = childToCtrl[edge.source] || null;
|
|
403
|
+
const tgtCtrl = childToCtrl[edge.target] || null;
|
|
404
|
+
if (srcCtrl && tgtCtrl && srcCtrl !== tgtCtrl) {
|
|
405
|
+
edge._crossGroup = true;
|
|
406
|
+
edge.style = __spreadProps(__spreadValues({}, edge.style), {
|
|
407
|
+
strokeWidth: 1.5,
|
|
408
|
+
opacity: 0.65
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
for (const edge of edges) {
|
|
413
|
+
if (edge._batchEdge) {
|
|
414
|
+
edge.style = __spreadProps(__spreadValues({}, edge.style), {
|
|
415
|
+
opacity: 0.7
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return { nodes, edges };
|
|
420
|
+
}
|
|
421
|
+
function buildGraph(graphspec, edgeType) {
|
|
422
|
+
if (graphspec) {
|
|
423
|
+
const analysis = buildDataflowAnalysis(graphspec);
|
|
424
|
+
if (analysis && (Object.keys(analysis.stuffProducers).length > 0 || Object.keys(analysis.stuffConsumers).length > 0)) {
|
|
425
|
+
return { graphData: buildDataflowGraph(graphspec, analysis, edgeType), analysis };
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return { graphData: { nodes: [], edges: [] }, analysis: null };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/graph/elkGraphBuilder.ts
|
|
432
|
+
function elkDirection(direction) {
|
|
433
|
+
switch (direction) {
|
|
434
|
+
case "LR":
|
|
435
|
+
return "RIGHT";
|
|
436
|
+
case "RL":
|
|
437
|
+
return "LEFT";
|
|
438
|
+
case "BT":
|
|
439
|
+
return "UP";
|
|
440
|
+
default:
|
|
441
|
+
return "DOWN";
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
var INPUT_PORT_SUFFIX = "_in";
|
|
445
|
+
var OUTPUT_PORT_SUFFIX = "_out";
|
|
446
|
+
function inputPortId(nodeId) {
|
|
447
|
+
return nodeId + INPUT_PORT_SUFFIX;
|
|
448
|
+
}
|
|
449
|
+
function outputPortId(nodeId) {
|
|
450
|
+
return nodeId + OUTPUT_PORT_SUFFIX;
|
|
451
|
+
}
|
|
452
|
+
function makePorts(nodeId, dims, direction) {
|
|
453
|
+
const portSides = {
|
|
454
|
+
LR: { inSide: "WEST", outSide: "EAST" },
|
|
455
|
+
RL: { inSide: "EAST", outSide: "WEST" },
|
|
456
|
+
TB: { inSide: "NORTH", outSide: "SOUTH" },
|
|
457
|
+
BT: { inSide: "SOUTH", outSide: "NORTH" }
|
|
458
|
+
};
|
|
459
|
+
const { inSide, outSide } = portSides[direction];
|
|
460
|
+
const isHorizontal = direction === "LR" || direction === "RL";
|
|
461
|
+
const inX = isHorizontal ? direction === "LR" ? 0 : dims.width : dims.width / 2;
|
|
462
|
+
const inY = isHorizontal ? dims.height / 2 : direction === "TB" ? 0 : dims.height;
|
|
463
|
+
const outX = isHorizontal ? direction === "LR" ? dims.width : 0 : dims.width / 2;
|
|
464
|
+
const outY = isHorizontal ? dims.height / 2 : direction === "TB" ? dims.height : 0;
|
|
465
|
+
return [
|
|
466
|
+
{
|
|
467
|
+
id: inputPortId(nodeId),
|
|
468
|
+
x: inX,
|
|
469
|
+
y: inY,
|
|
470
|
+
width: 1,
|
|
471
|
+
height: 1,
|
|
472
|
+
layoutOptions: { "elk.port.side": inSide }
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
id: outputPortId(nodeId),
|
|
476
|
+
x: outX,
|
|
477
|
+
y: outY,
|
|
478
|
+
width: 1,
|
|
479
|
+
height: 1,
|
|
480
|
+
layoutOptions: { "elk.port.side": outSide }
|
|
481
|
+
}
|
|
482
|
+
];
|
|
483
|
+
}
|
|
484
|
+
var PIPE_CARD_HEIGHT_CAP = 320;
|
|
485
|
+
var PIPE_CARD_PADDING_X = 28;
|
|
486
|
+
var PIPE_CARD_PADDING_Y = 24;
|
|
487
|
+
var PIPE_CARD_GAP = 8;
|
|
488
|
+
var PIPE_CARD_HEADER_HEIGHT = 22;
|
|
489
|
+
var PIPE_CARD_DESC_LINE_HEIGHT = 16;
|
|
490
|
+
var PIPE_CARD_DESC_MAX_LINES_LR = 3;
|
|
491
|
+
var PIPE_CARD_IO_SECTION_HEIGHT_LR = 38;
|
|
492
|
+
var PIPE_CARD_IO_SECTION_HEIGHT_TB = 30;
|
|
493
|
+
var PIPE_CARD_IO_EXTRA_ROW_HEIGHT = 22;
|
|
494
|
+
var PIPE_CARD_PILL_NAME_MAX_WIDTH = 140;
|
|
495
|
+
var PIPE_CARD_PILL_CONCEPT_MAX_WIDTH = 100;
|
|
496
|
+
var PIPE_CARD_PILL_CHROME_WIDTH = 17;
|
|
497
|
+
var PIPE_CARD_IO_LABEL_WIDTH_TB = 58;
|
|
498
|
+
var CHAR_WIDTH_DESC = 5.5;
|
|
499
|
+
var CHAR_WIDTH_PILL_NAME = 5;
|
|
500
|
+
var CHAR_WIDTH_PILL_CONCEPT = 4.5;
|
|
501
|
+
var MAX_VISIBLE_INPUTS = 4;
|
|
502
|
+
function estimateDescriptionLines(description, isHorizontal, cardWidth) {
|
|
503
|
+
if (!description) return 0;
|
|
504
|
+
if (!isHorizontal) return 1;
|
|
505
|
+
const textWidth = cardWidth - PIPE_CARD_PADDING_X;
|
|
506
|
+
const charsPerLine = Math.max(1, Math.floor(textWidth / CHAR_WIDTH_DESC));
|
|
507
|
+
const neededLines = Math.ceil(description.length / charsPerLine);
|
|
508
|
+
return Math.min(PIPE_CARD_DESC_MAX_LINES_LR, Math.max(1, neededLines));
|
|
509
|
+
}
|
|
510
|
+
function estimateTbPillWidth(name, concept) {
|
|
511
|
+
const nameWidth = Math.min(
|
|
512
|
+
PIPE_CARD_PILL_NAME_MAX_WIDTH,
|
|
513
|
+
Math.ceil(name.length * CHAR_WIDTH_PILL_NAME)
|
|
514
|
+
);
|
|
515
|
+
const conceptWidth = Math.min(
|
|
516
|
+
PIPE_CARD_PILL_CONCEPT_MAX_WIDTH,
|
|
517
|
+
Math.ceil(concept.length * CHAR_WIDTH_PILL_CONCEPT)
|
|
518
|
+
);
|
|
519
|
+
return nameWidth + conceptWidth + PIPE_CARD_PILL_CHROME_WIDTH;
|
|
520
|
+
}
|
|
521
|
+
function countTbPillRows(pills, cardWidth) {
|
|
522
|
+
if (pills.length === 0) return 0;
|
|
523
|
+
const availableWidth = cardWidth - PIPE_CARD_PADDING_X - PIPE_CARD_IO_LABEL_WIDTH_TB;
|
|
524
|
+
let rows = 1;
|
|
525
|
+
let currentRowWidth = 0;
|
|
526
|
+
for (const pill of pills) {
|
|
527
|
+
const pillWidth = estimateTbPillWidth(pill.name, pill.concept);
|
|
528
|
+
if (currentRowWidth === 0) {
|
|
529
|
+
currentRowWidth = pillWidth;
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
if (currentRowWidth + pillWidth <= availableWidth) {
|
|
533
|
+
currentRowWidth += pillWidth;
|
|
534
|
+
} else {
|
|
535
|
+
rows += 1;
|
|
536
|
+
currentRowWidth = pillWidth;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return rows;
|
|
540
|
+
}
|
|
541
|
+
function estimateNodeDimensions(node, isHorizontal) {
|
|
542
|
+
var _a, _b, _c;
|
|
543
|
+
const nodeData = node.data || {};
|
|
544
|
+
const isStuff = nodeData.isStuff;
|
|
545
|
+
const labelText = nodeData.labelText || "";
|
|
546
|
+
const isPipeCard = node.type === NODE_TYPE_PIPE_CARD;
|
|
547
|
+
const pipeCardMinWidth = isHorizontal ? 180 : 280;
|
|
548
|
+
const pipeCardMaxWidth = isHorizontal ? 240 : 400;
|
|
549
|
+
const estimatedWidth = Math.max(180, Math.min(pipeCardMaxWidth, labelText.length * 8 + 60));
|
|
550
|
+
let width;
|
|
551
|
+
if (isStuff) {
|
|
552
|
+
width = Math.max(180, estimatedWidth);
|
|
553
|
+
} else if (isPipeCard && nodeData.pipeCardData) {
|
|
554
|
+
width = pipeCardMaxWidth;
|
|
555
|
+
} else {
|
|
556
|
+
width = Math.max(isPipeCard ? pipeCardMinWidth : 200, estimatedWidth);
|
|
557
|
+
}
|
|
558
|
+
let height;
|
|
559
|
+
if (isStuff) {
|
|
560
|
+
height = 60;
|
|
561
|
+
} else if (isPipeCard && nodeData.pipeCardData) {
|
|
562
|
+
const pcd = nodeData.pipeCardData;
|
|
563
|
+
const inputs = (_a = pcd.inputs) != null ? _a : [];
|
|
564
|
+
const outputs = (_b = pcd.outputs) != null ? _b : [];
|
|
565
|
+
const description = pcd.description || ((_c = nodeData.nodeData) == null ? void 0 : _c.description) || "";
|
|
566
|
+
let total = PIPE_CARD_PADDING_Y + PIPE_CARD_HEADER_HEIGHT;
|
|
567
|
+
const descLines = estimateDescriptionLines(description, isHorizontal, width);
|
|
568
|
+
if (descLines > 0) {
|
|
569
|
+
total += PIPE_CARD_GAP + descLines * PIPE_CARD_DESC_LINE_HEIGHT;
|
|
570
|
+
}
|
|
571
|
+
const visibleInputs = inputs.slice(0, MAX_VISIBLE_INPUTS);
|
|
572
|
+
if (visibleInputs.length > 0) {
|
|
573
|
+
total += PIPE_CARD_GAP;
|
|
574
|
+
if (isHorizontal) {
|
|
575
|
+
total += PIPE_CARD_IO_SECTION_HEIGHT_LR + (visibleInputs.length - 1) * PIPE_CARD_IO_EXTRA_ROW_HEIGHT;
|
|
576
|
+
} else {
|
|
577
|
+
const rows = countTbPillRows(visibleInputs, width);
|
|
578
|
+
total += PIPE_CARD_IO_SECTION_HEIGHT_TB + (rows - 1) * PIPE_CARD_IO_EXTRA_ROW_HEIGHT;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (outputs.length > 0) {
|
|
582
|
+
total += PIPE_CARD_GAP;
|
|
583
|
+
if (isHorizontal) {
|
|
584
|
+
total += PIPE_CARD_IO_SECTION_HEIGHT_LR + (outputs.length - 1) * PIPE_CARD_IO_EXTRA_ROW_HEIGHT;
|
|
585
|
+
} else {
|
|
586
|
+
const rows = countTbPillRows(outputs, width);
|
|
587
|
+
total += PIPE_CARD_IO_SECTION_HEIGHT_TB + (rows - 1) * PIPE_CARD_IO_EXTRA_ROW_HEIGHT;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
height = Math.min(PIPE_CARD_HEIGHT_CAP, total);
|
|
591
|
+
} else {
|
|
592
|
+
height = isPipeCard ? 120 : 70;
|
|
593
|
+
}
|
|
594
|
+
return { width, height };
|
|
595
|
+
}
|
|
596
|
+
function computeDepths(controllerNodeIds, containmentTree) {
|
|
597
|
+
const depthCache = {};
|
|
598
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
599
|
+
function getDepth(ctrlId) {
|
|
600
|
+
if (depthCache[ctrlId] !== void 0) return depthCache[ctrlId];
|
|
601
|
+
if (visiting.has(ctrlId)) return 0;
|
|
602
|
+
visiting.add(ctrlId);
|
|
603
|
+
const children = containmentTree[ctrlId] || [];
|
|
604
|
+
let maxChildDepth = -1;
|
|
605
|
+
for (const childId of children) {
|
|
606
|
+
if (controllerNodeIds.has(childId)) {
|
|
607
|
+
maxChildDepth = Math.max(maxChildDepth, getDepth(childId));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
visiting.delete(ctrlId);
|
|
611
|
+
depthCache[ctrlId] = maxChildDepth + 1;
|
|
612
|
+
return depthCache[ctrlId];
|
|
613
|
+
}
|
|
614
|
+
for (const id of controllerNodeIds) getDepth(id);
|
|
615
|
+
return depthCache;
|
|
616
|
+
}
|
|
617
|
+
function makeLeafNode(nodeId, dims, direction) {
|
|
618
|
+
return {
|
|
619
|
+
id: nodeId,
|
|
620
|
+
width: dims.width,
|
|
621
|
+
height: dims.height,
|
|
622
|
+
ports: makePorts(nodeId, dims, direction),
|
|
623
|
+
layoutOptions: {
|
|
624
|
+
"elk.portConstraints": "FIXED_POS"
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function buildElkGraph(nodes, edges, graphspec, analysis, direction, layoutConfig) {
|
|
629
|
+
var _a, _b, _c;
|
|
630
|
+
const isHorizontal = direction === "LR" || direction === "RL";
|
|
631
|
+
const nodesep = (_a = layoutConfig == null ? void 0 : layoutConfig.nodesep) != null ? _a : 80;
|
|
632
|
+
const ranksep = (_b = layoutConfig == null ? void 0 : layoutConfig.ranksep) != null ? _b : 70;
|
|
633
|
+
const elkDir = elkDirection(direction);
|
|
634
|
+
const edgeNodeSpacing = "30";
|
|
635
|
+
const rootLayoutOptions = {
|
|
636
|
+
"elk.algorithm": "layered",
|
|
637
|
+
"elk.direction": elkDir,
|
|
638
|
+
"elk.hierarchyHandling": "INCLUDE_CHILDREN",
|
|
639
|
+
"elk.spacing.nodeNode": String(nodesep),
|
|
640
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": String(ranksep),
|
|
641
|
+
"elk.spacing.edgeNode": edgeNodeSpacing,
|
|
642
|
+
"elk.spacing.edgeEdge": "20",
|
|
643
|
+
"elk.layered.spacing.edgeNodeBetweenLayers": edgeNodeSpacing,
|
|
644
|
+
"elk.layered.spacing.edgeEdgeBetweenLayers": "15",
|
|
645
|
+
"elk.layered.nodePlacement.favorStraightEdges": "true"
|
|
646
|
+
};
|
|
647
|
+
if (!graphspec || !analysis || analysis.controllerNodeIds.size === 0) {
|
|
648
|
+
const dimensionMap2 = {};
|
|
649
|
+
const elkChildren = nodes.map((node) => {
|
|
650
|
+
const dims = estimateNodeDimensions(node, isHorizontal);
|
|
651
|
+
dimensionMap2[node.id] = dims;
|
|
652
|
+
return makeLeafNode(node.id, dims, direction);
|
|
653
|
+
});
|
|
654
|
+
const elkEdges2 = edges.map((edge) => ({
|
|
655
|
+
id: edge.id,
|
|
656
|
+
sources: [outputPortId(edge.source)],
|
|
657
|
+
targets: [inputPortId(edge.target)]
|
|
658
|
+
}));
|
|
659
|
+
return {
|
|
660
|
+
elkGraph: {
|
|
661
|
+
id: "root",
|
|
662
|
+
layoutOptions: rootLayoutOptions,
|
|
663
|
+
children: elkChildren,
|
|
664
|
+
edges: elkEdges2
|
|
665
|
+
},
|
|
666
|
+
dimensionMap: dimensionMap2
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
const childToCtrl = buildChildToControllerMap(graphspec, analysis);
|
|
670
|
+
const depths = computeDepths(analysis.controllerNodeIds, analysis.containmentTree);
|
|
671
|
+
const dimensionMap = {};
|
|
672
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
673
|
+
for (const node of nodes) nodeById.set(node.id, node);
|
|
674
|
+
const controllerElkNodes = {};
|
|
675
|
+
const controllerIds = Array.from(analysis.controllerNodeIds);
|
|
676
|
+
controllerIds.sort((a, b) => {
|
|
677
|
+
var _a2, _b2;
|
|
678
|
+
return ((_a2 = depths[a]) != null ? _a2 : 0) - ((_b2 = depths[b]) != null ? _b2 : 0);
|
|
679
|
+
});
|
|
680
|
+
for (const ctrlId of controllerIds) {
|
|
681
|
+
const depth = (_c = depths[ctrlId]) != null ? _c : 0;
|
|
682
|
+
const depthScale = 1 + depth * 0.15;
|
|
683
|
+
const padX = Math.round(CONTROLLER_PADDING_X * depthScale);
|
|
684
|
+
const padTop = Math.round(CONTROLLER_PADDING_TOP * depthScale);
|
|
685
|
+
const padBottom = Math.round(CONTROLLER_PADDING_BOTTOM * depthScale);
|
|
686
|
+
const ctrlLayoutOptions = {
|
|
687
|
+
"elk.padding": `[top=${padTop},left=${padX},bottom=${padBottom},right=${padX}]`,
|
|
688
|
+
"elk.spacing.nodeNode": String(nodesep),
|
|
689
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": String(ranksep),
|
|
690
|
+
"elk.spacing.edgeNode": edgeNodeSpacing,
|
|
691
|
+
"elk.layered.spacing.edgeNodeBetweenLayers": edgeNodeSpacing
|
|
692
|
+
};
|
|
693
|
+
const children = [];
|
|
694
|
+
const directChildren = analysis.containmentTree[ctrlId] || [];
|
|
695
|
+
for (const childId of directChildren) {
|
|
696
|
+
if (analysis.controllerNodeIds.has(childId)) {
|
|
697
|
+
const childElk = controllerElkNodes[childId];
|
|
698
|
+
if (childElk) children.push(childElk);
|
|
699
|
+
} else {
|
|
700
|
+
const graphNode = nodeById.get(childId);
|
|
701
|
+
if (graphNode) {
|
|
702
|
+
const dims = estimateNodeDimensions(graphNode, isHorizontal);
|
|
703
|
+
dimensionMap[childId] = dims;
|
|
704
|
+
children.push(makeLeafNode(childId, dims, direction));
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
for (const node of nodes) {
|
|
709
|
+
if (node.data.isStuff && childToCtrl[node.id] === ctrlId) {
|
|
710
|
+
if (!children.some((c) => c.id === node.id)) {
|
|
711
|
+
const dims = estimateNodeDimensions(node, isHorizontal);
|
|
712
|
+
dimensionMap[node.id] = dims;
|
|
713
|
+
children.push(makeLeafNode(node.id, dims, direction));
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
controllerElkNodes[ctrlId] = {
|
|
718
|
+
id: ctrlId,
|
|
719
|
+
layoutOptions: ctrlLayoutOptions,
|
|
720
|
+
children
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
const rootChildren = [];
|
|
724
|
+
for (const ctrlId of controllerIds) {
|
|
725
|
+
if (!childToCtrl[ctrlId]) {
|
|
726
|
+
const elkNode = controllerElkNodes[ctrlId];
|
|
727
|
+
if (elkNode) rootChildren.push(elkNode);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
for (const node of nodes) {
|
|
731
|
+
if (!childToCtrl[node.id] && !analysis.controllerNodeIds.has(node.id)) {
|
|
732
|
+
const dims = estimateNodeDimensions(node, isHorizontal);
|
|
733
|
+
dimensionMap[node.id] = dims;
|
|
734
|
+
rootChildren.push(makeLeafNode(node.id, dims, direction));
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const nodeIdSet = new Set(nodes.map((n) => n.id));
|
|
738
|
+
const elkEdges = edges.filter((e) => nodeIdSet.has(e.source) && nodeIdSet.has(e.target)).map((edge) => ({
|
|
739
|
+
id: edge.id,
|
|
740
|
+
sources: [outputPortId(edge.source)],
|
|
741
|
+
targets: [inputPortId(edge.target)]
|
|
742
|
+
}));
|
|
743
|
+
return {
|
|
744
|
+
elkGraph: {
|
|
745
|
+
id: "root",
|
|
746
|
+
layoutOptions: rootLayoutOptions,
|
|
747
|
+
children: rootChildren,
|
|
748
|
+
edges: elkEdges
|
|
749
|
+
},
|
|
750
|
+
dimensionMap
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function extractAbsolutePositions(elkResult) {
|
|
754
|
+
const positions = {};
|
|
755
|
+
function walk(node, parentX, parentY) {
|
|
756
|
+
var _a, _b, _c, _d, _e;
|
|
757
|
+
const absX = parentX + ((_a = node.x) != null ? _a : 0);
|
|
758
|
+
const absY = parentY + ((_b = node.y) != null ? _b : 0);
|
|
759
|
+
if (node.id !== "root") {
|
|
760
|
+
positions[node.id] = {
|
|
761
|
+
x: absX,
|
|
762
|
+
y: absY,
|
|
763
|
+
width: (_c = node.width) != null ? _c : 0,
|
|
764
|
+
height: (_d = node.height) != null ? _d : 0
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
for (const child of (_e = node.children) != null ? _e : []) {
|
|
768
|
+
walk(child, absX, absY);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
walk(elkResult, 0, 0);
|
|
772
|
+
return positions;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/graph/graphLayout.ts
|
|
776
|
+
import ELK from "elkjs/lib/elk.bundled.js";
|
|
777
|
+
var elkInstance = null;
|
|
778
|
+
function getElk() {
|
|
779
|
+
if (!elkInstance) elkInstance = new ELK();
|
|
780
|
+
return elkInstance;
|
|
781
|
+
}
|
|
782
|
+
async function getLayoutedElements(nodes, edges, direction, layoutConfig, graphspec, analysis) {
|
|
783
|
+
if (nodes.length === 0) return { nodes: [], edges, controllerPositions: {} };
|
|
784
|
+
const isHorizontal = direction === "LR" || direction === "RL";
|
|
785
|
+
const { elkGraph, dimensionMap } = buildElkGraph(
|
|
786
|
+
nodes,
|
|
787
|
+
edges,
|
|
788
|
+
graphspec != null ? graphspec : null,
|
|
789
|
+
analysis != null ? analysis : null,
|
|
790
|
+
direction,
|
|
791
|
+
layoutConfig
|
|
792
|
+
);
|
|
793
|
+
const layoutResult = await getElk().layout(elkGraph);
|
|
794
|
+
const positions = extractAbsolutePositions(layoutResult);
|
|
795
|
+
const controllerPositions = {};
|
|
796
|
+
if (analysis) {
|
|
797
|
+
for (const ctrlId of analysis.controllerNodeIds) {
|
|
798
|
+
if (positions[ctrlId]) {
|
|
799
|
+
controllerPositions[ctrlId] = positions[ctrlId];
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
const result = nodes.map((node) => {
|
|
804
|
+
var _a;
|
|
805
|
+
const pos = positions[node.id];
|
|
806
|
+
const dims = (_a = dimensionMap[node.id]) != null ? _a : estimateNodeDimensions(node, isHorizontal);
|
|
807
|
+
const width = dims.width;
|
|
808
|
+
const height = dims.height;
|
|
809
|
+
const pipeCardData = node.data.pipeCardData;
|
|
810
|
+
const cardDirection = isHorizontal ? "LR" : "TB";
|
|
811
|
+
const updatedPipeCardData = pipeCardData ? __spreadProps(__spreadValues({}, pipeCardData), { direction: cardDirection }) : void 0;
|
|
812
|
+
return __spreadProps(__spreadValues({}, node), {
|
|
813
|
+
data: __spreadProps(__spreadValues({}, node.data), {
|
|
814
|
+
_estimatedWidth: width,
|
|
815
|
+
_estimatedHeight: height,
|
|
816
|
+
pipeCardData: updatedPipeCardData
|
|
817
|
+
}),
|
|
818
|
+
// Lock the ReactFlow node wrapper to the exact dimensions ELK used for layout.
|
|
819
|
+
// This ensures ReactFlow's Handle (centered on the DOM element) matches
|
|
820
|
+
// the port position ELK computed (centered on the estimated dimensions).
|
|
821
|
+
style: __spreadProps(__spreadValues({}, node.style), {
|
|
822
|
+
width: width + "px",
|
|
823
|
+
height: height + "px"
|
|
824
|
+
}),
|
|
825
|
+
position: {
|
|
826
|
+
x: pos ? pos.x : 0,
|
|
827
|
+
y: pos ? pos.y : 0
|
|
828
|
+
},
|
|
829
|
+
sourcePosition: isHorizontal ? direction === "LR" ? "right" : "left" : direction === "TB" ? "bottom" : "top",
|
|
830
|
+
targetPosition: isHorizontal ? direction === "LR" ? "left" : "right" : direction === "TB" ? "top" : "bottom"
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
return { nodes: result, edges, controllerPositions };
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// src/graph/graphControllers.ts
|
|
837
|
+
var MAX_VISIBLE_CONTROLLER_CHILDREN = 5;
|
|
838
|
+
function getDescendants(controllerId, containmentTree, controllerNodeIds) {
|
|
839
|
+
const result = /* @__PURE__ */ new Set();
|
|
840
|
+
const stack = [controllerId];
|
|
841
|
+
while (stack.length > 0) {
|
|
842
|
+
const id = stack.pop();
|
|
843
|
+
for (const childId of containmentTree[id] || []) {
|
|
844
|
+
result.add(childId);
|
|
845
|
+
if (controllerNodeIds.has(childId)) stack.push(childId);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return result;
|
|
849
|
+
}
|
|
850
|
+
function buildControllerNodes(graphspec, analysis, layoutedNodes, controllerPositions) {
|
|
851
|
+
var _a;
|
|
852
|
+
const nodeById = {};
|
|
853
|
+
for (const n of layoutedNodes) {
|
|
854
|
+
nodeById[n.id] = n;
|
|
855
|
+
}
|
|
856
|
+
const controllerInfo = {};
|
|
857
|
+
for (const node of graphspec.nodes) {
|
|
858
|
+
if (analysis.controllerNodeIds.has(node.id)) {
|
|
859
|
+
controllerInfo[node.id] = node;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const depthCache = {};
|
|
863
|
+
const depthVisiting = /* @__PURE__ */ new Set();
|
|
864
|
+
function getDepth(controllerId) {
|
|
865
|
+
if (depthCache[controllerId] !== void 0) return depthCache[controllerId];
|
|
866
|
+
if (depthVisiting.has(controllerId)) {
|
|
867
|
+
throw new Error(
|
|
868
|
+
`Cycle detected in containment tree: controller "${controllerId}" is part of a containment cycle`
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
depthVisiting.add(controllerId);
|
|
872
|
+
const children = analysis.containmentTree[controllerId] || [];
|
|
873
|
+
let maxChildDepth = -1;
|
|
874
|
+
for (const childId of children) {
|
|
875
|
+
if (analysis.controllerNodeIds.has(childId)) {
|
|
876
|
+
maxChildDepth = Math.max(maxChildDepth, getDepth(childId));
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
depthVisiting.delete(controllerId);
|
|
880
|
+
depthCache[controllerId] = maxChildDepth + 1;
|
|
881
|
+
return depthCache[controllerId];
|
|
882
|
+
}
|
|
883
|
+
const childToController = buildChildToControllerMap(graphspec, analysis);
|
|
884
|
+
const controllerStuffChildren = {};
|
|
885
|
+
for (const [nodeId, ctrlId] of Object.entries(childToController)) {
|
|
886
|
+
if (!isStuffNodeId(nodeId)) continue;
|
|
887
|
+
if (!nodeById[nodeId]) continue;
|
|
888
|
+
if (!controllerStuffChildren[ctrlId]) controllerStuffChildren[ctrlId] = [];
|
|
889
|
+
controllerStuffChildren[ctrlId].push(nodeId);
|
|
890
|
+
}
|
|
891
|
+
const controllerIds = Array.from(analysis.controllerNodeIds);
|
|
892
|
+
for (const id of controllerIds) getDepth(id);
|
|
893
|
+
controllerIds.sort((a, b) => depthCache[a] - depthCache[b]);
|
|
894
|
+
const controllerNodes = [];
|
|
895
|
+
const childToParent = {};
|
|
896
|
+
for (const controllerId of controllerIds) {
|
|
897
|
+
const directChildren = analysis.containmentTree[controllerId] || [];
|
|
898
|
+
const renderedChildren = directChildren.filter((cid) => nodeById[cid]);
|
|
899
|
+
const stuffChildren = controllerStuffChildren[controllerId] || [];
|
|
900
|
+
const allChildren = [...renderedChildren, ...stuffChildren];
|
|
901
|
+
if (allChildren.length === 0) continue;
|
|
902
|
+
let groupX, groupY, groupW, groupH;
|
|
903
|
+
const elkPos = controllerPositions == null ? void 0 : controllerPositions[controllerId];
|
|
904
|
+
if (elkPos) {
|
|
905
|
+
groupX = elkPos.x;
|
|
906
|
+
groupY = elkPos.y;
|
|
907
|
+
groupW = elkPos.width;
|
|
908
|
+
groupH = elkPos.height;
|
|
909
|
+
} else {
|
|
910
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
911
|
+
for (const childId of allChildren) {
|
|
912
|
+
const child = nodeById[childId];
|
|
913
|
+
const pos = child.position;
|
|
914
|
+
const w = nodeWidth(child);
|
|
915
|
+
const h = nodeHeight(child);
|
|
916
|
+
minX = Math.min(minX, pos.x);
|
|
917
|
+
minY = Math.min(minY, pos.y);
|
|
918
|
+
maxX = Math.max(maxX, pos.x + w);
|
|
919
|
+
maxY = Math.max(maxY, pos.y + h);
|
|
920
|
+
}
|
|
921
|
+
const depth = (_a = depthCache[controllerId]) != null ? _a : 0;
|
|
922
|
+
const depthScale = 1 + depth * 0.15;
|
|
923
|
+
const padX = Math.round(CONTROLLER_PADDING_X * depthScale);
|
|
924
|
+
const padTop = Math.round(CONTROLLER_PADDING_TOP * depthScale);
|
|
925
|
+
const padBottom = Math.round(CONTROLLER_PADDING_BOTTOM * depthScale);
|
|
926
|
+
groupX = minX - padX;
|
|
927
|
+
groupY = minY - padTop;
|
|
928
|
+
groupW = maxX - minX + 2 * padX;
|
|
929
|
+
groupH = maxY - minY + padTop + padBottom;
|
|
930
|
+
}
|
|
931
|
+
const info = controllerInfo[controllerId] || {};
|
|
932
|
+
const pipeCode = info.pipe_code || controllerId.split(":").pop() || controllerId;
|
|
933
|
+
const groupNode = {
|
|
934
|
+
id: controllerId,
|
|
935
|
+
type: NODE_TYPE_CONTROLLER,
|
|
936
|
+
data: {
|
|
937
|
+
label: pipeCode,
|
|
938
|
+
pipeType: info.pipe_type,
|
|
939
|
+
isController: true,
|
|
940
|
+
isPipe: false,
|
|
941
|
+
isStuff: false,
|
|
942
|
+
pipeCode,
|
|
943
|
+
labelText: pipeCode
|
|
944
|
+
},
|
|
945
|
+
position: { x: groupX, y: groupY },
|
|
946
|
+
style: {
|
|
947
|
+
width: groupW + "px",
|
|
948
|
+
height: groupH + "px",
|
|
949
|
+
padding: "0"
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
controllerNodes.push(groupNode);
|
|
953
|
+
nodeById[controllerId] = groupNode;
|
|
954
|
+
for (const childId of allChildren) {
|
|
955
|
+
childToParent[childId] = controllerId;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
for (const [childId, parentId] of Object.entries(childToParent)) {
|
|
959
|
+
const child = nodeById[childId];
|
|
960
|
+
const parent = nodeById[parentId];
|
|
961
|
+
if (!child || !parent) continue;
|
|
962
|
+
child.position = {
|
|
963
|
+
x: child.position.x - parent.position.x,
|
|
964
|
+
y: child.position.y - parent.position.y
|
|
965
|
+
};
|
|
966
|
+
child.parentId = parentId;
|
|
967
|
+
child.extent = "parent";
|
|
968
|
+
}
|
|
969
|
+
return controllerNodes;
|
|
970
|
+
}
|
|
971
|
+
function applyControllers(layoutedNodes, layoutedEdges, graphspec, analysis, showControllers, expandedControllers, onToggleCollapse, controllerPositions) {
|
|
972
|
+
var _a;
|
|
973
|
+
if (!showControllers || !analysis || !graphspec) {
|
|
974
|
+
return { nodes: layoutedNodes, edges: layoutedEdges };
|
|
975
|
+
}
|
|
976
|
+
const childCounts = {};
|
|
977
|
+
const collapsedSet = /* @__PURE__ */ new Set();
|
|
978
|
+
const controllerTypeMap = {};
|
|
979
|
+
for (const node of graphspec.nodes) {
|
|
980
|
+
if (analysis.controllerNodeIds.has(node.id)) {
|
|
981
|
+
controllerTypeMap[node.id] = node.pipe_type || "";
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
for (const ctrlId of analysis.controllerNodeIds) {
|
|
985
|
+
const directChildren = analysis.containmentTree[ctrlId] || [];
|
|
986
|
+
childCounts[ctrlId] = directChildren.length;
|
|
987
|
+
const pipeType = controllerTypeMap[ctrlId] || "";
|
|
988
|
+
const isCollapsible = pipeType === "PipeParallel" || pipeType === "PipeBatch";
|
|
989
|
+
if (isCollapsible && directChildren.length > MAX_VISIBLE_CONTROLLER_CHILDREN && !(expandedControllers == null ? void 0 : expandedControllers.has(ctrlId))) {
|
|
990
|
+
collapsedSet.add(ctrlId);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
let filteredNodes = layoutedNodes;
|
|
994
|
+
let filteredEdges = layoutedEdges;
|
|
995
|
+
if (collapsedSet.size > 0) {
|
|
996
|
+
const hiddenNodes = /* @__PURE__ */ new Set();
|
|
997
|
+
for (const ctrlId of collapsedSet) {
|
|
998
|
+
const directChildren = analysis.containmentTree[ctrlId] || [];
|
|
999
|
+
const toHide = directChildren.slice(MAX_VISIBLE_CONTROLLER_CHILDREN);
|
|
1000
|
+
for (const childId of toHide) {
|
|
1001
|
+
hiddenNodes.add(childId);
|
|
1002
|
+
if (analysis.controllerNodeIds.has(childId)) {
|
|
1003
|
+
for (const d of getDescendants(
|
|
1004
|
+
childId,
|
|
1005
|
+
analysis.containmentTree,
|
|
1006
|
+
analysis.controllerNodeIds
|
|
1007
|
+
)) {
|
|
1008
|
+
hiddenNodes.add(d);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
const childToCtrl = buildChildToControllerMap(graphspec, analysis);
|
|
1014
|
+
for (const node of layoutedNodes) {
|
|
1015
|
+
if (!isStuffNodeId(node.id) || hiddenNodes.has(node.id)) continue;
|
|
1016
|
+
const ctrlId = childToCtrl[node.id];
|
|
1017
|
+
if (!ctrlId || !collapsedSet.has(ctrlId) && !hiddenNodes.has(ctrlId)) continue;
|
|
1018
|
+
const pipeEdges = layoutedEdges.filter((e) => {
|
|
1019
|
+
if (e.source !== node.id && e.target !== node.id) return false;
|
|
1020
|
+
const other = e.source === node.id ? e.target : e.source;
|
|
1021
|
+
return !isStuffNodeId(other);
|
|
1022
|
+
});
|
|
1023
|
+
if (pipeEdges.length === 0 || pipeEdges.every((e) => {
|
|
1024
|
+
const other = e.source === node.id ? e.target : e.source;
|
|
1025
|
+
return hiddenNodes.has(other);
|
|
1026
|
+
})) {
|
|
1027
|
+
hiddenNodes.add(node.id);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
filteredNodes = layoutedNodes.filter((n) => !hiddenNodes.has(n.id));
|
|
1031
|
+
filteredEdges = layoutedEdges.filter(
|
|
1032
|
+
(e) => !hiddenNodes.has(e.source) && !hiddenNodes.has(e.target)
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
const controllerNodes = buildControllerNodes(
|
|
1036
|
+
graphspec,
|
|
1037
|
+
analysis,
|
|
1038
|
+
filteredNodes,
|
|
1039
|
+
controllerPositions
|
|
1040
|
+
);
|
|
1041
|
+
if (controllerNodes.length === 0) {
|
|
1042
|
+
return { nodes: filteredNodes, edges: filteredEdges };
|
|
1043
|
+
}
|
|
1044
|
+
for (const cn of controllerNodes) {
|
|
1045
|
+
const count = (_a = childCounts[cn.id]) != null ? _a : 0;
|
|
1046
|
+
const isCollapsed = collapsedSet.has(cn.id);
|
|
1047
|
+
cn.data.childCount = count;
|
|
1048
|
+
cn.data.isCollapsed = isCollapsed;
|
|
1049
|
+
if (onToggleCollapse) {
|
|
1050
|
+
const id = cn.id;
|
|
1051
|
+
cn.data.onToggleCollapse = () => onToggleCollapse(id);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const allNodes = [...controllerNodes, ...filteredNodes];
|
|
1055
|
+
const nodeMap = {};
|
|
1056
|
+
for (const n of allNodes) nodeMap[n.id] = n;
|
|
1057
|
+
const depthOf = {};
|
|
1058
|
+
const depthVisiting = /* @__PURE__ */ new Set();
|
|
1059
|
+
function getContainmentDepth(id) {
|
|
1060
|
+
if (depthOf[id] !== void 0) return depthOf[id];
|
|
1061
|
+
if (depthVisiting.has(id)) {
|
|
1062
|
+
throw new Error(
|
|
1063
|
+
`Cycle detected in node parent chain: node "${id}" references itself as an ancestor`
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
depthVisiting.add(id);
|
|
1067
|
+
const n = nodeMap[id];
|
|
1068
|
+
depthOf[id] = n && n.parentId ? 1 + getContainmentDepth(n.parentId) : 0;
|
|
1069
|
+
depthVisiting.delete(id);
|
|
1070
|
+
return depthOf[id];
|
|
1071
|
+
}
|
|
1072
|
+
for (const n of allNodes) getContainmentDepth(n.id);
|
|
1073
|
+
allNodes.sort((a, b) => depthOf[a.id] - depthOf[b.id]);
|
|
1074
|
+
return { nodes: allNodes, edges: filteredEdges };
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// src/graph/graphConfig.ts
|
|
1078
|
+
var DEFAULT_GRAPH_CONFIG = {
|
|
1079
|
+
direction: "LR",
|
|
1080
|
+
showControllers: false,
|
|
1081
|
+
nodesep: 50,
|
|
1082
|
+
ranksep: 100,
|
|
1083
|
+
edgeType: EDGE_TYPE.DEFAULT,
|
|
1084
|
+
initialZoom: null,
|
|
1085
|
+
panToTop: true,
|
|
1086
|
+
paletteColors: {
|
|
1087
|
+
// Graph node/edge colors
|
|
1088
|
+
"--color-pipe": "#ff6b6b",
|
|
1089
|
+
"--color-pipe-bg": "rgba(224,108,117,0.18)",
|
|
1090
|
+
"--color-pipe-text": "#ffffff",
|
|
1091
|
+
"--color-stuff": "#4ECDC4",
|
|
1092
|
+
"--color-stuff-bg": "rgba(78,205,196,0.12)",
|
|
1093
|
+
"--color-stuff-border": "#9ddcfd",
|
|
1094
|
+
"--color-stuff-text": "#98FB98",
|
|
1095
|
+
"--color-stuff-text-dim": "#9ddcfd",
|
|
1096
|
+
"--color-edge": "#FFFACD",
|
|
1097
|
+
"--color-batch-item": "#bd93f9",
|
|
1098
|
+
"--color-batch-aggregate": "#50fa7b",
|
|
1099
|
+
"--color-parallel-combine": "#d6a4ff",
|
|
1100
|
+
"--color-success": "#50FA7B",
|
|
1101
|
+
"--color-success-bg": "rgba(80,250,123,0.15)",
|
|
1102
|
+
"--color-error": "#FF5555",
|
|
1103
|
+
"--color-error-bg": "rgba(255,85,85,0.15)",
|
|
1104
|
+
"--color-accent": "#8BE9FD",
|
|
1105
|
+
"--color-warning": "#FFB86C",
|
|
1106
|
+
// Base theme vars used by graph-core.css
|
|
1107
|
+
"--color-bg": "#0a0a0a",
|
|
1108
|
+
"--color-bg-dots": "rgba(255, 255, 255, 0.35)",
|
|
1109
|
+
"--color-text-muted": "#94a3b8",
|
|
1110
|
+
"--color-controller-text": "#94a3b8",
|
|
1111
|
+
"--font-sans": '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
1112
|
+
"--font-mono": '"JetBrains Mono", "Monaco", "Menlo", monospace',
|
|
1113
|
+
"--shadow-lg": "0 8px 24px rgba(0, 0, 0, 0.5)"
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
function getPaletteColors() {
|
|
1117
|
+
return DEFAULT_GRAPH_CONFIG.paletteColors || {};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
export {
|
|
1121
|
+
NODE_TYPE_PIPE_CARD,
|
|
1122
|
+
NODE_TYPE_STUFF,
|
|
1123
|
+
NODE_TYPE_CONTROLLER,
|
|
1124
|
+
STUFF_ID_PREFIX,
|
|
1125
|
+
stuffNodeId,
|
|
1126
|
+
isStuffNodeId,
|
|
1127
|
+
stuffDigestFromId,
|
|
1128
|
+
GRAPH_DIRECTION,
|
|
1129
|
+
EDGE_TYPE,
|
|
1130
|
+
CONTROLLER_PADDING_X,
|
|
1131
|
+
CONTROLLER_PADDING_TOP,
|
|
1132
|
+
CONTROLLER_PADDING_BOTTOM,
|
|
1133
|
+
ARROW_CLOSED_MARKER,
|
|
1134
|
+
nodeWidth,
|
|
1135
|
+
nodeHeight,
|
|
1136
|
+
buildDataflowAnalysis,
|
|
1137
|
+
buildChildToControllerMap,
|
|
1138
|
+
getPipeBlueprint,
|
|
1139
|
+
getConceptInfo,
|
|
1140
|
+
resolveConceptRef,
|
|
1141
|
+
buildDataflowGraph,
|
|
1142
|
+
buildGraph,
|
|
1143
|
+
inputPortId,
|
|
1144
|
+
outputPortId,
|
|
1145
|
+
estimateNodeDimensions,
|
|
1146
|
+
buildElkGraph,
|
|
1147
|
+
extractAbsolutePositions,
|
|
1148
|
+
getLayoutedElements,
|
|
1149
|
+
MAX_VISIBLE_CONTROLLER_CHILDREN,
|
|
1150
|
+
buildControllerNodes,
|
|
1151
|
+
applyControllers,
|
|
1152
|
+
DEFAULT_GRAPH_CONFIG,
|
|
1153
|
+
getPaletteColors
|
|
1154
|
+
};
|
|
1155
|
+
//# sourceMappingURL=chunk-CCUSQM3E.js.map
|