@growthub/cli 0.13.0 → 0.13.2
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/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +50 -25
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +141 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryReviewModal.jsx +38 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +522 -35
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +242 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +52 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +1203 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +163 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxOrchestrationEditorPanel.jsx +190 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolConfirmModal.jsx +64 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolDraftPanel.jsx +376 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/page.jsx +6 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +1062 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +10 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +906 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/page.jsx +12 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +492 -28
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +114 -30
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/field-contracts.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/nav-workflows.js +54 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +322 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +734 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +73 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-sidecar-routing.js +24 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +2 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +21 -1
- package/package.json +1 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
ArrowDownToLine,
|
|
6
|
+
Bot,
|
|
7
|
+
Filter,
|
|
8
|
+
Globe,
|
|
9
|
+
Maximize2,
|
|
10
|
+
Minus,
|
|
11
|
+
Plus,
|
|
12
|
+
Settings,
|
|
13
|
+
SlidersHorizontal,
|
|
14
|
+
Target,
|
|
15
|
+
ZoomIn,
|
|
16
|
+
ZoomOut
|
|
17
|
+
} from "lucide-react";
|
|
18
|
+
import { orderedGraphNodes, parseOrchestrationGraph } from "@/lib/orchestration-graph";
|
|
19
|
+
|
|
20
|
+
const NODE_TYPE_LABELS = {
|
|
21
|
+
input: "Input",
|
|
22
|
+
"api-registry-call": "API Registry",
|
|
23
|
+
"transform-filter": "Transform",
|
|
24
|
+
"normalize-output": "Transform",
|
|
25
|
+
"tool-result": "Result",
|
|
26
|
+
thinAdapter: "Agent",
|
|
27
|
+
"data-trigger": "Trigger",
|
|
28
|
+
"data-action": "Action",
|
|
29
|
+
"ai-agent": "AI",
|
|
30
|
+
"flow-control": "Flow",
|
|
31
|
+
"core-action": "Core",
|
|
32
|
+
"human-input": "Input"
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const NODE_ICONS = {
|
|
36
|
+
input: SlidersHorizontal,
|
|
37
|
+
"api-registry-call": Globe,
|
|
38
|
+
"transform-filter": Filter,
|
|
39
|
+
"normalize-output": Filter,
|
|
40
|
+
"tool-result": Target,
|
|
41
|
+
thinAdapter: Bot,
|
|
42
|
+
"data-trigger": SlidersHorizontal,
|
|
43
|
+
"data-action": Plus,
|
|
44
|
+
"ai-agent": Bot,
|
|
45
|
+
"flow-control": Settings,
|
|
46
|
+
"core-action": Globe,
|
|
47
|
+
"human-input": SlidersHorizontal
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const CONNECTOR_OPTIONS = [
|
|
51
|
+
{ id: "filter", label: "Add filter" },
|
|
52
|
+
{ id: "map", label: "Map fields" },
|
|
53
|
+
{ id: "preview", label: "Preview output" }
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
function nodeSubtitle(node) {
|
|
57
|
+
const config = node?.config || {};
|
|
58
|
+
if (node?.subtitle) return String(node.subtitle);
|
|
59
|
+
if (node?.type === "api-registry-call") {
|
|
60
|
+
const id = String(config.integrationId || config.registryId || "").trim();
|
|
61
|
+
const method = String(config.method || "GET").trim().toUpperCase();
|
|
62
|
+
const endpoint = String(config.endpoint || "").trim();
|
|
63
|
+
return endpoint ? `${id} · ${method} ${endpoint}` : id;
|
|
64
|
+
}
|
|
65
|
+
if (node?.type === "transform-filter" || node?.type === "normalize-output") {
|
|
66
|
+
return "Map fields and filter rows";
|
|
67
|
+
}
|
|
68
|
+
if (node?.type === "input") return "Manual or source payload";
|
|
69
|
+
if (node?.type === "tool-result") return "Save status and response";
|
|
70
|
+
if (node?.type === "thinAdapter") return node?.sandbox ? String(node.sandbox) : "Local agent step";
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function hoverHint(node) {
|
|
75
|
+
const type = String(node?.type || "");
|
|
76
|
+
if (type === "input") return "Configure input";
|
|
77
|
+
if (type === "api-registry-call") return "Configure API request";
|
|
78
|
+
if (type === "transform-filter" || type === "normalize-output") return "Map response fields";
|
|
79
|
+
if (type === "tool-result") return "Result settings";
|
|
80
|
+
if (type === "thinAdapter") return "Configure agent step";
|
|
81
|
+
return "Configure node";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function normalizedNodeType(node) {
|
|
85
|
+
const type = String(node?.type || "").trim();
|
|
86
|
+
if (type === "thinAdapter") return "AI Model";
|
|
87
|
+
return type || "node";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function nodeRecordName(node) {
|
|
91
|
+
if (node?.type === "thinAdapter") return String(node?.sandbox || node?.id || "").trim();
|
|
92
|
+
if (node?.config?.objectName) return String(node.config.objectName).trim();
|
|
93
|
+
if (node?.config?.objectId) return String(node.config.objectId).trim();
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function OrchestrationGraphCanvas({
|
|
98
|
+
graph,
|
|
99
|
+
selectedNodeId,
|
|
100
|
+
onSelectNode,
|
|
101
|
+
onConnectorAction,
|
|
102
|
+
showRunTest,
|
|
103
|
+
onRunTest,
|
|
104
|
+
runStatus,
|
|
105
|
+
runMessage,
|
|
106
|
+
statusLabel = "Draft",
|
|
107
|
+
}) {
|
|
108
|
+
const parsed = useMemo(() => parseOrchestrationGraph(graph) || graph, [graph]);
|
|
109
|
+
const nodes = useMemo(() => orderedGraphNodes(parsed), [parsed]);
|
|
110
|
+
const edges = useMemo(() => (Array.isArray(parsed?.edges) ? parsed.edges : []), [parsed]);
|
|
111
|
+
const [internalSelected, setInternalSelected] = useState(null);
|
|
112
|
+
const [connectorPopover, setConnectorPopover] = useState(null);
|
|
113
|
+
const [zoom, setZoom] = useState(1);
|
|
114
|
+
const activeId = selectedNodeId ?? internalSelected;
|
|
115
|
+
|
|
116
|
+
if (!nodes.length) {
|
|
117
|
+
return (
|
|
118
|
+
<div className="dm-orchestration-canvas dm-orchestration-canvas--empty">
|
|
119
|
+
<p>No orchestration nodes configured.</p>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function edgeBetween(fromId, toId) {
|
|
125
|
+
return edges.find((e) => String(e.from) === fromId && String(e.to) === toId);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className="dm-orchestration-canvas" aria-label="Orchestration graph field editor">
|
|
130
|
+
<span className={`dm-orchestration-canvas__badge is-${String(statusLabel || "draft").toLowerCase()}`}>{statusLabel}</span>
|
|
131
|
+
<div className="dm-orchestration-floating-tools" aria-label="Canvas tools">
|
|
132
|
+
<button type="button" title="Add node" aria-label="Add node" onClick={() => onConnectorAction?.({ action: "add-step", from: String(nodes[nodes.length - 1]?.id || ""), to: "" })}>
|
|
133
|
+
<Plus size={14} />
|
|
134
|
+
</button>
|
|
135
|
+
<button type="button" title="Tidy workflow" aria-label="Tidy workflow">
|
|
136
|
+
<Settings size={14} />
|
|
137
|
+
</button>
|
|
138
|
+
<button type="button" title="Zoom in" aria-label="Zoom in" onClick={() => setZoom((value) => Math.min(1.4, Number((value + 0.1).toFixed(2))))}>
|
|
139
|
+
<ZoomIn size={14} />
|
|
140
|
+
</button>
|
|
141
|
+
<button type="button" title="Zoom out" aria-label="Zoom out" onClick={() => setZoom((value) => Math.max(0.7, Number((value - 0.1).toFixed(2))))}>
|
|
142
|
+
<ZoomOut size={14} />
|
|
143
|
+
</button>
|
|
144
|
+
<button type="button" title="Reset zoom" aria-label="Reset zoom" onClick={() => setZoom(1)}>
|
|
145
|
+
<Maximize2 size={14} />
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
<div className="dm-orchestration-canvas__viewport" style={{ transform: `scale(${zoom})` }}>
|
|
149
|
+
{nodes.map((node, index) => {
|
|
150
|
+
const id = String(node.id || "");
|
|
151
|
+
const isSelected = activeId === id;
|
|
152
|
+
const prevId = index > 0 ? String(nodes[index - 1].id || "") : "";
|
|
153
|
+
const Icon = NODE_ICONS[node.type] || ArrowDownToLine;
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div key={id || index} className="dm-orchestration-canvas__step">
|
|
157
|
+
{index > 0 && (
|
|
158
|
+
<div className="dm-orchestration-connector">
|
|
159
|
+
<div className="dm-orchestration-connector__line" aria-hidden="true" />
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
className="dm-orchestration-connector__add"
|
|
163
|
+
aria-label="Add step"
|
|
164
|
+
onClick={() => {
|
|
165
|
+
onConnectorAction?.({ action: "add-step", from: prevId, to: id });
|
|
166
|
+
setConnectorPopover(null);
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<Plus size={14} />
|
|
170
|
+
</button>
|
|
171
|
+
{connectorPopover === `${prevId}-${id}` && (
|
|
172
|
+
<div className="dm-orchestration-connector__popover" role="menu">
|
|
173
|
+
<p>Add a step between these nodes</p>
|
|
174
|
+
{CONNECTOR_OPTIONS.map((opt) => (
|
|
175
|
+
<button
|
|
176
|
+
key={opt.id}
|
|
177
|
+
type="button"
|
|
178
|
+
role="menuitem"
|
|
179
|
+
onClick={() => {
|
|
180
|
+
onConnectorAction?.({ from: prevId, to: id, action: opt.id });
|
|
181
|
+
setConnectorPopover(null);
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
{opt.label}
|
|
185
|
+
</button>
|
|
186
|
+
))}
|
|
187
|
+
<button
|
|
188
|
+
type="button"
|
|
189
|
+
role="menuitem"
|
|
190
|
+
onClick={() => {
|
|
191
|
+
onConnectorAction?.({ action: "delete-edge-request", from: prevId, to: id });
|
|
192
|
+
setConnectorPopover(null);
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
<Minus size={12} /> Delete edge
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
<div
|
|
202
|
+
role="button"
|
|
203
|
+
tabIndex={0}
|
|
204
|
+
className={`dm-orchestration-node${isSelected ? " dm-orchestration-node--selected" : ""}`}
|
|
205
|
+
title={hoverHint(node)}
|
|
206
|
+
onClick={() => {
|
|
207
|
+
setInternalSelected(id);
|
|
208
|
+
onSelectNode?.(node);
|
|
209
|
+
}}
|
|
210
|
+
onKeyDown={(event) => {
|
|
211
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
212
|
+
event.preventDefault();
|
|
213
|
+
setInternalSelected(id);
|
|
214
|
+
onSelectNode?.(node);
|
|
215
|
+
}}
|
|
216
|
+
>
|
|
217
|
+
<span className="dm-orchestration-node__icon" aria-hidden="true">
|
|
218
|
+
<Icon size={14} />
|
|
219
|
+
</span>
|
|
220
|
+
<span className="dm-orchestration-node__type">{NODE_TYPE_LABELS[node.type] || node.type}</span>
|
|
221
|
+
<span className="dm-orchestration-node__title">{normalizedNodeType(node)}</span>
|
|
222
|
+
<span className="dm-orchestration-node__gear" aria-hidden="true">
|
|
223
|
+
<Settings size={13} />
|
|
224
|
+
</span>
|
|
225
|
+
<span className="dm-orchestration-node__subtitle">{nodeSubtitle(node)}</span>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
})}
|
|
230
|
+
</div>
|
|
231
|
+
{showRunTest && (
|
|
232
|
+
<div className="dm-orchestration-run-status">
|
|
233
|
+
<button type="button" className="dm-btn-primary-sm" onClick={onRunTest}>
|
|
234
|
+
Run sandbox
|
|
235
|
+
</button>
|
|
236
|
+
{runStatus && <span className={`dm-orchestration-run-status__badge is-${runStatus}`}>{runStatus}</span>}
|
|
237
|
+
{runMessage && <p className="dm-orchestration-run-status__message">{runMessage}</p>}
|
|
238
|
+
</div>
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
export function OrchestrationGraphEmptyCanvas({
|
|
6
|
+
onStartFromRegistry,
|
|
7
|
+
onStartBlank,
|
|
8
|
+
onPasteGraph,
|
|
9
|
+
disabled
|
|
10
|
+
}) {
|
|
11
|
+
const [showPaste, setShowPaste] = useState(false);
|
|
12
|
+
const [pasteText, setPasteText] = useState("");
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="dm-orchestration-canvas dm-orchestration-canvas--empty-state" aria-label="Empty orchestration graph">
|
|
16
|
+
<div className="dm-orchestration-canvas__empty-card">
|
|
17
|
+
<h3>Start orchestration graph</h3>
|
|
18
|
+
<p>Create a governed run plan for this sandbox tool. Nothing executes until Run sandbox.</p>
|
|
19
|
+
<div className="dm-orchestration-canvas__empty-actions">
|
|
20
|
+
<button type="button" className="dm-btn-primary-sm" disabled={disabled} onClick={onStartFromRegistry}>
|
|
21
|
+
Start from API Registry
|
|
22
|
+
</button>
|
|
23
|
+
<button type="button" className="dm-btn-outline" disabled={disabled} onClick={onStartBlank}>
|
|
24
|
+
Start blank
|
|
25
|
+
</button>
|
|
26
|
+
</div>
|
|
27
|
+
<details
|
|
28
|
+
className="dm-orchestration-canvas__paste"
|
|
29
|
+
open={showPaste}
|
|
30
|
+
onToggle={(e) => setShowPaste(e.target.open)}
|
|
31
|
+
>
|
|
32
|
+
<summary>Paste graph JSON</summary>
|
|
33
|
+
<textarea
|
|
34
|
+
rows={6}
|
|
35
|
+
value={pasteText}
|
|
36
|
+
disabled={disabled}
|
|
37
|
+
placeholder='{"version":1,"provider":"growthub-native","nodes":[],"edges":[]}'
|
|
38
|
+
onChange={(e) => setPasteText(e.target.value)}
|
|
39
|
+
/>
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
className="dm-btn-outline"
|
|
43
|
+
disabled={disabled || !pasteText.trim()}
|
|
44
|
+
onClick={() => onPasteGraph?.(pasteText)}
|
|
45
|
+
>
|
|
46
|
+
Apply pasted graph
|
|
47
|
+
</button>
|
|
48
|
+
</details>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|