@scenario-labs/sdk 2.1.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 +21 -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 +235 -27
- package/lib/workflow_converter.js.map +1 -1
- package/lib/workflow_converter.mjs +230 -27
- 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/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 +327 -20
- 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/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,55 @@ 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
|
+
'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;
|
|
95
139
|
};
|
|
96
140
|
|
|
97
141
|
export type WorkflowEditorFlowInputRef = {
|
|
@@ -176,6 +220,119 @@ function getConditionalInputHandleId(nodeId: string): string {
|
|
|
176
220
|
return `${nodeId}-source-conditional`;
|
|
177
221
|
}
|
|
178
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
|
+
|
|
179
336
|
function getForEachFlowListInputDescriptor(args: {
|
|
180
337
|
listProvider: WorkflowEditorNode | undefined;
|
|
181
338
|
outputHandle: WorkflowEditorHandleOutput | undefined;
|
|
@@ -448,14 +605,14 @@ function toStaticInput(name: string, type: string, value: unknown): WorkflowEdit
|
|
|
448
605
|
return { name, type, value: value as string | number | string[] | boolean };
|
|
449
606
|
}
|
|
450
607
|
|
|
451
|
-
function
|
|
608
|
+
function getIsFormArray(value: unknown): value is Record<string, unknown>[] {
|
|
452
609
|
return (
|
|
453
610
|
Array.isArray(value) &&
|
|
454
611
|
(value as unknown[]).every((item) => typeof item === 'object' && item !== null && !Array.isArray(item))
|
|
455
612
|
);
|
|
456
613
|
}
|
|
457
614
|
|
|
458
|
-
function
|
|
615
|
+
function getIsModelCompose(model: WorkflowEditorModel | undefined): boolean {
|
|
459
616
|
return !!model?.id && (model.tags ?? []).includes('compose');
|
|
460
617
|
}
|
|
461
618
|
|
|
@@ -473,6 +630,120 @@ function getIsConditionallyExecuted(
|
|
|
473
630
|
});
|
|
474
631
|
}
|
|
475
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
|
+
|
|
476
747
|
function getForEachBodyEditorNodeIds(
|
|
477
748
|
forEachId: string,
|
|
478
749
|
forEachEndId: string,
|
|
@@ -532,8 +803,6 @@ function getNodeOutputValue(
|
|
|
532
803
|
return undefined;
|
|
533
804
|
}
|
|
534
805
|
|
|
535
|
-
const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
|
|
536
|
-
|
|
537
806
|
function internalGetConnectedNodes(
|
|
538
807
|
nodes: WorkflowEditorNode[],
|
|
539
808
|
edges: WorkflowEditorEdge[],
|
|
@@ -605,18 +874,17 @@ function resolveIfElseConditionField(
|
|
|
605
874
|
function normalizeNodes(nodes: WorkflowEditorNode[]): WorkflowEditorNode[] {
|
|
606
875
|
return nodes.map((node) => {
|
|
607
876
|
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
877
|
const inputHandles = (node.data.inputHandles ?? [])
|
|
611
878
|
.flatMap((h) => (h.subHandles ? h.subHandles : [h]))
|
|
612
879
|
.filter(
|
|
613
880
|
(h) =>
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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') ||
|
|
620
888
|
(node.type === 'llm' && h.name === 'textInputs'),
|
|
621
889
|
);
|
|
622
890
|
return { ...node, data: { ...node.data, inputHandles } } as WorkflowEditorNode;
|
|
@@ -635,25 +903,31 @@ function normalizeNodes(nodes: WorkflowEditorNode[]): WorkflowEditorNode[] {
|
|
|
635
903
|
* await client.workflows.update(workflowId, { flow: flow as WorkflowUpdateParams.Flow[] });
|
|
636
904
|
* ```
|
|
637
905
|
*/
|
|
638
|
-
export
|
|
906
|
+
export type ConvertWorkflowEditorToFlowParams = {
|
|
639
907
|
nodes: WorkflowEditorNode[];
|
|
640
908
|
edges: WorkflowEditorEdge[];
|
|
641
909
|
inputKeys: string[];
|
|
642
910
|
/** Optional model resolver — return the model object, 'unknown', or undefined. */
|
|
643
911
|
getModel?: (modelId: string) => WorkflowEditorModel | 'unknown' | undefined;
|
|
644
|
-
}
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
export function convertWorkflowEditorToFlow(
|
|
915
|
+
params: ConvertWorkflowEditorToFlowParams,
|
|
916
|
+
): WorkflowEditorFlowItem[] {
|
|
645
917
|
const { edges, inputKeys } = params;
|
|
646
918
|
const nodes = normalizeNodes(params.nodes);
|
|
647
919
|
|
|
648
920
|
const inputNodeIds = nodes.filter((n) => !!n.data.isInput).map((n) => n.id);
|
|
649
921
|
|
|
650
922
|
const selectedOutputNodes = nodes.filter(
|
|
651
|
-
(n) => (n.type === 'model' || n.type === 'forEachEnd') && !!n.data.isOutput,
|
|
923
|
+
(n) => (n.type === 'model' || n.type === 'forEachEnd' || n.type === 'llm') && !!n.data.isOutput,
|
|
652
924
|
);
|
|
653
925
|
const selectedOutputNodeIds = selectedOutputNodes.map((n) => n.id);
|
|
654
926
|
const selectedOutputModelNodeIds = new Set(
|
|
655
927
|
selectedOutputNodes
|
|
656
|
-
.filter(
|
|
928
|
+
.filter(
|
|
929
|
+
(n): n is WorkflowEditorNode & { type: 'model' | 'llm' } => n.type === 'model' || n.type === 'llm',
|
|
930
|
+
)
|
|
657
931
|
.map((n) => n.id),
|
|
658
932
|
);
|
|
659
933
|
|
|
@@ -777,12 +1051,17 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
777
1051
|
const model = remoteModel === 'unknown' ? undefined : remoteModel;
|
|
778
1052
|
const modelInputs = model?.inputs;
|
|
779
1053
|
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
1054
|
+
const aspectRatioEdges: WorkflowEditorEdge[] = [];
|
|
780
1055
|
|
|
781
1056
|
const edgesByInput = new Map<string, WorkflowEditorEdge[]>();
|
|
782
1057
|
for (const edge of incomingEdges) {
|
|
783
1058
|
if (edge.sourceHandle === conditionalHandleId) continue;
|
|
784
1059
|
const handle = getInputHandle(edge, node.data.inputHandles);
|
|
785
1060
|
if (!handle?.name) continue;
|
|
1061
|
+
if (handleTypeIncludes(handle.type, 'aspectRatio')) {
|
|
1062
|
+
aspectRatioEdges.push(edge);
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
786
1065
|
const existing = edgesByInput.get(handle.name);
|
|
787
1066
|
if (existing) existing.push(edge);
|
|
788
1067
|
else edgesByInput.set(handle.name, [edge]);
|
|
@@ -794,7 +1073,7 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
794
1073
|
const modelInput = modelInputs?.find((inp) => inp.name === key);
|
|
795
1074
|
|
|
796
1075
|
if (modelInput?.type === 'inputs_array') {
|
|
797
|
-
if (!
|
|
1076
|
+
if (!getIsFormArray(value)) continue;
|
|
798
1077
|
const nestedInputs = modelInput.inputs ?? [];
|
|
799
1078
|
const items: WorkflowEditorFlowInput[][] = [];
|
|
800
1079
|
|
|
@@ -813,7 +1092,7 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
813
1092
|
}
|
|
814
1093
|
}
|
|
815
1094
|
const isValid =
|
|
816
|
-
|
|
1095
|
+
getIsModelCompose(model) ? getHasComposeLayerSourceInput(itemInputs) : itemInputs.length > 0;
|
|
817
1096
|
if (isValid) items.push(itemInputs);
|
|
818
1097
|
}
|
|
819
1098
|
if (items.length > 0) flowInputs.push({ name: key, type: 'inputs_array', items });
|
|
@@ -829,6 +1108,19 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
829
1108
|
}
|
|
830
1109
|
}
|
|
831
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
|
+
|
|
832
1124
|
for (const [inputName, inputEdges] of edgesByInput) {
|
|
833
1125
|
const existingIndex = flowInputs.findIndex((inp) => inp.name === inputName);
|
|
834
1126
|
const modelInput = modelInputs?.find((inp) => inp.name === inputName);
|
|
@@ -897,7 +1189,7 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
897
1189
|
const isRequired =
|
|
898
1190
|
(targetNode?.type === 'asset' &&
|
|
899
1191
|
!!(targetNode.data as { isRequired?: boolean }).isRequired) ||
|
|
900
|
-
|
|
1192
|
+
getIsModelCompose(model);
|
|
901
1193
|
transformVariables.push({ name: targetNodeKey, isArray: isArr, isRequired });
|
|
902
1194
|
|
|
903
1195
|
if (
|
|
@@ -973,7 +1265,9 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
973
1265
|
modelId: nodeData.modelId,
|
|
974
1266
|
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
975
1267
|
includeOutputsInWorkflowJob:
|
|
976
|
-
node.type === 'model' && selectedOutputModelNodeIds.has(node.id) ?
|
|
1268
|
+
(node.type === 'model' || node.type === 'llm') && selectedOutputModelNodeIds.has(node.id) ?
|
|
1269
|
+
true
|
|
1270
|
+
: undefined,
|
|
977
1271
|
});
|
|
978
1272
|
|
|
979
1273
|
ensureInputVideoTransformsAdded();
|
|
@@ -1037,6 +1331,19 @@ export function convertWorkflowEditorToFlow(params: {
|
|
|
1037
1331
|
addVideoFirstLastFrameTransforms(node.id, flowId, node, edges, flow, nodeHandleTransformMap);
|
|
1038
1332
|
}
|
|
1039
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
|
+
|
|
1040
1347
|
// ------------------------------------
|
|
1041
1348
|
// TransformText -> transform (CEL)
|
|
1042
1349
|
// ------------------------------------
|
|
@@ -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'
|
|
@@ -22,6 +23,7 @@ export type WorkflowEditorNodeType =
|
|
|
22
23
|
export const VALID_EDITOR_NODE_TYPES: readonly WorkflowEditorNodeType[] = [
|
|
23
24
|
'text',
|
|
24
25
|
'asset',
|
|
26
|
+
'aspectRatio',
|
|
25
27
|
'model',
|
|
26
28
|
'llm',
|
|
27
29
|
'transformText',
|