@scenario-labs/sdk 2.0.0 → 2.2.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 +40 -0
- package/api-promise.d.mts +1 -0
- package/api-promise.d.mts.map +1 -1
- package/api-promise.d.ts +1 -0
- package/api-promise.d.ts.map +1 -1
- package/api-promise.mjs +1 -0
- package/api-promise.mjs.map +1 -1
- package/client.d.mts.map +1 -1
- package/client.d.ts.map +1 -1
- package/client.mjs.map +1 -1
- package/core/api-promise.d.mts.map +1 -1
- package/core/api-promise.d.ts.map +1 -1
- package/core/api-promise.mjs.map +1 -1
- package/core/error.mjs.map +1 -1
- package/core/pagination.d.mts.map +1 -1
- package/core/pagination.d.ts.map +1 -1
- package/core/pagination.mjs.map +1 -1
- package/core/resource.d.mts.map +1 -1
- package/core/resource.d.ts.map +1 -1
- package/core/uploads.d.mts.map +1 -1
- package/core/uploads.d.ts.map +1 -1
- package/core/uploads.mjs.map +1 -1
- package/error.d.mts +1 -0
- package/error.d.mts.map +1 -1
- package/error.d.ts +1 -0
- package/error.d.ts.map +1 -1
- package/error.mjs +1 -0
- package/error.mjs.map +1 -1
- package/index.d.mts +1 -1
- package/index.d.mts.map +1 -1
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +6 -1
- package/index.js.map +1 -1
- package/index.mjs +1 -1
- package/index.mjs.map +1 -1
- package/internal/detect-platform.mjs.map +1 -1
- package/internal/headers.mjs.map +1 -1
- package/internal/parse.d.mts.map +1 -1
- package/internal/parse.d.ts.map +1 -1
- package/internal/parse.mjs.map +1 -1
- package/internal/qs/formats.d.mts.map +1 -1
- package/internal/qs/formats.d.ts.map +1 -1
- package/internal/qs/index.d.mts +2 -2
- package/internal/qs/index.d.mts.map +1 -1
- package/internal/qs/index.d.ts +2 -2
- package/internal/qs/index.d.ts.map +1 -1
- package/internal/qs/index.mjs.map +1 -1
- package/internal/qs/stringify.d.mts.map +1 -1
- package/internal/qs/stringify.d.ts.map +1 -1
- package/internal/qs/stringify.mjs.map +1 -1
- package/internal/qs/utils.d.mts.map +1 -1
- package/internal/qs/utils.d.ts.map +1 -1
- package/internal/qs/utils.mjs.map +1 -1
- package/internal/request-options.d.mts.map +1 -1
- package/internal/request-options.d.ts.map +1 -1
- package/internal/shims.d.mts +6 -0
- package/internal/shims.d.mts.map +1 -1
- package/internal/shims.d.ts +6 -0
- package/internal/shims.d.ts.map +1 -1
- package/internal/to-file.d.mts.map +1 -1
- package/internal/to-file.d.ts.map +1 -1
- package/internal/to-file.mjs.map +1 -1
- package/internal/types.d.mts +4 -4
- package/internal/types.d.mts.map +1 -1
- package/internal/types.d.ts +4 -4
- package/internal/types.d.ts.map +1 -1
- package/internal/uploads.d.mts.map +1 -1
- package/internal/uploads.d.ts.map +1 -1
- package/internal/uploads.mjs.map +1 -1
- package/internal/utils/base64.mjs.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.mjs.map +1 -1
- package/internal/utils/path.mjs.map +1 -1
- package/internal/utils/query.mjs.map +1 -1
- package/internal/utils/values.mjs.map +1 -1
- package/internal/utils.d.mts.map +1 -1
- package/internal/utils.d.ts.map +1 -1
- package/internal/utils.mjs.map +1 -1
- package/lib/asset.d.mts.map +1 -1
- package/lib/asset.d.ts.map +1 -1
- package/lib/asset.mjs.map +1 -1
- package/lib/generate.d.mts.map +1 -1
- package/lib/generate.d.ts.map +1 -1
- package/lib/generate.mjs.map +1 -1
- package/lib/job.d.mts.map +1 -1
- package/lib/job.d.ts.map +1 -1
- package/lib/job.mjs.map +1 -1
- package/lib/models.d.mts.map +1 -1
- package/lib/models.d.ts.map +1 -1
- package/lib/models.mjs.map +1 -1
- package/lib/scenario.d.mts.map +1 -1
- package/lib/scenario.d.ts.map +1 -1
- package/lib/scenario.mjs.map +1 -1
- package/lib/upload.d.mts.map +1 -1
- package/lib/upload.d.ts.map +1 -1
- package/lib/upload.mjs.map +1 -1
- package/lib/workflow.d.mts.map +1 -1
- package/lib/workflow.d.ts.map +1 -1
- package/lib/workflow.mjs.map +1 -1
- package/lib/workflow_converter.d.mts +58 -2
- package/lib/workflow_converter.d.mts.map +1 -1
- package/lib/workflow_converter.d.ts +58 -2
- package/lib/workflow_converter.d.ts.map +1 -1
- package/lib/workflow_converter.js +350 -35
- package/lib/workflow_converter.js.map +1 -1
- package/lib/workflow_converter.mjs +345 -35
- package/lib/workflow_converter.mjs.map +1 -1
- package/lib/workflow_import_validator.d.mts +1 -1
- package/lib/workflow_import_validator.d.mts.map +1 -1
- package/lib/workflow_import_validator.d.ts +1 -1
- package/lib/workflow_import_validator.d.ts.map +1 -1
- package/lib/workflow_import_validator.js +1 -0
- package/lib/workflow_import_validator.js.map +1 -1
- package/lib/workflow_import_validator.mjs +1 -0
- package/lib/workflow_import_validator.mjs.map +1 -1
- package/package.json +1 -1
- package/pagination.d.mts +1 -0
- package/pagination.d.mts.map +1 -1
- package/pagination.d.ts +1 -0
- package/pagination.d.ts.map +1 -1
- package/pagination.mjs +1 -0
- package/pagination.mjs.map +1 -1
- package/resource.d.mts +1 -0
- package/resource.d.mts.map +1 -1
- package/resource.d.ts +1 -0
- package/resource.d.ts.map +1 -1
- package/resource.mjs +1 -0
- package/resource.mjs.map +1 -1
- package/resources/assets/assets.d.mts +289 -135
- package/resources/assets/assets.d.mts.map +1 -1
- package/resources/assets/assets.d.ts +289 -135
- package/resources/assets/assets.d.ts.map +1 -1
- package/resources/assets/assets.js.map +1 -1
- package/resources/assets/assets.mjs.map +1 -1
- package/resources/assets/download.d.mts.map +1 -1
- package/resources/assets/download.d.ts.map +1 -1
- package/resources/assets/download.mjs.map +1 -1
- package/resources/assets/index.d.mts.map +1 -1
- package/resources/assets/index.d.ts.map +1 -1
- package/resources/assets/index.mjs.map +1 -1
- package/resources/assets.d.mts.map +1 -1
- package/resources/assets.d.ts.map +1 -1
- package/resources/assets.mjs.map +1 -1
- package/resources/collections/assets.d.mts.map +1 -1
- package/resources/collections/assets.d.ts.map +1 -1
- package/resources/collections/assets.mjs.map +1 -1
- package/resources/collections/collections.d.mts.map +1 -1
- package/resources/collections/collections.d.ts.map +1 -1
- package/resources/collections/collections.mjs.map +1 -1
- package/resources/collections/index.d.mts.map +1 -1
- package/resources/collections/index.d.ts.map +1 -1
- package/resources/collections/index.mjs.map +1 -1
- package/resources/collections/models.d.mts.map +1 -1
- package/resources/collections/models.d.ts.map +1 -1
- package/resources/collections/models.mjs.map +1 -1
- package/resources/collections.d.mts.map +1 -1
- package/resources/collections.d.ts.map +1 -1
- package/resources/collections.mjs.map +1 -1
- package/resources/generate.d.mts +86 -48
- package/resources/generate.d.mts.map +1 -1
- package/resources/generate.d.ts +86 -48
- package/resources/generate.d.ts.map +1 -1
- package/resources/generate.mjs.map +1 -1
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts.map +1 -1
- package/resources/index.mjs.map +1 -1
- package/resources/jobs.d.mts +6 -6
- package/resources/jobs.d.mts.map +1 -1
- package/resources/jobs.d.ts +6 -6
- package/resources/jobs.d.ts.map +1 -1
- package/resources/jobs.mjs.map +1 -1
- package/resources/models/description.d.mts +54 -4
- package/resources/models/description.d.mts.map +1 -1
- package/resources/models/description.d.ts +54 -4
- package/resources/models/description.d.ts.map +1 -1
- package/resources/models/description.mjs.map +1 -1
- package/resources/models/examples.d.mts +74 -36
- package/resources/models/examples.d.mts.map +1 -1
- package/resources/models/examples.d.ts +74 -36
- package/resources/models/examples.d.ts.map +1 -1
- package/resources/models/examples.mjs.map +1 -1
- package/resources/models/index.d.mts.map +1 -1
- package/resources/models/index.d.ts.map +1 -1
- package/resources/models/index.mjs.map +1 -1
- package/resources/models/models.d.mts +21 -21
- package/resources/models/models.d.mts.map +1 -1
- package/resources/models/models.d.ts +21 -21
- package/resources/models/models.d.ts.map +1 -1
- package/resources/models/models.js.map +1 -1
- package/resources/models/models.mjs.map +1 -1
- package/resources/models/train.d.mts +8 -8
- package/resources/models/train.d.mts.map +1 -1
- package/resources/models/train.d.ts +8 -8
- package/resources/models/train.d.ts.map +1 -1
- package/resources/models/train.mjs.map +1 -1
- package/resources/models/training-images.d.mts.map +1 -1
- package/resources/models/training-images.d.ts.map +1 -1
- package/resources/models/training-images.mjs.map +1 -1
- package/resources/models.d.mts.map +1 -1
- package/resources/models.d.ts.map +1 -1
- package/resources/models.mjs.map +1 -1
- package/resources/oscu.d.mts.map +1 -1
- package/resources/oscu.d.ts.map +1 -1
- package/resources/oscu.mjs.map +1 -1
- package/resources/pricing/index.d.mts.map +1 -1
- package/resources/pricing/index.d.ts.map +1 -1
- package/resources/pricing/index.mjs.map +1 -1
- package/resources/pricing/oscu.d.mts.map +1 -1
- package/resources/pricing/oscu.d.ts.map +1 -1
- package/resources/pricing/oscu.mjs.map +1 -1
- package/resources/pricing/pricing.d.mts.map +1 -1
- package/resources/pricing/pricing.d.ts.map +1 -1
- package/resources/pricing/pricing.mjs.map +1 -1
- package/resources/pricing.d.mts.map +1 -1
- package/resources/pricing.d.ts.map +1 -1
- package/resources/pricing.mjs.map +1 -1
- package/resources/search.d.mts +6 -0
- package/resources/search.d.mts.map +1 -1
- package/resources/search.d.ts +6 -0
- package/resources/search.d.ts.map +1 -1
- package/resources/search.mjs.map +1 -1
- package/resources/tags.d.mts.map +1 -1
- package/resources/tags.d.ts.map +1 -1
- package/resources/tags.mjs.map +1 -1
- package/resources/uploads.d.mts +4 -4
- package/resources/uploads.d.mts.map +1 -1
- package/resources/uploads.d.ts +4 -4
- package/resources/uploads.d.ts.map +1 -1
- package/resources/uploads.mjs.map +1 -1
- package/resources/usages.d.mts +31 -6
- package/resources/usages.d.mts.map +1 -1
- package/resources/usages.d.ts +31 -6
- package/resources/usages.d.ts.map +1 -1
- package/resources/usages.mjs.map +1 -1
- package/resources/workflows.d.mts +36 -36
- package/resources/workflows.d.mts.map +1 -1
- package/resources/workflows.d.ts +36 -36
- package/resources/workflows.d.ts.map +1 -1
- package/resources/workflows.mjs.map +1 -1
- package/resources.d.mts.map +1 -1
- package/resources.d.ts.map +1 -1
- package/resources.mjs.map +1 -1
- package/src/index.ts +9 -0
- package/src/lib/workflow_converter.ts +487 -28
- package/src/lib/workflow_import_validator.ts +2 -0
- package/src/resources/assets/assets.ts +376 -118
- package/src/resources/generate.ts +106 -44
- package/src/resources/jobs.ts +6 -6
- package/src/resources/models/description.ts +68 -2
- package/src/resources/models/examples.ts +94 -32
- package/src/resources/models/models.ts +21 -14
- package/src/resources/models/train.ts +8 -6
- package/src/resources/search.ts +7 -0
- package/src/resources/uploads.ts +4 -4
- package/src/resources/usages.ts +41 -4
- package/src/resources/workflows.ts +36 -36
- package/src/version.ts +1 -1
- package/uploads.d.mts +1 -0
- package/uploads.d.mts.map +1 -1
- package/uploads.d.ts +1 -0
- package/uploads.d.ts.map +1 -1
- package/uploads.mjs +1 -0
- package/uploads.mjs.map +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -47,6 +47,7 @@ export type WorkflowEditorNode =
|
|
|
47
47
|
}
|
|
48
48
|
| { id: string; type: 'transformText'; data: NodeCommonData & { value?: string } }
|
|
49
49
|
| { id: string; type: 'splitText'; data: NodeCommonData & { splitDelimiter?: string } }
|
|
50
|
+
| { id: string; type: 'aspectRatio'; data: NodeCommonData & { output?: string } }
|
|
50
51
|
| { id: string; type: 'groupItems'; data: NodeCommonData & { value?: string } }
|
|
51
52
|
| {
|
|
52
53
|
id: string;
|
|
@@ -60,6 +61,7 @@ export type WorkflowEditorNode =
|
|
|
60
61
|
type: 'ifElse';
|
|
61
62
|
data: NodeCommonData & { conditionBlocks?: WorkflowEditorConditionBlock[] };
|
|
62
63
|
}
|
|
64
|
+
| { id: string; type: 'approval'; data: NodeCommonData & { message?: string } }
|
|
63
65
|
| { id: string; type: string; data: NodeCommonData };
|
|
64
66
|
|
|
65
67
|
export type WorkflowEditorEdge = {
|
|
@@ -67,6 +69,7 @@ export type WorkflowEditorEdge = {
|
|
|
67
69
|
target: string;
|
|
68
70
|
sourceHandle?: string | null;
|
|
69
71
|
targetHandle?: string | null;
|
|
72
|
+
data?: { isForEachEndEdge?: boolean } | undefined;
|
|
70
73
|
};
|
|
71
74
|
|
|
72
75
|
export type WorkflowEditorCondition = {
|
|
@@ -84,12 +87,55 @@ export type WorkflowEditorModelInput = {
|
|
|
84
87
|
name: string;
|
|
85
88
|
type: string;
|
|
86
89
|
inputs?: Array<{ name: string; type: string }>;
|
|
90
|
+
min?: number;
|
|
91
|
+
max?: number;
|
|
92
|
+
step?: number;
|
|
93
|
+
allowedValues?: string[];
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type WorkflowEditorResolutionPreset = {
|
|
97
|
+
width?: number;
|
|
98
|
+
height?: number;
|
|
87
99
|
};
|
|
88
100
|
|
|
89
101
|
export type WorkflowEditorModel = {
|
|
90
102
|
id?: string;
|
|
91
103
|
inputs?: WorkflowEditorModelInput[];
|
|
92
104
|
tags?: string[];
|
|
105
|
+
uiConfig?: {
|
|
106
|
+
resolutionComponent?: {
|
|
107
|
+
widthInput?: string;
|
|
108
|
+
heightInput?: string;
|
|
109
|
+
presets?: WorkflowEditorResolutionPreset[];
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
|
|
115
|
+
export const ASPECT_RATIO_PRESETS = [
|
|
116
|
+
'16:9',
|
|
117
|
+
'3:2',
|
|
118
|
+
'4:3',
|
|
119
|
+
'5:4',
|
|
120
|
+
'1:1',
|
|
121
|
+
'4:5',
|
|
122
|
+
'3:4',
|
|
123
|
+
'2:3',
|
|
124
|
+
'9:16',
|
|
125
|
+
] as const;
|
|
126
|
+
|
|
127
|
+
export type AspectRatioBounds = {
|
|
128
|
+
minWidth: number;
|
|
129
|
+
maxWidth: number;
|
|
130
|
+
minHeight: number;
|
|
131
|
+
maxHeight: number;
|
|
132
|
+
multipleOf: number;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export type AspectRatioModelInputNames = {
|
|
136
|
+
widthInputName?: string | undefined;
|
|
137
|
+
heightInputName?: string | undefined;
|
|
138
|
+
aspectRatioInputName?: string | undefined;
|
|
93
139
|
};
|
|
94
140
|
|
|
95
141
|
export type WorkflowEditorFlowInputRef = {
|
|
@@ -122,6 +168,8 @@ export type WorkflowEditorFlowItem = {
|
|
|
122
168
|
logicType?: 'if-else' | undefined;
|
|
123
169
|
loopBodyNodeIds?: string[] | undefined;
|
|
124
170
|
includeOutputsInWorkflowJob?: true | undefined;
|
|
171
|
+
dependsOn?: string[] | undefined;
|
|
172
|
+
label?: string | undefined;
|
|
125
173
|
};
|
|
126
174
|
|
|
127
175
|
// ---- Internal types ----
|
|
@@ -172,6 +220,119 @@ function getConditionalInputHandleId(nodeId: string): string {
|
|
|
172
220
|
return `${nodeId}-source-conditional`;
|
|
173
221
|
}
|
|
174
222
|
|
|
223
|
+
function handleTypeIncludes(handleType: string | string[] | undefined, value: string): boolean {
|
|
224
|
+
if (Array.isArray(handleType)) return handleType.includes(value);
|
|
225
|
+
return handleType === value;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
type ParsedAspectRatio = { width: number; height: number };
|
|
229
|
+
|
|
230
|
+
function parseAspectRatio(aspectRatio: string | undefined): ParsedAspectRatio | undefined {
|
|
231
|
+
if (!aspectRatio) return undefined;
|
|
232
|
+
const [rawWidth, rawHeight] = aspectRatio.split(':');
|
|
233
|
+
const width = Number(rawWidth);
|
|
234
|
+
const height = Number(rawHeight);
|
|
235
|
+
if (
|
|
236
|
+
!Number.isFinite(width) ||
|
|
237
|
+
!Number.isFinite(height) ||
|
|
238
|
+
width <= 0 ||
|
|
239
|
+
height <= 0 ||
|
|
240
|
+
!Number.isInteger(width) ||
|
|
241
|
+
!Number.isInteger(height)
|
|
242
|
+
) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
return { width, height };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getGreatestCommonDivisor(a: number, b: number): number {
|
|
249
|
+
let x = Math.abs(Math.trunc(a));
|
|
250
|
+
let y = Math.abs(Math.trunc(b));
|
|
251
|
+
while (y) {
|
|
252
|
+
const temp = y;
|
|
253
|
+
y = x % y;
|
|
254
|
+
x = temp;
|
|
255
|
+
}
|
|
256
|
+
return x || 1;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function makeNumberAMultipleOfX(value: number, multipleOf: number): number {
|
|
260
|
+
if (multipleOf <= 0) return Math.round(value);
|
|
261
|
+
return Math.round(value / multipleOf) * multipleOf;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function clamp(value: number, min: number, max: number): number {
|
|
265
|
+
return Math.min(Math.max(value, min), max);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function normalizeAspectRatio(aspectRatio: string | undefined): string | undefined {
|
|
269
|
+
const parsed = parseAspectRatio(aspectRatio);
|
|
270
|
+
if (!parsed) return undefined;
|
|
271
|
+
const divisor = getGreatestCommonDivisor(parsed.width, parsed.height);
|
|
272
|
+
return `${parsed.width / divisor}:${parsed.height / divisor}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function getAspectRatioModelInputNames(
|
|
276
|
+
model: WorkflowEditorModel | undefined,
|
|
277
|
+
): AspectRatioModelInputNames {
|
|
278
|
+
if (!model) return {};
|
|
279
|
+
|
|
280
|
+
const widthInputName =
|
|
281
|
+
model.uiConfig?.resolutionComponent?.widthInput ??
|
|
282
|
+
model.inputs?.find((input) => input.name === 'width')?.name;
|
|
283
|
+
const heightInputName =
|
|
284
|
+
model.uiConfig?.resolutionComponent?.heightInput ??
|
|
285
|
+
model.inputs?.find((input) => input.name === 'height')?.name;
|
|
286
|
+
const aspectRatioInputName = model.inputs?.find((input) => input.name === 'aspectRatio')?.name;
|
|
287
|
+
|
|
288
|
+
return { widthInputName, heightInputName, aspectRatioInputName };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function getAspectRatioBounds(
|
|
292
|
+
model: WorkflowEditorModel | undefined,
|
|
293
|
+
{ widthInputName, heightInputName }: Pick<AspectRatioModelInputNames, 'widthInputName' | 'heightInputName'>,
|
|
294
|
+
): AspectRatioBounds | undefined {
|
|
295
|
+
if (!model || !widthInputName || !heightInputName) return undefined;
|
|
296
|
+
|
|
297
|
+
const widthInput = model.inputs?.find((input) => input.name === widthInputName);
|
|
298
|
+
const heightInput = model.inputs?.find((input) => input.name === heightInputName);
|
|
299
|
+
const presets = model.uiConfig?.resolutionComponent?.presets;
|
|
300
|
+
const widthPresets = (presets ?? [])
|
|
301
|
+
.map((preset) => preset.width)
|
|
302
|
+
.filter((value): value is number => typeof value === 'number');
|
|
303
|
+
const heightPresets = (presets ?? [])
|
|
304
|
+
.map((preset) => preset.height)
|
|
305
|
+
.filter((value): value is number => typeof value === 'number');
|
|
306
|
+
const multipleOf = widthInput?.step ?? 8;
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
maxWidth: widthInput?.max ?? (widthPresets.length > 0 ? Math.max(...widthPresets) : 2048),
|
|
310
|
+
minWidth: widthInput?.min ?? (widthPresets.length > 0 ? Math.min(...widthPresets) : 64),
|
|
311
|
+
maxHeight: heightInput?.max ?? (heightPresets.length > 0 ? Math.max(...heightPresets) : 2048),
|
|
312
|
+
minHeight: heightInput?.min ?? (heightPresets.length > 0 ? Math.min(...heightPresets) : 64),
|
|
313
|
+
multipleOf,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function getDimensionsFromAspectRatio(args: {
|
|
318
|
+
aspectRatio: string;
|
|
319
|
+
bounds: AspectRatioBounds;
|
|
320
|
+
}): { width: number; height: number } | undefined {
|
|
321
|
+
const parsed = parseAspectRatio(args.aspectRatio);
|
|
322
|
+
if (!parsed) return undefined;
|
|
323
|
+
|
|
324
|
+
const ratio = parsed.width / parsed.height;
|
|
325
|
+
let width = ratio >= 1 ? args.bounds.maxWidth : Math.round(args.bounds.maxHeight * ratio);
|
|
326
|
+
let height = ratio >= 1 ? Math.round(args.bounds.maxWidth / ratio) : args.bounds.maxHeight;
|
|
327
|
+
|
|
328
|
+
width = makeNumberAMultipleOfX(width, args.bounds.multipleOf);
|
|
329
|
+
height = makeNumberAMultipleOfX(height, args.bounds.multipleOf);
|
|
330
|
+
width = clamp(width, args.bounds.minWidth, args.bounds.maxWidth);
|
|
331
|
+
height = clamp(height, args.bounds.minHeight, args.bounds.maxHeight);
|
|
332
|
+
|
|
333
|
+
return { width, height };
|
|
334
|
+
}
|
|
335
|
+
|
|
175
336
|
function getForEachFlowListInputDescriptor(args: {
|
|
176
337
|
listProvider: WorkflowEditorNode | undefined;
|
|
177
338
|
outputHandle: WorkflowEditorHandleOutput | undefined;
|
|
@@ -444,14 +605,14 @@ function toStaticInput(name: string, type: string, value: unknown): WorkflowEdit
|
|
|
444
605
|
return { name, type, value: value as string | number | string[] | boolean };
|
|
445
606
|
}
|
|
446
607
|
|
|
447
|
-
function
|
|
608
|
+
function getIsFormArray(value: unknown): value is Record<string, unknown>[] {
|
|
448
609
|
return (
|
|
449
610
|
Array.isArray(value) &&
|
|
450
611
|
(value as unknown[]).every((item) => typeof item === 'object' && item !== null && !Array.isArray(item))
|
|
451
612
|
);
|
|
452
613
|
}
|
|
453
614
|
|
|
454
|
-
function
|
|
615
|
+
function getIsModelCompose(model: WorkflowEditorModel | undefined): boolean {
|
|
455
616
|
return !!model?.id && (model.tags ?? []).includes('compose');
|
|
456
617
|
}
|
|
457
618
|
|
|
@@ -469,6 +630,120 @@ function getIsConditionallyExecuted(
|
|
|
469
630
|
});
|
|
470
631
|
}
|
|
471
632
|
|
|
633
|
+
function buildDimensionLookupCEL(args: {
|
|
634
|
+
variable: string;
|
|
635
|
+
presets: Array<{ aspectRatio: string; width: number; height: number }>;
|
|
636
|
+
dimension: 'width' | 'height';
|
|
637
|
+
fallback: number;
|
|
638
|
+
}): string {
|
|
639
|
+
let expression = String(args.fallback);
|
|
640
|
+
for (let i = args.presets.length - 1; i >= 0; i--) {
|
|
641
|
+
const preset = args.presets[i]!;
|
|
642
|
+
expression = `trim(${args.variable}) == '${preset.aspectRatio}' ? ${
|
|
643
|
+
preset[args.dimension]
|
|
644
|
+
} : ${expression}`;
|
|
645
|
+
}
|
|
646
|
+
return expression;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function applyAspectRatioInputs(args: {
|
|
650
|
+
flow: WorkflowEditorFlowItem[];
|
|
651
|
+
flowInputs: WorkflowEditorFlowInput[];
|
|
652
|
+
flowId: string;
|
|
653
|
+
aspectRatioRef: WorkflowEditorFlowInputRef;
|
|
654
|
+
model: WorkflowEditorModel;
|
|
655
|
+
}): void {
|
|
656
|
+
const modelInputNames = getAspectRatioModelInputNames(args.model);
|
|
657
|
+
const modelBounds = getAspectRatioBounds(args.model, modelInputNames);
|
|
658
|
+
const { widthInputName, heightInputName, aspectRatioInputName } = modelInputNames;
|
|
659
|
+
|
|
660
|
+
if (aspectRatioInputName) {
|
|
661
|
+
const transformId = `${args.flowId}_aspectRatio_value`;
|
|
662
|
+
args.flow.push({
|
|
663
|
+
id: transformId,
|
|
664
|
+
type: 'transform',
|
|
665
|
+
logic: { transform: 'trim(aspectRatio)' },
|
|
666
|
+
inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
setOrPushFlowInput(
|
|
670
|
+
args.flowInputs,
|
|
671
|
+
args.flowInputs.findIndex((inp) => inp.name === aspectRatioInputName),
|
|
672
|
+
{
|
|
673
|
+
name: aspectRatioInputName,
|
|
674
|
+
type: 'string',
|
|
675
|
+
ref: { node: transformId, name: 'all' },
|
|
676
|
+
},
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (widthInputName && heightInputName && modelBounds) {
|
|
681
|
+
const presetDimensions = ASPECT_RATIO_PRESETS.flatMap((preset) => {
|
|
682
|
+
const dims = getDimensionsFromAspectRatio({ aspectRatio: preset, bounds: modelBounds });
|
|
683
|
+
if (!dims) return [];
|
|
684
|
+
const normalized = normalizeAspectRatio(preset);
|
|
685
|
+
if (!normalized) return [];
|
|
686
|
+
return [{ aspectRatio: normalized, ...dims }];
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
const fallbackDimensions = presetDimensions.find((preset) => preset.aspectRatio === '1:1') ??
|
|
690
|
+
presetDimensions[0] ?? {
|
|
691
|
+
width: modelBounds.maxWidth,
|
|
692
|
+
height: modelBounds.maxHeight,
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const widthTransformId = `${args.flowId}_aspectRatio_width`;
|
|
696
|
+
const heightTransformId = `${args.flowId}_aspectRatio_height`;
|
|
697
|
+
|
|
698
|
+
args.flow.push({
|
|
699
|
+
id: widthTransformId,
|
|
700
|
+
type: 'transform',
|
|
701
|
+
logic: {
|
|
702
|
+
transform: buildDimensionLookupCEL({
|
|
703
|
+
variable: 'aspectRatio',
|
|
704
|
+
presets: presetDimensions,
|
|
705
|
+
dimension: 'width',
|
|
706
|
+
fallback: fallbackDimensions.width,
|
|
707
|
+
}),
|
|
708
|
+
},
|
|
709
|
+
inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
args.flow.push({
|
|
713
|
+
id: heightTransformId,
|
|
714
|
+
type: 'transform',
|
|
715
|
+
logic: {
|
|
716
|
+
transform: buildDimensionLookupCEL({
|
|
717
|
+
variable: 'aspectRatio',
|
|
718
|
+
presets: presetDimensions,
|
|
719
|
+
dimension: 'height',
|
|
720
|
+
fallback: fallbackDimensions.height,
|
|
721
|
+
}),
|
|
722
|
+
},
|
|
723
|
+
inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
setOrPushFlowInput(
|
|
727
|
+
args.flowInputs,
|
|
728
|
+
args.flowInputs.findIndex((inp) => inp.name === widthInputName),
|
|
729
|
+
{
|
|
730
|
+
name: widthInputName,
|
|
731
|
+
type: 'number',
|
|
732
|
+
ref: { node: widthTransformId, name: 'all' },
|
|
733
|
+
},
|
|
734
|
+
);
|
|
735
|
+
setOrPushFlowInput(
|
|
736
|
+
args.flowInputs,
|
|
737
|
+
args.flowInputs.findIndex((inp) => inp.name === heightInputName),
|
|
738
|
+
{
|
|
739
|
+
name: heightInputName,
|
|
740
|
+
type: 'number',
|
|
741
|
+
ref: { node: heightTransformId, name: 'all' },
|
|
742
|
+
},
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
472
747
|
function getForEachBodyEditorNodeIds(
|
|
473
748
|
forEachId: string,
|
|
474
749
|
forEachEndId: string,
|
|
@@ -528,8 +803,6 @@ function getNodeOutputValue(
|
|
|
528
803
|
return undefined;
|
|
529
804
|
}
|
|
530
805
|
|
|
531
|
-
const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
|
|
532
|
-
|
|
533
806
|
function internalGetConnectedNodes(
|
|
534
807
|
nodes: WorkflowEditorNode[],
|
|
535
808
|
edges: WorkflowEditorEdge[],
|
|
@@ -582,21 +855,36 @@ function internalGetConnectedNodes(
|
|
|
582
855
|
return nodes.filter((n) => idSet.has(n.id));
|
|
583
856
|
}
|
|
584
857
|
|
|
858
|
+
function resolveIfElseConditionField(
|
|
859
|
+
field: string,
|
|
860
|
+
candidateNodes: WorkflowEditorNode[],
|
|
861
|
+
): { nodeId: string; targetHandleId?: string } | undefined {
|
|
862
|
+
const byExactId = candidateNodes.find((n) => n.id === field);
|
|
863
|
+
if (byExactId) return { nodeId: field };
|
|
864
|
+
for (const node of candidateNodes) {
|
|
865
|
+
for (const handle of node.data.outputHandles ?? []) {
|
|
866
|
+
if (`${node.id}_${handle.name}` === field) {
|
|
867
|
+
return { nodeId: node.id, targetHandleId: handle.id };
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return undefined;
|
|
872
|
+
}
|
|
873
|
+
|
|
585
874
|
function normalizeNodes(nodes: WorkflowEditorNode[]): WorkflowEditorNode[] {
|
|
586
875
|
return nodes.map((node) => {
|
|
587
876
|
if (node.type !== 'model' && node.type !== 'llm') return node;
|
|
588
|
-
const hasType = (t: string | string[] | undefined, v: string) =>
|
|
589
|
-
Array.isArray(t) ? t.includes(v) : t === v;
|
|
590
877
|
const inputHandles = (node.data.inputHandles ?? [])
|
|
591
878
|
.flatMap((h) => (h.subHandles ? h.subHandles : [h]))
|
|
592
879
|
.filter(
|
|
593
880
|
(h) =>
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
881
|
+
handleTypeIncludes(h.type, 'conditional') ||
|
|
882
|
+
handleTypeIncludes(h.type, 'aspectRatio') ||
|
|
883
|
+
handleTypeIncludes(h.type, 'prompt') ||
|
|
884
|
+
handleTypeIncludes(h.type, 'image') ||
|
|
885
|
+
handleTypeIncludes(h.type, 'video') ||
|
|
886
|
+
handleTypeIncludes(h.type, '3d') ||
|
|
887
|
+
handleTypeIncludes(h.type, 'audio') ||
|
|
600
888
|
(node.type === 'llm' && h.name === 'textInputs'),
|
|
601
889
|
);
|
|
602
890
|
return { ...node, data: { ...node.data, inputHandles } } as WorkflowEditorNode;
|
|
@@ -615,25 +903,31 @@ function normalizeNodes(nodes: WorkflowEditorNode[]): WorkflowEditorNode[] {
|
|
|
615
903
|
* await client.workflows.update(workflowId, { flow: flow as WorkflowUpdateParams.Flow[] });
|
|
616
904
|
* ```
|
|
617
905
|
*/
|
|
618
|
-
export
|
|
906
|
+
export type ConvertWorkflowEditorToFlowParams = {
|
|
619
907
|
nodes: WorkflowEditorNode[];
|
|
620
908
|
edges: WorkflowEditorEdge[];
|
|
621
909
|
inputKeys: string[];
|
|
622
910
|
/** Optional model resolver — return the model object, 'unknown', or undefined. */
|
|
623
911
|
getModel?: (modelId: string) => WorkflowEditorModel | 'unknown' | undefined;
|
|
624
|
-
}
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
export function convertWorkflowEditorToFlow(
|
|
915
|
+
params: ConvertWorkflowEditorToFlowParams,
|
|
916
|
+
): WorkflowEditorFlowItem[] {
|
|
625
917
|
const { edges, inputKeys } = params;
|
|
626
918
|
const nodes = normalizeNodes(params.nodes);
|
|
627
919
|
|
|
628
920
|
const inputNodeIds = nodes.filter((n) => !!n.data.isInput).map((n) => n.id);
|
|
629
921
|
|
|
630
922
|
const selectedOutputNodes = nodes.filter(
|
|
631
|
-
(n) => (n.type === 'model' || n.type === 'forEachEnd') && !!n.data.isOutput,
|
|
923
|
+
(n) => (n.type === 'model' || n.type === 'forEachEnd' || n.type === 'llm') && !!n.data.isOutput,
|
|
632
924
|
);
|
|
633
925
|
const selectedOutputNodeIds = selectedOutputNodes.map((n) => n.id);
|
|
634
926
|
const selectedOutputModelNodeIds = new Set(
|
|
635
927
|
selectedOutputNodes
|
|
636
|
-
.filter(
|
|
928
|
+
.filter(
|
|
929
|
+
(n): n is WorkflowEditorNode & { type: 'model' | 'llm' } => n.type === 'model' || n.type === 'llm',
|
|
930
|
+
)
|
|
637
931
|
.map((n) => n.id),
|
|
638
932
|
);
|
|
639
933
|
|
|
@@ -675,6 +969,12 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
675
969
|
.map((n) => [n.id, nodeIdToFlowId(n.id)]),
|
|
676
970
|
);
|
|
677
971
|
const nodeIdToInputNameMap = new Map<string, string>(inputNodeIds.map((id) => [id, nodeIdToFlowId(id)]));
|
|
972
|
+
const approvalByApprovedNodeId = buildApprovalByApprovedNodeIdMap({
|
|
973
|
+
nodes,
|
|
974
|
+
edges,
|
|
975
|
+
nodesInSelectedBranches,
|
|
976
|
+
selectedOutputNodeIds,
|
|
977
|
+
});
|
|
678
978
|
const nodeHandleTransformMap = new Map<string, string>();
|
|
679
979
|
|
|
680
980
|
const ctx: SourceRefContext = {
|
|
@@ -751,12 +1051,17 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
751
1051
|
const model = remoteModel === 'unknown' ? undefined : remoteModel;
|
|
752
1052
|
const modelInputs = model?.inputs;
|
|
753
1053
|
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
1054
|
+
const aspectRatioEdges: WorkflowEditorEdge[] = [];
|
|
754
1055
|
|
|
755
1056
|
const edgesByInput = new Map<string, WorkflowEditorEdge[]>();
|
|
756
1057
|
for (const edge of incomingEdges) {
|
|
757
1058
|
if (edge.sourceHandle === conditionalHandleId) continue;
|
|
758
1059
|
const handle = getInputHandle(edge, node.data.inputHandles);
|
|
759
1060
|
if (!handle?.name) continue;
|
|
1061
|
+
if (handleTypeIncludes(handle.type, 'aspectRatio')) {
|
|
1062
|
+
aspectRatioEdges.push(edge);
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
760
1065
|
const existing = edgesByInput.get(handle.name);
|
|
761
1066
|
if (existing) existing.push(edge);
|
|
762
1067
|
else edgesByInput.set(handle.name, [edge]);
|
|
@@ -768,7 +1073,7 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
768
1073
|
const modelInput = modelInputs?.find((inp) => inp.name === key);
|
|
769
1074
|
|
|
770
1075
|
if (modelInput?.type === 'inputs_array') {
|
|
771
|
-
if (!
|
|
1076
|
+
if (!getIsFormArray(value)) continue;
|
|
772
1077
|
const nestedInputs = modelInput.inputs ?? [];
|
|
773
1078
|
const items: WorkflowEditorFlowInput[][] = [];
|
|
774
1079
|
|
|
@@ -787,7 +1092,7 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
787
1092
|
}
|
|
788
1093
|
}
|
|
789
1094
|
const isValid =
|
|
790
|
-
|
|
1095
|
+
getIsModelCompose(model) ? getHasComposeLayerSourceInput(itemInputs) : itemInputs.length > 0;
|
|
791
1096
|
if (isValid) items.push(itemInputs);
|
|
792
1097
|
}
|
|
793
1098
|
if (items.length > 0) flowInputs.push({ name: key, type: 'inputs_array', items });
|
|
@@ -803,6 +1108,19 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
803
1108
|
}
|
|
804
1109
|
}
|
|
805
1110
|
|
|
1111
|
+
if (aspectRatioEdges.length > 0 && model) {
|
|
1112
|
+
const aspectRatioRef = getSourceRef(aspectRatioEdges[0]!, ctx);
|
|
1113
|
+
if (aspectRatioRef) {
|
|
1114
|
+
applyAspectRatioInputs({
|
|
1115
|
+
flow,
|
|
1116
|
+
flowInputs,
|
|
1117
|
+
flowId,
|
|
1118
|
+
aspectRatioRef,
|
|
1119
|
+
model,
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
806
1124
|
for (const [inputName, inputEdges] of edgesByInput) {
|
|
807
1125
|
const existingIndex = flowInputs.findIndex((inp) => inp.name === inputName);
|
|
808
1126
|
const modelInput = modelInputs?.find((inp) => inp.name === inputName);
|
|
@@ -871,7 +1189,7 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
871
1189
|
const isRequired =
|
|
872
1190
|
(targetNode?.type === 'asset' &&
|
|
873
1191
|
!!(targetNode.data as { isRequired?: boolean }).isRequired) ||
|
|
874
|
-
|
|
1192
|
+
getIsModelCompose(model);
|
|
875
1193
|
transformVariables.push({ name: targetNodeKey, isArray: isArr, isRequired });
|
|
876
1194
|
|
|
877
1195
|
if (
|
|
@@ -947,7 +1265,9 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
947
1265
|
modelId: nodeData.modelId,
|
|
948
1266
|
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
949
1267
|
includeOutputsInWorkflowJob:
|
|
950
|
-
node.type === 'model' && selectedOutputModelNodeIds.has(node.id) ?
|
|
1268
|
+
(node.type === 'model' || node.type === 'llm') && selectedOutputModelNodeIds.has(node.id) ?
|
|
1269
|
+
true
|
|
1270
|
+
: undefined,
|
|
951
1271
|
});
|
|
952
1272
|
|
|
953
1273
|
ensureInputVideoTransformsAdded();
|
|
@@ -1011,6 +1331,19 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
1011
1331
|
addVideoFirstLastFrameTransforms(node.id, flowId, node, edges, flow, nodeHandleTransformMap);
|
|
1012
1332
|
}
|
|
1013
1333
|
|
|
1334
|
+
// ------------------------------------
|
|
1335
|
+
// AspectRatio -> transform with ratio literal
|
|
1336
|
+
// ------------------------------------
|
|
1337
|
+
} else if (node.type === 'aspectRatio') {
|
|
1338
|
+
const flowInputs = collectConditionalConnectionInputs(incomingEdges, node.id, ctx);
|
|
1339
|
+
const value = (node.data as { output?: string }).output || '1:1';
|
|
1340
|
+
flow.push({
|
|
1341
|
+
id: flowId,
|
|
1342
|
+
type: 'transform',
|
|
1343
|
+
logic: { transform: `'${escapeCELString(value)}'` },
|
|
1344
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1014
1347
|
// ------------------------------------
|
|
1015
1348
|
// TransformText -> transform (CEL)
|
|
1016
1349
|
// ------------------------------------
|
|
@@ -1084,7 +1417,7 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
1084
1417
|
if (!ref) continue;
|
|
1085
1418
|
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
1086
1419
|
const outHandleName = outHandle?.name ?? 'output';
|
|
1087
|
-
const varName =
|
|
1420
|
+
const varName = `${targetNode.id}_${outHandleName}`;
|
|
1088
1421
|
const isArr =
|
|
1089
1422
|
(targetNode.type === 'asset' && !!(targetNode.data as { isMultiple?: boolean }).isMultiple) ||
|
|
1090
1423
|
targetNode.type === 'groupItems' ||
|
|
@@ -1199,16 +1532,50 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
1199
1532
|
}
|
|
1200
1533
|
}
|
|
1201
1534
|
|
|
1202
|
-
|
|
1535
|
+
const edgesIntoIfElse = edges.filter((e) => e.target === node.id);
|
|
1536
|
+
for (const fieldKey of fieldsUsed) {
|
|
1537
|
+
const resolved = resolveIfElseConditionField(fieldKey, nodes);
|
|
1538
|
+
const fieldNodeId = resolved?.nodeId;
|
|
1539
|
+
if (!fieldNodeId) continue;
|
|
1540
|
+
|
|
1541
|
+
const outputHandleName =
|
|
1542
|
+
resolved?.targetHandleId !== undefined ?
|
|
1543
|
+
nodes
|
|
1544
|
+
.find((n) => n.id === fieldNodeId)
|
|
1545
|
+
?.data.outputHandles?.find((h) => h.id === resolved.targetHandleId)?.name ?? 'output'
|
|
1546
|
+
: 'output';
|
|
1547
|
+
|
|
1548
|
+
const celVarName = getOutputHandleVarName(fieldNodeId, outputHandleName);
|
|
1549
|
+
nodeIdToInputNameMap.set(fieldKey, celVarName);
|
|
1550
|
+
|
|
1551
|
+
const edgeToIfElse =
|
|
1552
|
+
edgesIntoIfElse
|
|
1553
|
+
.filter((e) => e.source === fieldNodeId)
|
|
1554
|
+
.find((e) => {
|
|
1555
|
+
if (resolved.targetHandleId === undefined) return true;
|
|
1556
|
+
return e.sourceHandle === resolved.targetHandleId;
|
|
1557
|
+
}) ?? edgesIntoIfElse.find((e) => e.source === fieldNodeId);
|
|
1558
|
+
|
|
1203
1559
|
if (inputNodeIds.includes(fieldNodeId)) {
|
|
1204
|
-
const
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1560
|
+
const workflowInputName = nodeIdToInputNameMap.get(fieldNodeId);
|
|
1561
|
+
if (workflowInputName) {
|
|
1562
|
+
const ref =
|
|
1563
|
+
edgeToIfElse ?
|
|
1564
|
+
getSourceRef(edgeToIfElse, ctx)
|
|
1565
|
+
: { node: 'workflow' as const, name: workflowInputName };
|
|
1566
|
+
flowInputs.push({
|
|
1567
|
+
name: celVarName,
|
|
1568
|
+
type: 'string',
|
|
1569
|
+
ref: ref ?? { node: 'workflow', name: workflowInputName },
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1207
1572
|
} else {
|
|
1573
|
+
const refFromEdge = edgeToIfElse ? getSourceRef(edgeToIfElse, ctx) : undefined;
|
|
1208
1574
|
const targetFlowId = nodeIdToFlowIdMap.get(fieldNodeId);
|
|
1209
|
-
if (
|
|
1210
|
-
flowInputs.push({ name:
|
|
1211
|
-
|
|
1575
|
+
if (refFromEdge) {
|
|
1576
|
+
flowInputs.push({ name: celVarName, type: 'string', ref: refFromEdge });
|
|
1577
|
+
} else if (targetFlowId) {
|
|
1578
|
+
flowInputs.push({ name: celVarName, type: 'string', ref: { node: targetFlowId, name: 'all' } });
|
|
1212
1579
|
}
|
|
1213
1580
|
}
|
|
1214
1581
|
}
|
|
@@ -1246,5 +1613,97 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
1246
1613
|
forEachFlowItem.loopBodyNodeIds = forEachLoopFlowIds.get(forEachId) ?? [];
|
|
1247
1614
|
}
|
|
1248
1615
|
|
|
1616
|
+
applyApprovalsToFlow({ flow, edges, approvalByApprovedNodeId, nodeIdToFlowIdMap });
|
|
1617
|
+
|
|
1249
1618
|
return flow;
|
|
1250
1619
|
}
|
|
1620
|
+
|
|
1621
|
+
// ---- Approval helpers ----
|
|
1622
|
+
|
|
1623
|
+
interface ApprovalInfo {
|
|
1624
|
+
approvalFlowId: string;
|
|
1625
|
+
approvedFlowId: string;
|
|
1626
|
+
message: string;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
function buildApprovalByApprovedNodeIdMap({
|
|
1630
|
+
nodes,
|
|
1631
|
+
edges,
|
|
1632
|
+
nodesInSelectedBranches,
|
|
1633
|
+
selectedOutputNodeIds,
|
|
1634
|
+
}: {
|
|
1635
|
+
nodes: WorkflowEditorNode[];
|
|
1636
|
+
edges: WorkflowEditorEdge[];
|
|
1637
|
+
nodesInSelectedBranches: Set<string>;
|
|
1638
|
+
selectedOutputNodeIds: string[];
|
|
1639
|
+
}): Map<string, ApprovalInfo> {
|
|
1640
|
+
const result = new Map<string, ApprovalInfo>();
|
|
1641
|
+
for (const approvalNode of nodes) {
|
|
1642
|
+
if (approvalNode.type !== 'approval') continue;
|
|
1643
|
+
const approvalEdge = edges.find(
|
|
1644
|
+
(e) => e.source === approvalNode.id && e.sourceHandle === `${approvalNode.id}-source-approval`,
|
|
1645
|
+
);
|
|
1646
|
+
if (!approvalEdge) continue;
|
|
1647
|
+
const approvedNodeId = approvalEdge.target;
|
|
1648
|
+
if (!nodesInSelectedBranches.has(approvedNodeId) && !selectedOutputNodeIds.includes(approvedNodeId))
|
|
1649
|
+
continue;
|
|
1650
|
+
const approvedNode = nodes.find((n) => n.id === approvedNodeId);
|
|
1651
|
+
if (!approvedNode) continue;
|
|
1652
|
+
const approvedFlowId =
|
|
1653
|
+
approvedNode.type === 'forEachEnd' ?
|
|
1654
|
+
(approvedNode.data as { parentNodeId?: string }).parentNodeId ?
|
|
1655
|
+
nodeIdToFlowId((approvedNode.data as { parentNodeId?: string }).parentNodeId!)
|
|
1656
|
+
: undefined
|
|
1657
|
+
: nodeIdToFlowId(approvedNodeId);
|
|
1658
|
+
if (!approvedFlowId) continue;
|
|
1659
|
+
result.set(approvedNodeId, {
|
|
1660
|
+
approvalFlowId: nodeIdToFlowId(approvalNode.id),
|
|
1661
|
+
approvedFlowId,
|
|
1662
|
+
message: (approvalNode.data as { message?: string }).message ?? '',
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
return result;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function applyApprovalsToFlow({
|
|
1669
|
+
flow,
|
|
1670
|
+
edges,
|
|
1671
|
+
approvalByApprovedNodeId,
|
|
1672
|
+
nodeIdToFlowIdMap,
|
|
1673
|
+
}: {
|
|
1674
|
+
flow: WorkflowEditorFlowItem[];
|
|
1675
|
+
edges: WorkflowEditorEdge[];
|
|
1676
|
+
approvalByApprovedNodeId: Map<string, ApprovalInfo>;
|
|
1677
|
+
nodeIdToFlowIdMap: Map<string, string>;
|
|
1678
|
+
}): void {
|
|
1679
|
+
if (approvalByApprovedNodeId.size === 0) return;
|
|
1680
|
+
|
|
1681
|
+
const flowIdToWorkflowNodeId = new Map<string, string>(
|
|
1682
|
+
[...nodeIdToFlowIdMap.entries()].map(([nodeId, flowId]) => [flowId, nodeId]),
|
|
1683
|
+
);
|
|
1684
|
+
|
|
1685
|
+
for (const flowItem of flow) {
|
|
1686
|
+
const workflowNodeId = flowIdToWorkflowNodeId.get(flowItem.id);
|
|
1687
|
+
if (!workflowNodeId) continue;
|
|
1688
|
+
const dependsOn = new Set(flowItem.dependsOn ?? []);
|
|
1689
|
+
const incomingEdges = edges.filter(
|
|
1690
|
+
(e) =>
|
|
1691
|
+
e.source === workflowNodeId &&
|
|
1692
|
+
!(e.data as { isForEachEndEdge?: boolean } | undefined)?.isForEachEndEdge,
|
|
1693
|
+
);
|
|
1694
|
+
for (const edge of incomingEdges) {
|
|
1695
|
+
const approval = approvalByApprovedNodeId.get(edge.target);
|
|
1696
|
+
if (approval) dependsOn.add(approval.approvalFlowId);
|
|
1697
|
+
}
|
|
1698
|
+
if (dependsOn.size > 0) flowItem.dependsOn = [...dependsOn].sort();
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
for (const approvalInfo of approvalByApprovedNodeId.values()) {
|
|
1702
|
+
flow.push({
|
|
1703
|
+
id: approvalInfo.approvalFlowId,
|
|
1704
|
+
type: 'user-approval',
|
|
1705
|
+
dependsOn: [approvalInfo.approvedFlowId],
|
|
1706
|
+
label: approvalInfo.message,
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
}
|