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() {
|
||||
console.debug("Configured");
|
||||
this.eventBus.emit("configured", this);
|
||||
}
|
||||
|
||||
override onBeforeChange(graph: LGraph, info: any) {
|
||||
console.debug("BeforeChange", info);
|
||||
this.eventBus.emit("beforeChange", graph, info);
|
||||
}
|
||||
|
||||
override onAfterChange(graph: LGraph, info: any) {
|
||||
console.debug("AfterChange", info);
|
||||
this.eventBus.emit("afterChange", graph, info);
|
||||
}
|
||||
|
||||
override onAfterExecute() {
|
||||
@@ -44,6 +41,9 @@ export default class ComfyGraph extends LGraph {
|
||||
}
|
||||
|
||||
override onNodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) {
|
||||
if (options.subgraphs && options.subgraphs.length > 0)
|
||||
return
|
||||
|
||||
layoutState.nodeAdded(node, options)
|
||||
|
||||
// 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 EventEmitter from "events";
|
||||
import type { ComfyExecutionResult, ComfyImageLocation } from "./nodes/ComfyWidgetNodes";
|
||||
@@ -30,7 +30,7 @@ export type ComfyAPIQueueResponse = {
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type NodeID = UUID;
|
||||
export type ComfyNodeID = UUID; // To distinguish from Litegraph NodeID
|
||||
export type PromptID = UUID; // UUID
|
||||
|
||||
export type ComfyAPIHistoryItem = [
|
||||
@@ -38,7 +38,7 @@ export type ComfyAPIHistoryItem = [
|
||||
PromptID,
|
||||
SerializedPromptInputsAll,
|
||||
ComfyBoxPromptExtraData,
|
||||
NodeID[] // good outputs
|
||||
ComfyNodeID[] // good outputs
|
||||
]
|
||||
|
||||
export type ComfyAPIPromptResponse = {
|
||||
@@ -76,9 +76,9 @@ type ComfyAPIEvents = {
|
||||
progress: (progress: Progress) => void,
|
||||
reconnecting: () => void,
|
||||
reconnected: () => void,
|
||||
executing: (promptID: PromptID | null, runningNodeID: NodeID | null) => void,
|
||||
executed: (promptID: PromptID, nodeID: NodeID, output: SerializedPromptOutput) => void,
|
||||
execution_cached: (promptID: PromptID, nodes: NodeID[]) => void,
|
||||
executing: (promptID: PromptID | null, runningNodeID: ComfyNodeID | null) => void,
|
||||
executed: (promptID: PromptID, nodeID: ComfyNodeID, output: SerializedPromptOutputs) => void,
|
||||
execution_cached: (promptID: PromptID, nodes: ComfyNodeID[]) => 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 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 EventEmitter from "events";
|
||||
import type TypedEmitter from "typed-emitter";
|
||||
@@ -28,7 +28,7 @@ import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
import { get } from "svelte/store";
|
||||
import { tick } from "svelte";
|
||||
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 configState from "$lib/stores/configState";
|
||||
import { blankGraph } from "$lib/defaultGraph";
|
||||
@@ -57,7 +57,7 @@ export type SerializedAppState = {
|
||||
}
|
||||
|
||||
/** [link origin, link index] | value */
|
||||
export type SerializedPromptInput = [NodeID, number] | any
|
||||
export type SerializedPromptInput = [ComfyNodeID, number] | any
|
||||
|
||||
export type SerializedPromptInputs = {
|
||||
/* property name -> value or link */
|
||||
@@ -65,14 +65,14 @@ export type SerializedPromptInputs = {
|
||||
class_type: string
|
||||
}
|
||||
|
||||
export type SerializedPromptInputsAll = Record<NodeID, SerializedPromptInputs>
|
||||
export type SerializedPromptInputsAll = Record<ComfyNodeID, SerializedPromptInputs>
|
||||
|
||||
export type SerializedPrompt = {
|
||||
workflow: SerializedLGraph,
|
||||
output: SerializedPromptInputsAll
|
||||
}
|
||||
|
||||
export type SerializedPromptOutputs = Record<NodeID, ComfyExecutionResult>
|
||||
export type SerializedPromptOutputs = Record<ComfyNodeID, ComfyExecutionResult>
|
||||
|
||||
export type Progress = {
|
||||
value: number,
|
||||
@@ -324,21 +324,21 @@ export default class ComfyApp {
|
||||
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);
|
||||
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;
|
||||
const node = this.lGraph.getNodeById(nodeID) as ComfyGraphNode;
|
||||
const node = this.lGraph.getNodeByIdRecursive(nodeID) as ComfyGraphNode;
|
||||
if (node?.onExecuted) {
|
||||
node.onExecuted(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)
|
||||
});
|
||||
|
||||
@@ -490,6 +490,7 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
layoutState.onStartConfigure();
|
||||
this.lCanvas.closeAllSubgraphs();
|
||||
this.lGraph.configure(blankGraph)
|
||||
layoutState.initDefaultLayout();
|
||||
uiState.update(s => {
|
||||
@@ -588,6 +589,7 @@ export default class ComfyApp {
|
||||
|
||||
const p = this.graphToPrompt(tag);
|
||||
const l = layoutState.serialize();
|
||||
console.debug(graphToGraphVis(this.lGraph))
|
||||
console.debug(promptToGraphVis(p))
|
||||
|
||||
const extraData: ComfyBoxPromptExtraData = {
|
||||
@@ -619,19 +621,20 @@ export default class ComfyApp {
|
||||
|
||||
error = response.error;
|
||||
} catch (err) {
|
||||
error = err
|
||||
error = { error: err }
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
const mes = error.response || error.toString()
|
||||
const mes = error.error
|
||||
notify(`Error queuing prompt:\n${mes}`, { type: "error" })
|
||||
console.error(graphToGraphVis(this.lGraph))
|
||||
console.error(promptToGraphVis(p))
|
||||
console.error("Error queuing prompt", error, num, p)
|
||||
break;
|
||||
}
|
||||
|
||||
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) {
|
||||
(node as ComfyGraphNode).afterQueued(p, tag);
|
||||
}
|
||||
@@ -689,8 +692,6 @@ export default class ComfyApp {
|
||||
async refreshComboInNodes(flashUI: boolean = false) {
|
||||
const defs = await this.api.getNodeDefs();
|
||||
|
||||
const toUpdate: BackendComboNode[] = []
|
||||
|
||||
const isComfyComboNode = (node: LGraphNode): boolean => {
|
||||
return node
|
||||
&& node.type === "ui/combo"
|
||||
@@ -704,14 +705,14 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
||||
// 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
|
||||
// slot connected to.
|
||||
for (const node of this.lGraph.iterateNodesInOrder()) {
|
||||
for (const node of this.lGraph.iterateNodesInOrderRecursive()) {
|
||||
if (!(node as any).isBackendNode)
|
||||
continue;
|
||||
|
||||
@@ -738,40 +739,43 @@ export default class ComfyApp {
|
||||
const hasBackendConfig = def["input"]["required"][inputSlot.name] !== undefined
|
||||
|
||||
if (hasBackendConfig) {
|
||||
backendCombos.add(comboNode.id)
|
||||
toUpdate.push({ comboNode, inputSlot, backendNode })
|
||||
backendUpdatedCombos[comboNode.id] = { 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.
|
||||
for (const node of this.lGraph.iterateNodesInOrder()) {
|
||||
if (isComfyComboNode(node) && !backendCombos.has(node.id)) {
|
||||
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);
|
||||
for (const node of this.lGraph.iterateNodesOfClassRecursive(nodes.ComfyComboNode)) {
|
||||
if (backendUpdatedCombos[node.id] != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
// 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 rawValues = def["input"]["required"][inputSlot.name][0];
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ export default class ComfyPromptSerializer {
|
||||
parent = null;
|
||||
}
|
||||
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;
|
||||
parent = nextParent;
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@ export class ComfySetNodeModeAdvancedAction extends ComfyGraphNode {
|
||||
}
|
||||
|
||||
for (const [nodeId, newMode] of Object.entries(nodeChanges)) {
|
||||
// NOTE: Only applies to this subgraph, not parent/child graphs.
|
||||
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 ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
|
||||
import ImageEditorWidget from "$lib/widgets/ImageEditorWidget.svelte";
|
||||
import type { NodeID } from "$lib/api";
|
||||
import type { ComfyNodeID } from "$lib/api";
|
||||
|
||||
export type AutoConfigOptions = {
|
||||
includeProperties?: Set<string> | null,
|
||||
@@ -272,7 +272,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
}
|
||||
|
||||
if (options.setWidgetTitle) {
|
||||
const widget = layoutState.findLayoutForNode(this.id as NodeID)
|
||||
const widget = layoutState.findLayoutForNode(this.id as ComfyNodeID)
|
||||
if (widget && input.name !== "") {
|
||||
widget.attrs.title = input.name;
|
||||
}
|
||||
@@ -291,7 +291,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
}
|
||||
|
||||
notifyPropsChanged() {
|
||||
const layoutEntry = layoutState.findLayoutEntryForNode(this.id as NodeID)
|
||||
const layoutEntry = layoutState.findLayoutEntryForNode(this.id as ComfyNodeID)
|
||||
if (layoutEntry && layoutEntry.parent) {
|
||||
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 { SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
||||
import type { ComfyWidgetNode } from '$lib/nodes';
|
||||
import type { NodeID } from '$lib/api';
|
||||
import type { ComfyNodeID } from '$lib/api';
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
type DragItemEntry = {
|
||||
@@ -60,7 +60,7 @@ export type LayoutState = {
|
||||
* Items indexed by the litegraph node they're bound to
|
||||
* Only contains drag items of type "widget"
|
||||
*/
|
||||
allItemsByNode: Record<NodeID, DragItemEntry>,
|
||||
allItemsByNode: Record<ComfyNodeID, DragItemEntry>,
|
||||
|
||||
/*
|
||||
* Selected drag items.
|
||||
@@ -663,8 +663,8 @@ type LayoutStateOps = {
|
||||
groupItems: (dragItems: IDragItem[], attrs?: Partial<Attributes>) => ContainerLayout,
|
||||
ungroup: (container: ContainerLayout) => void,
|
||||
getCurrentSelection: () => IDragItem[],
|
||||
findLayoutEntryForNode: (nodeId: NodeID) => DragItemEntry | null,
|
||||
findLayoutForNode: (nodeId: NodeID) => IDragItem | null,
|
||||
findLayoutEntryForNode: (nodeId: ComfyNodeID) => DragItemEntry | null,
|
||||
findLayoutForNode: (nodeId: ComfyNodeID) => IDragItem | null,
|
||||
serialize: () => SerializedLayoutState,
|
||||
deserialize: (data: SerializedLayoutState, graph: LGraph) => void,
|
||||
initDefaultLayout: () => void,
|
||||
@@ -924,7 +924,7 @@ function ungroup(container: ContainerLayout) {
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function findLayoutEntryForNode(nodeId: NodeID): DragItemEntry | null {
|
||||
function findLayoutEntryForNode(nodeId: ComfyNodeID): DragItemEntry | null {
|
||||
const state = get(store)
|
||||
const found = Object.entries(state.allItems).find(pair =>
|
||||
pair[1].dragItem.type === "widget"
|
||||
@@ -934,7 +934,7 @@ function findLayoutEntryForNode(nodeId: NodeID): DragItemEntry | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function findLayoutForNode(nodeId: NodeID): WidgetLayout | null {
|
||||
function findLayoutForNode(nodeId: ComfyNodeID): WidgetLayout | null {
|
||||
const found = findLayoutEntryForNode(nodeId);
|
||||
if (!found)
|
||||
return null;
|
||||
@@ -1034,7 +1034,9 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
||||
|
||||
if (dragItem.type === "widget") {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { ComfyExecutionResult } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import notify from "$lib/notify";
|
||||
@@ -14,12 +14,12 @@ type QueueStateOps = {
|
||||
queueUpdated: (resp: ComfyAPIQueueResponse) => void,
|
||||
historyUpdated: (resp: ComfyAPIHistoryResponse) => void,
|
||||
statusUpdated: (status: ComfyAPIStatusResponse | null) => void,
|
||||
executingUpdated: (promptID: PromptID | null, runningNodeID: NodeID | null) => void,
|
||||
executionCached: (promptID: PromptID, nodes: NodeID[]) => void,
|
||||
executingUpdated: (promptID: PromptID | null, runningNodeID: ComfyNodeID | null) => void,
|
||||
executionCached: (promptID: PromptID, nodes: ComfyNodeID[]) => void,
|
||||
executionError: (promptID: PromptID, message: string) => void,
|
||||
progressUpdated: (progress: Progress) => 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 = {
|
||||
@@ -30,7 +30,7 @@ export type QueueEntry = {
|
||||
promptID: PromptID,
|
||||
prompt: SerializedPromptInputsAll,
|
||||
extraData: ComfyBoxPromptExtraData,
|
||||
goodOutputs: NodeID[],
|
||||
goodOutputs: ComfyNodeID[],
|
||||
|
||||
/* Data not sent by ComfyUI's API, lost on page refresh */
|
||||
|
||||
@@ -38,8 +38,8 @@ export type QueueEntry = {
|
||||
outputs: SerializedPromptOutputs,
|
||||
|
||||
/* Nodes in of the workflow that have finished running so far. */
|
||||
nodesRan: Set<NodeID>,
|
||||
cachedNodes: Set<NodeID>
|
||||
nodesRan: Set<ComfyNodeID>,
|
||||
cachedNodes: Set<ComfyNodeID>
|
||||
}
|
||||
|
||||
export type CompletedQueueEntry = {
|
||||
@@ -54,7 +54,7 @@ export type QueueState = {
|
||||
queuePending: Writable<QueueEntry[]>,
|
||||
queueCompleted: Writable<CompletedQueueEntry[]>,
|
||||
queueRemaining: number | "X" | null;
|
||||
runningNodeID: NodeID | null;
|
||||
runningNodeID: ComfyNodeID | null;
|
||||
progress: Progress | null,
|
||||
isInterrupting: boolean
|
||||
}
|
||||
@@ -161,7 +161,7 @@ function moveToCompleted(index: number, queue: Writable<QueueEntry[]>, status: Q
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function executingUpdated(promptID: PromptID, runningNodeID: NodeID | null) {
|
||||
function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null) {
|
||||
console.debug("[queueState] executingUpdated", promptID, runningNodeID)
|
||||
store.update((s) => {
|
||||
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)
|
||||
store.update(s => {
|
||||
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)
|
||||
store.update(s => {
|
||||
const [index, entry, queue] = findEntryInPending(promptID)
|
||||
|
||||
@@ -6,8 +6,9 @@ import { get } from "svelte/store"
|
||||
import layoutState from "$lib/stores/layoutState"
|
||||
import type { SvelteComponentDev } from "svelte/internal";
|
||||
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 { ComfyNodeID } from "./api";
|
||||
|
||||
export function clamp(n: number, min: number, max: number): number {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -130,39 +131,23 @@ export function graphToGraphVis(graph: LGraph): string {
|
||||
}
|
||||
|
||||
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)) {
|
||||
// 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 += ` 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
|
||||
}
|
||||
@@ -186,17 +171,21 @@ export function promptToGraphVis(prompt: SerializedPrompt): string {
|
||||
for (const pair of Object.entries(prompt.output)) {
|
||||
const [id, o] = pair;
|
||||
const outNode = prompt.workflow.nodes.find(n => n.id == id)
|
||||
for (const pair2 of Object.entries(o.inputs)) {
|
||||
const [inpName, i] = pair2;
|
||||
if (outNode) {
|
||||
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") {
|
||||
// Link
|
||||
const inpNode = prompt.workflow.nodes.find(n => n.id == i[0])
|
||||
out += `"${inpNode.title}" -> "${outNode.title}"\n`
|
||||
}
|
||||
else {
|
||||
// Value
|
||||
out += `"${id}-${inpName}-${i}" -> "${outNode.title}"\n`
|
||||
if (Array.isArray(i) && i.length === 2 && typeof i[0] === "string" && typeof i[1] === "number") {
|
||||
// Link
|
||||
const inpNode = prompt.workflow.nodes.find(n => n.id == i[0])
|
||||
if (inpNode) {
|
||||
out += `"${inpNode.title}" -> "${outNode.title}"\n`
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Value
|
||||
out += `"${id}-${inpName}-${i}" -> "${outNode.title}"\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,12 +194,13 @@ export function promptToGraphVis(prompt: SerializedPrompt): string {
|
||||
return out
|
||||
}
|
||||
|
||||
export function getNodeInfo(nodeId: NodeID): string {
|
||||
export function getNodeInfo(nodeId: ComfyNodeID): string {
|
||||
let app = (window as any).app;
|
||||
if (!app || !app.lGraph)
|
||||
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 + ")"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LiteGraph } from '@litegraph-ts/core';
|
||||
import { configureLitegraph } from '$lib/init';
|
||||
import App from './App.svelte';
|
||||
|
||||
LiteGraph.use_uuids = true;
|
||||
configureLitegraph()
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app'),
|
||||
|
||||
@@ -6,18 +6,14 @@ import ComfyApp from '$lib/components/ComfyApp';
|
||||
import uiState from '$lib/stores/uiState';
|
||||
import { LiteGraph } from '@litegraph-ts/core';
|
||||
import ComfyGraph from '$lib/ComfyGraph';
|
||||
import { configureLitegraph } from '$lib/init';
|
||||
|
||||
Framework7.use(Framework7Svelte);
|
||||
|
||||
LiteGraph.use_uuids = true;
|
||||
LiteGraph.dialog_close_on_mouse_leave = false;
|
||||
LiteGraph.search_hide_on_mouse_leave = false;
|
||||
LiteGraph.pointerevents_method = "pointer";
|
||||
configureLitegraph(true);
|
||||
|
||||
const comfyApp = new ComfyApp();
|
||||
|
||||
uiState.update(s => { s.app = comfyApp; return s; })
|
||||
|
||||
const app = new AppMobile({
|
||||
target: document.getElementById('app'),
|
||||
props: { app: comfyApp }
|
||||
|
||||
@@ -131,6 +131,44 @@ export default class ComfyPromptSerializerTests extends UnitTest {
|
||||
|
||||
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(result.output)
|
||||
expect(Object.keys(result.output)).toHaveLength(3);
|
||||
|
||||
Reference in New Issue
Block a user