@scenario-labs/sdk 1.2.0 → 2.1.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 +53 -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.js +51 -38
- package/client.js.map +1 -1
- package/client.mjs +51 -39
- 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.js +7 -7
- package/core/api-promise.js.map +1 -1
- package/core/api-promise.mjs +7 -7
- package/core/api-promise.mjs.map +1 -1
- package/core/error.js +6 -0
- package/core/error.js.map +1 -1
- package/core/error.mjs +6 -0
- package/core/error.mjs.map +1 -1
- package/core/pagination.d.mts.map +1 -1
- package/core/pagination.d.ts.map +1 -1
- package/core/pagination.js +24 -6
- package/core/pagination.js.map +1 -1
- package/core/pagination.mjs +24 -6
- package/core/pagination.mjs.map +1 -1
- package/core/resource.d.mts.map +1 -1
- package/core/resource.d.ts.map +1 -1
- package/core/resource.js +1 -0
- package/core/resource.js.map +1 -1
- package/core/resource.mjs +1 -0
- package/core/resource.mjs.map +1 -1
- package/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 -0
- package/index.d.mts.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +3 -1
- package/index.js.map +1 -1
- package/index.mjs +1 -0
- package/index.mjs.map +1 -1
- package/internal/detect-platform.js +1 -1
- package/internal/detect-platform.js.map +1 -1
- package/internal/detect-platform.mjs +1 -1
- package/internal/detect-platform.mjs.map +1 -1
- package/internal/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.js +1 -1
- package/internal/qs/stringify.js.map +1 -1
- package/internal/qs/stringify.mjs +1 -1
- package/internal/qs/stringify.mjs.map +1 -1
- package/internal/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.js +2 -2
- package/internal/to-file.js.map +1 -1
- package/internal/to-file.mjs +2 -2
- package/internal/to-file.mjs.map +1 -1
- package/internal/tslib.js +0 -18
- package/internal/tslib.mjs +1 -17
- package/internal/types.d.mts +10 -10
- package/internal/types.d.mts.map +1 -1
- package/internal/types.d.ts +10 -10
- 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.js +2 -0
- package/internal/utils/log.js.map +1 -1
- package/internal/utils/log.mjs +2 -0
- package/internal/utils/log.mjs.map +1 -1
- package/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.js +3 -3
- package/lib/job.js.map +1 -1
- package/lib/job.mjs +3 -3
- package/lib/job.mjs.map +1 -1
- package/lib/models.d.mts.map +1 -1
- package/lib/models.d.ts.map +1 -1
- package/lib/models.js +1 -4
- package/lib/models.js.map +1 -1
- package/lib/models.mjs +1 -4
- package/lib/models.mjs.map +1 -1
- package/lib/scenario.d.mts.map +1 -1
- package/lib/scenario.d.ts.map +1 -1
- package/lib/scenario.js +6 -9
- package/lib/scenario.js.map +1 -1
- package/lib/scenario.mjs +6 -9
- package/lib/scenario.mjs.map +1 -1
- package/lib/upload.d.mts.map +1 -1
- package/lib/upload.d.ts.map +1 -1
- package/lib/upload.js +6 -6
- package/lib/upload.js.map +1 -1
- package/lib/upload.mjs +6 -6
- package/lib/upload.mjs.map +1 -1
- package/lib/workflow.d.mts.map +1 -1
- package/lib/workflow.d.ts.map +1 -1
- package/lib/workflow.mjs.map +1 -1
- package/lib/workflow_converter.d.mts +185 -0
- package/lib/workflow_converter.d.mts.map +1 -0
- package/lib/workflow_converter.d.ts +185 -0
- package/lib/workflow_converter.d.ts.map +1 -0
- package/lib/workflow_converter.js +1102 -0
- package/lib/workflow_converter.js.map +1 -0
- package/lib/workflow_converter.mjs +1099 -0
- package/lib/workflow_converter.mjs.map +1 -0
- 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 +12 -0
- package/resources/assets/assets.d.mts.map +1 -1
- package/resources/assets/assets.d.ts +12 -0
- package/resources/assets/assets.d.ts.map +1 -1
- package/resources/assets/assets.js +1 -4
- package/resources/assets/assets.js.map +1 -1
- package/resources/assets/assets.mjs +1 -4
- 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.js +2 -5
- package/resources/collections/collections.js.map +1 -1
- package/resources/collections/collections.mjs +2 -5
- package/resources/collections/collections.mjs.map +1 -1
- package/resources/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 +96 -0
- package/resources/generate.d.mts.map +1 -1
- package/resources/generate.d.ts +96 -0
- package/resources/generate.d.ts.map +1 -1
- package/resources/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 +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/jobs.mjs.map +1 -1
- package/resources/models/description.d.mts.map +1 -1
- package/resources/models/description.d.ts.map +1 -1
- package/resources/models/description.mjs.map +1 -1
- package/resources/models/examples.d.mts +24 -0
- package/resources/models/examples.d.mts.map +1 -1
- package/resources/models/examples.d.ts +24 -0
- package/resources/models/examples.d.ts.map +1 -1
- package/resources/models/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 +42 -0
- package/resources/models/models.d.mts.map +1 -1
- package/resources/models/models.d.ts +42 -0
- package/resources/models/models.d.ts.map +1 -1
- package/resources/models/models.js +4 -7
- package/resources/models/models.js.map +1 -1
- package/resources/models/models.mjs +4 -7
- package/resources/models/models.mjs.map +1 -1
- package/resources/models/train.d.mts +24 -0
- package/resources/models/train.d.mts.map +1 -1
- package/resources/models/train.d.ts +24 -0
- package/resources/models/train.d.ts.map +1 -1
- package/resources/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.js +1 -4
- package/resources/pricing/pricing.js.map +1 -1
- package/resources/pricing/pricing.mjs +1 -4
- 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.map +1 -1
- 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.map +1 -1
- package/resources/uploads.d.ts.map +1 -1
- package/resources/uploads.mjs.map +1 -1
- package/resources/usages.d.mts +23 -0
- package/resources/usages.d.mts.map +1 -1
- package/resources/usages.d.ts +23 -0
- package/resources/usages.d.ts.map +1 -1
- package/resources/usages.mjs.map +1 -1
- package/resources/workflows.d.mts +132 -0
- package/resources/workflows.d.mts.map +1 -1
- package/resources/workflows.d.ts +132 -0
- package/resources/workflows.d.ts.map +1 -1
- package/resources/workflows.js +4 -7
- package/resources/workflows.js.map +1 -1
- package/resources/workflows.mjs +4 -7
- 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 +14 -0
- package/src/internal/types.ts +6 -8
- package/src/internal/utils/log.ts +2 -0
- package/src/lib/workflow_converter.ts +1402 -0
- package/src/resources/assets/assets.ts +14 -0
- package/src/resources/generate.ts +112 -0
- package/src/resources/jobs.ts +42 -0
- package/src/resources/models/examples.ts +28 -0
- package/src/resources/models/models.ts +49 -0
- package/src/resources/models/train.ts +28 -0
- package/src/resources/usages.ts +26 -0
- package/src/resources/workflows.ts +154 -0
- 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
|
@@ -0,0 +1,1402 @@
|
|
|
1
|
+
// Converts a workflow editor state (nodes + edges) to a flow array suitable for the Scenario API.
|
|
2
|
+
// Ported from fawkes useConvertWorkflowToApp — pure function, no React dependencies.
|
|
3
|
+
|
|
4
|
+
// ---- Public types ----
|
|
5
|
+
|
|
6
|
+
export type WorkflowEditorHandleInput = {
|
|
7
|
+
id: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
type?: string | string[];
|
|
11
|
+
subHandles?: WorkflowEditorHandleInput[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type WorkflowEditorHandleOutput = {
|
|
15
|
+
id: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
type?: string;
|
|
18
|
+
isArray?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type NodeCommonData = {
|
|
22
|
+
isInput?: boolean;
|
|
23
|
+
isOutput?: boolean;
|
|
24
|
+
inputHandles?: WorkflowEditorHandleInput[];
|
|
25
|
+
outputHandles?: WorkflowEditorHandleOutput[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type WorkflowEditorNode =
|
|
29
|
+
| {
|
|
30
|
+
id: string;
|
|
31
|
+
type: 'model';
|
|
32
|
+
data: NodeCommonData & { modelId?: string; type?: string; form?: Record<string, unknown> };
|
|
33
|
+
}
|
|
34
|
+
| { id: string; type: 'llm'; data: NodeCommonData & { modelId?: string; form?: Record<string, unknown> } }
|
|
35
|
+
| { id: string; type: 'text'; data: NodeCommonData & { value?: string } }
|
|
36
|
+
| {
|
|
37
|
+
id: string;
|
|
38
|
+
type: 'asset';
|
|
39
|
+
data: NodeCommonData & {
|
|
40
|
+
type?: string;
|
|
41
|
+
value?: string | string[];
|
|
42
|
+
isMultiple?: boolean;
|
|
43
|
+
isRequired?: boolean;
|
|
44
|
+
valueFirstFrame?: string;
|
|
45
|
+
valueLastFrame?: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
| { id: string; type: 'transformText'; data: NodeCommonData & { value?: string } }
|
|
49
|
+
| { id: string; type: 'splitText'; data: NodeCommonData & { splitDelimiter?: string } }
|
|
50
|
+
| { id: string; type: 'groupItems'; data: NodeCommonData & { value?: string } }
|
|
51
|
+
| {
|
|
52
|
+
id: string;
|
|
53
|
+
type: 'sliceAssets';
|
|
54
|
+
data: NodeCommonData & { from?: number | string; count?: number | string };
|
|
55
|
+
}
|
|
56
|
+
| { id: string; type: 'forEach'; data: NodeCommonData }
|
|
57
|
+
| { id: string; type: 'forEachEnd'; data: NodeCommonData & { parentNodeId?: string } }
|
|
58
|
+
| {
|
|
59
|
+
id: string;
|
|
60
|
+
type: 'ifElse';
|
|
61
|
+
data: NodeCommonData & { conditionBlocks?: WorkflowEditorConditionBlock[] };
|
|
62
|
+
}
|
|
63
|
+
| { id: string; type: 'approval'; data: NodeCommonData & { message?: string } }
|
|
64
|
+
| { id: string; type: string; data: NodeCommonData };
|
|
65
|
+
|
|
66
|
+
export type WorkflowEditorEdge = {
|
|
67
|
+
source: string;
|
|
68
|
+
target: string;
|
|
69
|
+
sourceHandle?: string | null;
|
|
70
|
+
targetHandle?: string | null;
|
|
71
|
+
data?: { isForEachEndEdge?: boolean } | undefined;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type WorkflowEditorCondition = {
|
|
75
|
+
field: string | undefined;
|
|
76
|
+
operator: string;
|
|
77
|
+
value?: string | string[];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type WorkflowEditorConditionBlock = {
|
|
81
|
+
conditions: WorkflowEditorCondition[];
|
|
82
|
+
logic: 'and' | 'or';
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type WorkflowEditorModelInput = {
|
|
86
|
+
name: string;
|
|
87
|
+
type: string;
|
|
88
|
+
inputs?: Array<{ name: string; type: string }>;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type WorkflowEditorModel = {
|
|
92
|
+
id?: string;
|
|
93
|
+
inputs?: WorkflowEditorModelInput[];
|
|
94
|
+
tags?: string[];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type WorkflowEditorFlowInputRef = {
|
|
98
|
+
equal?: string | undefined;
|
|
99
|
+
node?: string | undefined;
|
|
100
|
+
conditional?: string[] | undefined;
|
|
101
|
+
name?: string | undefined;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export type WorkflowEditorFlowInput = {
|
|
105
|
+
name: string;
|
|
106
|
+
type: string;
|
|
107
|
+
ref?: WorkflowEditorFlowInputRef | undefined;
|
|
108
|
+
value?: string | number | string[] | boolean | undefined;
|
|
109
|
+
items?: WorkflowEditorFlowInput[][] | undefined;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type WorkflowEditorFlowItem = {
|
|
113
|
+
id: string;
|
|
114
|
+
type: string;
|
|
115
|
+
modelId?: string | undefined;
|
|
116
|
+
inputs?: WorkflowEditorFlowInput[] | undefined;
|
|
117
|
+
logic?:
|
|
118
|
+
| {
|
|
119
|
+
transform?: string | undefined;
|
|
120
|
+
cases?: { condition: string; value: string }[] | undefined;
|
|
121
|
+
default?: string | undefined;
|
|
122
|
+
}
|
|
123
|
+
| undefined;
|
|
124
|
+
logicType?: 'if-else' | undefined;
|
|
125
|
+
loopBodyNodeIds?: string[] | undefined;
|
|
126
|
+
includeOutputsInWorkflowJob?: true | undefined;
|
|
127
|
+
dependsOn?: string[] | undefined;
|
|
128
|
+
label?: string | undefined;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// ---- Internal types ----
|
|
132
|
+
|
|
133
|
+
type ConcatVariable = { name: string; isArray: boolean; isRequired: boolean };
|
|
134
|
+
|
|
135
|
+
type SourceRefContext = {
|
|
136
|
+
nodes: WorkflowEditorNode[];
|
|
137
|
+
inputNodeIds: string[];
|
|
138
|
+
nodeIdToInputNameMap: Map<string, string>;
|
|
139
|
+
nodeIdToFlowIdMap: Map<string, string>;
|
|
140
|
+
nodeHandleTransformMap?: Map<string, string>;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// ---- Utility functions ----
|
|
144
|
+
|
|
145
|
+
function nodeIdToFlowId(nodeId: string): string {
|
|
146
|
+
return nodeId.replace(/\s/g, '');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function escapeCELString(value: string): string {
|
|
150
|
+
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function formatTextAsCELString(text: string): string {
|
|
154
|
+
return `'${text.replace(/'/g, "\\'").replace(/\n/g, '\\n')}'`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function buildConcatArrayTransform(variables: ConcatVariable[]): string {
|
|
158
|
+
if (variables.length === 0) return '[]';
|
|
159
|
+
return variables
|
|
160
|
+
.map((v) => {
|
|
161
|
+
const expr = v.isArray ? v.name : `[${v.name}]`;
|
|
162
|
+
return v.isRequired ? expr : `(exists('${v.name}') ? ${expr} : [])`;
|
|
163
|
+
})
|
|
164
|
+
.join(' + ');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function convertValueToList(value: string): string[] {
|
|
168
|
+
return value
|
|
169
|
+
.replace(/\[|\]/g, '')
|
|
170
|
+
.split('+')
|
|
171
|
+
.map((v) => v.trim())
|
|
172
|
+
.filter(Boolean);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getConditionalInputHandleId(nodeId: string): string {
|
|
176
|
+
return `${nodeId}-source-conditional`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getForEachFlowListInputDescriptor(args: {
|
|
180
|
+
listProvider: WorkflowEditorNode | undefined;
|
|
181
|
+
outputHandle: WorkflowEditorHandleOutput | undefined;
|
|
182
|
+
inputIndex: number;
|
|
183
|
+
}): { name: string; type: 'file_array' | 'string_array' } {
|
|
184
|
+
const { listProvider, outputHandle, inputIndex } = args;
|
|
185
|
+
const isText =
|
|
186
|
+
listProvider?.type === 'splitText' ||
|
|
187
|
+
listProvider?.type === 'text' ||
|
|
188
|
+
listProvider?.type === 'llm' ||
|
|
189
|
+
listProvider?.type === 'transformText' ||
|
|
190
|
+
(outputHandle?.type === 'text' && !!outputHandle.isArray);
|
|
191
|
+
return isText ?
|
|
192
|
+
{ name: `text${inputIndex}`, type: 'string_array' }
|
|
193
|
+
: { name: `image${inputIndex}`, type: 'file_array' };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getForEachIterationRefName(
|
|
197
|
+
outputHandle: WorkflowEditorHandleOutput | undefined,
|
|
198
|
+
outputIndex: string,
|
|
199
|
+
): string {
|
|
200
|
+
return outputHandle?.type === 'text' ? `text${outputIndex}` : `image${outputIndex}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function getHasComposeLayerSourceInput(layerInputs: { name: string }[]): boolean {
|
|
204
|
+
return layerInputs.some((inp) => inp.name === 'source');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function conditionToCelExpression(condition: WorkflowEditorCondition): string {
|
|
208
|
+
if (!condition.field) return 'false';
|
|
209
|
+
const { field, operator, value } = condition;
|
|
210
|
+
|
|
211
|
+
function escStr(s: string) {
|
|
212
|
+
return `'${s.replace(/'/g, "''")}'`;
|
|
213
|
+
}
|
|
214
|
+
function escRx(s: string) {
|
|
215
|
+
return s.replace(/[.+*?[\]()^$|\\]/g, '\\$&');
|
|
216
|
+
}
|
|
217
|
+
function fmtVal(v: string | string[] | undefined): string {
|
|
218
|
+
if (v === undefined) return "''";
|
|
219
|
+
if (Array.isArray(v)) return `[${v.map((x) => escStr(String(x))).join(', ')}]`;
|
|
220
|
+
const n = Number(v);
|
|
221
|
+
if (!isNaN(n) && v !== '' && String(n) === String(v).trim()) return String(n);
|
|
222
|
+
return escStr(String(v));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
switch (operator) {
|
|
226
|
+
case 'isEmpty':
|
|
227
|
+
return `${field} == null || size(${field}) == 0 || (type(${field}) != list && trim(${field}) == "")`;
|
|
228
|
+
case 'isNotEmpty':
|
|
229
|
+
return `${field} != null && size(${field}) > 0 && (type(${field}) == list || trim(${field}) != "")`;
|
|
230
|
+
case 'equals':
|
|
231
|
+
return `trim(${field}) == ${fmtVal(value)}`;
|
|
232
|
+
case 'notEquals':
|
|
233
|
+
return `trim(${field}) != ${fmtVal(value)}`;
|
|
234
|
+
case 'contains':
|
|
235
|
+
return `${field}.matches('.*${escRx(String(value || '')).replace(/'/g, "''")}.*')`;
|
|
236
|
+
case 'notContains':
|
|
237
|
+
return `!${field}.matches('.*${escRx(String(value || '')).replace(/'/g, "''")}.*')`;
|
|
238
|
+
case 'greaterThan':
|
|
239
|
+
return `${field} > ${fmtVal(value)}`;
|
|
240
|
+
case 'greaterThanOrEqual':
|
|
241
|
+
return `${field} >= ${fmtVal(value)}`;
|
|
242
|
+
case 'lessThan':
|
|
243
|
+
return `${field} < ${fmtVal(value)}`;
|
|
244
|
+
case 'lessThanOrEqual':
|
|
245
|
+
return `${field} <= ${fmtVal(value)}`;
|
|
246
|
+
case 'between':
|
|
247
|
+
if (Array.isArray(value) && value.length === 2) {
|
|
248
|
+
const n1 = Number(value[0]);
|
|
249
|
+
const n2 = Number(value[1]);
|
|
250
|
+
if (!isNaN(n1) && !isNaN(n2)) return `${field} >= ${n1} && ${field} <= ${n2}`;
|
|
251
|
+
}
|
|
252
|
+
return 'false';
|
|
253
|
+
default:
|
|
254
|
+
return 'false';
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function conditionBlockToCEL(
|
|
259
|
+
block: WorkflowEditorConditionBlock,
|
|
260
|
+
nodeIdToInputName: Map<string, string>,
|
|
261
|
+
): string {
|
|
262
|
+
const exprs = block.conditions
|
|
263
|
+
.map((c) => {
|
|
264
|
+
const mappedField = c.field ? nodeIdToInputName.get(c.field) ?? c.field : undefined;
|
|
265
|
+
return conditionToCelExpression({ ...c, field: mappedField });
|
|
266
|
+
})
|
|
267
|
+
.filter((e) => e !== 'false');
|
|
268
|
+
if (exprs.length === 0) return '';
|
|
269
|
+
if (exprs.length === 1) return exprs[0]!;
|
|
270
|
+
return exprs.join(block.logic === 'or' ? ' || ' : ' && ');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function getSourceRef(
|
|
274
|
+
edge: WorkflowEditorEdge,
|
|
275
|
+
ctx: SourceRefContext,
|
|
276
|
+
): WorkflowEditorFlowInputRef | undefined {
|
|
277
|
+
const { nodes, inputNodeIds, nodeIdToInputNameMap, nodeIdToFlowIdMap, nodeHandleTransformMap } = ctx;
|
|
278
|
+
const targetNodeId = edge.target;
|
|
279
|
+
const targetNode = nodes.find((n) => n.id === targetNodeId);
|
|
280
|
+
if (!targetNode) return undefined;
|
|
281
|
+
|
|
282
|
+
if (inputNodeIds.includes(targetNodeId) && nodeHandleTransformMap) {
|
|
283
|
+
const handle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
284
|
+
const handleName = handle?.name ?? 'output';
|
|
285
|
+
if (handleName !== 'output') {
|
|
286
|
+
const transformId = nodeHandleTransformMap.get(`${targetNodeId}_${handleName}`);
|
|
287
|
+
if (transformId) return { node: transformId, name: 'all' };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (inputNodeIds.includes(targetNodeId)) {
|
|
292
|
+
const inputName = nodeIdToInputNameMap.get(targetNodeId);
|
|
293
|
+
if (inputName) return { node: 'workflow', name: inputName };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (targetNode.type === 'forEach' && edge.targetHandle) {
|
|
297
|
+
const m = edge.targetHandle.match(/-output-(\d+)$/);
|
|
298
|
+
if (m) {
|
|
299
|
+
const targetFlowId = nodeIdToFlowIdMap.get(targetNodeId);
|
|
300
|
+
if (targetFlowId) {
|
|
301
|
+
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
302
|
+
return { node: targetFlowId, name: getForEachIterationRefName(outHandle, m[1]!) };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (targetNode.type === 'forEachEnd') {
|
|
308
|
+
const parentId = (targetNode.data as { parentNodeId?: string }).parentNodeId;
|
|
309
|
+
if (parentId) {
|
|
310
|
+
const forEachFlowId = nodeIdToFlowIdMap.get(parentId);
|
|
311
|
+
if (forEachFlowId) return { node: forEachFlowId };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (targetNode.type === 'ifElse') {
|
|
316
|
+
const handles = targetNode.data.outputHandles ?? [];
|
|
317
|
+
const handleIndex = handles.findIndex((h) => h.id === edge.targetHandle);
|
|
318
|
+
if (handleIndex !== -1) {
|
|
319
|
+
const targetFlowId = nodeIdToFlowIdMap.get(targetNodeId);
|
|
320
|
+
const blocks =
|
|
321
|
+
(targetNode.data as { conditionBlocks?: WorkflowEditorConditionBlock[] }).conditionBlocks ?? [];
|
|
322
|
+
const isElse = handleIndex >= blocks.length;
|
|
323
|
+
return { node: targetFlowId, equal: isElse ? '1' : String(handleIndex + 2) };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (
|
|
328
|
+
(targetNode.type === 'model' || targetNode.type === 'asset') &&
|
|
329
|
+
edge.targetHandle &&
|
|
330
|
+
nodeHandleTransformMap
|
|
331
|
+
) {
|
|
332
|
+
const handle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
333
|
+
const handleName = handle?.name ?? 'output';
|
|
334
|
+
if (handleName !== 'output') {
|
|
335
|
+
const transformId = nodeHandleTransformMap.get(`${targetNodeId}_${handleName}`);
|
|
336
|
+
if (transformId) return { node: transformId, name: 'all' };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (targetNode.type === 'llm' && edge.targetHandle && nodeHandleTransformMap) {
|
|
341
|
+
const handle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
342
|
+
if (handle?.name === 'outputs') {
|
|
343
|
+
const transformId = nodeHandleTransformMap.get(`${targetNodeId}_outputs`);
|
|
344
|
+
if (transformId) return { node: transformId, name: 'all' };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const targetFlowId = nodeIdToFlowIdMap.get(targetNodeId);
|
|
349
|
+
if (targetFlowId) return { node: targetFlowId, name: 'all' };
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function getInputHandle(
|
|
354
|
+
edge: WorkflowEditorEdge,
|
|
355
|
+
inputHandles: WorkflowEditorHandleInput[] | undefined,
|
|
356
|
+
): WorkflowEditorHandleInput | undefined {
|
|
357
|
+
return inputHandles?.flatMap((h) => {
|
|
358
|
+
if (h.id === edge.sourceHandle) return [h];
|
|
359
|
+
for (const sub of h.subHandles ?? []) {
|
|
360
|
+
if (sub.id === edge.sourceHandle) return [sub];
|
|
361
|
+
}
|
|
362
|
+
return [];
|
|
363
|
+
})[0];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function hasHandleConnection(
|
|
367
|
+
node: WorkflowEditorNode,
|
|
368
|
+
handleName: string,
|
|
369
|
+
edges: WorkflowEditorEdge[],
|
|
370
|
+
nodeId: string,
|
|
371
|
+
): boolean {
|
|
372
|
+
return edges
|
|
373
|
+
.filter((e) => e.target === nodeId)
|
|
374
|
+
.some(
|
|
375
|
+
(e) => (node.data.outputHandles?.find((h) => h.id === e.targetHandle)?.name ?? 'output') === handleName,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function getOutputHandleVarName(targetNodeId: string, handleName: string): string {
|
|
380
|
+
return ['output', 'allOutputs'].includes(handleName) ? targetNodeId : `${targetNodeId}_${handleName}`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function setOrPushFlowInput(
|
|
384
|
+
flowInputs: WorkflowEditorFlowInput[],
|
|
385
|
+
existingIndex: number,
|
|
386
|
+
input: WorkflowEditorFlowInput,
|
|
387
|
+
): void {
|
|
388
|
+
if (existingIndex !== -1) {
|
|
389
|
+
flowInputs[existingIndex] = input;
|
|
390
|
+
} else {
|
|
391
|
+
flowInputs.push(input);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function addVideoFirstLastFrameTransforms(
|
|
396
|
+
nodeId: string,
|
|
397
|
+
flowId: string,
|
|
398
|
+
node: WorkflowEditorNode,
|
|
399
|
+
edges: WorkflowEditorEdge[],
|
|
400
|
+
flow: WorkflowEditorFlowItem[],
|
|
401
|
+
nodeHandleTransformMap: Map<string, string>,
|
|
402
|
+
options?: { inputName?: string; onlyHandle?: 'firstFrame' | 'lastFrame' },
|
|
403
|
+
): void {
|
|
404
|
+
const handles: ('firstFrame' | 'lastFrame')[] =
|
|
405
|
+
options?.onlyHandle ? [options.onlyHandle] : ['firstFrame', 'lastFrame'];
|
|
406
|
+
for (const handleName of handles) {
|
|
407
|
+
if (!hasHandleConnection(node, handleName, edges, nodeId)) continue;
|
|
408
|
+
const transformId = options?.inputName ? flowId : `${flowId}_${handleName}`;
|
|
409
|
+
const ref =
|
|
410
|
+
options?.inputName ?
|
|
411
|
+
{ node: 'workflow' as const, name: options.inputName }
|
|
412
|
+
: { node: flowId, name: 'all' as const };
|
|
413
|
+
flow.push({
|
|
414
|
+
id: transformId,
|
|
415
|
+
type: 'transform',
|
|
416
|
+
logic: { transform: `exists('videoOutput') ? assetJson(videoOutput).${handleName}.assetId : []` },
|
|
417
|
+
inputs: [{ name: 'videoOutput', type: 'file', ref }],
|
|
418
|
+
});
|
|
419
|
+
nodeHandleTransformMap.set(`${nodeId}_${handleName}`, transformId);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function collectConditionalConnectionInputs(
|
|
424
|
+
incomingEdges: WorkflowEditorEdge[],
|
|
425
|
+
nodeId: string,
|
|
426
|
+
ctx: SourceRefContext,
|
|
427
|
+
): WorkflowEditorFlowInput[] {
|
|
428
|
+
const conditionalHandleId = getConditionalInputHandleId(nodeId);
|
|
429
|
+
return incomingEdges
|
|
430
|
+
.filter((e) => e.sourceHandle === conditionalHandleId)
|
|
431
|
+
.flatMap((edge) => {
|
|
432
|
+
const targetNode = ctx.nodes.find((n) => n.id === edge.target);
|
|
433
|
+
if (targetNode?.type !== 'ifElse') return [];
|
|
434
|
+
const ref = getSourceRef(edge, ctx);
|
|
435
|
+
if (!ref) return [];
|
|
436
|
+
return [{ name: 'connection', type: 'connection', ref }];
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function toStaticInput(name: string, type: string, value: unknown): WorkflowEditorFlowInput | undefined {
|
|
441
|
+
if (value === undefined || value === '') return undefined;
|
|
442
|
+
const isNum = typeof value === 'number';
|
|
443
|
+
const isStr = typeof value === 'string';
|
|
444
|
+
const isBool = typeof value === 'boolean';
|
|
445
|
+
const isStrArr = Array.isArray(value) && (value as unknown[]).every((v) => typeof v === 'string');
|
|
446
|
+
if (!isNum && !isStr && !isBool && !isStrArr) return undefined;
|
|
447
|
+
if (isStrArr && (value as string[]).length === 0) return undefined;
|
|
448
|
+
return { name, type, value: value as string | number | string[] | boolean };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function isFormArray(value: unknown): value is Record<string, unknown>[] {
|
|
452
|
+
return (
|
|
453
|
+
Array.isArray(value) &&
|
|
454
|
+
(value as unknown[]).every((item) => typeof item === 'object' && item !== null && !Array.isArray(item))
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function isModelCompose(model: WorkflowEditorModel | undefined): boolean {
|
|
459
|
+
return !!model?.id && (model.tags ?? []).includes('compose');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function getIsConditionallyExecuted(
|
|
463
|
+
targetNodeId: string,
|
|
464
|
+
nodes: WorkflowEditorNode[],
|
|
465
|
+
edges: WorkflowEditorEdge[],
|
|
466
|
+
): boolean {
|
|
467
|
+
const conditionalHandleId = getConditionalInputHandleId(targetNodeId);
|
|
468
|
+
return edges.some((e) => {
|
|
469
|
+
const sourceNode = nodes.find((n) => n.id === e.target);
|
|
470
|
+
return (
|
|
471
|
+
e.source === targetNodeId && e.sourceHandle === conditionalHandleId && sourceNode?.type === 'ifElse'
|
|
472
|
+
);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function getForEachBodyEditorNodeIds(
|
|
477
|
+
forEachId: string,
|
|
478
|
+
forEachEndId: string,
|
|
479
|
+
edges: WorkflowEditorEdge[],
|
|
480
|
+
): Set<string> {
|
|
481
|
+
const bodyNodeIds = new Set<string>();
|
|
482
|
+
const queue: string[] = [];
|
|
483
|
+
const escaped = forEachId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
484
|
+
for (const edge of edges) {
|
|
485
|
+
if (edge.target === forEachId && edge.targetHandle?.match(new RegExp(`^${escaped}-output-\\d+$`))) {
|
|
486
|
+
queue.push(edge.source);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
while (queue.length > 0) {
|
|
490
|
+
const nodeId = queue.pop()!;
|
|
491
|
+
if (nodeId === forEachEndId || bodyNodeIds.has(nodeId)) continue;
|
|
492
|
+
bodyNodeIds.add(nodeId);
|
|
493
|
+
for (const edge of edges) {
|
|
494
|
+
if (edge.target === nodeId && !bodyNodeIds.has(edge.source) && edge.source !== forEachEndId) {
|
|
495
|
+
queue.push(edge.source);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return bodyNodeIds;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function sortNodesByInputKeys(nodes: WorkflowEditorNode[], inputKeys: string[]): WorkflowEditorNode[] {
|
|
503
|
+
return [...nodes].sort((a, b) => {
|
|
504
|
+
const ai = inputKeys.indexOf(a.id);
|
|
505
|
+
const bi = inputKeys.indexOf(b.id);
|
|
506
|
+
if (ai === -1 && bi === -1) return 0;
|
|
507
|
+
if (ai === -1) return 1;
|
|
508
|
+
if (bi === -1) return -1;
|
|
509
|
+
return ai - bi;
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function getNodeOutputValue(
|
|
514
|
+
node: WorkflowEditorNode,
|
|
515
|
+
targetHandleId?: string,
|
|
516
|
+
): string | string[] | undefined {
|
|
517
|
+
if (node.type === 'text') return (node.data as { value?: string }).value || '';
|
|
518
|
+
if (node.type === 'asset') {
|
|
519
|
+
const d = node.data as {
|
|
520
|
+
value?: string | string[];
|
|
521
|
+
valueFirstFrame?: string;
|
|
522
|
+
valueLastFrame?: string;
|
|
523
|
+
outputHandles?: WorkflowEditorHandleOutput[];
|
|
524
|
+
};
|
|
525
|
+
if (targetHandleId) {
|
|
526
|
+
const handleName = d.outputHandles?.find((h) => h.id === targetHandleId)?.name ?? 'output';
|
|
527
|
+
if (handleName === 'firstFrame') return d.valueFirstFrame;
|
|
528
|
+
if (handleName === 'lastFrame') return d.valueLastFrame;
|
|
529
|
+
}
|
|
530
|
+
return (Array.isArray(d.value) ? d.value : d.value) || undefined;
|
|
531
|
+
}
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
|
|
536
|
+
|
|
537
|
+
function internalGetConnectedNodes(
|
|
538
|
+
nodes: WorkflowEditorNode[],
|
|
539
|
+
edges: WorkflowEditorEdge[],
|
|
540
|
+
startNodeId: string,
|
|
541
|
+
direction: 'input' | 'output',
|
|
542
|
+
isStoppingAtRunnableNodes: boolean,
|
|
543
|
+
): WorkflowEditorNode[] {
|
|
544
|
+
type NodeMeta = { id: string; type: string; parentNodeId?: string };
|
|
545
|
+
const nodesById = new Map<string, NodeMeta>(
|
|
546
|
+
nodes.map((n) => {
|
|
547
|
+
const meta: NodeMeta = { id: n.id, type: n.type };
|
|
548
|
+
if (n.type === 'forEachEnd') {
|
|
549
|
+
const parentId = (n.data as { parentNodeId?: string }).parentNodeId;
|
|
550
|
+
if (parentId !== undefined) {
|
|
551
|
+
meta.parentNodeId = parentId;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return [n.id, meta];
|
|
555
|
+
}),
|
|
556
|
+
);
|
|
557
|
+
const edgesSimple = edges.map((e) => ({ source: e.source, target: e.target }));
|
|
558
|
+
|
|
559
|
+
function isInternalForEachEdge(e: { source: string; target: string }): boolean {
|
|
560
|
+
const src = nodesById.get(e.source);
|
|
561
|
+
const tgt = nodesById.get(e.target);
|
|
562
|
+
return src?.type === 'forEach' && tgt?.type === 'forEachEnd' && tgt.parentNodeId === src.id;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function findIds(nodeId: string, visited: Set<string>): string[] {
|
|
566
|
+
if (visited.has(nodeId)) return [];
|
|
567
|
+
visited.add(nodeId);
|
|
568
|
+
const result: string[] = [];
|
|
569
|
+
const relevant = edgesSimple.filter(
|
|
570
|
+
(e) =>
|
|
571
|
+
(direction === 'output' ? e.target === nodeId : e.source === nodeId) && !isInternalForEachEdge(e),
|
|
572
|
+
);
|
|
573
|
+
for (const edge of relevant) {
|
|
574
|
+
const connectedId = direction === 'output' ? edge.source : edge.target;
|
|
575
|
+
const connectedNode = nodesById.get(connectedId);
|
|
576
|
+
if (!connectedNode) continue;
|
|
577
|
+
result.push(connectedId);
|
|
578
|
+
if (isStoppingAtRunnableNodes && RUNNABLE_TYPES.has(connectedNode.type)) continue;
|
|
579
|
+
result.push(...findIds(connectedId, visited));
|
|
580
|
+
}
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const ids = findIds(startNodeId, new Set());
|
|
585
|
+
const idSet = new Set(ids);
|
|
586
|
+
return nodes.filter((n) => idSet.has(n.id));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function resolveIfElseConditionField(
|
|
590
|
+
field: string,
|
|
591
|
+
candidateNodes: WorkflowEditorNode[],
|
|
592
|
+
): { nodeId: string; targetHandleId?: string } | undefined {
|
|
593
|
+
const byExactId = candidateNodes.find((n) => n.id === field);
|
|
594
|
+
if (byExactId) return { nodeId: field };
|
|
595
|
+
for (const node of candidateNodes) {
|
|
596
|
+
for (const handle of node.data.outputHandles ?? []) {
|
|
597
|
+
if (`${node.id}_${handle.name}` === field) {
|
|
598
|
+
return { nodeId: node.id, targetHandleId: handle.id };
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return undefined;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function normalizeNodes(nodes: WorkflowEditorNode[]): WorkflowEditorNode[] {
|
|
606
|
+
return nodes.map((node) => {
|
|
607
|
+
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
|
+
const inputHandles = (node.data.inputHandles ?? [])
|
|
611
|
+
.flatMap((h) => (h.subHandles ? h.subHandles : [h]))
|
|
612
|
+
.filter(
|
|
613
|
+
(h) =>
|
|
614
|
+
hasType(h.type, 'conditional') ||
|
|
615
|
+
hasType(h.type, 'prompt') ||
|
|
616
|
+
hasType(h.type, 'image') ||
|
|
617
|
+
hasType(h.type, 'video') ||
|
|
618
|
+
hasType(h.type, '3d') ||
|
|
619
|
+
hasType(h.type, 'audio') ||
|
|
620
|
+
(node.type === 'llm' && h.name === 'textInputs'),
|
|
621
|
+
);
|
|
622
|
+
return { ...node, data: { ...node.data, inputHandles } } as WorkflowEditorNode;
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ---- Main function ----
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Converts a workflow editor state (nodes, edges, inputKeys) into a flow array
|
|
630
|
+
* ready to be passed to `client.workflows.update({ flow: ... })`.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```ts
|
|
634
|
+
* const flow = convertWorkflowEditorToFlow({ nodes, edges, inputKeys, getModel });
|
|
635
|
+
* await client.workflows.update(workflowId, { flow: flow as WorkflowUpdateParams.Flow[] });
|
|
636
|
+
* ```
|
|
637
|
+
*/
|
|
638
|
+
export function convertWorkflowEditorToFlow(params: {
|
|
639
|
+
nodes: WorkflowEditorNode[];
|
|
640
|
+
edges: WorkflowEditorEdge[];
|
|
641
|
+
inputKeys: string[];
|
|
642
|
+
/** Optional model resolver — return the model object, 'unknown', or undefined. */
|
|
643
|
+
getModel?: (modelId: string) => WorkflowEditorModel | 'unknown' | undefined;
|
|
644
|
+
}): WorkflowEditorFlowItem[] {
|
|
645
|
+
const { edges, inputKeys } = params;
|
|
646
|
+
const nodes = normalizeNodes(params.nodes);
|
|
647
|
+
|
|
648
|
+
const inputNodeIds = nodes.filter((n) => !!n.data.isInput).map((n) => n.id);
|
|
649
|
+
|
|
650
|
+
const selectedOutputNodes = nodes.filter(
|
|
651
|
+
(n) => (n.type === 'model' || n.type === 'forEachEnd') && !!n.data.isOutput,
|
|
652
|
+
);
|
|
653
|
+
const selectedOutputNodeIds = selectedOutputNodes.map((n) => n.id);
|
|
654
|
+
const selectedOutputModelNodeIds = new Set(
|
|
655
|
+
selectedOutputNodes
|
|
656
|
+
.filter((n): n is WorkflowEditorNode & { type: 'model' } => n.type === 'model')
|
|
657
|
+
.map((n) => n.id),
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
// Collect all nodes in branches that lead to selected outputs
|
|
661
|
+
const nodesInSelectedBranches = new Set<string>();
|
|
662
|
+
for (const outputNode of selectedOutputNodes) {
|
|
663
|
+
const branchNodes = internalGetConnectedNodes(nodes, edges, outputNode.id, 'input', false);
|
|
664
|
+
for (const n of branchNodes) {
|
|
665
|
+
nodesInSelectedBranches.add(n.id);
|
|
666
|
+
if (outputNode.type === 'forEachEnd' && n.type === 'model' && !!n.data.isOutput) {
|
|
667
|
+
selectedOutputModelNodeIds.add(n.id);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Models fed from a forEachEnd "results" handle also inherit output inclusion
|
|
673
|
+
for (const outputNode of selectedOutputNodes) {
|
|
674
|
+
if (outputNode.type !== 'forEachEnd') continue;
|
|
675
|
+
const inputHandleIds = new Set((outputNode.data.inputHandles ?? []).map((h) => h.id));
|
|
676
|
+
for (const edge of edges) {
|
|
677
|
+
if (!edge.sourceHandle || edge.source !== outputNode.id || !inputHandleIds.has(edge.sourceHandle))
|
|
678
|
+
continue;
|
|
679
|
+
const tgt = nodes.find((n) => n.id === edge.target);
|
|
680
|
+
if (tgt?.type === 'model') selectedOutputModelNodeIds.add(tgt.id);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const filteredNodes = nodes.filter(
|
|
685
|
+
(n) =>
|
|
686
|
+
nodesInSelectedBranches.has(n.id) ||
|
|
687
|
+
inputNodeIds.includes(n.id) ||
|
|
688
|
+
selectedOutputNodeIds.includes(n.id),
|
|
689
|
+
);
|
|
690
|
+
const sortedNodes = sortNodesByInputKeys(filteredNodes, inputKeys);
|
|
691
|
+
|
|
692
|
+
const nodeIdToFlowIdMap = new Map<string, string>(
|
|
693
|
+
sortedNodes
|
|
694
|
+
.filter((n) => !inputNodeIds.includes(n.id) && n.type !== 'forEachEnd')
|
|
695
|
+
.map((n) => [n.id, nodeIdToFlowId(n.id)]),
|
|
696
|
+
);
|
|
697
|
+
const nodeIdToInputNameMap = new Map<string, string>(inputNodeIds.map((id) => [id, nodeIdToFlowId(id)]));
|
|
698
|
+
const approvalByApprovedNodeId = buildApprovalByApprovedNodeIdMap({
|
|
699
|
+
nodes,
|
|
700
|
+
edges,
|
|
701
|
+
nodesInSelectedBranches,
|
|
702
|
+
selectedOutputNodeIds,
|
|
703
|
+
});
|
|
704
|
+
const nodeHandleTransformMap = new Map<string, string>();
|
|
705
|
+
|
|
706
|
+
const ctx: SourceRefContext = {
|
|
707
|
+
nodes,
|
|
708
|
+
inputNodeIds,
|
|
709
|
+
nodeIdToInputNameMap,
|
|
710
|
+
nodeIdToFlowIdMap,
|
|
711
|
+
nodeHandleTransformMap,
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
const flow: WorkflowEditorFlowItem[] = [];
|
|
715
|
+
const forEachBodyEditorNodeIds = new Map<string, Set<string>>();
|
|
716
|
+
const forEachLoopFlowIds = new Map<string, string[]>();
|
|
717
|
+
const forEachFlowItemRefs = new Map<string, WorkflowEditorFlowItem>();
|
|
718
|
+
|
|
719
|
+
for (const node of sortedNodes) {
|
|
720
|
+
if (node.type !== 'forEach') continue;
|
|
721
|
+
const endNode = nodes.find(
|
|
722
|
+
(n) => n.type === 'forEachEnd' && (n.data as { parentNodeId?: string }).parentNodeId === node.id,
|
|
723
|
+
);
|
|
724
|
+
if (!endNode) continue;
|
|
725
|
+
forEachBodyEditorNodeIds.set(node.id, getForEachBodyEditorNodeIds(node.id, endNode.id, edges));
|
|
726
|
+
forEachLoopFlowIds.set(node.id, []);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Pre-populate video firstFrame/lastFrame transform IDs for input nodes
|
|
730
|
+
const addedInputVideoTransforms = new Set<string>();
|
|
731
|
+
for (const inputNodeId of inputNodeIds) {
|
|
732
|
+
const inputNode = nodes.find((n) => n.id === inputNodeId);
|
|
733
|
+
if (!inputNode || inputNode.type !== 'asset' || (inputNode.data as { type?: string }).type !== 'video')
|
|
734
|
+
continue;
|
|
735
|
+
for (const handleName of ['firstFrame', 'lastFrame'] as const) {
|
|
736
|
+
if (hasHandleConnection(inputNode, handleName, edges, inputNodeId)) {
|
|
737
|
+
nodeHandleTransformMap.set(`${inputNodeId}_${handleName}`, `${inputNodeId}_${handleName}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function ensureInputVideoTransformsAdded() {
|
|
743
|
+
for (const inputNodeId of inputNodeIds) {
|
|
744
|
+
const inputNode = nodes.find((n) => n.id === inputNodeId);
|
|
745
|
+
if (!inputNode || inputNode.type !== 'asset' || (inputNode.data as { type?: string }).type !== 'video')
|
|
746
|
+
continue;
|
|
747
|
+
const inputName = nodeIdToInputNameMap.get(inputNodeId);
|
|
748
|
+
if (!inputName) continue;
|
|
749
|
+
for (const handleName of ['firstFrame', 'lastFrame'] as const) {
|
|
750
|
+
const key = `${inputNodeId}_${handleName}`;
|
|
751
|
+
if (addedInputVideoTransforms.has(key)) continue;
|
|
752
|
+
if (!hasHandleConnection(inputNode, handleName, edges, inputNodeId)) continue;
|
|
753
|
+
addVideoFirstLastFrameTransforms(inputNodeId, key, inputNode, edges, flow, nodeHandleTransformMap, {
|
|
754
|
+
inputName,
|
|
755
|
+
onlyHandle: handleName,
|
|
756
|
+
});
|
|
757
|
+
addedInputVideoTransforms.add(key);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
for (const node of sortedNodes) {
|
|
763
|
+
if (inputNodeIds.includes(node.id)) continue;
|
|
764
|
+
const flowId = nodeIdToFlowIdMap.get(node.id);
|
|
765
|
+
if (!flowId) continue;
|
|
766
|
+
|
|
767
|
+
const flowLengthBefore = flow.length;
|
|
768
|
+
const incomingEdges = edges.filter((e) => e.source === node.id);
|
|
769
|
+
|
|
770
|
+
// ------------------------------------
|
|
771
|
+
// Model / LLM -> custom-model
|
|
772
|
+
// ------------------------------------
|
|
773
|
+
if (node.type === 'model' || node.type === 'llm') {
|
|
774
|
+
const nodeData = node.data as NodeCommonData & { modelId?: string; form?: Record<string, unknown> };
|
|
775
|
+
const flowInputs: WorkflowEditorFlowInput[] = [];
|
|
776
|
+
const remoteModel = nodeData.modelId ? params.getModel?.(nodeData.modelId) : undefined;
|
|
777
|
+
const model = remoteModel === 'unknown' ? undefined : remoteModel;
|
|
778
|
+
const modelInputs = model?.inputs;
|
|
779
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
780
|
+
|
|
781
|
+
const edgesByInput = new Map<string, WorkflowEditorEdge[]>();
|
|
782
|
+
for (const edge of incomingEdges) {
|
|
783
|
+
if (edge.sourceHandle === conditionalHandleId) continue;
|
|
784
|
+
const handle = getInputHandle(edge, node.data.inputHandles);
|
|
785
|
+
if (!handle?.name) continue;
|
|
786
|
+
const existing = edgesByInput.get(handle.name);
|
|
787
|
+
if (existing) existing.push(edge);
|
|
788
|
+
else edgesByInput.set(handle.name, [edge]);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (nodeData.form) {
|
|
792
|
+
for (const [key, value] of Object.entries(nodeData.form)) {
|
|
793
|
+
if (value === undefined || value === '') continue;
|
|
794
|
+
const modelInput = modelInputs?.find((inp) => inp.name === key);
|
|
795
|
+
|
|
796
|
+
if (modelInput?.type === 'inputs_array') {
|
|
797
|
+
if (!isFormArray(value)) continue;
|
|
798
|
+
const nestedInputs = modelInput.inputs ?? [];
|
|
799
|
+
const items: WorkflowEditorFlowInput[][] = [];
|
|
800
|
+
|
|
801
|
+
for (const [index, form] of (value as Record<string, unknown>[]).entries()) {
|
|
802
|
+
const itemInputs: WorkflowEditorFlowInput[] = [];
|
|
803
|
+
for (const nestedInput of nestedInputs) {
|
|
804
|
+
const edgeKey = `${key}[${index}].${nestedInput.name}`;
|
|
805
|
+
const edgeGroup = edgesByInput.get(edgeKey);
|
|
806
|
+
if (edgeGroup && edgeGroup.length > 0) {
|
|
807
|
+
const ref = getSourceRef(edgeGroup[0]!, ctx);
|
|
808
|
+
if (ref) itemInputs.push({ name: nestedInput.name, type: nestedInput.type, ref });
|
|
809
|
+
edgesByInput.delete(edgeKey);
|
|
810
|
+
} else {
|
|
811
|
+
const inp = toStaticInput(nestedInput.name, nestedInput.type, form[nestedInput.name]);
|
|
812
|
+
if (inp) itemInputs.push(inp);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const isValid =
|
|
816
|
+
isModelCompose(model) ? getHasComposeLayerSourceInput(itemInputs) : itemInputs.length > 0;
|
|
817
|
+
if (isValid) items.push(itemInputs);
|
|
818
|
+
}
|
|
819
|
+
if (items.length > 0) flowInputs.push({ name: key, type: 'inputs_array', items });
|
|
820
|
+
} else {
|
|
821
|
+
const type =
|
|
822
|
+
modelInput?.type ??
|
|
823
|
+
(typeof value === 'number' ? 'number'
|
|
824
|
+
: typeof value === 'boolean' ? 'boolean'
|
|
825
|
+
: 'string');
|
|
826
|
+
const inp = toStaticInput(key, type, value);
|
|
827
|
+
if (inp) flowInputs.push(inp);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
for (const [inputName, inputEdges] of edgesByInput) {
|
|
833
|
+
const existingIndex = flowInputs.findIndex((inp) => inp.name === inputName);
|
|
834
|
+
const modelInput = modelInputs?.find((inp) => inp.name === inputName);
|
|
835
|
+
if (!modelInput) continue;
|
|
836
|
+
const inputType = modelInput.type;
|
|
837
|
+
|
|
838
|
+
if (inputEdges.length === 1) {
|
|
839
|
+
const ref = getSourceRef(inputEdges[0]!, ctx);
|
|
840
|
+
if (!ref) continue;
|
|
841
|
+
setOrPushFlowInput(flowInputs, existingIndex, { name: inputName, type: inputType, ref });
|
|
842
|
+
} else {
|
|
843
|
+
const allConditional = inputEdges.every((e) => getIsConditionallyExecuted(e.target, nodes, edges));
|
|
844
|
+
|
|
845
|
+
if (allConditional) {
|
|
846
|
+
const conditionalNodeIds = inputEdges
|
|
847
|
+
.map((e) =>
|
|
848
|
+
inputNodeIds.includes(e.target) ?
|
|
849
|
+
nodeIdToInputNameMap.get(e.target)
|
|
850
|
+
: nodeIdToFlowIdMap.get(e.target),
|
|
851
|
+
)
|
|
852
|
+
.filter((id): id is string => id != null);
|
|
853
|
+
if (conditionalNodeIds.length > 0) {
|
|
854
|
+
setOrPushFlowInput(flowInputs, existingIndex, {
|
|
855
|
+
name: inputName,
|
|
856
|
+
type: inputType,
|
|
857
|
+
ref: { conditional: conditionalNodeIds },
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
} else if (inputType === 'file_array' || inputType === 'string_array') {
|
|
861
|
+
const transformVariables: ConcatVariable[] = [];
|
|
862
|
+
const transformInputs: WorkflowEditorFlowInput[] = [];
|
|
863
|
+
|
|
864
|
+
for (const edge of inputEdges) {
|
|
865
|
+
const targetNode = filteredNodes.find((n) => n.id === edge.target);
|
|
866
|
+
const targetOutputHandle = targetNode?.data.outputHandles?.find(
|
|
867
|
+
(h) => h.id === edge.targetHandle,
|
|
868
|
+
);
|
|
869
|
+
const targetOutputHandleName = targetOutputHandle?.name ?? 'output';
|
|
870
|
+
|
|
871
|
+
let targetNodeKey: string;
|
|
872
|
+
let targetInputType: string;
|
|
873
|
+
|
|
874
|
+
if (targetNode?.type === 'forEach' && edge.targetHandle) {
|
|
875
|
+
const m = edge.targetHandle.match(/-output-(\d+)$/);
|
|
876
|
+
const outputIdx = m ? m[1] ?? '0' : '0';
|
|
877
|
+
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
878
|
+
targetNodeKey = getForEachIterationRefName(outHandle, outputIdx);
|
|
879
|
+
targetInputType = outHandle?.type === 'text' ? 'string' : 'file';
|
|
880
|
+
} else {
|
|
881
|
+
targetNodeKey = getOutputHandleVarName(edge.target, targetOutputHandleName);
|
|
882
|
+
targetInputType =
|
|
883
|
+
(
|
|
884
|
+
(targetNode?.type === 'asset' &&
|
|
885
|
+
!!(targetNode.data as { isMultiple?: boolean }).isMultiple) ||
|
|
886
|
+
targetNode?.type === 'groupItems' ||
|
|
887
|
+
targetNode?.type === 'sliceAssets' ||
|
|
888
|
+
targetOutputHandleName === 'allOutputs'
|
|
889
|
+
) ?
|
|
890
|
+
'file_array'
|
|
891
|
+
: targetNode?.type === 'llm' && targetOutputHandleName === 'outputs' ? 'string_array'
|
|
892
|
+
: targetNode?.type === 'asset' || targetNode?.type === 'model' ? 'file'
|
|
893
|
+
: 'string';
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const isArr = targetInputType === 'file_array';
|
|
897
|
+
const isRequired =
|
|
898
|
+
(targetNode?.type === 'asset' &&
|
|
899
|
+
!!(targetNode.data as { isRequired?: boolean }).isRequired) ||
|
|
900
|
+
isModelCompose(model);
|
|
901
|
+
transformVariables.push({ name: targetNodeKey, isArray: isArr, isRequired });
|
|
902
|
+
|
|
903
|
+
if (
|
|
904
|
+
!targetNode?.data.isInput &&
|
|
905
|
+
(targetNode?.type === 'asset' || targetNode?.type === 'text')
|
|
906
|
+
) {
|
|
907
|
+
transformInputs.push({
|
|
908
|
+
name: targetNodeKey,
|
|
909
|
+
type: targetInputType,
|
|
910
|
+
value: getNodeOutputValue(targetNode, edge.targetHandle ?? undefined) as
|
|
911
|
+
| string
|
|
912
|
+
| string[]
|
|
913
|
+
| undefined,
|
|
914
|
+
});
|
|
915
|
+
} else {
|
|
916
|
+
const ref = getSourceRef(edge, ctx);
|
|
917
|
+
if (!ref) continue;
|
|
918
|
+
transformInputs.push({ name: targetNodeKey, type: targetInputType, ref });
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
const hasLoopIterationInputs = transformInputs.some((inp) => /^(image|text)\d+$/.test(inp.name));
|
|
923
|
+
if (hasLoopIterationInputs) {
|
|
924
|
+
const sortByIdx = (a: { name: string }, b: { name: string }) =>
|
|
925
|
+
parseInt(a.name.replace(/^(image|text)/, ''), 10) -
|
|
926
|
+
parseInt(b.name.replace(/^(image|text)/, ''), 10);
|
|
927
|
+
transformVariables.sort(sortByIdx);
|
|
928
|
+
transformInputs.sort(sortByIdx);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const concatItem: WorkflowEditorFlowItem = {
|
|
932
|
+
id: `${flowId}_transform_${inputName}`,
|
|
933
|
+
type: 'transform',
|
|
934
|
+
logic: { transform: buildConcatArrayTransform(transformVariables) },
|
|
935
|
+
inputs: transformInputs,
|
|
936
|
+
};
|
|
937
|
+
flow.push(concatItem);
|
|
938
|
+
setOrPushFlowInput(flowInputs, existingIndex, {
|
|
939
|
+
name: inputName,
|
|
940
|
+
type: inputType,
|
|
941
|
+
ref: { node: concatItem.id, name: 'all' },
|
|
942
|
+
});
|
|
943
|
+
} else {
|
|
944
|
+
const ref = getSourceRef(inputEdges[0]!, ctx);
|
|
945
|
+
if (!ref) continue;
|
|
946
|
+
setOrPushFlowInput(flowInputs, existingIndex, { name: inputName, type: inputType, ref });
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Conditional control flow connections
|
|
952
|
+
const conditionalEdges = incomingEdges.filter((e) => e.sourceHandle === conditionalHandleId);
|
|
953
|
+
if (conditionalEdges.length > 0) {
|
|
954
|
+
const conditionalRefs: string[] = [];
|
|
955
|
+
for (const edge of conditionalEdges) {
|
|
956
|
+
const targetNode = nodes.find((n) => n.id === edge.target);
|
|
957
|
+
if (targetNode?.type === 'ifElse') {
|
|
958
|
+
const ref = getSourceRef(edge, ctx);
|
|
959
|
+
if (ref?.equal) flowInputs.push({ name: 'connection', type: 'connection', ref });
|
|
960
|
+
} else {
|
|
961
|
+
const targetFlowId = nodeIdToFlowIdMap.get(edge.target);
|
|
962
|
+
if (targetFlowId) conditionalRefs.push(targetFlowId);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (conditionalRefs.length > 0) {
|
|
966
|
+
flowInputs.push({ name: 'connection', type: 'connection', ref: { conditional: conditionalRefs } });
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
flow.push({
|
|
971
|
+
id: flowId,
|
|
972
|
+
type: 'custom-model',
|
|
973
|
+
modelId: nodeData.modelId,
|
|
974
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
975
|
+
includeOutputsInWorkflowJob:
|
|
976
|
+
node.type === 'model' && selectedOutputModelNodeIds.has(node.id) ? true : undefined,
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
ensureInputVideoTransformsAdded();
|
|
980
|
+
|
|
981
|
+
if (node.type === 'model' && (node.data as { type?: string }).type === 'video') {
|
|
982
|
+
addVideoFirstLastFrameTransforms(node.id, flowId, node, edges, flow, nodeHandleTransformMap);
|
|
983
|
+
} else if (node.type === 'model' && node.data.outputHandles?.some((h) => h.name === 'firstOutput')) {
|
|
984
|
+
if (hasHandleConnection(node, 'firstOutput', edges, node.id)) {
|
|
985
|
+
const transformId = `${flowId}_firstOutput`;
|
|
986
|
+
flow.push({
|
|
987
|
+
id: transformId,
|
|
988
|
+
type: 'transform',
|
|
989
|
+
logic: { transform: "exists('output') && output.size() > 0 ? output[0] : []" },
|
|
990
|
+
inputs: [{ name: 'output', type: 'file_array', ref: { node: flowId, name: 'all' } }],
|
|
991
|
+
});
|
|
992
|
+
nodeHandleTransformMap.set(`${node.id}_firstOutput`, transformId);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (node.type === 'llm' && hasHandleConnection(node, 'outputs', edges, node.id)) {
|
|
997
|
+
const transformId = `${flowId}_outputs`;
|
|
998
|
+
flow.push({
|
|
999
|
+
id: transformId,
|
|
1000
|
+
type: 'transform',
|
|
1001
|
+
logic: { transform: '[output]' },
|
|
1002
|
+
inputs: [{ name: 'output', type: 'string', ref: { node: flowId, name: 'all' } }],
|
|
1003
|
+
});
|
|
1004
|
+
nodeHandleTransformMap.set(`${node.id}_outputs`, transformId);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// ------------------------------------
|
|
1008
|
+
// Text -> transform with string literal
|
|
1009
|
+
// ------------------------------------
|
|
1010
|
+
} else if (node.type === 'text') {
|
|
1011
|
+
const flowInputs = collectConditionalConnectionInputs(incomingEdges, node.id, ctx);
|
|
1012
|
+
const value = (node.data as { value?: string }).value || '';
|
|
1013
|
+
flow.push({
|
|
1014
|
+
id: flowId,
|
|
1015
|
+
type: 'transform',
|
|
1016
|
+
logic: { transform: `'${escapeCELString(value)}'` },
|
|
1017
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
// ------------------------------------
|
|
1021
|
+
// Asset -> transform with file reference
|
|
1022
|
+
// ------------------------------------
|
|
1023
|
+
} else if (node.type === 'asset') {
|
|
1024
|
+
const flowInputs = collectConditionalConnectionInputs(incomingEdges, node.id, ctx);
|
|
1025
|
+
const d = node.data as { value?: string | string[]; type?: string };
|
|
1026
|
+
const value = d.value;
|
|
1027
|
+
if (!value || (Array.isArray(value) && value.length === 0)) continue;
|
|
1028
|
+
flow.push({
|
|
1029
|
+
id: flowId,
|
|
1030
|
+
type: 'transform',
|
|
1031
|
+
logic: {
|
|
1032
|
+
transform: Array.isArray(value) ? `['${(value as string[]).join("'] + ['")}']` : `'${value}'`,
|
|
1033
|
+
},
|
|
1034
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1035
|
+
});
|
|
1036
|
+
if (d.type === 'video') {
|
|
1037
|
+
addVideoFirstLastFrameTransforms(node.id, flowId, node, edges, flow, nodeHandleTransformMap);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// ------------------------------------
|
|
1041
|
+
// TransformText -> transform (CEL)
|
|
1042
|
+
// ------------------------------------
|
|
1043
|
+
} else if (node.type === 'transformText') {
|
|
1044
|
+
const flowInputs: WorkflowEditorFlowInput[] = [];
|
|
1045
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
1046
|
+
const value = (node.data as { value?: string }).value || "''";
|
|
1047
|
+
|
|
1048
|
+
for (const edge of incomingEdges) {
|
|
1049
|
+
if (edge.sourceHandle === conditionalHandleId) continue;
|
|
1050
|
+
const targetNode = nodes.find((n) => n.id === edge.target);
|
|
1051
|
+
if (!targetNode) continue;
|
|
1052
|
+
const ref = getSourceRef(edge, ctx);
|
|
1053
|
+
if (!ref) continue;
|
|
1054
|
+
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
1055
|
+
const outHandleName = outHandle?.name ?? 'output';
|
|
1056
|
+
const targetFlowId = nodeIdToFlowIdMap.get(targetNode.id) ?? targetNode.id;
|
|
1057
|
+
flowInputs.push({ name: `${targetFlowId}_${outHandleName}`, type: 'string', ref });
|
|
1058
|
+
}
|
|
1059
|
+
flowInputs.push(...collectConditionalConnectionInputs(incomingEdges, node.id, ctx));
|
|
1060
|
+
flow.push({
|
|
1061
|
+
id: flowId,
|
|
1062
|
+
type: 'transform',
|
|
1063
|
+
logic: { transform: value },
|
|
1064
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
// ------------------------------------
|
|
1068
|
+
// SplitText -> split string into list
|
|
1069
|
+
// ------------------------------------
|
|
1070
|
+
} else if (node.type === 'splitText') {
|
|
1071
|
+
const flowInputs: WorkflowEditorFlowInput[] = [];
|
|
1072
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
1073
|
+
let textSourceVarName: string | undefined;
|
|
1074
|
+
|
|
1075
|
+
for (const edge of incomingEdges) {
|
|
1076
|
+
if (edge.sourceHandle === conditionalHandleId) continue;
|
|
1077
|
+
const targetNode = nodes.find((n) => n.id === edge.target);
|
|
1078
|
+
if (!targetNode) continue;
|
|
1079
|
+
const ref = getSourceRef(edge, ctx);
|
|
1080
|
+
if (!ref) continue;
|
|
1081
|
+
flowInputs.push({ name: targetNode.id, type: 'string', ref });
|
|
1082
|
+
textSourceVarName = targetNode.id;
|
|
1083
|
+
}
|
|
1084
|
+
flowInputs.push(...collectConditionalConnectionInputs(incomingEdges, node.id, ctx));
|
|
1085
|
+
const delimRaw = (node.data as { splitDelimiter?: string }).splitDelimiter ?? ',';
|
|
1086
|
+
const delim = delimRaw.length > 0 ? delimRaw : ',';
|
|
1087
|
+
flow.push({
|
|
1088
|
+
id: flowId,
|
|
1089
|
+
type: 'transform',
|
|
1090
|
+
logic: {
|
|
1091
|
+
transform:
|
|
1092
|
+
textSourceVarName ? `trim(${textSourceVarName}).split(${formatTextAsCELString(delim)})` : '[]',
|
|
1093
|
+
},
|
|
1094
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
// ------------------------------------
|
|
1098
|
+
// GroupItems -> array concatenation
|
|
1099
|
+
// ------------------------------------
|
|
1100
|
+
} else if (node.type === 'groupItems') {
|
|
1101
|
+
const flowInputs: WorkflowEditorFlowInput[] = [];
|
|
1102
|
+
const variables: ConcatVariable[] = [];
|
|
1103
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
1104
|
+
|
|
1105
|
+
for (const edge of incomingEdges) {
|
|
1106
|
+
if (edge.sourceHandle === conditionalHandleId) continue;
|
|
1107
|
+
const targetNode = nodes.find((n) => n.id === edge.target);
|
|
1108
|
+
if (!targetNode) continue;
|
|
1109
|
+
const ref = getSourceRef(edge, ctx);
|
|
1110
|
+
if (!ref) continue;
|
|
1111
|
+
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
1112
|
+
const outHandleName = outHandle?.name ?? 'output';
|
|
1113
|
+
const varName = `${targetNode.id}_${outHandleName}`;
|
|
1114
|
+
const isArr =
|
|
1115
|
+
(targetNode.type === 'asset' && !!(targetNode.data as { isMultiple?: boolean }).isMultiple) ||
|
|
1116
|
+
targetNode.type === 'groupItems' ||
|
|
1117
|
+
targetNode.type === 'sliceAssets' ||
|
|
1118
|
+
outHandleName === 'allOutputs';
|
|
1119
|
+
const isRequired =
|
|
1120
|
+
inputNodeIds.includes(targetNode.id) ?
|
|
1121
|
+
(targetNode.type === 'asset' && !!(targetNode.data as { isRequired?: boolean }).isRequired) ||
|
|
1122
|
+
['firstFrame', 'lastFrame'].includes(outHandleName)
|
|
1123
|
+
: true;
|
|
1124
|
+
variables.push({ name: varName, isArray: isArr, isRequired });
|
|
1125
|
+
flowInputs.push({ name: varName, type: isArr ? 'file_array' : 'file', ref });
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
const orderedValues = convertValueToList((node.data as { value?: string }).value ?? '');
|
|
1129
|
+
variables.sort((a, b) => orderedValues.indexOf(a.name) - orderedValues.indexOf(b.name));
|
|
1130
|
+
flowInputs.sort((a, b) => orderedValues.indexOf(a.name) - orderedValues.indexOf(b.name));
|
|
1131
|
+
flowInputs.push(...collectConditionalConnectionInputs(incomingEdges, node.id, ctx));
|
|
1132
|
+
flow.push({
|
|
1133
|
+
id: flowId,
|
|
1134
|
+
type: 'transform',
|
|
1135
|
+
logic: { transform: buildConcatArrayTransform(variables) },
|
|
1136
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
// ------------------------------------
|
|
1140
|
+
// SliceAssets -> slice(list, from, from+count)
|
|
1141
|
+
// ------------------------------------
|
|
1142
|
+
} else if (node.type === 'sliceAssets') {
|
|
1143
|
+
const assetsEdge = incomingEdges.find((e) => e.sourceHandle === `${node.id}-source-assets`);
|
|
1144
|
+
if (!assetsEdge) {
|
|
1145
|
+
flow.push({ id: flowId, type: 'transform', logic: { transform: '[]' } });
|
|
1146
|
+
} else {
|
|
1147
|
+
const targetNode = nodes.find((n) => n.id === assetsEdge.target);
|
|
1148
|
+
if (targetNode) {
|
|
1149
|
+
const ref = getSourceRef(assetsEdge, ctx);
|
|
1150
|
+
if (ref) {
|
|
1151
|
+
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === assetsEdge.targetHandle);
|
|
1152
|
+
const varName = getOutputHandleVarName(targetNode.id, outHandle?.name ?? 'output');
|
|
1153
|
+
const sliceData = node.data as { from?: number | string; count?: number | string };
|
|
1154
|
+
const from = parseInt(String(sliceData.from ?? ''), 10) || 0;
|
|
1155
|
+
const count = parseInt(String(sliceData.count ?? ''), 10) || 0;
|
|
1156
|
+
const transform =
|
|
1157
|
+
from < 0 ?
|
|
1158
|
+
`slice(${varName}, (${varName}).size() + ${from}, (${varName}).size() + ${from + count})`
|
|
1159
|
+
: `slice(${varName}, ${from}, ${from + count})`;
|
|
1160
|
+
const flowInputs: WorkflowEditorFlowInput[] = [
|
|
1161
|
+
{ name: varName, type: 'file_array', ref },
|
|
1162
|
+
...collectConditionalConnectionInputs(incomingEdges, node.id, ctx),
|
|
1163
|
+
];
|
|
1164
|
+
flow.push({
|
|
1165
|
+
id: flowId,
|
|
1166
|
+
type: 'transform',
|
|
1167
|
+
logic: { transform },
|
|
1168
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// ------------------------------------
|
|
1175
|
+
// ForEach -> for-each loop
|
|
1176
|
+
// ------------------------------------
|
|
1177
|
+
} else if (node.type === 'forEach') {
|
|
1178
|
+
const flowInputs: WorkflowEditorFlowInput[] = [];
|
|
1179
|
+
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
1180
|
+
|
|
1181
|
+
for (const edge of incomingEdges) {
|
|
1182
|
+
if (edge.sourceHandle === conditionalHandleId) continue;
|
|
1183
|
+
const inputMatch = edge.sourceHandle?.match(/-input-(\d+)$/);
|
|
1184
|
+
if (!inputMatch) continue;
|
|
1185
|
+
const inputIndex = parseInt(inputMatch[1]!, 10);
|
|
1186
|
+
const listProvider = nodes.find((n) => n.id === edge.target);
|
|
1187
|
+
const listOutputHandle = listProvider?.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
1188
|
+
const { name: inputName, type: inputApiType } = getForEachFlowListInputDescriptor({
|
|
1189
|
+
listProvider,
|
|
1190
|
+
outputHandle: listOutputHandle,
|
|
1191
|
+
inputIndex,
|
|
1192
|
+
});
|
|
1193
|
+
if (flowInputs.find((inp) => inp.name === inputName)) continue;
|
|
1194
|
+
const ref = getSourceRef(edge, ctx);
|
|
1195
|
+
if (!ref) continue;
|
|
1196
|
+
flowInputs.push({ name: inputName, type: inputApiType, ref });
|
|
1197
|
+
}
|
|
1198
|
+
flowInputs.sort(
|
|
1199
|
+
(a, b) =>
|
|
1200
|
+
parseInt(a.name.replace(/^(image|text)/, ''), 10) -
|
|
1201
|
+
parseInt(b.name.replace(/^(image|text)/, ''), 10),
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
const forEachFlowItem: WorkflowEditorFlowItem = {
|
|
1205
|
+
id: flowId,
|
|
1206
|
+
type: 'for-each',
|
|
1207
|
+
loopBodyNodeIds: [],
|
|
1208
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1209
|
+
};
|
|
1210
|
+
flow.push(forEachFlowItem);
|
|
1211
|
+
forEachFlowItemRefs.set(node.id, forEachFlowItem);
|
|
1212
|
+
|
|
1213
|
+
// ------------------------------------
|
|
1214
|
+
// IfElse -> logic if-else
|
|
1215
|
+
// ------------------------------------
|
|
1216
|
+
} else if (node.type === 'ifElse') {
|
|
1217
|
+
const flowInputs: WorkflowEditorFlowInput[] = [];
|
|
1218
|
+
const conditionBlocks =
|
|
1219
|
+
(node.data as { conditionBlocks?: WorkflowEditorConditionBlock[] }).conditionBlocks ?? [];
|
|
1220
|
+
|
|
1221
|
+
const fieldsUsed = new Set<string>();
|
|
1222
|
+
for (const block of conditionBlocks) {
|
|
1223
|
+
for (const condition of block.conditions) {
|
|
1224
|
+
if (condition.field) fieldsUsed.add(condition.field);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
const edgesIntoIfElse = edges.filter((e) => e.target === node.id);
|
|
1229
|
+
for (const fieldKey of fieldsUsed) {
|
|
1230
|
+
const resolved = resolveIfElseConditionField(fieldKey, nodes);
|
|
1231
|
+
const fieldNodeId = resolved?.nodeId;
|
|
1232
|
+
if (!fieldNodeId) continue;
|
|
1233
|
+
|
|
1234
|
+
const outputHandleName =
|
|
1235
|
+
resolved?.targetHandleId !== undefined ?
|
|
1236
|
+
nodes
|
|
1237
|
+
.find((n) => n.id === fieldNodeId)
|
|
1238
|
+
?.data.outputHandles?.find((h) => h.id === resolved.targetHandleId)?.name ?? 'output'
|
|
1239
|
+
: 'output';
|
|
1240
|
+
|
|
1241
|
+
const celVarName = getOutputHandleVarName(fieldNodeId, outputHandleName);
|
|
1242
|
+
nodeIdToInputNameMap.set(fieldKey, celVarName);
|
|
1243
|
+
|
|
1244
|
+
const edgeToIfElse =
|
|
1245
|
+
edgesIntoIfElse
|
|
1246
|
+
.filter((e) => e.source === fieldNodeId)
|
|
1247
|
+
.find((e) => {
|
|
1248
|
+
if (resolved.targetHandleId === undefined) return true;
|
|
1249
|
+
return e.sourceHandle === resolved.targetHandleId;
|
|
1250
|
+
}) ?? edgesIntoIfElse.find((e) => e.source === fieldNodeId);
|
|
1251
|
+
|
|
1252
|
+
if (inputNodeIds.includes(fieldNodeId)) {
|
|
1253
|
+
const workflowInputName = nodeIdToInputNameMap.get(fieldNodeId);
|
|
1254
|
+
if (workflowInputName) {
|
|
1255
|
+
const ref =
|
|
1256
|
+
edgeToIfElse ?
|
|
1257
|
+
getSourceRef(edgeToIfElse, ctx)
|
|
1258
|
+
: { node: 'workflow' as const, name: workflowInputName };
|
|
1259
|
+
flowInputs.push({
|
|
1260
|
+
name: celVarName,
|
|
1261
|
+
type: 'string',
|
|
1262
|
+
ref: ref ?? { node: 'workflow', name: workflowInputName },
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
} else {
|
|
1266
|
+
const refFromEdge = edgeToIfElse ? getSourceRef(edgeToIfElse, ctx) : undefined;
|
|
1267
|
+
const targetFlowId = nodeIdToFlowIdMap.get(fieldNodeId);
|
|
1268
|
+
if (refFromEdge) {
|
|
1269
|
+
flowInputs.push({ name: celVarName, type: 'string', ref: refFromEdge });
|
|
1270
|
+
} else if (targetFlowId) {
|
|
1271
|
+
flowInputs.push({ name: celVarName, type: 'string', ref: { node: targetFlowId, name: 'all' } });
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
const cases: { condition: string; value: string }[] = [];
|
|
1277
|
+
for (let i = 0; i < conditionBlocks.length; i++) {
|
|
1278
|
+
const condition = conditionBlockToCEL(conditionBlocks[i]!, nodeIdToInputNameMap);
|
|
1279
|
+
if (condition) cases.push({ condition, value: String(i + 2) });
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
flow.push({
|
|
1283
|
+
id: flowId,
|
|
1284
|
+
type: 'logic',
|
|
1285
|
+
logicType: 'if-else',
|
|
1286
|
+
logic: { cases: cases.length > 0 ? cases : undefined, default: '1' },
|
|
1287
|
+
inputs: flowInputs.length > 0 ? flowInputs : undefined,
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// Track which flow items belong to each forEach body
|
|
1292
|
+
for (const [forEachId, bodyNodeIds] of forEachBodyEditorNodeIds) {
|
|
1293
|
+
if (bodyNodeIds.has(node.id)) {
|
|
1294
|
+
const loopFlowIds = forEachLoopFlowIds.get(forEachId);
|
|
1295
|
+
if (loopFlowIds) {
|
|
1296
|
+
for (let i = flowLengthBefore; i < flow.length; i++) {
|
|
1297
|
+
loopFlowIds.push(flow[i]!.id);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// Populate loopBodyNodeIds on each forEach flow item
|
|
1305
|
+
for (const [forEachId, forEachFlowItem] of forEachFlowItemRefs) {
|
|
1306
|
+
forEachFlowItem.loopBodyNodeIds = forEachLoopFlowIds.get(forEachId) ?? [];
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
applyApprovalsToFlow({ flow, edges, approvalByApprovedNodeId, nodeIdToFlowIdMap });
|
|
1310
|
+
|
|
1311
|
+
return flow;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// ---- Approval helpers ----
|
|
1315
|
+
|
|
1316
|
+
interface ApprovalInfo {
|
|
1317
|
+
approvalFlowId: string;
|
|
1318
|
+
approvedFlowId: string;
|
|
1319
|
+
message: string;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function buildApprovalByApprovedNodeIdMap({
|
|
1323
|
+
nodes,
|
|
1324
|
+
edges,
|
|
1325
|
+
nodesInSelectedBranches,
|
|
1326
|
+
selectedOutputNodeIds,
|
|
1327
|
+
}: {
|
|
1328
|
+
nodes: WorkflowEditorNode[];
|
|
1329
|
+
edges: WorkflowEditorEdge[];
|
|
1330
|
+
nodesInSelectedBranches: Set<string>;
|
|
1331
|
+
selectedOutputNodeIds: string[];
|
|
1332
|
+
}): Map<string, ApprovalInfo> {
|
|
1333
|
+
const result = new Map<string, ApprovalInfo>();
|
|
1334
|
+
for (const approvalNode of nodes) {
|
|
1335
|
+
if (approvalNode.type !== 'approval') continue;
|
|
1336
|
+
const approvalEdge = edges.find(
|
|
1337
|
+
(e) => e.source === approvalNode.id && e.sourceHandle === `${approvalNode.id}-source-approval`,
|
|
1338
|
+
);
|
|
1339
|
+
if (!approvalEdge) continue;
|
|
1340
|
+
const approvedNodeId = approvalEdge.target;
|
|
1341
|
+
if (!nodesInSelectedBranches.has(approvedNodeId) && !selectedOutputNodeIds.includes(approvedNodeId))
|
|
1342
|
+
continue;
|
|
1343
|
+
const approvedNode = nodes.find((n) => n.id === approvedNodeId);
|
|
1344
|
+
if (!approvedNode) continue;
|
|
1345
|
+
const approvedFlowId =
|
|
1346
|
+
approvedNode.type === 'forEachEnd' ?
|
|
1347
|
+
(approvedNode.data as { parentNodeId?: string }).parentNodeId ?
|
|
1348
|
+
nodeIdToFlowId((approvedNode.data as { parentNodeId?: string }).parentNodeId!)
|
|
1349
|
+
: undefined
|
|
1350
|
+
: nodeIdToFlowId(approvedNodeId);
|
|
1351
|
+
if (!approvedFlowId) continue;
|
|
1352
|
+
result.set(approvedNodeId, {
|
|
1353
|
+
approvalFlowId: nodeIdToFlowId(approvalNode.id),
|
|
1354
|
+
approvedFlowId,
|
|
1355
|
+
message: (approvalNode.data as { message?: string }).message ?? '',
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
return result;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function applyApprovalsToFlow({
|
|
1362
|
+
flow,
|
|
1363
|
+
edges,
|
|
1364
|
+
approvalByApprovedNodeId,
|
|
1365
|
+
nodeIdToFlowIdMap,
|
|
1366
|
+
}: {
|
|
1367
|
+
flow: WorkflowEditorFlowItem[];
|
|
1368
|
+
edges: WorkflowEditorEdge[];
|
|
1369
|
+
approvalByApprovedNodeId: Map<string, ApprovalInfo>;
|
|
1370
|
+
nodeIdToFlowIdMap: Map<string, string>;
|
|
1371
|
+
}): void {
|
|
1372
|
+
if (approvalByApprovedNodeId.size === 0) return;
|
|
1373
|
+
|
|
1374
|
+
const flowIdToWorkflowNodeId = new Map<string, string>(
|
|
1375
|
+
[...nodeIdToFlowIdMap.entries()].map(([nodeId, flowId]) => [flowId, nodeId]),
|
|
1376
|
+
);
|
|
1377
|
+
|
|
1378
|
+
for (const flowItem of flow) {
|
|
1379
|
+
const workflowNodeId = flowIdToWorkflowNodeId.get(flowItem.id);
|
|
1380
|
+
if (!workflowNodeId) continue;
|
|
1381
|
+
const dependsOn = new Set(flowItem.dependsOn ?? []);
|
|
1382
|
+
const incomingEdges = edges.filter(
|
|
1383
|
+
(e) =>
|
|
1384
|
+
e.source === workflowNodeId &&
|
|
1385
|
+
!(e.data as { isForEachEndEdge?: boolean } | undefined)?.isForEachEndEdge,
|
|
1386
|
+
);
|
|
1387
|
+
for (const edge of incomingEdges) {
|
|
1388
|
+
const approval = approvalByApprovedNodeId.get(edge.target);
|
|
1389
|
+
if (approval) dependsOn.add(approval.approvalFlowId);
|
|
1390
|
+
}
|
|
1391
|
+
if (dependsOn.size > 0) flowItem.dependsOn = [...dependsOn].sort();
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
for (const approvalInfo of approvalByApprovedNodeId.values()) {
|
|
1395
|
+
flow.push({
|
|
1396
|
+
id: approvalInfo.approvalFlowId,
|
|
1397
|
+
type: 'user-approval',
|
|
1398
|
+
dependsOn: [approvalInfo.approvedFlowId],
|
|
1399
|
+
label: approvalInfo.message,
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
}
|