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