@scenario-labs/sdk 1.1.0 → 2.0.0
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/CHANGELOG.md +51 -0
- package/client.d.mts +6 -6
- package/client.d.mts.map +1 -1
- package/client.d.ts +6 -6
- package/client.d.ts.map +1 -1
- package/client.js +67 -43
- package/client.js.map +1 -1
- package/client.mjs +67 -44
- package/client.mjs.map +1 -1
- package/core/api-promise.js +7 -7
- package/core/api-promise.js.map +1 -1
- package/core/api-promise.mjs +7 -7
- package/core/api-promise.mjs.map +1 -1
- package/core/error.js +6 -0
- package/core/error.js.map +1 -1
- package/core/error.mjs +6 -0
- package/core/error.mjs.map +1 -1
- package/core/pagination.js +24 -6
- package/core/pagination.js.map +1 -1
- package/core/pagination.mjs +24 -6
- package/core/pagination.mjs.map +1 -1
- package/core/resource.js +1 -0
- package/core/resource.js.map +1 -1
- package/core/resource.mjs +1 -0
- package/core/resource.mjs.map +1 -1
- package/index.d.mts +1 -0
- package/index.d.mts.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +3 -1
- package/index.js.map +1 -1
- package/index.mjs +1 -0
- package/index.mjs.map +1 -1
- package/internal/detect-platform.js +1 -1
- package/internal/detect-platform.js.map +1 -1
- package/internal/detect-platform.mjs +1 -1
- package/internal/detect-platform.mjs.map +1 -1
- package/internal/qs/stringify.js +1 -1
- package/internal/qs/stringify.js.map +1 -1
- package/internal/qs/stringify.mjs +1 -1
- package/internal/qs/stringify.mjs.map +1 -1
- package/internal/request-options.d.mts +0 -4
- package/internal/request-options.d.mts.map +1 -1
- package/internal/request-options.d.ts +0 -4
- package/internal/request-options.d.ts.map +1 -1
- package/internal/request-options.js.map +1 -1
- package/internal/request-options.mjs.map +1 -1
- package/internal/to-file.js +2 -2
- package/internal/to-file.js.map +1 -1
- package/internal/to-file.mjs +2 -2
- package/internal/to-file.mjs.map +1 -1
- package/internal/tslib.js +0 -18
- package/internal/tslib.mjs +1 -17
- package/internal/types.d.mts +6 -6
- package/internal/types.d.mts.map +1 -1
- package/internal/types.d.ts +6 -6
- package/internal/types.d.ts.map +1 -1
- package/internal/utils/log.d.mts.map +1 -1
- package/internal/utils/log.d.ts.map +1 -1
- package/internal/utils/log.js +2 -0
- package/internal/utils/log.js.map +1 -1
- package/internal/utils/log.mjs +2 -0
- package/internal/utils/log.mjs.map +1 -1
- package/lib/job.js +3 -3
- package/lib/job.js.map +1 -1
- package/lib/job.mjs +3 -3
- package/lib/job.mjs.map +1 -1
- package/lib/models.js +1 -4
- package/lib/models.js.map +1 -1
- package/lib/models.mjs +1 -4
- package/lib/models.mjs.map +1 -1
- package/lib/scenario.js +6 -9
- package/lib/scenario.js.map +1 -1
- package/lib/scenario.mjs +6 -9
- package/lib/scenario.mjs.map +1 -1
- package/lib/upload.js +6 -6
- package/lib/upload.js.map +1 -1
- package/lib/upload.mjs +6 -6
- package/lib/upload.mjs.map +1 -1
- package/lib/workflow.d.mts +2 -2
- package/lib/workflow.d.mts.map +1 -1
- package/lib/workflow.d.ts +2 -2
- package/lib/workflow.d.ts.map +1 -1
- package/lib/workflow.js +2 -2
- package/lib/workflow.js.map +1 -1
- package/lib/workflow.mjs +2 -2
- package/lib/workflow.mjs.map +1 -1
- package/lib/workflow_converter.d.mts +174 -0
- package/lib/workflow_converter.d.mts.map +1 -0
- package/lib/workflow_converter.d.ts +174 -0
- package/lib/workflow_converter.d.ts.map +1 -0
- package/lib/workflow_converter.js +995 -0
- package/lib/workflow_converter.js.map +1 -0
- package/lib/workflow_converter.mjs +992 -0
- package/lib/workflow_converter.mjs.map +1 -0
- package/package.json +1 -1
- package/resources/assets/assets.d.mts +13 -5
- package/resources/assets/assets.d.mts.map +1 -1
- package/resources/assets/assets.d.ts +13 -5
- package/resources/assets/assets.d.ts.map +1 -1
- package/resources/assets/assets.js +1 -8
- package/resources/assets/assets.js.map +1 -1
- package/resources/assets/assets.mjs +1 -8
- package/resources/assets/assets.mjs.map +1 -1
- package/resources/assets/index.d.mts +0 -1
- package/resources/assets/index.d.mts.map +1 -1
- package/resources/assets/index.d.ts +0 -1
- package/resources/assets/index.d.ts.map +1 -1
- package/resources/assets/index.js +1 -3
- package/resources/assets/index.js.map +1 -1
- package/resources/assets/index.mjs +0 -1
- package/resources/assets/index.mjs.map +1 -1
- package/resources/collections/collections.js +2 -5
- package/resources/collections/collections.js.map +1 -1
- package/resources/collections/collections.mjs +2 -5
- package/resources/collections/collections.mjs.map +1 -1
- package/resources/generate.d.mts +96 -0
- package/resources/generate.d.mts.map +1 -1
- package/resources/generate.d.ts +96 -0
- package/resources/generate.d.ts.map +1 -1
- package/resources/index.d.mts +2 -1
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +2 -1
- package/resources/index.d.ts.map +1 -1
- package/resources/index.js +3 -1
- package/resources/index.js.map +1 -1
- package/resources/index.mjs +1 -0
- package/resources/index.mjs.map +1 -1
- package/resources/jobs.d.mts +46 -3
- package/resources/jobs.d.mts.map +1 -1
- package/resources/jobs.d.ts +46 -3
- package/resources/jobs.d.ts.map +1 -1
- package/resources/models/examples.d.mts +24 -0
- package/resources/models/examples.d.mts.map +1 -1
- package/resources/models/examples.d.ts +24 -0
- package/resources/models/examples.d.ts.map +1 -1
- package/resources/models/models.d.mts +55 -11
- package/resources/models/models.d.mts.map +1 -1
- package/resources/models/models.d.ts +55 -11
- package/resources/models/models.d.ts.map +1 -1
- package/resources/models/models.js +4 -7
- package/resources/models/models.js.map +1 -1
- package/resources/models/models.mjs +4 -7
- package/resources/models/models.mjs.map +1 -1
- package/resources/models/train.d.mts +24 -0
- package/resources/models/train.d.mts.map +1 -1
- package/resources/models/train.d.ts +24 -0
- package/resources/models/train.d.ts.map +1 -1
- package/resources/oscu.d.mts +5 -0
- package/resources/oscu.d.mts.map +1 -1
- package/resources/oscu.d.ts +5 -0
- package/resources/oscu.d.ts.map +1 -1
- package/resources/oscu.js +5 -0
- package/resources/oscu.js.map +1 -1
- package/resources/oscu.mjs +5 -0
- package/resources/oscu.mjs.map +1 -1
- package/resources/pricing/index.d.mts +3 -0
- package/resources/pricing/index.d.mts.map +1 -0
- package/resources/pricing/index.d.ts +3 -0
- package/resources/pricing/index.d.ts.map +1 -0
- package/resources/pricing/index.js +9 -0
- package/resources/pricing/index.js.map +1 -0
- package/resources/pricing/index.mjs +4 -0
- package/resources/pricing/index.mjs.map +1 -0
- package/resources/pricing/oscu.d.mts +25 -0
- package/resources/pricing/oscu.d.mts.map +1 -0
- package/resources/pricing/oscu.d.ts +25 -0
- package/resources/pricing/oscu.d.ts.map +1 -0
- package/resources/pricing/oscu.js +16 -0
- package/resources/pricing/oscu.js.map +1 -0
- package/resources/pricing/oscu.mjs +12 -0
- package/resources/pricing/oscu.mjs.map +1 -0
- package/resources/pricing/pricing.d.mts +10 -0
- package/resources/pricing/pricing.d.mts.map +1 -0
- package/resources/pricing/pricing.d.ts +10 -0
- package/resources/pricing/pricing.d.ts.map +1 -0
- package/resources/pricing/pricing.js +14 -0
- package/resources/pricing/pricing.js.map +1 -0
- package/resources/pricing/pricing.mjs +9 -0
- package/resources/pricing/pricing.mjs.map +1 -0
- package/resources/pricing.d.mts +2 -0
- package/resources/pricing.d.mts.map +1 -0
- package/resources/pricing.d.ts +2 -0
- package/resources/pricing.d.ts.map +1 -0
- package/resources/pricing.js +6 -0
- package/resources/pricing.js.map +1 -0
- package/resources/pricing.mjs +3 -0
- package/resources/pricing.mjs.map +1 -0
- package/resources/usages.d.mts +29 -4
- package/resources/usages.d.mts.map +1 -1
- package/resources/usages.d.ts +29 -4
- package/resources/usages.d.ts.map +1 -1
- package/resources/workflows.d.mts +1298 -1152
- package/resources/workflows.d.mts.map +1 -1
- package/resources/workflows.d.ts +1298 -1152
- package/resources/workflows.d.ts.map +1 -1
- package/resources/workflows.js +8 -4
- package/resources/workflows.js.map +1 -1
- package/resources/workflows.mjs +8 -4
- package/resources/workflows.mjs.map +1 -1
- package/src/client.ts +24 -11
- package/src/index.ts +14 -0
- package/src/internal/request-options.ts +0 -2
- package/src/internal/types.ts +6 -8
- package/src/internal/utils/log.ts +2 -0
- package/src/lib/workflow.ts +4 -4
- package/src/lib/workflow_converter.ts +1250 -0
- package/src/resources/assets/assets.ts +17 -13
- package/src/resources/assets/index.ts +0 -6
- package/src/resources/generate.ts +112 -0
- package/src/resources/index.ts +3 -1
- package/src/resources/jobs.ts +54 -4
- package/src/resources/models/examples.ts +28 -0
- package/src/resources/models/models.ts +100 -11
- package/src/resources/models/train.ts +28 -0
- package/src/resources/oscu.ts +5 -0
- package/src/resources/pricing/index.ts +4 -0
- package/src/resources/pricing/oscu.ts +35 -0
- package/src/resources/pricing/pricing.ts +15 -0
- package/src/resources/pricing.ts +3 -0
- package/src/resources/usages.ts +33 -3
- package/src/resources/workflows.ts +1691 -1519
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
- package/resources/assets/public.d.mts +0 -1538
- package/resources/assets/public.d.mts.map +0 -1
- package/resources/assets/public.d.ts +0 -1538
- package/resources/assets/public.d.ts.map +0 -1
- package/resources/assets/public.js +0 -28
- package/resources/assets/public.js.map +0 -1
- package/resources/assets/public.mjs +0 -24
- package/resources/assets/public.mjs.map +0 -1
- package/src/resources/assets/public.ts +0 -2461
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Converts a workflow editor state (nodes + edges) to a flow array suitable for the Scenario API.
|
|
3
|
+
// Ported from fawkes useConvertWorkflowToApp — pure function, no React dependencies.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.convertWorkflowEditorToFlow = convertWorkflowEditorToFlow;
|
|
6
|
+
// ---- Utility functions ----
|
|
7
|
+
function nodeIdToFlowId(nodeId) {
|
|
8
|
+
return nodeId.replace(/\s/g, '');
|
|
9
|
+
}
|
|
10
|
+
function escapeCELString(value) {
|
|
11
|
+
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n');
|
|
12
|
+
}
|
|
13
|
+
function formatTextAsCELString(text) {
|
|
14
|
+
return `'${text.replace(/'/g, "\\'").replace(/\n/g, '\\n')}'`;
|
|
15
|
+
}
|
|
16
|
+
function buildConcatArrayTransform(variables) {
|
|
17
|
+
if (variables.length === 0)
|
|
18
|
+
return '[]';
|
|
19
|
+
return variables
|
|
20
|
+
.map((v) => {
|
|
21
|
+
const expr = v.isArray ? v.name : `[${v.name}]`;
|
|
22
|
+
return v.isRequired ? expr : `(exists('${v.name}') ? ${expr} : [])`;
|
|
23
|
+
})
|
|
24
|
+
.join(' + ');
|
|
25
|
+
}
|
|
26
|
+
function convertValueToList(value) {
|
|
27
|
+
return value
|
|
28
|
+
.replace(/\[|\]/g, '')
|
|
29
|
+
.split('+')
|
|
30
|
+
.map((v) => v.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
function getConditionalInputHandleId(nodeId) {
|
|
34
|
+
return `${nodeId}-source-conditional`;
|
|
35
|
+
}
|
|
36
|
+
function getForEachFlowListInputDescriptor(args) {
|
|
37
|
+
const { listProvider, outputHandle, inputIndex } = args;
|
|
38
|
+
const isText = listProvider?.type === 'splitText' ||
|
|
39
|
+
listProvider?.type === 'text' ||
|
|
40
|
+
listProvider?.type === 'llm' ||
|
|
41
|
+
listProvider?.type === 'transformText' ||
|
|
42
|
+
(outputHandle?.type === 'text' && !!outputHandle.isArray);
|
|
43
|
+
return isText ?
|
|
44
|
+
{ name: `text${inputIndex}`, type: 'string_array' }
|
|
45
|
+
: { name: `image${inputIndex}`, type: 'file_array' };
|
|
46
|
+
}
|
|
47
|
+
function getForEachIterationRefName(outputHandle, outputIndex) {
|
|
48
|
+
return outputHandle?.type === 'text' ? `text${outputIndex}` : `image${outputIndex}`;
|
|
49
|
+
}
|
|
50
|
+
function getHasComposeLayerSourceInput(layerInputs) {
|
|
51
|
+
return layerInputs.some((inp) => inp.name === 'source');
|
|
52
|
+
}
|
|
53
|
+
function conditionToCelExpression(condition) {
|
|
54
|
+
if (!condition.field)
|
|
55
|
+
return 'false';
|
|
56
|
+
const { field, operator, value } = condition;
|
|
57
|
+
function escStr(s) {
|
|
58
|
+
return `'${s.replace(/'/g, "''")}'`;
|
|
59
|
+
}
|
|
60
|
+
function escRx(s) {
|
|
61
|
+
return s.replace(/[.+*?[\]()^$|\\]/g, '\\$&');
|
|
62
|
+
}
|
|
63
|
+
function fmtVal(v) {
|
|
64
|
+
if (v === undefined)
|
|
65
|
+
return "''";
|
|
66
|
+
if (Array.isArray(v))
|
|
67
|
+
return `[${v.map((x) => escStr(String(x))).join(', ')}]`;
|
|
68
|
+
const n = Number(v);
|
|
69
|
+
if (!isNaN(n) && v !== '' && String(n) === String(v).trim())
|
|
70
|
+
return String(n);
|
|
71
|
+
return escStr(String(v));
|
|
72
|
+
}
|
|
73
|
+
switch (operator) {
|
|
74
|
+
case 'isEmpty':
|
|
75
|
+
return `${field} == null || size(${field}) == 0 || (type(${field}) != list && trim(${field}) == "")`;
|
|
76
|
+
case 'isNotEmpty':
|
|
77
|
+
return `${field} != null && size(${field}) > 0 && (type(${field}) == list || trim(${field}) != "")`;
|
|
78
|
+
case 'equals':
|
|
79
|
+
return `trim(${field}) == ${fmtVal(value)}`;
|
|
80
|
+
case 'notEquals':
|
|
81
|
+
return `trim(${field}) != ${fmtVal(value)}`;
|
|
82
|
+
case 'contains':
|
|
83
|
+
return `${field}.matches('.*${escRx(String(value || '')).replace(/'/g, "''")}.*')`;
|
|
84
|
+
case 'notContains':
|
|
85
|
+
return `!${field}.matches('.*${escRx(String(value || '')).replace(/'/g, "''")}.*')`;
|
|
86
|
+
case 'greaterThan':
|
|
87
|
+
return `${field} > ${fmtVal(value)}`;
|
|
88
|
+
case 'greaterThanOrEqual':
|
|
89
|
+
return `${field} >= ${fmtVal(value)}`;
|
|
90
|
+
case 'lessThan':
|
|
91
|
+
return `${field} < ${fmtVal(value)}`;
|
|
92
|
+
case 'lessThanOrEqual':
|
|
93
|
+
return `${field} <= ${fmtVal(value)}`;
|
|
94
|
+
case 'between':
|
|
95
|
+
if (Array.isArray(value) && value.length === 2) {
|
|
96
|
+
const n1 = Number(value[0]);
|
|
97
|
+
const n2 = Number(value[1]);
|
|
98
|
+
if (!isNaN(n1) && !isNaN(n2))
|
|
99
|
+
return `${field} >= ${n1} && ${field} <= ${n2}`;
|
|
100
|
+
}
|
|
101
|
+
return 'false';
|
|
102
|
+
default:
|
|
103
|
+
return 'false';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function conditionBlockToCEL(block, nodeIdToInputName) {
|
|
107
|
+
const exprs = block.conditions
|
|
108
|
+
.map((c) => {
|
|
109
|
+
const mappedField = c.field ? nodeIdToInputName.get(c.field) ?? c.field : undefined;
|
|
110
|
+
return conditionToCelExpression({ ...c, field: mappedField });
|
|
111
|
+
})
|
|
112
|
+
.filter((e) => e !== 'false');
|
|
113
|
+
if (exprs.length === 0)
|
|
114
|
+
return '';
|
|
115
|
+
if (exprs.length === 1)
|
|
116
|
+
return exprs[0];
|
|
117
|
+
return exprs.join(block.logic === 'or' ? ' || ' : ' && ');
|
|
118
|
+
}
|
|
119
|
+
function getSourceRef(edge, ctx) {
|
|
120
|
+
const { nodes, inputNodeIds, nodeIdToInputNameMap, nodeIdToFlowIdMap, nodeHandleTransformMap } = ctx;
|
|
121
|
+
const targetNodeId = edge.target;
|
|
122
|
+
const targetNode = nodes.find((n) => n.id === targetNodeId);
|
|
123
|
+
if (!targetNode)
|
|
124
|
+
return undefined;
|
|
125
|
+
if (inputNodeIds.includes(targetNodeId) && nodeHandleTransformMap) {
|
|
126
|
+
const handle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
127
|
+
const handleName = handle?.name ?? 'output';
|
|
128
|
+
if (handleName !== 'output') {
|
|
129
|
+
const transformId = nodeHandleTransformMap.get(`${targetNodeId}_${handleName}`);
|
|
130
|
+
if (transformId)
|
|
131
|
+
return { node: transformId, name: 'all' };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (inputNodeIds.includes(targetNodeId)) {
|
|
135
|
+
const inputName = nodeIdToInputNameMap.get(targetNodeId);
|
|
136
|
+
if (inputName)
|
|
137
|
+
return { node: 'workflow', name: inputName };
|
|
138
|
+
}
|
|
139
|
+
if (targetNode.type === 'forEach' && edge.targetHandle) {
|
|
140
|
+
const m = edge.targetHandle.match(/-output-(\d+)$/);
|
|
141
|
+
if (m) {
|
|
142
|
+
const targetFlowId = nodeIdToFlowIdMap.get(targetNodeId);
|
|
143
|
+
if (targetFlowId) {
|
|
144
|
+
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
145
|
+
return { node: targetFlowId, name: getForEachIterationRefName(outHandle, m[1]) };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (targetNode.type === 'forEachEnd') {
|
|
150
|
+
const parentId = targetNode.data.parentNodeId;
|
|
151
|
+
if (parentId) {
|
|
152
|
+
const forEachFlowId = nodeIdToFlowIdMap.get(parentId);
|
|
153
|
+
if (forEachFlowId)
|
|
154
|
+
return { node: forEachFlowId };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (targetNode.type === 'ifElse') {
|
|
158
|
+
const handles = targetNode.data.outputHandles ?? [];
|
|
159
|
+
const handleIndex = handles.findIndex((h) => h.id === edge.targetHandle);
|
|
160
|
+
if (handleIndex !== -1) {
|
|
161
|
+
const targetFlowId = nodeIdToFlowIdMap.get(targetNodeId);
|
|
162
|
+
const blocks = targetNode.data.conditionBlocks ?? [];
|
|
163
|
+
const isElse = handleIndex >= blocks.length;
|
|
164
|
+
return { node: targetFlowId, equal: isElse ? '1' : String(handleIndex + 2) };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if ((targetNode.type === 'model' || targetNode.type === 'asset') &&
|
|
168
|
+
edge.targetHandle &&
|
|
169
|
+
nodeHandleTransformMap) {
|
|
170
|
+
const handle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
171
|
+
const handleName = handle?.name ?? 'output';
|
|
172
|
+
if (handleName !== 'output') {
|
|
173
|
+
const transformId = nodeHandleTransformMap.get(`${targetNodeId}_${handleName}`);
|
|
174
|
+
if (transformId)
|
|
175
|
+
return { node: transformId, name: 'all' };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (targetNode.type === 'llm' && edge.targetHandle && nodeHandleTransformMap) {
|
|
179
|
+
const handle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
180
|
+
if (handle?.name === 'outputs') {
|
|
181
|
+
const transformId = nodeHandleTransformMap.get(`${targetNodeId}_outputs`);
|
|
182
|
+
if (transformId)
|
|
183
|
+
return { node: transformId, name: 'all' };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const targetFlowId = nodeIdToFlowIdMap.get(targetNodeId);
|
|
187
|
+
if (targetFlowId)
|
|
188
|
+
return { node: targetFlowId, name: 'all' };
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
function getInputHandle(edge, inputHandles) {
|
|
192
|
+
return inputHandles?.flatMap((h) => {
|
|
193
|
+
if (h.id === edge.sourceHandle)
|
|
194
|
+
return [h];
|
|
195
|
+
for (const sub of h.subHandles ?? []) {
|
|
196
|
+
if (sub.id === edge.sourceHandle)
|
|
197
|
+
return [sub];
|
|
198
|
+
}
|
|
199
|
+
return [];
|
|
200
|
+
})[0];
|
|
201
|
+
}
|
|
202
|
+
function hasHandleConnection(node, handleName, edges, nodeId) {
|
|
203
|
+
return edges
|
|
204
|
+
.filter((e) => e.target === nodeId)
|
|
205
|
+
.some((e) => (node.data.outputHandles?.find((h) => h.id === e.targetHandle)?.name ?? 'output') === handleName);
|
|
206
|
+
}
|
|
207
|
+
function getOutputHandleVarName(targetNodeId, handleName) {
|
|
208
|
+
return ['output', 'allOutputs'].includes(handleName) ? targetNodeId : `${targetNodeId}_${handleName}`;
|
|
209
|
+
}
|
|
210
|
+
function setOrPushFlowInput(flowInputs, existingIndex, input) {
|
|
211
|
+
if (existingIndex !== -1) {
|
|
212
|
+
flowInputs[existingIndex] = input;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
flowInputs.push(input);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function addVideoFirstLastFrameTransforms(nodeId, flowId, node, edges, flow, nodeHandleTransformMap, options) {
|
|
219
|
+
const handles = options?.onlyHandle ? [options.onlyHandle] : ['firstFrame', 'lastFrame'];
|
|
220
|
+
for (const handleName of handles) {
|
|
221
|
+
if (!hasHandleConnection(node, handleName, edges, nodeId))
|
|
222
|
+
continue;
|
|
223
|
+
const transformId = options?.inputName ? flowId : `${flowId}_${handleName}`;
|
|
224
|
+
const ref = options?.inputName ?
|
|
225
|
+
{ node: 'workflow', name: options.inputName }
|
|
226
|
+
: { node: flowId, name: 'all' };
|
|
227
|
+
flow.push({
|
|
228
|
+
id: transformId,
|
|
229
|
+
type: 'transform',
|
|
230
|
+
logic: { transform: `exists('videoOutput') ? assetJson(videoOutput).${handleName}.assetId : []` },
|
|
231
|
+
inputs: [{ name: 'videoOutput', type: 'file', ref }],
|
|
232
|
+
});
|
|
233
|
+
nodeHandleTransformMap.set(`${nodeId}_${handleName}`, transformId);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function collectConditionalConnectionInputs(incomingEdges, nodeId, ctx) {
|
|
237
|
+
const conditionalHandleId = getConditionalInputHandleId(nodeId);
|
|
238
|
+
return incomingEdges
|
|
239
|
+
.filter((e) => e.sourceHandle === conditionalHandleId)
|
|
240
|
+
.flatMap((edge) => {
|
|
241
|
+
const targetNode = ctx.nodes.find((n) => n.id === edge.target);
|
|
242
|
+
if (targetNode?.type !== 'ifElse')
|
|
243
|
+
return [];
|
|
244
|
+
const ref = getSourceRef(edge, ctx);
|
|
245
|
+
if (!ref)
|
|
246
|
+
return [];
|
|
247
|
+
return [{ name: 'connection', type: 'connection', ref }];
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function toStaticInput(name, type, value) {
|
|
251
|
+
if (value === undefined || value === '')
|
|
252
|
+
return undefined;
|
|
253
|
+
const isNum = typeof value === 'number';
|
|
254
|
+
const isStr = typeof value === 'string';
|
|
255
|
+
const isBool = typeof value === 'boolean';
|
|
256
|
+
const isStrArr = Array.isArray(value) && value.every((v) => typeof v === 'string');
|
|
257
|
+
if (!isNum && !isStr && !isBool && !isStrArr)
|
|
258
|
+
return undefined;
|
|
259
|
+
if (isStrArr && value.length === 0)
|
|
260
|
+
return undefined;
|
|
261
|
+
return { name, type, value: value };
|
|
262
|
+
}
|
|
263
|
+
function isFormArray(value) {
|
|
264
|
+
return (Array.isArray(value) &&
|
|
265
|
+
value.every((item) => typeof item === 'object' && item !== null && !Array.isArray(item)));
|
|
266
|
+
}
|
|
267
|
+
function isModelCompose(model) {
|
|
268
|
+
return !!model?.id && (model.tags ?? []).includes('compose');
|
|
269
|
+
}
|
|
270
|
+
function getIsConditionallyExecuted(targetNodeId, nodes, edges) {
|
|
271
|
+
const conditionalHandleId = getConditionalInputHandleId(targetNodeId);
|
|
272
|
+
return edges.some((e) => {
|
|
273
|
+
const sourceNode = nodes.find((n) => n.id === e.target);
|
|
274
|
+
return (e.source === targetNodeId && e.sourceHandle === conditionalHandleId && sourceNode?.type === 'ifElse');
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
function getForEachBodyEditorNodeIds(forEachId, forEachEndId, edges) {
|
|
278
|
+
const bodyNodeIds = new Set();
|
|
279
|
+
const queue = [];
|
|
280
|
+
const escaped = forEachId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
281
|
+
for (const edge of edges) {
|
|
282
|
+
if (edge.target === forEachId && edge.targetHandle?.match(new RegExp(`^${escaped}-output-\\d+$`))) {
|
|
283
|
+
queue.push(edge.source);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
while (queue.length > 0) {
|
|
287
|
+
const nodeId = queue.pop();
|
|
288
|
+
if (nodeId === forEachEndId || bodyNodeIds.has(nodeId))
|
|
289
|
+
continue;
|
|
290
|
+
bodyNodeIds.add(nodeId);
|
|
291
|
+
for (const edge of edges) {
|
|
292
|
+
if (edge.target === nodeId && !bodyNodeIds.has(edge.source) && edge.source !== forEachEndId) {
|
|
293
|
+
queue.push(edge.source);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return bodyNodeIds;
|
|
298
|
+
}
|
|
299
|
+
function sortNodesByInputKeys(nodes, inputKeys) {
|
|
300
|
+
return [...nodes].sort((a, b) => {
|
|
301
|
+
const ai = inputKeys.indexOf(a.id);
|
|
302
|
+
const bi = inputKeys.indexOf(b.id);
|
|
303
|
+
if (ai === -1 && bi === -1)
|
|
304
|
+
return 0;
|
|
305
|
+
if (ai === -1)
|
|
306
|
+
return 1;
|
|
307
|
+
if (bi === -1)
|
|
308
|
+
return -1;
|
|
309
|
+
return ai - bi;
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
function getNodeOutputValue(node, targetHandleId) {
|
|
313
|
+
if (node.type === 'text')
|
|
314
|
+
return node.data.value || '';
|
|
315
|
+
if (node.type === 'asset') {
|
|
316
|
+
const d = node.data;
|
|
317
|
+
if (targetHandleId) {
|
|
318
|
+
const handleName = d.outputHandles?.find((h) => h.id === targetHandleId)?.name ?? 'output';
|
|
319
|
+
if (handleName === 'firstFrame')
|
|
320
|
+
return d.valueFirstFrame;
|
|
321
|
+
if (handleName === 'lastFrame')
|
|
322
|
+
return d.valueLastFrame;
|
|
323
|
+
}
|
|
324
|
+
return (Array.isArray(d.value) ? d.value : d.value) || undefined;
|
|
325
|
+
}
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
|
|
329
|
+
function internalGetConnectedNodes(nodes, edges, startNodeId, direction, isStoppingAtRunnableNodes) {
|
|
330
|
+
const nodesById = new Map(nodes.map((n) => {
|
|
331
|
+
const meta = { id: n.id, type: n.type };
|
|
332
|
+
if (n.type === 'forEachEnd') {
|
|
333
|
+
const parentId = n.data.parentNodeId;
|
|
334
|
+
if (parentId !== undefined) {
|
|
335
|
+
meta.parentNodeId = parentId;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return [n.id, meta];
|
|
339
|
+
}));
|
|
340
|
+
const edgesSimple = edges.map((e) => ({ source: e.source, target: e.target }));
|
|
341
|
+
function isInternalForEachEdge(e) {
|
|
342
|
+
const src = nodesById.get(e.source);
|
|
343
|
+
const tgt = nodesById.get(e.target);
|
|
344
|
+
return src?.type === 'forEach' && tgt?.type === 'forEachEnd' && tgt.parentNodeId === src.id;
|
|
345
|
+
}
|
|
346
|
+
function findIds(nodeId, visited) {
|
|
347
|
+
if (visited.has(nodeId))
|
|
348
|
+
return [];
|
|
349
|
+
visited.add(nodeId);
|
|
350
|
+
const result = [];
|
|
351
|
+
const relevant = edgesSimple.filter((e) => (direction === 'output' ? e.target === nodeId : e.source === nodeId) && !isInternalForEachEdge(e));
|
|
352
|
+
for (const edge of relevant) {
|
|
353
|
+
const connectedId = direction === 'output' ? edge.source : edge.target;
|
|
354
|
+
const connectedNode = nodesById.get(connectedId);
|
|
355
|
+
if (!connectedNode)
|
|
356
|
+
continue;
|
|
357
|
+
result.push(connectedId);
|
|
358
|
+
if (isStoppingAtRunnableNodes && RUNNABLE_TYPES.has(connectedNode.type))
|
|
359
|
+
continue;
|
|
360
|
+
result.push(...findIds(connectedId, visited));
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
const ids = findIds(startNodeId, new Set());
|
|
365
|
+
const idSet = new Set(ids);
|
|
366
|
+
return nodes.filter((n) => idSet.has(n.id));
|
|
367
|
+
}
|
|
368
|
+
function normalizeNodes(nodes) {
|
|
369
|
+
return nodes.map((node) => {
|
|
370
|
+
if (node.type !== 'model' && node.type !== 'llm')
|
|
371
|
+
return node;
|
|
372
|
+
const hasType = (t, v) => Array.isArray(t) ? t.includes(v) : t === v;
|
|
373
|
+
const inputHandles = (node.data.inputHandles ?? [])
|
|
374
|
+
.flatMap((h) => (h.subHandles ? h.subHandles : [h]))
|
|
375
|
+
.filter((h) => hasType(h.type, 'conditional') ||
|
|
376
|
+
hasType(h.type, 'prompt') ||
|
|
377
|
+
hasType(h.type, 'image') ||
|
|
378
|
+
hasType(h.type, 'video') ||
|
|
379
|
+
hasType(h.type, '3d') ||
|
|
380
|
+
hasType(h.type, 'audio') ||
|
|
381
|
+
(node.type === 'llm' && h.name === 'textInputs'));
|
|
382
|
+
return { ...node, data: { ...node.data, inputHandles } };
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
// ---- Main function ----
|
|
386
|
+
/**
|
|
387
|
+
* Converts a workflow editor state (nodes, edges, inputKeys) into a flow array
|
|
388
|
+
* ready to be passed to `client.workflows.update({ flow: ... })`.
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```ts
|
|
392
|
+
* const flow = convertWorkflowEditorToFlow({ nodes, edges, inputKeys, getModel });
|
|
393
|
+
* await client.workflows.update(workflowId, { flow: flow as WorkflowUpdateParams.Flow[] });
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
function convertWorkflowEditorToFlow(params) {
|
|
397
|
+
const { edges, inputKeys } = params;
|
|
398
|
+
const nodes = normalizeNodes(params.nodes);
|
|
399
|
+
const inputNodeIds = nodes.filter((n) => !!n.data.isInput).map((n) => n.id);
|
|
400
|
+
const selectedOutputNodes = nodes.filter((n) => (n.type === 'model' || n.type === 'forEachEnd') && !!n.data.isOutput);
|
|
401
|
+
const selectedOutputNodeIds = selectedOutputNodes.map((n) => n.id);
|
|
402
|
+
const selectedOutputModelNodeIds = new Set(selectedOutputNodes
|
|
403
|
+
.filter((n) => n.type === 'model')
|
|
404
|
+
.map((n) => n.id));
|
|
405
|
+
// Collect all nodes in branches that lead to selected outputs
|
|
406
|
+
const nodesInSelectedBranches = new Set();
|
|
407
|
+
for (const outputNode of selectedOutputNodes) {
|
|
408
|
+
const branchNodes = internalGetConnectedNodes(nodes, edges, outputNode.id, 'input', false);
|
|
409
|
+
for (const n of branchNodes) {
|
|
410
|
+
nodesInSelectedBranches.add(n.id);
|
|
411
|
+
if (outputNode.type === 'forEachEnd' && n.type === 'model' && !!n.data.isOutput) {
|
|
412
|
+
selectedOutputModelNodeIds.add(n.id);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Models fed from a forEachEnd "results" handle also inherit output inclusion
|
|
417
|
+
for (const outputNode of selectedOutputNodes) {
|
|
418
|
+
if (outputNode.type !== 'forEachEnd')
|
|
419
|
+
continue;
|
|
420
|
+
const inputHandleIds = new Set((outputNode.data.inputHandles ?? []).map((h) => h.id));
|
|
421
|
+
for (const edge of edges) {
|
|
422
|
+
if (!edge.sourceHandle || edge.source !== outputNode.id || !inputHandleIds.has(edge.sourceHandle))
|
|
423
|
+
continue;
|
|
424
|
+
const tgt = nodes.find((n) => n.id === edge.target);
|
|
425
|
+
if (tgt?.type === 'model')
|
|
426
|
+
selectedOutputModelNodeIds.add(tgt.id);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const filteredNodes = nodes.filter((n) => nodesInSelectedBranches.has(n.id) ||
|
|
430
|
+
inputNodeIds.includes(n.id) ||
|
|
431
|
+
selectedOutputNodeIds.includes(n.id));
|
|
432
|
+
const sortedNodes = sortNodesByInputKeys(filteredNodes, inputKeys);
|
|
433
|
+
const nodeIdToFlowIdMap = new Map(sortedNodes
|
|
434
|
+
.filter((n) => !inputNodeIds.includes(n.id) && n.type !== 'forEachEnd')
|
|
435
|
+
.map((n) => [n.id, nodeIdToFlowId(n.id)]));
|
|
436
|
+
const nodeIdToInputNameMap = new Map(inputNodeIds.map((id) => [id, nodeIdToFlowId(id)]));
|
|
437
|
+
const nodeHandleTransformMap = new Map();
|
|
438
|
+
const ctx = {
|
|
439
|
+
nodes,
|
|
440
|
+
inputNodeIds,
|
|
441
|
+
nodeIdToInputNameMap,
|
|
442
|
+
nodeIdToFlowIdMap,
|
|
443
|
+
nodeHandleTransformMap,
|
|
444
|
+
};
|
|
445
|
+
const flow = [];
|
|
446
|
+
const forEachBodyEditorNodeIds = new Map();
|
|
447
|
+
const forEachLoopFlowIds = new Map();
|
|
448
|
+
const forEachFlowItemRefs = new Map();
|
|
449
|
+
for (const node of sortedNodes) {
|
|
450
|
+
if (node.type !== 'forEach')
|
|
451
|
+
continue;
|
|
452
|
+
const endNode = nodes.find((n) => n.type === 'forEachEnd' && n.data.parentNodeId === node.id);
|
|
453
|
+
if (!endNode)
|
|
454
|
+
continue;
|
|
455
|
+
forEachBodyEditorNodeIds.set(node.id, getForEachBodyEditorNodeIds(node.id, endNode.id, edges));
|
|
456
|
+
forEachLoopFlowIds.set(node.id, []);
|
|
457
|
+
}
|
|
458
|
+
// Pre-populate video firstFrame/lastFrame transform IDs for input nodes
|
|
459
|
+
const addedInputVideoTransforms = new Set();
|
|
460
|
+
for (const inputNodeId of inputNodeIds) {
|
|
461
|
+
const inputNode = nodes.find((n) => n.id === inputNodeId);
|
|
462
|
+
if (!inputNode || inputNode.type !== 'asset' || inputNode.data.type !== 'video')
|
|
463
|
+
continue;
|
|
464
|
+
for (const handleName of ['firstFrame', 'lastFrame']) {
|
|
465
|
+
if (hasHandleConnection(inputNode, handleName, edges, inputNodeId)) {
|
|
466
|
+
nodeHandleTransformMap.set(`${inputNodeId}_${handleName}`, `${inputNodeId}_${handleName}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function ensureInputVideoTransformsAdded() {
|
|
471
|
+
for (const inputNodeId of inputNodeIds) {
|
|
472
|
+
const inputNode = nodes.find((n) => n.id === inputNodeId);
|
|
473
|
+
if (!inputNode || inputNode.type !== 'asset' || inputNode.data.type !== 'video')
|
|
474
|
+
continue;
|
|
475
|
+
const inputName = nodeIdToInputNameMap.get(inputNodeId);
|
|
476
|
+
if (!inputName)
|
|
477
|
+
continue;
|
|
478
|
+
for (const handleName of ['firstFrame', 'lastFrame']) {
|
|
479
|
+
const key = `${inputNodeId}_${handleName}`;
|
|
480
|
+
if (addedInputVideoTransforms.has(key))
|
|
481
|
+
continue;
|
|
482
|
+
if (!hasHandleConnection(inputNode, handleName, edges, inputNodeId))
|
|
483
|
+
continue;
|
|
484
|
+
addVideoFirstLastFrameTransforms(inputNodeId, key, inputNode, edges, flow, nodeHandleTransformMap, {
|
|
485
|
+
inputName,
|
|
486
|
+
onlyHandle: handleName,
|
|
487
|
+
});
|
|
488
|
+
addedInputVideoTransforms.add(key);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
for (const node of sortedNodes) {
|
|
493
|
+
if (inputNodeIds.includes(node.id))
|
|
494
|
+
continue;
|
|
495
|
+
const flowId = nodeIdToFlowIdMap.get(node.id);
|
|
496
|
+
if (!flowId)
|
|
497
|
+
continue;
|
|
498
|
+
const flowLengthBefore = flow.length;
|
|
499
|
+
const incomingEdges = edges.filter((e) => e.source === node.id);
|
|
500
|
+
// ------------------------------------
|
|
501
|
+
// Model / LLM -> custom-model
|
|
502
|
+
// ------------------------------------
|
|
503
|
+
if (node.type === 'model' || node.type === 'llm') {
|
|
504
|
+
const nodeData = node.data;
|
|
505
|
+
const flowInputs = [];
|
|
506
|
+
const remoteModel = nodeData.modelId ? params.getModel?.(nodeData.modelId) : undefined;
|
|
507
|
+
const model = remoteModel === 'unknown' ? undefined : remoteModel;
|
|
508
|
+
const modelInputs = model?.inputs;
|
|
509
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
510
|
+
const edgesByInput = new Map();
|
|
511
|
+
for (const edge of incomingEdges) {
|
|
512
|
+
if (edge.sourceHandle === conditionalHandleId)
|
|
513
|
+
continue;
|
|
514
|
+
const handle = getInputHandle(edge, node.data.inputHandles);
|
|
515
|
+
if (!handle?.name)
|
|
516
|
+
continue;
|
|
517
|
+
const existing = edgesByInput.get(handle.name);
|
|
518
|
+
if (existing)
|
|
519
|
+
existing.push(edge);
|
|
520
|
+
else
|
|
521
|
+
edgesByInput.set(handle.name, [edge]);
|
|
522
|
+
}
|
|
523
|
+
if (nodeData.form) {
|
|
524
|
+
for (const [key, value] of Object.entries(nodeData.form)) {
|
|
525
|
+
if (value === undefined || value === '')
|
|
526
|
+
continue;
|
|
527
|
+
const modelInput = modelInputs?.find((inp) => inp.name === key);
|
|
528
|
+
if (modelInput?.type === 'inputs_array') {
|
|
529
|
+
if (!isFormArray(value))
|
|
530
|
+
continue;
|
|
531
|
+
const nestedInputs = modelInput.inputs ?? [];
|
|
532
|
+
const items = [];
|
|
533
|
+
for (const [index, form] of value.entries()) {
|
|
534
|
+
const itemInputs = [];
|
|
535
|
+
for (const nestedInput of nestedInputs) {
|
|
536
|
+
const edgeKey = `${key}[${index}].${nestedInput.name}`;
|
|
537
|
+
const edgeGroup = edgesByInput.get(edgeKey);
|
|
538
|
+
if (edgeGroup && edgeGroup.length > 0) {
|
|
539
|
+
const ref = getSourceRef(edgeGroup[0], ctx);
|
|
540
|
+
if (ref)
|
|
541
|
+
itemInputs.push({ name: nestedInput.name, type: nestedInput.type, ref });
|
|
542
|
+
edgesByInput.delete(edgeKey);
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
const inp = toStaticInput(nestedInput.name, nestedInput.type, form[nestedInput.name]);
|
|
546
|
+
if (inp)
|
|
547
|
+
itemInputs.push(inp);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const isValid = isModelCompose(model) ? getHasComposeLayerSourceInput(itemInputs) : itemInputs.length > 0;
|
|
551
|
+
if (isValid)
|
|
552
|
+
items.push(itemInputs);
|
|
553
|
+
}
|
|
554
|
+
if (items.length > 0)
|
|
555
|
+
flowInputs.push({ name: key, type: 'inputs_array', items });
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
const type = modelInput?.type ??
|
|
559
|
+
(typeof value === 'number' ? 'number'
|
|
560
|
+
: typeof value === 'boolean' ? 'boolean'
|
|
561
|
+
: 'string');
|
|
562
|
+
const inp = toStaticInput(key, type, value);
|
|
563
|
+
if (inp)
|
|
564
|
+
flowInputs.push(inp);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
for (const [inputName, inputEdges] of edgesByInput) {
|
|
569
|
+
const existingIndex = flowInputs.findIndex((inp) => inp.name === inputName);
|
|
570
|
+
const modelInput = modelInputs?.find((inp) => inp.name === inputName);
|
|
571
|
+
if (!modelInput)
|
|
572
|
+
continue;
|
|
573
|
+
const inputType = modelInput.type;
|
|
574
|
+
if (inputEdges.length === 1) {
|
|
575
|
+
const ref = getSourceRef(inputEdges[0], ctx);
|
|
576
|
+
if (!ref)
|
|
577
|
+
continue;
|
|
578
|
+
setOrPushFlowInput(flowInputs, existingIndex, { name: inputName, type: inputType, ref });
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
const allConditional = inputEdges.every((e) => getIsConditionallyExecuted(e.target, nodes, edges));
|
|
582
|
+
if (allConditional) {
|
|
583
|
+
const conditionalNodeIds = inputEdges
|
|
584
|
+
.map((e) => inputNodeIds.includes(e.target) ?
|
|
585
|
+
nodeIdToInputNameMap.get(e.target)
|
|
586
|
+
: nodeIdToFlowIdMap.get(e.target))
|
|
587
|
+
.filter((id) => id != null);
|
|
588
|
+
if (conditionalNodeIds.length > 0) {
|
|
589
|
+
setOrPushFlowInput(flowInputs, existingIndex, {
|
|
590
|
+
name: inputName,
|
|
591
|
+
type: inputType,
|
|
592
|
+
ref: { conditional: conditionalNodeIds },
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
else if (inputType === 'file_array' || inputType === 'string_array') {
|
|
597
|
+
const transformVariables = [];
|
|
598
|
+
const transformInputs = [];
|
|
599
|
+
for (const edge of inputEdges) {
|
|
600
|
+
const targetNode = filteredNodes.find((n) => n.id === edge.target);
|
|
601
|
+
const targetOutputHandle = targetNode?.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
602
|
+
const targetOutputHandleName = targetOutputHandle?.name ?? 'output';
|
|
603
|
+
let targetNodeKey;
|
|
604
|
+
let targetInputType;
|
|
605
|
+
if (targetNode?.type === 'forEach' && edge.targetHandle) {
|
|
606
|
+
const m = edge.targetHandle.match(/-output-(\d+)$/);
|
|
607
|
+
const outputIdx = m ? m[1] ?? '0' : '0';
|
|
608
|
+
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
609
|
+
targetNodeKey = getForEachIterationRefName(outHandle, outputIdx);
|
|
610
|
+
targetInputType = outHandle?.type === 'text' ? 'string' : 'file';
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
targetNodeKey = getOutputHandleVarName(edge.target, targetOutputHandleName);
|
|
614
|
+
targetInputType =
|
|
615
|
+
((targetNode?.type === 'asset' &&
|
|
616
|
+
!!targetNode.data.isMultiple) ||
|
|
617
|
+
targetNode?.type === 'groupItems' ||
|
|
618
|
+
targetNode?.type === 'sliceAssets' ||
|
|
619
|
+
targetOutputHandleName === 'allOutputs') ?
|
|
620
|
+
'file_array'
|
|
621
|
+
: targetNode?.type === 'llm' && targetOutputHandleName === 'outputs' ? 'string_array'
|
|
622
|
+
: targetNode?.type === 'asset' || targetNode?.type === 'model' ? 'file'
|
|
623
|
+
: 'string';
|
|
624
|
+
}
|
|
625
|
+
const isArr = targetInputType === 'file_array';
|
|
626
|
+
const isRequired = (targetNode?.type === 'asset' &&
|
|
627
|
+
!!targetNode.data.isRequired) ||
|
|
628
|
+
isModelCompose(model);
|
|
629
|
+
transformVariables.push({ name: targetNodeKey, isArray: isArr, isRequired });
|
|
630
|
+
if (!targetNode?.data.isInput &&
|
|
631
|
+
(targetNode?.type === 'asset' || targetNode?.type === 'text')) {
|
|
632
|
+
transformInputs.push({
|
|
633
|
+
name: targetNodeKey,
|
|
634
|
+
type: targetInputType,
|
|
635
|
+
value: getNodeOutputValue(targetNode, edge.targetHandle ?? undefined),
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
const ref = getSourceRef(edge, ctx);
|
|
640
|
+
if (!ref)
|
|
641
|
+
continue;
|
|
642
|
+
transformInputs.push({ name: targetNodeKey, type: targetInputType, ref });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const hasLoopIterationInputs = transformInputs.some((inp) => /^(image|text)\d+$/.test(inp.name));
|
|
646
|
+
if (hasLoopIterationInputs) {
|
|
647
|
+
const sortByIdx = (a, b) => parseInt(a.name.replace(/^(image|text)/, ''), 10) -
|
|
648
|
+
parseInt(b.name.replace(/^(image|text)/, ''), 10);
|
|
649
|
+
transformVariables.sort(sortByIdx);
|
|
650
|
+
transformInputs.sort(sortByIdx);
|
|
651
|
+
}
|
|
652
|
+
const concatItem = {
|
|
653
|
+
id: `${flowId}_transform_${inputName}`,
|
|
654
|
+
type: 'transform',
|
|
655
|
+
logic: { transform: buildConcatArrayTransform(transformVariables) },
|
|
656
|
+
inputs: transformInputs,
|
|
657
|
+
};
|
|
658
|
+
flow.push(concatItem);
|
|
659
|
+
setOrPushFlowInput(flowInputs, existingIndex, {
|
|
660
|
+
name: inputName,
|
|
661
|
+
type: inputType,
|
|
662
|
+
ref: { node: concatItem.id, name: 'all' },
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
const ref = getSourceRef(inputEdges[0], ctx);
|
|
667
|
+
if (!ref)
|
|
668
|
+
continue;
|
|
669
|
+
setOrPushFlowInput(flowInputs, existingIndex, { name: inputName, type: inputType, ref });
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// Conditional control flow connections
|
|
674
|
+
const conditionalEdges = incomingEdges.filter((e) => e.sourceHandle === conditionalHandleId);
|
|
675
|
+
if (conditionalEdges.length > 0) {
|
|
676
|
+
const conditionalRefs = [];
|
|
677
|
+
for (const edge of conditionalEdges) {
|
|
678
|
+
const targetNode = nodes.find((n) => n.id === edge.target);
|
|
679
|
+
if (targetNode?.type === 'ifElse') {
|
|
680
|
+
const ref = getSourceRef(edge, ctx);
|
|
681
|
+
if (ref?.equal)
|
|
682
|
+
flowInputs.push({ name: 'connection', type: 'connection', ref });
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
const targetFlowId = nodeIdToFlowIdMap.get(edge.target);
|
|
686
|
+
if (targetFlowId)
|
|
687
|
+
conditionalRefs.push(targetFlowId);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (conditionalRefs.length > 0) {
|
|
691
|
+
flowInputs.push({ name: 'connection', type: 'connection', ref: { conditional: conditionalRefs } });
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
flow.push({
|
|
695
|
+
id: flowId,
|
|
696
|
+
type: 'custom-model',
|
|
697
|
+
modelId: nodeData.modelId,
|
|
698
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
699
|
+
includeOutputsInWorkflowJob: node.type === 'model' && selectedOutputModelNodeIds.has(node.id) ? true : undefined,
|
|
700
|
+
});
|
|
701
|
+
ensureInputVideoTransformsAdded();
|
|
702
|
+
if (node.type === 'model' && node.data.type === 'video') {
|
|
703
|
+
addVideoFirstLastFrameTransforms(node.id, flowId, node, edges, flow, nodeHandleTransformMap);
|
|
704
|
+
}
|
|
705
|
+
else if (node.type === 'model' && node.data.outputHandles?.some((h) => h.name === 'firstOutput')) {
|
|
706
|
+
if (hasHandleConnection(node, 'firstOutput', edges, node.id)) {
|
|
707
|
+
const transformId = `${flowId}_firstOutput`;
|
|
708
|
+
flow.push({
|
|
709
|
+
id: transformId,
|
|
710
|
+
type: 'transform',
|
|
711
|
+
logic: { transform: "exists('output') && output.size() > 0 ? output[0] : []" },
|
|
712
|
+
inputs: [{ name: 'output', type: 'file_array', ref: { node: flowId, name: 'all' } }],
|
|
713
|
+
});
|
|
714
|
+
nodeHandleTransformMap.set(`${node.id}_firstOutput`, transformId);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (node.type === 'llm' && hasHandleConnection(node, 'outputs', edges, node.id)) {
|
|
718
|
+
const transformId = `${flowId}_outputs`;
|
|
719
|
+
flow.push({
|
|
720
|
+
id: transformId,
|
|
721
|
+
type: 'transform',
|
|
722
|
+
logic: { transform: '[output]' },
|
|
723
|
+
inputs: [{ name: 'output', type: 'string', ref: { node: flowId, name: 'all' } }],
|
|
724
|
+
});
|
|
725
|
+
nodeHandleTransformMap.set(`${node.id}_outputs`, transformId);
|
|
726
|
+
}
|
|
727
|
+
// ------------------------------------
|
|
728
|
+
// Text -> transform with string literal
|
|
729
|
+
// ------------------------------------
|
|
730
|
+
}
|
|
731
|
+
else if (node.type === 'text') {
|
|
732
|
+
const flowInputs = collectConditionalConnectionInputs(incomingEdges, node.id, ctx);
|
|
733
|
+
const value = node.data.value || '';
|
|
734
|
+
flow.push({
|
|
735
|
+
id: flowId,
|
|
736
|
+
type: 'transform',
|
|
737
|
+
logic: { transform: `'${escapeCELString(value)}'` },
|
|
738
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
739
|
+
});
|
|
740
|
+
// ------------------------------------
|
|
741
|
+
// Asset -> transform with file reference
|
|
742
|
+
// ------------------------------------
|
|
743
|
+
}
|
|
744
|
+
else if (node.type === 'asset') {
|
|
745
|
+
const flowInputs = collectConditionalConnectionInputs(incomingEdges, node.id, ctx);
|
|
746
|
+
const d = node.data;
|
|
747
|
+
const value = d.value;
|
|
748
|
+
if (!value || (Array.isArray(value) && value.length === 0))
|
|
749
|
+
continue;
|
|
750
|
+
flow.push({
|
|
751
|
+
id: flowId,
|
|
752
|
+
type: 'transform',
|
|
753
|
+
logic: {
|
|
754
|
+
transform: Array.isArray(value) ? `['${value.join("'] + ['")}']` : `'${value}'`,
|
|
755
|
+
},
|
|
756
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
757
|
+
});
|
|
758
|
+
if (d.type === 'video') {
|
|
759
|
+
addVideoFirstLastFrameTransforms(node.id, flowId, node, edges, flow, nodeHandleTransformMap);
|
|
760
|
+
}
|
|
761
|
+
// ------------------------------------
|
|
762
|
+
// TransformText -> transform (CEL)
|
|
763
|
+
// ------------------------------------
|
|
764
|
+
}
|
|
765
|
+
else if (node.type === 'transformText') {
|
|
766
|
+
const flowInputs = [];
|
|
767
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
768
|
+
const value = node.data.value || "''";
|
|
769
|
+
for (const edge of incomingEdges) {
|
|
770
|
+
if (edge.sourceHandle === conditionalHandleId)
|
|
771
|
+
continue;
|
|
772
|
+
const targetNode = nodes.find((n) => n.id === edge.target);
|
|
773
|
+
if (!targetNode)
|
|
774
|
+
continue;
|
|
775
|
+
const ref = getSourceRef(edge, ctx);
|
|
776
|
+
if (!ref)
|
|
777
|
+
continue;
|
|
778
|
+
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
779
|
+
const outHandleName = outHandle?.name ?? 'output';
|
|
780
|
+
const targetFlowId = nodeIdToFlowIdMap.get(targetNode.id) ?? targetNode.id;
|
|
781
|
+
flowInputs.push({ name: `${targetFlowId}_${outHandleName}`, type: 'string', ref });
|
|
782
|
+
}
|
|
783
|
+
flowInputs.push(...collectConditionalConnectionInputs(incomingEdges, node.id, ctx));
|
|
784
|
+
flow.push({
|
|
785
|
+
id: flowId,
|
|
786
|
+
type: 'transform',
|
|
787
|
+
logic: { transform: value },
|
|
788
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
789
|
+
});
|
|
790
|
+
// ------------------------------------
|
|
791
|
+
// SplitText -> split string into list
|
|
792
|
+
// ------------------------------------
|
|
793
|
+
}
|
|
794
|
+
else if (node.type === 'splitText') {
|
|
795
|
+
const flowInputs = [];
|
|
796
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
797
|
+
let textSourceVarName;
|
|
798
|
+
for (const edge of incomingEdges) {
|
|
799
|
+
if (edge.sourceHandle === conditionalHandleId)
|
|
800
|
+
continue;
|
|
801
|
+
const targetNode = nodes.find((n) => n.id === edge.target);
|
|
802
|
+
if (!targetNode)
|
|
803
|
+
continue;
|
|
804
|
+
const ref = getSourceRef(edge, ctx);
|
|
805
|
+
if (!ref)
|
|
806
|
+
continue;
|
|
807
|
+
flowInputs.push({ name: targetNode.id, type: 'string', ref });
|
|
808
|
+
textSourceVarName = targetNode.id;
|
|
809
|
+
}
|
|
810
|
+
flowInputs.push(...collectConditionalConnectionInputs(incomingEdges, node.id, ctx));
|
|
811
|
+
const delimRaw = node.data.splitDelimiter ?? ',';
|
|
812
|
+
const delim = delimRaw.length > 0 ? delimRaw : ',';
|
|
813
|
+
flow.push({
|
|
814
|
+
id: flowId,
|
|
815
|
+
type: 'transform',
|
|
816
|
+
logic: {
|
|
817
|
+
transform: textSourceVarName ? `trim(${textSourceVarName}).split(${formatTextAsCELString(delim)})` : '[]',
|
|
818
|
+
},
|
|
819
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
820
|
+
});
|
|
821
|
+
// ------------------------------------
|
|
822
|
+
// GroupItems -> array concatenation
|
|
823
|
+
// ------------------------------------
|
|
824
|
+
}
|
|
825
|
+
else if (node.type === 'groupItems') {
|
|
826
|
+
const flowInputs = [];
|
|
827
|
+
const variables = [];
|
|
828
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
829
|
+
for (const edge of incomingEdges) {
|
|
830
|
+
if (edge.sourceHandle === conditionalHandleId)
|
|
831
|
+
continue;
|
|
832
|
+
const targetNode = nodes.find((n) => n.id === edge.target);
|
|
833
|
+
if (!targetNode)
|
|
834
|
+
continue;
|
|
835
|
+
const ref = getSourceRef(edge, ctx);
|
|
836
|
+
if (!ref)
|
|
837
|
+
continue;
|
|
838
|
+
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
839
|
+
const outHandleName = outHandle?.name ?? 'output';
|
|
840
|
+
const varName = getOutputHandleVarName(targetNode.id, outHandleName);
|
|
841
|
+
const isArr = (targetNode.type === 'asset' && !!targetNode.data.isMultiple) ||
|
|
842
|
+
targetNode.type === 'groupItems' ||
|
|
843
|
+
targetNode.type === 'sliceAssets' ||
|
|
844
|
+
outHandleName === 'allOutputs';
|
|
845
|
+
const isRequired = inputNodeIds.includes(targetNode.id) ?
|
|
846
|
+
(targetNode.type === 'asset' && !!targetNode.data.isRequired) ||
|
|
847
|
+
['firstFrame', 'lastFrame'].includes(outHandleName)
|
|
848
|
+
: true;
|
|
849
|
+
variables.push({ name: varName, isArray: isArr, isRequired });
|
|
850
|
+
flowInputs.push({ name: varName, type: isArr ? 'file_array' : 'file', ref });
|
|
851
|
+
}
|
|
852
|
+
const orderedValues = convertValueToList(node.data.value ?? '');
|
|
853
|
+
variables.sort((a, b) => orderedValues.indexOf(a.name) - orderedValues.indexOf(b.name));
|
|
854
|
+
flowInputs.sort((a, b) => orderedValues.indexOf(a.name) - orderedValues.indexOf(b.name));
|
|
855
|
+
flowInputs.push(...collectConditionalConnectionInputs(incomingEdges, node.id, ctx));
|
|
856
|
+
flow.push({
|
|
857
|
+
id: flowId,
|
|
858
|
+
type: 'transform',
|
|
859
|
+
logic: { transform: buildConcatArrayTransform(variables) },
|
|
860
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
861
|
+
});
|
|
862
|
+
// ------------------------------------
|
|
863
|
+
// SliceAssets -> slice(list, from, from+count)
|
|
864
|
+
// ------------------------------------
|
|
865
|
+
}
|
|
866
|
+
else if (node.type === 'sliceAssets') {
|
|
867
|
+
const assetsEdge = incomingEdges.find((e) => e.sourceHandle === `${node.id}-source-assets`);
|
|
868
|
+
if (!assetsEdge) {
|
|
869
|
+
flow.push({ id: flowId, type: 'transform', logic: { transform: '[]' } });
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
const targetNode = nodes.find((n) => n.id === assetsEdge.target);
|
|
873
|
+
if (targetNode) {
|
|
874
|
+
const ref = getSourceRef(assetsEdge, ctx);
|
|
875
|
+
if (ref) {
|
|
876
|
+
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === assetsEdge.targetHandle);
|
|
877
|
+
const varName = getOutputHandleVarName(targetNode.id, outHandle?.name ?? 'output');
|
|
878
|
+
const sliceData = node.data;
|
|
879
|
+
const from = parseInt(String(sliceData.from ?? ''), 10) || 0;
|
|
880
|
+
const count = parseInt(String(sliceData.count ?? ''), 10) || 0;
|
|
881
|
+
const transform = from < 0 ?
|
|
882
|
+
`slice(${varName}, (${varName}).size() + ${from}, (${varName}).size() + ${from + count})`
|
|
883
|
+
: `slice(${varName}, ${from}, ${from + count})`;
|
|
884
|
+
const flowInputs = [
|
|
885
|
+
{ name: varName, type: 'file_array', ref },
|
|
886
|
+
...collectConditionalConnectionInputs(incomingEdges, node.id, ctx),
|
|
887
|
+
];
|
|
888
|
+
flow.push({
|
|
889
|
+
id: flowId,
|
|
890
|
+
type: 'transform',
|
|
891
|
+
logic: { transform },
|
|
892
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
// ------------------------------------
|
|
898
|
+
// ForEach -> for-each loop
|
|
899
|
+
// ------------------------------------
|
|
900
|
+
}
|
|
901
|
+
else if (node.type === 'forEach') {
|
|
902
|
+
const flowInputs = [];
|
|
903
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
904
|
+
for (const edge of incomingEdges) {
|
|
905
|
+
if (edge.sourceHandle === conditionalHandleId)
|
|
906
|
+
continue;
|
|
907
|
+
const inputMatch = edge.sourceHandle?.match(/-input-(\d+)$/);
|
|
908
|
+
if (!inputMatch)
|
|
909
|
+
continue;
|
|
910
|
+
const inputIndex = parseInt(inputMatch[1], 10);
|
|
911
|
+
const listProvider = nodes.find((n) => n.id === edge.target);
|
|
912
|
+
const listOutputHandle = listProvider?.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
913
|
+
const { name: inputName, type: inputApiType } = getForEachFlowListInputDescriptor({
|
|
914
|
+
listProvider,
|
|
915
|
+
outputHandle: listOutputHandle,
|
|
916
|
+
inputIndex,
|
|
917
|
+
});
|
|
918
|
+
if (flowInputs.find((inp) => inp.name === inputName))
|
|
919
|
+
continue;
|
|
920
|
+
const ref = getSourceRef(edge, ctx);
|
|
921
|
+
if (!ref)
|
|
922
|
+
continue;
|
|
923
|
+
flowInputs.push({ name: inputName, type: inputApiType, ref });
|
|
924
|
+
}
|
|
925
|
+
flowInputs.sort((a, b) => parseInt(a.name.replace(/^(image|text)/, ''), 10) -
|
|
926
|
+
parseInt(b.name.replace(/^(image|text)/, ''), 10));
|
|
927
|
+
const forEachFlowItem = {
|
|
928
|
+
id: flowId,
|
|
929
|
+
type: 'for-each',
|
|
930
|
+
loopBodyNodeIds: [],
|
|
931
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
932
|
+
};
|
|
933
|
+
flow.push(forEachFlowItem);
|
|
934
|
+
forEachFlowItemRefs.set(node.id, forEachFlowItem);
|
|
935
|
+
// ------------------------------------
|
|
936
|
+
// IfElse -> logic if-else
|
|
937
|
+
// ------------------------------------
|
|
938
|
+
}
|
|
939
|
+
else if (node.type === 'ifElse') {
|
|
940
|
+
const flowInputs = [];
|
|
941
|
+
const conditionBlocks = node.data.conditionBlocks ?? [];
|
|
942
|
+
const fieldsUsed = new Set();
|
|
943
|
+
for (const block of conditionBlocks) {
|
|
944
|
+
for (const condition of block.conditions) {
|
|
945
|
+
if (condition.field)
|
|
946
|
+
fieldsUsed.add(condition.field);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
for (const fieldNodeId of fieldsUsed) {
|
|
950
|
+
if (inputNodeIds.includes(fieldNodeId)) {
|
|
951
|
+
const inputName = nodeIdToInputNameMap.get(fieldNodeId);
|
|
952
|
+
if (inputName)
|
|
953
|
+
flowInputs.push({ name: inputName, type: 'string', ref: { node: 'workflow', name: inputName } });
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
const targetFlowId = nodeIdToFlowIdMap.get(fieldNodeId);
|
|
957
|
+
if (targetFlowId) {
|
|
958
|
+
flowInputs.push({ name: targetFlowId, type: 'string', ref: { node: targetFlowId, name: 'all' } });
|
|
959
|
+
nodeIdToInputNameMap.set(fieldNodeId, targetFlowId);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
const cases = [];
|
|
964
|
+
for (let i = 0; i < conditionBlocks.length; i++) {
|
|
965
|
+
const condition = conditionBlockToCEL(conditionBlocks[i], nodeIdToInputNameMap);
|
|
966
|
+
if (condition)
|
|
967
|
+
cases.push({ condition, value: String(i + 2) });
|
|
968
|
+
}
|
|
969
|
+
flow.push({
|
|
970
|
+
id: flowId,
|
|
971
|
+
type: 'logic',
|
|
972
|
+
logicType: 'if-else',
|
|
973
|
+
logic: { cases: cases.length > 0 ? cases : undefined, default: '1' },
|
|
974
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
// Track which flow items belong to each forEach body
|
|
978
|
+
for (const [forEachId, bodyNodeIds] of forEachBodyEditorNodeIds) {
|
|
979
|
+
if (bodyNodeIds.has(node.id)) {
|
|
980
|
+
const loopFlowIds = forEachLoopFlowIds.get(forEachId);
|
|
981
|
+
if (loopFlowIds) {
|
|
982
|
+
for (let i = flowLengthBefore; i < flow.length; i++) {
|
|
983
|
+
loopFlowIds.push(flow[i].id);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
// Populate loopBodyNodeIds on each forEach flow item
|
|
990
|
+
for (const [forEachId, forEachFlowItem] of forEachFlowItemRefs) {
|
|
991
|
+
forEachFlowItem.loopBodyNodeIds = forEachLoopFlowIds.get(forEachId) ?? [];
|
|
992
|
+
}
|
|
993
|
+
return flow;
|
|
994
|
+
}
|
|
995
|
+
//# sourceMappingURL=workflow_converter.js.map
|