@bian-womp/spark-workbench 0.1.1 → 0.1.3

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 CHANGED
@@ -3,11 +3,11 @@
3
3
  var sparkGraph = require('@bian-womp/spark-graph');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var React = require('react');
6
+ var sparkRemote = require('@bian-womp/spark-remote');
6
7
  var ReactFlow = require('reactflow');
7
8
  require('reactflow/dist/style.css');
8
9
  var cx = require('classnames');
9
10
  var react = require('@phosphor-icons/react');
10
- var sparkRemote = require('@bian-womp/spark-remote');
11
11
 
12
12
  class DefaultUIExtensionRegistry {
13
13
  constructor() {
@@ -1157,7 +1157,8 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
1157
1157
  return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedEdge.typeId] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.lastError && (jsxRuntime.jsx("div", { className: "mt-2 text-sm text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 break-words", children: String(selectedNodeStatus.lastError?.message ??
1158
1158
  selectedNodeStatus.lastError) }))] })), jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
1159
1159
  const typeId = (selectedDesc?.inputs ?? {})[h];
1160
- const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId && e.target.handle === h);
1160
+ const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
1161
+ e.target.handle === h);
1161
1162
  const commonProps = {
1162
1163
  style: { flex: 1 },
1163
1164
  disabled: isLinked,
@@ -1176,7 +1177,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
1176
1177
  const orig = originals[h] ?? toDisplay(typeId, current);
1177
1178
  setDrafts((d) => ({ ...d, [h]: orig }));
1178
1179
  };
1179
- const isEnum = typeId?.startsWith("enum:");
1180
+ const isEnum = typeId?.includes("enum:");
1180
1181
  const inIssues = selectedNodeHandleValidation.inputs.filter((m) => m.handle === h);
1181
1182
  const hasValidation = inIssues.length > 0;
1182
1183
  const hasErr = inIssues.some((m) => m.level === "error");
@@ -1539,6 +1540,60 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
1539
1540
  setExampleState(key);
1540
1541
  onExampleChange?.(key);
1541
1542
  }, [runner, wb, onExampleChange, runAutoLayout]);
1543
+ const hydrateFromBackend = React.useCallback(async (kind, base) => {
1544
+ try {
1545
+ const transport = kind === "remote-http"
1546
+ ? new sparkRemote.HttpPollingTransport(base)
1547
+ : new sparkRemote.WebSocketTransport(base);
1548
+ await transport.connect();
1549
+ const rr = new sparkRemote.RemoteRunner(transport);
1550
+ const desc = await rr.describeRegistry();
1551
+ const r = new sparkGraph.Registry();
1552
+ // Types
1553
+ for (const t of desc.types) {
1554
+ r.registerType({
1555
+ id: t.id,
1556
+ displayName: t.displayName,
1557
+ validate: (_v) => true,
1558
+ });
1559
+ }
1560
+ // Categories: create placeholders for display name
1561
+ for (const c of desc.categories || []) {
1562
+ // If you later expose real category descriptors, register them here
1563
+ // For now, rely on ComputeCategory for behavior
1564
+ const category = {
1565
+ id: c.id,
1566
+ displayName: c.displayName,
1567
+ createRuntime: () => ({
1568
+ async onInputsChanged() { },
1569
+ }),
1570
+ policy: { mode: "push", asyncConcurrency: "switch" },
1571
+ };
1572
+ r.categories.register(category);
1573
+ }
1574
+ // Coercions (client-side no-op to satisfy validation) if provided
1575
+ for (const c of desc.coercions) {
1576
+ r.registerCoercion(c.from, c.to, (v) => v);
1577
+ }
1578
+ // Nodes (use no-op impl for compute)
1579
+ for (const n of desc.nodes) {
1580
+ r.registerNode({
1581
+ id: n.id,
1582
+ categoryId: n.categoryId,
1583
+ displayName: n.displayName,
1584
+ inputs: n.inputs || {},
1585
+ outputs: n.outputs || {},
1586
+ impl: () => { },
1587
+ });
1588
+ }
1589
+ setRegistry(r);
1590
+ wb.setRegistry(r);
1591
+ await transport.close();
1592
+ }
1593
+ catch (err) {
1594
+ console.error("Failed to hydrate registry from backend:", err);
1595
+ }
1596
+ }, [setRegistry, wb]);
1542
1597
  // Ensure initial example is loaded (and sync when example prop changes)
