@bian-womp/spark-workbench 0.2.8 → 0.2.9
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 +172 -14
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/DefaultNode.d.ts +18 -1
- package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/mapping.d.ts +1 -0
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +1 -0
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +1 -0
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +172 -15
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/DefaultNode.d.ts +18 -1
- package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/mapping.d.ts +1 -0
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +1 -0
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +1 -0
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -548,6 +548,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
548
548
|
displayName: d.displayName,
|
|
549
549
|
options: d.options,
|
|
550
550
|
opts: d.opts,
|
|
551
|
+
bakeTarget: d.bakeTarget,
|
|
551
552
|
});
|
|
552
553
|
}
|
|
553
554
|
else if (d.kind === "register-type") {
|
|
@@ -556,6 +557,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
556
557
|
id: d.id,
|
|
557
558
|
displayName: d.displayName,
|
|
558
559
|
validate: (_v) => true,
|
|
560
|
+
bakeTarget: d.bakeTarget,
|
|
559
561
|
});
|
|
560
562
|
}
|
|
561
563
|
}
|
|
@@ -678,6 +680,15 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
678
680
|
catch { }
|
|
679
681
|
});
|
|
680
682
|
}
|
|
683
|
+
async coerce(from, to, value) {
|
|
684
|
+
const runner = await this.ensureRemoteRunner();
|
|
685
|
+
try {
|
|
686
|
+
return await runner.coerce(from, to, value);
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
return value;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
681
692
|
getOutputs(def) {
|
|
682
693
|
const out = {};
|
|
683
694
|
const cache = this.valueCache;
|
|
@@ -1108,6 +1119,15 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1108
1119
|
const EDGE_STYLE_ERROR = { stroke: "#ef4444", strokeWidth: 2 };
|
|
1109
1120
|
const EDGE_STYLE_RUNNING = { stroke: "#3b82f6" };
|
|
1110
1121
|
const nodeHandleMap = {};
|
|
1122
|
+
// Precompute which inputs are connected per node
|
|
1123
|
+
const connectedInputs = {};
|
|
1124
|
+
for (const e of def.edges) {
|
|
1125
|
+
const nid = e.target.nodeId;
|
|
1126
|
+
const hid = e.target.handle;
|
|
1127
|
+
if (!connectedInputs[nid])
|
|
1128
|
+
connectedInputs[nid] = new Set();
|
|
1129
|
+
connectedInputs[nid].add(hid);
|
|
1130
|
+
}
|
|
1111
1131
|
const nodes = def.nodes.map((n) => {
|
|
1112
1132
|
const desc = registry.nodes.get(n.typeId);
|
|
1113
1133
|
const inputHandles = Object.entries(desc?.inputs ?? {})
|
|
@@ -1156,6 +1176,10 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1156
1176
|
params: n.params,
|
|
1157
1177
|
inputHandles,
|
|
1158
1178
|
outputHandles,
|
|
1179
|
+
inputConnected: Object.fromEntries(inputHandles.map((h) => [
|
|
1180
|
+
h.id,
|
|
1181
|
+
!!connectedInputs[n.nodeId]?.has(h.id),
|
|
1182
|
+
])),
|
|
1159
1183
|
handleLayout: [
|
|
1160
1184
|
...inputHandles.map((h, i) => ({
|
|
1161
1185
|
id: h.id,
|
|
@@ -2029,17 +2053,37 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
2029
2053
|
position: "relative",
|
|
2030
2054
|
minWidth: typeof data.renderWidth === "number" ? data.renderWidth : undefined,
|
|
2031
2055
|
minHeight: typeof data.renderHeight === "number" ? data.renderHeight : undefined,
|
|
2032
|
-
}, children: [jsxRuntime.
|
|
2033
|
-
maxHeight: NODE_HEADER_HEIGHT_PX,
|
|
2034
|
-
minHeight: NODE_HEADER_HEIGHT_PX,
|
|
2035
|
-
}, children: [jsxRuntime.jsx("strong", { className: "flex-1 h-full text-sm", style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` }, children: typeId }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [validation.issues && validation.issues.length > 0 && (jsxRuntime.jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
|
|
2036
|
-
? "error"
|
|
2037
|
-
: "warning", size: 12, className: "w-3 h-3", title: validation.issues
|
|
2038
|
-
.map((v) => `${v.code}: ${v.message}`)
|
|
2039
|
-
.join("; ") })), jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }), jsxRuntime.jsx(DefaultNodeContent, { data: data, isConnectable: isConnectable })] }));
|
|
2056
|
+
}, children: [jsxRuntime.jsx(DefaultNodeHeader, { id: id, title: typeId, status: status, validation: validation }), jsxRuntime.jsx(DefaultNodeContent, { id: id, data: data, isConnectable: isConnectable })] }));
|
|
2040
2057
|
});
|
|
2041
2058
|
DefaultNode.displayName = "DefaultNode";
|
|
2042
|
-
function
|
|
2059
|
+
function DefaultNodeHeader({ id, title, status, validation, right, onInvalidate, }) {
|
|
2060
|
+
const ctx = useWorkbenchContext();
|
|
2061
|
+
const handleInvalidate = React.useCallback(() => {
|
|
2062
|
+
try {
|
|
2063
|
+
if (onInvalidate)
|
|
2064
|
+
return onInvalidate();
|
|
2065
|
+
const kind = ctx.engineKind?.();
|
|
2066
|
+
if (kind === "pull")
|
|
2067
|
+
ctx.runner.computeNode(id);
|
|
2068
|
+
else
|
|
2069
|
+
ctx.triggerExternal?.(id, { type: "invalidate" });
|
|
2070
|
+
}
|
|
2071
|
+
catch { }
|
|
2072
|
+
}, [ctx, id, onInvalidate]);
|
|
2073
|
+
return (jsxRuntime.jsxs("div", { className: "flex items-center justify-center px-2 border-b border-solid border-gray-500 dark:border-gray-400 text-gray-600 dark:text-gray-300", style: {
|
|
2074
|
+
maxHeight: NODE_HEADER_HEIGHT_PX,
|
|
2075
|
+
minHeight: NODE_HEADER_HEIGHT_PX,
|
|
2076
|
+
}, children: [jsxRuntime.jsx("strong", { className: "flex-1 h-full text-sm", style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` }, children: title }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("button", { className: "w-4 h-4 border border-gray-400 rounded text-[10px] leading-3 flex items-center justify-center", title: "Invalidate and re-run", onClick: (e) => {
|
|
2077
|
+
e.stopPropagation();
|
|
2078
|
+
handleInvalidate();
|
|
2079
|
+
}, children: "\u21BB" }), right, validation.issues && validation.issues.length > 0 && (jsxRuntime.jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
|
|
2080
|
+
? "error"
|
|
2081
|
+
: "warning", size: 12, className: "w-3 h-3", title: validation.issues
|
|
2082
|
+
.map((v) => `${v.code}: ${v.message}`)
|
|
2083
|
+
.join("; ") })), jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
|
|
2084
|
+
}
|
|
2085
|
+
function DefaultNodeContent({ id, data, isConnectable, }) {
|
|
2086
|
+
const ctx = useWorkbenchContext();
|
|
2043
2087
|
const { showValues, inputValues, outputValues, toString } = data;
|
|
2044
2088
|
const inputEntries = data.inputHandles ?? [];
|
|
2045
2089
|
const outputEntries = data.outputHandles ?? [];
|
|
@@ -2049,17 +2093,109 @@ function DefaultNodeContent({ data, isConnectable, }) {
|
|
|
2049
2093
|
outputs: []};
|
|
2050
2094
|
const isRunning = !!status.activeRuns;
|
|
2051
2095
|
const pct = Math.round(Math.max(0, Math.min(1, Number(status.progress) || 0)) * 100);
|
|
2096
|
+
const handleBake = React.useCallback(async (handleId) => {
|
|
2097
|
+
try {
|
|
2098
|
+
const typeId = ctx.outputTypesMap?.[id]?.[handleId];
|
|
2099
|
+
const rawValue = ctx.outputsMap?.[id]?.[handleId];
|
|
2100
|
+
if (!typeId || rawValue === undefined)
|
|
2101
|
+
return;
|
|
2102
|
+
const unwrap = (v) => sparkGraph.isTypedOutput(v) ? sparkGraph.getTypedOutputValue(v) : v;
|
|
2103
|
+
const clone = (v) => typeof structuredClone === "function"
|
|
2104
|
+
? structuredClone(v)
|
|
2105
|
+
: JSON.parse(JSON.stringify(v));
|
|
2106
|
+
const coerceIfNeeded = async (fromType, toType, value) => {
|
|
2107
|
+
if (!toType || toType === fromType || !ctx.runner?.coerce)
|
|
2108
|
+
return value;
|
|
2109
|
+
try {
|
|
2110
|
+
return await ctx.runner.coerce(fromType, toType, value);
|
|
2111
|
+
}
|
|
2112
|
+
catch {
|
|
2113
|
+
return value;
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
const positions = ctx.wb.getPositions();
|
|
2117
|
+
const pos = positions[id] || { x: 0, y: 0 };
|
|
2118
|
+
const isArray = typeId.endsWith("[]");
|
|
2119
|
+
const baseTypeId = isArray ? typeId.slice(0, -2) : typeId;
|
|
2120
|
+
const tArr = isArray ? ctx.registry?.types.get(typeId) : undefined;
|
|
2121
|
+
const tElem = ctx.registry?.types.get(baseTypeId);
|
|
2122
|
+
const singleTarget = !isArray ? tElem?.bakeTarget : undefined;
|
|
2123
|
+
const arrTarget = isArray ? tArr?.bakeTarget : undefined;
|
|
2124
|
+
const elemTarget = isArray ? tElem?.bakeTarget : undefined;
|
|
2125
|
+
const makeTargetInfo = (bt) => {
|
|
2126
|
+
if (!bt)
|
|
2127
|
+
return undefined;
|
|
2128
|
+
const node = ctx.registry?.nodes.get(String(bt.nodeTypeId));
|
|
2129
|
+
const inType = sparkGraph.getInputTypeId(node?.inputs, String(bt.inputHandle || "Value"));
|
|
2130
|
+
return { bt, inType };
|
|
2131
|
+
};
|
|
2132
|
+
// Plan and execute
|
|
2133
|
+
if (singleTarget) {
|
|
2134
|
+
const info = makeTargetInfo(singleTarget);
|
|
2135
|
+
const v = unwrap(rawValue);
|
|
2136
|
+
const coerced = await coerceIfNeeded(typeId, info?.inType, v);
|
|
2137
|
+
ctx.wb.addNode({
|
|
2138
|
+
nodeId: undefined,
|
|
2139
|
+
typeId: String(singleTarget.nodeTypeId),
|
|
2140
|
+
position: { x: pos.x + 180, y: pos.y },
|
|
2141
|
+
params: {},
|
|
2142
|
+
initialInputs: {
|
|
2143
|
+
[String(singleTarget.inputHandle || "Value")]: clone(coerced),
|
|
2144
|
+
},
|
|
2145
|
+
});
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
if (isArray && arrTarget) {
|
|
2149
|
+
const info = makeTargetInfo(arrTarget);
|
|
2150
|
+
const v = unwrap(rawValue);
|
|
2151
|
+
const coerced = await coerceIfNeeded(typeId, info?.inType, v);
|
|
2152
|
+
ctx.wb.addNode({
|
|
2153
|
+
nodeId: undefined,
|
|
2154
|
+
typeId: String(arrTarget.nodeTypeId),
|
|
2155
|
+
position: { x: pos.x + 180, y: pos.y },
|
|
2156
|
+
params: {},
|
|
2157
|
+
initialInputs: {
|
|
2158
|
+
[String(arrTarget.inputHandle || "Value")]: clone(coerced),
|
|
2159
|
+
},
|
|
2160
|
+
});
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
if (isArray && elemTarget && Array.isArray(rawValue)) {
|
|
2164
|
+
const info = makeTargetInfo(elemTarget);
|
|
2165
|
+
const items = rawValue.map(unwrap);
|
|
2166
|
+
const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, info?.inType, v)));
|
|
2167
|
+
const COLS = 4;
|
|
2168
|
+
const DX = 180;
|
|
2169
|
+
const DY = 160;
|
|
2170
|
+
coercedItems.forEach((cv, idx) => {
|
|
2171
|
+
const col = idx % COLS;
|
|
2172
|
+
const row = Math.floor(idx / COLS);
|
|
2173
|
+
ctx.wb.addNode({
|
|
2174
|
+
nodeId: undefined,
|
|
2175
|
+
typeId: String(elemTarget.nodeTypeId),
|
|
2176
|
+
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
2177
|
+
params: {},
|
|
2178
|
+
initialInputs: {
|
|
2179
|
+
[String(elemTarget.inputHandle || "Value")]: clone(cv),
|
|
2180
|
+
},
|
|
2181
|
+
});
|
|
2182
|
+
});
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
catch { }
|
|
2187
|
+
}, [ctx, id]);
|
|
2052
2188
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: cx("h-px", (isRunning || pct > 0) && "bg-blue-200 dark:bg-blue-900"), children: jsxRuntime.jsx("div", { className: cx("h-px transition-all", (isRunning || pct > 0) && "bg-blue-500"), style: { width: isRunning || pct > 0 ? `${pct}%` : 0 } }) }), jsxRuntime.jsx(NodeHandles, { data: data, isConnectable: isConnectable, getClassName: ({ kind, id }) => {
|
|
2053
2189
|
const vIssues = (kind === "input" ? validation.inputs : validation.outputs).filter((v) => v.handle === id);
|
|
2054
2190
|
const hasAny = vIssues.length > 0;
|
|
2055
2191
|
const hasErr = vIssues.some((v) => v.level === "error");
|
|
2056
2192
|
return cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", kind === "output" && "!rounded-none", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500"));
|
|
2057
|
-
}, renderLabel: ({ kind, id }) => {
|
|
2193
|
+
}, renderLabel: ({ kind, id: handleId }) => {
|
|
2058
2194
|
const entries = kind === "input" ? inputEntries : outputEntries;
|
|
2059
|
-
const entry = entries.find((e) => e.id ===
|
|
2195
|
+
const entry = entries.find((e) => e.id === handleId);
|
|
2060
2196
|
if (!entry)
|
|
2061
|
-
return
|
|
2062
|
-
const vIssues = (kind === "input" ? validation.inputs : validation.outputs).filter((v) => v.handle ===
|
|
2197
|
+
return handleId;
|
|
2198
|
+
const vIssues = (kind === "input" ? validation.inputs : validation.outputs).filter((v) => v.handle === handleId);
|
|
2063
2199
|
const hasAny = vIssues.length > 0;
|
|
2064
2200
|
const hasErr = vIssues.some((v) => v.level === "error");
|
|
2065
2201
|
const title = vIssues
|
|
@@ -2076,7 +2212,26 @@ function DefaultNodeContent({ data, isConnectable, }) {
|
|
|
2076
2212
|
const txt = toString(resolved.typeId, resolved.value);
|
|
2077
2213
|
return typeof txt === "string" ? txt : String(txt);
|
|
2078
2214
|
})();
|
|
2079
|
-
|
|
2215
|
+
const tId = ctx.outputTypesMap?.[id]?.[handleId];
|
|
2216
|
+
let canBake = false;
|
|
2217
|
+
if (tId?.endsWith("[]")) {
|
|
2218
|
+
const base = tId.slice(0, -2);
|
|
2219
|
+
const tArr = ctx.registry?.types.get(tId);
|
|
2220
|
+
const tElem = ctx.registry?.types.get(base);
|
|
2221
|
+
const arrTarget = tArr?.bakeTarget;
|
|
2222
|
+
const elemTarget = tElem?.bakeTarget;
|
|
2223
|
+
canBake = !!((arrTarget && ctx.registry?.nodes?.has?.(arrTarget.nodeTypeId)) ||
|
|
2224
|
+
(elemTarget && ctx.registry?.nodes?.has?.(elemTarget.nodeTypeId)));
|
|
2225
|
+
}
|
|
2226
|
+
else if (tId) {
|
|
2227
|
+
const t = ctx.registry?.types.get(tId);
|
|
2228
|
+
const target = t?.bakeTarget;
|
|
2229
|
+
canBake = !!(target && ctx.registry?.nodes?.has?.(target.nodeTypeId));
|
|
2230
|
+
}
|
|
2231
|
+
return (jsxRuntime.jsxs("span", { className: "flex items-center gap-1 w-full", children: [kind === "output" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [canBake && (jsxRuntime.jsx("button", { onClick: (e) => {
|
|
2232
|
+
e.stopPropagation();
|
|
2233
|
+
handleBake(handleId);
|
|
2234
|
+
}, title: "Bake value", className: "pointer-events-auto border border-gray-300 rounded px-1 py-0.5 text-[10px] bg-white/80 hover:bg-white mr-2", children: "Bake" })), valueText !== undefined && (jsxRuntime.jsx("span", { className: "opacity-60 truncate pl-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText })), jsxRuntime.jsx("span", { className: "truncate shrink-0", style: { maxWidth: "40%" }, children: handleId })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "truncate shrink-0", style: { maxWidth: "40%" }, children: handleId }), valueText !== undefined && (jsxRuntime.jsx("span", { className: "opacity-60 truncate pr-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText }))] })), hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "shrink-0", title: title }))] }));
|
|
2080
2235
|
} })] }));
|
|
2081
2236
|
}
|
|
2082
2237
|
|
|
@@ -2641,6 +2796,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2641
2796
|
r.registerEnum({
|
|
2642
2797
|
id: t.id,
|
|
2643
2798
|
options: t.options,
|
|
2799
|
+
bakeTarget: t.bakeTarget,
|
|
2644
2800
|
});
|
|
2645
2801
|
}
|
|
2646
2802
|
else {
|
|
@@ -2648,6 +2804,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2648
2804
|
id: t.id,
|
|
2649
2805
|
displayName: t.displayName,
|
|
2650
2806
|
validate: (_v) => true,
|
|
2807
|
+
bakeTarget: t.bakeTarget,
|
|
2651
2808
|
});
|
|
2652
2809
|
}
|
|
2653
2810
|
}
|
|
@@ -2955,6 +3112,7 @@ exports.AbstractWorkbench = AbstractWorkbench;
|
|
|
2955
3112
|
exports.CLIWorkbench = CLIWorkbench;
|
|
2956
3113
|
exports.DefaultNode = DefaultNode;
|
|
2957
3114
|
exports.DefaultNodeContent = DefaultNodeContent;
|
|
3115
|
+
exports.DefaultNodeHeader = DefaultNodeHeader;
|
|
2958
3116
|
exports.DefaultUIExtensionRegistry = DefaultUIExtensionRegistry;
|
|
2959
3117
|
exports.InMemoryWorkbench = InMemoryWorkbench;
|
|
2960
3118
|
exports.Inspector = Inspector;
|