@bian-womp/spark-workbench 0.2.48 → 0.2.49
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 +165 -85
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/Inspector.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +1 -0
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +2 -0
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +1 -0
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +8 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +166 -86
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/Inspector.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +1 -0
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +2 -0
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +1 -0
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +8 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -334,6 +334,7 @@ class AbstractGraphRunner {
|
|
|
334
334
|
this.backend = backend;
|
|
335
335
|
this.listeners = new Map();
|
|
336
336
|
this.stagedInputs = {};
|
|
337
|
+
this.runnerId = "";
|
|
337
338
|
}
|
|
338
339
|
launch(def, opts) {
|
|
339
340
|
// Auto-stop if engine is already running
|
|
@@ -454,6 +455,8 @@ class AbstractGraphRunner {
|
|
|
454
455
|
}
|
|
455
456
|
}
|
|
456
457
|
|
|
458
|
+
// Counter for generating readable runner IDs
|
|
459
|
+
let localRunnerCounter = 0;
|
|
457
460
|
class LocalGraphRunner extends AbstractGraphRunner {
|
|
458
461
|
constructor(registry) {
|
|
459
462
|
super(registry, { kind: "local" });
|
|
@@ -472,7 +475,11 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
472
475
|
this.getEnvironment = () => {
|
|
473
476
|
return this.runtime?.getEnvironment?.();
|
|
474
477
|
};
|
|
475
|
-
this
|
|
478
|
+
// Generate readable ID for this runner instance (e.g., local-001, local-002)
|
|
479
|
+
localRunnerCounter++;
|
|
480
|
+
this.runnerId = `local-${String(localRunnerCounter).padStart(3, "0")}`;
|
|
481
|
+
console.info(`[LocalGraphRunner] Created runner with ID: ${this.runnerId}`);
|
|
482
|
+
this.emit("transport", { runnerId: this.runnerId, state: "local" });
|
|
476
483
|
}
|
|
477
484
|
build(def) {
|
|
478
485
|
const builder = new sparkGraph.GraphBuilder(this.registry);
|
|
@@ -624,10 +631,12 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
624
631
|
dispose() {
|
|
625
632
|
super.dispose();
|
|
626
633
|
this.runtime = undefined;
|
|
627
|
-
this.emit("transport", { state: "local" });
|
|
634
|
+
this.emit("transport", { runnerId: this.runnerId, state: "local" });
|
|
628
635
|
}
|
|
629
636
|
}
|
|
630
637
|
|
|
638
|
+
// Counter for generating readable runner IDs
|
|
639
|
+
let remoteRunnerCounter = 0;
|
|
631
640
|
class RemoteGraphRunner extends AbstractGraphRunner {
|
|
632
641
|
/**
|
|
633
642
|
* Fetch full registry description from remote and register it locally.
|
|
@@ -766,13 +775,20 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
766
775
|
setupClientSubscriptions(client) {
|
|
767
776
|
// Subscribe to transport status changes
|
|
768
777
|
// Convert RuntimeApiClient.TransportStatus to IGraphRunner.TransportStatus
|
|
778
|
+
// Only emit status if it matches this runner's ID
|
|
769
779
|
this.transportStatusUnsubscribe = client.onTransportStatus((status) => {
|
|
780
|
+
if (status.runnerId && status.runnerId !== this.runnerId)
|
|
781
|
+
return;
|
|
770
782
|
// Map remote-unix to undefined since RemoteGraphRunner doesn't support it
|
|
771
783
|
const mappedKind = status.kind === "remote-unix" ? undefined : status.kind;
|
|
772
|
-
|
|
784
|
+
const transportStatus = {
|
|
773
785
|
state: status.state,
|
|
774
786
|
kind: mappedKind,
|
|
775
|
-
|
|
787
|
+
runnerId: this.runnerId,
|
|
788
|
+
};
|
|
789
|
+
// Track current status
|
|
790
|
+
this.currentTransportStatus = transportStatus;
|
|
791
|
+
this.emit("transport", transportStatus);
|
|
776
792
|
});
|
|
777
793
|
}
|
|
778
794
|
// Ensure remote client
|
|
@@ -793,6 +809,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
793
809
|
// Create client with custom event handler if provided
|
|
794
810
|
const client = new sparkRemote.RuntimeApiClient(clientConfig, {
|
|
795
811
|
onCustomEvent: backend.onCustomEvent,
|
|
812
|
+
runnerId: this.runnerId,
|
|
796
813
|
});
|
|
797
814
|
// Setup event subscriptions
|
|
798
815
|
this.setupClientSubscriptions(client);
|
|
@@ -825,6 +842,15 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
825
842
|
this.registryFetching = false;
|
|
826
843
|
this.MAX_REGISTRY_FETCH_ATTEMPTS = 3;
|
|
827
844
|
this.INITIAL_RETRY_DELAY_MS = 1000; // 1 second
|
|
845
|
+
// Generate readable ID for this runner instance (e.g., remote-001, remote-002)
|
|
846
|
+
remoteRunnerCounter++;
|
|
847
|
+
this.runnerId = `remote-${String(remoteRunnerCounter).padStart(3, "0")}`;
|
|
848
|
+
console.info(`[RemoteGraphRunner] Created runner with ID: ${this.runnerId}`);
|
|
849
|
+
// Initialize transport status as "connecting" - will be updated when connection completes
|
|
850
|
+
this.currentTransportStatus = {
|
|
851
|
+
state: "connecting",
|
|
852
|
+
kind: backend.kind,
|
|
853
|
+
};
|
|
828
854
|
// Auto-handle registry-changed invalidations from remote
|
|
829
855
|
// We listen on invalidate and if reason matches, we rehydrate registry and emit a registry event
|
|
830
856
|
this.ensureClient().then(async (client) => {
|
|
@@ -988,13 +1014,16 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
988
1014
|
await this.whenIdle();
|
|
989
1015
|
// Capture current state
|
|
990
1016
|
const currentInputs = { ...this.stagedInputs };
|
|
991
|
-
//
|
|
992
|
-
|
|
993
|
-
//
|
|
1017
|
+
// For remote runners, we cannot call this.stop() because it sends a Dispose
|
|
1018
|
+
// command that destroys the graphRuntime on the backend. Instead, we rely on
|
|
1019
|
+
// the backend's launch() method to dispose the old engine and create a new one.
|
|
1020
|
+
// Reconfigure engine on the backend (this will dispose old engine and create new one)
|
|
994
1021
|
const client = await this.ensureClient();
|
|
995
1022
|
await client.launch(opts);
|
|
996
|
-
// Get the remote engine proxy
|
|
1023
|
+
// Get the remote engine proxy (should be the same RemoteEngine instance)
|
|
997
1024
|
const eng = client.getEngine();
|
|
1025
|
+
// Update local state to reflect new engine kind
|
|
1026
|
+
// Note: The RemoteEngine instance itself doesn't change, but the backend engine does
|
|
998
1027
|
this.engine = eng;
|
|
999
1028
|
this.runningKind = opts?.engine ?? "push";
|
|
1000
1029
|
this.emit("status", { running: true, engine: this.runningKind });
|
|
@@ -1162,6 +1191,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1162
1191
|
if (this.disposed)
|
|
1163
1192
|
return;
|
|
1164
1193
|
this.disposed = true;
|
|
1194
|
+
console.info(`[RemoteGraphRunner] Disposing runner with ID: ${this.runnerId}`);
|
|
1165
1195
|
super.dispose();
|
|
1166
1196
|
// Clear client promise if any
|
|
1167
1197
|
this.clientPromise = undefined;
|
|
@@ -1181,10 +1211,30 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1181
1211
|
console.warn("[RemoteGraphRunner] Error disposing client:", err);
|
|
1182
1212
|
});
|
|
1183
1213
|
}
|
|
1184
|
-
|
|
1214
|
+
const disconnectedStatus = {
|
|
1185
1215
|
state: "disconnected",
|
|
1186
1216
|
kind: this.backend.kind,
|
|
1187
|
-
|
|
1217
|
+
runnerId: this.runnerId,
|
|
1218
|
+
};
|
|
1219
|
+
this.currentTransportStatus = disconnectedStatus;
|
|
1220
|
+
this.emit("transport", disconnectedStatus);
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Override on() to emit current transport status immediately when a new listener subscribes.
|
|
1224
|
+
* This ensures listeners don't miss the current status when they attach after connection.
|
|
1225
|
+
*/
|
|
1226
|
+
on(event, handler) {
|
|
1227
|
+
const unsubscribe = super.on(event, handler);
|
|
1228
|
+
// If subscribing to transport events and we have a current status, emit it immediately
|
|
1229
|
+
if (event === "transport" && this.currentTransportStatus) {
|
|
1230
|
+
// Use setTimeout to ensure this happens after the listener is registered
|
|
1231
|
+
// This prevents issues if the handler modifies state synchronously
|
|
1232
|
+
setTimeout(() => {
|
|
1233
|
+
// Type assertion is safe here because we checked event === "transport"
|
|
1234
|
+
handler(this.currentTransportStatus);
|
|
1235
|
+
}, 0);
|
|
1236
|
+
}
|
|
1237
|
+
return unsubscribe;
|
|
1188
1238
|
}
|
|
1189
1239
|
}
|
|
1190
1240
|
|
|
@@ -2371,15 +2421,27 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2371
2421
|
offRunnerTransport();
|
|
2372
2422
|
};
|
|
2373
2423
|
}, [runner, wb, setRegistry]);
|
|
2424
|
+
const isRunning = React.useCallback(() => runner.isRunning(), [runner]);
|
|
2425
|
+
const engineKind = React.useCallback(() => runner.getRunningEngine(), [runner]);
|
|
2426
|
+
const start = React.useCallback((engine) => {
|
|
2427
|
+
try {
|
|
2428
|
+
runner.launch(wb.export(), { engine });
|
|
2429
|
+
}
|
|
2430
|
+
catch { }
|
|
2431
|
+
}, [runner, wb]);
|
|
2432
|
+
const stop = React.useCallback(() => runner.stop(), [runner]);
|
|
2433
|
+
const step = React.useCallback(() => runner.step(), [runner]);
|
|
2434
|
+
const flush = React.useCallback(() => runner.flush(), [runner]);
|
|
2374
2435
|
// Push incremental updates into running engine without full reload
|
|
2436
|
+
const isGraphRunning = isRunning();
|
|
2375
2437
|
React.useEffect(() => {
|
|
2376
|
-
if (
|
|
2438
|
+
if (isGraphRunning) {
|
|
2377
2439
|
try {
|
|
2378
2440
|
runner.update(def);
|
|
2379
2441
|
}
|
|
2380
2442
|
catch { }
|
|
2381
2443
|
}
|
|
2382
|
-
}, [runner, def, graphTick]);
|
|
2444
|
+
}, [runner, isGraphRunning, def, graphTick]);
|
|
2383
2445
|
const validationByNode = React.useMemo(() => {
|
|
2384
2446
|
const inputs = {};
|
|
2385
2447
|
const outputs = {};
|
|
@@ -2443,17 +2505,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2443
2505
|
}
|
|
2444
2506
|
return { errors, issues };
|
|
2445
2507
|
}, [validation]);
|
|
2446
|
-
const isRunning = React.useCallback(() => runner.isRunning(), [runner]);
|
|
2447
|
-
const engineKind = React.useCallback(() => runner.getRunningEngine(), [runner]);
|
|
2448
|
-
const start = React.useCallback((engine) => {
|
|
2449
|
-
try {
|
|
2450
|
-
runner.launch(wb.export(), { engine });
|
|
2451
|
-
}
|
|
2452
|
-
catch { }
|
|
2453
|
-
}, [runner, wb]);
|
|
2454
|
-
const stop = React.useCallback(() => runner.stop(), [runner]);
|
|
2455
|
-
const step = React.useCallback(() => runner.step(), [runner]);
|
|
2456
|
-
const flush = React.useCallback(() => runner.flush(), [runner]);
|
|
2457
2508
|
const value = React.useMemo(() => ({
|
|
2458
2509
|
wb,
|
|
2459
2510
|
runner,
|
|
@@ -2659,6 +2710,42 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2659
2710
|
outputs: nodeValidationHandles?.outputs?.[selectedNodeId] ?? [],
|
|
2660
2711
|
}
|
|
2661
2712
|
: { inputs: [], outputs: [] };
|
|
2713
|
+
// Render edge status indicator
|
|
2714
|
+
const renderEdgeStatus = React.useCallback(() => {
|
|
2715
|
+
if (!selectedEdge)
|
|
2716
|
+
return null;
|
|
2717
|
+
const status = edgeStatus?.[selectedEdge.id];
|
|
2718
|
+
if (status?.activeRuns > 0) {
|
|
2719
|
+
return (jsxRuntime.jsxs("div", { className: "mt-1 text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1", children: [jsxRuntime.jsxs("div", { className: "font-semibold", children: ["Running (", status.activeRuns, ")"] }), jsxRuntime.jsx("div", { className: "text-[10px] text-blue-600 mt-1", children: "Note: Edge runIds are not available in stats events" })] }));
|
|
2720
|
+
}
|
|
2721
|
+
return null;
|
|
2722
|
+
}, [selectedEdge, edgeStatus]);
|
|
2723
|
+
// Render linked input display value
|
|
2724
|
+
const renderLinkedInputDisplay = React.useCallback((typeId, current) => {
|
|
2725
|
+
const displayStr = safeToString(typeId, current);
|
|
2726
|
+
const isLong = displayStr.length > 50;
|
|
2727
|
+
const truncated = isLong ? truncateValue(displayStr) : displayStr;
|
|
2728
|
+
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("span", { className: "truncate", children: truncated }), isLong && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded", onClick: () => copyToClipboard(displayStr), title: "Copy full value", children: jsxRuntime.jsx(react$1.CopyIcon, { size: 14 }) }))] }));
|
|
2729
|
+
}, [safeToString, truncateValue, copyToClipboard]);
|
|
2730
|
+
// Render output validation issues badge
|
|
2731
|
+
const renderOutputValidationBadge = React.useCallback((handle) => {
|
|
2732
|
+
const outIssues = selectedNodeHandleValidation.outputs.filter((m) => m.handle === handle);
|
|
2733
|
+
if (outIssues.length === 0)
|
|
2734
|
+
return null;
|
|
2735
|
+
const outErr = outIssues.some((m) => m.level === "error");
|
|
2736
|
+
const outTitle = outIssues
|
|
2737
|
+
.map((v) => `${v.code}: ${v.message}`)
|
|
2738
|
+
.join("; ");
|
|
2739
|
+
return (jsxRuntime.jsx(IssueBadge, { level: outErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: outTitle }));
|
|
2740
|
+
}, [selectedNodeHandleValidation]);
|
|
2741
|
+
// Render output display value
|
|
2742
|
+
const renderOutputDisplay = React.useCallback((outputValue, effectiveHandle) => {
|
|
2743
|
+
const { typeId, value } = resolveOutputDisplay(outputValue, effectiveHandle);
|
|
2744
|
+
const displayStr = safeToString(typeId, value);
|
|
2745
|
+
const isLong = displayStr.length > 50;
|
|
2746
|
+
const truncated = isLong ? truncateValue(displayStr) : displayStr;
|
|
2747
|
+
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("span", { className: "truncate", children: truncated }), isLong && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded", onClick: () => copyToClipboard(displayStr), title: "Copy full value", children: jsxRuntime.jsx(react$1.CopyIcon, { size: 14 }) }))] }));
|
|
2748
|
+
}, [safeToString, truncateValue, copyToClipboard]);
|
|
2662
2749
|
// Local drafts and originals for commit-on-blur/enter behavior
|
|
2663
2750
|
const [drafts, setDrafts] = React.useState({});
|
|
2664
2751
|
const [originals, setOriginals] = React.useState({});
|
|
@@ -2716,10 +2803,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2716
2803
|
return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-auto`, children: [jsxRuntime.jsxs("div", { className: "flex-1 overflow-auto", children: [contextPanel && jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel }), inputValidationErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [inputValidationErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Input Validation Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message }), jsxRuntime.jsxs("div", { className: "text-[10px] text-red-600 mt-1", children: [err.nodeId, ".", err.handle, " (type: ", err.typeId, ")"] })] }), jsxRuntime.jsx("button", { className: "text-red-500 hover:text-red-700 text-[10px] px-1", onClick: () => removeInputValidationError(i), title: "Dismiss", children: jsxRuntime.jsx(react$1.XIcon, { size: 10 }) })] }, i))), inputValidationErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-red-600 hover:text-red-800 underline", onClick: clearInputValidationErrors, children: "Clear all" }))] })), systemErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [systemErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: err.code ? `Error ${err.code}` : "Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message })] }), jsxRuntime.jsx("button", { className: "text-red-500 hover:text-red-700 text-[10px] px-1", onClick: () => removeSystemError(i), title: "Dismiss", children: jsxRuntime.jsx(react$1.XIcon, { size: 10 }) })] }, i))), systemErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-red-600 hover:text-red-800 underline", onClick: clearSystemErrors, children: "Clear all" }))] })), registryErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [registryErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Registry Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message }), err.attempt && err.maxAttempts && (jsxRuntime.jsxs("div", { className: "text-[10px] text-amber-600 mt-1", children: ["Attempt ", err.attempt, " of ", err.maxAttempts] }))] }), jsxRuntime.jsx("button", { className: "text-amber-500 hover:text-amber-700 text-[10px] px-1", onClick: () => removeRegistryError(i), title: "Dismiss", children: jsxRuntime.jsx(react$1.XIcon, { size: 10 }) })] }, i))), registryErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-amber-600 hover:text-amber-800 underline", onClick: clearRegistryErrors, children: "Clear all" }))] })), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1", 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}` }), !!m.data?.edgeId && (jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
|
|
2717
2804
|
e.stopPropagation();
|
|
2718
2805
|
deleteEdgeById(m.data?.edgeId);
|
|
2719
|
-
}, title: "Delete referenced edge", children: "Delete edge" }))] }, 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] }), (() => {
|
|
2720
|
-
const status = edgeStatus?.[selectedEdge.id];
|
|
2721
|
-
return status?.activeRuns > 0 ? (jsxRuntime.jsxs("div", { className: "mt-1 text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1", children: [jsxRuntime.jsxs("div", { className: "font-semibold", children: ["Running (", status.activeRuns, ")"] }), jsxRuntime.jsx("div", { className: "text-[10px] text-blue-600 mt-1", children: "Note: Edge runIds are not available in stats events" })] })) : null;
|
|
2722
|
-
})(), jsxRuntime.jsx("div", { className: "mt-1", children: jsxRuntime.jsx("button", { className: "text-xs px-2 py-1 border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
|
|
2806
|
+
}, title: "Delete referenced edge", children: "Delete edge" }))] }, 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] }), renderEdgeStatus(), jsxRuntime.jsx("div", { className: "mt-1", children: jsxRuntime.jsx("button", { className: "text-xs px-2 py-1 border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
|
|
2723
2807
|
e.stopPropagation();
|
|
2724
2808
|
deleteEdgeById(selectedEdge.id);
|
|
2725
2809
|
}, title: "Delete this edge", children: "Delete edge" }) }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-1", children: [jsxRuntime.jsxs("label", { className: "w-20 flex flex-col", children: [jsxRuntime.jsx("span", { children: "Type" }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: "DataTypeId" })] }), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full", value: selectedEdge.typeId ?? "", onChange: (e) => {
|
|
@@ -2798,14 +2882,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2798
2882
|
? `Default: ${placeholder}`
|
|
2799
2883
|
: "(select)" }), registry.enums
|
|
2800
2884
|
.get(typeId)
|
|
2801
|
-
?.options.map((opt) => (jsxRuntime.jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsxRuntime.jsx(react$1.XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsxRuntime.jsx("div", { className: "flex items-center gap-1 flex-1", children: jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: (
|
|
2802
|
-
const displayStr = safeToString(typeId, current);
|
|
2803
|
-
const isLong = displayStr.length > 50;
|
|
2804
|
-
const truncated = isLong
|
|
2805
|
-
? truncateValue(displayStr)
|
|
2806
|
-
: displayStr;
|
|
2807
|
-
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("span", { className: "truncate", children: truncated }), isLong && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded", onClick: () => copyToClipboard(displayStr), title: "Copy full value", children: jsxRuntime.jsx(react$1.CopyIcon, { size: 14 }) }))] }));
|
|
2808
|
-
})() }) })) : (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxRuntime.jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1", placeholder: placeholder
|
|
2885
|
+
?.options.map((opt) => (jsxRuntime.jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsxRuntime.jsx(react$1.XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsxRuntime.jsx("div", { className: "flex items-center gap-1 flex-1", children: jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: renderLinkedInputDisplay(typeId, current) }) })) : (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxRuntime.jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1", placeholder: placeholder
|
|
2809
2886
|
? `Default: ${placeholder}`
|
|
2810
2887
|
: undefined, value: displayValue, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
|
|
2811
2888
|
if (e.key === "Enter")
|
|
@@ -2813,24 +2890,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2813
2890
|
if (e.key === "Escape")
|
|
2814
2891
|
revert();
|
|
2815
2892
|
}, ...commonProps }), hasValue && !isLinked && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsxRuntime.jsx(react$1.XCircleIcon, { size: 16 }) }))] }))] }, h));
|
|
2816
|
-
}))] }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Outputs" }), outputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No outputs" })) : (outputHandles.map((h) => (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsxs("label", { className: "w-20 flex flex-col", children: [jsxRuntime.jsx("span", { children: prettyHandle(h) }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: outputTypesMap[selectedNodeId]?.[h] ?? "" })] }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: (() => {
|
|
2817
|
-
const { typeId, value } = resolveOutputDisplay(nodeOutputs[h], effectiveHandles.outputs[h]);
|
|
2818
|
-
const displayStr = safeToString(typeId, value);
|
|
2819
|
-
const isLong = displayStr.length > 50;
|
|
2820
|
-
const truncated = isLong
|
|
2821
|
-
? truncateValue(displayStr)
|
|
2822
|
-
: displayStr;
|
|
2823
|
-
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("span", { className: "truncate", children: truncated }), isLong && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded", onClick: () => copyToClipboard(displayStr), title: "Copy full value", children: jsxRuntime.jsx(react$1.CopyIcon, { size: 14 }) }))] }));
|
|
2824
|
-
})() }), (() => {
|
|
2825
|
-
const outIssues = selectedNodeHandleValidation.outputs.filter((m) => m.handle === h);
|
|
2826
|
-
if (outIssues.length === 0)
|
|
2827
|
-
return null;
|
|
2828
|
-
const outErr = outIssues.some((m) => m.level === "error");
|
|
2829
|
-
const outTitle = outIssues
|
|
2830
|
-
.map((v) => `${v.code}: ${v.message}`)
|
|
2831
|
-
.join("; ");
|
|
2832
|
-
return (jsxRuntime.jsx(IssueBadge, { level: outErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: outTitle }));
|
|
2833
|
-
})()] }, h))))] }), selectedNodeValidation.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: selectedNodeValidation.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}` }), !!m.data?.edgeId && (jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
|
|
2893
|
+
}))] }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Outputs" }), outputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No outputs" })) : (outputHandles.map((h) => (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsxs("label", { className: "w-20 flex flex-col", children: [jsxRuntime.jsx("span", { children: prettyHandle(h) }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: outputTypesMap[selectedNodeId]?.[h] ?? "" })] }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: renderOutputDisplay(nodeOutputs[h], effectiveHandles.outputs[h]) }), renderOutputValidationBadge(h)] }, h))))] }), selectedNodeValidation.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: selectedNodeValidation.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}` }), !!m.data?.edgeId && (jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
|
|
2834
2894
|
e.stopPropagation();
|
|
2835
2895
|
deleteEdgeById(m.data?.edgeId);
|
|
2836
2896
|
}, title: "Delete referenced edge", children: "Delete edge" }))] }, i))) })] }))] })) })] }), debug && (jsxRuntime.jsx("div", { className: "mt-3 flex-none min-h-0 h-[50%]", children: jsxRuntime.jsx(DebugEvents, { autoScroll: !!autoScroll, hideWorkbench: !!hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange }) }))] }));
|
|
@@ -3585,6 +3645,44 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3585
3645
|
? computeEffectiveHandles(selectedNode, registry)
|
|
3586
3646
|
: { inputs: {}, outputs: {}, inputDefaults: {} };
|
|
3587
3647
|
const [exampleState, setExampleState] = React.useState(example ?? "");
|
|
3648
|
+
const isGraphRunning = runner.isRunning();
|
|
3649
|
+
const engineKind = runner.getRunningEngine();
|
|
3650
|
+
// Render Start/Stop button based on transport and runner state
|
|
3651
|
+
const renderStartStopButton = React.useCallback(() => {
|
|
3652
|
+
// Check if transport is connecting/retrying
|
|
3653
|
+
const isConnecting = transportStatus.state === "connecting" ||
|
|
3654
|
+
transportStatus.state === "retrying";
|
|
3655
|
+
// Only allow Start/Stop when transport is connected or local
|
|
3656
|
+
const canControl = transportStatus.state === "connected" ||
|
|
3657
|
+
transportStatus.state === "local";
|
|
3658
|
+
if (isConnecting) {
|
|
3659
|
+
return (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-gray-500 border-gray-400 flex items-center gap-1 disabled:opacity-50", disabled: true, title: "Connecting to backend...", children: [jsxRuntime.jsx(react$1.ClockClockwiseIcon, { size: 16, className: "animate-spin" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Connecting..." })] }));
|
|
3660
|
+
}
|
|
3661
|
+
if (isGraphRunning) {
|
|
3662
|
+
return (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-red-700 border-red-600 flex items-center gap-1 disabled:opacity-50 disabled:text-gray-400 disabled:border-gray-300", onClick: () => runner.stop(), disabled: !canControl, title: canControl ? "Stop engine" : "Waiting for connection", children: [jsxRuntime.jsx(react$1.StopIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Stop" })] }));
|
|
3663
|
+
}
|
|
3664
|
+
return (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-green-700 border-green-600 flex items-center gap-1 disabled:text-gray-400 disabled:border-gray-300 disabled:opacity-50", onClick: (evt) => {
|
|
3665
|
+
const kind = engine;
|
|
3666
|
+
if (!kind)
|
|
3667
|
+
return alert("Select an engine first.");
|
|
3668
|
+
if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
|
|
3669
|
+
return;
|
|
3670
|
+
try {
|
|
3671
|
+
runner.launch(wb.export(), {
|
|
3672
|
+
engine: kind,
|
|
3673
|
+
invalidate: evt.shiftKey,
|
|
3674
|
+
});
|
|
3675
|
+
}
|
|
3676
|
+
catch (err) {
|
|
3677
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3678
|
+
alert(message);
|
|
3679
|
+
}
|
|
3680
|
+
}, disabled: !engine || !canControl, title: !engine
|
|
3681
|
+
? "Select an engine first"
|
|
3682
|
+
: !canControl
|
|
3683
|
+
? "Waiting for connection"
|
|
3684
|
+
: "Start engine", children: [jsxRuntime.jsx(react$1.PlayIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
|
|
3685
|
+
}, [transportStatus, isGraphRunning, runner, engine, wb]);
|
|
3588
3686
|
const defaultExamples = React.useMemo(() => [
|
|
3589
3687
|
{
|
|
3590
3688
|
id: "simple",
|
|
@@ -3633,7 +3731,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3633
3731
|
return backendKind === "local";
|
|
3634
3732
|
});
|
|
3635
3733
|
// Expose init callback with setInitialGraph helper
|
|
3636
|
-
// Note: This runs whenever runner changes
|
|
3734
|
+
// Note: This runs whenever runner changes
|
|
3637
3735
|
React.useEffect(() => {
|
|
3638
3736
|
if (!onInit)
|
|
3639
3737
|
return;
|
|
@@ -3809,7 +3907,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3809
3907
|
applyExample(example);
|
|
3810
3908
|
}, [example, wb]);
|
|
3811
3909
|
React.useEffect(() => {
|
|
3812
|
-
const off = runner.on("transport",
|
|
3910
|
+
const off = runner.on("transport", setTransportStatus);
|
|
3813
3911
|
return () => off();
|
|
3814
3912
|
}, [runner]);
|
|
3815
3913
|
// Track registry readiness for remote backends
|
|
@@ -3830,7 +3928,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3830
3928
|
React.useEffect(() => {
|
|
3831
3929
|
if (!engine)
|
|
3832
3930
|
return;
|
|
3833
|
-
if (
|
|
3931
|
+
if (isGraphRunning)
|
|
3834
3932
|
return;
|
|
3835
3933
|
// Only auto-launch for local backend; require explicit Start for remote
|
|
3836
3934
|
if (backendKind !== "local")
|
|
@@ -3847,7 +3945,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3847
3945
|
catch {
|
|
3848
3946
|
// ignore
|
|
3849
3947
|
}
|
|
3850
|
-
}, [engine, runner, wb, backendKind]);
|
|
3948
|
+
}, [engine, runner, isGraphRunning, wb, backendKind]);
|
|
3851
3949
|
// Registry is automatically fetched by RemoteGraphRunner when it connects
|
|
3852
3950
|
// Run auto layout after registry is hydrated (for remote backends)
|
|
3853
3951
|
React.useEffect(() => {
|
|
@@ -4033,11 +4131,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
4033
4131
|
return overrides.toElement(baseToElement, { registry });
|
|
4034
4132
|
return baseToElement;
|
|
4035
4133
|
}, [overrides, baseToElement, registry]);
|
|
4036
|
-
return (jsxRuntime.jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxRuntime.jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [
|
|
4037
|
-
? "Stop engine before switching example"
|
|
4038
|
-
: undefined, children: [jsxRuntime.jsx("option", { value: "", children: "Select Example\u2026" }), examples.map((ex) => (jsxRuntime.jsx("option", { value: ex.id, children: ex.label }, ex.id)))] }), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: backendKind, onChange: (e) => onBackendKindChange(e.target.value), disabled: runner.isRunning(), title: runner.isRunning()
|
|
4039
|
-
? "Stop engine before switching backend"
|
|
4040
|
-
: undefined, children: [jsxRuntime.jsx("option", { value: "local", children: "Local" }), jsxRuntime.jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsxRuntime.jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && !!onHttpBaseUrlChange && (jsxRuntime.jsx("input", { className: "ml-2 border border-gray-300 rounded px-2 py-1 w-72", placeholder: "http://127.0.0.1:18080", value: httpBaseUrl, onChange: (e) => onHttpBaseUrlChange(e.target.value) })), backendKind === "remote-ws" && !!onWsUrlChange && (jsxRuntime.jsx("input", { className: "ml-2 border border-gray-300 rounded px-2 py-1 w-72", placeholder: "ws://127.0.0.1:18081", value: wsUrl, onChange: (e) => onWsUrlChange(e.target.value) })), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: runner.getRunningEngine() ?? engine ?? "", onChange: async (e) => {
|
|
4134
|
+
return (jsxRuntime.jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxRuntime.jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [isGraphRunning ? (jsxRuntime.jsxs("span", { className: "ml-2 text-sm text-green-700", children: ["Running: ", engineKind] })) : (jsxRuntime.jsx("span", { className: "ml-2 text-sm text-gray-500", children: "Stopped" })), jsxRuntime.jsxs("span", { className: "ml-2 flex items-center gap-1 text-xs", title: transportStatus.kind || undefined, children: [transportStatus.state === "local" && (jsxRuntime.jsx(react$1.PlugsConnectedIcon, { size: 14, className: "text-gray-500" })), transportStatus.state === "connecting" && (jsxRuntime.jsx(react$1.ClockClockwiseIcon, { size: 14, className: "text-amber-600 animate-pulse" })), transportStatus.state === "connected" && (jsxRuntime.jsx(react$1.WifiHighIcon, { size: 14, className: "text-green-600" })), transportStatus.state === "disconnected" && (jsxRuntime.jsx(react$1.WifiSlashIcon, { size: 14, className: "text-red-600" })), transportStatus.state === "retrying" && (jsxRuntime.jsx(react$1.ClockClockwiseIcon, { size: 14, className: "text-amber-700 animate-pulse" }))] }), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: exampleState, onChange: (e) => applyExample(e.target.value), disabled: isGraphRunning, title: isGraphRunning ? "Stop engine before switching example" : undefined, children: [jsxRuntime.jsx("option", { value: "", children: "Select Example\u2026" }), examples.map((ex) => (jsxRuntime.jsx("option", { value: ex.id, children: ex.label }, ex.id)))] }), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: backendKind, onChange: (e) => onBackendKindChange(e.target.value), disabled: isGraphRunning, title: isGraphRunning ? "Stop engine before switching backend" : undefined, children: [jsxRuntime.jsx("option", { value: "local", children: "Local" }), jsxRuntime.jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsxRuntime.jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && !!onHttpBaseUrlChange && (jsxRuntime.jsx("input", { className: "border border-gray-300 rounded px-2 py-1 w-72", placeholder: "http://127.0.0.1:18080", value: httpBaseUrl, onChange: (e) => onHttpBaseUrlChange(e.target.value) })), backendKind === "remote-ws" && !!onWsUrlChange && (jsxRuntime.jsx("input", { className: "border border-gray-300 rounded px-2 py-1 w-72", placeholder: "ws://127.0.0.1:18081", value: wsUrl, onChange: (e) => onWsUrlChange(e.target.value) })), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: engineKind ?? engine ?? "", onChange: async (e) => {
|
|
4041
4135
|
const kind = e.target.value || undefined;
|
|
4042
4136
|
const currentEngine = runner.getRunningEngine();
|
|
4043
4137
|
// If engine is running and user selected a different engine, switch it
|
|
@@ -4064,23 +4158,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
4064
4158
|
// Normal change when not running
|
|
4065
4159
|
onEngineChange?.(kind);
|
|
4066
4160
|
}
|
|
4067
|
-
}, children: [jsxRuntime.jsx("option", { value: "", children: "Select Engine\u2026" }), jsxRuntime.jsx("option", { value: "push", children: "Push" }), jsxRuntime.jsx("option", { value: "batched", children: "Batched" }), jsxRuntime.jsx("option", { value: "pull", children: "Pull" }), jsxRuntime.jsx("option", { value: "hybrid", children: "Hybrid" }), jsxRuntime.jsx("option", { value: "step", children: "Step" })] }),
|
|
4068
|
-
const kind = engine;
|
|
4069
|
-
if (!kind)
|
|
4070
|
-
return alert("Select an engine first.");
|
|
4071
|
-
if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
|
|
4072
|
-
return;
|
|
4073
|
-
try {
|
|
4074
|
-
runner.launch(wb.export(), {
|
|
4075
|
-
engine: kind,
|
|
4076
|
-
invalidate: evt.shiftKey,
|
|
4077
|
-
});
|
|
4078
|
-
}
|
|
4079
|
-
catch (err) {
|
|
4080
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4081
|
-
alert(message);
|
|
4082
|
-
}
|
|
4083
|
-
}, disabled: !engine, title: engine ? "Start engine" : "Select an engine first", children: [jsxRuntime.jsx(react$1.PlayIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Start" })] })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsxRuntime.jsx(react$1.TreeStructureIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsxRuntime.jsx(react$1.CornersOutIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded p-1", onClick: downloadGraph, children: jsxRuntime.jsx(react$1.DownloadSimpleIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded p-1", onClick: async () => {
|
|
4161
|
+
}, children: [jsxRuntime.jsx("option", { value: "", children: "Select Engine\u2026" }), jsxRuntime.jsx("option", { value: "push", children: "Push" }), jsxRuntime.jsx("option", { value: "batched", children: "Batched" }), jsxRuntime.jsx("option", { value: "pull", children: "Pull" }), jsxRuntime.jsx("option", { value: "hybrid", children: "Hybrid" }), jsxRuntime.jsx("option", { value: "step", children: "Step" })] }), engineKind === "step" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !isGraphRunning, title: "Step", children: jsxRuntime.jsx(react$1.PlayPauseIcon, { size: 24 }) })), engineKind === "batched" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !isGraphRunning, title: "Flush", children: jsxRuntime.jsx(react$1.LightningIcon, { size: 24 }) })), renderStartStopButton(), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsxRuntime.jsx(react$1.TreeStructureIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsxRuntime.jsx(react$1.CornersOutIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: downloadGraph, children: jsxRuntime.jsx(react$1.DownloadSimpleIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
|
|
4084
4162
|
try {
|
|
4085
4163
|
const def = wb.export();
|
|
4086
4164
|
const positions = wb.getPositions();
|
|
@@ -4109,7 +4187,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
4109
4187
|
const message = err instanceof Error ? err.message : String(err);
|
|
4110
4188
|
alert(message);
|
|
4111
4189
|
}
|
|
4112
|
-
}, children: jsxRuntime.jsx(react$1.DownloadIcon, { size: 24 }) }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "
|
|
4190
|
+
}, children: jsxRuntime.jsx(react$1.DownloadIcon, { size: 24 }) }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsxRuntime.jsx(react$1.UploadIcon, { size: 24 }) }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx(react$1.BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx(react$1.ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), 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, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, contextPanel: overrides?.contextPanel })] })] }));
|
|
4113
4191
|
}
|
|
4114
4192
|
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
|
|
4115
4193
|
const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
|
|
@@ -4173,6 +4251,7 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
4173
4251
|
}
|
|
4174
4252
|
};
|
|
4175
4253
|
}, [runner]);
|
|
4254
|
+
const isGraphRunning = runner.isRunning();
|
|
4176
4255
|
// Track UI registration version to trigger nodeTypes recomputation
|
|
4177
4256
|
const [uiVersion, setUiVersion] = React.useState(0);
|
|
4178
4257
|
// Allow external UI registration (e.g., node renderers) with access to wb
|
|
@@ -4183,11 +4262,12 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
4183
4262
|
setUiVersion((v) => v + 1);
|
|
4184
4263
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4185
4264
|
}, [wb, runner, overrides]);
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4265
|
+
const onBackendKindChangeWithDispose = React.useCallback(() => (v) => {
|
|
4266
|
+
if (isGraphRunning)
|
|
4267
|
+
runner.dispose();
|
|
4268
|
+
onBackendKindChange(v);
|
|
4269
|
+
}, [isGraphRunning]);
|
|
4270
|
+
return (jsxRuntime.jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, overrides: overrides, uiVersion: uiVersion, children: jsxRuntime.jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, engine: engine, onEngineChange: onEngineChange, backendKind: backendKind, onBackendKindChange: onBackendKindChangeWithDispose, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange, backendOptions: backendOptions, overrides: overrides, onInit: onInit, onChange: onChange }) }));
|
|
4191
4271
|
}
|
|
4192
4272
|
|
|
4193
4273
|
exports.AbstractWorkbench = AbstractWorkbench;
|