1543
1598
  React.useEffect(() => {
1544
1599
  applyExample(example ?? "simple");
@@ -1561,6 +1616,15 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
1561
1616
  // ignore
1562
1617
  }
1563
1618
  }, [engine, runner, wb]);
1619
+ // When switching to remote backend, auto-hydrate registry from backend
1620
+ React.useEffect(() => {
1621
+ if (backendKind === "remote-http" && httpBaseUrl) {
1622
+ void hydrateFromBackend("remote-http", httpBaseUrl);
1623
+ }
1624
+ else if (backendKind === "remote-ws" && wsUrl) {
1625
+ void hydrateFromBackend("remote-ws", wsUrl);
1626
+ }
1627
+ }, [backendKind, httpBaseUrl, wsUrl, hydrateFromBackend]);
1564
1628
  React.useEffect(() => {
1565
1629
  if (autoLayoutRan.current)
1566
1630
  return;
@@ -1597,33 +1661,33 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
1597
1661
  .map(map);
1598
1662
  };
1599
1663
  switch (typeId) {
1600
- case "float": {
1664
+ case "base.float": {
1601
1665
  const n = Number(raw);
1602
1666
  value = Number.isFinite(n) ? n : 0;
1603
1667
  break;
1604
1668
  }
1605
- case "bool": {
1669
+ case "base.bool": {
1606
1670
  value = Boolean(raw);
1607
1671
  break;
1608
1672
  }
1609
- case "string": {
1673
+ case "base.string": {
1610
1674
  value = String(raw);
1611
1675
  break;
1612
1676
  }
1613
- case "float[]": {
1677
+ case "base.float[]": {
1614
1678
  value = parseArray(String(raw), (x) => Number(x));
1615
1679
  break;
1616
1680
  }
1617
- case "bool[]": {
1681
+ case "base.bool[]": {
1618
1682
  value = parseArray(String(raw), (x) => /^(true|1)$/i.test(x));
1619
1683
  break;
1620
1684
  }
1621
- case "vec3": {
1685
+ case "base.vec3": {
1622
1686
  const arr = parseArray(String(raw), (x) => Number(x));
1623
1687
  value = [arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0];
1624
1688
  break;
1625
1689
  }
1626
- case "vec3[]": {
1690
+ case "base.vec3[]": {
1627
1691
  try {
1628
1692
  const parsed = JSON.parse(String(raw));
1629
1693
  if (Array.isArray(parsed)) {
@@ -1655,13 +1719,13 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
1655
1719
  const toDisplay = React.useCallback((typeId, value) => {
1656
1720
  if (value === undefined || value === null)
1657
1721
  return "";
1658
- if (typeId && typeId.startsWith("enum:")) {
1722
+ if (typeId && typeId.includes("enum:")) {
1659
1723
  const n = Number(value);
1660
1724
  const label = registry.getEnumLabel(typeId, n);
1661
1725
  return label ?? String(n);
1662
1726
  }
1663
1727
  const round4 = (n) => Math.round(Number(n) * 10000) / 10000;
1664
- if (typeId === "vec3" && Array.isArray(value)) {
1728
+ if (typeId === "base.vec3" && Array.isArray(value)) {
1665
1729
  const a = value;
1666
1730
  return [round4(a[0] ?? 0), round4(a[1] ?? 0), round4(a[2] ?? 0)].join(",");
1667
1731
  }