Graphvis debug view

This commit is contained in:
space-nuko
2023-05-16 11:02:03 -05:00
parent 22aad57bc4
commit 0c74b3af59
6 changed files with 262 additions and 42 deletions

View File

@@ -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<string, SerializedPromptInput> {
// 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];
}
}
}

View File

@@ -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<number>
return [...Array(size).keys()].map(i => i + startAt);
}
export function* enumerate<T>(iterable: Iterable<T>): 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<string, [Subgraph, string[]]> = {}
let subgraphNodes: Record<number | UUID, Subgraph> = {}
let idToInt: Record<number | UUID, number> = {}
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"

View File

@@ -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({})
}
}