@growthub/cli 0.12.2 → 0.13.1
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 +556 -248
- 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/components/dm-shared.jsx +8 -2
- 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 +2897 -934
- 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/views/[viewId]/page.jsx +206 -0
- 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 +493 -28
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +1363 -8
- 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 +13 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper-apply.js +96 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +122 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1203 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
detectFieldIdsFromLastResponse,
|
|
6
|
+
FILTER_CONJUNCTIONS,
|
|
7
|
+
FILTER_OPERATORS,
|
|
8
|
+
isApiRegistryTestSuccessful
|
|
9
|
+
} from "@/lib/orchestration-graph";
|
|
10
|
+
|
|
11
|
+
const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
12
|
+
const MODEL_OPTIONS = ["Claude Opus 4.6", "Claude Sonnet 4.5", "GPT-5.2", "Local agent host"];
|
|
13
|
+
const OUTPUT_TYPES = ["Text", "Number", "Boolean", "JSON", "Record ID"];
|
|
14
|
+
function normalizeTags(tags) {
|
|
15
|
+
return Array.from(new Set((Array.isArray(tags) ? tags : [])
|
|
16
|
+
.map((tag) => String(tag || "").trim().toLowerCase())
|
|
17
|
+
.filter(Boolean)));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function inferDeltaTagsForNode(node, config) {
|
|
21
|
+
const tags = [];
|
|
22
|
+
const type = String(node?.type || "").trim();
|
|
23
|
+
const action = String(config?.action || node?.id || "").trim();
|
|
24
|
+
|
|
25
|
+
if (type === "thinAdapter") tags.push("model", "prompt", "routing");
|
|
26
|
+
if (type === "ai-agent") tags.push("model", "prompt", "output");
|
|
27
|
+
if (type === "data-action" || type === "data-trigger") tags.push("input", "output");
|
|
28
|
+
if (type === "flow-control") tags.push("routing");
|
|
29
|
+
if (type === "core-action") tags.push("runtime");
|
|
30
|
+
if (type === "human-input") tags.push("input");
|
|
31
|
+
|
|
32
|
+
if (action.includes("search") || action.includes("filter") || type === "transform-filter") tags.push("evaluation", "guardrail");
|
|
33
|
+
if (action.includes("delete") || config?.confirmationRequired) tags.push("guardrail");
|
|
34
|
+
if (action.includes("http") || config?.url || config?.method) tags.push("routing", "input", "output");
|
|
35
|
+
if (action.includes("email")) tags.push("input", "output");
|
|
36
|
+
if (action.includes("delay") || config?.duration || config?.unit) tags.push("runtime");
|
|
37
|
+
if (config?.objectId || config?.fieldMap || config?.filters) tags.push("input", "output");
|
|
38
|
+
if (config?.model || config?.prompt) tags.push("model", "prompt");
|
|
39
|
+
|
|
40
|
+
return normalizeTags(tags);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getDeltaTagDefaultValue(tag, node, config, sandboxRow) {
|
|
44
|
+
const normalized = String(tag || "").trim().toLowerCase();
|
|
45
|
+
if (normalized === "model") {
|
|
46
|
+
return String(config?.model || sandboxRow?.adapter || sandboxRow?.executionAdapter || sandboxRow?.runAdapter || node?.sandbox || "").trim();
|
|
47
|
+
}
|
|
48
|
+
if (normalized === "prompt") {
|
|
49
|
+
return String(config?.prompt || sandboxRow?.prompt || sandboxRow?.instructions || sandboxRow?.command || "").trim();
|
|
50
|
+
}
|
|
51
|
+
if (normalized === "routing") {
|
|
52
|
+
return String(config?.inputBinding || config?.executionPolicy || config?.filterMode || config?.endpoint || config?.url || "").trim();
|
|
53
|
+
}
|
|
54
|
+
if (normalized === "input") {
|
|
55
|
+
return String(config?.objectName || config?.objectId || config?.inputBinding || config?.bodyTemplate || config?.samplePayload ? (
|
|
56
|
+
config?.objectName || config?.objectId || config?.inputBinding || "configured input"
|
|
57
|
+
) : "").trim();
|
|
58
|
+
}
|
|
59
|
+
if (normalized === "output") {
|
|
60
|
+
return String(config?.outputKey || config?.outputVariable || config?.outputField || config?.fieldMap ? (
|
|
61
|
+
config?.outputKey || config?.outputVariable || config?.outputField || "configured output"
|
|
62
|
+
) : "").trim();
|
|
63
|
+
}
|
|
64
|
+
if (normalized === "evaluation") {
|
|
65
|
+
return String(config?.filters || config?.successStatusCodes || config?.expectedStatus ? "configured evaluation" : "").trim();
|
|
66
|
+
}
|
|
67
|
+
if (normalized === "guardrail") {
|
|
68
|
+
return String(config?.confirmationRequired ? "confirmation required" : config?.networkPolicy || sandboxRow?.networkPolicy || "").trim();
|
|
69
|
+
}
|
|
70
|
+
if (normalized === "runtime") {
|
|
71
|
+
return String(config?.duration || config?.unit ? `${config.duration || ""} ${config.unit || ""}`.trim() : sandboxRow?.runtime || "").trim();
|
|
72
|
+
}
|
|
73
|
+
return String(config?.deltaValues?.[normalized] || "").trim();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getObjectFields(object) {
|
|
77
|
+
return (Array.isArray(object?.fields) ? object.fields : [])
|
|
78
|
+
.map((field) => ({
|
|
79
|
+
id: String(field.id || field.name || field.label || "").trim(),
|
|
80
|
+
label: String(field.label || field.name || field.id || "").trim(),
|
|
81
|
+
type: String(field.type || field.fieldType || "text").trim()
|
|
82
|
+
}))
|
|
83
|
+
.filter((field) => field.id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getSelectedObject(workspaceObjects, objectId) {
|
|
87
|
+
return workspaceObjects.find((object) => String(object.id) === String(objectId || "")) || null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function KeyValueRows({ label, entries, onChange, disabled, keyPlaceholder = "Key", valuePlaceholder = "Value" }) {
|
|
91
|
+
const rows = Array.isArray(entries)
|
|
92
|
+
? entries
|
|
93
|
+
: Object.entries(entries && typeof entries === "object" ? entries : {}).map(([key, value]) => ({ key, value }));
|
|
94
|
+
|
|
95
|
+
function normalize(nextRows) {
|
|
96
|
+
onChange(nextRows.filter((row) => String(row.key || row.name || "").trim()));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function updateRow(index, patch) {
|
|
100
|
+
const next = rows.map((row, i) => (i === index ? { ...row, ...patch } : row));
|
|
101
|
+
normalize(next);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className="dm-orchestration-config__fieldmap">
|
|
106
|
+
<span className="dm-orchestration-config__field-label">{label}</span>
|
|
107
|
+
{rows.map((row, index) => (
|
|
108
|
+
<div key={index} className="dm-orchestration-config__fieldmap-row">
|
|
109
|
+
<input
|
|
110
|
+
placeholder={keyPlaceholder}
|
|
111
|
+
value={row.key || row.name || ""}
|
|
112
|
+
disabled={disabled}
|
|
113
|
+
onChange={(e) => updateRow(index, { key: e.target.value })}
|
|
114
|
+
/>
|
|
115
|
+
<input
|
|
116
|
+
placeholder={valuePlaceholder}
|
|
117
|
+
value={row.value == null ? "" : String(row.value)}
|
|
118
|
+
disabled={disabled}
|
|
119
|
+
onChange={(e) => updateRow(index, { value: e.target.value })}
|
|
120
|
+
/>
|
|
121
|
+
<button type="button" className="dm-btn-ghost" disabled={disabled} onClick={() => normalize(rows.filter((_, i) => i !== index))}>
|
|
122
|
+
Remove
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
))}
|
|
126
|
+
<button type="button" className="dm-btn-outline" disabled={disabled} onClick={() => normalize([...rows, { key: "", value: "" }])}>
|
|
127
|
+
+ Add {label.toLowerCase()}
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function FilterClauseList({ filters, filterMode, onChange, disabled, fieldOptions = [] }) {
|
|
134
|
+
const clauses = Array.isArray(filters) ? filters : [];
|
|
135
|
+
const mode = FILTER_CONJUNCTIONS.includes(filterMode) ? filterMode : "and";
|
|
136
|
+
|
|
137
|
+
function updateClause(index, patch) {
|
|
138
|
+
const next = clauses.map((c, i) => (i === index ? { ...c, ...patch } : c));
|
|
139
|
+
onChange(next, mode);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function addClause() {
|
|
143
|
+
onChange([...clauses, { fieldId: "", operator: "eq", value: "" }], mode);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function removeClause(index) {
|
|
147
|
+
onChange(clauses.filter((_, i) => i !== index), mode);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div className="dm-orchestration-config__filters">
|
|
152
|
+
<p className="dm-orchestration-config__hint">Where</p>
|
|
153
|
+
<label className="dm-orchestration-config__field">
|
|
154
|
+
<span>Match</span>
|
|
155
|
+
<select value={mode} disabled={disabled} onChange={(e) => onChange(clauses, e.target.value)}>
|
|
156
|
+
{FILTER_CONJUNCTIONS.map((c) => (
|
|
157
|
+
<option key={c} value={c}>{c}</option>
|
|
158
|
+
))}
|
|
159
|
+
</select>
|
|
160
|
+
</label>
|
|
161
|
+
{clauses.map((clause, index) => (
|
|
162
|
+
<div key={index} className="dm-orchestration-config__filter-row">
|
|
163
|
+
{fieldOptions.length > 0 ? (
|
|
164
|
+
<select
|
|
165
|
+
value={clause.fieldId || ""}
|
|
166
|
+
disabled={disabled}
|
|
167
|
+
onChange={(e) => updateClause(index, { fieldId: e.target.value })}
|
|
168
|
+
>
|
|
169
|
+
<option value="">Select field</option>
|
|
170
|
+
{fieldOptions.map((field) => (
|
|
171
|
+
<option key={field} value={field}>{field}</option>
|
|
172
|
+
))}
|
|
173
|
+
</select>
|
|
174
|
+
) : (
|
|
175
|
+
<input
|
|
176
|
+
placeholder="field"
|
|
177
|
+
value={clause.fieldId || ""}
|
|
178
|
+
disabled={disabled}
|
|
179
|
+
onChange={(e) => updateClause(index, { fieldId: e.target.value })}
|
|
180
|
+
/>
|
|
181
|
+
)}
|
|
182
|
+
<select
|
|
183
|
+
value={clause.operator || "eq"}
|
|
184
|
+
disabled={disabled}
|
|
185
|
+
onChange={(e) => updateClause(index, { operator: e.target.value })}
|
|
186
|
+
>
|
|
187
|
+
{FILTER_OPERATORS.map((op) => (
|
|
188
|
+
<option key={op} value={op}>{op}</option>
|
|
189
|
+
))}
|
|
190
|
+
</select>
|
|
191
|
+
<input
|
|
192
|
+
placeholder="value"
|
|
193
|
+
value={clause.value ?? ""}
|
|
194
|
+
disabled={disabled || clause.operator === "isEmpty" || clause.operator === "isNotEmpty"}
|
|
195
|
+
onChange={(e) => updateClause(index, { value: e.target.value })}
|
|
196
|
+
/>
|
|
197
|
+
<button type="button" className="dm-btn-ghost" disabled={disabled} onClick={() => removeClause(index)}>
|
|
198
|
+
Remove
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
))}
|
|
202
|
+
<button type="button" className="dm-btn-outline dm-orchestration-config__add-filter" disabled={disabled} onClick={addClause}>
|
|
203
|
+
+ Add filter rule
|
|
204
|
+
</button>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function FieldMapRows({ fieldMap, onChange, disabled, fieldOptions = [] }) {
|
|
210
|
+
const entries = Object.entries(fieldMap && typeof fieldMap === "object" ? fieldMap : {});
|
|
211
|
+
|
|
212
|
+
function updateEntry(index, target, sourcePath) {
|
|
213
|
+
const next = [...entries];
|
|
214
|
+
next[index] = [target, sourcePath];
|
|
215
|
+
onChange(Object.fromEntries(next.filter(([t]) => String(t).trim())));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function addEntry() {
|
|
219
|
+
onChange({ ...Object.fromEntries(entries), "": "" });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function removeEntry(index) {
|
|
223
|
+
const next = entries.filter((_, i) => i !== index);
|
|
224
|
+
onChange(Object.fromEntries(next));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div className="dm-orchestration-config__fieldmap">
|
|
229
|
+
<span className="dm-orchestration-config__field-label">Field mapping</span>
|
|
230
|
+
{entries.length === 0 && (
|
|
231
|
+
<p className="dm-orchestration-config__hint">Map workspace field names to paths in the API response.</p>
|
|
232
|
+
)}
|
|
233
|
+
{entries.map(([target, sourcePath], index) => (
|
|
234
|
+
<div key={`${target}-${index}`} className="dm-orchestration-config__fieldmap-row">
|
|
235
|
+
<input
|
|
236
|
+
placeholder="Output field"
|
|
237
|
+
value={target}
|
|
238
|
+
disabled={disabled}
|
|
239
|
+
onChange={(e) => updateEntry(index, e.target.value, sourcePath)}
|
|
240
|
+
/>
|
|
241
|
+
{fieldOptions.length > 0 ? (
|
|
242
|
+
<select
|
|
243
|
+
value={sourcePath || ""}
|
|
244
|
+
disabled={disabled}
|
|
245
|
+
onChange={(e) => updateEntry(index, target, e.target.value)}
|
|
246
|
+
>
|
|
247
|
+
<option value="">Source path</option>
|
|
248
|
+
{fieldOptions.map((field) => (
|
|
249
|
+
<option key={field} value={field}>{field}</option>
|
|
250
|
+
))}
|
|
251
|
+
</select>
|
|
252
|
+
) : (
|
|
253
|
+
<input
|
|
254
|
+
placeholder="Source path"
|
|
255
|
+
value={sourcePath || ""}
|
|
256
|
+
disabled={disabled}
|
|
257
|
+
onChange={(e) => updateEntry(index, target, e.target.value)}
|
|
258
|
+
/>
|
|
259
|
+
)}
|
|
260
|
+
<button type="button" className="dm-btn-ghost" disabled={disabled} onClick={() => removeEntry(index)}>
|
|
261
|
+
Remove
|
|
262
|
+
</button>
|
|
263
|
+
</div>
|
|
264
|
+
))}
|
|
265
|
+
<button type="button" className="dm-btn-outline" disabled={disabled} onClick={addEntry}>
|
|
266
|
+
+ Add field mapping
|
|
267
|
+
</button>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function PayloadKeyRows({ payload, onChange, disabled }) {
|
|
273
|
+
const entries = Object.entries(payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {});
|
|
274
|
+
|
|
275
|
+
function setEntries(nextEntries) {
|
|
276
|
+
onChange(Object.fromEntries(nextEntries.filter(([k]) => String(k).trim())));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div className="dm-orchestration-config__payload">
|
|
281
|
+
<span className="dm-orchestration-config__field-label">Test payload fields</span>
|
|
282
|
+
{entries.map(([key, value], index) => (
|
|
283
|
+
<div key={index} className="dm-orchestration-config__payload-row">
|
|
284
|
+
<input
|
|
285
|
+
placeholder="key"
|
|
286
|
+
value={key}
|
|
287
|
+
disabled={disabled}
|
|
288
|
+
onChange={(e) => {
|
|
289
|
+
const next = [...entries];
|
|
290
|
+
next[index] = [e.target.value, value];
|
|
291
|
+
setEntries(next);
|
|
292
|
+
}}
|
|
293
|
+
/>
|
|
294
|
+
<input
|
|
295
|
+
placeholder="value"
|
|
296
|
+
value={value == null ? "" : String(value)}
|
|
297
|
+
disabled={disabled}
|
|
298
|
+
onChange={(e) => {
|
|
299
|
+
const next = [...entries];
|
|
300
|
+
next[index] = [key, e.target.value];
|
|
301
|
+
setEntries(next);
|
|
302
|
+
}}
|
|
303
|
+
/>
|
|
304
|
+
<button
|
|
305
|
+
type="button"
|
|
306
|
+
className="dm-btn-ghost"
|
|
307
|
+
disabled={disabled}
|
|
308
|
+
onClick={() => setEntries(entries.filter((_, i) => i !== index))}
|
|
309
|
+
>
|
|
310
|
+
Remove
|
|
311
|
+
</button>
|
|
312
|
+
</div>
|
|
313
|
+
))}
|
|
314
|
+
<button
|
|
315
|
+
type="button"
|
|
316
|
+
className="dm-btn-outline"
|
|
317
|
+
disabled={disabled}
|
|
318
|
+
onClick={() => setEntries([...entries, ["", ""]])}
|
|
319
|
+
>
|
|
320
|
+
+ Add payload field
|
|
321
|
+
</button>
|
|
322
|
+
</div>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function VersionDeltaControls({ node, config, sandboxRow, onChange, disabled }) {
|
|
327
|
+
const explicitTags = normalizeTags(config?.deltaTags);
|
|
328
|
+
const inferredTags = inferDeltaTagsForNode(node, config);
|
|
329
|
+
const selectedTags = explicitTags.length > 0 ? explicitTags : inferredTags;
|
|
330
|
+
const deltaValues = config?.deltaValues && typeof config.deltaValues === "object" && !Array.isArray(config.deltaValues)
|
|
331
|
+
? config.deltaValues
|
|
332
|
+
: {};
|
|
333
|
+
|
|
334
|
+
function patch(patchValue) {
|
|
335
|
+
onChange?.({ ...(config || {}), ...patchValue });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function patchDeltaValue(tag, value) {
|
|
339
|
+
patch({ deltaValues: { ...deltaValues, [tag]: value } });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return (
|
|
343
|
+
<div className="dm-orchestration-config__section dm-version-delta">
|
|
344
|
+
<label className="dm-orchestration-config__field">
|
|
345
|
+
<span className="dm-version-delta__label-row">
|
|
346
|
+
Delta tags
|
|
347
|
+
<span
|
|
348
|
+
className="dm-version-delta__info"
|
|
349
|
+
title={explicitTags.length > 0 ? "Saved orchestration config tags." : "Derived from this node's real bindings."}
|
|
350
|
+
aria-label={explicitTags.length > 0 ? "Saved orchestration config tags" : "Derived from this node's real bindings"}
|
|
351
|
+
>
|
|
352
|
+
i
|
|
353
|
+
</span>
|
|
354
|
+
</span>
|
|
355
|
+
<input
|
|
356
|
+
value={selectedTags.join(", ")}
|
|
357
|
+
placeholder="routing, prompt, evaluation"
|
|
358
|
+
disabled={disabled}
|
|
359
|
+
onChange={(event) => patch({ deltaTags: normalizeTags(event.target.value.split(",")) })}
|
|
360
|
+
/>
|
|
361
|
+
</label>
|
|
362
|
+
{selectedTags.length > 0 && (
|
|
363
|
+
<div className="dm-version-delta__tag-fields">
|
|
364
|
+
{selectedTags.map((tag) => (
|
|
365
|
+
<label key={tag} className="dm-orchestration-config__field">
|
|
366
|
+
<span>{tag} value</span>
|
|
367
|
+
<input
|
|
368
|
+
value={deltaValues[tag] ?? getDeltaTagDefaultValue(tag, node, config, sandboxRow)}
|
|
369
|
+
placeholder={`Set ${tag} delta value`}
|
|
370
|
+
disabled={disabled}
|
|
371
|
+
onChange={(event) => patchDeltaValue(tag, event.target.value)}
|
|
372
|
+
/>
|
|
373
|
+
</label>
|
|
374
|
+
))}
|
|
375
|
+
</div>
|
|
376
|
+
)}
|
|
377
|
+
</div>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function OrchestrationNodeConfigPanel({
|
|
382
|
+
node,
|
|
383
|
+
onConfigChange,
|
|
384
|
+
onDeleteNode,
|
|
385
|
+
disabled,
|
|
386
|
+
registryRow,
|
|
387
|
+
workspaceConfig,
|
|
388
|
+
sandboxRow,
|
|
389
|
+
activeTab: controlledTab,
|
|
390
|
+
onTabChange
|
|
391
|
+
}) {
|
|
392
|
+
const [internalTab, setInternalTab] = useState("node");
|
|
393
|
+
const rawActiveTab = controlledTab ?? internalTab;
|
|
394
|
+
|
|
395
|
+
function setActiveTab(tab) {
|
|
396
|
+
if (controlledTab == null) setInternalTab(tab);
|
|
397
|
+
onTabChange?.(tab);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const detectedFields = useMemo(
|
|
401
|
+
() => detectFieldIdsFromLastResponse(registryRow?.lastResponse),
|
|
402
|
+
[registryRow?.lastResponse]
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
if (!node) {
|
|
406
|
+
return (
|
|
407
|
+
<div className="dm-orchestration-config dm-orchestration-config--empty">
|
|
408
|
+
<p>Select a node on the canvas to configure it in this panel.</p>
|
|
409
|
+
</div>
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const config = node.config || {};
|
|
414
|
+
const type = String(node.type || "");
|
|
415
|
+
const meta = config.requestHeadersMetadata || {};
|
|
416
|
+
const workspaceObjects = (Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [])
|
|
417
|
+
.filter((object) => object?.id && object?.objectType !== "sandbox-environment" && object?.objectType !== "api-registry");
|
|
418
|
+
const selectedObject = getSelectedObject(workspaceObjects, config.objectId);
|
|
419
|
+
const selectedObjectFields = getObjectFields(selectedObject);
|
|
420
|
+
const selectedObjectFieldIds = selectedObjectFields.map((field) => field.id);
|
|
421
|
+
|
|
422
|
+
function patchConfig(patch) {
|
|
423
|
+
onConfigChange?.({ ...config, ...patch });
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const tabsForType = type === "api-registry-call" || type === "core-action"
|
|
427
|
+
? ["configuration", "test", "advanced"]
|
|
428
|
+
: type === "input" || type === "transform-filter" || type === "data-action" || type === "data-trigger" || type === "ai-agent" || type === "flow-control" || type === "human-input"
|
|
429
|
+
? ["configuration", "advanced"]
|
|
430
|
+
: ["configuration"];
|
|
431
|
+
const activeTab = tabsForType.includes(rawActiveTab) ? rawActiveTab : "configuration";
|
|
432
|
+
|
|
433
|
+
const registryConnected = isApiRegistryTestSuccessful(registryRow);
|
|
434
|
+
const responseMode = config.responseMode || config.mode || "json";
|
|
435
|
+
|
|
436
|
+
return (
|
|
437
|
+
<div className="dm-orchestration-config">
|
|
438
|
+
{tabsForType.length > 1 && (
|
|
439
|
+
<div className="dm-orchestration-config__tabs" role="tablist">
|
|
440
|
+
{tabsForType.map((tab) => (
|
|
441
|
+
<button
|
|
442
|
+
key={tab}
|
|
443
|
+
type="button"
|
|
444
|
+
role="tab"
|
|
445
|
+
aria-selected={activeTab === tab}
|
|
446
|
+
className={activeTab === tab ? "is-active" : ""}
|
|
447
|
+
onClick={() => setActiveTab(tab)}
|
|
448
|
+
>
|
|
449
|
+
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
|
450
|
+
</button>
|
|
451
|
+
))}
|
|
452
|
+
</div>
|
|
453
|
+
)}
|
|
454
|
+
|
|
455
|
+
{activeTab === "configuration" && type === "input" && (
|
|
456
|
+
<div className="dm-orchestration-config__pane">
|
|
457
|
+
<label className="dm-orchestration-config__field">
|
|
458
|
+
<span>Input mode</span>
|
|
459
|
+
<select value={config.inputMode || "manual"} disabled={disabled} onChange={(e) => patchConfig({ inputMode: e.target.value })}>
|
|
460
|
+
<option value="manual">manual</option>
|
|
461
|
+
<option value="record">record</option>
|
|
462
|
+
<option value="source-record">source-record</option>
|
|
463
|
+
</select>
|
|
464
|
+
</label>
|
|
465
|
+
<PayloadKeyRows
|
|
466
|
+
payload={config.samplePayload}
|
|
467
|
+
disabled={disabled}
|
|
468
|
+
onChange={(samplePayload) => patchConfig({ samplePayload })}
|
|
469
|
+
/>
|
|
470
|
+
<p className="dm-orchestration-config__hint">
|
|
471
|
+
Bind values with {"{{input.key}}"} in the API endpoint or body template.
|
|
472
|
+
</p>
|
|
473
|
+
</div>
|
|
474
|
+
)}
|
|
475
|
+
|
|
476
|
+
{activeTab === "configuration" && type === "api-registry-call" && (
|
|
477
|
+
<div className="dm-orchestration-config__pane">
|
|
478
|
+
{registryConnected && (
|
|
479
|
+
<span className="dm-orchestration-config__badge is-connected">Connected</span>
|
|
480
|
+
)}
|
|
481
|
+
<label className="dm-orchestration-config__field">
|
|
482
|
+
<span>Method</span>
|
|
483
|
+
<select
|
|
484
|
+
value={String(config.method || "GET").toUpperCase()}
|
|
485
|
+
disabled={disabled}
|
|
486
|
+
onChange={(e) => patchConfig({ method: e.target.value })}
|
|
487
|
+
>
|
|
488
|
+
{["GET", "POST", "PUT", "PATCH", "DELETE"].map((m) => (
|
|
489
|
+
<option key={m} value={m}>{m}</option>
|
|
490
|
+
))}
|
|
491
|
+
</select>
|
|
492
|
+
</label>
|
|
493
|
+
<label className="dm-orchestration-config__field">
|
|
494
|
+
<span>Endpoint</span>
|
|
495
|
+
<input value={config.endpoint || ""} disabled={disabled} onChange={(e) => patchConfig({ endpoint: e.target.value })} />
|
|
496
|
+
</label>
|
|
497
|
+
<label className="dm-orchestration-config__field">
|
|
498
|
+
<span>Body template</span>
|
|
499
|
+
<textarea rows={3} value={config.bodyTemplate || ""} disabled={disabled} onChange={(e) => patchConfig({ bodyTemplate: e.target.value })} />
|
|
500
|
+
</label>
|
|
501
|
+
<label className="dm-orchestration-config__field">
|
|
502
|
+
<span>Auth reference</span>
|
|
503
|
+
<input value={config.authRef || ""} disabled={disabled} onChange={(e) => patchConfig({ authRef: e.target.value })} />
|
|
504
|
+
</label>
|
|
505
|
+
<label className="dm-orchestration-config__field">
|
|
506
|
+
<span>Auth header name</span>
|
|
507
|
+
<input
|
|
508
|
+
value={meta.authHeaderName || config.authHeaderName || ""}
|
|
509
|
+
disabled={disabled}
|
|
510
|
+
onChange={(e) => patchConfig({
|
|
511
|
+
authHeaderName: e.target.value,
|
|
512
|
+
requestHeadersMetadata: { ...meta, authHeaderName: e.target.value }
|
|
513
|
+
})}
|
|
514
|
+
/>
|
|
515
|
+
</label>
|
|
516
|
+
<label className="dm-orchestration-config__field">
|
|
517
|
+
<span>Auth prefix</span>
|
|
518
|
+
<input
|
|
519
|
+
value={meta.authPrefix || config.authPrefix || ""}
|
|
520
|
+
disabled={disabled}
|
|
521
|
+
onChange={(e) => patchConfig({
|
|
522
|
+
authPrefix: e.target.value,
|
|
523
|
+
requestHeadersMetadata: { ...meta, authPrefix: e.target.value }
|
|
524
|
+
})}
|
|
525
|
+
/>
|
|
526
|
+
</label>
|
|
527
|
+
</div>
|
|
528
|
+
)}
|
|
529
|
+
|
|
530
|
+
{activeTab === "configuration" && type === "transform-filter" && (
|
|
531
|
+
<div className="dm-orchestration-config__pane">
|
|
532
|
+
{detectedFields.length > 0 && (
|
|
533
|
+
<p className="dm-orchestration-config__meta">{detectedFields.length} fields detected from last API test</p>
|
|
534
|
+
)}
|
|
535
|
+
<label className="dm-orchestration-config__field">
|
|
536
|
+
<span>Root path</span>
|
|
537
|
+
<input
|
|
538
|
+
value={config.rootPath || ""}
|
|
539
|
+
placeholder="data.items"
|
|
540
|
+
disabled={disabled}
|
|
541
|
+
onChange={(e) => patchConfig({ rootPath: e.target.value })}
|
|
542
|
+
/>
|
|
543
|
+
</label>
|
|
544
|
+
<label className="dm-orchestration-config__field">
|
|
545
|
+
<span>Response mode</span>
|
|
546
|
+
<select
|
|
547
|
+
value={responseMode}
|
|
548
|
+
disabled={disabled}
|
|
549
|
+
onChange={(e) => patchConfig({ responseMode: e.target.value, mode: e.target.value })}
|
|
550
|
+
>
|
|
551
|
+
<option value="json">json</option>
|
|
552
|
+
<option value="array">array</option>
|
|
553
|
+
<option value="object">object</option>
|
|
554
|
+
</select>
|
|
555
|
+
</label>
|
|
556
|
+
<FieldMapRows
|
|
557
|
+
fieldMap={config.fieldMap}
|
|
558
|
+
fieldOptions={detectedFields}
|
|
559
|
+
disabled={disabled}
|
|
560
|
+
onChange={(fieldMap) => patchConfig({ fieldMap })}
|
|
561
|
+
/>
|
|
562
|
+
</div>
|
|
563
|
+
)}
|
|
564
|
+
|
|
565
|
+
{activeTab === "configuration" && type === "tool-result" && (
|
|
566
|
+
<div className="dm-orchestration-config__pane">
|
|
567
|
+
{registryRow?.status && (
|
|
568
|
+
<span className={`dm-orchestration-config__badge is-${String(registryRow.status).toLowerCase()}`}>
|
|
569
|
+
Latest registry test: {registryRow.status}
|
|
570
|
+
</span>
|
|
571
|
+
)}
|
|
572
|
+
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
573
|
+
<input
|
|
574
|
+
type="checkbox"
|
|
575
|
+
checked={config.writeLastResponse !== false}
|
|
576
|
+
disabled={disabled}
|
|
577
|
+
onChange={(e) => patchConfig({ writeLastResponse: e.target.checked })}
|
|
578
|
+
/>
|
|
579
|
+
<span>Write lastResponse on success</span>
|
|
580
|
+
</label>
|
|
581
|
+
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
582
|
+
<input
|
|
583
|
+
type="checkbox"
|
|
584
|
+
checked={config.writeSourceRecord !== false}
|
|
585
|
+
disabled={disabled}
|
|
586
|
+
onChange={(e) => patchConfig({ writeSourceRecord: e.target.checked })}
|
|
587
|
+
/>
|
|
588
|
+
<span>Write source record history</span>
|
|
589
|
+
</label>
|
|
590
|
+
<label className="dm-orchestration-config__field">
|
|
591
|
+
<span>Success HTTP codes</span>
|
|
592
|
+
<input
|
|
593
|
+
value={Array.isArray(config.successStatusCodes) ? config.successStatusCodes.join(", ") : "200"}
|
|
594
|
+
disabled={disabled}
|
|
595
|
+
onChange={(e) => {
|
|
596
|
+
const codes = e.target.value.split(",").map((v) => Number(v.trim())).filter(Number.isFinite);
|
|
597
|
+
patchConfig({ successStatusCodes: codes.length ? codes : [200] });
|
|
598
|
+
}}
|
|
599
|
+
/>
|
|
600
|
+
</label>
|
|
601
|
+
<label className="dm-orchestration-config__field">
|
|
602
|
+
<span>Output mode</span>
|
|
603
|
+
<input value={config.outputMode || "normalized-json"} disabled={disabled} onChange={(e) => patchConfig({ outputMode: e.target.value })} />
|
|
604
|
+
</label>
|
|
605
|
+
{registryRow?.lastResponse && (
|
|
606
|
+
<div className="dm-orchestration-preview">
|
|
607
|
+
<span>Registry test preview</span>
|
|
608
|
+
<pre>{String(registryRow.lastResponse).slice(0, 400)}{String(registryRow.lastResponse).length > 400 ? "…" : ""}</pre>
|
|
609
|
+
</div>
|
|
610
|
+
)}
|
|
611
|
+
</div>
|
|
612
|
+
)}
|
|
613
|
+
|
|
614
|
+
{activeTab === "configuration" && type === "thinAdapter" && (
|
|
615
|
+
<div className="dm-orchestration-config__pane">
|
|
616
|
+
<div className="dm-orchestration-config__section">
|
|
617
|
+
<label className="dm-orchestration-config__field">
|
|
618
|
+
<span>Node id</span>
|
|
619
|
+
<input
|
|
620
|
+
value={node.id || ""}
|
|
621
|
+
disabled
|
|
622
|
+
/>
|
|
623
|
+
</label>
|
|
624
|
+
<label className="dm-orchestration-config__field">
|
|
625
|
+
<span>Node type</span>
|
|
626
|
+
<input value="AI Model" disabled />
|
|
627
|
+
</label>
|
|
628
|
+
<label className="dm-orchestration-config__field">
|
|
629
|
+
<span>Model reference</span>
|
|
630
|
+
<input
|
|
631
|
+
value={node.sandbox || ""}
|
|
632
|
+
disabled={disabled}
|
|
633
|
+
onChange={(event) => patchConfig({ __nodePatch: { sandbox: event.target.value } })}
|
|
634
|
+
/>
|
|
635
|
+
</label>
|
|
636
|
+
<label className="dm-orchestration-config__field">
|
|
637
|
+
<span>Execution policy</span>
|
|
638
|
+
<select
|
|
639
|
+
value={config.executionPolicy || "sequential"}
|
|
640
|
+
disabled={disabled}
|
|
641
|
+
onChange={(event) => patchConfig({ executionPolicy: event.target.value })}
|
|
642
|
+
>
|
|
643
|
+
<option value="sequential">sequential</option>
|
|
644
|
+
<option value="parallel">parallel</option>
|
|
645
|
+
<option value="conditional">conditional</option>
|
|
646
|
+
</select>
|
|
647
|
+
</label>
|
|
648
|
+
<label className="dm-orchestration-config__field">
|
|
649
|
+
<span>Input binding</span>
|
|
650
|
+
<input
|
|
651
|
+
value={config.inputBinding || ""}
|
|
652
|
+
placeholder="{{previous.output}}"
|
|
653
|
+
disabled={disabled}
|
|
654
|
+
onChange={(event) => patchConfig({ inputBinding: event.target.value })}
|
|
655
|
+
/>
|
|
656
|
+
</label>
|
|
657
|
+
<label className="dm-orchestration-config__field">
|
|
658
|
+
<span>Output key</span>
|
|
659
|
+
<input
|
|
660
|
+
value={config.outputKey || ""}
|
|
661
|
+
placeholder="result"
|
|
662
|
+
disabled={disabled}
|
|
663
|
+
onChange={(event) => patchConfig({ outputKey: event.target.value })}
|
|
664
|
+
/>
|
|
665
|
+
</label>
|
|
666
|
+
</div>
|
|
667
|
+
|
|
668
|
+
<VersionDeltaControls node={node} config={config} sandboxRow={sandboxRow} disabled={disabled} onChange={onConfigChange} />
|
|
669
|
+
|
|
670
|
+
<details className="dm-orchestration-config__advanced-json dm-orchestration-config__node-json">
|
|
671
|
+
<summary>Node JSON</summary>
|
|
672
|
+
<pre className="dm-orchestration-preview"><code>{JSON.stringify(node, null, 2)}</code></pre>
|
|
673
|
+
</details>
|
|
674
|
+
</div>
|
|
675
|
+
)}
|
|
676
|
+
|
|
677
|
+
{activeTab === "configuration" && (type === "data-action" || type === "data-trigger") && (
|
|
678
|
+
<div className="dm-orchestration-config__pane">
|
|
679
|
+
{config.destructive && (
|
|
680
|
+
<span className="dm-orchestration-config__badge is-failed">Double confirmation required</span>
|
|
681
|
+
)}
|
|
682
|
+
<label className="dm-orchestration-config__field">
|
|
683
|
+
<span>Workspace object</span>
|
|
684
|
+
<select
|
|
685
|
+
value={config.objectId || ""}
|
|
686
|
+
disabled={disabled}
|
|
687
|
+
onChange={(e) => {
|
|
688
|
+
const selected = workspaceObjects.find((object) => String(object.id) === e.target.value);
|
|
689
|
+
const objectName = String(selected?.name || selected?.label || selected?.id || "");
|
|
690
|
+
patchConfig({
|
|
691
|
+
objectId: e.target.value,
|
|
692
|
+
objectType: String(selected?.objectType || ""),
|
|
693
|
+
objectName,
|
|
694
|
+
__nodePatch: {
|
|
695
|
+
subtitle: objectName || "Select workspace object"
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
}}
|
|
699
|
+
>
|
|
700
|
+
<option value="">Select object</option>
|
|
701
|
+
{workspaceObjects.map((object) => (
|
|
702
|
+
<option key={object.id} value={object.id}>
|
|
703
|
+
{object.name || object.label || object.id}
|
|
704
|
+
</option>
|
|
705
|
+
))}
|
|
706
|
+
</select>
|
|
707
|
+
</label>
|
|
708
|
+
<label className="dm-orchestration-config__field">
|
|
709
|
+
<span>Action</span>
|
|
710
|
+
<select value={config.action || node.id || ""} disabled={disabled} onChange={(e) => patchConfig({ action: e.target.value })}>
|
|
711
|
+
<option value="create-record">Create Record</option>
|
|
712
|
+
<option value="update-record">Update Record</option>
|
|
713
|
+
<option value="delete-record">Delete Record</option>
|
|
714
|
+
<option value="search-records">Search Records</option>
|
|
715
|
+
<option value="upsert-record">Create or Update Record</option>
|
|
716
|
+
<option value="record-created">Record is created</option>
|
|
717
|
+
<option value="record-updated">Record is updated</option>
|
|
718
|
+
<option value="record-deleted">Record is deleted</option>
|
|
719
|
+
</select>
|
|
720
|
+
</label>
|
|
721
|
+
{selectedObjectFields.length > 0 && (
|
|
722
|
+
<div className="dm-orchestration-config__section">
|
|
723
|
+
<span>Object fields</span>
|
|
724
|
+
{(config.action === "search-records" || config.action === "update-record" || config.action === "delete-record" || config.action === "upsert-record") && (
|
|
725
|
+
<FilterClauseList
|
|
726
|
+
filters={config.filters}
|
|
727
|
+
filterMode={config.filterMode}
|
|
728
|
+
disabled={disabled}
|
|
729
|
+
fieldOptions={selectedObjectFieldIds}
|
|
730
|
+
onChange={(filters, filterMode) => patchConfig({ filters, filterMode })}
|
|
731
|
+
/>
|
|
732
|
+
)}
|
|
733
|
+
{(config.action === "create-record" || config.action === "update-record" || config.action === "upsert-record") && (
|
|
734
|
+
<FieldMapRows
|
|
735
|
+
fieldMap={config.fieldValues}
|
|
736
|
+
fieldOptions={selectedObjectFieldIds}
|
|
737
|
+
disabled={disabled}
|
|
738
|
+
onChange={(fieldValues) => patchConfig({ fieldValues })}
|
|
739
|
+
/>
|
|
740
|
+
)}
|
|
741
|
+
{config.action === "search-records" && (
|
|
742
|
+
<>
|
|
743
|
+
<label className="dm-orchestration-config__field">
|
|
744
|
+
<span>Result limit</span>
|
|
745
|
+
<input type="number" min="1" value={config.limit ?? 25} disabled={disabled} onChange={(e) => patchConfig({ limit: Number(e.target.value) })} />
|
|
746
|
+
</label>
|
|
747
|
+
<label className="dm-orchestration-config__field">
|
|
748
|
+
<span>Sort field</span>
|
|
749
|
+
<select value={config.sortField || ""} disabled={disabled} onChange={(e) => patchConfig({ sortField: e.target.value })}>
|
|
750
|
+
<option value="">No sort</option>
|
|
751
|
+
{selectedObjectFields.map((field) => <option key={field.id} value={field.id}>{field.label}</option>)}
|
|
752
|
+
</select>
|
|
753
|
+
</label>
|
|
754
|
+
</>
|
|
755
|
+
)}
|
|
756
|
+
</div>
|
|
757
|
+
)}
|
|
758
|
+
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
759
|
+
<input
|
|
760
|
+
type="checkbox"
|
|
761
|
+
checked={config.confirmationRequired === true}
|
|
762
|
+
disabled={disabled || config.destructive === true}
|
|
763
|
+
onChange={(e) => patchConfig({ confirmationRequired: e.target.checked })}
|
|
764
|
+
/>
|
|
765
|
+
<span>Require confirmation before destructive or version-changing execution</span>
|
|
766
|
+
</label>
|
|
767
|
+
<p className="dm-orchestration-config__hint">
|
|
768
|
+
Data actions bind only to this workspace data model. Execution resolves the latest object schema at run time.
|
|
769
|
+
</p>
|
|
770
|
+
</div>
|
|
771
|
+
)}
|
|
772
|
+
|
|
773
|
+
{activeTab === "configuration" && type === "ai-agent" && (
|
|
774
|
+
<div className="dm-orchestration-config__pane">
|
|
775
|
+
<label className="dm-orchestration-config__field">
|
|
776
|
+
<span>Model</span>
|
|
777
|
+
<select value={config.model || MODEL_OPTIONS[0]} disabled={disabled} onChange={(e) => patchConfig({ model: e.target.value })}>
|
|
778
|
+
{MODEL_OPTIONS.map((model) => <option key={model} value={model}>{model}</option>)}
|
|
779
|
+
</select>
|
|
780
|
+
</label>
|
|
781
|
+
<label className="dm-orchestration-config__field">
|
|
782
|
+
<span>Input prompt</span>
|
|
783
|
+
<textarea
|
|
784
|
+
rows={4}
|
|
785
|
+
placeholder="Describe what you want the AI to do..."
|
|
786
|
+
value={config.prompt || ""}
|
|
787
|
+
disabled={disabled}
|
|
788
|
+
onChange={(e) => patchConfig({ prompt: e.target.value })}
|
|
789
|
+
/>
|
|
790
|
+
</label>
|
|
791
|
+
<div className="dm-orchestration-config__section">
|
|
792
|
+
<span>Permissions</span>
|
|
793
|
+
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
794
|
+
<input
|
|
795
|
+
type="checkbox"
|
|
796
|
+
checked={config.canReadWorkspace !== false}
|
|
797
|
+
disabled={disabled}
|
|
798
|
+
onChange={(e) => patchConfig({ canReadWorkspace: e.target.checked })}
|
|
799
|
+
/>
|
|
800
|
+
<span>Read workspace data</span>
|
|
801
|
+
</label>
|
|
802
|
+
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
803
|
+
<input
|
|
804
|
+
type="checkbox"
|
|
805
|
+
checked={config.canWriteDraft === true}
|
|
806
|
+
disabled={disabled}
|
|
807
|
+
onChange={(e) => patchConfig({ canWriteDraft: e.target.checked })}
|
|
808
|
+
/>
|
|
809
|
+
<span>Write draft changes only</span>
|
|
810
|
+
</label>
|
|
811
|
+
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
812
|
+
<input
|
|
813
|
+
type="checkbox"
|
|
814
|
+
checked={config.networkAccess === true}
|
|
815
|
+
disabled={disabled}
|
|
816
|
+
onChange={(e) => patchConfig({ networkAccess: e.target.checked })}
|
|
817
|
+
/>
|
|
818
|
+
<span>Allow network access</span>
|
|
819
|
+
</label>
|
|
820
|
+
</div>
|
|
821
|
+
<KeyValueRows
|
|
822
|
+
label="Output fields"
|
|
823
|
+
entries={config.outputs}
|
|
824
|
+
disabled={disabled}
|
|
825
|
+
keyPlaceholder="Variable name"
|
|
826
|
+
valuePlaceholder="Instruction for AI"
|
|
827
|
+
onChange={(outputs) => patchConfig({ outputs })}
|
|
828
|
+
/>
|
|
829
|
+
<label className="dm-orchestration-config__field">
|
|
830
|
+
<span>Default output type</span>
|
|
831
|
+
<select value={config.outputType || "Text"} disabled={disabled} onChange={(e) => patchConfig({ outputType: e.target.value })}>
|
|
832
|
+
{OUTPUT_TYPES.map((item) => <option key={item} value={item}>{item}</option>)}
|
|
833
|
+
</select>
|
|
834
|
+
</label>
|
|
835
|
+
</div>
|
|
836
|
+
)}
|
|
837
|
+
|
|
838
|
+
{activeTab === "configuration" && type === "flow-control" && (
|
|
839
|
+
<div className="dm-orchestration-config__pane">
|
|
840
|
+
{config.action === "iterator" && (
|
|
841
|
+
<>
|
|
842
|
+
<label className="dm-orchestration-config__field">
|
|
843
|
+
<span>Collection path</span>
|
|
844
|
+
<input placeholder="{{previous.records}}" value={config.collectionPath || ""} disabled={disabled} onChange={(e) => patchConfig({ collectionPath: e.target.value })} />
|
|
845
|
+
</label>
|
|
846
|
+
<label className="dm-orchestration-config__field">
|
|
847
|
+
<span>Item variable</span>
|
|
848
|
+
<input placeholder="item" value={config.itemVariable || "item"} disabled={disabled} onChange={(e) => patchConfig({ itemVariable: e.target.value })} />
|
|
849
|
+
</label>
|
|
850
|
+
<label className="dm-orchestration-config__field">
|
|
851
|
+
<span>Concurrency</span>
|
|
852
|
+
<input type="number" min="1" value={config.concurrency ?? 1} disabled={disabled} onChange={(e) => patchConfig({ concurrency: Number(e.target.value) })} />
|
|
853
|
+
</label>
|
|
854
|
+
</>
|
|
855
|
+
)}
|
|
856
|
+
{config.action === "filter" && (
|
|
857
|
+
<FilterClauseList
|
|
858
|
+
filters={config.filters}
|
|
859
|
+
filterMode={config.filterMode}
|
|
860
|
+
disabled={disabled}
|
|
861
|
+
fieldOptions={detectedFields}
|
|
862
|
+
onChange={(filters, filterMode) => patchConfig({ filters, filterMode })}
|
|
863
|
+
/>
|
|
864
|
+
)}
|
|
865
|
+
{config.action === "if-else" && (
|
|
866
|
+
<>
|
|
867
|
+
<FilterClauseList
|
|
868
|
+
filters={config.conditions}
|
|
869
|
+
filterMode={config.conditionMode}
|
|
870
|
+
disabled={disabled}
|
|
871
|
+
fieldOptions={detectedFields}
|
|
872
|
+
onChange={(conditions, conditionMode) => patchConfig({ conditions, conditionMode })}
|
|
873
|
+
/>
|
|
874
|
+
<label className="dm-orchestration-config__field">
|
|
875
|
+
<span>True branch label</span>
|
|
876
|
+
<input value={config.trueLabel || "Yes"} disabled={disabled} onChange={(e) => patchConfig({ trueLabel: e.target.value })} />
|
|
877
|
+
</label>
|
|
878
|
+
<label className="dm-orchestration-config__field">
|
|
879
|
+
<span>False branch label</span>
|
|
880
|
+
<input value={config.falseLabel || "No"} disabled={disabled} onChange={(e) => patchConfig({ falseLabel: e.target.value })} />
|
|
881
|
+
</label>
|
|
882
|
+
</>
|
|
883
|
+
)}
|
|
884
|
+
{config.action === "delay" && (
|
|
885
|
+
<>
|
|
886
|
+
<label className="dm-orchestration-config__field">
|
|
887
|
+
<span>Delay amount</span>
|
|
888
|
+
<input type="number" min="1" value={config.delayAmount ?? 5} disabled={disabled} onChange={(e) => patchConfig({ delayAmount: Number(e.target.value) })} />
|
|
889
|
+
</label>
|
|
890
|
+
<label className="dm-orchestration-config__field">
|
|
891
|
+
<span>Delay unit</span>
|
|
892
|
+
<select value={config.delayUnit || "minutes"} disabled={disabled} onChange={(e) => patchConfig({ delayUnit: e.target.value })}>
|
|
893
|
+
<option value="seconds">seconds</option>
|
|
894
|
+
<option value="minutes">minutes</option>
|
|
895
|
+
<option value="hours">hours</option>
|
|
896
|
+
<option value="days">days</option>
|
|
897
|
+
</select>
|
|
898
|
+
</label>
|
|
899
|
+
</>
|
|
900
|
+
)}
|
|
901
|
+
</div>
|
|
902
|
+
)}
|
|
903
|
+
|
|
904
|
+
{activeTab === "configuration" && type === "core-action" && (
|
|
905
|
+
<div className="dm-orchestration-config__pane">
|
|
906
|
+
{(config.action === "http-request" || node.id === "http-request") && (
|
|
907
|
+
<>
|
|
908
|
+
<label className="dm-orchestration-config__field">
|
|
909
|
+
<span>URL</span>
|
|
910
|
+
<input placeholder="https://api.example.com/endpoint" value={config.url || ""} disabled={disabled} onChange={(e) => patchConfig({ url: e.target.value })} />
|
|
911
|
+
</label>
|
|
912
|
+
<label className="dm-orchestration-config__field">
|
|
913
|
+
<span>HTTP Method</span>
|
|
914
|
+
<select value={String(config.method || "GET").toUpperCase()} disabled={disabled} onChange={(e) => patchConfig({ method: e.target.value })}>
|
|
915
|
+
{HTTP_METHODS.map((method) => <option key={method} value={method}>{method}</option>)}
|
|
916
|
+
</select>
|
|
917
|
+
</label>
|
|
918
|
+
<KeyValueRows
|
|
919
|
+
label="Headers Input"
|
|
920
|
+
entries={config.headers}
|
|
921
|
+
disabled={disabled}
|
|
922
|
+
keyPlaceholder="Header name"
|
|
923
|
+
valuePlaceholder="Header value"
|
|
924
|
+
onChange={(headers) => patchConfig({ headers })}
|
|
925
|
+
/>
|
|
926
|
+
<label className="dm-orchestration-config__field">
|
|
927
|
+
<span>Request Body</span>
|
|
928
|
+
<textarea rows={4} placeholder="{ }" value={config.body || ""} disabled={disabled} onChange={(e) => patchConfig({ body: e.target.value })} />
|
|
929
|
+
</label>
|
|
930
|
+
<label className="dm-orchestration-config__field">
|
|
931
|
+
<span>Expected Response Body</span>
|
|
932
|
+
<textarea
|
|
933
|
+
rows={5}
|
|
934
|
+
placeholder={'{\n "id": "123",\n "status": "ok"\n}'}
|
|
935
|
+
value={config.expectedResponseBody || ""}
|
|
936
|
+
disabled={disabled}
|
|
937
|
+
onChange={(e) => patchConfig({ expectedResponseBody: e.target.value })}
|
|
938
|
+
/>
|
|
939
|
+
</label>
|
|
940
|
+
</>
|
|
941
|
+
)}
|
|
942
|
+
{(config.action === "send-email" || config.action === "draft-email") && (
|
|
943
|
+
<>
|
|
944
|
+
<label className="dm-orchestration-config__field">
|
|
945
|
+
<span>To</span>
|
|
946
|
+
<input placeholder="{{record.email}}" value={config.to || ""} disabled={disabled} onChange={(e) => patchConfig({ to: e.target.value })} />
|
|
947
|
+
</label>
|
|
948
|
+
<label className="dm-orchestration-config__field">
|
|
949
|
+
<span>Subject</span>
|
|
950
|
+
<input value={config.subject || ""} disabled={disabled} onChange={(e) => patchConfig({ subject: e.target.value })} />
|
|
951
|
+
</label>
|
|
952
|
+
<label className="dm-orchestration-config__field">
|
|
953
|
+
<span>Message</span>
|
|
954
|
+
<textarea rows={6} value={config.message || ""} disabled={disabled} onChange={(e) => patchConfig({ message: e.target.value })} />
|
|
955
|
+
</label>
|
|
956
|
+
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
957
|
+
<input type="checkbox" checked={config.requireApproval !== false} disabled={disabled} onChange={(e) => patchConfig({ requireApproval: e.target.checked })} />
|
|
958
|
+
<span>Require approval before sending</span>
|
|
959
|
+
</label>
|
|
960
|
+
</>
|
|
961
|
+
)}
|
|
962
|
+
{config.action === "code-function" && (
|
|
963
|
+
<>
|
|
964
|
+
<label className="dm-orchestration-config__field">
|
|
965
|
+
<span>Function name</span>
|
|
966
|
+
<input value={config.functionName || "logicFunction"} disabled={disabled} onChange={(e) => patchConfig({ functionName: e.target.value })} />
|
|
967
|
+
</label>
|
|
968
|
+
<label className="dm-orchestration-config__field">
|
|
969
|
+
<span>Code</span>
|
|
970
|
+
<textarea rows={8} placeholder="return input;" value={config.code || ""} disabled={disabled} onChange={(e) => patchConfig({ code: e.target.value })} />
|
|
971
|
+
</label>
|
|
972
|
+
<KeyValueRows label="Environment references" entries={config.envRefs} disabled={disabled} onChange={(envRefs) => patchConfig({ envRefs })} />
|
|
973
|
+
</>
|
|
974
|
+
)}
|
|
975
|
+
</div>
|
|
976
|
+
)}
|
|
977
|
+
|
|
978
|
+
{activeTab === "configuration" && type === "human-input" && (
|
|
979
|
+
<div className="dm-orchestration-config__pane">
|
|
980
|
+
<label className="dm-orchestration-config__field">
|
|
981
|
+
<span>Form title</span>
|
|
982
|
+
<input value={config.title || "Review input"} disabled={disabled} onChange={(e) => patchConfig({ title: e.target.value })} />
|
|
983
|
+
</label>
|
|
984
|
+
<label className="dm-orchestration-config__field">
|
|
985
|
+
<span>Instructions</span>
|
|
986
|
+
<textarea rows={4} value={config.instructions || ""} disabled={disabled} onChange={(e) => patchConfig({ instructions: e.target.value })} />
|
|
987
|
+
</label>
|
|
988
|
+
<KeyValueRows
|
|
989
|
+
label="Form fields"
|
|
990
|
+
entries={config.fields}
|
|
991
|
+
disabled={disabled}
|
|
992
|
+
keyPlaceholder="Field name"
|
|
993
|
+
valuePlaceholder="Field type or help text"
|
|
994
|
+
onChange={(fields) => patchConfig({ fields })}
|
|
995
|
+
/>
|
|
996
|
+
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
997
|
+
<input type="checkbox" checked={config.required !== false} disabled={disabled} onChange={(e) => patchConfig({ required: e.target.checked })} />
|
|
998
|
+
<span>Require response before continuing</span>
|
|
999
|
+
</label>
|
|
1000
|
+
</div>
|
|
1001
|
+
)}
|
|
1002
|
+
|
|
1003
|
+
{activeTab === "test" && (
|
|
1004
|
+
<div className="dm-orchestration-config__pane">
|
|
1005
|
+
<label className="dm-orchestration-config__field">
|
|
1006
|
+
<span>Success condition</span>
|
|
1007
|
+
<input value={config.successCondition || "HTTP 200 or successful execution"} disabled={disabled} onChange={(e) => patchConfig({ successCondition: e.target.value })} />
|
|
1008
|
+
</label>
|
|
1009
|
+
<label className="dm-orchestration-config__field">
|
|
1010
|
+
<span>Sample response</span>
|
|
1011
|
+
<textarea rows={5} value={config.sampleResponse || ""} disabled={disabled} onChange={(e) => patchConfig({ sampleResponse: e.target.value })} />
|
|
1012
|
+
</label>
|
|
1013
|
+
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
1014
|
+
<input type="checkbox" checked={config.blockPublishOnFailure !== false} disabled={disabled} onChange={(e) => patchConfig({ blockPublishOnFailure: e.target.checked })} />
|
|
1015
|
+
<span>Block Publish unless this step passes</span>
|
|
1016
|
+
</label>
|
|
1017
|
+
</div>
|
|
1018
|
+
)}
|
|
1019
|
+
|
|
1020
|
+
{activeTab === "advanced" && (type === "data-action" || type === "data-trigger" || type === "ai-agent" || type === "flow-control" || type === "core-action" || type === "human-input") && (
|
|
1021
|
+
<div className="dm-orchestration-config__pane">
|
|
1022
|
+
<details className="dm-orchestration-config__advanced-json" open>
|
|
1023
|
+
<summary>Advanced JSON</summary>
|
|
1024
|
+
<pre className="dm-orchestration-preview">{JSON.stringify(config, null, 2)}</pre>
|
|
1025
|
+
</details>
|
|
1026
|
+
</div>
|
|
1027
|
+
)}
|
|
1028
|
+
|
|
1029
|
+
{activeTab === "filters" && (type === "input" || type === "transform-filter") && (
|
|
1030
|
+
<FilterClauseList
|
|
1031
|
+
filters={config.filters}
|
|
1032
|
+
filterMode={config.filterMode}
|
|
1033
|
+
disabled={disabled}
|
|
1034
|
+
fieldOptions={type === "transform-filter" ? detectedFields : []}
|
|
1035
|
+
onChange={(filters, filterMode) => patchConfig({ filters, filterMode })}
|
|
1036
|
+
/>
|
|
1037
|
+
)}
|
|
1038
|
+
|
|
1039
|
+
{activeTab === "preview" && (
|
|
1040
|
+
<div className="dm-orchestration-config__pane">
|
|
1041
|
+
{type === "thinAdapter" ? (
|
|
1042
|
+
<>
|
|
1043
|
+
<ul className="dm-orchestration-config__preview-list">
|
|
1044
|
+
<li>Sandbox: {node.sandbox || node.id || "unknown"}</li>
|
|
1045
|
+
<li>Type: thinAdapter</li>
|
|
1046
|
+
</ul>
|
|
1047
|
+
<details className="dm-orchestration-config__advanced-json dm-orchestration-config__node-json">
|
|
1048
|
+
<summary>Node JSON</summary>
|
|
1049
|
+
<pre className="dm-orchestration-preview"><code>{JSON.stringify(node, null, 2)}</code></pre>
|
|
1050
|
+
</details>
|
|
1051
|
+
</>
|
|
1052
|
+
) : type === "tool-result" ? (
|
|
1053
|
+
<>
|
|
1054
|
+
<p className="dm-orchestration-config__hint">
|
|
1055
|
+
After Run sandbox, status, lastTested, and lastResponse update on the sandbox row.
|
|
1056
|
+
</p>
|
|
1057
|
+
<ul className="dm-orchestration-config__preview-list">
|
|
1058
|
+
<li>Success codes: {Array.isArray(config.successStatusCodes) ? config.successStatusCodes.join(", ") : "200"}</li>
|
|
1059
|
+
<li>Write lastResponse: {config.writeLastResponse !== false ? "yes" : "no"}</li>
|
|
1060
|
+
<li>Write source record: {config.writeSourceRecord !== false ? "yes" : "no"}</li>
|
|
1061
|
+
<li>Output mode: {config.outputMode || "normalized-json"}</li>
|
|
1062
|
+
</ul>
|
|
1063
|
+
{registryRow?.lastResponse && (
|
|
1064
|
+
<div className="dm-orchestration-preview">
|
|
1065
|
+
<span>Latest API test output (preview)</span>
|
|
1066
|
+
<pre>{String(registryRow.lastResponse).slice(0, 500)}{String(registryRow.lastResponse).length > 500 ? "…" : ""}</pre>
|
|
1067
|
+
</div>
|
|
1068
|
+
)}
|
|
1069
|
+
</>
|
|
1070
|
+
) : (
|
|
1071
|
+
<p className="dm-orchestration-config__hint">Advanced node configuration preview.</p>
|
|
1072
|
+
)}
|
|
1073
|
+
<details className="dm-orchestration-config__advanced-json">
|
|
1074
|
+
<summary>Advanced JSON</summary>
|
|
1075
|
+
<pre className="dm-orchestration-preview">{JSON.stringify(config, null, 2)}</pre>
|
|
1076
|
+
</details>
|
|
1077
|
+
</div>
|
|
1078
|
+
)}
|
|
1079
|
+
|
|
1080
|
+
{activeTab === "advanced" && (
|
|
1081
|
+
<div className="dm-orchestration-config__pane">
|
|
1082
|
+
{type === "api-registry-call" && (
|
|
1083
|
+
<>
|
|
1084
|
+
<label className="dm-orchestration-config__field">
|
|
1085
|
+
<span>Timeout (ms)</span>
|
|
1086
|
+
<input
|
|
1087
|
+
type="number"
|
|
1088
|
+
value={config.timeoutMs ?? 30000}
|
|
1089
|
+
disabled={disabled}
|
|
1090
|
+
onChange={(e) => patchConfig({ timeoutMs: Number(e.target.value) })}
|
|
1091
|
+
/>
|
|
1092
|
+
</label>
|
|
1093
|
+
<label className="dm-orchestration-config__field">
|
|
1094
|
+
<span>Query params (JSON object)</span>
|
|
1095
|
+
<textarea
|
|
1096
|
+
rows={2}
|
|
1097
|
+
disabled={disabled}
|
|
1098
|
+
value={JSON.stringify(config.queryParams || {}, null, 2)}
|
|
1099
|
+
onChange={(e) => {
|
|
1100
|
+
try {
|
|
1101
|
+
patchConfig({ queryParams: JSON.parse(e.target.value || "{}") });
|
|
1102
|
+
} catch {
|
|
1103
|
+
/* keep typing */
|
|
1104
|
+
}
|
|
1105
|
+
}}
|
|
1106
|
+
/>
|
|
1107
|
+
</label>
|
|
1108
|
+
</>
|
|
1109
|
+
)}
|
|
1110
|
+
{type === "input" && (
|
|
1111
|
+
<>
|
|
1112
|
+
<label className="dm-orchestration-config__field">
|
|
1113
|
+
<span>Source type</span>
|
|
1114
|
+
<input value={config.sourceType || ""} disabled={disabled} onChange={(e) => patchConfig({ sourceType: e.target.value })} />
|
|
1115
|
+
</label>
|
|
1116
|
+
<label className="dm-orchestration-config__field">
|
|
1117
|
+
<span>Source ID</span>
|
|
1118
|
+
<input value={config.sourceId || ""} disabled={disabled} onChange={(e) => patchConfig({ sourceId: e.target.value })} />
|
|
1119
|
+
</label>
|
|
1120
|
+
<label className="dm-orchestration-config__field">
|
|
1121
|
+
<span>Entity ID</span>
|
|
1122
|
+
<input value={config.entityId || ""} disabled={disabled} onChange={(e) => patchConfig({ entityId: e.target.value })} />
|
|
1123
|
+
</label>
|
|
1124
|
+
</>
|
|
1125
|
+
)}
|
|
1126
|
+
{type === "transform-filter" && (
|
|
1127
|
+
<>
|
|
1128
|
+
<label className="dm-orchestration-config__field">
|
|
1129
|
+
<span>Max rows (0 = no limit)</span>
|
|
1130
|
+
<input
|
|
1131
|
+
type="number"
|
|
1132
|
+
value={config.maxRows ?? 0}
|
|
1133
|
+
disabled={disabled}
|
|
1134
|
+
onChange={(e) => patchConfig({ maxRows: Number(e.target.value) })}
|
|
1135
|
+
/>
|
|
1136
|
+
</label>
|
|
1137
|
+
<label className="dm-orchestration-config__field">
|
|
1138
|
+
<span>Include fields (comma-separated)</span>
|
|
1139
|
+
<input
|
|
1140
|
+
value={Array.isArray(config.includeFields) ? config.includeFields.join(", ") : ""}
|
|
1141
|
+
disabled={disabled}
|
|
1142
|
+
onChange={(e) => patchConfig({
|
|
1143
|
+
includeFields: e.target.value.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1144
|
+
})}
|
|
1145
|
+
/>
|
|
1146
|
+
</label>
|
|
1147
|
+
<label className="dm-orchestration-config__field">
|
|
1148
|
+
<span>Exclude fields (comma-separated)</span>
|
|
1149
|
+
<input
|
|
1150
|
+
value={Array.isArray(config.excludeFields) ? config.excludeFields.join(", ") : ""}
|
|
1151
|
+
disabled={disabled}
|
|
1152
|
+
onChange={(e) => patchConfig({
|
|
1153
|
+
excludeFields: e.target.value.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1154
|
+
})}
|
|
1155
|
+
/>
|
|
1156
|
+
</label>
|
|
1157
|
+
</>
|
|
1158
|
+
)}
|
|
1159
|
+
{type === "tool-result" && (
|
|
1160
|
+
<>
|
|
1161
|
+
<label className="dm-orchestration-config__field">
|
|
1162
|
+
<span>Preview fields (comma-separated)</span>
|
|
1163
|
+
<input
|
|
1164
|
+
value={Array.isArray(config.previewFields) ? config.previewFields.join(", ") : ""}
|
|
1165
|
+
disabled={disabled}
|
|
1166
|
+
onChange={(e) => patchConfig({
|
|
1167
|
+
previewFields: e.target.value.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1168
|
+
})}
|
|
1169
|
+
/>
|
|
1170
|
+
</label>
|
|
1171
|
+
<label className="dm-orchestration-config__field">
|
|
1172
|
+
<span>Status field name</span>
|
|
1173
|
+
<input value={config.statusField || "status"} disabled={disabled} onChange={(e) => patchConfig({ statusField: e.target.value })} />
|
|
1174
|
+
</label>
|
|
1175
|
+
<label className="dm-orchestration-config__field">
|
|
1176
|
+
<span>Last tested field name</span>
|
|
1177
|
+
<input value={config.lastTestedField || "lastTested"} disabled={disabled} onChange={(e) => patchConfig({ lastTestedField: e.target.value })} />
|
|
1178
|
+
</label>
|
|
1179
|
+
</>
|
|
1180
|
+
)}
|
|
1181
|
+
{(type === "input" || type === "transform-filter") && (
|
|
1182
|
+
<details className="dm-orchestration-config__advanced-json">
|
|
1183
|
+
<summary>Advanced JSON</summary>
|
|
1184
|
+
<pre className="dm-orchestration-preview">{JSON.stringify(config, null, 2)}</pre>
|
|
1185
|
+
</details>
|
|
1186
|
+
)}
|
|
1187
|
+
</div>
|
|
1188
|
+
)}
|
|
1189
|
+
|
|
1190
|
+
{activeTab === "configuration" && type !== "thinAdapter" && (
|
|
1191
|
+
<VersionDeltaControls node={node} config={config} sandboxRow={sandboxRow} disabled={disabled} onChange={onConfigChange} />
|
|
1192
|
+
)}
|
|
1193
|
+
<div className="dm-workflow-node-config-foot">
|
|
1194
|
+
<button type="button" className="dm-workflow-node-options" disabled={disabled}>
|
|
1195
|
+
Options ⌘O
|
|
1196
|
+
</button>
|
|
1197
|
+
<button type="button" className="dm-workflow-node-delete" disabled={disabled} onClick={onDeleteNode}>
|
|
1198
|
+
Delete
|
|
1199
|
+
</button>
|
|
1200
|
+
</div>
|
|
1201
|
+
</div>
|
|
1202
|
+
);
|
|
1203
|
+
}
|