Graphvis debug view
This commit is contained in:
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
101
src/lib/utils.ts
101
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<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"
|
||||
|
||||
|
||||
@@ -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({})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user