@scenario-labs/sdk 3.3.1 → 3.4.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 +25 -0
- package/client.d.mts +4 -4
- package/client.d.mts.map +1 -1
- package/client.d.ts +4 -4
- package/client.d.ts.map +1 -1
- package/client.js.map +1 -1
- package/client.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 +2 -1
- package/index.js.map +1 -1
- package/index.mjs +1 -1
- package/index.mjs.map +1 -1
- package/lib/user_selection_limits.d.mts +13 -0
- package/lib/user_selection_limits.d.mts.map +1 -0
- package/lib/user_selection_limits.d.ts +13 -0
- package/lib/user_selection_limits.d.ts.map +1 -0
- package/lib/user_selection_limits.js +16 -0
- package/lib/user_selection_limits.js.map +1 -0
- package/lib/user_selection_limits.mjs +13 -0
- package/lib/user_selection_limits.mjs.map +1 -0
- package/lib/workflow_converter.d.mts +6 -0
- package/lib/workflow_converter.d.mts.map +1 -1
- package/lib/workflow_converter.d.ts +6 -0
- package/lib/workflow_converter.d.ts.map +1 -1
- package/lib/workflow_converter.js +154 -52
- package/lib/workflow_converter.js.map +1 -1
- package/lib/workflow_converter.mjs +154 -52
- package/lib/workflow_converter.mjs.map +1 -1
- package/lib/workflow_import_validator.d.mts +17 -2
- package/lib/workflow_import_validator.d.mts.map +1 -1
- package/lib/workflow_import_validator.d.ts +17 -2
- package/lib/workflow_import_validator.d.ts.map +1 -1
- package/lib/workflow_import_validator.js +100 -1
- package/lib/workflow_import_validator.js.map +1 -1
- package/lib/workflow_import_validator.mjs +99 -1
- package/lib/workflow_import_validator.mjs.map +1 -1
- package/lib/workflow_validator.d.mts +1 -1
- package/lib/workflow_validator.d.mts.map +1 -1
- package/lib/workflow_validator.d.ts +1 -1
- package/lib/workflow_validator.d.ts.map +1 -1
- package/lib/workflow_validator.js +10 -11
- package/lib/workflow_validator.js.map +1 -1
- package/lib/workflow_validator.mjs +7 -8
- package/lib/workflow_validator.mjs.map +1 -1
- package/package.json +1 -1
- package/resources/assets/assets.d.mts +126 -30
- package/resources/assets/assets.d.mts.map +1 -1
- package/resources/assets/assets.d.ts +126 -30
- package/resources/assets/assets.d.ts.map +1 -1
- package/resources/assets/assets.js +12 -0
- package/resources/assets/assets.js.map +1 -1
- package/resources/assets/assets.mjs +12 -0
- package/resources/assets/assets.mjs.map +1 -1
- package/resources/assets/index.d.mts +1 -1
- package/resources/assets/index.d.mts.map +1 -1
- package/resources/assets/index.d.ts +1 -1
- package/resources/assets/index.d.ts.map +1 -1
- package/resources/assets/index.js.map +1 -1
- package/resources/assets/index.mjs.map +1 -1
- package/resources/generate.d.mts +120 -9
- package/resources/generate.d.mts.map +1 -1
- package/resources/generate.d.ts +120 -9
- package/resources/generate.d.ts.map +1 -1
- package/resources/index.d.mts +2 -2
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +2 -2
- package/resources/index.d.ts.map +1 -1
- package/resources/index.js.map +1 -1
- package/resources/index.mjs.map +1 -1
- package/resources/jobs.d.mts +36 -0
- package/resources/jobs.d.mts.map +1 -1
- package/resources/jobs.d.ts +36 -0
- package/resources/jobs.d.ts.map +1 -1
- package/resources/models/description.d.mts +4 -4
- package/resources/models/description.d.mts.map +1 -1
- package/resources/models/description.d.ts +4 -4
- package/resources/models/description.d.ts.map +1 -1
- package/resources/models/examples.d.mts +40 -6
- package/resources/models/examples.d.mts.map +1 -1
- package/resources/models/examples.d.ts +40 -6
- package/resources/models/examples.d.ts.map +1 -1
- package/resources/models/index.d.mts +1 -1
- package/resources/models/index.d.mts.map +1 -1
- package/resources/models/index.d.ts +1 -1
- package/resources/models/index.d.ts.map +1 -1
- package/resources/models/index.js.map +1 -1
- package/resources/models/index.mjs.map +1 -1
- package/resources/models/models.d.mts +1353 -199
- package/resources/models/models.d.mts.map +1 -1
- package/resources/models/models.d.ts +1353 -199
- package/resources/models/models.d.ts.map +1 -1
- package/resources/models/models.js +13 -0
- package/resources/models/models.js.map +1 -1
- package/resources/models/models.mjs +13 -0
- package/resources/models/models.mjs.map +1 -1
- package/resources/models/train.d.mts +36 -2
- package/resources/models/train.d.mts.map +1 -1
- package/resources/models/train.d.ts +36 -2
- package/resources/models/train.d.ts.map +1 -1
- package/resources/recommendations.d.mts +16 -5
- package/resources/recommendations.d.mts.map +1 -1
- package/resources/recommendations.d.ts +16 -5
- package/resources/recommendations.d.ts.map +1 -1
- package/resources/usages.d.mts +14 -2
- package/resources/usages.d.mts.map +1 -1
- package/resources/usages.d.ts +14 -2
- package/resources/usages.d.ts.map +1 -1
- package/resources/workflows.d.mts +152 -9
- package/resources/workflows.d.mts.map +1 -1
- package/resources/workflows.d.ts +152 -9
- package/resources/workflows.d.ts.map +1 -1
- package/resources/workflows.js +2 -3
- package/resources/workflows.js.map +1 -1
- package/resources/workflows.mjs +2 -3
- package/resources/workflows.mjs.map +1 -1
- package/src/client.ts +8 -0
- package/src/index.ts +1 -0
- package/src/lib/user_selection_limits.ts +14 -0
- package/src/lib/workflow_converter.ts +173 -54
- package/src/lib/workflow_import_validator.ts +116 -1
- package/src/lib/workflow_validator.ts +12 -8
- package/src/resources/assets/assets.ts +164 -4
- package/src/resources/assets/index.ts +2 -0
- package/src/resources/generate.ts +142 -0
- package/src/resources/index.ts +4 -0
- package/src/resources/jobs.ts +42 -0
- package/src/resources/models/description.ts +6 -0
- package/src/resources/models/examples.ts +48 -0
- package/src/resources/models/index.ts +2 -0
- package/src/resources/models/models.ts +1610 -114
- package/src/resources/models/train.ts +42 -0
- package/src/resources/recommendations.ts +18 -3
- package/src/resources/usages.ts +16 -0
- package/src/resources/workflows.ts +176 -9
- 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
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// Converts a workflow editor state (nodes + edges) to a flow array suitable for the Scenario API.
|
|
2
2
|
// Ported from fawkes useConvertWorkflowToApp — pure function, no React dependencies.
|
|
3
|
+
import { USER_SELECTION_MAX_CANDIDATES, USER_SELECTION_MIN_SELECT } from "./user_selection_limits.mjs";
|
|
3
4
|
const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
|
|
4
5
|
const MODEL_CUSTOM_INPUT_HANDLE_SUFFIX = '-source-custom';
|
|
5
|
-
const USER_SELECTION_MIN_SELECT = 1;
|
|
6
|
-
const USER_SELECTION_MAX_SELECT = 100;
|
|
7
6
|
const DEFAULT_SLICE_ASSETS_CHOOSE_MESSAGE = 'Select the items to continue';
|
|
8
7
|
export const ASPECT_RATIO_PRESETS = [
|
|
9
8
|
'21:9',
|
|
@@ -27,7 +26,12 @@ function toCelIdentifier(name) {
|
|
|
27
26
|
return name.replaceAll('-', '_');
|
|
28
27
|
}
|
|
29
28
|
function escapeCELString(value) {
|
|
30
|
-
return value
|
|
29
|
+
return value
|
|
30
|
+
.replace(/\\/g, '\\\\')
|
|
31
|
+
.replace(/'/g, "\\'")
|
|
32
|
+
.replace(/\n/g, '\\n')
|
|
33
|
+
.replace(/\r/g, '\\r')
|
|
34
|
+
.replace(/\t/g, '\\t');
|
|
31
35
|
}
|
|
32
36
|
function formatTextAsCELString(text) {
|
|
33
37
|
return `'${escapeCELString(text)}'`;
|
|
@@ -152,6 +156,7 @@ function getForEachFlowListInputDescriptor(args) {
|
|
|
152
156
|
listProvider?.type === 'text' ||
|
|
153
157
|
listProvider?.type === 'llm' ||
|
|
154
158
|
listProvider?.type === 'transformText' ||
|
|
159
|
+
listProvider?.type === 'joinTexts' ||
|
|
155
160
|
(outputHandle?.type === 'text' && !!outputHandle.isArray);
|
|
156
161
|
return isText ?
|
|
157
162
|
{ name: `text${inputIndex}`, type: 'string_array' }
|
|
@@ -325,17 +330,41 @@ function getIsSliceAssetsItemsSourceHandle(nodeId, sourceHandle) {
|
|
|
325
330
|
return sourceHandle === `${nodeId}-source-items` || sourceHandle === `${nodeId}-source-assets`;
|
|
326
331
|
}
|
|
327
332
|
/**
|
|
328
|
-
*
|
|
329
|
-
* list
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
333
|
+
* True when `expression` treats `varName` as a CEL list via indexing, `size(x)`,
|
|
334
|
+
* or list macros (`.map` / `.filter` / …). This is a converter tolerance for
|
|
335
|
+
* flows that index a list producer (editor First/Last/Nth stores `x[0]` on
|
|
336
|
+
* `isArray` handles; agent/MCP/import flows may index without that flag). It is
|
|
337
|
+
* not Fawkes's preview rule: the webapp binds a list only when the handle has
|
|
338
|
+
* `isArray`. Intentionally narrow so Prompt Builder cannot be steered into
|
|
339
|
+
* list semantics through other CEL list idioms.
|
|
340
|
+
*/
|
|
341
|
+
function expressionUsesListVariable(expression, varName) {
|
|
342
|
+
const escaped = varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
343
|
+
return (new RegExp(`${escaped}\\s*\\[`).test(expression) ||
|
|
344
|
+
new RegExp(`\\bsize\\s*\\(\\s*${escaped}\\s*\\)`).test(expression) ||
|
|
345
|
+
new RegExp(`${escaped}\\s*\\.\\s*(map|filter|all|exists|exists_one)\\s*\\(`).test(expression));
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Prompt Builder (transformText) binds upstream vars for CEL. List producers
|
|
349
|
+
* bind as `string_array` when the source handle is `isArray`, or when the
|
|
350
|
+
* expression indexes/uses the variable as a list (see
|
|
351
|
+
* `expressionUsesListVariable`). Otherwise bare refs stay `string` so the API
|
|
352
|
+
* collapses the list to its first item.
|
|
353
|
+
*
|
|
354
|
+
* Fawkes today omits `isArray` on Slice/Group Items on purpose: only `isArray`
|
|
355
|
+
* handles get an indexed insert (`x[0]`); every other list insert is bare `x`
|
|
356
|
+
* and must collapse to the first item. Adding `isArray` to those handles later
|
|
357
|
+
* requires migrating stored expressions (`x` → `x[0]`) in the same Fawkes
|
|
358
|
+
* release, or both preview and published flows break on `'…' + x`.
|
|
334
359
|
*/
|
|
335
|
-
function getPromptBuilderInputType(targetNode, outHandle, outHandleName) {
|
|
336
|
-
|
|
337
|
-
'
|
|
338
|
-
|
|
360
|
+
function getPromptBuilderInputType(targetNode, outHandle, outHandleName, expression, varName) {
|
|
361
|
+
if (getConcatListSourceDescriptor(targetNode, outHandle, outHandleName).inputType !== 'string_array') {
|
|
362
|
+
return 'string';
|
|
363
|
+
}
|
|
364
|
+
if (outHandle?.isArray || expressionUsesListVariable(expression, varName)) {
|
|
365
|
+
return 'string_array';
|
|
366
|
+
}
|
|
367
|
+
return 'string';
|
|
339
368
|
}
|
|
340
369
|
function getConcatListSourceDescriptor(targetNode, outHandle, outHandleName) {
|
|
341
370
|
const isArray = (targetNode.type === 'asset' && !!targetNode.data.isMultiple) ||
|
|
@@ -552,12 +581,43 @@ function applyAspectRatioInputs(args) {
|
|
|
552
581
|
});
|
|
553
582
|
}
|
|
554
583
|
}
|
|
555
|
-
function
|
|
584
|
+
function buildLoopGraph(nodes, edges) {
|
|
585
|
+
const nodeById = new Map(nodes.map((n) => [n.id, n]));
|
|
586
|
+
const isInternalForEachEdge = (e) => {
|
|
587
|
+
const target = nodeById.get(e.target);
|
|
588
|
+
return (nodeById.get(e.source)?.type === 'forEach' &&
|
|
589
|
+
target?.type === 'forEachEnd' &&
|
|
590
|
+
target.data.parentNodeId === e.source);
|
|
591
|
+
};
|
|
592
|
+
const push = (map, key, value) => {
|
|
593
|
+
const list = map.get(key);
|
|
594
|
+
if (list)
|
|
595
|
+
list.push(value);
|
|
596
|
+
else
|
|
597
|
+
map.set(key, [value]);
|
|
598
|
+
};
|
|
599
|
+
const graph = {
|
|
600
|
+
nodeById,
|
|
601
|
+
consumers: new Map(),
|
|
602
|
+
dataProviders: new Map(),
|
|
603
|
+
dataConsumers: new Map(),
|
|
604
|
+
};
|
|
605
|
+
for (const e of edges) {
|
|
606
|
+
push(graph.consumers, e.target, e.source);
|
|
607
|
+
if (isInternalForEachEdge(e) || nodeById.get(e.source)?.type === 'approval')
|
|
608
|
+
continue;
|
|
609
|
+
push(graph.dataProviders, e.source, e.target);
|
|
610
|
+
push(graph.dataConsumers, e.target, e.source);
|
|
611
|
+
}
|
|
612
|
+
return graph;
|
|
613
|
+
}
|
|
614
|
+
function getForEachBodyEditorNodeIds(forEachId, forEachEndId, edges, graph) {
|
|
556
615
|
const bodyNodeIds = new Set();
|
|
557
616
|
const queue = [];
|
|
558
617
|
const escaped = forEachId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
618
|
+
const itemHandle = new RegExp(`^${escaped}-output-\\d+$`);
|
|
559
619
|
for (const edge of edges) {
|
|
560
|
-
if (edge.target === forEachId && edge.targetHandle
|
|
620
|
+
if (edge.target === forEachId && edge.targetHandle && itemHandle.test(edge.targetHandle)) {
|
|
561
621
|
queue.push(edge.source);
|
|
562
622
|
}
|
|
563
623
|
}
|
|
@@ -566,13 +626,12 @@ function getForEachBodyEditorNodeIds(forEachId, forEachEndId, edges, nodes) {
|
|
|
566
626
|
if (nodeId === forEachEndId || bodyNodeIds.has(nodeId))
|
|
567
627
|
continue;
|
|
568
628
|
bodyNodeIds.add(nodeId);
|
|
569
|
-
for (const
|
|
570
|
-
if (
|
|
571
|
-
queue.push(
|
|
572
|
-
}
|
|
629
|
+
for (const consumer of graph.consumers.get(nodeId) ?? []) {
|
|
630
|
+
if (!bodyNodeIds.has(consumer) && consumer !== forEachEndId)
|
|
631
|
+
queue.push(consumer);
|
|
573
632
|
}
|
|
574
633
|
}
|
|
575
|
-
addRandomizeClosureToBody(bodyNodeIds, forEachEndId,
|
|
634
|
+
addRandomizeClosureToBody(bodyNodeIds, forEachEndId, graph);
|
|
576
635
|
return bodyNodeIds;
|
|
577
636
|
}
|
|
578
637
|
/**
|
|
@@ -586,28 +645,34 @@ function getForEachBodyEditorNodeIds(forEachId, forEachEndId, edges, nodes) {
|
|
|
586
645
|
* for an UNLOCKED randomize node we mutate `bodyNodeIds` in place to also include
|
|
587
646
|
* that node plus the deterministic chain down to the body, so the runtime clones
|
|
588
647
|
* it per iteration and each iteration re-draws. We only pull it in when *every*
|
|
589
|
-
* path out of it lands in the body — if it also feeds a
|
|
590
|
-
* cloning it would break that external consumer, so we
|
|
648
|
+
* path out of it lands in the body or in this loop's results — if it also feeds a
|
|
649
|
+
* node outside the loop, cloning it would break that external consumer, so we
|
|
650
|
+
* leave it shared.
|
|
651
|
+
*
|
|
652
|
+
* Loop nodes (any for-each or loop end outside the body) stop both walks: a
|
|
653
|
+
* loop's list is computed once, before the loop runs, so neither a loop nor what
|
|
654
|
+
* feeds its list may be cloned with another loop's body. A for-each in its own
|
|
655
|
+
* body would even expand again on every iteration. A node that reaches one stays
|
|
656
|
+
* shared.
|
|
591
657
|
*/
|
|
592
|
-
function addRandomizeClosureToBody(bodyNodeIds, forEachEndId,
|
|
593
|
-
const
|
|
658
|
+
function addRandomizeClosureToBody(bodyNodeIds, forEachEndId, graph) {
|
|
659
|
+
const isLoopNode = (id) => {
|
|
660
|
+
const type = graph.nodeById.get(id)?.type;
|
|
661
|
+
return type === 'forEach' || type === 'forEachEnd';
|
|
662
|
+
};
|
|
594
663
|
const isUnlockedRandomize = (id) => {
|
|
595
|
-
const data = nodeById.get(id)?.data;
|
|
664
|
+
const data = graph.nodeById.get(id)?.data;
|
|
596
665
|
return !!data && getSliceAssetsMode(data) === 'randomize' && data.isSeedLocked !== true;
|
|
597
666
|
};
|
|
598
|
-
//
|
|
599
|
-
// on `source` reads an output handle on `target`.
|
|
600
|
-
const providersOf = (id) => edges.filter((e) => e.source === id).map((e) => e.target);
|
|
601
|
-
const consumersOf = (id) => edges.filter((e) => e.target === id).map((e) => e.source);
|
|
602
|
-
// Every node strictly upstream of the body (its transitive input providers).
|
|
667
|
+
// The nodes that feed the body without going through a loop node.
|
|
603
668
|
const upstream = new Set();
|
|
604
669
|
const upstreamQueue = [...bodyNodeIds];
|
|
605
670
|
while (upstreamQueue.length > 0) {
|
|
606
|
-
for (const provider of
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
671
|
+
for (const provider of graph.dataProviders.get(upstreamQueue.pop()) ?? []) {
|
|
672
|
+
if (bodyNodeIds.has(provider) || upstream.has(provider) || isLoopNode(provider))
|
|
673
|
+
continue;
|
|
674
|
+
upstream.add(provider);
|
|
675
|
+
upstreamQueue.push(provider);
|
|
611
676
|
}
|
|
612
677
|
}
|
|
613
678
|
for (const randomId of upstream) {
|
|
@@ -619,18 +684,17 @@ function addRandomizeClosureToBody(bodyNodeIds, forEachEndId, edges, nodes) {
|
|
|
619
684
|
let escapesLoop = false;
|
|
620
685
|
while (chainQueue.length > 0) {
|
|
621
686
|
const id = chainQueue.pop();
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
const consumers =
|
|
626
|
-
|
|
627
|
-
|
|
687
|
+
// The body and this loop's results are where a per-iteration draw belongs.
|
|
688
|
+
if (bodyNodeIds.has(id) || id === forEachEndId || chain.has(id))
|
|
689
|
+
continue;
|
|
690
|
+
const consumers = graph.dataConsumers.get(id) ?? [];
|
|
691
|
+
// Another loop, or a dead end outside the loop: the draw must stay shared.
|
|
692
|
+
if (isLoopNode(id) || consumers.length === 0) {
|
|
693
|
+
escapesLoop = true;
|
|
628
694
|
break;
|
|
629
695
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
chainQueue.push(consumer);
|
|
633
|
-
}
|
|
696
|
+
chain.add(id);
|
|
697
|
+
chainQueue.push(...consumers);
|
|
634
698
|
}
|
|
635
699
|
if (!escapesLoop)
|
|
636
700
|
for (const id of chain)
|
|
@@ -842,13 +906,14 @@ export function convertWorkflowEditorToFlow(params) {
|
|
|
842
906
|
const forEachBodyEditorNodeIds = new Map();
|
|
843
907
|
const forEachLoopFlowIds = new Map();
|
|
844
908
|
const forEachFlowItemRefs = new Map();
|
|
909
|
+
const loopGraph = buildLoopGraph(nodes, edges);
|
|
845
910
|
for (const node of sortedNodes) {
|
|
846
911
|
if (node.type !== 'forEach')
|
|
847
912
|
continue;
|
|
848
913
|
const endNode = nodes.find((n) => n.type === 'forEachEnd' && n.data.parentNodeId === node.id);
|
|
849
914
|
if (!endNode)
|
|
850
915
|
continue;
|
|
851
|
-
forEachBodyEditorNodeIds.set(node.id, getForEachBodyEditorNodeIds(node.id, endNode.id, edges,
|
|
916
|
+
forEachBodyEditorNodeIds.set(node.id, getForEachBodyEditorNodeIds(node.id, endNode.id, edges, loopGraph));
|
|
852
917
|
forEachLoopFlowIds.set(node.id, []);
|
|
853
918
|
}
|
|
854
919
|
// Pre-populate video firstFrame/lastFrame transform IDs for input nodes
|
|
@@ -1200,9 +1265,10 @@ export function convertWorkflowEditorToFlow(params) {
|
|
|
1200
1265
|
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
1201
1266
|
const outHandleName = outHandle?.name ?? 'output';
|
|
1202
1267
|
const targetFlowId = nodeIdToFlowIdMap.get(targetNode.id) ?? targetNode.id;
|
|
1268
|
+
const varName = `${targetFlowId}_${outHandleName}`;
|
|
1203
1269
|
flowInputs.push({
|
|
1204
|
-
name:
|
|
1205
|
-
type: getPromptBuilderInputType(targetNode, outHandle, outHandleName),
|
|
1270
|
+
name: varName,
|
|
1271
|
+
type: getPromptBuilderInputType(targetNode, outHandle, outHandleName, value, varName),
|
|
1206
1272
|
ref,
|
|
1207
1273
|
});
|
|
1208
1274
|
}
|
|
@@ -1245,6 +1311,37 @@ export function convertWorkflowEditorToFlow(params) {
|
|
|
1245
1311
|
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1246
1312
|
});
|
|
1247
1313
|
// ------------------------------------
|
|
1314
|
+
// JoinTexts -> join string list into a single string
|
|
1315
|
+
// ------------------------------------
|
|
1316
|
+
}
|
|
1317
|
+
else if (node.type === 'joinTexts') {
|
|
1318
|
+
const flowInputs = [];
|
|
1319
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
1320
|
+
let listSourceVarName;
|
|
1321
|
+
for (const edge of incomingEdges) {
|
|
1322
|
+
if (edge.sourceHandle === conditionalHandleId)
|
|
1323
|
+
continue;
|
|
1324
|
+
const targetNode = nodes.find((n) => n.id === edge.target);
|
|
1325
|
+
if (!targetNode)
|
|
1326
|
+
continue;
|
|
1327
|
+
const ref = getSourceRef(edge, ctx);
|
|
1328
|
+
if (!ref)
|
|
1329
|
+
continue;
|
|
1330
|
+
const varName = toCelIdentifier(targetNode.id);
|
|
1331
|
+
flowInputs.push({ name: varName, type: 'string_array', ref });
|
|
1332
|
+
listSourceVarName = varName;
|
|
1333
|
+
}
|
|
1334
|
+
flowInputs.push(...collectConditionalConnectionInputs(incomingEdges, node.id, ctx));
|
|
1335
|
+
const delim = node.data.joinDelimiter ?? ', ';
|
|
1336
|
+
flow.push({
|
|
1337
|
+
id: flowId,
|
|
1338
|
+
type: 'transform',
|
|
1339
|
+
logic: {
|
|
1340
|
+
transform: `${listSourceVarName ?? '[]'}.join(${formatTextAsCELString(delim)})`,
|
|
1341
|
+
},
|
|
1342
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1343
|
+
});
|
|
1344
|
+
// ------------------------------------
|
|
1248
1345
|
// GroupItems -> array concatenation
|
|
1249
1346
|
// ------------------------------------
|
|
1250
1347
|
}
|
|
@@ -1285,7 +1382,9 @@ export function convertWorkflowEditorToFlow(params) {
|
|
|
1285
1382
|
else {
|
|
1286
1383
|
const { variables, flowInputs } = collectConcatVarsAndInputs(itemsEdges, ctx, getOutputHandleVarName);
|
|
1287
1384
|
const count = clampUserSelectionCount(parseInt(String(sliceData.count ?? ''), 10) || 1);
|
|
1288
|
-
const message = sliceData.message
|
|
1385
|
+
const message = typeof sliceData.message === 'string' ?
|
|
1386
|
+
sliceData.message.trim() || DEFAULT_SLICE_ASSETS_CHOOSE_MESSAGE
|
|
1387
|
+
: DEFAULT_SLICE_ASSETS_CHOOSE_MESSAGE;
|
|
1289
1388
|
const arrayType = getUserSelectionArrayType(flowInputs);
|
|
1290
1389
|
// Single array-typed source can feed user-selection directly; scalars must be wrapped.
|
|
1291
1390
|
if (variables.length === 1 && variables[0]?.isArray && flowInputs[0]?.ref) {
|
|
@@ -1417,6 +1516,9 @@ export function convertWorkflowEditorToFlow(params) {
|
|
|
1417
1516
|
fieldsUsed.add(condition.field);
|
|
1418
1517
|
}
|
|
1419
1518
|
}
|
|
1519
|
+
// CEL conditions need hyphen→underscore identifiers, but the shared input-name
|
|
1520
|
+
// map must keep the real workflow input keys (`text-1`, not `text_1`).
|
|
1521
|
+
const celFieldNameMap = new Map();
|
|
1420
1522
|
const edgesIntoIfElse = edges.filter((e) => e.target === node.id);
|
|
1421
1523
|
for (const fieldKey of fieldsUsed) {
|
|
1422
1524
|
const resolved = resolveIfElseConditionField(fieldKey, nodes);
|
|
@@ -1429,7 +1531,7 @@ export function convertWorkflowEditorToFlow(params) {
|
|
|
1429
1531
|
?.data.outputHandles?.find((h) => h.id === resolved.targetHandleId)?.name ?? 'output'
|
|
1430
1532
|
: 'output';
|
|
1431
1533
|
const celVarName = getOutputHandleVarName(fieldNodeId, outputHandleName);
|
|
1432
|
-
|
|
1534
|
+
celFieldNameMap.set(fieldKey, celVarName);
|
|
1433
1535
|
const edgeToIfElse = edgesIntoIfElse
|
|
1434
1536
|
.filter((e) => e.source === fieldNodeId)
|
|
1435
1537
|
.find((e) => {
|
|
@@ -1463,7 +1565,7 @@ export function convertWorkflowEditorToFlow(params) {
|
|
|
1463
1565
|
}
|
|
1464
1566
|
const cases = [];
|
|
1465
1567
|
for (let i = 0; i < conditionBlocks.length; i++) {
|
|
1466
|
-
const condition = conditionBlockToCEL(conditionBlocks[i],
|
|
1568
|
+
const condition = conditionBlockToCEL(conditionBlocks[i], celFieldNameMap);
|
|
1467
1569
|
if (condition)
|
|
1468
1570
|
cases.push({ condition, value: String(i + 2) });
|
|
1469
1571
|
}
|
|
@@ -1570,7 +1672,7 @@ function getSliceAssetsMode(data) {
|
|
|
1570
1672
|
return data.isRandomize ? 'randomize' : 'default';
|
|
1571
1673
|
}
|
|
1572
1674
|
function clampUserSelectionCount(count) {
|
|
1573
|
-
return Math.min(
|
|
1675
|
+
return Math.min(USER_SELECTION_MAX_CANDIDATES, Math.max(USER_SELECTION_MIN_SELECT, count));
|
|
1574
1676
|
}
|
|
1575
1677
|
function getUserSelectionArrayType(flowInputs) {
|
|
1576
1678
|
const hasFile = flowInputs.some((input) => input.type === 'file' || input.type === 'file_array');
|