@bian-womp/spark-workbench 0.2.37 → 0.2.38
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 +340 -213
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/index.d.ts +1 -0
- package/lib/cjs/src/index.d.ts.map +1 -1
- package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/cjs/src/misc/Inspector.d.ts +1 -2
- 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/mapping.d.ts +19 -0
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +32 -17
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +337 -215
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/index.d.ts +1 -0
- package/lib/esm/src/index.d.ts.map +1 -1
- package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/esm/src/misc/Inspector.d.ts +1 -2
- 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/mapping.d.ts +19 -0
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +32 -17
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/esm/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { GraphBuilder, StepEngine, HybridEngine, PullEngine, BatchedEngine, PushEngine, isTypedOutput, getTypedOutputValue, getTypedOutputTypeId, isInputPrivate, getInputTypeId, createSimpleGraphRegistry, createSimpleGraphDef, createAsyncGraphDef, createAsyncGraphRegistry, createProgressGraphDef, createProgressGraphRegistry, createValidationGraphDef, createValidationGraphRegistry } from '@bian-womp/spark-graph';
|
|
2
|
-
import {
|
|
2
|
+
import { RuntimeApiClient } from '@bian-womp/spark-remote';
|
|
3
3
|
import React, { useCallback, useState, useRef, useEffect, useMemo, createContext, useContext, useImperativeHandle } from 'react';
|
|
4
4
|
import { Position, Handle, useUpdateNodeInternals, useReactFlow, ReactFlowProvider, ReactFlow, Background, BackgroundVariant, MiniMap, Controls } from '@xyflow/react';
|
|
5
|
+
import cx from 'classnames';
|
|
5
6
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
7
|
import { XCircleIcon, WarningCircleIcon, CopyIcon, TrashIcon, XIcon, ArrowClockwiseIcon, PlugsConnectedIcon, ClockClockwiseIcon, WifiHighIcon, WifiSlashIcon, PlayPauseIcon, LightningIcon, StopIcon, PlayIcon, TreeStructureIcon, CornersOutIcon, DownloadSimpleIcon, DownloadIcon, UploadIcon, BugBeetleIcon, ListBulletsIcon } from '@phosphor-icons/react';
|
|
7
|
-
import cx from 'classnames';
|
|
8
8
|
import isEqual from 'lodash/isEqual';
|
|
9
9
|
|
|
10
10
|
class DefaultUIExtensionRegistry {
|
|
@@ -600,8 +600,200 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
600
600
|
}
|
|
601
601
|
|
|
602
602
|
class RemoteGraphRunner extends AbstractGraphRunner {
|
|
603
|
+
/**
|
|
604
|
+
* Fetch full registry description from remote and register it locally.
|
|
605
|
+
* Called automatically on first connection with retry mechanism.
|
|
606
|
+
*/
|
|
607
|
+
async fetchRegistry(client, attempt = 1) {
|
|
608
|
+
if (this.registryFetching) {
|
|
609
|
+
// Already fetching, don't start another fetch
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
this.registryFetching = true;
|
|
613
|
+
try {
|
|
614
|
+
const desc = await client.describeRegistry();
|
|
615
|
+
// Register types
|
|
616
|
+
for (const t of desc.types) {
|
|
617
|
+
if (t.options) {
|
|
618
|
+
this.registry.registerEnum({
|
|
619
|
+
id: t.id,
|
|
620
|
+
options: t.options,
|
|
621
|
+
bakeTarget: t.bakeTarget,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
if (!this.registry.types.has(t.id)) {
|
|
626
|
+
this.registry.registerType({
|
|
627
|
+
id: t.id,
|
|
628
|
+
displayName: t.displayName,
|
|
629
|
+
validate: (_v) => true,
|
|
630
|
+
bakeTarget: t.bakeTarget,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
// Register categories
|
|
636
|
+
for (const c of desc.categories || []) {
|
|
637
|
+
if (!this.registry.categories.has(c.id)) {
|
|
638
|
+
// Create placeholder category descriptor
|
|
639
|
+
const category = {
|
|
640
|
+
id: c.id,
|
|
641
|
+
displayName: c.displayName,
|
|
642
|
+
createRuntime: () => ({
|
|
643
|
+
async onInputsChanged() { },
|
|
644
|
+
}),
|
|
645
|
+
policy: { asyncConcurrency: "switch" },
|
|
646
|
+
};
|
|
647
|
+
this.registry.categories.register(category);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// Register coercions
|
|
651
|
+
for (const c of desc.coercions) {
|
|
652
|
+
if (c.async) {
|
|
653
|
+
this.registry.registerAsyncCoercion(c.from, c.to, async (v) => v, {
|
|
654
|
+
nonTransitive: c.nonTransitive,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
this.registry.registerCoercion(c.from, c.to, (v) => v, {
|
|
659
|
+
nonTransitive: c.nonTransitive,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
// Register nodes
|
|
664
|
+
for (const n of desc.nodes) {
|
|
665
|
+
if (!this.registry.nodes.has(n.id)) {
|
|
666
|
+
this.registry.registerNode({
|
|
667
|
+
id: n.id,
|
|
668
|
+
categoryId: n.categoryId,
|
|
669
|
+
displayName: n.displayName,
|
|
670
|
+
inputs: n.inputs || {},
|
|
671
|
+
outputs: n.outputs || {},
|
|
672
|
+
impl: () => { },
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
this.registryFetched = true;
|
|
677
|
+
this.registryFetching = false;
|
|
678
|
+
this.emit("registry", this.registry);
|
|
679
|
+
}
|
|
680
|
+
catch (err) {
|
|
681
|
+
this.registryFetching = false;
|
|
682
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
683
|
+
// Retry with exponential backoff if attempts remaining
|
|
684
|
+
if (attempt < this.MAX_REGISTRY_FETCH_ATTEMPTS) {
|
|
685
|
+
const delayMs = this.INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
|
|
686
|
+
console.warn(`Failed to fetch registry (attempt ${attempt}/${this.MAX_REGISTRY_FETCH_ATTEMPTS}), retrying in ${delayMs}ms...`, error);
|
|
687
|
+
// Emit error event for UI feedback
|
|
688
|
+
this.emit("error", {
|
|
689
|
+
kind: "registry",
|
|
690
|
+
message: `Registry fetch failed (attempt ${attempt}/${this.MAX_REGISTRY_FETCH_ATTEMPTS}), retrying...`,
|
|
691
|
+
err: error,
|
|
692
|
+
attempt,
|
|
693
|
+
maxAttempts: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
694
|
+
});
|
|
695
|
+
// Retry after delay
|
|
696
|
+
setTimeout(() => {
|
|
697
|
+
this.fetchRegistry(client, attempt + 1).catch(() => {
|
|
698
|
+
// Final failure handled below
|
|
699
|
+
});
|
|
700
|
+
}, delayMs);
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
// Max attempts reached, emit final error
|
|
704
|
+
console.error(`Failed to fetch registry after ${this.MAX_REGISTRY_FETCH_ATTEMPTS} attempts:`, error);
|
|
705
|
+
this.emit("error", {
|
|
706
|
+
kind: "registry",
|
|
707
|
+
message: `Failed to fetch registry after ${this.MAX_REGISTRY_FETCH_ATTEMPTS} attempts. Please check your connection and try refreshing.`,
|
|
708
|
+
err: error,
|
|
709
|
+
attempt: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
710
|
+
maxAttempts: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Build RuntimeApiClient config from RemoteExecutionBackend config.
|
|
717
|
+
*/
|
|
718
|
+
buildClientConfig(backend) {
|
|
719
|
+
if (backend.kind === "remote-http") {
|
|
720
|
+
return {
|
|
721
|
+
kind: "remote-http",
|
|
722
|
+
baseUrl: backend.baseUrl,
|
|
723
|
+
connectOptions: backend.connectOptions,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
return {
|
|
728
|
+
kind: "remote-ws",
|
|
729
|
+
url: backend.url,
|
|
730
|
+
connectOptions: backend.connectOptions,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Setup event subscriptions for the client.
|
|
736
|
+
*/
|
|
737
|
+
setupClientSubscriptions(client, backend) {
|
|
738
|
+
// Subscribe to custom events (for additional listeners if needed)
|
|
739
|
+
if (backend.onCustomEvent) {
|
|
740
|
+
this.customEventUnsubscribe = client.subscribeCustomEvents(backend.onCustomEvent);
|
|
741
|
+
}
|
|
742
|
+
// Subscribe to transport status changes
|
|
743
|
+
// Convert RuntimeApiClient.TransportStatus to IGraphRunner.TransportStatus
|
|
744
|
+
this.transportStatusUnsubscribe = client.onTransportStatus((status) => {
|
|
745
|
+
// Map remote-unix to undefined since RemoteGraphRunner doesn't support it
|
|
746
|
+
const mappedKind = status.kind === "remote-unix" ? undefined : status.kind;
|
|
747
|
+
this.emit("transport", {
|
|
748
|
+
state: status.state,
|
|
749
|
+
kind: mappedKind,
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
// Ensure remote client
|
|
754
|
+
async ensureClient() {
|
|
755
|
+
if (this.disposed) {
|
|
756
|
+
throw new Error("Cannot ensure client: RemoteGraphRunner has been disposed");
|
|
757
|
+
}
|
|
758
|
+
if (this.client)
|
|
759
|
+
return this.client;
|
|
760
|
+
// If already connecting, wait for that connection to complete
|
|
761
|
+
if (this.clientPromise)
|
|
762
|
+
return this.clientPromise;
|
|
763
|
+
const backend = this.backend;
|
|
764
|
+
// Create connection promise to prevent concurrent connections
|
|
765
|
+
this.clientPromise = (async () => {
|
|
766
|
+
// Build client config from backend config
|
|
767
|
+
const clientConfig = this.buildClientConfig(backend);
|
|
768
|
+
// Create client with custom event handler if provided
|
|
769
|
+
const client = new RuntimeApiClient(clientConfig, {
|
|
770
|
+
onCustomEvent: backend.onCustomEvent,
|
|
771
|
+
});
|
|
772
|
+
// Setup event subscriptions
|
|
773
|
+
this.setupClientSubscriptions(client, backend);
|
|
774
|
+
// Connect the client (this will create and connect transport)
|
|
775
|
+
await client.connect();
|
|
776
|
+
this.client = client;
|
|
777
|
+
this.valueCache.clear();
|
|
778
|
+
this.listenersBound = false;
|
|
779
|
+
// Auto-fetch registry on first connection (only once)
|
|
780
|
+
if (!this.registryFetched && !this.registryFetching) {
|
|
781
|
+
// Log loading state (UI can listen to transport status for loading indication)
|
|
782
|
+
console.info("Loading registry from remote...");
|
|
783
|
+
this.fetchRegistry(client).catch((err) => {
|
|
784
|
+
console.error("[RemoteGraphRunner] Failed to fetch registry:", err);
|
|
785
|
+
// Error handling is done inside fetchRegistry, but we catch unhandled rejections
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
// Clear promise on success
|
|
789
|
+
this.clientPromise = undefined;
|
|
790
|
+
return client;
|
|
791
|
+
})();
|
|
792
|
+
return this.clientPromise;
|
|
793
|
+
}
|
|
603
794
|
constructor(registry, backend) {
|
|
604
795
|
super(registry, backend);
|
|
796
|
+
this.disposed = false;
|
|
605
797
|
this.valueCache = new Map();
|
|
606
798
|
this.listenersBound = false;
|
|
607
799
|
this.registryFetched = false;
|
|
@@ -610,8 +802,8 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
610
802
|
this.INITIAL_RETRY_DELAY_MS = 1000; // 1 second
|
|
611
803
|
// Auto-handle registry-changed invalidations from remote
|
|
612
804
|
// We listen on invalidate and if reason matches, we rehydrate registry and emit a registry event
|
|
613
|
-
this.
|
|
614
|
-
const eng =
|
|
805
|
+
this.ensureClient().then(async (client) => {
|
|
806
|
+
const eng = client.getEngine();
|
|
615
807
|
if (!this.listenersBound) {
|
|
616
808
|
eng.on("invalidate", async (e) => {
|
|
617
809
|
if (e.reason === "registry-changed") {
|
|
@@ -675,9 +867,9 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
675
867
|
}
|
|
676
868
|
update(def) {
|
|
677
869
|
// Remote: forward update; ignore errors (fire-and-forget)
|
|
678
|
-
this.
|
|
870
|
+
this.ensureClient().then(async (client) => {
|
|
679
871
|
try {
|
|
680
|
-
await
|
|
872
|
+
await client.update(def);
|
|
681
873
|
this.emit("invalidate", { reason: "graph-updated" });
|
|
682
874
|
this.lastDef = def;
|
|
683
875
|
}
|
|
@@ -687,14 +879,14 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
687
879
|
launch(def, opts) {
|
|
688
880
|
super.launch(def, opts);
|
|
689
881
|
// Remote: build remotely then launch
|
|
690
|
-
this.
|
|
691
|
-
await
|
|
882
|
+
this.ensureClient().then(async (client) => {
|
|
883
|
+
await client.build(def);
|
|
692
884
|
// Signal UI after remote build as well
|
|
693
885
|
this.emit("invalidate", { reason: "graph-built" });
|
|
694
886
|
this.lastDef = def;
|
|
695
887
|
// Hydrate current remote inputs/outputs (including defaults) into cache
|
|
696
888
|
try {
|
|
697
|
-
const snap = await
|
|
889
|
+
const snap = await client.snapshot();
|
|
698
890
|
for (const [nodeId, map] of Object.entries(snap.inputs || {})) {
|
|
699
891
|
for (const [handle, value] of Object.entries(map || {})) {
|
|
700
892
|
this.valueCache.set(`${nodeId}.${handle}`, {
|
|
@@ -717,7 +909,47 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
717
909
|
catch {
|
|
718
910
|
console.error("Failed to hydrate remote inputs/outputs");
|
|
719
911
|
}
|
|
720
|
-
const eng =
|
|
912
|
+
const eng = client.getEngine();
|
|
913
|
+
if (!this.listenersBound) {
|
|
914
|
+
eng.on("value", (e) => {
|
|
915
|
+
this.valueCache.set(`${e.nodeId}.${e.handle}`, {
|
|
916
|
+
io: e.io,
|
|
917
|
+
value: e.value,
|
|
918
|
+
runtimeTypeId: e.runtimeTypeId,
|
|
919
|
+
});
|
|
920
|
+
this.emit("value", e);
|
|
921
|
+
});
|
|
922
|
+
eng.on("error", (e) => this.emit("error", e));
|
|
923
|
+
eng.on("invalidate", (e) => this.emit("invalidate", e));
|
|
924
|
+
eng.on("stats", (e) => this.emit("stats", e));
|
|
925
|
+
this.listenersBound = true;
|
|
926
|
+
}
|
|
927
|
+
this.engine = eng;
|
|
928
|
+
this.engine.launch(opts.invalidate);
|
|
929
|
+
this.runningKind = "push";
|
|
930
|
+
this.emit("status", { running: true, engine: this.runningKind });
|
|
931
|
+
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
932
|
+
this.engine.setInputs(nodeId, map);
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Launch using an existing backend runtime that has already been built and hydrated.
|
|
938
|
+
* This is used when resuming from a snapshot where the backend has already applied
|
|
939
|
+
* ApplySnapshotFull (which builds the graph and hydrates inputs/outputs).
|
|
940
|
+
* Unlike launch(), this method does NOT call client.build() to avoid destroying
|
|
941
|
+
* the runtime state that was just restored.
|
|
942
|
+
*/
|
|
943
|
+
launchExisting(def, opts) {
|
|
944
|
+
super.launch(def, opts);
|
|
945
|
+
// Remote: attach to existing runtime and launch (do NOT rebuild)
|
|
946
|
+
this.ensureClient().then(async (client) => {
|
|
947
|
+
// NOTE: We do NOT call client.build(def) here because the backend runtime
|
|
948
|
+
// has already been built and hydrated via ApplySnapshotFull.
|
|
949
|
+
// Calling build() would create a new runtime and lose the restored state.
|
|
950
|
+
this.lastDef = def;
|
|
951
|
+
// Attach to the existing engine
|
|
952
|
+
const eng = client.getEngine();
|
|
721
953
|
if (!this.listenersBound) {
|
|
722
954
|
eng.on("value", (e) => {
|
|
723
955
|
this.valueCache.set(`${e.nodeId}.${e.handle}`, {
|
|
@@ -751,26 +983,26 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
751
983
|
console.warn("Unsupported operation for remote runner");
|
|
752
984
|
}
|
|
753
985
|
triggerExternal(nodeId, event) {
|
|
754
|
-
this.
|
|
986
|
+
this.ensureClient().then(async (client) => {
|
|
755
987
|
try {
|
|
756
|
-
await
|
|
988
|
+
await client.getEngine().triggerExternal(nodeId, event);
|
|
757
989
|
}
|
|
758
990
|
catch { }
|
|
759
991
|
});
|
|
760
992
|
}
|
|
761
993
|
async coerce(from, to, value) {
|
|
762
|
-
const
|
|
994
|
+
const client = await this.ensureClient();
|
|
763
995
|
try {
|
|
764
|
-
return await
|
|
996
|
+
return await client.coerce(from, to, value);
|
|
765
997
|
}
|
|
766
998
|
catch {
|
|
767
999
|
return value;
|
|
768
1000
|
}
|
|
769
1001
|
}
|
|
770
1002
|
async snapshotFull() {
|
|
771
|
-
const
|
|
1003
|
+
const client = await this.ensureClient();
|
|
772
1004
|
try {
|
|
773
|
-
return await
|
|
1005
|
+
return await client.snapshotFull();
|
|
774
1006
|
}
|
|
775
1007
|
catch {
|
|
776
1008
|
return { def: undefined, environment: {}, inputs: {}, outputs: {} };
|
|
@@ -778,17 +1010,17 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
778
1010
|
}
|
|
779
1011
|
async applySnapshotFull(payload) {
|
|
780
1012
|
// Hydrate local cache first so UI can display values immediately
|
|
781
|
-
this.
|
|
1013
|
+
this.hydrateSnapshotFull(payload);
|
|
782
1014
|
// Then sync with backend
|
|
783
|
-
const
|
|
784
|
-
await
|
|
1015
|
+
const client = await this.ensureClient();
|
|
1016
|
+
await client.applySnapshotFull(payload);
|
|
785
1017
|
}
|
|
786
1018
|
/**
|
|
787
1019
|
* Hydrates the local valueCache from a snapshot and emits value events.
|
|
788
1020
|
* This ensures the UI can display inputs/outputs immediately without waiting
|
|
789
1021
|
* for value events from the remote backend.
|
|
790
1022
|
*/
|
|
791
|
-
|
|
1023
|
+
hydrateSnapshotFull(snapshot) {
|
|
792
1024
|
// Hydrate inputs
|
|
793
1025
|
for (const [nodeId, map] of Object.entries(snapshot.inputs || {})) {
|
|
794
1026
|
for (const [handle, value] of Object.entries(map || {})) {
|
|
@@ -811,20 +1043,25 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
811
1043
|
}
|
|
812
1044
|
}
|
|
813
1045
|
setEnvironment(env, opts) {
|
|
814
|
-
|
|
815
|
-
if (
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1046
|
+
// Use client if available, otherwise ensure client and then set environment
|
|
1047
|
+
if (this.client) {
|
|
1048
|
+
this.client.setEnvironment(env, opts).catch(() => { });
|
|
1049
|
+
}
|
|
1050
|
+
else {
|
|
1051
|
+
// If client not ready yet, ensure it and then set environment
|
|
1052
|
+
this.ensureClient()
|
|
1053
|
+
.then((client) => {
|
|
1054
|
+
client.setEnvironment(env, opts).catch(() => { });
|
|
1055
|
+
})
|
|
1056
|
+
.catch(() => { });
|
|
1057
|
+
}
|
|
823
1058
|
}
|
|
824
1059
|
getEnvironment() {
|
|
825
|
-
//
|
|
826
|
-
//
|
|
827
|
-
//
|
|
1060
|
+
// Interface requires sync return, but RuntimeApiClient.getEnvironment() is async.
|
|
1061
|
+
// Returns undefined synchronously; callers needing the actual value should:
|
|
1062
|
+
// - Use snapshotFull() which includes environment
|
|
1063
|
+
// - Call client.getEnvironment() directly if they have access to the client
|
|
1064
|
+
// This is a limitation of the sync interface signature.
|
|
828
1065
|
return undefined;
|
|
829
1066
|
}
|
|
830
1067
|
getOutputs(def) {
|
|
@@ -888,183 +1125,37 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
888
1125
|
return out;
|
|
889
1126
|
}
|
|
890
1127
|
dispose() {
|
|
1128
|
+
// Idempotent: allow multiple calls safely
|
|
1129
|
+
if (this.disposed)
|
|
1130
|
+
return;
|
|
1131
|
+
this.disposed = true;
|
|
891
1132
|
super.dispose();
|
|
892
|
-
|
|
893
|
-
this.
|
|
1133
|
+
// Clear client promise if any
|
|
1134
|
+
this.clientPromise = undefined;
|
|
1135
|
+
// Unsubscribe from custom events and transport status
|
|
1136
|
+
if (this.customEventUnsubscribe) {
|
|
1137
|
+
this.customEventUnsubscribe();
|
|
1138
|
+
this.customEventUnsubscribe = undefined;
|
|
1139
|
+
}
|
|
1140
|
+
if (this.transportStatusUnsubscribe) {
|
|
1141
|
+
this.transportStatusUnsubscribe();
|
|
1142
|
+
this.transportStatusUnsubscribe = undefined;
|
|
1143
|
+
}
|
|
1144
|
+
// Dispose client (which will close transport)
|
|
1145
|
+
const clientToDispose = this.client;
|
|
1146
|
+
this.client = undefined;
|
|
894
1147
|
this.registryFetched = false; // Reset so registry is fetched again on reconnect
|
|
895
1148
|
this.registryFetching = false; // Reset fetching state
|
|
1149
|
+
if (clientToDispose) {
|
|
1150
|
+
clientToDispose.dispose().catch((err) => {
|
|
1151
|
+
console.warn("[RemoteGraphRunner] Error disposing client:", err);
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
896
1154
|
this.emit("transport", {
|
|
897
1155
|
state: "disconnected",
|
|
898
1156
|
kind: this.backend.kind,
|
|
899
1157
|
});
|
|
900
1158
|
}
|
|
901
|
-
/**
|
|
902
|
-
* Fetch full registry description from remote and register it locally.
|
|
903
|
-
* Called automatically on first connection with retry mechanism.
|
|
904
|
-
*/
|
|
905
|
-
async fetchRegistry(runner, attempt = 1) {
|
|
906
|
-
if (this.registryFetching) {
|
|
907
|
-
// Already fetching, don't start another fetch
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
this.registryFetching = true;
|
|
911
|
-
try {
|
|
912
|
-
const desc = await runner.describeRegistry();
|
|
913
|
-
// Register types
|
|
914
|
-
for (const t of desc.types) {
|
|
915
|
-
if (t.options) {
|
|
916
|
-
this.registry.registerEnum({
|
|
917
|
-
id: t.id,
|
|
918
|
-
options: t.options,
|
|
919
|
-
bakeTarget: t.bakeTarget,
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
else {
|
|
923
|
-
if (!this.registry.types.has(t.id)) {
|
|
924
|
-
this.registry.registerType({
|
|
925
|
-
id: t.id,
|
|
926
|
-
displayName: t.displayName,
|
|
927
|
-
validate: (_v) => true,
|
|
928
|
-
bakeTarget: t.bakeTarget,
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
// Register categories
|
|
934
|
-
for (const c of desc.categories || []) {
|
|
935
|
-
if (!this.registry.categories.has(c.id)) {
|
|
936
|
-
// Create placeholder category descriptor
|
|
937
|
-
const category = {
|
|
938
|
-
id: c.id,
|
|
939
|
-
displayName: c.displayName,
|
|
940
|
-
createRuntime: () => ({
|
|
941
|
-
async onInputsChanged() { },
|
|
942
|
-
}),
|
|
943
|
-
policy: { asyncConcurrency: "switch" },
|
|
944
|
-
};
|
|
945
|
-
this.registry.categories.register(category);
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
// Register coercions
|
|
949
|
-
for (const c of desc.coercions) {
|
|
950
|
-
if (c.async) {
|
|
951
|
-
this.registry.registerAsyncCoercion(c.from, c.to, async (v) => v, {
|
|
952
|
-
nonTransitive: c.nonTransitive,
|
|
953
|
-
});
|
|
954
|
-
}
|
|
955
|
-
else {
|
|
956
|
-
this.registry.registerCoercion(c.from, c.to, (v) => v, {
|
|
957
|
-
nonTransitive: c.nonTransitive,
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
// Register nodes
|
|
962
|
-
for (const n of desc.nodes) {
|
|
963
|
-
if (!this.registry.nodes.has(n.id)) {
|
|
964
|
-
this.registry.registerNode({
|
|
965
|
-
id: n.id,
|
|
966
|
-
categoryId: n.categoryId,
|
|
967
|
-
displayName: n.displayName,
|
|
968
|
-
inputs: n.inputs || {},
|
|
969
|
-
outputs: n.outputs || {},
|
|
970
|
-
impl: () => { },
|
|
971
|
-
});
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
this.registryFetched = true;
|
|
975
|
-
this.registryFetching = false;
|
|
976
|
-
this.emit("registry", this.registry);
|
|
977
|
-
}
|
|
978
|
-
catch (err) {
|
|
979
|
-
this.registryFetching = false;
|
|
980
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
981
|
-
// Retry with exponential backoff if attempts remaining
|
|
982
|
-
if (attempt < this.MAX_REGISTRY_FETCH_ATTEMPTS) {
|
|
983
|
-
const delayMs = this.INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
|
|
984
|
-
console.warn(`Failed to fetch registry (attempt ${attempt}/${this.MAX_REGISTRY_FETCH_ATTEMPTS}), retrying in ${delayMs}ms...`, error);
|
|
985
|
-
// Emit error event for UI feedback
|
|
986
|
-
this.emit("error", {
|
|
987
|
-
kind: "registry",
|
|
988
|
-
message: `Registry fetch failed (attempt ${attempt}/${this.MAX_REGISTRY_FETCH_ATTEMPTS}), retrying...`,
|
|
989
|
-
err: error,
|
|
990
|
-
attempt,
|
|
991
|
-
maxAttempts: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
992
|
-
});
|
|
993
|
-
// Retry after delay
|
|
994
|
-
setTimeout(() => {
|
|
995
|
-
this.fetchRegistry(runner, attempt + 1).catch(() => {
|
|
996
|
-
// Final failure handled below
|
|
997
|
-
});
|
|
998
|
-
}, delayMs);
|
|
999
|
-
}
|
|
1000
|
-
else {
|
|
1001
|
-
// Max attempts reached, emit final error
|
|
1002
|
-
console.error(`Failed to fetch registry after ${this.MAX_REGISTRY_FETCH_ATTEMPTS} attempts:`, error);
|
|
1003
|
-
this.emit("error", {
|
|
1004
|
-
kind: "registry",
|
|
1005
|
-
message: `Failed to fetch registry after ${this.MAX_REGISTRY_FETCH_ATTEMPTS} attempts. Please check your connection and try refreshing.`,
|
|
1006
|
-
err: error,
|
|
1007
|
-
attempt: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
1008
|
-
maxAttempts: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
// Ensure remote transport/runner
|
|
1014
|
-
async ensureRemoteRunner() {
|
|
1015
|
-
if (this.runner)
|
|
1016
|
-
return this.runner;
|
|
1017
|
-
let transport;
|
|
1018
|
-
const kind = this.backend.kind;
|
|
1019
|
-
const backend = this.backend;
|
|
1020
|
-
const connectOptions = backend.connectOptions;
|
|
1021
|
-
this.emit("transport", { state: "connecting", kind });
|
|
1022
|
-
if (backend.kind === "remote-http") {
|
|
1023
|
-
if (!HttpPollingTransport)
|
|
1024
|
-
throw new Error("HttpPollingTransport not available");
|
|
1025
|
-
transport = new HttpPollingTransport(backend.baseUrl);
|
|
1026
|
-
await transport.connect(connectOptions);
|
|
1027
|
-
}
|
|
1028
|
-
else if (backend.kind === "remote-ws") {
|
|
1029
|
-
if (!WebSocketTransport)
|
|
1030
|
-
throw new Error("WebSocketTransport not available");
|
|
1031
|
-
transport = new WebSocketTransport(backend.url);
|
|
1032
|
-
await transport.connect(connectOptions);
|
|
1033
|
-
}
|
|
1034
|
-
else {
|
|
1035
|
-
throw new Error("Remote backend not configured");
|
|
1036
|
-
}
|
|
1037
|
-
// Subscribe to custom events if handler provided
|
|
1038
|
-
if (backend.onCustomEvent) {
|
|
1039
|
-
transport.subscribe((event) => {
|
|
1040
|
-
// Filter out standard runtime events, pass others to custom handler
|
|
1041
|
-
const msg = event.message;
|
|
1042
|
-
if (msg && typeof msg === "object" && "type" in msg) {
|
|
1043
|
-
const type = msg.type;
|
|
1044
|
-
// Standard runtime events: stats, value, error, invalidate
|
|
1045
|
-
// Custom events are anything else (e.g., flow-opened, flow-latest)
|
|
1046
|
-
if (!["stats", "value", "error", "invalidate"].includes(type)) {
|
|
1047
|
-
backend.onCustomEvent?.(event);
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
const runner = new RemoteRunner(transport);
|
|
1053
|
-
this.runner = runner;
|
|
1054
|
-
this.transport = transport;
|
|
1055
|
-
this.valueCache.clear();
|
|
1056
|
-
this.listenersBound = false;
|
|
1057
|
-
this.emit("transport", { state: "connected", kind });
|
|
1058
|
-
// Auto-fetch registry on first connection (only once)
|
|
1059
|
-
if (!this.registryFetched && !this.registryFetching) {
|
|
1060
|
-
// Log loading state (UI can listen to transport status for loading indication)
|
|
1061
|
-
console.info("Loading registry from remote...");
|
|
1062
|
-
this.fetchRegistry(runner).catch(() => {
|
|
1063
|
-
// Error handling is done inside fetchRegistry
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
|
-
return runner;
|
|
1067
|
-
}
|
|
1068
1159
|
}
|
|
1069
1160
|
|
|
1070
1161
|
function useWorkbenchBridge(wb) {
|
|
@@ -1732,6 +1823,33 @@ function getNodeBorderClassNames(args) {
|
|
|
1732
1823
|
: "";
|
|
1733
1824
|
return [borderWidth, borderStyle, borderColor, ring].join(" ").trim();
|
|
1734
1825
|
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Shared utility to generate handle className based on validation and value state.
|
|
1828
|
+
* - Linked handles (with inbound edges) get black borders
|
|
1829
|
+
* - Handles with values (but not linked) get darker gray borders
|
|
1830
|
+
* - Handles with only defaults (no value, not linked) get lighter gray borders
|
|
1831
|
+
* - Validation errors (red/amber) take precedence over value-based styling.
|
|
1832
|
+
*/
|
|
1833
|
+
function getHandleClassName(args) {
|
|
1834
|
+
const { kind, id, validation } = args;
|
|
1835
|
+
const vIssues = (kind === "input" ? validation.inputs : validation.outputs)?.filter((v) => v.handle === id) || [];
|
|
1836
|
+
const hasAny = vIssues.length > 0;
|
|
1837
|
+
const hasErr = vIssues.some((v) => v.level === "error");
|
|
1838
|
+
// Determine border color based on priority:
|
|
1839
|
+
// 1. Validation errors (red/amber) - highest priority
|
|
1840
|
+
// 2. Gray border
|
|
1841
|
+
let borderColor;
|
|
1842
|
+
if (hasAny && hasErr) {
|
|
1843
|
+
borderColor = "!border-red-500";
|
|
1844
|
+
}
|
|
1845
|
+
else if (hasAny) {
|
|
1846
|
+
borderColor = "!border-amber-500";
|
|
1847
|
+
}
|
|
1848
|
+
else {
|
|
1849
|
+
borderColor = "!border-gray-500 dark:!border-gray-400";
|
|
1850
|
+
}
|
|
1851
|
+
return cx("!w-3 !h-3 !bg-white !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
|
|
1852
|
+
}
|
|
1735
1853
|
|
|
1736
1854
|
const WorkbenchContext = createContext(null);
|
|
1737
1855
|
function useWorkbenchContext() {
|
|
@@ -2454,7 +2572,7 @@ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWork
|
|
|
2454
2572
|
return (jsxs("div", { className: "flex flex-col h-full min-h-0", children: [jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsx("div", { className: "font-semibold", children: "Events" }), jsxs("div", { className: "flex items-center gap-2", children: [jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsx("input", { type: "checkbox", checked: hideWorkbench, onChange: (e) => onHideWorkbenchChange?.(e.target.checked) }), jsx("span", { children: "Hide workbench" })] }), jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsx("input", { type: "checkbox", checked: autoScroll, onChange: (e) => onAutoScrollChange?.(e.target.checked) }), jsx("span", { children: "Auto scroll" })] }), jsx("button", { onClick: handleCopyLogs, className: "p-2 border border-gray-300 rounded flex items-center justify-center", title: copied ? "Copied!" : "Copy logs as formatted JSON", children: jsx(CopyIcon, { size: 14 }) }), jsx("button", { onClick: clearEvents, className: "p-2 border border-gray-300 rounded flex items-center justify-center", title: "Clear all events", children: jsx(TrashIcon, { size: 14 }) })] })] }), jsx("div", { ref: scrollRef, className: "flex-1 overflow-auto text-[11px] leading-4 divide-y divide-gray-200", children: rows.map((ev) => (jsxs("div", { className: "opacity-85 odd:bg-gray-50 px-2 py-1", children: [jsxs("div", { className: "flex items-baseline gap-2", children: [jsx("span", { className: "w-12 shrink-0 text-right text-gray-500 select-none", children: ev.no }), jsxs("span", { className: "text-gray-500", children: [new Date(ev.at).toLocaleTimeString(), " \u00B7 ", ev.source, ":", ev.type] })] }), jsx("pre", { className: "m-0 whitespace-pre-wrap ml-12", children: renderPayload(ev.payload) })] }, `${ev.at}:${ev.no}`))) })] }));
|
|
2455
2573
|
}
|
|
2456
2574
|
|
|
2457
|
-
function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString,
|
|
2575
|
+
function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString, contextPanel, setInput, }) {
|
|
2458
2576
|
const safeToString = (typeId, value) => {
|
|
2459
2577
|
try {
|
|
2460
2578
|
if (typeof toString === "function") {
|
|
@@ -2832,15 +2950,17 @@ function DefaultNodeContent({ data, isConnectable, }) {
|
|
|
2832
2950
|
const status = data.status ?? { activeRuns: 0 };
|
|
2833
2951
|
const validation = data.validation ?? {
|
|
2834
2952
|
inputs: [],
|
|
2835
|
-
outputs: []
|
|
2953
|
+
outputs: [],
|
|
2954
|
+
issues: [],
|
|
2955
|
+
};
|
|
2836
2956
|
const isRunning = !!status.activeRuns;
|
|
2837
2957
|
const pct = Math.round(Math.max(0, Math.min(1, Number(status.progress) || 0)) * 100);
|
|
2838
|
-
return (jsxs(Fragment, { children: [jsx("div", { className: cx("h-px", (isRunning || pct > 0) && "bg-blue-200 dark:bg-blue-900"), children: jsx("div", { className: cx("h-px transition-all", (isRunning || pct > 0) && "bg-blue-500"), style: { width: isRunning || pct > 0 ? `${pct}%` : 0 } }) }), jsx(NodeHandles, { data: data, isConnectable: isConnectable, getClassName: ({ kind, id }) => {
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
}, renderLabel: ({ kind, id: handleId }) => {
|
|
2958
|
+
return (jsxs(Fragment, { children: [jsx("div", { className: cx("h-px", (isRunning || pct > 0) && "bg-blue-200 dark:bg-blue-900"), children: jsx("div", { className: cx("h-px transition-all", (isRunning || pct > 0) && "bg-blue-500"), style: { width: isRunning || pct > 0 ? `${pct}%` : 0 } }) }), jsx(NodeHandles, { data: data, isConnectable: isConnectable, getClassName: ({ kind, id }) => getHandleClassName({
|
|
2959
|
+
kind,
|
|
2960
|
+
id,
|
|
2961
|
+
validation,
|
|
2962
|
+
inputConnected: data.inputConnected,
|
|
2963
|
+
}), renderLabel: ({ kind, id: handleId }) => {
|
|
2844
2964
|
const entries = kind === "input" ? inputEntries : outputEntries;
|
|
2845
2965
|
const entry = entries.find((e) => e.id === handleId);
|
|
2846
2966
|
if (!entry)
|
|
@@ -3446,9 +3566,6 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3446
3566
|
state: "local",
|
|
3447
3567
|
});
|
|
3448
3568
|
const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
|
|
3449
|
-
selectedNode
|
|
3450
|
-
? registry.nodes.get(selectedNode.typeId)
|
|
3451
|
-
: undefined;
|
|
3452
3569
|
const effectiveHandles = selectedNode
|
|
3453
3570
|
? computeEffectiveHandles(selectedNode, registry)
|
|
3454
3571
|
: { inputs: {}, outputs: {}, inputDefaults: {} };
|
|
@@ -3739,7 +3856,12 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3739
3856
|
const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId && e.target.handle === handle);
|
|
3740
3857
|
if (isLinked)
|
|
3741
3858
|
return;
|
|
3742
|
-
|
|
3859
|
+
// If raw is undefined, pass it through to delete the input value
|
|
3860
|
+
if (raw === undefined) {
|
|
3861
|
+
runner.setInput(selectedNodeId, handle, undefined);
|
|
3862
|
+
return;
|
|
3863
|
+
}
|
|
3864
|
+
const typeId = getInputTypeId(effectiveHandles.inputs, handle);
|
|
3743
3865
|
let value = raw;
|
|
3744
3866
|
const parseArray = (s, map) => {
|
|
3745
3867
|
const str = String(s).trim();
|
|
@@ -3948,7 +4070,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3948
4070
|
const message = err instanceof Error ? err.message : String(err);
|
|
3949
4071
|
alert(message);
|
|
3950
4072
|
}
|
|
3951
|
-
}, children: jsx(DownloadIcon, { size: 24 }) }), jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: triggerUpload, children: jsx(UploadIcon, { size: 24 }) }), jsxs("label", { className: "ml-2 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: "ml-2 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,
|
|
4073
|
+
}, children: jsx(DownloadIcon, { size: 24 }) }), jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: triggerUpload, children: jsx(UploadIcon, { size: 24 }) }), jsxs("label", { className: "ml-2 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: "ml-2 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 })] })] }));
|
|
3952
4074
|
}
|
|
3953
4075
|
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
|
|
3954
4076
|
const [registry, setRegistry] = useState(createSimpleGraphRegistry());
|
|
@@ -4029,5 +4151,5 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
4029
4151
|
}, 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 }) }));
|
|
4030
4152
|
}
|
|
4031
4153
|
|
|
4032
|
-
export { AbstractWorkbench, CLIWorkbench, DefaultNode, DefaultNodeContent, DefaultNodeHeader, DefaultUIExtensionRegistry, InMemoryWorkbench, Inspector, LocalGraphRunner, NodeHandles, RemoteGraphRunner, WorkbenchCanvas, WorkbenchContext, WorkbenchProvider, WorkbenchStudio, formatDataUrlAsLabel, formatDeclaredTypeSignature, getNodeBorderClassNames, preformatValueForDisplay, prettyHandle, resolveOutputDisplay, summarizeDeep, toReactFlow, useQueryParamBoolean, useQueryParamString, useThrottledValue, useWorkbenchBridge, useWorkbenchContext, useWorkbenchGraphTick, useWorkbenchGraphUiTick, useWorkbenchVersionTick };
|
|
4154
|
+
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 };
|
|
4033
4155
|
//# sourceMappingURL=index.js.map
|