@bian-womp/spark-workbench 0.1.29 → 0.2.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/lib/cjs/index.cjs +336 -240
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/index.d.ts +3 -1
- package/lib/cjs/src/index.d.ts.map +1 -1
- package/lib/cjs/src/misc/DefaultNode.d.ts +3 -2
- package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/cjs/src/misc/Inspector.d.ts +3 -1
- package/lib/cjs/src/misc/Inspector.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts +6 -4
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +2 -2
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts +2 -2
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/cjs/src/misc/hooks.d.ts +3 -3
- package/lib/cjs/src/misc/hooks.d.ts.map +1 -1
- package/lib/cjs/src/misc/mapping.d.ts +1 -1
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +29 -0
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -0
- package/lib/cjs/src/runtime/{GraphRunner.d.ts → IGraphRunner.d.ts} +21 -31
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -0
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +16 -0
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -0
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +27 -0
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -0
- package/lib/esm/index.js +332 -237
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/index.d.ts +3 -1
- package/lib/esm/src/index.d.ts.map +1 -1
- package/lib/esm/src/misc/DefaultNode.d.ts +3 -2
- package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/esm/src/misc/Inspector.d.ts +3 -1
- package/lib/esm/src/misc/Inspector.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts +6 -4
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +2 -2
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts +2 -2
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/esm/src/misc/hooks.d.ts +3 -3
- package/lib/esm/src/misc/hooks.d.ts.map +1 -1
- package/lib/esm/src/misc/mapping.d.ts +1 -1
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +29 -0
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -0
- package/lib/esm/src/runtime/{GraphRunner.d.ts → IGraphRunner.d.ts} +21 -31
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -0
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +16 -0
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -0
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +27 -0
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -0
- package/package.json +8 -8
- package/lib/cjs/src/runtime/GraphRunner.d.ts.map +0 -1
- package/lib/esm/src/runtime/GraphRunner.d.ts.map +0 -1
package/lib/cjs/index.cjs
CHANGED
|
@@ -5,7 +5,7 @@ var sparkRemote = require('@bian-womp/spark-remote');
|
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
var react = require('@phosphor-icons/react');
|
|
8
|
-
var
|
|
8
|
+
var react$1 = require('@xyflow/react');
|
|
9
9
|
var cx = require('classnames');
|
|
10
10
|
|
|
11
11
|
class DefaultUIExtensionRegistry {
|
|
@@ -319,104 +319,295 @@ class CLIWorkbench {
|
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
class
|
|
322
|
+
class AbstractGraphRunner {
|
|
323
323
|
constructor(registry, backend) {
|
|
324
324
|
this.registry = registry;
|
|
325
|
+
this.backend = backend;
|
|
325
326
|
this.listeners = new Map();
|
|
326
327
|
this.stagedInputs = {};
|
|
327
|
-
this.backend = { kind: "local" };
|
|
328
|
-
if (backend)
|
|
329
|
-
this.backend = backend;
|
|
330
|
-
// Emit initial transport status
|
|
331
|
-
if (this.backend.kind === "local")
|
|
332
|
-
this.emit("transport", { state: "local" });
|
|
333
328
|
}
|
|
334
|
-
|
|
335
|
-
if (this.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
329
|
+
launch(def, opts) {
|
|
330
|
+
if (this.engine) {
|
|
331
|
+
throw new Error("Engine already running. Stop the current engine first.");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
setInput(nodeId, handle, value) {
|
|
335
|
+
if (!this.stagedInputs[nodeId])
|
|
336
|
+
this.stagedInputs[nodeId] = {};
|
|
337
|
+
this.stagedInputs[nodeId][handle] = value;
|
|
338
|
+
if (this.engine) {
|
|
339
|
+
this.engine.setInput(nodeId, handle, value);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Emit a value event so UI updates even when engine isn't running
|
|
343
|
+
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Batch update multiple inputs on a node and trigger a single run
|
|
347
|
+
setInputs(nodeId, inputs) {
|
|
348
|
+
if (!inputs)
|
|
340
349
|
return;
|
|
350
|
+
if (!this.stagedInputs[nodeId])
|
|
351
|
+
this.stagedInputs[nodeId] = {};
|
|
352
|
+
Object.assign(this.stagedInputs[nodeId], inputs);
|
|
353
|
+
if (this.engine) {
|
|
354
|
+
// Running: set all inputs
|
|
355
|
+
this.engine.setInputs(nodeId, inputs);
|
|
341
356
|
}
|
|
342
|
-
|
|
357
|
+
else {
|
|
358
|
+
// Not running: emit a single synthetic value event per handle; UI will coalesce
|
|
359
|
+
console.warn("Engine does not exists");
|
|
360
|
+
for (const [handle, value] of Object.entries(inputs)) {
|
|
361
|
+
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async whenIdle() {
|
|
366
|
+
await this.engine?.whenIdle();
|
|
367
|
+
}
|
|
368
|
+
on(event, handler) {
|
|
369
|
+
if (!this.listeners.has(event))
|
|
370
|
+
this.listeners.set(event, new Set());
|
|
371
|
+
const set = this.listeners.get(event);
|
|
372
|
+
set.add(handler);
|
|
373
|
+
return () => set.delete(handler);
|
|
374
|
+
}
|
|
375
|
+
emit(event, payload) {
|
|
376
|
+
const set = this.listeners.get(event);
|
|
377
|
+
if (set)
|
|
378
|
+
for (const h of Array.from(set))
|
|
379
|
+
h(payload);
|
|
380
|
+
}
|
|
381
|
+
dispose() {
|
|
382
|
+
this.engine?.dispose();
|
|
383
|
+
this.engine = undefined;
|
|
384
|
+
this.runtime?.dispose();
|
|
385
|
+
this.runtime = undefined;
|
|
386
|
+
if (this.runningKind) {
|
|
387
|
+
this.runningKind = undefined;
|
|
388
|
+
this.emit("status", { running: false, engine: undefined });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
isRunning() {
|
|
392
|
+
return !!this.engine;
|
|
393
|
+
}
|
|
394
|
+
getRunningEngine() {
|
|
395
|
+
return this.runningKind;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
class LocalGraphRunner extends AbstractGraphRunner {
|
|
400
|
+
constructor(registry) {
|
|
401
|
+
super(registry, { kind: "local" });
|
|
402
|
+
this.emit("transport", { state: "local" });
|
|
403
|
+
}
|
|
404
|
+
build(def) {
|
|
405
|
+
const builder = new sparkGraph.GraphBuilder(this.registry);
|
|
406
|
+
this.runtime = builder.build(def);
|
|
407
|
+
// Signal UI that freshly built graph should be considered invalidated
|
|
408
|
+
this.emit("invalidate", { reason: "graph-built" });
|
|
343
409
|
}
|
|
344
410
|
update(def) {
|
|
345
|
-
if (this.
|
|
346
|
-
if (!this.runtime)
|
|
347
|
-
return;
|
|
348
|
-
// Prevent mid-run churn while wiring changes are applied
|
|
349
|
-
this.runtime.pause();
|
|
350
|
-
this.runtime.update(def, this.registry);
|
|
351
|
-
this.runtime.resume();
|
|
352
|
-
this.emit("invalidate", { reason: "graph-updated" });
|
|
411
|
+
if (!this.runtime)
|
|
353
412
|
return;
|
|
413
|
+
// Prevent mid-run churn while wiring changes are applied
|
|
414
|
+
this.runtime.pause();
|
|
415
|
+
this.runtime.update(def, this.registry);
|
|
416
|
+
this.runtime.resume();
|
|
417
|
+
this.emit("invalidate", { reason: "graph-updated" });
|
|
418
|
+
}
|
|
419
|
+
launch(def, opts) {
|
|
420
|
+
super.launch(def, opts);
|
|
421
|
+
this.build(def);
|
|
422
|
+
if (!this.runtime)
|
|
423
|
+
throw new Error("Runtime not built");
|
|
424
|
+
const rt = this.runtime;
|
|
425
|
+
switch (opts.engine) {
|
|
426
|
+
case "push":
|
|
427
|
+
this.engine = new sparkGraph.PushEngine(rt);
|
|
428
|
+
break;
|
|
429
|
+
case "batched":
|
|
430
|
+
this.engine = new sparkGraph.BatchedEngine(rt, {
|
|
431
|
+
flushIntervalMs: opts.batched?.flushIntervalMs ?? 0,
|
|
432
|
+
});
|
|
433
|
+
break;
|
|
434
|
+
case "pull":
|
|
435
|
+
this.engine = new sparkGraph.PullEngine(rt);
|
|
436
|
+
break;
|
|
437
|
+
case "hybrid":
|
|
438
|
+
this.engine = new sparkGraph.HybridEngine(rt, {
|
|
439
|
+
windowMs: opts.hybrid?.windowMs ?? 250,
|
|
440
|
+
batchThreshold: opts.hybrid?.batchThreshold ?? 3,
|
|
441
|
+
});
|
|
442
|
+
break;
|
|
443
|
+
case "step":
|
|
444
|
+
this.engine = new sparkGraph.StepEngine(rt);
|
|
445
|
+
break;
|
|
446
|
+
default:
|
|
447
|
+
throw new Error("Unknown engine kind");
|
|
448
|
+
}
|
|
449
|
+
this.engine.on("value", (e) => this.emit("value", e));
|
|
450
|
+
this.engine.on("error", (e) => this.emit("error", e));
|
|
451
|
+
this.engine.on("invalidate", (e) => this.emit("invalidate", e));
|
|
452
|
+
this.engine.on("stats", (e) => this.emit("stats", e));
|
|
453
|
+
this.engine.launch();
|
|
454
|
+
this.runningKind = opts.engine;
|
|
455
|
+
this.emit("status", { running: true, engine: this.runningKind });
|
|
456
|
+
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
457
|
+
this.engine.setInputs(nodeId, map);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async step() {
|
|
461
|
+
const eng = this.engine;
|
|
462
|
+
if (eng instanceof sparkGraph.StepEngine)
|
|
463
|
+
await eng.step();
|
|
464
|
+
}
|
|
465
|
+
async computeNode(nodeId) {
|
|
466
|
+
const eng = this.engine;
|
|
467
|
+
if (eng instanceof sparkGraph.PullEngine)
|
|
468
|
+
await eng.computeNode(nodeId);
|
|
469
|
+
}
|
|
470
|
+
flush() {
|
|
471
|
+
const eng = this.engine;
|
|
472
|
+
if (eng instanceof sparkGraph.BatchedEngine)
|
|
473
|
+
eng.flush();
|
|
474
|
+
}
|
|
475
|
+
getOutputs(def) {
|
|
476
|
+
const out = {};
|
|
477
|
+
if (!this.runtime)
|
|
478
|
+
return out;
|
|
479
|
+
for (const n of def.nodes) {
|
|
480
|
+
const desc = this.registry.nodes.get(n.typeId);
|
|
481
|
+
const handles = Object.keys(desc?.outputs ?? {});
|
|
482
|
+
for (const h of handles) {
|
|
483
|
+
const v = this.runtime.getOutput(n.nodeId, h);
|
|
484
|
+
if (v !== undefined) {
|
|
485
|
+
if (!out[n.nodeId])
|
|
486
|
+
out[n.nodeId] = {};
|
|
487
|
+
out[n.nodeId][h] = v;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
354
490
|
}
|
|
491
|
+
return out;
|
|
492
|
+
}
|
|
493
|
+
getInputs(def) {
|
|
494
|
+
const out = {};
|
|
495
|
+
for (const n of def.nodes) {
|
|
496
|
+
const staged = this.stagedInputs[n.nodeId] ?? {};
|
|
497
|
+
const runtimeInputs = this.runtime
|
|
498
|
+
? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
|
|
499
|
+
: {};
|
|
500
|
+
if (this.isRunning()) {
|
|
501
|
+
out[n.nodeId] = runtimeInputs;
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
const merged = { ...runtimeInputs, ...staged };
|
|
505
|
+
if (Object.keys(merged).length > 0)
|
|
506
|
+
out[n.nodeId] = merged;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return out;
|
|
510
|
+
}
|
|
511
|
+
dispose() {
|
|
512
|
+
super.dispose();
|
|
513
|
+
this.runtime = undefined;
|
|
514
|
+
this.emit("transport", { state: "local" });
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
class RemoteGraphRunner extends AbstractGraphRunner {
|
|
519
|
+
constructor(registry, backend) {
|
|
520
|
+
super(registry, backend);
|
|
521
|
+
this.valueCache = new Map();
|
|
522
|
+
this.listenersBound = false;
|
|
523
|
+
// Auto-handle registry-changed invalidations from remote
|
|
524
|
+
// We listen on invalidate and if reason matches, we rehydrate registry and emit a registry event
|
|
525
|
+
this.ensureRemoteRunner().then(async (runner) => {
|
|
526
|
+
const eng = runner.getEngine();
|
|
527
|
+
if (!this.listenersBound) {
|
|
528
|
+
eng.on("invalidate", async (e) => {
|
|
529
|
+
if (e.reason === "registry-changed") {
|
|
530
|
+
try {
|
|
531
|
+
const deltas = Array.isArray(e.deltas) ? e.deltas : [];
|
|
532
|
+
for (const d of deltas) {
|
|
533
|
+
if (!d || typeof d !== "object")
|
|
534
|
+
continue;
|
|
535
|
+
if (d.kind === "register-enum") {
|
|
536
|
+
this.registry.registerEnum({
|
|
537
|
+
id: d.id,
|
|
538
|
+
displayName: d.displayName,
|
|
539
|
+
options: d.options,
|
|
540
|
+
opts: d.opts,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
else if (d.kind === "register-type") {
|
|
544
|
+
if (!this.registry.types.has(d.id)) {
|
|
545
|
+
this.registry.registerType({
|
|
546
|
+
id: d.id,
|
|
547
|
+
displayName: d.displayName,
|
|
548
|
+
validate: (_v) => true,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
else if (d.kind === "register-node") {
|
|
553
|
+
if (!this.registry.nodes.has(d.desc?.id)) {
|
|
554
|
+
this.registry.registerNode({
|
|
555
|
+
id: String(d.desc?.id || ""),
|
|
556
|
+
categoryId: String(d.desc?.categoryId || "compute"),
|
|
557
|
+
displayName: d.desc?.displayName,
|
|
558
|
+
inputs: d.desc?.inputs || {},
|
|
559
|
+
outputs: d.desc?.outputs || {},
|
|
560
|
+
impl: () => { },
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
this.emit("registry", this.registry);
|
|
566
|
+
// Trigger update so validation/UI refreshes using last known graph
|
|
567
|
+
try {
|
|
568
|
+
if (this.lastDef)
|
|
569
|
+
this.update(this.lastDef);
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
console.error("Failed to update graph definition after registry changed");
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
console.error("Failed to handle registry changed event");
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
build(def) {
|
|
584
|
+
console.warn("Unsupported operation for remote runner");
|
|
585
|
+
}
|
|
586
|
+
update(def) {
|
|
355
587
|
// Remote: forward update; ignore errors (fire-and-forget)
|
|
356
|
-
|
|
588
|
+
this.ensureRemoteRunner().then(async (runner) => {
|
|
357
589
|
try {
|
|
358
|
-
await
|
|
590
|
+
await runner.update(def);
|
|
359
591
|
this.emit("invalidate", { reason: "graph-updated" });
|
|
592
|
+
this.lastDef = def;
|
|
360
593
|
}
|
|
361
594
|
catch { }
|
|
362
595
|
});
|
|
363
596
|
}
|
|
364
597
|
launch(def, opts) {
|
|
365
|
-
|
|
366
|
-
throw new Error("Engine already running. Stop the current engine first.");
|
|
367
|
-
}
|
|
368
|
-
if (this.backend.kind === "local") {
|
|
369
|
-
this.build(def);
|
|
370
|
-
if (!this.runtime)
|
|
371
|
-
throw new Error("Runtime not built");
|
|
372
|
-
const rt = this.runtime;
|
|
373
|
-
switch (opts.engine) {
|
|
374
|
-
case "push":
|
|
375
|
-
this.engine = new sparkGraph.PushEngine(rt);
|
|
376
|
-
break;
|
|
377
|
-
case "batched":
|
|
378
|
-
this.engine = new sparkGraph.BatchedEngine(rt, {
|
|
379
|
-
flushIntervalMs: opts.batched?.flushIntervalMs ?? 0,
|
|
380
|
-
});
|
|
381
|
-
break;
|
|
382
|
-
case "pull":
|
|
383
|
-
this.engine = new sparkGraph.PullEngine(rt);
|
|
384
|
-
break;
|
|
385
|
-
case "hybrid":
|
|
386
|
-
this.engine = new sparkGraph.HybridEngine(rt, {
|
|
387
|
-
windowMs: opts.hybrid?.windowMs ?? 250,
|
|
388
|
-
batchThreshold: opts.hybrid?.batchThreshold ?? 3,
|
|
389
|
-
});
|
|
390
|
-
break;
|
|
391
|
-
case "step":
|
|
392
|
-
this.engine = new sparkGraph.StepEngine(rt);
|
|
393
|
-
break;
|
|
394
|
-
default:
|
|
395
|
-
throw new Error("Unknown engine kind");
|
|
396
|
-
}
|
|
397
|
-
this.engine.on("value", (e) => this.emit("value", e));
|
|
398
|
-
this.engine.on("error", (e) => this.emit("error", e));
|
|
399
|
-
this.engine.on("invalidate", (e) => this.emit("invalidate", e));
|
|
400
|
-
this.engine.on("stats", (e) => this.emit("stats", e));
|
|
401
|
-
this.engine.launch();
|
|
402
|
-
this.runningKind = opts.engine;
|
|
403
|
-
this.emit("status", { running: true, engine: this.runningKind });
|
|
404
|
-
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
405
|
-
this.engine.setInputs(nodeId, map);
|
|
406
|
-
}
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
598
|
+
super.launch(def, opts);
|
|
409
599
|
// Remote: build remotely then launch
|
|
410
|
-
|
|
411
|
-
await
|
|
600
|
+
this.ensureRemoteRunner().then(async (runner) => {
|
|
601
|
+
await runner.build(def);
|
|
412
602
|
// Signal UI after remote build as well
|
|
413
603
|
this.emit("invalidate", { reason: "graph-built" });
|
|
604
|
+
this.lastDef = def;
|
|
414
605
|
// Hydrate current remote inputs/outputs (including defaults) into cache
|
|
415
606
|
try {
|
|
416
|
-
const snap = await
|
|
607
|
+
const snap = await runner.snapshot();
|
|
417
608
|
for (const [nodeId, map] of Object.entries(snap.inputs || {})) {
|
|
418
609
|
for (const [handle, value] of Object.entries(map || {})) {
|
|
419
|
-
|
|
610
|
+
this.valueCache.set(`${nodeId}.${handle}`, {
|
|
420
611
|
io: "input",
|
|
421
612
|
value,
|
|
422
613
|
});
|
|
@@ -425,7 +616,7 @@ class GraphRunner {
|
|
|
425
616
|
}
|
|
426
617
|
for (const [nodeId, map] of Object.entries(snap.outputs || {})) {
|
|
427
618
|
for (const [handle, value] of Object.entries(map || {})) {
|
|
428
|
-
|
|
619
|
+
this.valueCache.set(`${nodeId}.${handle}`, {
|
|
429
620
|
io: "output",
|
|
430
621
|
value,
|
|
431
622
|
});
|
|
@@ -436,10 +627,10 @@ class GraphRunner {
|
|
|
436
627
|
catch {
|
|
437
628
|
console.error("Failed to hydrate remote inputs/outputs");
|
|
438
629
|
}
|
|
439
|
-
const eng =
|
|
440
|
-
if (!
|
|
630
|
+
const eng = runner.getEngine();
|
|
631
|
+
if (!this.listenersBound) {
|
|
441
632
|
eng.on("value", (e) => {
|
|
442
|
-
|
|
633
|
+
this.valueCache.set(`${e.nodeId}.${e.handle}`, {
|
|
443
634
|
io: e.io,
|
|
444
635
|
value: e.value,
|
|
445
636
|
runtimeTypeId: e.runtimeTypeId,
|
|
@@ -449,7 +640,7 @@ class GraphRunner {
|
|
|
449
640
|
eng.on("error", (e) => this.emit("error", e));
|
|
450
641
|
eng.on("invalidate", (e) => this.emit("invalidate", e));
|
|
451
642
|
eng.on("stats", (e) => this.emit("stats", e));
|
|
452
|
-
|
|
643
|
+
this.listenersBound = true;
|
|
453
644
|
}
|
|
454
645
|
this.engine = eng;
|
|
455
646
|
this.engine.launch();
|
|
@@ -460,85 +651,18 @@ class GraphRunner {
|
|
|
460
651
|
}
|
|
461
652
|
});
|
|
462
653
|
}
|
|
463
|
-
setInput(nodeId, handle, value) {
|
|
464
|
-
if (!this.stagedInputs[nodeId])
|
|
465
|
-
this.stagedInputs[nodeId] = {};
|
|
466
|
-
this.stagedInputs[nodeId][handle] = value;
|
|
467
|
-
if (this.engine) {
|
|
468
|
-
this.engine.setInput(nodeId, handle, value);
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
// Emit a value event so UI updates even when engine isn't running
|
|
472
|
-
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
// Batch update multiple inputs on a node and trigger a single run
|
|
476
|
-
setInputs(nodeId, inputs) {
|
|
477
|
-
if (!inputs)
|
|
478
|
-
return;
|
|
479
|
-
if (!this.stagedInputs[nodeId])
|
|
480
|
-
this.stagedInputs[nodeId] = {};
|
|
481
|
-
Object.assign(this.stagedInputs[nodeId], inputs);
|
|
482
|
-
// Local running: pause, set all inputs, resume, schedule a single recompute
|
|
483
|
-
if (this.backend.kind === "local" && this.engine && this.runtime) {
|
|
484
|
-
this.engine.setInputs(nodeId, inputs);
|
|
485
|
-
}
|
|
486
|
-
// Remote running: forward inputs individually (no batch API available)
|
|
487
|
-
else if (this.engine &&
|
|
488
|
-
this.backend.kind !== "local" &&
|
|
489
|
-
this.engine instanceof sparkRemote.RemoteEngine) {
|
|
490
|
-
this.engine.setInputs(nodeId, inputs);
|
|
491
|
-
}
|
|
492
|
-
// Not running: emit value events so UI reflects staged values
|
|
493
|
-
else if (!this.engine) {
|
|
494
|
-
// Not running: emit a single synthetic value event per handle; UI will coalesce
|
|
495
|
-
console.warn("Remote engine does not exists");
|
|
496
|
-
for (const [handle, value] of Object.entries(inputs)) {
|
|
497
|
-
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
654
|
async step() {
|
|
502
|
-
|
|
503
|
-
return; // unsupported remotely
|
|
504
|
-
const eng = this.engine;
|
|
505
|
-
if (eng instanceof sparkGraph.StepEngine)
|
|
506
|
-
await eng.step();
|
|
655
|
+
console.warn("Unsupported operation for remote runner");
|
|
507
656
|
}
|
|
508
657
|
async computeNode(nodeId) {
|
|
509
|
-
|
|
510
|
-
return; // unsupported remotely
|
|
511
|
-
const eng = this.engine;
|
|
512
|
-
if (eng instanceof sparkGraph.PullEngine)
|
|
513
|
-
await eng.computeNode(nodeId);
|
|
658
|
+
console.warn("Unsupported operation for remote runner");
|
|
514
659
|
}
|
|
515
660
|
flush() {
|
|
516
|
-
|
|
517
|
-
return; // unsupported remotely
|
|
518
|
-
const eng = this.engine;
|
|
519
|
-
if (eng instanceof sparkGraph.BatchedEngine)
|
|
520
|
-
eng.flush();
|
|
661
|
+
console.warn("Unsupported operation for remote runner");
|
|
521
662
|
}
|
|
522
663
|
getOutputs(def) {
|
|
523
664
|
const out = {};
|
|
524
|
-
|
|
525
|
-
if (!this.runtime)
|
|
526
|
-
return out;
|
|
527
|
-
for (const n of def.nodes) {
|
|
528
|
-
const desc = this.registry.nodes.get(n.typeId);
|
|
529
|
-
const handles = Object.keys(desc?.outputs ?? {});
|
|
530
|
-
for (const h of handles) {
|
|
531
|
-
const v = this.runtime.getOutput(n.nodeId, h);
|
|
532
|
-
if (v !== undefined) {
|
|
533
|
-
if (!out[n.nodeId])
|
|
534
|
-
out[n.nodeId] = {};
|
|
535
|
-
out[n.nodeId][h] = v;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
return out;
|
|
540
|
-
}
|
|
541
|
-
const cache = this.remote?.valueCache;
|
|
665
|
+
const cache = this.valueCache;
|
|
542
666
|
if (!cache)
|
|
543
667
|
return out;
|
|
544
668
|
for (const n of def.nodes) {
|
|
@@ -558,31 +682,14 @@ class GraphRunner {
|
|
|
558
682
|
}
|
|
559
683
|
getInputs(def) {
|
|
560
684
|
const out = {};
|
|
561
|
-
|
|
562
|
-
for (const n of def.nodes) {
|
|
563
|
-
const staged = this.stagedInputs[n.nodeId] ?? {};
|
|
564
|
-
const runtimeInputs = this.runtime
|
|
565
|
-
? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
|
|
566
|
-
: {};
|
|
567
|
-
if (this.isRunning()) {
|
|
568
|
-
out[n.nodeId] = runtimeInputs;
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
const merged = { ...runtimeInputs, ...staged };
|
|
572
|
-
if (Object.keys(merged).length > 0)
|
|
573
|
-
out[n.nodeId] = merged;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
return out;
|
|
577
|
-
}
|
|
578
|
-
const cache = this.remote?.valueCache;
|
|
685
|
+
const cache = this.valueCache;
|
|
579
686
|
for (const n of def.nodes) {
|
|
580
687
|
const staged = this.stagedInputs[n.nodeId] ?? {};
|
|
581
688
|
const desc = this.registry.nodes.get(n.typeId);
|
|
582
689
|
const handles = Object.keys(desc?.inputs ?? {});
|
|
583
690
|
const cur = {};
|
|
584
691
|
for (const h of handles) {
|
|
585
|
-
const rec = cache
|
|
692
|
+
const rec = cache.get(`${n.nodeId}.${h}`);
|
|
586
693
|
if (rec && rec.io === "input")
|
|
587
694
|
cur[h] = rec.value;
|
|
588
695
|
}
|
|
@@ -592,52 +699,21 @@ class GraphRunner {
|
|
|
592
699
|
}
|
|
593
700
|
return out;
|
|
594
701
|
}
|
|
595
|
-
async whenIdle() {
|
|
596
|
-
await this.engine?.whenIdle();
|
|
597
|
-
}
|
|
598
|
-
on(event, handler) {
|
|
599
|
-
if (!this.listeners.has(event))
|
|
600
|
-
this.listeners.set(event, new Set());
|
|
601
|
-
const set = this.listeners.get(event);
|
|
602
|
-
set.add(handler);
|
|
603
|
-
return () => set.delete(handler);
|
|
604
|
-
}
|
|
605
|
-
emit(event, payload) {
|
|
606
|
-
const set = this.listeners.get(event);
|
|
607
|
-
if (set)
|
|
608
|
-
for (const h of Array.from(set))
|
|
609
|
-
h(payload);
|
|
610
|
-
}
|
|
611
702
|
dispose() {
|
|
612
|
-
|
|
613
|
-
this.
|
|
614
|
-
this.
|
|
615
|
-
this.runtime = undefined;
|
|
616
|
-
this.remote = undefined;
|
|
617
|
-
if (this.runningKind) {
|
|
618
|
-
this.runningKind = undefined;
|
|
619
|
-
this.emit("status", { running: false, engine: undefined });
|
|
620
|
-
}
|
|
621
|
-
const kind = this.backend.kind === "local"
|
|
622
|
-
? undefined
|
|
623
|
-
: this.backend.kind;
|
|
703
|
+
super.dispose();
|
|
704
|
+
this.runner = undefined;
|
|
705
|
+
this.transport = undefined;
|
|
624
706
|
this.emit("transport", {
|
|
625
|
-
state:
|
|
626
|
-
kind,
|
|
707
|
+
state: "disconnected",
|
|
708
|
+
kind: this.backend.kind,
|
|
627
709
|
});
|
|
628
710
|
}
|
|
629
|
-
isRunning() {
|
|
630
|
-
return !!this.engine;
|
|
631
|
-
}
|
|
632
|
-
getRunningEngine() {
|
|
633
|
-
return this.runningKind;
|
|
634
|
-
}
|
|
635
711
|
// Ensure remote transport/runner
|
|
636
|
-
async
|
|
637
|
-
if (this.
|
|
638
|
-
return this.
|
|
712
|
+
async ensureRemoteRunner() {
|
|
713
|
+
if (this.runner)
|
|
714
|
+
return this.runner;
|
|
639
715
|
let transport;
|
|
640
|
-
const kind = this.backend.kind
|
|
716
|
+
const kind = this.backend.kind;
|
|
641
717
|
this.emit("transport", { state: "connecting", kind });
|
|
642
718
|
if (this.backend.kind === "remote-http") {
|
|
643
719
|
if (!sparkRemote.HttpPollingTransport)
|
|
@@ -655,14 +731,12 @@ class GraphRunner {
|
|
|
655
731
|
throw new Error("Remote backend not configured");
|
|
656
732
|
}
|
|
657
733
|
const runner = new sparkRemote.RemoteRunner(transport);
|
|
658
|
-
this.
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
listenersBound: false,
|
|
663
|
-
};
|
|
734
|
+
this.runner = runner;
|
|
735
|
+
this.transport = transport;
|
|
736
|
+
this.valueCache.clear();
|
|
737
|
+
this.listenersBound = false;
|
|
664
738
|
this.emit("transport", { state: "connected", kind });
|
|
665
|
-
return
|
|
739
|
+
return runner;
|
|
666
740
|
}
|
|
667
741
|
}
|
|
668
742
|
|
|
@@ -1335,6 +1409,23 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
|
|
|
1335
1409
|
});
|
|
1336
1410
|
const off7 = wb.on("error", add("workbench", "error"));
|
|
1337
1411
|
wb.refreshValidation();
|
|
1412
|
+
// Registry updates: swap registry and refresh graph validation/UI
|
|
1413
|
+
const offReg = runner.on("registry", (newReg) => {
|
|
1414
|
+
try {
|
|
1415
|
+
setRegistry(newReg);
|
|
1416
|
+
wb.setRegistry(newReg);
|
|
1417
|
+
// Trigger a graph update so the UI revalidates with new types/enums/nodes
|
|
1418
|
+
try {
|
|
1419
|
+
runner.update(wb.export());
|
|
1420
|
+
}
|
|
1421
|
+
catch {
|
|
1422
|
+
console.error("Failed to update graph definition after registry changed");
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
catch {
|
|
1426
|
+
console.error("Failed to handle registry changed event");
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1338
1429
|
return () => {
|
|
1339
1430
|
off1();
|
|
1340
1431
|
off2();
|
|
@@ -1347,6 +1438,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
|
|
|
1347
1438
|
off5b();
|
|
1348
1439
|
off6();
|
|
1349
1440
|
off7();
|
|
1441
|
+
offReg();
|
|
1350
1442
|
};
|
|
1351
1443
|
}, [runner, wb]);
|
|
1352
1444
|
// Push incremental updates into running engine without full reload
|
|
@@ -1528,7 +1620,7 @@ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWork
|
|
|
1528
1620
|
return (jsxRuntime.jsxs("div", { className: "flex flex-col h-full min-h-0", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Events" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: hideWorkbench, onChange: (e) => onHideWorkbenchChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Hide workbench" })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: autoScroll, onChange: (e) => onAutoScrollChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Auto scroll" })] }), jsxRuntime.jsx("button", { onClick: clearEvents, className: "text-xs px-2 py-0.5 border border-gray-300 rounded", children: "Clear" })] })] }), jsxRuntime.jsx("div", { ref: scrollRef, className: "flex-1 overflow-auto text-[11px] leading-4 divide-y divide-gray-200", children: rows.map((ev, idx) => (jsxRuntime.jsxs("div", { className: "opacity-85 odd:bg-gray-50 px-2 py-1", children: [jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [jsxRuntime.jsx("span", { className: "w-8 shrink-0 text-right text-gray-500 select-none", children: idx + 1 }), jsxRuntime.jsxs("span", { className: "text-gray-500", children: [new Date(ev.at).toLocaleTimeString(), " \u00B7 ", ev.source, ":", ev.type] })] }), jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap ml-10", children: renderPayload(ev.payload) })] }, `${ev.at}:${idx}`))) })] }));
|
|
1529
1621
|
}
|
|
1530
1622
|
|
|
1531
|
-
function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString, toElement, setInput, }) {
|
|
1623
|
+
function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString, toElement, contextPanel, setInput, }) {
|
|
1532
1624
|
const safeToString = (typeId, value) => {
|
|
1533
1625
|
try {
|
|
1534
1626
|
if (typeof toString === "function") {
|
|
@@ -1617,7 +1709,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
1617
1709
|
setOriginals(nextOriginals);
|
|
1618
1710
|
}, [selectedNodeId, selectedDesc, valuesTick]);
|
|
1619
1711
|
const widthClass = debug ? "w-[480px]" : "w-[320px]";
|
|
1620
|
-
return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedEdge.typeId] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.lastError && (jsxRuntime.jsx("div", { className: "mt-2 text-sm text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 break-words", children: String(selectedNodeStatus.lastError?.message ??
|
|
1712
|
+
return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [contextPanel && (jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel })), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedEdge.typeId] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.lastError && (jsxRuntime.jsx("div", { className: "mt-2 text-sm text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 break-words", children: String(selectedNodeStatus.lastError?.message ??
|
|
1621
1713
|
selectedNodeStatus.lastError) }))] })), jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
|
|
1622
1714
|
const typeId = sparkGraph.getInputTypeId(selectedDesc?.inputs, h);
|
|
1623
1715
|
const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
|
|
@@ -1722,10 +1814,8 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
1722
1814
|
const vIssues = validation.inputs.filter((v) => v.handle === entry.id);
|
|
1723
1815
|
const hasAny = vIssues.length > 0;
|
|
1724
1816
|
const hasErr = vIssues.some((v) => v.level === "error");
|
|
1725
|
-
const title = vIssues
|
|
1726
|
-
|
|
1727
|
-
.join("; ");
|
|
1728
|
-
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "target", position: ReactFlow.Position.Left, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { left: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute left-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
|
|
1817
|
+
const title = vIssues.map((v) => `${v.code}: ${v.message}`).join("; ");
|
|
1818
|
+
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(react$1.Handle, { id: entry.id, type: "target", position: react$1.Position.Left, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { left: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute left-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
|
|
1729
1819
|
top: topFor(i) - 8,
|
|
1730
1820
|
right: "50%",
|
|
1731
1821
|
whiteSpace: "nowrap",
|
|
@@ -1736,11 +1826,9 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
1736
1826
|
const vIssues = validation.outputs.filter((v) => v.handle === entry.id);
|
|
1737
1827
|
const hasAny = vIssues.length > 0;
|
|
1738
1828
|
const hasErr = vIssues.some((v) => v.level === "error");
|
|
1739
|
-
const title = vIssues
|
|
1740
|
-
.map((v) => `${v.code}: ${v.message}`)
|
|
1741
|
-
.join("; ");
|
|
1829
|
+
const title = vIssues.map((v) => `${v.code}: ${v.message}`).join("; ");
|
|
1742
1830
|
const resolved = resolveOutputDisplay(outputValues?.[entry.id], entry.typeId);
|
|
1743
|
-
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(
|
|
1831
|
+
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(react$1.Handle, { id: entry.id, type: "source", position: react$1.Position.Right, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400 !rounded-none", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { right: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute right-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
|
|
1744
1832
|
top: topFor(i) - 8,
|
|
1745
1833
|
textAlign: "right",
|
|
1746
1834
|
left: "50%",
|
|
@@ -1754,7 +1842,7 @@ DefaultNode.displayName = "DefaultNode";
|
|
|
1754
1842
|
|
|
1755
1843
|
function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
1756
1844
|
const { registry } = useWorkbenchContext();
|
|
1757
|
-
const rf =
|
|
1845
|
+
const rf = react$1.useReactFlow();
|
|
1758
1846
|
const ids = Array.from(registry.nodes.keys());
|
|
1759
1847
|
const [query, setQuery] = React.useState("");
|
|
1760
1848
|
const q = query.trim().toLowerCase();
|
|
@@ -1961,7 +2049,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement }, r
|
|
|
1961
2049
|
selectedNodeIds: new Set(sel.nodes),
|
|
1962
2050
|
selectedEdgeIds: new Set(sel.edges),
|
|
1963
2051
|
});
|
|
1964
|
-
console.info(nodeTypes, out);
|
|
2052
|
+
// console.info(nodeTypes, out);
|
|
1965
2053
|
return out;
|
|
1966
2054
|
}, [
|
|
1967
2055
|
showValues,
|
|
@@ -2003,7 +2091,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement }, r
|
|
|
2003
2091
|
const addNodeAt = (typeId, pos) => {
|
|
2004
2092
|
wb.addNode({ typeId, position: pos });
|
|
2005
2093
|
};
|
|
2006
|
-
return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, selectionOnDrag: true, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onSelectionChange: onSelectionChange, deleteKeyCode: ["Backspace", "Delete"], fitView: true, onInit: (inst) => (rfInstanceRef.current = inst), children: [jsxRuntime.jsx(
|
|
2094
|
+
return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsxs(react$1.ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, selectionOnDrag: true, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onSelectionChange: onSelectionChange, deleteKeyCode: ["Backspace", "Delete"], fitView: true, onInit: (inst) => (rfInstanceRef.current = inst), children: [jsxRuntime.jsx(react$1.Background, {}), jsxRuntime.jsx(react$1.MiniMap, {}), jsxRuntime.jsx(react$1.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: () => setMenuOpen(false) }), jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: () => setNodeMenuOpen(false) })] }) }));
|
|
2007
2095
|
});
|
|
2008
2096
|
|
|
2009
2097
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
@@ -2080,7 +2168,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2080
2168
|
runAutoLayout();
|
|
2081
2169
|
};
|
|
2082
2170
|
onInit({ wb, runner, setInitialGraph });
|
|
2083
|
-
}, [onInit, wb, runner, runAutoLayout]);
|
|
2171
|
+
}, [onInit, wb, runner, runAutoLayout, registry, setRegistry]);
|
|
2084
2172
|
// Expose change callback on graph/value changes
|
|
2085
2173
|
React.useEffect(() => {
|
|
2086
2174
|
if (!onChange)
|
|
@@ -2235,6 +2323,9 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2235
2323
|
return;
|
|
2236
2324
|
if (runner.isRunning())
|
|
2237
2325
|
return;
|
|
2326
|
+
// Only auto-launch for local backend; require explicit Start for remote
|
|
2327
|
+
if (backendKind !== "local")
|
|
2328
|
+
return;
|
|
2238
2329
|
const d = wb.export();
|
|
2239
2330
|
if (!d.nodes || d.nodes.length === 0)
|
|
2240
2331
|
return;
|
|
@@ -2247,14 +2338,14 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2247
2338
|
catch {
|
|
2248
2339
|
// ignore
|
|
2249
2340
|
}
|
|
2250
|
-
}, [engine, runner, wb]);
|
|
2341
|
+
}, [engine, runner, wb, backendKind]);
|
|
2251
2342
|
// When switching to remote backend, auto-hydrate registry from backend
|
|
2252
2343
|
React.useEffect(() => {
|
|
2253
2344
|
if (backendKind === "remote-http" && httpBaseUrl) {
|
|
2254
|
-
|
|
2345
|
+
hydrateFromBackend("remote-http", httpBaseUrl);
|
|
2255
2346
|
}
|
|
2256
2347
|
else if (backendKind === "remote-ws" && wsUrl) {
|
|
2257
|
-
|
|
2348
|
+
hydrateFromBackend("remote-ws", wsUrl);
|
|
2258
2349
|
}
|
|
2259
2350
|
}, [backendKind, httpBaseUrl, wsUrl, hydrateFromBackend]);
|
|
2260
2351
|
React.useEffect(() => {
|
|
@@ -2441,18 +2532,22 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2441
2532
|
catch (err) {
|
|
2442
2533
|
alert(String(err?.message ?? err));
|
|
2443
2534
|
}
|
|
2444
|
-
}, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded px-2 py-1.5", onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: downloadGraph, children: "Download Graph" }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Debug events" })] }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Show values in nodes" })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement })] })] }));
|
|
2535
|
+
}, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded px-2 py-1.5", onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: downloadGraph, children: "Download Graph" }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Debug events" })] }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Show values in nodes" })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement, contextPanel: overrides?.contextPanel })] })] }));
|
|
2445
2536
|
}
|
|
2446
2537
|
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, overrides, onInit, onChange, }) {
|
|
2447
2538
|
const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
|
|
2448
2539
|
const [wb] = React.useState(() => new InMemoryWorkbench({ ui: new DefaultUIExtensionRegistry() }));
|
|
2449
2540
|
const runner = React.useMemo(() => {
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2541
|
+
if (backendKind === "remote-http") {
|
|
2542
|
+
return new RemoteGraphRunner(registry, {
|
|
2543
|
+
kind: "remote-http",
|
|
2544
|
+
baseUrl: httpBaseUrl,
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
if (backendKind === "remote-ws") {
|
|
2548
|
+
return new RemoteGraphRunner(registry, { kind: "remote-ws", url: wsUrl });
|
|
2549
|
+
}
|
|
2550
|
+
return new LocalGraphRunner(registry);
|
|
2456
2551
|
}, [registry, backendKind, httpBaseUrl, wsUrl]);
|
|
2457
2552
|
// Allow external UI registration (e.g., node renderers) with access to wb
|
|
2458
2553
|
React.useEffect(() => {
|
|
@@ -2470,9 +2565,10 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
2470
2565
|
exports.AbstractWorkbench = AbstractWorkbench;
|
|
2471
2566
|
exports.CLIWorkbench = CLIWorkbench;
|
|
2472
2567
|
exports.DefaultUIExtensionRegistry = DefaultUIExtensionRegistry;
|
|
2473
|
-
exports.GraphRunner = GraphRunner;
|
|
2474
2568
|
exports.InMemoryWorkbench = InMemoryWorkbench;
|
|
2475
2569
|
exports.Inspector = Inspector;
|
|
2570
|
+
exports.LocalGraphRunner = LocalGraphRunner;
|
|
2571
|
+
exports.RemoteGraphRunner = RemoteGraphRunner;
|
|
2476
2572
|
exports.WorkbenchCanvas = WorkbenchCanvas;
|
|
2477
2573
|
exports.WorkbenchContext = WorkbenchContext;
|
|
2478
2574
|
exports.WorkbenchProvider = WorkbenchProvider;
|