@scenario-labs/sdk 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- 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/lib/workflow_converter.d.mts +47 -2
- package/lib/workflow_converter.d.mts.map +1 -1
- package/lib/workflow_converter.d.ts +47 -2
- package/lib/workflow_converter.d.ts.map +1 -1
- package/lib/workflow_converter.js +253 -28
- package/lib/workflow_converter.js.map +1 -1
- package/lib/workflow_converter.mjs +248 -28
- 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 +2 -0
- package/lib/workflow_import_validator.js.map +1 -1
- package/lib/workflow_import_validator.mjs +2 -0
- package/lib/workflow_import_validator.mjs.map +1 -1
- package/package.json +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/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/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/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/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/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/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/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/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/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/src/index.ts +9 -0
- package/src/lib/workflow_converter.ts +347 -21
- package/src/lib/workflow_import_validator.ts +5 -1
- 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/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;
|
|
@@ -86,12 +87,57 @@ export type WorkflowEditorModelInput = {
|
|
|
86
87
|
name: string;
|
|
87
88
|
type: string;
|
|
88
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;
|
|
89
99
|
};
|
|
90
100
|
|
|
91
101
|
export type WorkflowEditorModel = {
|
|
92
102
|
id?: string;
|
|
93
103
|
inputs?: WorkflowEditorModelInput[];
|
|
94
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
|
+
'21:9',
|
|
117
|
+
'16:9',
|
|
118
|
+
'3:2',
|
|
119
|
+
'4:3',
|
|
120
|
+
'5:4',
|
|
121
|
+
'1:1',
|
|
122
|
+
'4:5',
|
|
123
|
+
'3:4',
|
|
124
|
+
'2:3',
|
|
125
|
+
'9:16',
|
|
126
|
+
'9:21',
|
|
127
|
+
] as const;
|
|
128
|
+
|
|
129
|
+
export type AspectRatioBounds = {
|
|
130
|
+
minWidth: number;
|
|
131
|
+
maxWidth: number;
|
|
132
|
+
minHeight: number;
|
|
133
|
+
maxHeight: number;
|
|
134
|
+
multipleOf: number;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export type AspectRatioModelInputNames = {
|
|
138
|
+
widthInputName?: string | undefined;
|
|
139
|
+
heightInputName?: string | undefined;
|
|
140
|
+
aspectRatioInputName?: string | undefined;
|
|
95
141
|
};
|
|
96
142
|
|
|
97
143
|
export type WorkflowEditorFlowInputRef = {
|
|
@@ -176,6 +222,125 @@ function getConditionalInputHandleId(nodeId: string): string {
|
|
|
176
222
|
return `${nodeId}-source-conditional`;
|
|
177
223
|
}
|
|
178
224
|
|
|
225
|
+
function handleTypeIncludes(handleType: string | string[] | undefined, value: string): boolean {
|
|
226
|
+
if (Array.isArray(handleType)) return handleType.includes(value);
|
|
227
|
+
return handleType === value;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
type ParsedAspectRatio = { width: number; height: number };
|
|
231
|
+
|
|
232
|
+
function parseAspectRatio(aspectRatio: string | undefined): ParsedAspectRatio | undefined {
|
|
233
|
+
if (!aspectRatio) return undefined;
|
|
234
|
+
const [rawWidth, rawHeight] = aspectRatio.split(':');
|
|
235
|
+
const width = Number(rawWidth);
|
|
236
|
+
const height = Number(rawHeight);
|
|
237
|
+
if (
|
|
238
|
+
!Number.isFinite(width) ||
|
|
239
|
+
!Number.isFinite(height) ||
|
|
240
|
+
width <= 0 ||
|
|
241
|
+
height <= 0 ||
|
|
242
|
+
!Number.isInteger(width) ||
|
|
243
|
+
!Number.isInteger(height)
|
|
244
|
+
) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
return { width, height };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getGreatestCommonDivisor(a: number, b: number): number {
|
|
251
|
+
let x = Math.abs(Math.trunc(a));
|
|
252
|
+
let y = Math.abs(Math.trunc(b));
|
|
253
|
+
while (y) {
|
|
254
|
+
const temp = y;
|
|
255
|
+
y = x % y;
|
|
256
|
+
x = temp;
|
|
257
|
+
}
|
|
258
|
+
return x || 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function makeNumberAMultipleOfX(value: number, multipleOf: number): number {
|
|
262
|
+
if (multipleOf <= 0) return Math.round(value);
|
|
263
|
+
return Math.round(value / multipleOf) * multipleOf;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function clamp(value: number, min: number, max: number): number {
|
|
267
|
+
return Math.min(Math.max(value, min), max);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function normalizeAspectRatio(aspectRatio: string | undefined): string | undefined {
|
|
271
|
+
const parsed = parseAspectRatio(aspectRatio);
|
|
272
|
+
if (!parsed) return undefined;
|
|
273
|
+
const divisor = getGreatestCommonDivisor(parsed.width, parsed.height);
|
|
274
|
+
const normalizedAspectRatio = `${parsed.width / divisor}:${parsed.height / divisor}`;
|
|
275
|
+
return (
|
|
276
|
+
{
|
|
277
|
+
'3:7': '9:21',
|
|
278
|
+
'7:3': '21:9',
|
|
279
|
+
}[normalizedAspectRatio] ?? normalizedAspectRatio
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function getAspectRatioModelInputNames(
|
|
284
|
+
model: WorkflowEditorModel | undefined,
|
|
285
|
+
): AspectRatioModelInputNames {
|
|
286
|
+
if (!model) return {};
|
|
287
|
+
|
|
288
|
+
const widthInputName =
|
|
289
|
+
model.uiConfig?.resolutionComponent?.widthInput ??
|
|
290
|
+
model.inputs?.find((input) => input.name === 'width')?.name;
|
|
291
|
+
const heightInputName =
|
|
292
|
+
model.uiConfig?.resolutionComponent?.heightInput ??
|
|
293
|
+
model.inputs?.find((input) => input.name === 'height')?.name;
|
|
294
|
+
const aspectRatioInputName = model.inputs?.find((input) => input.name === 'aspectRatio')?.name;
|
|
295
|
+
|
|
296
|
+
return { widthInputName, heightInputName, aspectRatioInputName };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function getAspectRatioBounds(
|
|
300
|
+
model: WorkflowEditorModel | undefined,
|
|
301
|
+
{ widthInputName, heightInputName }: Pick<AspectRatioModelInputNames, 'widthInputName' | 'heightInputName'>,
|
|
302
|
+
): AspectRatioBounds | undefined {
|
|
303
|
+
if (!model || !widthInputName || !heightInputName) return undefined;
|
|
304
|
+
|
|
305
|
+
const widthInput = model.inputs?.find((input) => input.name === widthInputName);
|
|
306
|
+
const heightInput = model.inputs?.find((input) => input.name === heightInputName);
|
|
307
|
+
const presets = model.uiConfig?.resolutionComponent?.presets;
|
|
308
|
+
const widthPresets = (presets ?? [])
|
|
309
|
+
.map((preset) => preset.width)
|
|
310
|
+
.filter((value): value is number => typeof value === 'number');
|
|
311
|
+
const heightPresets = (presets ?? [])
|
|
312
|
+
.map((preset) => preset.height)
|
|
313
|
+
.filter((value): value is number => typeof value === 'number');
|
|
314
|
+
const multipleOf = widthInput?.step ?? 8;
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
maxWidth: widthInput?.max ?? (widthPresets.length > 0 ? Math.max(...widthPresets) : 2048),
|
|
318
|
+
minWidth: widthInput?.min ?? (widthPresets.length > 0 ? Math.min(...widthPresets) : 64),
|
|
319
|
+
maxHeight: heightInput?.max ?? (heightPresets.length > 0 ? Math.max(...heightPresets) : 2048),
|
|
320
|
+
minHeight: heightInput?.min ?? (heightPresets.length > 0 ? Math.min(...heightPresets) : 64),
|
|
321
|
+
multipleOf,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function getDimensionsFromAspectRatio(args: {
|
|
326
|
+
aspectRatio: string;
|
|
327
|
+
bounds: AspectRatioBounds;
|
|
328
|
+
}): { width: number; height: number } | undefined {
|
|
329
|
+
const parsed = parseAspectRatio(args.aspectRatio);
|
|
330
|
+
if (!parsed) return undefined;
|
|
331
|
+
|
|
332
|
+
const ratio = parsed.width / parsed.height;
|
|
333
|
+
let width = ratio >= 1 ? args.bounds.maxWidth : Math.round(args.bounds.maxHeight * ratio);
|
|
334
|
+
let height = ratio >= 1 ? Math.round(args.bounds.maxWidth / ratio) : args.bounds.maxHeight;
|
|
335
|
+
|
|
336
|
+
width = makeNumberAMultipleOfX(width, args.bounds.multipleOf);
|
|
337
|
+
height = makeNumberAMultipleOfX(height, args.bounds.multipleOf);
|
|
338
|
+
width = clamp(width, args.bounds.minWidth, args.bounds.maxWidth);
|
|
339
|
+
height = clamp(height, args.bounds.minHeight, args.bounds.maxHeight);
|
|
340
|
+
|
|
341
|
+
return { width, height };
|
|
342
|
+
}
|
|
343
|
+
|
|
179
344
|
function getForEachFlowListInputDescriptor(args: {
|
|
180
345
|
listProvider: WorkflowEditorNode | undefined;
|
|
181
346
|
outputHandle: WorkflowEditorHandleOutput | undefined;
|
|
@@ -448,14 +613,14 @@ function toStaticInput(name: string, type: string, value: unknown): WorkflowEdit
|
|
|
448
613
|
return { name, type, value: value as string | number | string[] | boolean };
|
|
449
614
|
}
|
|
450
615
|
|
|
451
|
-
function
|
|
616
|
+
function getIsFormArray(value: unknown): value is Record<string, unknown>[] {
|
|
452
617
|
return (
|
|
453
618
|
Array.isArray(value) &&
|
|
454
619
|
(value as unknown[]).every((item) => typeof item === 'object' && item !== null && !Array.isArray(item))
|
|
455
620
|
);
|
|
456
621
|
}
|
|
457
622
|
|
|
458
|
-
function
|
|
623
|
+
function getIsModelCompose(model: WorkflowEditorModel | undefined): boolean {
|
|
459
624
|
return !!model?.id && (model.tags ?? []).includes('compose');
|
|
460
625
|
}
|
|
461
626
|
|
|
@@ -473,6 +638,120 @@ function getIsConditionallyExecuted(
|
|
|
473
638
|
});
|
|
474
639
|
}
|
|
475
640
|
|
|
641
|
+
function buildDimensionLookupCEL(args: {
|
|
642
|
+
variable: string;
|
|
643
|
+
presets: Array<{ aspectRatio: string; width: number; height: number }>;
|
|
644
|
+
dimension: 'width' | 'height';
|
|
645
|
+
fallback: number;
|
|
646
|
+
}): string {
|
|
647
|
+
let expression = String(args.fallback);
|
|
648
|
+
for (let i = args.presets.length - 1; i >= 0; i--) {
|
|
649
|
+
const preset = args.presets[i]!;
|
|
650
|
+
expression = `trim(${args.variable}) == '${preset.aspectRatio}' ? ${
|
|
651
|
+
preset[args.dimension]
|
|
652
|
+
} : ${expression}`;
|
|
653
|
+
}
|
|
654
|
+
return expression;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function applyAspectRatioInputs(args: {
|
|
658
|
+
flow: WorkflowEditorFlowItem[];
|
|
659
|
+
flowInputs: WorkflowEditorFlowInput[];
|
|
660
|
+
flowId: string;
|
|
661
|
+
aspectRatioRef: WorkflowEditorFlowInputRef;
|
|
662
|
+
model: WorkflowEditorModel;
|
|
663
|
+
}): void {
|
|
664
|
+
const modelInputNames = getAspectRatioModelInputNames(args.model);
|
|
665
|
+
const modelBounds = getAspectRatioBounds(args.model, modelInputNames);
|
|
666
|
+
const { widthInputName, heightInputName, aspectRatioInputName } = modelInputNames;
|
|
667
|
+
|
|
668
|
+
if (aspectRatioInputName) {
|
|
669
|
+
const transformId = `${args.flowId}_aspectRatio_value`;
|
|
670
|
+
args.flow.push({
|
|
671
|
+
id: transformId,
|
|
672
|
+
type: 'transform',
|
|
673
|
+
logic: { transform: 'trim(aspectRatio)' },
|
|
674
|
+
inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
setOrPushFlowInput(
|
|
678
|
+
args.flowInputs,
|
|
679
|
+
args.flowInputs.findIndex((inp) => inp.name === aspectRatioInputName),
|
|
680
|
+
{
|
|
681
|
+
name: aspectRatioInputName,
|
|
682
|
+
type: 'string',
|
|
683
|
+
ref: { node: transformId, name: 'all' },
|
|
684
|
+
},
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (widthInputName && heightInputName && modelBounds) {
|
|
689
|
+
const presetDimensions = ASPECT_RATIO_PRESETS.flatMap((preset) => {
|
|
690
|
+
const dims = getDimensionsFromAspectRatio({ aspectRatio: preset, bounds: modelBounds });
|
|
691
|
+
if (!dims) return [];
|
|
692
|
+
const normalized = normalizeAspectRatio(preset);
|
|
693
|
+
if (!normalized) return [];
|
|
694
|
+
return [{ aspectRatio: normalized, ...dims }];
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
const fallbackDimensions = presetDimensions.find((preset) => preset.aspectRatio === '1:1') ??
|
|
698
|
+
presetDimensions[0] ?? {
|
|
699
|
+
width: modelBounds.maxWidth,
|
|
700
|
+
height: modelBounds.maxHeight,
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
const widthTransformId = `${args.flowId}_aspectRatio_width`;
|
|
704
|
+
const heightTransformId = `${args.flowId}_aspectRatio_height`;
|
|
705
|
+
|
|
706
|
+
args.flow.push({
|
|
707
|
+
id: widthTransformId,
|
|
708
|
+
type: 'transform',
|
|
709
|
+
logic: {
|
|
710
|
+
transform: buildDimensionLookupCEL({
|
|
711
|
+
variable: 'aspectRatio',
|
|
712
|
+
presets: presetDimensions,
|
|
713
|
+
dimension: 'width',
|
|
714
|
+
fallback: fallbackDimensions.width,
|
|
715
|
+
}),
|
|
716
|
+
},
|
|
717
|
+
inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
args.flow.push({
|
|
721
|
+
id: heightTransformId,
|
|
722
|
+
type: 'transform',
|
|
723
|
+
logic: {
|
|
724
|
+
transform: buildDimensionLookupCEL({
|
|
725
|
+
variable: 'aspectRatio',
|
|
726
|
+
presets: presetDimensions,
|
|
727
|
+
dimension: 'height',
|
|
728
|
+
fallback: fallbackDimensions.height,
|
|
729
|
+
}),
|
|
730
|
+
},
|
|
731
|
+
inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
setOrPushFlowInput(
|
|
735
|
+
args.flowInputs,
|
|
736
|
+
args.flowInputs.findIndex((inp) => inp.name === widthInputName),
|
|
737
|
+
{
|
|
738
|
+
name: widthInputName,
|
|
739
|
+
type: 'number',
|
|
740
|
+
ref: { node: widthTransformId, name: 'all' },
|
|
741
|
+
},
|
|
742
|
+
);
|
|
743
|
+
setOrPushFlowInput(
|
|
744
|
+
args.flowInputs,
|
|
745
|
+
args.flowInputs.findIndex((inp) => inp.name === heightInputName),
|
|
746
|
+
{
|
|
747
|
+
name: heightInputName,
|
|
748
|
+
type: 'number',
|
|
749
|
+
ref: { node: heightTransformId, name: 'all' },
|
|
750
|
+
},
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
476
755
|
function getForEachBodyEditorNodeIds(
|
|
477
756
|
forEachId: string,
|
|
478
757
|
forEachEndId: string,
|
|
@@ -532,8 +811,6 @@ function getNodeOutputValue(
|
|
|
532
811
|
return undefined;
|
|
533
812
|
}
|
|
534
813
|
|
|
535
|
-
const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
|
|
536
|
-
|
|
537
814
|
function internalGetConnectedNodes(
|
|
538
815
|
nodes: WorkflowEditorNode[],
|
|
539
816
|
edges: WorkflowEditorEdge[],
|
|
@@ -605,18 +882,17 @@ function resolveIfElseConditionField(
|
|
|
605
882
|
function normalizeNodes(nodes: WorkflowEditorNode[]): WorkflowEditorNode[] {
|
|
606
883
|
return nodes.map((node) => {
|
|
607
884
|
if (node.type !== 'model' && node.type !== 'llm') return node;
|
|
608
|
-
const hasType = (t: string | string[] | undefined, v: string) =>
|
|
609
|
-
Array.isArray(t) ? t.includes(v) : t === v;
|
|
610
885
|
const inputHandles = (node.data.inputHandles ?? [])
|
|
611
886
|
.flatMap((h) => (h.subHandles ? h.subHandles : [h]))
|
|
612
887
|
.filter(
|
|
613
888
|
(h) =>
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
889
|
+
handleTypeIncludes(h.type, 'conditional') ||
|
|
890
|
+
handleTypeIncludes(h.type, 'aspectRatio') ||
|
|
891
|
+
handleTypeIncludes(h.type, 'prompt') ||
|
|
892
|
+
handleTypeIncludes(h.type, 'image') ||
|
|
893
|
+
handleTypeIncludes(h.type, 'video') ||
|
|
894
|
+
handleTypeIncludes(h.type, '3d') ||
|
|
895
|
+
handleTypeIncludes(h.type, 'audio') ||
|
|
620
896
|
(node.type === 'llm' && h.name === 'textInputs'),
|
|
621
897
|
);
|
|
622
898
|
return { ...node, data: { ...node.data, inputHandles } } as WorkflowEditorNode;
|
|
@@ -635,25 +911,31 @@ function normalizeNodes(nodes: WorkflowEditorNode[]): WorkflowEditorNode[] {
|
|
|
635
911
|
* await client.workflows.update(workflowId, { flow: flow as WorkflowUpdateParams.Flow[] });
|
|
636
912
|
* ```
|
|
637
913
|
*/
|
|
638
|
-
export
|
|
914
|
+
export type ConvertWorkflowEditorToFlowParams = {
|
|
639
915
|
nodes: WorkflowEditorNode[];
|
|
640
916
|
edges: WorkflowEditorEdge[];
|
|
641
917
|
inputKeys: string[];
|
|
642
918
|
/** Optional model resolver — return the model object, 'unknown', or undefined. */
|
|
643
919
|
getModel?: (modelId: string) => WorkflowEditorModel | 'unknown' | undefined;
|
|
644
|
-
}
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
export function convertWorkflowEditorToFlow(
|
|
923
|
+
params: ConvertWorkflowEditorToFlowParams,
|
|
924
|
+
): WorkflowEditorFlowItem[] {
|
|
645
925
|
const { edges, inputKeys } = params;
|
|
646
926
|
const nodes = normalizeNodes(params.nodes);
|
|
647
927
|
|
|
648
928
|
const inputNodeIds = nodes.filter((n) => !!n.data.isInput).map((n) => n.id);
|
|
649
929
|
|
|
650
930
|
const selectedOutputNodes = nodes.filter(
|
|
651
|
-
(n) => (n.type === 'model' || n.type === 'forEachEnd') && !!n.data.isOutput,
|
|
931
|
+
(n) => (n.type === 'model' || n.type === 'forEachEnd' || n.type === 'llm') && !!n.data.isOutput,
|
|
652
932
|
);
|
|
653
933
|
const selectedOutputNodeIds = selectedOutputNodes.map((n) => n.id);
|
|
654
934
|
const selectedOutputModelNodeIds = new Set(
|
|
655
935
|
selectedOutputNodes
|
|
656
|
-
.filter(
|
|
936
|
+
.filter(
|
|
937
|
+
(n): n is WorkflowEditorNode & { type: 'model' | 'llm' } => n.type === 'model' || n.type === 'llm',
|
|
938
|
+
)
|
|
657
939
|
.map((n) => n.id),
|
|
658
940
|
);
|
|
659
941
|
|
|
@@ -777,12 +1059,17 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
777
1059
|
const model = remoteModel === 'unknown' ? undefined : remoteModel;
|
|
778
1060
|
const modelInputs = model?.inputs;
|
|
779
1061
|
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
1062
|
+
const aspectRatioEdges: WorkflowEditorEdge[] = [];
|
|
780
1063
|
|
|
781
1064
|
const edgesByInput = new Map<string, WorkflowEditorEdge[]>();
|
|
782
1065
|
for (const edge of incomingEdges) {
|
|
783
1066
|
if (edge.sourceHandle === conditionalHandleId) continue;
|
|
784
1067
|
const handle = getInputHandle(edge, node.data.inputHandles);
|
|
785
1068
|
if (!handle?.name) continue;
|
|
1069
|
+
if (handleTypeIncludes(handle.type, 'aspectRatio')) {
|
|
1070
|
+
aspectRatioEdges.push(edge);
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
786
1073
|
const existing = edgesByInput.get(handle.name);
|
|
787
1074
|
if (existing) existing.push(edge);
|
|
788
1075
|
else edgesByInput.set(handle.name, [edge]);
|
|
@@ -794,7 +1081,7 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
794
1081
|
const modelInput = modelInputs?.find((inp) => inp.name === key);
|
|
795
1082
|
|
|
796
1083
|
if (modelInput?.type === 'inputs_array') {
|
|
797
|
-
if (!
|
|
1084
|
+
if (!getIsFormArray(value)) continue;
|
|
798
1085
|
const nestedInputs = modelInput.inputs ?? [];
|
|
799
1086
|
const items: WorkflowEditorFlowInput[][] = [];
|
|
800
1087
|
|
|
@@ -813,7 +1100,7 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
813
1100
|
}
|
|
814
1101
|
}
|
|
815
1102
|
const isValid =
|
|
816
|
-
|
|
1103
|
+
getIsModelCompose(model) ? getHasComposeLayerSourceInput(itemInputs) : itemInputs.length > 0;
|
|
817
1104
|
if (isValid) items.push(itemInputs);
|
|
818
1105
|
}
|
|
819
1106
|
if (items.length > 0) flowInputs.push({ name: key, type: 'inputs_array', items });
|
|
@@ -829,6 +1116,19 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
829
1116
|
}
|
|
830
1117
|
}
|
|
831
1118
|
|
|
1119
|
+
if (aspectRatioEdges.length > 0 && model) {
|
|
1120
|
+
const aspectRatioRef = getSourceRef(aspectRatioEdges[0]!, ctx);
|
|
1121
|
+
if (aspectRatioRef) {
|
|
1122
|
+
applyAspectRatioInputs({
|
|
1123
|
+
flow,
|
|
1124
|
+
flowInputs,
|
|
1125
|
+
flowId,
|
|
1126
|
+
aspectRatioRef,
|
|
1127
|
+
model,
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
832
1132
|
for (const [inputName, inputEdges] of edgesByInput) {
|
|
833
1133
|
const existingIndex = flowInputs.findIndex((inp) => inp.name === inputName);
|
|
834
1134
|
const modelInput = modelInputs?.find((inp) => inp.name === inputName);
|
|
@@ -897,7 +1197,7 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
897
1197
|
const isRequired =
|
|
898
1198
|
(targetNode?.type === 'asset' &&
|
|
899
1199
|
!!(targetNode.data as { isRequired?: boolean }).isRequired) ||
|
|
900
|
-
|
|
1200
|
+
getIsModelCompose(model);
|
|
901
1201
|
transformVariables.push({ name: targetNodeKey, isArray: isArr, isRequired });
|
|
902
1202
|
|
|
903
1203
|
if (
|
|
@@ -973,7 +1273,9 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
973
1273
|
modelId: nodeData.modelId,
|
|
974
1274
|
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
975
1275
|
includeOutputsInWorkflowJob:
|
|
976
|
-
node.type === 'model' && selectedOutputModelNodeIds.has(node.id) ?
|
|
1276
|
+
(node.type === 'model' || node.type === 'llm') && selectedOutputModelNodeIds.has(node.id) ?
|
|
1277
|
+
true
|
|
1278
|
+
: undefined,
|
|
977
1279
|
});
|
|
978
1280
|
|
|
979
1281
|
ensureInputVideoTransformsAdded();
|
|
@@ -1037,6 +1339,19 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
1037
1339
|
addVideoFirstLastFrameTransforms(node.id, flowId, node, edges, flow, nodeHandleTransformMap);
|
|
1038
1340
|
}
|
|
1039
1341
|
|
|
1342
|
+
// ------------------------------------
|
|
1343
|
+
// AspectRatio -> transform with ratio literal
|
|
1344
|
+
// ------------------------------------
|
|
1345
|
+
} else if (node.type === 'aspectRatio') {
|
|
1346
|
+
const flowInputs = collectConditionalConnectionInputs(incomingEdges, node.id, ctx);
|
|
1347
|
+
const value = (node.data as { output?: string }).output || '1:1';
|
|
1348
|
+
flow.push({
|
|
1349
|
+
id: flowId,
|
|
1350
|
+
type: 'transform',
|
|
1351
|
+
logic: { transform: `'${escapeCELString(value)}'` },
|
|
1352
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1040
1355
|
// ------------------------------------
|
|
1041
1356
|
// TransformText -> transform (CEL)
|
|
1042
1357
|
// ------------------------------------
|
|
@@ -1303,7 +1618,18 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
1303
1618
|
|
|
1304
1619
|
// Populate loopBodyNodeIds on each forEach flow item
|
|
1305
1620
|
for (const [forEachId, forEachFlowItem] of forEachFlowItemRefs) {
|
|
1306
|
-
|
|
1621
|
+
const loopFlowIds = forEachLoopFlowIds.get(forEachId) ?? [];
|
|
1622
|
+
// Approval nodes attached to a node inside the loop body belong to the loop too,
|
|
1623
|
+
// but they are appended to the flow separately and never tracked above.
|
|
1624
|
+
const bodyEditorNodeIds = forEachBodyEditorNodeIds.get(forEachId);
|
|
1625
|
+
if (bodyEditorNodeIds) {
|
|
1626
|
+
for (const [approvedNodeId, approvalInfo] of approvalByApprovedNodeId) {
|
|
1627
|
+
if (bodyEditorNodeIds.has(approvedNodeId) && !loopFlowIds.includes(approvalInfo.approvalFlowId)) {
|
|
1628
|
+
loopFlowIds.push(approvalInfo.approvalFlowId);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
forEachFlowItem.loopBodyNodeIds = loopFlowIds;
|
|
1307
1633
|
}
|
|
1308
1634
|
|
|
1309
1635
|
applyApprovalsToFlow({ flow, edges, approvalByApprovedNodeId, nodeIdToFlowIdMap });
|
|
@@ -8,6 +8,7 @@ export class WorkflowImportError extends Error {
|
|
|
8
8
|
export type WorkflowEditorNodeType =
|
|
9
9
|
| 'text'
|
|
10
10
|
| 'asset'
|
|
11
|
+
| 'aspectRatio'
|
|
11
12
|
| 'model'
|
|
12
13
|
| 'llm'
|
|
13
14
|
| 'transformText'
|
|
@@ -17,11 +18,13 @@ export type WorkflowEditorNodeType =
|
|
|
17
18
|
| 'sliceAssets'
|
|
18
19
|
| 'forEach'
|
|
19
20
|
| 'forEachEnd'
|
|
20
|
-
| 'stickyNote'
|
|
21
|
+
| 'stickyNote'
|
|
22
|
+
| 'approval';
|
|
21
23
|
|
|
22
24
|
export const VALID_EDITOR_NODE_TYPES: readonly WorkflowEditorNodeType[] = [
|
|
23
25
|
'text',
|
|
24
26
|
'asset',
|
|
27
|
+
'aspectRatio',
|
|
25
28
|
'model',
|
|
26
29
|
'llm',
|
|
27
30
|
'transformText',
|
|
@@ -32,6 +35,7 @@ export const VALID_EDITOR_NODE_TYPES: readonly WorkflowEditorNodeType[] = [
|
|
|
32
35
|
'forEach',
|
|
33
36
|
'forEachEnd',
|
|
34
37
|
'stickyNote',
|
|
38
|
+
'approval',
|
|
35
39
|
];
|
|
36
40
|
|
|
37
41
|
export interface WorkflowExportData {
|