From 0c74b3af59aa5d6c52c656e692fe5117853682fd Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Tue, 16 May 2023 11:02:03 -0500 Subject: [PATCH] Graphvis debug view --- package.json | 2 +- pnpm-lock.yaml | 140 +++++++++++++++++--- src/lib/components/ComfyPromptSerializer.ts | 40 +++--- src/lib/utils.ts | 101 +++++++++++++- src/tests/ComfyPromptSerializerTests.ts | 19 ++- vite.config.ts | 2 +- 6 files changed, 262 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index f1e98d3..7b9d40a 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "vite-plugin-static-copy": "^0.14.0", "vite-plugin-svelte-console-remover": "^1.0.10", "vite-tsconfig-paths": "^4.0.8", - "vitest": "^0.25.8" + "vitest": "^0.27.3" }, "type": "module", "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ccc617f..50d3a51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,8 +183,8 @@ importers: specifier: ^4.0.8 version: 4.0.8(typescript@5.0.3)(vite@4.3.1) vitest: - specifier: ^0.25.8 - version: 0.25.8(happy-dom@9.18.3)(jsdom@22.0.0)(sass@1.61.0) + specifier: ^0.27.3 + version: 0.27.3(happy-dom@9.18.3)(jsdom@22.0.0)(sass@1.61.0) gradio/client/js: dependencies: @@ -780,7 +780,7 @@ importers: version: 4.10.1(postcss-load-config@3.1.4)(postcss@8.4.21)(svelte@3.58.0)(typescript@4.5.4) tailwindcss: specifier: ^3.0.12 - version: 3.3.1 + version: 3.3.1(postcss@8.4.21) tslib: specifier: ^2.3.1 version: 2.5.0 @@ -3725,7 +3725,6 @@ packages: /cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - dev: false /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} @@ -6321,7 +6320,6 @@ packages: /jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} - dev: false /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -6721,7 +6719,6 @@ packages: pathe: 1.1.0 pkg-types: 1.0.3 ufo: 1.1.2 - dev: false /mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} @@ -7047,9 +7044,12 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + /pathe@0.2.0: + resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} + dev: true + /pathe@1.1.0: resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} - dev: false /pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} @@ -7086,7 +7086,6 @@ packages: jsonc-parser: 3.2.0 mlly: 1.2.1 pathe: 1.1.0 - dev: false /plotly.js-dist-min@2.10.1: resolution: {integrity: sha512-H0ls1C2uu2U+qWw76djo4/zOGtUKfMILwFhu7tCOaG/wH5ypujrYGCH03N9SQVf1SXcctTfW57USf8LmagSiPQ==} @@ -7106,6 +7105,17 @@ packages: prettier: 2.8.7 dev: false + /postcss-import@14.1.0: + resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} + engines: {node: '>=10.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.2 + dev: false + /postcss-import@14.1.0(postcss@8.4.21): resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} @@ -7116,6 +7126,16 @@ packages: postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.2 + dev: true + + /postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + dev: false /postcss-js@4.0.1(postcss@8.4.21): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} @@ -7125,6 +7145,23 @@ packages: dependencies: camelcase-css: 2.0.1 postcss: 8.4.21 + dev: true + + /postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + dev: false /postcss-load-config@3.1.4(postcss@8.4.21): resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} @@ -7141,6 +7178,16 @@ packages: lilconfig: 2.1.0 postcss: 8.4.21 yaml: 1.10.2 + dev: true + + /postcss-nested@6.0.0: + resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss-selector-parser: 6.0.11 + dev: false /postcss-nested@6.0.0(postcss@8.4.21): resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} @@ -7150,6 +7197,7 @@ packages: dependencies: postcss: 8.4.21 postcss-selector-parser: 6.0.11 + dev: true /postcss-prefix-selector@1.16.0(postcss@8.4.21): resolution: {integrity: sha512-rdVMIi7Q4B0XbXqNUEI+Z4E+pueiu/CS5E6vRCQommzdQ/sgsS4dK42U7GX8oJR+TJOtT+Qv3GkNo6iijUMp3Q==} @@ -7627,7 +7675,6 @@ packages: /siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - dev: false /sigmund@1.0.1: resolution: {integrity: sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==} @@ -7694,7 +7741,6 @@ packages: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - dev: false /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} @@ -7752,7 +7798,6 @@ packages: /stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - dev: false /standardized-audio-context@25.3.45: resolution: {integrity: sha512-d1UVvbz0mDmEqNehvoTKlpSevRJ3YiVZ6kdboeaWX+8cl94H1w8x7c5RNdg0nqxiE049LMeF4tFPuDl5Vm78Kg==} @@ -7764,7 +7809,6 @@ packages: /std-env@3.3.3: resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==} - dev: false /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} @@ -8205,6 +8249,43 @@ packages: resolution: {integrity: sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==} engines: {node: '>=12.13.0'} hasBin: true + peerDependencies: + postcss: ^8.0.9 + dependencies: + arg: 5.0.2 + chokidar: 3.5.3 + color-name: 1.1.4 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.2.12 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.18.2 + lilconfig: 2.1.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.21 + postcss-import: 14.1.0 + postcss-js: 4.0.1 + postcss-load-config: 3.1.4 + postcss-nested: 6.0.0 + postcss-selector-parser: 6.0.11 + postcss-value-parser: 4.2.0 + quick-lru: 5.1.1 + resolve: 1.22.2 + sucrase: 3.32.0 + transitivePeerDependencies: + - ts-node + dev: false + + /tailwindcss@3.3.1(postcss@8.4.21): + resolution: {integrity: sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==} + engines: {node: '>=12.13.0'} + hasBin: true + peerDependencies: + postcss: ^8.0.9 dependencies: arg: 5.0.2 chokidar: 3.5.3 @@ -8232,6 +8313,7 @@ packages: sucrase: 3.32.0 transitivePeerDependencies: - ts-node + dev: true /term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} @@ -8488,7 +8570,6 @@ packages: /ufo@1.1.2: resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} - dev: false /undici@5.20.0: resolution: {integrity: sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==} @@ -8969,6 +9050,29 @@ packages: extsprintf: 1.3.0 dev: false + /vite-node@0.27.3(@types/node@18.16.0)(sass@1.61.0): + resolution: {integrity: sha512-eyJYOO64o5HIp8poc4bJX+ZNBwMZeI3f6/JdiUmJgW02Mt7LnoCtDMRVmLaY9S05SIsjGe339ZK4uo2wQ+bF9g==} + engines: {node: '>=v14.16.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + mlly: 1.2.1 + pathe: 0.2.0 + picocolors: 1.0.0 + source-map: 0.6.1 + source-map-support: 0.5.21 + vite: 4.3.1(@types/node@18.16.0)(sass@1.61.0) + transitivePeerDependencies: + - '@types/node' + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-node@0.31.0(@types/node@18.16.0): resolution: {integrity: sha512-8x1x1LNuPvE2vIvkSB7c1mApX5oqlgsxzHQesYF7l5n1gKrEmrClIiZuOFbFDQcjLsmcWSwwmrWrcGWm9Fxc/g==} engines: {node: '>=v14.18.0'} @@ -9362,8 +9466,8 @@ packages: vite: 4.3.1(sass@1.61.0) dev: false - /vitest@0.25.8(happy-dom@9.18.3)(jsdom@22.0.0)(sass@1.61.0): - resolution: {integrity: sha512-X75TApG2wZTJn299E/TIYevr4E9/nBo1sUtZzn0Ci5oK8qnpZAZyhwg0qCeMSakGIWtc6oRwcQFyFfW14aOFWg==} + /vitest@0.27.3(happy-dom@9.18.3)(jsdom@22.0.0)(sass@1.61.0): + resolution: {integrity: sha512-Ld3UVgRVhJUtqvQ3dW89GxiApFAgBsWJZBCWzK+gA3w2yG68csXlGZZ4WDJURf+8ecNfgrScga6xY+8YSOpiMg==} engines: {node: '>=v14.16.0'} hasBin: true peerDependencies: @@ -9389,17 +9493,22 @@ packages: '@types/node': 18.16.0 acorn: 8.8.2 acorn-walk: 8.2.0 + cac: 6.7.14 chai: 4.3.7 debug: 4.3.4 happy-dom: 9.18.3 jsdom: 22.0.0 local-pkg: 0.4.3 + picocolors: 1.0.0 source-map: 0.6.1 + std-env: 3.3.3 strip-literal: 1.0.1 tinybench: 2.4.0 tinypool: 0.3.1 tinyspy: 1.1.1 vite: 4.3.1(@types/node@18.16.0)(sass@1.61.0) + vite-node: 0.27.3(@types/node@18.16.0)(sass@1.61.0) + why-is-node-running: 2.2.2 transitivePeerDependencies: - less - sass @@ -9591,7 +9700,6 @@ packages: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - dev: false /word-wrap@1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} diff --git a/src/lib/components/ComfyPromptSerializer.ts b/src/lib/components/ComfyPromptSerializer.ts index d416a64..e0d038a 100644 --- a/src/lib/components/ComfyPromptSerializer.ts +++ b/src/lib/components/ComfyPromptSerializer.ts @@ -54,6 +54,23 @@ function followGraphInput(graphInput: GraphInput, link: LLink): LLink | null { return nextLink; } +function getUpstreamLink(parent: LGraphNode, currentLink: LLink): LLink | null { + if (parent.is(Subgraph)) { + console.warn("FollowSubgraph") + return followSubgraph(parent, currentLink); + } + else if (parent.is(GraphInput)) { + console.warn("FollowGraphInput") + + return followGraphInput(parent, currentLink); + } + else if ("getUpstreamLink" in parent) { + return (parent as ComfyGraphNode).getUpstreamLink(); + } + console.warn("[graphToPrompt] Node does not support getUpstreamLink", parent.type) + return null; +} + export default class ComfyPromptSerializer { serializeInputValues(node: ComfyBackendNode): Record { // Store input values passed by frontend-only nodes @@ -118,7 +135,7 @@ export default class ComfyPromptSerializer { let parent: ComfyGraphNode = node.getInputNode(i) as ComfyGraphNode; if (parent) { const seen = {} - let link = node.getInputLink(i); + let currentLink = node.getInputLink(i); const isFrontendParent = (parent: ComfyGraphNode) => { if (!parent || parent.isBackendNode) @@ -135,20 +152,7 @@ export default class ComfyPromptSerializer { // nodes have conditional logic that determines which link // to follow backwards. while (isFrontendParent(parent)) { - let nextLink = null; - if (parent.is(Subgraph)) { - nextLink = followSubgraph(parent, link); - } - else if (parent.is(GraphInput)) { - nextLink = followGraphInput(parent, link); - } - else if ("getUpstreamLink" in parent) { - nextLink = parent.getUpstreamLink(); - } - else { - console.warn("[graphToPrompt] Node does not support getUpstreamLink", parent.type) - break; - } + const nextLink = getUpstreamLink(parent, currentLink); if (nextLink == null) { console.warn("[graphToPrompt] No upstream link found in frontend node", parent) @@ -164,7 +168,7 @@ export default class ComfyPromptSerializer { } else { console.debug("[graphToPrompt] Traverse upstream link", parent.id, inputNode?.id, inputNode?.isBackendNode) - link = nextLink; + currentLink = nextLink; parent = inputNode; } } else { @@ -172,7 +176,7 @@ export default class ComfyPromptSerializer { } } - if (link && parent && parent.isBackendNode) { + if (currentLink && parent && parent.isBackendNode) { if (tag && !hasTag(parent, tag)) continue; @@ -181,7 +185,7 @@ export default class ComfyPromptSerializer { // TODO can null be a legitimate value in some cases? // Nodes like CLIPLoader will never have a value in the frontend, hence "null". if (!(input.name in inputs)) - inputs[input.name] = [String(link.origin_id), link.origin_slot]; + inputs[input.name] = [String(currentLink.origin_id), currentLink.origin_slot]; } } } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9c0cdc1..31b5c23 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -5,7 +5,7 @@ import TextWidget from "$lib/widgets/TextWidget.svelte"; import { get } from "svelte/store" import layoutState from "$lib/stores/layoutState" import type { SvelteComponentDev } from "svelte/internal"; -import type { SerializedLGraph } from "@litegraph-ts/core"; +import { Subgraph, type LGraph, type LGraphNode, type LLink, type SerializedLGraph, type UUID, GraphInput } from "@litegraph-ts/core"; import type { FileNameOrGalleryData, ComfyExecutionResult, ComfyImageLocation } from "./nodes/ComfyWidgetNodes"; import type { FileData as GradioFileData } from "@gradio/upload"; @@ -21,6 +21,13 @@ export function range(size: number, startAt: number = 0): ReadonlyArray return [...Array(size).keys()].map(i => i + startAt); } +export function* enumerate(iterable: Iterable): Iterable<[number, T]> { + let index = 0; + for (const value of iterable) { + yield [index++, value]; + } +} + export function download(filename: string, text: string, type: string = "text/plain") { const blob = new Blob([text], { type: type }); const url = URL.createObjectURL(blob); @@ -68,6 +75,98 @@ export function startDrag(evt: MouseEvent) { export function stopDrag(evt: MouseEvent) { }; +export function graphToGraphVis(graph: LGraph): string { + let links: string[] = [] + let seenLinks = new Set() + let subgraphs: Record = {} + let subgraphNodes: Record = {} + let idToInt: Record = {} + let curId = 0; + + const convId = (id: number | UUID): number => { + if (idToInt[id] == null) { + idToInt[id] = curId++; + } + return idToInt[id]; + } + + const addLink = (node: LGraphNode, link: LLink): string => { + const nodeA = node.graph.getNodeById(link.origin_id) + const nodeB = node.graph.getNodeById(link.target_id); + seenLinks.add(link.id) + return ` "${convId(nodeA.id)}_${nodeA.title}" -> "${convId(nodeB.id)}_${nodeB.title}";\n`; + } + + for (const node of graph.iterateNodesInOrderRecursive()) { + for (let [index, input] of enumerate(node.iterateInputInfo())) { + const link = node.getInputLink(index); + if (link && !seenLinks.has(link.id)) { + const linkText = addLink(node, link) + if (node.graph != graph) { + subgraphs[node.graph._subgraph_node.id] ||= [node.graph._subgraph_node, []] + subgraphs[node.graph._subgraph_node.id][1].push(linkText) + subgraphNodes[node.graph._subgraph_node.id] = node.graph._subgraph_node + } + else { + links.push(linkText) + } + } + } + for (let [index, output] of enumerate(node.iterateOutputInfo())) { + for (const link of node.getOutputLinks(index)) { + if (!seenLinks.has(link.id)) { + const linkText = addLink(node, link) + if (node.graph != graph) { + subgraphs[node.graph._subgraph_node.id] ||= [node.graph._subgraph_node, []] + subgraphs[node.graph._subgraph_node.id][1].push(linkText) + subgraphNodes[node.graph._subgraph_node.id] = node.graph._subgraph_node + } + else if (!node.is(Subgraph) && !node.graph.getNodeById(link.target_id)?.is(Subgraph)) { + links.push(linkText) + } + } + } + } + } + + let out = "digraph {\n" + out += " node [shape=box];\n" + + for (const [subgraph, links] of Object.values(subgraphs)) { + // Subgraph name has to be prefixed with "cluster" to show up as a cluster... + out += ` subgraph cluster_subgraph_${convId(subgraph.id)} {\n` + out += ` label="${convId(subgraph.id)}: ${subgraph.title}";\n`; + out += " color=red;\n"; + // out += " style=grey;\n"; + out += " node [style=filled,fillcolor=white];\n"; + out += " " + links.join(" ") + out += " }\n" + } + + out += links.join("") + + for (const subgraphNode of Object.values(subgraphNodes)) { + for (const [index, input] of enumerate(subgraphNode.iterateInputInfo())) { + const link = subgraphNode.getInputLink(index); + if (link) { + const inputNode = subgraphNode.getInputNode(link.origin_slot); + const innerInput = subgraphNode.getInnerGraphInputByIndex(index); + out += ` "${convId(link.origin_id)}_${inputNode.title}" -> "${convId(innerInput.id)}_${innerInput.title}";\n` + } + } + for (const [index, output] of enumerate(subgraphNode.iterateOutputInfo())) { + for (const link of subgraphNode.getOutputLinks(index)) { + const outputNode = subgraphNode.graph.getNodeById(link.target_id) + const innerOutput = subgraphNode.getInnerGraphOutputByIndex(index); + out += ` "${convId(innerOutput.id)}_${innerOutput.title}" -> "${convId(link.origin_id)}_${outputNode.title}";\n` + } + } + } + + out += "}" + return out +} + export function workflowToGraphVis(workflow: SerializedLGraph): string { let out = "digraph {\n" diff --git a/src/tests/ComfyPromptSerializerTests.ts b/src/tests/ComfyPromptSerializerTests.ts index 44723c9..9bce926 100644 --- a/src/tests/ComfyPromptSerializerTests.ts +++ b/src/tests/ComfyPromptSerializerTests.ts @@ -6,6 +6,7 @@ import ComfyGraph from "$lib/ComfyGraph"; import ComfyPromptSerializer from "$lib/components/ComfyPromptSerializer"; import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode"; import ComfyGraphNode from "$lib/nodes/ComfyGraphNode"; +import { graphToGraphVis } from "$lib/utils"; class MockBackendInput extends ComfyGraphNode { override isBackendNode = true; @@ -97,9 +98,12 @@ export default class ComfyPromptSerializerTests extends UnitTest { const result = ser.serialize(graph) + console.warn(result.output) expect(Object.keys(result.output)).toHaveLength(3); - expect(Object.keys(result.output[input.id].inputs)).toHaveLength(1); - expect(Object.keys(result.output[link.id].inputs)).toHaveLength(1); + expect(result.output[input.id].inputs["in"]).toBeInstanceOf(Array) + expect(result.output[input.id].inputs["in"][0]).toEqual(link.id) + expect(result.output[link.id].inputs["in"]).toBeInstanceOf(Array) + expect(result.output[link.id].inputs["in"][0]).toEqual(output.id) expect(Object.keys(result.output[output.id].inputs)).toHaveLength(0); } @@ -115,6 +119,7 @@ export default class ComfyPromptSerializerTests extends UnitTest { const graphInput = subgraph.addGraphInput("testIn", "number") const graphOutput = subgraph.addGraphOutput("testOut", "number") + graph.add(subgraph) graph.add(output) subgraph.subgraph.add(link) graph.add(input) @@ -126,9 +131,13 @@ export default class ComfyPromptSerializerTests extends UnitTest { const result = ser.serialize(graph) + console.warn(graphToGraphVis(graph)) + console.warn(result.output) expect(Object.keys(result.output)).toHaveLength(3); - expect(Object.keys(result.output[input.id].inputs)).toHaveLength(1); - expect(Object.keys(result.output[link.id].inputs)).toHaveLength(1); - expect(Object.keys(result.output[output.id].inputs)).toHaveLength(0); + expect(result.output[input.id].inputs["in"]).toBeInstanceOf(Array) + expect(result.output[input.id].inputs["in"][0]).toEqual(link.id) + expect(result.output[link.id].inputs["in"]).toBeInstanceOf(Array) + expect(result.output[link.id].inputs["in"][0]).toEqual(output.id) + expect(result.output[output.id].inputs).toEqual({}) } } diff --git a/vite.config.ts b/vite.config.ts index 5938dbd..9e6586f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -74,7 +74,7 @@ export default defineConfig({ test: { environment: 'jsdom', deps: { - inline: [/^svelte/, /^@floating-ui/, /dist/, "skeleton-elements", "mdn-polyfills"] + inline: [/^svelte/, /^@floating-ui/, /dist/, "skeleton-elements", "mdn-polyfills", "loupe"] }, include: [ 'litegraph/packages/tests/src/main.ts',