602 lines
23 KiB
TypeScript
602 lines
23 KiB
TypeScript
import {
|
|
synthesizeWorkflowFromPromptGraph,
|
|
} from "../../../components/sidebar/utils/minimap.js";
|
|
import { fetchComfyApi } from "../../../app/comfyApiBridge.js";
|
|
import {
|
|
getWorkflowNodeDisplayName,
|
|
getWorkflowNodeRawType,
|
|
getWorkflowNodeTypeLabel,
|
|
} from "./workflowNodeLabeling.js";
|
|
|
|
const OBJECT_INFO_CACHE = new Map();
|
|
let OBJECT_INFO_ALL_PROMISE: any = null;
|
|
|
|
export async function ensureWorkflowObjectInfo(workflow: any): Promise<unknown> {
|
|
const types = Array.from(
|
|
new Set(
|
|
getWorkflowNodes(workflow)
|
|
.map((node: any) => getNodeType(node))
|
|
.filter(Boolean),
|
|
),
|
|
);
|
|
if (!types.length) return;
|
|
const missing = types.filter((type: any) => !OBJECT_INFO_CACHE.has(type));
|
|
if (!missing.length) return;
|
|
try {
|
|
if (!OBJECT_INFO_ALL_PROMISE) {
|
|
OBJECT_INFO_ALL_PROMISE = fetchComfyApi("/object_info")
|
|
.then((res: any) => (res?.ok ? res.json() : null))
|
|
.then((data: any) => {
|
|
if (data && typeof data === "object") {
|
|
for (const [key, value] of Object.entries(data)) {
|
|
OBJECT_INFO_CACHE.set(String(key), value);
|
|
}
|
|
}
|
|
return data;
|
|
})
|
|
.catch(() => null);
|
|
}
|
|
await OBJECT_INFO_ALL_PROMISE;
|
|
} catch {
|
|
// Best-effort only. Saved workflow data still renders without object_info.
|
|
}
|
|
}
|
|
|
|
export function resolveAssetWorkflow(asset: any): any {
|
|
const candidates = _collectWorkflowCandidates(asset);
|
|
for (const candidate of candidates) {
|
|
const parsed = _coerceObject(candidate);
|
|
const workflow = _normalizeWorkflow(parsed);
|
|
if (workflow) {
|
|
_decorateWorkflowSubgraphNames(workflow);
|
|
return workflow;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function getWorkflowNodes(workflow: any): unknown[] {
|
|
const nodes = Array.isArray(workflow?.nodes) ? workflow.nodes.filter(Boolean) : [];
|
|
const out = [...nodes];
|
|
const definitions = _getSubgraphDefinitions(workflow);
|
|
for (const node of nodes) {
|
|
const subgraph = _getNodeSubgraph(workflow, node, definitions);
|
|
if (subgraph) out.push(...getWorkflowNodes(subgraph));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function findWorkflowNode(workflow: any, nodeId: any): any {
|
|
const wanted = String(nodeId ?? "");
|
|
if (!wanted) return null;
|
|
if (wanted.includes("::")) {
|
|
const [parentId, ...rest] = wanted.split("::");
|
|
const childId = rest.join("::");
|
|
const parent = findWorkflowNode(workflow, parentId);
|
|
const subgraph = parent ? _getNodeSubgraph(workflow, parent, _getSubgraphDefinitions(workflow)) : null;
|
|
return subgraph ? findWorkflowNode(subgraph, childId) : null;
|
|
}
|
|
const direct =
|
|
(Array.isArray(workflow?.nodes) ? workflow.nodes : []).find(
|
|
(node: any) => String(node?.id ?? node?.ID ?? "") === wanted,
|
|
) || null;
|
|
if (direct) return direct;
|
|
for (const node of Array.isArray(workflow?.nodes) ? workflow.nodes : []) {
|
|
const subgraph = _getNodeSubgraph(workflow, node, _getSubgraphDefinitions(workflow));
|
|
const found = subgraph ? findWorkflowNode(subgraph, wanted) : null;
|
|
if (found) return found;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function getNodeDisplayName(node: any): string {
|
|
return getWorkflowNodeDisplayName(node);
|
|
}
|
|
|
|
export function getNodeType(node: any): string {
|
|
return getWorkflowNodeRawType(node);
|
|
}
|
|
|
|
export function getNodeTypeLabel(node: any) {
|
|
return getWorkflowNodeTypeLabel(node);
|
|
}
|
|
|
|
export function getNodeParamEntries(node: any) {
|
|
const entries = _getNodeValueParamEntries(node);
|
|
const properties = node?.properties && typeof node.properties === "object" ? node.properties : null;
|
|
|
|
if (Array.isArray(node?._mjrSubgraphProxyParams)) {
|
|
for (const entry of node._mjrSubgraphProxyParams) {
|
|
const key = String(entry?.label || entry?.key || "").trim();
|
|
if (!key || entries.some(([existingKey]) => String(existingKey) === key)) continue;
|
|
entries.push([key, entry?.value]);
|
|
}
|
|
}
|
|
|
|
if (properties) {
|
|
for (const [key, value] of Object.entries(properties)) {
|
|
if (_propertyKeyIsTechnical(key)) continue;
|
|
if (value == null || typeof value === "object") continue;
|
|
entries.push([key, value]);
|
|
}
|
|
}
|
|
|
|
return entries.slice(0, 160);
|
|
}
|
|
|
|
function _getNodeValueParamEntries(node: any) {
|
|
const entries = [];
|
|
const inputs = node?.inputs && typeof node.inputs === "object" ? node.inputs : null;
|
|
|
|
if (inputs && !Array.isArray(inputs)) {
|
|
for (const [key, value] of Object.entries(inputs)) {
|
|
if (_valueLooksLikePromptLink(value)) continue;
|
|
entries.push([key, value]);
|
|
}
|
|
}
|
|
|
|
for (const { label, value } of getNodeWidgetValueEntries(node)) {
|
|
if (entries.some(([key]) => String(key) === String(label))) continue;
|
|
entries.push([label, value]);
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
|
|
export function getNodeWidgetValueEntries(node: any) {
|
|
const widgetsValuesRaw = node?.widgets_values;
|
|
if (widgetsValuesRaw && typeof widgetsValuesRaw === "object" && !Array.isArray(widgetsValuesRaw)) {
|
|
return Object.entries(widgetsValuesRaw).map(([label, value], index) => ({ label, value, index }));
|
|
}
|
|
const widgetsValues = Array.isArray(widgetsValuesRaw) ? widgetsValuesRaw : [];
|
|
const widgets = Array.isArray(node?.widgets) ? node.widgets : [];
|
|
const objectInfo = getNodeObjectInfo(node);
|
|
const objectInputNames = getObjectInfoWidgetInputNames(objectInfo);
|
|
const inputSlotNames = getNodeInputSlotNames(node);
|
|
return widgetsValues.map((value, index) => {
|
|
const guessedLabel = _guessWidgetLabel(node, index, value);
|
|
const label =
|
|
widgets[index]?.name ||
|
|
widgets[index]?.label ||
|
|
objectInputNames[index] ||
|
|
inputSlotNames[index] ||
|
|
guessedLabel ||
|
|
`param ${index + 1}`;
|
|
return { label, value, index };
|
|
});
|
|
}
|
|
|
|
export function getNodeInputSlotNames(node: any) {
|
|
const inputs = Array.isArray(node?.inputs) ? node.inputs : [];
|
|
const widgetInputs = inputs.filter(_inputLooksLikeWidget);
|
|
const unlinkedWidgetLikeInputs = inputs.filter(
|
|
(input: any) => !_inputHasLink(input) && _inputTypeLooksWidgetCapable(input?.type),
|
|
);
|
|
const labelSource: any[] = [];
|
|
const seen = new Set();
|
|
const addInput = (input: any) => {
|
|
const key = `${String(input?.name || "")}\u0000${String(input?.label || "")}\u0000${String(input?.link ?? "")}`;
|
|
if (seen.has(key)) return;
|
|
seen.add(key);
|
|
labelSource.push(input);
|
|
};
|
|
for (const input of widgetInputs) addInput(input);
|
|
for (const input of unlinkedWidgetLikeInputs) addInput(input);
|
|
return labelSource.map((input: any) =>
|
|
String(
|
|
input?.label ||
|
|
input?.localized_name ||
|
|
input?.name ||
|
|
input?.widget?.name ||
|
|
input?.widget?.label ||
|
|
"",
|
|
).trim(),
|
|
);
|
|
}
|
|
|
|
function _valueLooksLikePromptLink(value: any) {
|
|
return (
|
|
Array.isArray(value) &&
|
|
value.length === 2 &&
|
|
String(value[0] ?? "").trim() !== "" &&
|
|
Number.isFinite(Number(value[1]))
|
|
);
|
|
}
|
|
|
|
function _propertyKeyIsTechnical(key: any) {
|
|
const text = String(key ?? "").trim();
|
|
const normalized = _normalizeParamName(text);
|
|
if (!normalized) return true;
|
|
return (
|
|
normalized === "cnr_id" ||
|
|
normalized === "ver" ||
|
|
normalized === "node_name_for_s&r" ||
|
|
normalized === "subgraph_name" ||
|
|
normalized === "subgraph_id" ||
|
|
normalized === "enabletabs" ||
|
|
normalized === "tabwidth" ||
|
|
normalized === "tabxoffset" ||
|
|
normalized === "hassecondtab" ||
|
|
normalized === "secondtabtext" ||
|
|
normalized === "secondtaboffset" ||
|
|
normalized === "secondtabwidth" ||
|
|
normalized.startsWith("ue_")
|
|
);
|
|
}
|
|
|
|
export function getNodeObjectInfo(node: any) {
|
|
const type = getNodeType(node);
|
|
return type ? OBJECT_INFO_CACHE.get(type) || null : null;
|
|
}
|
|
|
|
export function getObjectInfoInputNames(info: any) {
|
|
const order = info?.input_order;
|
|
if (order && typeof order === "object") {
|
|
return [
|
|
...(Array.isArray(order.required) ? order.required : []),
|
|
...(Array.isArray(order.optional) ? order.optional : []),
|
|
].filter(Boolean);
|
|
}
|
|
const input = info?.input;
|
|
if (input && typeof input === "object") {
|
|
return ["required", "optional"]
|
|
.flatMap((section: any) =>
|
|
input[section] && typeof input[section] === "object"
|
|
? Object.keys(input[section])
|
|
: [],
|
|
)
|
|
.filter(Boolean);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
export function getObjectInfoWidgetInputNames(info: any) {
|
|
const input = info?.input;
|
|
if (!input || typeof input !== "object") return getObjectInfoInputNames(info);
|
|
const out = [];
|
|
for (const section of ["required", "optional"]) {
|
|
const block = input[section];
|
|
if (!block || typeof block !== "object") continue;
|
|
for (const [name, config] of Object.entries(block)) {
|
|
if (_objectInfoInputLooksWidget(config)) out.push(name);
|
|
}
|
|
}
|
|
return out.length ? out : getObjectInfoInputNames(info);
|
|
}
|
|
|
|
function _inputLooksLikeWidget(input: any) {
|
|
if (!input || typeof input !== "object") return false;
|
|
if (input.widget === true) return true;
|
|
if (input.widget && typeof input.widget === "object") return true;
|
|
if (typeof input.widget === "string" && input.widget.trim()) return true;
|
|
return false;
|
|
}
|
|
|
|
function _inputHasLink(input: any) {
|
|
if (!input || typeof input !== "object") return false;
|
|
if (input.link != null) return true;
|
|
if (Array.isArray(input.links) && input.links.length) return true;
|
|
return false;
|
|
}
|
|
|
|
function _objectInfoInputLooksWidget(config: any) {
|
|
const tuple = Array.isArray(config) ? config : [];
|
|
const type = tuple[0];
|
|
const options =
|
|
tuple[1] && typeof tuple[1] === "object" && !Array.isArray(tuple[1]) ? tuple[1] : null;
|
|
if (options?.forceInput === true) return false;
|
|
if (options?.rawLink === true) return false;
|
|
if (options?.widgetType && String(options.widgetType).trim()) return true;
|
|
return _inputTypeLooksWidgetCapable(type);
|
|
}
|
|
|
|
function _inputTypeLooksWidgetCapable(type: any) {
|
|
if (Array.isArray(type)) return true;
|
|
const text = String(type || "").trim().toUpperCase();
|
|
if (!text) return false;
|
|
return (
|
|
text === "INT" ||
|
|
text === "FLOAT" ||
|
|
text === "STRING" ||
|
|
text === "BOOLEAN" ||
|
|
text === "BOOL" ||
|
|
text === "COMBO" ||
|
|
text === "ENUM"
|
|
);
|
|
}
|
|
|
|
function _guessWidgetLabel(node: any, index: any, value: any) {
|
|
const type = getNodeType(node);
|
|
const title = getNodeDisplayName(node);
|
|
const haystack = `${type} ${title}`.toLowerCase();
|
|
const valueText = String(value ?? "").toLowerCase();
|
|
if (haystack.includes("ksamplerselect")) return "sampler_name";
|
|
if (haystack.includes("manualsigmas")) return "sigmas";
|
|
if (haystack.includes("randomnoise")) return index === 0 ? "noise_seed" : index === 1 ? "control_after_generate" : null;
|
|
if (haystack.includes("cfgguider")) return "cfg";
|
|
if (haystack.includes("loraloadermodelonly")) return index === 0 ? "lora_name" : index === 1 ? "strength_model" : null;
|
|
if (haystack.includes("resizeimagesbylongeredge")) return "longer_edge";
|
|
if (haystack.includes("vaedecodetiled")) {
|
|
return ["tile_size", "overlap", "temporal_size", "temporal_overlap"][index] || null;
|
|
}
|
|
if (haystack.includes("textencoderloader") || haystack.includes("text encoder loader")) {
|
|
if (index === 0) return "text_encoder";
|
|
if (index === 1) return "ckpt_name";
|
|
if (index === 2) return "device";
|
|
}
|
|
if (/primitive(?:int|float|string|boolean)|constant/.test(haystack) && index === 0) {
|
|
const titleLabel = _normalizeParamName(title);
|
|
return titleLabel && titleLabel !== _normalizeParamName(type) ? titleLabel : "value";
|
|
}
|
|
if (haystack.includes("cliptext") || haystack.includes("prompt")) return index === 0 ? "text" : null;
|
|
if (valueText.includes(".safetensors") || valueText.includes(".ckpt")) return "model";
|
|
if (typeof value === "number") {
|
|
if (haystack.includes("sampler") && index === 0) return "seed";
|
|
if (haystack.includes("sampler") && index === 1) return "steps";
|
|
if (haystack.includes("latent") && index === 0) return "width";
|
|
if (haystack.includes("latent") && index === 1) return "height";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function _collectWorkflowCandidates(asset: any) {
|
|
const raw = _coerceObject(asset?.metadata_raw);
|
|
const meta = _coerceObject(asset?.metadata);
|
|
return [
|
|
asset?.workflow,
|
|
asset?.Workflow,
|
|
asset?.comfy_workflow,
|
|
asset?.workflow_json,
|
|
asset?.template,
|
|
asset?.Template,
|
|
asset?.comfy_template,
|
|
asset?.subgraph,
|
|
asset?.Subgraph,
|
|
asset?.comfy_subgraph,
|
|
raw?.workflow,
|
|
raw?.Workflow,
|
|
raw?.comfy_workflow,
|
|
raw?.workflow_json,
|
|
raw?.template,
|
|
raw?.Template,
|
|
raw?.comfy_template,
|
|
raw?.subgraph,
|
|
raw?.Subgraph,
|
|
raw?.comfy_subgraph,
|
|
raw?.comfyui,
|
|
raw?.ComfyUI,
|
|
meta?.workflow,
|
|
meta?.Workflow,
|
|
meta?.comfy_workflow,
|
|
meta?.workflow_json,
|
|
meta?.template,
|
|
meta?.Template,
|
|
meta?.comfy_template,
|
|
meta?.subgraph,
|
|
meta?.Subgraph,
|
|
meta?.comfy_subgraph,
|
|
asset?.prompt,
|
|
raw?.prompt,
|
|
raw?.Prompt,
|
|
meta?.prompt,
|
|
meta?.Prompt,
|
|
].filter((value: any) => value != null);
|
|
}
|
|
|
|
function _coerceObject(value: any) {
|
|
if (!value) return null;
|
|
if (typeof value === "object") return value;
|
|
if (typeof value !== "string") return null;
|
|
const trimmed = value.trim();
|
|
if (!trimmed || !/^[{[]/.test(trimmed)) return null;
|
|
try {
|
|
return JSON.parse(trimmed);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function _normalizeWorkflow(value: any) {
|
|
if (!value || typeof value !== "object") return null;
|
|
if (Array.isArray(value.nodes)) return value;
|
|
const graphLike = _getFirstGraphLikeObject(value);
|
|
if (graphLike) {
|
|
return graphLike;
|
|
}
|
|
if (value.prompt && typeof value.prompt === "object") {
|
|
return synthesizeWorkflowFromPromptGraph(value.prompt);
|
|
}
|
|
const synthesized = synthesizeWorkflowFromPromptGraph(value);
|
|
return synthesized && Array.isArray(synthesized.nodes) ? synthesized : null;
|
|
}
|
|
|
|
function _getFirstGraphLikeObject(value: any) {
|
|
for (const key of ["workflow", "Workflow", "template", "Template", "subgraph", "Subgraph", "graph", "lgraph"]) {
|
|
const candidate = value?.[key];
|
|
if (candidate && typeof candidate === "object" && Array.isArray(candidate.nodes)) return candidate;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function _decorateWorkflowSubgraphNames(workflow: any, visited = new WeakSet()) {
|
|
if (!workflow || typeof workflow !== "object") return;
|
|
if (visited.has(workflow)) return;
|
|
visited.add(workflow);
|
|
const definitions = _getSubgraphDefinitions(workflow);
|
|
for (const node of Array.isArray(workflow?.nodes) ? workflow.nodes : []) {
|
|
_applySubgraphDefinitionMetadata(node, definitions);
|
|
const subgraph = _getNodeSubgraph(workflow, node, definitions);
|
|
if (subgraph) {
|
|
_decorateSubgraphProxyParams(node, subgraph);
|
|
_decorateWorkflowSubgraphNames(subgraph, visited);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _applySubgraphDefinitionMetadata(node: any, definitions: any) {
|
|
if (!node || typeof node !== "object") return;
|
|
const definitionKey = _getNodeSubgraphDefinitionKeys(node).find((key) => definitions.has(String(key)));
|
|
if (!definitionKey) return;
|
|
const definition = definitions.get(String(definitionKey));
|
|
const name = String(
|
|
definition?.name || definition?.title || node?.subgraph?.name || node?.subgraph_instance?.name || "",
|
|
).trim();
|
|
if (!name) return;
|
|
const props = node?.properties && typeof node.properties === "object" ? node.properties : (node.properties = {});
|
|
if (!String(props.subgraph_name || "").trim()) props.subgraph_name = name;
|
|
if (!String(props.subgraph_id || "").trim()) props.subgraph_id = definitionKey;
|
|
}
|
|
|
|
function _decorateSubgraphProxyParams(node: any, subgraph: any) {
|
|
const proxyWidgets = Array.isArray(node?.properties?.proxyWidgets) ? node.properties.proxyWidgets : [];
|
|
if (!proxyWidgets.length || !Array.isArray(subgraph?.nodes)) return;
|
|
const nodeById = new Map(
|
|
subgraph.nodes
|
|
.filter(Boolean)
|
|
.map((innerNode: any) => [String(innerNode?.id ?? innerNode?.ID ?? ""), innerNode]),
|
|
);
|
|
const exposedInputNames = _getExposedSubgraphWidgetInputNames(node);
|
|
const inputLabelsByTarget = _buildSubgraphInputLabelsByTarget(subgraph);
|
|
const params = [];
|
|
for (let index = 0; index < proxyWidgets.length; index += 1) {
|
|
const proxy = proxyWidgets[index];
|
|
const innerId = Array.isArray(proxy) ? proxy[0] : proxy?.nodeId ?? proxy?.node_id ?? proxy?.id;
|
|
const widgetName = Array.isArray(proxy) ? proxy[1] : proxy?.widget ?? proxy?.name ?? proxy?.widgetName;
|
|
const innerNode = nodeById.get(String(innerId ?? ""));
|
|
if (!innerNode) continue;
|
|
const innerEntries = getNodeParamEntries(innerNode);
|
|
if (!innerEntries.length) continue;
|
|
const matched =
|
|
innerEntries.find(([key]) => _normalizeParamName(key) === _normalizeParamName(widgetName)) ||
|
|
innerEntries.find(([key]) => _normalizeParamName(key) === "value") ||
|
|
(innerEntries.length === 1 ? innerEntries[0] : null);
|
|
if (!matched) continue;
|
|
const targetKey = `${String(innerId)}:${_getProxyTargetSlot(proxy, widgetName, matched[0])}`;
|
|
const label =
|
|
inputLabelsByTarget.get(targetKey) ||
|
|
inputLabelsByTarget.get(String(innerId)) ||
|
|
_getReadableProxyLabel(innerNode, matched[0], widgetName);
|
|
if (exposedInputNames.size && !exposedInputNames.has(_normalizeParamName(label))) continue;
|
|
params.push({ label, value: matched[1], innerNodeId: innerId, widgetName });
|
|
}
|
|
if (params.length) node._mjrSubgraphProxyParams = params;
|
|
}
|
|
|
|
function _getExposedSubgraphWidgetInputNames(node: any) {
|
|
const inputs = Array.isArray(node?.inputs) ? node.inputs : [];
|
|
const names = new Set();
|
|
for (const input of inputs) {
|
|
if (!_inputLooksLikeWidget(input)) continue;
|
|
const label = String(input?.label || input?.localized_name || input?.name || "").trim();
|
|
if (label) names.add(_normalizeParamName(label));
|
|
}
|
|
return names;
|
|
}
|
|
|
|
function _buildSubgraphInputLabelsByTarget(subgraph: any) {
|
|
const inputs = Array.isArray(subgraph?.inputs) ? subgraph.inputs : [];
|
|
const links = Array.isArray(subgraph?.links) ? subgraph.links : [];
|
|
const map = new Map();
|
|
for (const link of links) {
|
|
const originId = Array.isArray(link) ? link[1] : link?.origin_id ?? link?.originId ?? link?.from;
|
|
if (String(originId) !== "-10") continue;
|
|
const originSlot = Number(Array.isArray(link) ? link[2] : link?.origin_slot ?? link?.originSlot ?? link?.fromSlot);
|
|
const targetId = Array.isArray(link) ? link[3] : link?.target_id ?? link?.targetId ?? link?.to;
|
|
const targetSlot = Number(Array.isArray(link) ? link[4] : link?.target_slot ?? link?.targetSlot ?? link?.toSlot);
|
|
const input = Number.isFinite(originSlot) ? inputs[originSlot] : null;
|
|
const label = String(input?.label || input?.localized_name || input?.name || "").trim();
|
|
if (!label || targetId == null) continue;
|
|
if (_normalizeParamName(label) === "value") continue;
|
|
map.set(String(targetId), label);
|
|
if (Number.isFinite(targetSlot)) map.set(`${String(targetId)}:${targetSlot}`, label);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
function _getProxyTargetSlot(proxy: any, widgetName: any, matchedKey: any) {
|
|
if (proxy && typeof proxy === "object" && !Array.isArray(proxy)) {
|
|
const slot = proxy.target_slot ?? proxy.targetSlot ?? proxy.slot;
|
|
if (Number.isFinite(Number(slot))) return Number(slot);
|
|
}
|
|
void widgetName;
|
|
void matchedKey;
|
|
return 0;
|
|
}
|
|
|
|
function _getReadableProxyLabel(innerNode: any, matchedKey: any, widgetName: any) {
|
|
const title = String(getNodeDisplayName(innerNode) || "").trim();
|
|
const key = String(matchedKey || widgetName || "").trim();
|
|
if (title && key && _normalizeParamName(key) !== "value") return `${title} ${key}`;
|
|
return title || key || "param";
|
|
}
|
|
|
|
function _normalizeParamName(value: any) {
|
|
return String(value ?? "")
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/\s+/g, "_");
|
|
}
|
|
|
|
function _getSubgraphDefinitions(workflow: any) {
|
|
const defs = [
|
|
...(Array.isArray(workflow?.definitions?.subgraphs) ? workflow.definitions.subgraphs : []),
|
|
...(Array.isArray(workflow?.subgraphs) ? workflow.subgraphs : []),
|
|
...(Array.isArray(workflow?.rootGraph?.subgraphs) ? workflow.rootGraph.subgraphs : []),
|
|
];
|
|
const map = new Map();
|
|
for (const def of defs) {
|
|
for (const id of _getSubgraphDefinitionKeys(def)) {
|
|
if (id != null) map.set(String(id), def);
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
function _getSubgraphDefinitionKeys(def: any) {
|
|
const props = def?.properties && typeof def.properties === "object" ? def.properties : {};
|
|
return [
|
|
def?.id,
|
|
def?.name,
|
|
def?.title,
|
|
def?.type,
|
|
def?.uuid,
|
|
def?.workflowId,
|
|
def?.workflow_id,
|
|
props.subgraph_id,
|
|
props.subgraphId,
|
|
].filter((id) => id != null && String(id).trim());
|
|
}
|
|
|
|
function _getNodeSubgraphDefinitionKeys(node: any) {
|
|
const props = node?.properties && typeof node.properties === "object" ? node.properties : {};
|
|
return [
|
|
node?.type,
|
|
node?.class_type,
|
|
node?.subgraph_id,
|
|
node?.subgraphId,
|
|
props.subgraph_id,
|
|
props.subgraphId,
|
|
props.subgraph_name,
|
|
].filter((id) => id != null && String(id).trim());
|
|
}
|
|
|
|
function _getNodeSubgraph(workflow: any, node: any, definitions = _getSubgraphDefinitions(workflow)) {
|
|
const candidates = [
|
|
node?.subgraph,
|
|
node?._subgraph,
|
|
node?.subgraph?.graph,
|
|
node?.subgraph?.lgraph,
|
|
node?.properties?.subgraph,
|
|
node?.subgraph_instance,
|
|
node?.subgraph_instance?.graph,
|
|
node?.inner_graph,
|
|
node?.subgraph_graph,
|
|
..._getNodeSubgraphDefinitionKeys(node).map((key) => definitions.get(String(key))),
|
|
];
|
|
for (const candidate of candidates) {
|
|
if (candidate && typeof candidate === "object" && Array.isArray(candidate.nodes)) return candidate;
|
|
}
|
|
if (Array.isArray(node?.nodes)) return { nodes: node.nodes };
|
|
return null;
|
|
}
|