Work on subgraph support
This commit is contained in:
Submodule litegraph updated: 7fb95e5b83...edc0ccbb08
@@ -26,17 +26,14 @@ export default class ComfyGraph extends LGraph {
|
|||||||
|
|
||||||
override onConfigure() {
|
override onConfigure() {
|
||||||
console.debug("Configured");
|
console.debug("Configured");
|
||||||
this.eventBus.emit("configured", this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override onBeforeChange(graph: LGraph, info: any) {
|
override onBeforeChange(graph: LGraph, info: any) {
|
||||||
console.debug("BeforeChange", info);
|
console.debug("BeforeChange", info);
|
||||||
this.eventBus.emit("beforeChange", graph, info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override onAfterChange(graph: LGraph, info: any) {
|
override onAfterChange(graph: LGraph, info: any) {
|
||||||
console.debug("AfterChange", info);
|
console.debug("AfterChange", info);
|
||||||
this.eventBus.emit("afterChange", graph, info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override onAfterExecute() {
|
override onAfterExecute() {
|
||||||
@@ -44,6 +41,9 @@ export default class ComfyGraph extends LGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override onNodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) {
|
override onNodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) {
|
||||||
|
if (options.subgraphs && options.subgraphs.length > 0)
|
||||||
|
return
|
||||||
|
|
||||||
layoutState.nodeAdded(node, options)
|
layoutState.nodeAdded(node, options)
|
||||||
|
|
||||||
// All nodes whether they come from base litegraph or ComfyBox should
|
// All nodes whether they come from base litegraph or ComfyBox should
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Progress, SerializedPrompt, SerializedPromptInputs, SerializedPromptInputsAll, SerializedPromptOutput, SerializedPromptOutputs } from "./components/ComfyApp";
|
import type { Progress, SerializedPrompt, SerializedPromptInputs, SerializedPromptInputsAll, SerializedPromptOutputs } from "./components/ComfyApp";
|
||||||
import type TypedEmitter from "typed-emitter";
|
import type TypedEmitter from "typed-emitter";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import type { ComfyExecutionResult, ComfyImageLocation } from "./nodes/ComfyWidgetNodes";
|
import type { ComfyExecutionResult, ComfyImageLocation } from "./nodes/ComfyWidgetNodes";
|
||||||
@@ -30,7 +30,7 @@ export type ComfyAPIQueueResponse = {
|
|||||||
error?: string
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodeID = UUID;
|
export type ComfyNodeID = UUID; // To distinguish from Litegraph NodeID
|
||||||
export type PromptID = UUID; // UUID
|
export type PromptID = UUID; // UUID
|
||||||
|
|
||||||
export type ComfyAPIHistoryItem = [
|
export type ComfyAPIHistoryItem = [
|
||||||
@@ -38,7 +38,7 @@ export type ComfyAPIHistoryItem = [
|
|||||||
PromptID,
|
PromptID,
|
||||||
SerializedPromptInputsAll,
|
SerializedPromptInputsAll,
|
||||||
ComfyBoxPromptExtraData,
|
ComfyBoxPromptExtraData,
|
||||||
NodeID[] // good outputs
|
ComfyNodeID[] // good outputs
|
||||||
]
|
]
|
||||||
|
|
||||||
export type ComfyAPIPromptResponse = {
|
export type ComfyAPIPromptResponse = {
|
||||||
@@ -76,9 +76,9 @@ type ComfyAPIEvents = {
|
|||||||
progress: (progress: Progress) => void,
|
progress: (progress: Progress) => void,
|
||||||
reconnecting: () => void,
|
reconnecting: () => void,
|
||||||
reconnected: () => void,
|
reconnected: () => void,
|
||||||
executing: (promptID: PromptID | null, runningNodeID: NodeID | null) => void,
|
executing: (promptID: PromptID | null, runningNodeID: ComfyNodeID | null) => void,
|
||||||
executed: (promptID: PromptID, nodeID: NodeID, output: SerializedPromptOutput) => void,
|
executed: (promptID: PromptID, nodeID: ComfyNodeID, output: SerializedPromptOutputs) => void,
|
||||||
execution_cached: (promptID: PromptID, nodes: NodeID[]) => void,
|
execution_cached: (promptID: PromptID, nodes: ComfyNodeID[]) => void,
|
||||||
execution_error: (promptID: PromptID, message: string) => void,
|
execution_error: (promptID: PromptID, message: string) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType, type INodeInputSlot } from "@litegraph-ts/core";
|
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType, type INodeInputSlot, type NodeID } from "@litegraph-ts/core";
|
||||||
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
|
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
|
||||||
import ComfyAPI, { type ComfyAPIStatusResponse, type ComfyBoxPromptExtraData, type ComfyPromptRequest, type NodeID, type PromptID } from "$lib/api"
|
import ComfyAPI, { type ComfyAPIStatusResponse, type ComfyBoxPromptExtraData, type ComfyPromptRequest, type ComfyNodeID, type PromptID } from "$lib/api"
|
||||||
import { getPngMetadata, importA1111 } from "$lib/pnginfo";
|
import { getPngMetadata, importA1111 } from "$lib/pnginfo";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import type TypedEmitter from "typed-emitter";
|
import type TypedEmitter from "typed-emitter";
|
||||||
@@ -28,7 +28,7 @@ import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { tick } from "svelte";
|
import { tick } from "svelte";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
import { download, jsonToJsObject, promptToGraphVis, range, workflowToGraphVis } from "$lib/utils";
|
import { download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range, workflowToGraphVis } from "$lib/utils";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
import configState from "$lib/stores/configState";
|
import configState from "$lib/stores/configState";
|
||||||
import { blankGraph } from "$lib/defaultGraph";
|
import { blankGraph } from "$lib/defaultGraph";
|
||||||
@@ -57,7 +57,7 @@ export type SerializedAppState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** [link origin, link index] | value */
|
/** [link origin, link index] | value */
|
||||||
export type SerializedPromptInput = [NodeID, number] | any
|
export type SerializedPromptInput = [ComfyNodeID, number] | any
|
||||||
|
|
||||||
export type SerializedPromptInputs = {
|
export type SerializedPromptInputs = {
|
||||||
/* property name -> value or link */
|
/* property name -> value or link */
|
||||||
@@ -65,14 +65,14 @@ export type SerializedPromptInputs = {
|
|||||||
class_type: string
|
class_type: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedPromptInputsAll = Record<NodeID, SerializedPromptInputs>
|
export type SerializedPromptInputsAll = Record<ComfyNodeID, SerializedPromptInputs>
|
||||||
|
|
||||||
export type SerializedPrompt = {
|
export type SerializedPrompt = {
|
||||||
workflow: SerializedLGraph,
|
workflow: SerializedLGraph,
|
||||||
output: SerializedPromptInputsAll
|
output: SerializedPromptInputsAll
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedPromptOutputs = Record<NodeID, ComfyExecutionResult>
|
export type SerializedPromptOutputs = Record<ComfyNodeID, ComfyExecutionResult>
|
||||||
|
|
||||||
export type Progress = {
|
export type Progress = {
|
||||||
value: number,
|
value: number,
|
||||||
@@ -324,21 +324,21 @@ export default class ComfyApp {
|
|||||||
this.lGraph.setDirtyCanvas(true, false);
|
this.lGraph.setDirtyCanvas(true, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.api.addEventListener("executing", (promptID: PromptID | null, nodeID: NodeID | null) => {
|
this.api.addEventListener("executing", (promptID: PromptID | null, nodeID: ComfyNodeID | null) => {
|
||||||
queueState.executingUpdated(promptID, nodeID);
|
queueState.executingUpdated(promptID, nodeID);
|
||||||
this.lGraph.setDirtyCanvas(true, false);
|
this.lGraph.setDirtyCanvas(true, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.api.addEventListener("executed", (promptID: PromptID, nodeID: NodeID, output: ComfyExecutionResult) => {
|
this.api.addEventListener("executed", (promptID: PromptID, nodeID: ComfyNodeID, output: ComfyExecutionResult) => {
|
||||||
this.nodeOutputs[nodeID] = output;
|
this.nodeOutputs[nodeID] = output;
|
||||||
const node = this.lGraph.getNodeById(nodeID) as ComfyGraphNode;
|
const node = this.lGraph.getNodeByIdRecursive(nodeID) as ComfyGraphNode;
|
||||||
if (node?.onExecuted) {
|
if (node?.onExecuted) {
|
||||||
node.onExecuted(output);
|
node.onExecuted(output);
|
||||||
}
|
}
|
||||||
queueState.onExecuted(promptID, nodeID, output)
|
queueState.onExecuted(promptID, nodeID, output)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.api.addEventListener("execution_cached", (promptID: PromptID, nodes: NodeID[]) => {
|
this.api.addEventListener("execution_cached", (promptID: PromptID, nodes: ComfyNodeID[]) => {
|
||||||
queueState.executionCached(promptID, nodes)
|
queueState.executionCached(promptID, nodes)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -490,6 +490,7 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layoutState.onStartConfigure();
|
layoutState.onStartConfigure();
|
||||||
|
this.lCanvas.closeAllSubgraphs();
|
||||||
this.lGraph.configure(blankGraph)
|
this.lGraph.configure(blankGraph)
|
||||||
layoutState.initDefaultLayout();
|
layoutState.initDefaultLayout();
|
||||||
uiState.update(s => {
|
uiState.update(s => {
|
||||||
@@ -588,6 +589,7 @@ export default class ComfyApp {
|
|||||||
|
|
||||||
const p = this.graphToPrompt(tag);
|
const p = this.graphToPrompt(tag);
|
||||||
const l = layoutState.serialize();
|
const l = layoutState.serialize();
|
||||||
|
console.debug(graphToGraphVis(this.lGraph))
|
||||||
console.debug(promptToGraphVis(p))
|
console.debug(promptToGraphVis(p))
|
||||||
|
|
||||||
const extraData: ComfyBoxPromptExtraData = {
|
const extraData: ComfyBoxPromptExtraData = {
|
||||||
@@ -619,19 +621,20 @@ export default class ComfyApp {
|
|||||||
|
|
||||||
error = response.error;
|
error = response.error;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err
|
error = { error: err }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
const mes = error.response || error.toString()
|
const mes = error.error
|
||||||
notify(`Error queuing prompt:\n${mes}`, { type: "error" })
|
notify(`Error queuing prompt:\n${mes}`, { type: "error" })
|
||||||
|
console.error(graphToGraphVis(this.lGraph))
|
||||||
console.error(promptToGraphVis(p))
|
console.error(promptToGraphVis(p))
|
||||||
console.error("Error queuing prompt", error, num, p)
|
console.error("Error queuing prompt", error, num, p)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const n of p.workflow.nodes) {
|
for (const n of p.workflow.nodes) {
|
||||||
const node = this.lGraph.getNodeById(n.id);
|
const node = this.lGraph.getNodeByIdRecursive(n.id);
|
||||||
if ("afterQueued" in node) {
|
if ("afterQueued" in node) {
|
||||||
(node as ComfyGraphNode).afterQueued(p, tag);
|
(node as ComfyGraphNode).afterQueued(p, tag);
|
||||||
}
|
}
|
||||||
@@ -689,8 +692,6 @@ export default class ComfyApp {
|
|||||||
async refreshComboInNodes(flashUI: boolean = false) {
|
async refreshComboInNodes(flashUI: boolean = false) {
|
||||||
const defs = await this.api.getNodeDefs();
|
const defs = await this.api.getNodeDefs();
|
||||||
|
|
||||||
const toUpdate: BackendComboNode[] = []
|
|
||||||
|
|
||||||
const isComfyComboNode = (node: LGraphNode): boolean => {
|
const isComfyComboNode = (node: LGraphNode): boolean => {
|
||||||
return node
|
return node
|
||||||
&& node.type === "ui/combo"
|
&& node.type === "ui/combo"
|
||||||
@@ -704,14 +705,14 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Node IDs of combo widgets attached to a backend node
|
// Node IDs of combo widgets attached to a backend node
|
||||||
let backendCombos: Set<number> = new Set()
|
const backendUpdatedCombos: Record<NodeID, BackendComboNode> = {}
|
||||||
|
|
||||||
console.debug("[refreshComboInNodes] start")
|
console.debug("[refreshComboInNodes] start")
|
||||||
|
|
||||||
// Figure out which combo nodes to update. They need to be connected to
|
// Figure out which combo nodes to update. They need to be connected to
|
||||||
// an input slot on a backend node with a backend config in the input
|
// an input slot on a backend node with a backend config in the input
|
||||||
// slot connected to.
|
// slot connected to.
|
||||||
for (const node of this.lGraph.iterateNodesInOrder()) {
|
for (const node of this.lGraph.iterateNodesInOrderRecursive()) {
|
||||||
if (!(node as any).isBackendNode)
|
if (!(node as any).isBackendNode)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -738,40 +739,43 @@ export default class ComfyApp {
|
|||||||
const hasBackendConfig = def["input"]["required"][inputSlot.name] !== undefined
|
const hasBackendConfig = def["input"]["required"][inputSlot.name] !== undefined
|
||||||
|
|
||||||
if (hasBackendConfig) {
|
if (hasBackendConfig) {
|
||||||
backendCombos.add(comboNode.id)
|
backendUpdatedCombos[comboNode.id] = { comboNode, inputSlot, backendNode }
|
||||||
toUpdate.push({ comboNode, inputSlot, backendNode })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug("[refreshComboInNodes] found:", toUpdate.length, toUpdate)
|
console.debug("[refreshComboInNodes] found:", backendUpdatedCombos.length, backendUpdatedCombos)
|
||||||
|
|
||||||
// Mark combo nodes without backend configs as being loaded already.
|
// Mark combo nodes without backend configs as being loaded already.
|
||||||
for (const node of this.lGraph.iterateNodesInOrder()) {
|
for (const node of this.lGraph.iterateNodesOfClassRecursive(nodes.ComfyComboNode)) {
|
||||||
if (isComfyComboNode(node) && !backendCombos.has(node.id)) {
|
if (backendUpdatedCombos[node.id] != null) {
|
||||||
const comboNode = node as nodes.ComfyComboNode;
|
continue;
|
||||||
let values = comboNode.properties.values;
|
|
||||||
|
|
||||||
// Frontend nodes can declare defaultWidgets which creates a
|
|
||||||
// config inside their own inputs slots too.
|
|
||||||
const foundInput = range(node.outputs.length)
|
|
||||||
.flatMap(i => node.getInputSlotsConnectedTo(i))
|
|
||||||
.find(inp => "config" in inp && Array.isArray((inp.config as any).values))
|
|
||||||
|
|
||||||
if (foundInput != null) {
|
|
||||||
const comfyInput = foundInput as IComfyInputSlot;
|
|
||||||
console.warn("[refreshComboInNodes] found frontend config:", node.title, node.type, comfyInput.config.values)
|
|
||||||
values = comfyInput.config.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
comboNode.formatValues(values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This node isn't connected to a backend node, so it's configured
|
||||||
|
// by the frontend instead.
|
||||||
|
const comboNode = node as nodes.ComfyComboNode;
|
||||||
|
let values = comboNode.properties.values;
|
||||||
|
|
||||||
|
// Frontend nodes can declare defaultWidgets which creates a
|
||||||
|
// config inside their own inputs slots too.
|
||||||
|
const foundInput = range(node.outputs.length)
|
||||||
|
.flatMap(i => node.getInputSlotsConnectedTo(i))
|
||||||
|
.find(inp => "config" in inp && Array.isArray((inp.config as any).values))
|
||||||
|
|
||||||
|
if (foundInput != null) {
|
||||||
|
const comfyInput = foundInput as IComfyInputSlot;
|
||||||
|
console.warn("[refreshComboInNodes] found frontend config:", node.title, node.type, comfyInput.config.values)
|
||||||
|
values = comfyInput.config.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
comboNode.formatValues(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
// Load definitions from the backend.
|
// Load definitions from the backend.
|
||||||
for (const { comboNode, inputSlot, backendNode } of toUpdate) {
|
for (const { comboNode, inputSlot, backendNode } of Object.values(backendUpdatedCombos)) {
|
||||||
const def = defs[backendNode.type];
|
const def = defs[backendNode.type];
|
||||||
const rawValues = def["input"]["required"][inputSlot.name][0];
|
const rawValues = def["input"]["required"][inputSlot.name][0];
|
||||||
|
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ export default class ComfyPromptSerializer {
|
|||||||
parent = null;
|
parent = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.debug("[graphToPrompt] Traverse upstream link", parent.id, nextParent?.id, nextParent?.isBackendNode)
|
console.debug("[graphToPrompt] Traverse upstream link", parent.id, nextParent?.id, (nextParent as any)?.isBackendNode)
|
||||||
currentLink = nextLink;
|
currentLink = nextLink;
|
||||||
parent = nextParent;
|
parent = nextParent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -530,6 +530,7 @@ export class ComfySetNodeModeAdvancedAction extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const [nodeId, newMode] of Object.entries(nodeChanges)) {
|
for (const [nodeId, newMode] of Object.entries(nodeChanges)) {
|
||||||
|
// NOTE: Only applies to this subgraph, not parent/child graphs.
|
||||||
this.graph.getNodeById(nodeId).changeMode(newMode);
|
this.graph.getNodeById(nodeId).changeMode(newMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import CheckboxWidget from "$lib/widgets/CheckboxWidget.svelte";
|
|||||||
import RadioWidget from "$lib/widgets/RadioWidget.svelte";
|
import RadioWidget from "$lib/widgets/RadioWidget.svelte";
|
||||||
import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
|
import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
|
||||||
import ImageEditorWidget from "$lib/widgets/ImageEditorWidget.svelte";
|
import ImageEditorWidget from "$lib/widgets/ImageEditorWidget.svelte";
|
||||||
import type { NodeID } from "$lib/api";
|
import type { ComfyNodeID } from "$lib/api";
|
||||||
|
|
||||||
export type AutoConfigOptions = {
|
export type AutoConfigOptions = {
|
||||||
includeProperties?: Set<string> | null,
|
includeProperties?: Set<string> | null,
|
||||||
@@ -272,7 +272,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.setWidgetTitle) {
|
if (options.setWidgetTitle) {
|
||||||
const widget = layoutState.findLayoutForNode(this.id as NodeID)
|
const widget = layoutState.findLayoutForNode(this.id as ComfyNodeID)
|
||||||
if (widget && input.name !== "") {
|
if (widget && input.name !== "") {
|
||||||
widget.attrs.title = input.name;
|
widget.attrs.title = input.name;
|
||||||
}
|
}
|
||||||
@@ -291,7 +291,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notifyPropsChanged() {
|
notifyPropsChanged() {
|
||||||
const layoutEntry = layoutState.findLayoutEntryForNode(this.id as NodeID)
|
const layoutEntry = layoutState.findLayoutEntryForNode(this.id as ComfyNodeID)
|
||||||
if (layoutEntry && layoutEntry.parent) {
|
if (layoutEntry && layoutEntry.parent) {
|
||||||
layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1)
|
layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type ComfyApp from "$lib/components/ComfyApp"
|
|||||||
import { type LGraphNode, type IWidget, type LGraph, NodeMode, type LGraphRemoveNodeOptions, type LGraphAddNodeOptions, type UUID } from "@litegraph-ts/core"
|
import { type LGraphNode, type IWidget, type LGraph, NodeMode, type LGraphRemoveNodeOptions, type LGraphAddNodeOptions, type UUID } from "@litegraph-ts/core"
|
||||||
import { SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
import { SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
||||||
import type { ComfyWidgetNode } from '$lib/nodes';
|
import type { ComfyWidgetNode } from '$lib/nodes';
|
||||||
import type { NodeID } from '$lib/api';
|
import type { ComfyNodeID } from '$lib/api';
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
type DragItemEntry = {
|
type DragItemEntry = {
|
||||||
@@ -60,7 +60,7 @@ export type LayoutState = {
|
|||||||
* Items indexed by the litegraph node they're bound to
|
* Items indexed by the litegraph node they're bound to
|
||||||
* Only contains drag items of type "widget"
|
* Only contains drag items of type "widget"
|
||||||
*/
|
*/
|
||||||
allItemsByNode: Record<NodeID, DragItemEntry>,
|
allItemsByNode: Record<ComfyNodeID, DragItemEntry>,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Selected drag items.
|
* Selected drag items.
|
||||||
@@ -663,8 +663,8 @@ type LayoutStateOps = {
|
|||||||
groupItems: (dragItems: IDragItem[], attrs?: Partial<Attributes>) => ContainerLayout,
|
groupItems: (dragItems: IDragItem[], attrs?: Partial<Attributes>) => ContainerLayout,
|
||||||
ungroup: (container: ContainerLayout) => void,
|
ungroup: (container: ContainerLayout) => void,
|
||||||
getCurrentSelection: () => IDragItem[],
|
getCurrentSelection: () => IDragItem[],
|
||||||
findLayoutEntryForNode: (nodeId: NodeID) => DragItemEntry | null,
|
findLayoutEntryForNode: (nodeId: ComfyNodeID) => DragItemEntry | null,
|
||||||
findLayoutForNode: (nodeId: NodeID) => IDragItem | null,
|
findLayoutForNode: (nodeId: ComfyNodeID) => IDragItem | null,
|
||||||
serialize: () => SerializedLayoutState,
|
serialize: () => SerializedLayoutState,
|
||||||
deserialize: (data: SerializedLayoutState, graph: LGraph) => void,
|
deserialize: (data: SerializedLayoutState, graph: LGraph) => void,
|
||||||
initDefaultLayout: () => void,
|
initDefaultLayout: () => void,
|
||||||
@@ -924,7 +924,7 @@ function ungroup(container: ContainerLayout) {
|
|||||||
store.set(state)
|
store.set(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
function findLayoutEntryForNode(nodeId: NodeID): DragItemEntry | null {
|
function findLayoutEntryForNode(nodeId: ComfyNodeID): DragItemEntry | null {
|
||||||
const state = get(store)
|
const state = get(store)
|
||||||
const found = Object.entries(state.allItems).find(pair =>
|
const found = Object.entries(state.allItems).find(pair =>
|
||||||
pair[1].dragItem.type === "widget"
|
pair[1].dragItem.type === "widget"
|
||||||
@@ -934,7 +934,7 @@ function findLayoutEntryForNode(nodeId: NodeID): DragItemEntry | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findLayoutForNode(nodeId: NodeID): WidgetLayout | null {
|
function findLayoutForNode(nodeId: ComfyNodeID): WidgetLayout | null {
|
||||||
const found = findLayoutEntryForNode(nodeId);
|
const found = findLayoutEntryForNode(nodeId);
|
||||||
if (!found)
|
if (!found)
|
||||||
return null;
|
return null;
|
||||||
@@ -1034,7 +1034,9 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
|||||||
|
|
||||||
if (dragItem.type === "widget") {
|
if (dragItem.type === "widget") {
|
||||||
const widget = dragItem as WidgetLayout;
|
const widget = dragItem as WidgetLayout;
|
||||||
widget.node = graph.getNodeById(entry.dragItem.nodeId) as ComfyWidgetNode
|
widget.node = graph.getNodeByIdRecursive(entry.dragItem.nodeId) as ComfyWidgetNode
|
||||||
|
if (widget.node == null)
|
||||||
|
throw (`Node in litegraph not found! ${entry.dragItem.nodeId}`)
|
||||||
allItemsByNode[entry.dragItem.nodeId] = dragEntry
|
allItemsByNode[entry.dragItem.nodeId] = dragEntry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ComfyAPIHistoryEntry, ComfyAPIHistoryItem, ComfyAPIHistoryResponse, ComfyAPIQueueResponse, ComfyAPIStatusResponse, ComfyBoxPromptExtraData, NodeID, PromptID } from "$lib/api";
|
import type { ComfyAPIHistoryEntry, ComfyAPIHistoryItem, ComfyAPIHistoryResponse, ComfyAPIQueueResponse, ComfyAPIStatusResponse, ComfyBoxPromptExtraData, ComfyNodeID, PromptID } from "$lib/api";
|
||||||
import type { Progress, SerializedPromptInputsAll, SerializedPromptOutputs } from "$lib/components/ComfyApp";
|
import type { Progress, SerializedPromptInputsAll, SerializedPromptOutputs } from "$lib/components/ComfyApp";
|
||||||
import type { ComfyExecutionResult } from "$lib/nodes/ComfyWidgetNodes";
|
import type { ComfyExecutionResult } from "$lib/nodes/ComfyWidgetNodes";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
@@ -14,12 +14,12 @@ type QueueStateOps = {
|
|||||||
queueUpdated: (resp: ComfyAPIQueueResponse) => void,
|
queueUpdated: (resp: ComfyAPIQueueResponse) => void,
|
||||||
historyUpdated: (resp: ComfyAPIHistoryResponse) => void,
|
historyUpdated: (resp: ComfyAPIHistoryResponse) => void,
|
||||||
statusUpdated: (status: ComfyAPIStatusResponse | null) => void,
|
statusUpdated: (status: ComfyAPIStatusResponse | null) => void,
|
||||||
executingUpdated: (promptID: PromptID | null, runningNodeID: NodeID | null) => void,
|
executingUpdated: (promptID: PromptID | null, runningNodeID: ComfyNodeID | null) => void,
|
||||||
executionCached: (promptID: PromptID, nodes: NodeID[]) => void,
|
executionCached: (promptID: PromptID, nodes: ComfyNodeID[]) => void,
|
||||||
executionError: (promptID: PromptID, message: string) => void,
|
executionError: (promptID: PromptID, message: string) => void,
|
||||||
progressUpdated: (progress: Progress) => void
|
progressUpdated: (progress: Progress) => void
|
||||||
afterQueued: (promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void
|
afterQueued: (promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void
|
||||||
onExecuted: (promptID: PromptID, nodeID: NodeID, output: ComfyExecutionResult) => void
|
onExecuted: (promptID: PromptID, nodeID: ComfyNodeID, output: ComfyExecutionResult) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QueueEntry = {
|
export type QueueEntry = {
|
||||||
@@ -30,7 +30,7 @@ export type QueueEntry = {
|
|||||||
promptID: PromptID,
|
promptID: PromptID,
|
||||||
prompt: SerializedPromptInputsAll,
|
prompt: SerializedPromptInputsAll,
|
||||||
extraData: ComfyBoxPromptExtraData,
|
extraData: ComfyBoxPromptExtraData,
|
||||||
goodOutputs: NodeID[],
|
goodOutputs: ComfyNodeID[],
|
||||||
|
|
||||||
/* Data not sent by ComfyUI's API, lost on page refresh */
|
/* Data not sent by ComfyUI's API, lost on page refresh */
|
||||||
|
|
||||||
@@ -38,8 +38,8 @@ export type QueueEntry = {
|
|||||||
outputs: SerializedPromptOutputs,
|
outputs: SerializedPromptOutputs,
|
||||||
|
|
||||||
/* Nodes in of the workflow that have finished running so far. */
|
/* Nodes in of the workflow that have finished running so far. */
|
||||||
nodesRan: Set<NodeID>,
|
nodesRan: Set<ComfyNodeID>,
|
||||||
cachedNodes: Set<NodeID>
|
cachedNodes: Set<ComfyNodeID>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompletedQueueEntry = {
|
export type CompletedQueueEntry = {
|
||||||
@@ -54,7 +54,7 @@ export type QueueState = {
|
|||||||
queuePending: Writable<QueueEntry[]>,
|
queuePending: Writable<QueueEntry[]>,
|
||||||
queueCompleted: Writable<CompletedQueueEntry[]>,
|
queueCompleted: Writable<CompletedQueueEntry[]>,
|
||||||
queueRemaining: number | "X" | null;
|
queueRemaining: number | "X" | null;
|
||||||
runningNodeID: NodeID | null;
|
runningNodeID: ComfyNodeID | null;
|
||||||
progress: Progress | null,
|
progress: Progress | null,
|
||||||
isInterrupting: boolean
|
isInterrupting: boolean
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ function moveToCompleted(index: number, queue: Writable<QueueEntry[]>, status: Q
|
|||||||
store.set(state)
|
store.set(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
function executingUpdated(promptID: PromptID, runningNodeID: NodeID | null) {
|
function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null) {
|
||||||
console.debug("[queueState] executingUpdated", promptID, runningNodeID)
|
console.debug("[queueState] executingUpdated", promptID, runningNodeID)
|
||||||
store.update((s) => {
|
store.update((s) => {
|
||||||
s.progress = null;
|
s.progress = null;
|
||||||
@@ -199,7 +199,7 @@ function executingUpdated(promptID: PromptID, runningNodeID: NodeID | null) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function executionCached(promptID: PromptID, nodes: NodeID[]) {
|
function executionCached(promptID: PromptID, nodes: ComfyNodeID[]) {
|
||||||
console.debug("[queueState] executionCached", promptID, nodes)
|
console.debug("[queueState] executionCached", promptID, nodes)
|
||||||
store.update(s => {
|
store.update(s => {
|
||||||
const [index, entry, queue] = findEntryInPending(promptID);
|
const [index, entry, queue] = findEntryInPending(promptID);
|
||||||
@@ -257,7 +257,7 @@ function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromp
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function onExecuted(promptID: PromptID, nodeID: NodeID, output: ComfyExecutionResult) {
|
function onExecuted(promptID: PromptID, nodeID: ComfyNodeID, output: ComfyExecutionResult) {
|
||||||
console.debug("[queueState] onExecuted", promptID, nodeID, output)
|
console.debug("[queueState] onExecuted", promptID, nodeID, output)
|
||||||
store.update(s => {
|
store.update(s => {
|
||||||
const [index, entry, queue] = findEntryInPending(promptID)
|
const [index, entry, queue] = findEntryInPending(promptID)
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import { get } from "svelte/store"
|
|||||||
import layoutState from "$lib/stores/layoutState"
|
import layoutState from "$lib/stores/layoutState"
|
||||||
import type { SvelteComponentDev } from "svelte/internal";
|
import type { SvelteComponentDev } from "svelte/internal";
|
||||||
import { Subgraph, type LGraph, type LGraphNode, type LLink, type SerializedLGraph, type UUID, GraphInput } 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 { ComfyExecutionResult, ComfyImageLocation } from "./nodes/ComfyWidgetNodes";
|
||||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||||
|
import type { ComfyNodeID } from "./api";
|
||||||
|
|
||||||
export function clamp(n: number, min: number, max: number): number {
|
export function clamp(n: number, min: number, max: number): number {
|
||||||
return Math.min(Math.max(n, min), max)
|
return Math.min(Math.max(n, min), max)
|
||||||
@@ -121,7 +122,7 @@ export function graphToGraphVis(graph: LGraph): string {
|
|||||||
subgraphs[node.graph._subgraph_node.id][1].push(linkText)
|
subgraphs[node.graph._subgraph_node.id][1].push(linkText)
|
||||||
subgraphNodes[node.graph._subgraph_node.id] = node.graph._subgraph_node
|
subgraphNodes[node.graph._subgraph_node.id] = node.graph._subgraph_node
|
||||||
}
|
}
|
||||||
else if (!node.is(Subgraph) && !node.graph.getNodeById(link.target_id)?.is(Subgraph)) {
|
else {
|
||||||
links.push(linkText)
|
links.push(linkText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,39 +131,23 @@ export function graphToGraphVis(graph: LGraph): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let out = "digraph {\n"
|
let out = "digraph {\n"
|
||||||
out += " node [shape=box];\n"
|
out += ' fontname="Helvetica,Arial,sans-serif"\n'
|
||||||
|
out += ' node [fontname="Helvetica,Arial,sans-serif"]\n'
|
||||||
|
out += ' edge [fontname="Helvetica,Arial,sans-serif"]\n'
|
||||||
|
out += ' node [shape=box style=filled fillcolor="#DDDDDD"]\n'
|
||||||
|
|
||||||
for (const [subgraph, links] of Object.values(subgraphs)) {
|
for (const [subgraph, links] of Object.values(subgraphs)) {
|
||||||
// Subgraph name has to be prefixed with "cluster" to show up as a cluster...
|
// Subgraph name has to be prefixed with "cluster" to show up as a cluster...
|
||||||
out += ` subgraph cluster_subgraph_${convId(subgraph.id)} {\n`
|
out += ` subgraph cluster_subgraph_${convId(subgraph.id)} {\n`
|
||||||
out += ` label="${convId(subgraph.id)}: ${subgraph.title}";\n`;
|
out += ` label="${convId(subgraph.id)}_${subgraph.title}";\n`;
|
||||||
out += " color=red;\n";
|
out += " color=red;\n";
|
||||||
// out += " style=grey;\n";
|
// out += " style=grey;\n";
|
||||||
out += " node [style=filled,fillcolor=white];\n";
|
|
||||||
out += " " + links.join(" ")
|
out += " " + links.join(" ")
|
||||||
out += " }\n"
|
out += " }\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
out += links.join("")
|
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 += "}"
|
out += "}"
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@@ -186,17 +171,21 @@ export function promptToGraphVis(prompt: SerializedPrompt): string {
|
|||||||
for (const pair of Object.entries(prompt.output)) {
|
for (const pair of Object.entries(prompt.output)) {
|
||||||
const [id, o] = pair;
|
const [id, o] = pair;
|
||||||
const outNode = prompt.workflow.nodes.find(n => n.id == id)
|
const outNode = prompt.workflow.nodes.find(n => n.id == id)
|
||||||
for (const pair2 of Object.entries(o.inputs)) {
|
if (outNode) {
|
||||||
const [inpName, i] = pair2;
|
for (const pair2 of Object.entries(o.inputs)) {
|
||||||
|
const [inpName, i] = pair2;
|
||||||
|
|
||||||
if (Array.isArray(i) && i.length === 2 && typeof i[0] === "string" && typeof i[1] === "number") {
|
if (Array.isArray(i) && i.length === 2 && typeof i[0] === "string" && typeof i[1] === "number") {
|
||||||
// Link
|
// Link
|
||||||
const inpNode = prompt.workflow.nodes.find(n => n.id == i[0])
|
const inpNode = prompt.workflow.nodes.find(n => n.id == i[0])
|
||||||
out += `"${inpNode.title}" -> "${outNode.title}"\n`
|
if (inpNode) {
|
||||||
}
|
out += `"${inpNode.title}" -> "${outNode.title}"\n`
|
||||||
else {
|
}
|
||||||
// Value
|
}
|
||||||
out += `"${id}-${inpName}-${i}" -> "${outNode.title}"\n`
|
else {
|
||||||
|
// Value
|
||||||
|
out += `"${id}-${inpName}-${i}" -> "${outNode.title}"\n`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,12 +194,13 @@ export function promptToGraphVis(prompt: SerializedPrompt): string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNodeInfo(nodeId: NodeID): string {
|
export function getNodeInfo(nodeId: ComfyNodeID): string {
|
||||||
let app = (window as any).app;
|
let app = (window as any).app;
|
||||||
if (!app || !app.lGraph)
|
if (!app || !app.lGraph)
|
||||||
return String(nodeId);
|
return String(nodeId);
|
||||||
|
|
||||||
const title = app.lGraph.getNodeById(nodeId)?.title || String(nodeId);
|
// TODO subgraph support
|
||||||
|
const title = app.lGraph.getNodeByIdRecursive(nodeId)?.title || String(nodeId);
|
||||||
return title + " (" + nodeId + ")"
|
return title + " (" + nodeId + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { LiteGraph } from '@litegraph-ts/core';
|
import { configureLitegraph } from '$lib/init';
|
||||||
import App from './App.svelte';
|
import App from './App.svelte';
|
||||||
|
|
||||||
LiteGraph.use_uuids = true;
|
configureLitegraph()
|
||||||
|
|
||||||
const app = new App({
|
const app = new App({
|
||||||
target: document.getElementById('app'),
|
target: document.getElementById('app'),
|
||||||
|
|||||||
@@ -6,18 +6,14 @@ import ComfyApp from '$lib/components/ComfyApp';
|
|||||||
import uiState from '$lib/stores/uiState';
|
import uiState from '$lib/stores/uiState';
|
||||||
import { LiteGraph } from '@litegraph-ts/core';
|
import { LiteGraph } from '@litegraph-ts/core';
|
||||||
import ComfyGraph from '$lib/ComfyGraph';
|
import ComfyGraph from '$lib/ComfyGraph';
|
||||||
|
import { configureLitegraph } from '$lib/init';
|
||||||
|
|
||||||
Framework7.use(Framework7Svelte);
|
Framework7.use(Framework7Svelte);
|
||||||
|
|
||||||
LiteGraph.use_uuids = true;
|
configureLitegraph(true);
|
||||||
LiteGraph.dialog_close_on_mouse_leave = false;
|
|
||||||
LiteGraph.search_hide_on_mouse_leave = false;
|
|
||||||
LiteGraph.pointerevents_method = "pointer";
|
|
||||||
|
|
||||||
const comfyApp = new ComfyApp();
|
const comfyApp = new ComfyApp();
|
||||||
|
|
||||||
uiState.update(s => { s.app = comfyApp; return s; })
|
|
||||||
|
|
||||||
const app = new AppMobile({
|
const app = new AppMobile({
|
||||||
target: document.getElementById('app'),
|
target: document.getElementById('app'),
|
||||||
props: { app: comfyApp }
|
props: { app: comfyApp }
|
||||||
|
|||||||
@@ -131,6 +131,44 @@ export default class ComfyPromptSerializerTests extends UnitTest {
|
|||||||
|
|
||||||
const result = ser.serialize(graph)
|
const result = ser.serialize(graph)
|
||||||
|
|
||||||
|
expect(Object.keys(result.output)).toHaveLength(3);
|
||||||
|
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({})
|
||||||
|
}
|
||||||
|
|
||||||
|
test__serialize__shouldFollowSubgraphsRecursively() {
|
||||||
|
const ser = new ComfyPromptSerializer();
|
||||||
|
const graph = new ComfyGraph();
|
||||||
|
|
||||||
|
const output = LiteGraph.createNode(MockBackendOutput)
|
||||||
|
const link = LiteGraph.createNode(MockBackendLink)
|
||||||
|
const input = LiteGraph.createNode(MockBackendInput)
|
||||||
|
|
||||||
|
const subgraphA = LiteGraph.createNode(Subgraph)
|
||||||
|
const subgraphB = LiteGraph.createNode(Subgraph)
|
||||||
|
const graphInputA = subgraphA.addGraphInput("testIn", "number")
|
||||||
|
const graphOutputA = subgraphA.addGraphOutput("testOut", "number")
|
||||||
|
const graphInputB = subgraphB.addGraphInput("testIn", "number")
|
||||||
|
const graphOutputB = subgraphB.addGraphOutput("testOut", "number")
|
||||||
|
|
||||||
|
graph.add(subgraphA)
|
||||||
|
subgraphA.subgraph.add(subgraphB)
|
||||||
|
graph.add(output)
|
||||||
|
subgraphB.subgraph.add(link)
|
||||||
|
graph.add(input)
|
||||||
|
|
||||||
|
output.connect(0, subgraphA, 0)
|
||||||
|
graphInputA.innerNode.connect(0, subgraphB, 0)
|
||||||
|
graphInputB.innerNode.connect(0, link, 0)
|
||||||
|
link.connect(0, graphOutputB.innerNode, 0)
|
||||||
|
subgraphB.connect(0, graphOutputA.innerNode, 0)
|
||||||
|
subgraphA.connect(0, input, 0)
|
||||||
|
|
||||||
|
const result = ser.serialize(graph)
|
||||||
|
|
||||||
console.warn(graphToGraphVis(graph))
|
console.warn(graphToGraphVis(graph))
|
||||||
console.warn(result.output)
|
console.warn(result.output)
|
||||||
expect(Object.keys(result.output)).toHaveLength(3);
|
expect(Object.keys(result.output)).toHaveLength(3);
|
||||||
|
|||||||
Reference in New Issue
Block a user