Files
MajoorWaldi-ComfyUI-Majoor-…/ui/app/graphTraversal.ts
T

120 lines
4.8 KiB
TypeScript

type LooseRecord = Record<string, any>;
export type GraphVisit = { graph: LooseRecord; label: string };
export type GraphNodeVisit = { node: LooseRecord; graph: LooseRecord; label: string };
export function getHostRootGraph(app: any = null): LooseRecord | null {
return app?.rootGraph ?? app?.graph?.rootGraph ?? app?.graph ?? app?.canvas?.graph ?? null;
}
export function getGraphNodes(graph: any): LooseRecord[] {
if (!graph || typeof graph !== "object") return [];
if (Array.isArray(graph.nodes)) return graph.nodes.filter(Boolean);
if (Array.isArray(graph._nodes)) return graph._nodes.filter(Boolean);
const byId = graph._nodes_by_id ?? graph.nodes_by_id ?? null;
if (byId instanceof Map) return Array.from(byId.values()).filter(Boolean) as LooseRecord[];
if (byId && typeof byId === "object") return Object.values(byId).filter(Boolean) as LooseRecord[];
return [];
}
export function getGraphLinks(graph: any): any {
return graph?.links ?? graph?._links ?? null;
}
export function getGraphLabel(graph: any, fallback: string): string {
return String(graph?.name ?? graph?.title ?? graph?.id ?? fallback).trim() || fallback;
}
function _pushGraphVisit(out: GraphVisit[], seen: Set<any>, graph: any, label: string): void {
if (!graph || typeof graph !== "object" || seen.has(graph)) return;
seen.add(graph);
out.push({ graph, label });
}
export function getGraphSubgraphs(graph: any): LooseRecord[] {
if (!graph || typeof graph !== "object") return [];
const source = graph.subgraphs ?? graph.definitions?.subgraphs ?? graph.workflow?.definitions?.subgraphs;
if (!source) return [];
if (source instanceof Map) return Array.from(source.values()).filter(Boolean) as LooseRecord[];
if (Array.isArray(source)) return source.filter(Boolean);
if (typeof source === "object") return Object.values(source).filter(Boolean) as LooseRecord[];
return [];
}
export function getNodeSubgraphs(node: any): LooseRecord[] {
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,
].filter((graph): graph is LooseRecord => Boolean(graph && typeof graph === "object" && getGraphNodes(graph).length > 0));
if (Array.isArray(node?.nodes) && node.nodes.length > 0 && node.nodes !== node?.graph?.nodes) {
candidates.push({ nodes: node.nodes });
}
return candidates;
}
function _serializedWorkflowGraphs(graph: any): GraphVisit[] {
const workflow = typeof graph?.serialize === "function" ? graph.serialize() : null;
const subgraphs = Array.isArray(workflow?.definitions?.subgraphs) ? workflow.definitions.subgraphs : [];
return subgraphs.map((subgraph: LooseRecord, index: number) => ({
graph: subgraph,
label: `Subgraph ${getGraphLabel(subgraph, String(subgraph?.id ?? index + 1))}`,
}));
}
export function collectGraphVisits(appOrGraph: any): GraphVisit[] {
const root = appOrGraph?.graph || appOrGraph?.canvas || appOrGraph?.rootGraph ? getHostRootGraph(appOrGraph) : appOrGraph;
const out: GraphVisit[] = [];
const seen = new Set<any>();
const stack: GraphVisit[] = [];
_pushGraphVisit(stack, seen, root, "Workflow");
while (stack.length) {
const current = stack.pop();
if (!current) continue;
out.push(current);
for (const subgraph of getGraphSubgraphs(current.graph)) {
_pushGraphVisit(stack, seen, subgraph, `${current.label} / ${getGraphLabel(subgraph, "Subgraph")}`);
}
for (const node of getGraphNodes(current.graph)) {
for (const subgraph of getNodeSubgraphs(node)) {
_pushGraphVisit(
stack,
seen,
subgraph,
`${current.label} / ${String(node?.title || node?.type || "Subgraph").trim()}`,
);
}
}
}
for (const serialized of _serializedWorkflowGraphs(root)) {
_pushGraphVisit(out, seen, serialized.graph, serialized.label);
}
return out;
}
export function walkGraphNodes(appOrGraph: any, callback: (visit: GraphNodeVisit) => void): void {
for (const visit of collectGraphVisits(appOrGraph)) {
for (const node of getGraphNodes(visit.graph)) {
callback({ node, graph: visit.graph, label: visit.label });
}
}
}
export function findGraphNodeById(appOrGraph: any, nodeId: any): LooseRecord | null {
const wanted = String(nodeId ?? "");
if (!wanted) return null;
let found: LooseRecord | null = null;
walkGraphNodes(appOrGraph, ({ node }) => {
if (!found && String(node?.id ?? "") === wanted) found = node;
});
return found;
}