Prepare for multiple workflows
This commit is contained in:
@@ -20,6 +20,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
||||
|
||||
constructor(
|
||||
app: ComfyApp,
|
||||
graph: LGraph,
|
||||
canvas: HTMLCanvasElement | string,
|
||||
options: {
|
||||
skip_render?: boolean;
|
||||
@@ -28,7 +29,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
||||
viewport?: Vector4;
|
||||
} = {}
|
||||
) {
|
||||
super(canvas, app.lGraph, options);
|
||||
super(canvas, graph, options);
|
||||
this.app = app;
|
||||
this._unsubscribe = selectionState.subscribe(ss => {
|
||||
for (const node of Object.values(this.selected_nodes)) {
|
||||
|
||||
@@ -27,7 +27,18 @@ export type ComfyNodeDefInput = [ComfyNodeDefInputType, ComfyNodeDefInputOptions
|
||||
export type ComfyNodeDefInputType = any[] | keyof typeof ComfyWidgets | string
|
||||
|
||||
export type ComfyNodeDefInputOptions = {
|
||||
forceInput?: boolean
|
||||
forceInput?: boolean;
|
||||
|
||||
// NOTE: For COMBO type inputs, the default value is always the first entry the list.
|
||||
default?: any,
|
||||
|
||||
// INT/FLOAT options
|
||||
min?: number,
|
||||
max?: number,
|
||||
step?: number,
|
||||
|
||||
// STRING options
|
||||
multiline?: boolean,
|
||||
}
|
||||
|
||||
// TODO if/when comfy refactors
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
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, type NodeTypeSpec, type NodeTypeOpts, type SlotIndex } 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, type NodeTypeSpec, type NodeTypeOpts, type SlotIndex, type UUID } from "@litegraph-ts/core";
|
||||
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
|
||||
import ComfyAPI, { type ComfyAPIStatusResponse, type ComfyBoxPromptExtraData, type ComfyPromptRequest, type ComfyNodeID, type PromptID } from "$lib/api"
|
||||
import { getPngMetadata, importA1111 } from "$lib/pnginfo";
|
||||
@@ -21,7 +21,7 @@ import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import { type SvelteComponentDev } from "svelte/internal";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import type { SerializedLayoutState } from "$lib/stores/layoutState";
|
||||
import type { LayoutState, SerializedLayoutState, WritableLayoutStateStore } from "$lib/stores/layoutState";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import ComfyGraph from "$lib/ComfyGraph";
|
||||
@@ -33,7 +33,7 @@ import { download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range, wor
|
||||
import notify from "$lib/notify";
|
||||
import configState from "$lib/stores/configState";
|
||||
import { blankGraph } from "$lib/defaultGraph";
|
||||
import type { ComfyExecutionResult } from "$lib/utils";
|
||||
import type { SerializedPromptOutput } from "$lib/utils";
|
||||
import ComfyPromptSerializer, { UpstreamNodeLocator, isActiveBackendNode } from "./ComfyPromptSerializer";
|
||||
import { iterateNodeDefInputs, type ComfyNodeDef, isBackendNodeDefInputType, iterateNodeDefOutputs } from "$lib/ComfyNodeDef";
|
||||
import { ComfyComboNode } from "$lib/nodes/widgets";
|
||||
@@ -41,6 +41,7 @@ import parseA1111, { type A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import convertA1111ToStdPrompt from "$lib/convertA1111ToStdPrompt";
|
||||
import type { ComfyBoxStdPrompt } from "$lib/ComfyBoxStdPrompt";
|
||||
import ComfyBoxStdPromptSerializer from "$lib/ComfyBoxStdPromptSerializer";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||
|
||||
@@ -51,12 +52,11 @@ if (typeof window !== "undefined") {
|
||||
|
||||
/*
|
||||
* Queued prompt that hasn't been sent to the backend yet.
|
||||
* TODO: Assumes the currently active graph will be serialized, needs to change
|
||||
* for multiple loaded workflow support
|
||||
*/
|
||||
type QueueItem = {
|
||||
type PromptQueueItem = {
|
||||
num: number,
|
||||
batchCount: number
|
||||
workflow: ComfyWorkflow
|
||||
}
|
||||
|
||||
export type A1111PromptAndInfo = {
|
||||
@@ -111,7 +111,7 @@ export type SerializedPrompt = {
|
||||
/*
|
||||
* Outputs for each node.
|
||||
*/
|
||||
export type SerializedPromptOutputs = Record<ComfyNodeID, ComfyExecutionResult>
|
||||
export type SerializedPromptOutputs = Record<ComfyNodeID, SerializedPromptOutput>
|
||||
|
||||
export type Progress = {
|
||||
value: number,
|
||||
@@ -134,20 +134,156 @@ type CanvasState = {
|
||||
canvas: ComfyGraphCanvas,
|
||||
}
|
||||
|
||||
type WorkflowState = {
|
||||
title: string,
|
||||
graph: ComfyGraph,
|
||||
type ActiveCanvas = {
|
||||
canvas: LGraphCanvas | null;
|
||||
canvasHandler: () => void | null;
|
||||
state: SerializedGraphCanvasState;
|
||||
}
|
||||
|
||||
export type SerializedWorkflowState = {
|
||||
graph: SerializedLGraph,
|
||||
layout: SerializedLayoutState
|
||||
}
|
||||
|
||||
/*
|
||||
* ID for an opened workflow.
|
||||
*
|
||||
* Unlike NodeID and PromptID, these are *not* saved to the workflow itself.
|
||||
* They are only used for identifying an open workflow in the program. If the
|
||||
* workflow is closed and reopened, a different workflow ID will be assigned to
|
||||
* it.
|
||||
*/
|
||||
export type WorkflowInstID = UUID;
|
||||
|
||||
export class ComfyWorkflow {
|
||||
/*
|
||||
* Used for uniquely identifying the instance of the opened workflow in the frontend.
|
||||
*/
|
||||
id: WorkflowInstID;
|
||||
title: string;
|
||||
graph: ComfyGraph;
|
||||
layout: WritableLayoutStateStore;
|
||||
|
||||
canvases: Record<string, ActiveCanvas> = {};
|
||||
|
||||
constructor(title: string, graph: ComfyGraph, layout: WritableLayoutStateStore) {
|
||||
this.id = uuidv4();
|
||||
this.title = title;
|
||||
this.layout = layout;
|
||||
this.graph = graph;
|
||||
}
|
||||
|
||||
start(key: string, canvas: ComfyGraphCanvas) {
|
||||
if (this.canvases[key] != null)
|
||||
throw new Error(`This workflow is already being displayed on canvas ${key}`)
|
||||
|
||||
const canvasHandler = () => canvas.draw(true);
|
||||
|
||||
this.canvases[key] = {
|
||||
canvas,
|
||||
canvasHandler,
|
||||
state: {
|
||||
// TODO
|
||||
offset: [0, 0],
|
||||
scale: 1
|
||||
}
|
||||
}
|
||||
|
||||
this.graph.attachCanvas(canvas);
|
||||
this.graph.eventBus.on("afterExecute", canvasHandler)
|
||||
|
||||
if (Object.keys(this.canvases).length === 1)
|
||||
this.graph.start();
|
||||
}
|
||||
|
||||
stop(key: string) {
|
||||
const canvas = this.canvases[key]
|
||||
if (canvas == null)
|
||||
throw new Error(`This workflow is not being displayed on canvas ${key}`)
|
||||
|
||||
this.graph.detachCanvas(canvas.canvas);
|
||||
this.graph.eventBus.removeListener("afterExecute", canvas.canvasHandler)
|
||||
|
||||
delete this.canvases[key]
|
||||
|
||||
if (Object.keys(this.canvases).length === 0)
|
||||
this.graph.stop();
|
||||
}
|
||||
|
||||
stopAll() {
|
||||
for (const key of Object.keys(this.canvases))
|
||||
this.stop(key)
|
||||
this.graph.stop()
|
||||
}
|
||||
|
||||
serialize(): SerializedWorkflowState {
|
||||
const graph = this.graph;
|
||||
|
||||
const serializedGraph = graph.serialize()
|
||||
const serializedLayout = this.layout.serialize()
|
||||
|
||||
return {
|
||||
graph: serializedGraph,
|
||||
layout: serializedLayout
|
||||
}
|
||||
}
|
||||
|
||||
static deserialize(data: SerializedWorkflowState): ComfyWorkflow {
|
||||
const layout = layoutState; // TODO
|
||||
// Ensure loadGraphData does not trigger any state changes in layoutState
|
||||
// (isConfiguring is set to true here)
|
||||
// lGraph.configure will add new nodes, triggering onNodeAdded, but we
|
||||
// want to restore the layoutState ourselves
|
||||
layout.onStartConfigure();
|
||||
|
||||
// Patch T2IAdapterLoader to ControlNetLoader since they are the same node now
|
||||
for (let n of data.graph.nodes) {
|
||||
if (n.type == "T2IAdapterLoader") n.type = "ControlNetLoader";
|
||||
}
|
||||
|
||||
const graph = new ComfyGraph();
|
||||
graph.configure(data.graph);
|
||||
|
||||
for (const node of graph._nodes) {
|
||||
const size = node.computeSize();
|
||||
size[0] = Math.max(node.size[0], size[0]);
|
||||
size[1] = Math.max(node.size[1], size[1]);
|
||||
node.size = size;
|
||||
// this.#invokeExtensions("loadedGraphNode", node);
|
||||
}
|
||||
|
||||
// Now restore the layout
|
||||
// Subsequent added nodes will add the UI data to layoutState
|
||||
// TODO
|
||||
layout.deserialize(data.layout, graph)
|
||||
|
||||
return new ComfyWorkflow("Workflow X", graph, layout);
|
||||
}
|
||||
}
|
||||
|
||||
export default class ComfyApp {
|
||||
api: ComfyAPI;
|
||||
|
||||
rootEl: HTMLDivElement | null = null;
|
||||
canvasEl: HTMLCanvasElement | null = null;
|
||||
canvasCtx: CanvasRenderingContext2D | null = null;
|
||||
lGraph: ComfyGraph | null = null;
|
||||
lCanvas: ComfyGraphCanvas | null = null;
|
||||
dropZone: HTMLElement | null = null;
|
||||
nodeOutputs: Record<string, any> = {};
|
||||
|
||||
openedWorkflows: ComfyWorkflow[] = [];
|
||||
openedWorkflowsByID: Record<WorkflowInstID, ComfyWorkflow> = {};
|
||||
activeWorkflowIdx: number = -1;
|
||||
|
||||
get activeWorkflow(): ComfyWorkflow | null {
|
||||
return this.openedWorkflows[this.activeWorkflowIdx]
|
||||
}
|
||||
|
||||
get activeGraph(): ComfyGraph | null {
|
||||
return this.activeWorkflow?.graph;
|
||||
}
|
||||
|
||||
getWorkflow(id: WorkflowInstID): ComfyWorkflow | null {
|
||||
return this.openedWorkflowsByID[id];
|
||||
}
|
||||
|
||||
shiftDown: boolean = false;
|
||||
ctrlDown: boolean = false;
|
||||
@@ -155,7 +291,7 @@ export default class ComfyApp {
|
||||
alreadySetup: Writable<boolean> = writable(false);
|
||||
a1111Prompt: Writable<A1111PromptAndInfo | null> = writable(null);
|
||||
|
||||
private queueItems: QueueItem[] = [];
|
||||
private queueItems: PromptQueueItem[] = [];
|
||||
private processingQueue: boolean = false;
|
||||
private promptSerializer: ComfyPromptSerializer;
|
||||
private stdPromptSerializer: ComfyBoxStdPromptSerializer;
|
||||
@@ -176,8 +312,7 @@ export default class ComfyApp {
|
||||
|
||||
this.rootEl = document.getElementById("app-root") as HTMLDivElement;
|
||||
this.canvasEl = document.getElementById("graph-canvas") as HTMLCanvasElement;
|
||||
this.lGraph = new ComfyGraph();
|
||||
this.lCanvas = new ComfyGraphCanvas(this, this.canvasEl);
|
||||
this.lCanvas = new ComfyGraphCanvas(this, null, this.canvasEl);
|
||||
this.canvasCtx = this.canvasEl.getContext("2d");
|
||||
|
||||
const uiUnlocked = get(uiState).uiUnlocked;
|
||||
@@ -193,7 +328,7 @@ export default class ComfyApp {
|
||||
const json = localStorage.getItem("workflow");
|
||||
if (json) {
|
||||
const state = JSON.parse(json) as SerializedAppState;
|
||||
await this.deserialize(state)
|
||||
await this.openWorkflow(state)
|
||||
restored = true;
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -236,6 +371,24 @@ export default class ComfyApp {
|
||||
this.lCanvas.draw(true, true);
|
||||
}
|
||||
|
||||
serialize(): SerializedAppState {
|
||||
const workflow = this.activeWorkflow
|
||||
if (workflow == null)
|
||||
throw new Error("No workflow active!")
|
||||
|
||||
const { graph, layout } = workflow.serialize();
|
||||
const canvas = this.lCanvas.serialize();
|
||||
|
||||
return {
|
||||
createdBy: "ComfyBox",
|
||||
version: COMFYBOX_SERIAL_VERSION,
|
||||
commitHash: __GIT_COMMIT_HASH__,
|
||||
workflow: graph,
|
||||
layout,
|
||||
canvas
|
||||
}
|
||||
}
|
||||
|
||||
saveStateToLocalStorage() {
|
||||
try {
|
||||
uiState.update(s => { s.isSavingToLocalStorage = true; return s; })
|
||||
@@ -362,8 +515,12 @@ export default class ComfyApp {
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
if (workflow && workflow.version && workflow.nodes && workflow.extra) {
|
||||
this.loadGraphData(workflow);
|
||||
if (workflow && workflow.createdBy === "ComfyBox") {
|
||||
this.openWorkflow(workflow);
|
||||
}
|
||||
else {
|
||||
// TODO handle vanilla workflows
|
||||
throw new Error("Workflow was not in ComfyBox format!")
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -386,21 +543,27 @@ export default class ComfyApp {
|
||||
|
||||
this.api.addEventListener("progress", (progress: Progress) => {
|
||||
queueState.progressUpdated(progress);
|
||||
this.lGraph.setDirtyCanvas(true, false);
|
||||
this.activeGraph?.setDirtyCanvas(true, false); // TODO PromptID
|
||||
});
|
||||
|
||||
this.api.addEventListener("executing", (promptID: PromptID | null, nodeID: ComfyNodeID | null) => {
|
||||
queueState.executingUpdated(promptID, nodeID);
|
||||
this.lGraph.setDirtyCanvas(true, false);
|
||||
const queueEntry = queueState.executingUpdated(promptID, nodeID);
|
||||
if (queueEntry != null) {
|
||||
const workflow = this.getWorkflow(queueEntry.workflowID)
|
||||
workflow?.graph.setDirtyCanvas(true, false);
|
||||
}
|
||||
});
|
||||
|
||||
this.api.addEventListener("executed", (promptID: PromptID, nodeID: ComfyNodeID, output: ComfyExecutionResult) => {
|
||||
this.nodeOutputs[nodeID] = output;
|
||||
const node = this.lGraph.getNodeByIdRecursive(nodeID) as ComfyGraphNode;
|
||||
if (node?.onExecuted) {
|
||||
node.onExecuted(output);
|
||||
this.api.addEventListener("executed", (promptID: PromptID, nodeID: ComfyNodeID, output: SerializedPromptOutput) => {
|
||||
const queueEntry = queueState.onExecuted(promptID, nodeID, output)
|
||||
if (queueEntry != null) {
|
||||
const workflow = this.getWorkflow(queueEntry.workflowID)
|
||||
workflow?.graph.setDirtyCanvas(true, false);
|
||||
const node = workflow?.graph.getNodeByIdRecursive(nodeID) as ComfyGraphNode;
|
||||
if (node?.onExecuted) {
|
||||
node.onExecuted(output);
|
||||
}
|
||||
}
|
||||
queueState.onExecuted(promptID, nodeID, output)
|
||||
});
|
||||
|
||||
this.api.addEventListener("execution_start", (promptID: PromptID) => {
|
||||
@@ -467,46 +630,68 @@ export default class ComfyApp {
|
||||
setColor(BuiltInSlotType.ACTION, "lightseagreen")
|
||||
}
|
||||
|
||||
serialize(): SerializedAppState {
|
||||
const graph = this.lGraph;
|
||||
|
||||
const serializedGraph = graph.serialize()
|
||||
const serializedLayout = layoutState.serialize()
|
||||
const serializedCanvas = this.lCanvas.serialize();
|
||||
|
||||
return {
|
||||
createdBy: "ComfyBox",
|
||||
version: COMFYBOX_SERIAL_VERSION,
|
||||
workflow: serializedGraph,
|
||||
layout: serializedLayout,
|
||||
canvas: serializedCanvas
|
||||
}
|
||||
createNewWorkflow(): ComfyWorkflow {
|
||||
// TODO remove
|
||||
const workflow = ComfyWorkflow.deserialize({ graph: blankGraph.workflow, layout: blankGraph.layout })
|
||||
this.openedWorkflows.push(workflow);
|
||||
this.setActiveWorkflow(this.openedWorkflows.length - 1)
|
||||
return workflow;
|
||||
}
|
||||
|
||||
async deserialize(data: SerializedAppState) {
|
||||
async openWorkflow(data: SerializedAppState): Promise<ComfyWorkflow> {
|
||||
if (data.version !== COMFYBOX_SERIAL_VERSION) {
|
||||
throw `Invalid ComfyBox saved data format: ${data.version}`
|
||||
}
|
||||
|
||||
// Ensure loadGraphData does not trigger any state changes in layoutState
|
||||
// (isConfiguring is set to true here)
|
||||
// lGraph.configure will add new nodes, triggering onNodeAdded, but we
|
||||
// want to restore the layoutState ourselves
|
||||
layoutState.onStartConfigure();
|
||||
this.clean();
|
||||
|
||||
this.loadGraphData(data.workflow)
|
||||
|
||||
// Now restore the layout
|
||||
// Subsequent added nodes will add the UI data to layoutState
|
||||
layoutState.deserialize(data.layout, this.lGraph)
|
||||
const workflow = ComfyWorkflow.deserialize({ graph: data.workflow, layout: data.layout })
|
||||
|
||||
// Restore canvas offset/zoom
|
||||
this.lCanvas.deserialize(data.canvas)
|
||||
|
||||
await this.refreshComboInNodes();
|
||||
await this.refreshComboInNodes(workflow);
|
||||
|
||||
this.lGraph.start();
|
||||
this.lGraph.eventBus.on("afterExecute", () => this.lCanvas.draw(true))
|
||||
this.openedWorkflows.push(workflow);
|
||||
this.setActiveWorkflow(this.openedWorkflows.length - 1)
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
closeWorkflow(index: number) {
|
||||
if (index < 0 || index >= this.openedWorkflows.length)
|
||||
return;
|
||||
|
||||
const workflow = this.openedWorkflows[index];
|
||||
workflow.stopAll();
|
||||
|
||||
this.openedWorkflows.splice(index, 1)
|
||||
this.setActiveWorkflow(0);
|
||||
}
|
||||
|
||||
closeAllWorkflows() {
|
||||
while (this.openedWorkflows.length > 0)
|
||||
this.closeWorkflow(0)
|
||||
}
|
||||
|
||||
setActiveWorkflow(index: number) {
|
||||
if (this.openedWorkflows.length === 0) {
|
||||
this.activeWorkflowIdx = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= this.openedWorkflows.length || this.activeWorkflowIdx === index)
|
||||
return;
|
||||
|
||||
if (this.activeWorkflow != null)
|
||||
this.activeWorkflow.stop("app")
|
||||
|
||||
const workflow = this.openedWorkflows[index]
|
||||
this.activeWorkflowIdx = index;
|
||||
|
||||
console.warn("START")
|
||||
workflow.start("app", this.lCanvas);
|
||||
this.lCanvas.deserialize(workflow.canvases["app"].state)
|
||||
}
|
||||
|
||||
async initDefaultGraph() {
|
||||
@@ -520,50 +705,14 @@ export default class ComfyApp {
|
||||
notify(`Failed to load default graph: ${error}`, { type: "error" })
|
||||
state = structuredClone(blankGraph)
|
||||
}
|
||||
await this.deserialize(state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the graph with the specified workflow data
|
||||
* @param {*} graphData A serialized graph object
|
||||
*/
|
||||
loadGraphData(graphData: SerializedLGraph) {
|
||||
this.clean();
|
||||
|
||||
// Patch T2IAdapterLoader to ControlNetLoader since they are the same node now
|
||||
for (let n of graphData.nodes) {
|
||||
if (n.type == "T2IAdapterLoader") n.type = "ControlNetLoader";
|
||||
}
|
||||
|
||||
this.lGraph.configure(graphData);
|
||||
|
||||
for (const node of this.lGraph._nodes) {
|
||||
const size = node.computeSize();
|
||||
size[0] = Math.max(node.size[0], size[0]);
|
||||
size[1] = Math.max(node.size[1], size[1]);
|
||||
node.size = size;
|
||||
// this.#invokeExtensions("loadedGraphNode", node);
|
||||
}
|
||||
await this.openWorkflow(state)
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.clean();
|
||||
|
||||
const blankGraph: SerializedLGraph = {
|
||||
last_node_id: 0,
|
||||
last_link_id: 0,
|
||||
nodes: [],
|
||||
links: [],
|
||||
groups: [],
|
||||
config: {},
|
||||
extra: {},
|
||||
version: 0
|
||||
}
|
||||
|
||||
layoutState.onStartConfigure();
|
||||
this.lCanvas.closeAllSubgraphs();
|
||||
this.lGraph.configure(blankGraph)
|
||||
layoutState.initDefaultLayout();
|
||||
this.closeAllWorkflows();
|
||||
uiState.update(s => {
|
||||
s.uiUnlocked = true;
|
||||
s.uiEditMode = "widgets";
|
||||
@@ -572,7 +721,10 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
runDefaultQueueAction() {
|
||||
for (const node of this.lGraph.iterateNodesInOrderRecursive()) {
|
||||
if (this.activeWorkflow == null)
|
||||
return;
|
||||
|
||||
for (const node of this.activeGraph.iterateNodesInOrderRecursive()) {
|
||||
if ("onDefaultQueueAction" in node) {
|
||||
(node as ComfyGraphNode).onDefaultQueueAction()
|
||||
}
|
||||
@@ -614,12 +766,17 @@ export default class ComfyApp {
|
||||
* Converts the current graph workflow for sending to the API
|
||||
* @returns The workflow and node links
|
||||
*/
|
||||
graphToPrompt(tag: string | null = null): SerializedPrompt {
|
||||
return this.promptSerializer.serialize(this.lGraph, tag)
|
||||
graphToPrompt(workflow: ComfyWorkflow, tag: string | null = null): SerializedPrompt {
|
||||
return this.promptSerializer.serialize(workflow.graph, tag)
|
||||
}
|
||||
|
||||
async queuePrompt(num: number, batchCount: number = 1, tag: string | null = null) {
|
||||
this.queueItems.push({ num, batchCount });
|
||||
if (this.activeWorkflow === null) {
|
||||
notify("No workflow is opened!", { type: "error" })
|
||||
return;
|
||||
}
|
||||
|
||||
this.queueItems.push({ num, batchCount, workflow: this.activeWorkflow });
|
||||
|
||||
// Only have one action process the items so each one gets a unique seed correctly
|
||||
if (this.processingQueue) {
|
||||
@@ -630,13 +787,15 @@ export default class ComfyApp {
|
||||
tag = null;
|
||||
|
||||
this.processingQueue = true;
|
||||
let workflow;
|
||||
|
||||
try {
|
||||
while (this.queueItems.length) {
|
||||
({ num, batchCount } = this.queueItems.pop());
|
||||
({ num, batchCount, workflow } = this.queueItems.pop());
|
||||
console.debug(`Queue get! ${num} ${batchCount} ${tag}`);
|
||||
|
||||
const thumbnails = []
|
||||
for (const node of this.lGraph.iterateNodesInOrderRecursive()) {
|
||||
for (const node of workflow.graph.iterateNodesInOrderRecursive()) {
|
||||
if (node.mode !== NodeMode.ALWAYS
|
||||
|| (tag != null
|
||||
&& Array.isArray(node.properties.tags)
|
||||
@@ -651,7 +810,7 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
for (let i = 0; i < batchCount; i++) {
|
||||
for (const node of this.lGraph.iterateNodesInOrderRecursive()) {
|
||||
for (const node of workflow.graph.iterateNodesInOrderRecursive()) {
|
||||
if (node.mode !== NodeMode.ALWAYS)
|
||||
continue;
|
||||
|
||||
@@ -660,9 +819,9 @@ export default class ComfyApp {
|
||||
}
|
||||
}
|
||||
|
||||
const p = this.graphToPrompt(tag);
|
||||
const p = this.graphToPrompt(workflow, tag);
|
||||
const l = layoutState.serialize();
|
||||
console.debug(graphToGraphVis(this.lGraph))
|
||||
console.debug(graphToGraphVis(workflow.graph))
|
||||
console.debug(promptToGraphVis(p))
|
||||
|
||||
const stdPrompt = this.stdPromptSerializer.serialize(p);
|
||||
@@ -692,7 +851,7 @@ export default class ComfyApp {
|
||||
error = response.error;
|
||||
}
|
||||
else {
|
||||
queueState.afterQueued(response.promptID, num, p.output, extraData)
|
||||
queueState.afterQueued(workflow.id, response.promptID, num, p.output, extraData)
|
||||
}
|
||||
} catch (err) {
|
||||
error = err?.toString();
|
||||
@@ -701,13 +860,13 @@ export default class ComfyApp {
|
||||
if (error != null) {
|
||||
const mes: string = error;
|
||||
notify(`Error queuing prompt:\n${mes}`, { type: "error" })
|
||||
console.error(graphToGraphVis(this.lGraph))
|
||||
console.error(graphToGraphVis(workflow.graph))
|
||||
console.error(promptToGraphVis(p))
|
||||
console.error("Error queuing prompt", error, num, p)
|
||||
break;
|
||||
}
|
||||
|
||||
for (const node of this.lGraph.iterateNodesInOrderRecursive()) {
|
||||
for (const node of workflow.graph.iterateNodesInOrderRecursive()) {
|
||||
if ("afterQueued" in node) {
|
||||
(node as ComfyGraphNode).afterQueued(p, tag);
|
||||
}
|
||||
@@ -730,7 +889,7 @@ export default class ComfyApp {
|
||||
const pngInfo = await getPngMetadata(file);
|
||||
if (pngInfo) {
|
||||
if (pngInfo.comfyBoxConfig) {
|
||||
this.deserialize(JSON.parse(pngInfo.comfyBoxConfig));
|
||||
await this.openWorkflow(JSON.parse(pngInfo.comfyBoxConfig));
|
||||
} else if (pngInfo.parameters) {
|
||||
const parsed = parseA1111(pngInfo.parameters)
|
||||
if ("error" in parsed) {
|
||||
@@ -752,8 +911,8 @@ export default class ComfyApp {
|
||||
}
|
||||
} else if (file.type === "application/json" || file.name.endsWith(".json")) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
this.deserialize(JSON.parse(reader.result as string));
|
||||
reader.onload = async () => {
|
||||
await this.openWorkflow(JSON.parse(reader.result as string));
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
@@ -772,7 +931,7 @@ export default class ComfyApp {
|
||||
/**
|
||||
* Refresh combo list on whole nodes
|
||||
*/
|
||||
async refreshComboInNodes(flashUI: boolean = false) {
|
||||
async refreshComboInNodes(workflow: ComfyWorkflow, flashUI: boolean = false) {
|
||||
const defs = await this.api.getNodeDefs();
|
||||
|
||||
const isComfyComboNode = (node: LGraphNode): node is ComfyComboNode => {
|
||||
@@ -816,7 +975,7 @@ export default class ComfyApp {
|
||||
return result
|
||||
}
|
||||
|
||||
for (const node of this.lGraph.iterateNodesInOrderRecursive()) {
|
||||
for (const node of workflow.graph.iterateNodesInOrderRecursive()) {
|
||||
if (!isActiveBackendNode(node))
|
||||
continue;
|
||||
|
||||
@@ -838,7 +997,7 @@ export default class ComfyApp {
|
||||
console.debug("[refreshComboInNodes] found:", backendUpdatedCombos.length, backendUpdatedCombos)
|
||||
|
||||
// Mark combo nodes without backend configs as being loaded already.
|
||||
for (const node of this.lGraph.iterateNodesOfClassRecursive(ComfyComboNode)) {
|
||||
for (const node of workflow.graph.iterateNodesOfClassRecursive(ComfyComboNode)) {
|
||||
if (backendUpdatedCombos[node.id] != null) {
|
||||
continue;
|
||||
}
|
||||
@@ -870,12 +1029,15 @@ export default class ComfyApp {
|
||||
// Load definitions from the backend.
|
||||
for (const { comboNode, comfyInput, backendNode } of Object.values(backendUpdatedCombos)) {
|
||||
const def = defs[backendNode.type];
|
||||
const rawValues = def["input"]["required"][comfyInput.name][0];
|
||||
const [rawValues, opts] = def.input.required[comfyInput.name];
|
||||
|
||||
console.debug("[ComfyApp] Reconfiguring combo widget", backendNode.type, "=>", comboNode.type, rawValues.length)
|
||||
comboNode.doAutoConfig(comfyInput, { includeProperties: new Set(["values"]), setWidgetTitle: false })
|
||||
|
||||
comboNode.formatValues(rawValues as string[], true)
|
||||
const values = rawValues as string[]
|
||||
const defaultValue = rawValues[0];
|
||||
|
||||
comboNode.formatValues(values, defaultValue, true)
|
||||
if (!rawValues?.includes(get(comboNode.value))) {
|
||||
comboNode.setValue(rawValues[0], comfyInput.config.defaultValue)
|
||||
}
|
||||
@@ -886,7 +1048,6 @@ export default class ComfyApp {
|
||||
* Clean current state
|
||||
*/
|
||||
clean() {
|
||||
this.nodeOutputs = {};
|
||||
this.a1111Prompt.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +275,12 @@
|
||||
Error loading app
|
||||
</div>
|
||||
<div>{error}</div>
|
||||
{#if error.stack}
|
||||
{@const lines = error.stack.split("\n")}
|
||||
{#each lines as line}
|
||||
<div style:font-size="16px">{line}</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type SerializedAppState from "./ComfyApp"
|
||||
import type { SerializedAppState } from "./components/ComfyApp"
|
||||
|
||||
const blankGraph: SerializedAppState = {
|
||||
createdBy: "ComfyBox",
|
||||
@@ -13,7 +13,14 @@ const blankGraph: SerializedAppState = {
|
||||
extra: {},
|
||||
version: 0
|
||||
},
|
||||
panes: {}
|
||||
layout: {
|
||||
root: null,
|
||||
allItems: {},
|
||||
attrs: {
|
||||
queuePromptButtonName: "Queue Prompt",
|
||||
queuePromptButtonRunWorkflow: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { blankGraph }
|
||||
|
||||
@@ -8,7 +8,7 @@ import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"
|
||||
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
|
||||
import type { NotifyOptions } from "$lib/notify";
|
||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||
import { type ComfyExecutionResult, type ComfyImageLocation, convertComfyOutputToGradio, type ComfyUploadImageAPIResponse, parseWhateverIntoComfyImageLocations } from "$lib/utils";
|
||||
import { type SerializedPromptOutput, type ComfyImageLocation, convertComfyOutputToGradio, type ComfyUploadImageAPIResponse, parseWhateverIntoComfyImageLocations } from "$lib/utils";
|
||||
|
||||
export class ComfyQueueEvents extends ComfyGraphNode {
|
||||
static slotLayout: SlotLayout = {
|
||||
@@ -63,7 +63,7 @@ LiteGraph.registerNodeType({
|
||||
})
|
||||
|
||||
export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperties {
|
||||
images: ComfyExecutionResult | null
|
||||
images: SerializedPromptOutput | null
|
||||
}
|
||||
|
||||
export class ComfyStoreImagesAction extends ComfyGraphNode {
|
||||
@@ -90,7 +90,7 @@ export class ComfyStoreImagesAction extends ComfyGraphNode {
|
||||
if (action !== "store" || !param || !("images" in param))
|
||||
return;
|
||||
|
||||
this.setProperty("images", param as ComfyExecutionResult)
|
||||
this.setProperty("images", param as SerializedPromptOutput)
|
||||
this.setOutputData(0, this.properties.images)
|
||||
}
|
||||
}
|
||||
@@ -223,7 +223,7 @@ export class ComfyNotifyAction extends ComfyGraphNode {
|
||||
// native notifications.
|
||||
if (param != null && typeof param === "object") {
|
||||
if ("images" in param) {
|
||||
const output = param as ComfyExecutionResult;
|
||||
const output = param as SerializedPromptOutput;
|
||||
const converted = convertComfyOutputToGradio(output);
|
||||
if (converted.length > 0)
|
||||
options.imageUrl = converted[0].data;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { BuiltInSlotShape, BuiltInSlotType, type SerializedLGraphNode } from "@l
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import type { ComfyInputConfig } from "$lib/IComfyInputSlot";
|
||||
import { iterateNodeDefOutputs, type ComfyNodeDef, iterateNodeDefInputs } from "$lib/ComfyNodeDef";
|
||||
import type { ComfyExecutionResult } from "$lib/utils";
|
||||
import type { SerializedPromptOutput } from "$lib/utils";
|
||||
|
||||
/*
|
||||
* Base class for any node with configuration sent by the backend.
|
||||
@@ -111,7 +111,7 @@ export class ComfyBackendNode extends ComfyGraphNode {
|
||||
}
|
||||
}
|
||||
|
||||
override onExecuted(outputData: ComfyExecutionResult) {
|
||||
override onExecuted(outputData: SerializedPromptOutput) {
|
||||
console.warn("onExecuted outputs", outputData)
|
||||
this.triggerSlot(0, outputData)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
||||
import { LGraph, LGraphNode, LLink, LiteGraph, NodeMode, type INodeInputSlot, type SerializedLGraphNode, type Vector2, type INodeOutputSlot, LConnectionKind, type SlotType, LGraphCanvas, getStaticPropertyOnInstance, type PropertyLayout, type SlotLayout } from "@litegraph-ts/core";
|
||||
import type { SvelteComponentDev } from "svelte/internal";
|
||||
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
|
||||
import type { ComfyExecutionResult, ComfyImageLocation } from "$lib/utils"
|
||||
import type { SerializedPromptOutput, ComfyImageLocation } from "$lib/utils"
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { get } from "svelte/store";
|
||||
@@ -48,7 +48,7 @@ export default class ComfyGraphNode extends LGraphNode {
|
||||
* Triggered when the backend sends a finished output back with this node's ID.
|
||||
* Valid for output nodes like SaveImage and PreviewImage.
|
||||
*/
|
||||
onExecuted?(output: ComfyExecutionResult): void;
|
||||
onExecuted?(output: SerializedPromptOutput): void;
|
||||
|
||||
/*
|
||||
* When a prompt is queued, this will be called on the node if it can
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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, WorkflowInstID } from "$lib/components/ComfyApp";
|
||||
import type { ComfyExecutionResult } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import notify from "$lib/notify";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
@@ -11,12 +11,13 @@ type QueueStateOps = {
|
||||
historyUpdated: (resp: ComfyAPIHistoryResponse) => void,
|
||||
statusUpdated: (status: ComfyAPIStatusResponse | null) => void,
|
||||
executionStart: (promptID: PromptID) => void,
|
||||
executingUpdated: (promptID: PromptID | null, runningNodeID: ComfyNodeID | null) => void,
|
||||
executingUpdated: (promptID: PromptID | null, runningNodeID: ComfyNodeID | null) => QueueEntry | null;
|
||||
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: ComfyNodeID, output: ComfyExecutionResult) => void
|
||||
getQueueEntry: (promptID: PromptID) => QueueEntry | null;
|
||||
afterQueued: (workflowID: WorkflowInstID, promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void
|
||||
onExecuted: (promptID: PromptID, nodeID: ComfyNodeID, output: ComfyExecutionResult) => QueueEntry | null
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -36,6 +37,8 @@ export type QueueEntry = {
|
||||
|
||||
/*** Data not sent by ComfyUI's API, lost on page refresh ***/
|
||||
|
||||
/* Workflow tab that sent the prompt. */
|
||||
workflowID?: WorkflowInstID,
|
||||
/* Prompt outputs, collected while the prompt is still executing */
|
||||
outputs: SerializedPromptOutputs,
|
||||
/* Nodes of the workflow that have finished running so far. */
|
||||
@@ -150,6 +153,21 @@ function statusUpdated(status: ComfyAPIStatusResponse | null) {
|
||||
})
|
||||
}
|
||||
|
||||
function getQueueEntry(promptID: PromptID): QueueEntry | null {
|
||||
const state = get(store);
|
||||
|
||||
let found = get(state.queuePending).find(e => e.promptID === promptID)
|
||||
if (found != null) return found;
|
||||
|
||||
found = get(state.queueRunning).find(e => e.promptID === promptID)
|
||||
if (found != null) return found;
|
||||
|
||||
let foundCompleted = get(state.queueCompleted).find(e => e.entry.promptID === promptID)
|
||||
if (foundCompleted != null) return foundCompleted.entry;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function findEntryInPending(promptID: PromptID): [number, QueueEntry | null, Writable<QueueEntry[]> | null] {
|
||||
const state = get(store);
|
||||
let index = get(state.queuePending).findIndex(e => e.promptID === promptID)
|
||||
@@ -180,8 +198,10 @@ function moveToCompleted(index: number, queue: Writable<QueueEntry[]>, status: Q
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null) {
|
||||
function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null): QueueEntry | null {
|
||||
console.debug("[queueState] executingUpdated", promptID, runningNodeID)
|
||||
let entry_ = null;
|
||||
|
||||
store.update((s) => {
|
||||
s.progress = null;
|
||||
|
||||
@@ -214,8 +234,11 @@ function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null)
|
||||
s.progress = null;
|
||||
s.runningNodeID = null;
|
||||
}
|
||||
entry_ = entry;
|
||||
return s
|
||||
})
|
||||
|
||||
return entry_;
|
||||
}
|
||||
|
||||
function executionCached(promptID: PromptID, nodes: ComfyNodeID[]) {
|
||||
@@ -283,16 +306,18 @@ function executionStart(promptID: PromptID) {
|
||||
})
|
||||
}
|
||||
|
||||
function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) {
|
||||
function afterQueued(workflowID: WorkflowInstID, promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) {
|
||||
console.debug("[queueState] afterQueued", promptID, Object.keys(prompt))
|
||||
store.update(s => {
|
||||
const [index, entry, queue] = findEntryInPending(promptID);
|
||||
if (entry == null) {
|
||||
const entry = createNewQueueEntry(promptID, number, prompt, extraData);
|
||||
entry.workflowID = workflowID;
|
||||
s.queuePending.update(qp => { qp.push(entry); return qp })
|
||||
console.debug("[queueState] ADD PROMPT", promptID)
|
||||
}
|
||||
else {
|
||||
entry.workflowID = workflowID;
|
||||
entry.number = number;
|
||||
entry.prompt = prompt
|
||||
entry.extraData = extraData
|
||||
@@ -304,19 +329,22 @@ function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromp
|
||||
})
|
||||
}
|
||||
|
||||
function onExecuted(promptID: PromptID, nodeID: ComfyNodeID, output: ComfyExecutionResult) {
|
||||
console.debug("[queueState] onExecuted", promptID, nodeID, output)
|
||||
function onExecuted(promptID: PromptID, nodeID: ComfyNodeID, outputs: ComfyExecutionResult): QueueEntry | null {
|
||||
console.debug("[queueState] onExecuted", promptID, nodeID, outputs)
|
||||
let entry_ = null;
|
||||
store.update(s => {
|
||||
const [index, entry, queue] = findEntryInPending(promptID)
|
||||
if (entry != null) {
|
||||
entry.outputs[nodeID] = output;
|
||||
entry.outputs[nodeID] = outputs;
|
||||
queue.set(get(queue))
|
||||
}
|
||||
else {
|
||||
console.error("[queueState] Could not find in pending! (onExecuted)", promptID)
|
||||
}
|
||||
entry_ = entry;
|
||||
return s
|
||||
})
|
||||
return entry;
|
||||
}
|
||||
|
||||
const queueStateStore: WritableQueueStateStore =
|
||||
@@ -331,6 +359,7 @@ const queueStateStore: WritableQueueStateStore =
|
||||
executionCached,
|
||||
executionError,
|
||||
afterQueued,
|
||||
getQueueEntry,
|
||||
onExecuted
|
||||
}
|
||||
export default queueStateStore;
|
||||
|
||||
@@ -203,12 +203,12 @@ export function promptToGraphVis(prompt: SerializedPrompt): string {
|
||||
|
||||
export function getNodeInfo(nodeId: ComfyNodeID): string {
|
||||
let app = (window as any).app;
|
||||
if (!app || !app.lGraph)
|
||||
if (!app?.activeGraph)
|
||||
return String(nodeId);
|
||||
|
||||
const displayNodeID = nodeId ? (nodeId.split("-")[0]) : String(nodeId);
|
||||
|
||||
const title = app.lGraph.getNodeByIdRecursive(nodeId)?.title || String(nodeId);
|
||||
const title = app.activeGraph.getNodeByIdRecursive(nodeId)?.title || String(nodeId);
|
||||
return title + " (" + displayNodeID + ")"
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ export const debounce = (callback: Function, wait = 250) => {
|
||||
};
|
||||
};
|
||||
|
||||
export function convertComfyOutputToGradio(output: ComfyExecutionResult): GradioFileData[] {
|
||||
export function convertComfyOutputToGradio(output: SerializedPromptOutput): GradioFileData[] {
|
||||
return output.images.map(convertComfyOutputEntryToGradio);
|
||||
}
|
||||
|
||||
@@ -329,11 +329,16 @@ export async function uploadImageToComfyUI(blob: Blob, filename: string, type: C
|
||||
}
|
||||
|
||||
/** Raw output as received from ComfyUI's backend */
|
||||
export interface ComfyExecutionResult {
|
||||
export interface SerializedPromptOutput {
|
||||
// Technically this response can contain arbitrary data, but "images" is the
|
||||
// most frequently used as it's output by LoadImage and PreviewImage, the
|
||||
// only two output nodes in base ComfyUI.
|
||||
images: ComfyImageLocation[] | null,
|
||||
|
||||
/*
|
||||
* Other data
|
||||
*/
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/** Raw output entry as received from ComfyUI's backend */
|
||||
@@ -375,7 +380,7 @@ export function isComfyBoxImageMetadataArray(value: any): value is ComfyBoxImage
|
||||
return Array.isArray(value) && value.every(isComfyBoxImageMetadata);
|
||||
}
|
||||
|
||||
export function isComfyExecutionResult(value: any): value is ComfyExecutionResult {
|
||||
export function isComfyExecutionResult(value: any): value is SerializedPromptOutput {
|
||||
return value && typeof value === "object" && Array.isArray(value.images)
|
||||
}
|
||||
|
||||
@@ -415,7 +420,7 @@ export function comfyFileToAnnotatedFilepath(comfyUIFile: ComfyImageLocation): s
|
||||
return path;
|
||||
}
|
||||
|
||||
export function executionResultToImageMetadata(result: ComfyExecutionResult): ComfyBoxImageMetadata[] {
|
||||
export function executionResultToImageMetadata(result: SerializedPromptOutput): ComfyBoxImageMetadata[] {
|
||||
return result.images.map(comfyFileToComfyBoxMetadata)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@ import { LGraphNode, LiteGraph } from "@litegraph-ts/core";
|
||||
import type IComfyInputSlot from "./IComfyInputSlot";
|
||||
import type { ComfyInputConfig } from "./IComfyInputSlot";
|
||||
import { ComfyComboNode, ComfyNumberNode, ComfyTextNode } from "./nodes/widgets";
|
||||
import type { ComfyNodeDefInput } from "./ComfyNodeDef";
|
||||
|
||||
type WidgetFactory = (node: LGraphNode, inputName: string, inputData: any) => IComfyInputSlot;
|
||||
type WidgetFactory = (node: LGraphNode, inputName: string, inputData: ComfyNodeDefInput) => IComfyInputSlot;
|
||||
|
||||
function getNumberDefaults(inputData: any, defaultStep: number): ComfyInputConfig {
|
||||
let defaultValue = inputData[1]["default"];
|
||||
function getNumberDefaults(inputData: ComfyNodeDefInput, defaultStep: number): ComfyInputConfig {
|
||||
let defaultValue = inputData[1].default;
|
||||
let { min, max, step } = inputData[1];
|
||||
|
||||
if (defaultValue == undefined) defaultValue = 0;
|
||||
@@ -33,25 +34,25 @@ function addComfyInput(node: LGraphNode, inputName: string, extraInfo: Partial<I
|
||||
return input;
|
||||
}
|
||||
|
||||
const FLOAT: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any): IComfyInputSlot => {
|
||||
const FLOAT: WidgetFactory = (node: LGraphNode, inputName: string, inputData: ComfyNodeDefInput): IComfyInputSlot => {
|
||||
const config = getNumberDefaults(inputData, 0.5);
|
||||
return addComfyInput(node, inputName, { type: "number", config, defaultWidgetNode: ComfyNumberNode })
|
||||
}
|
||||
|
||||
const INT: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any): IComfyInputSlot => {
|
||||
const INT: WidgetFactory = (node: LGraphNode, inputName: string, inputData: ComfyNodeDefInput): IComfyInputSlot => {
|
||||
const config = getNumberDefaults(inputData, 1);
|
||||
return addComfyInput(node, inputName, { type: "number", config, defaultWidgetNode: ComfyNumberNode })
|
||||
};
|
||||
|
||||
const STRING: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any): IComfyInputSlot => {
|
||||
const STRING: WidgetFactory = (node: LGraphNode, inputName: string, inputData: ComfyNodeDefInput): IComfyInputSlot => {
|
||||
const defaultValue = inputData[1].default || "";
|
||||
const multiline = !!inputData[1].multiline;
|
||||
|
||||
return addComfyInput(node, inputName, { type: "string", config: { defaultValue, multiline }, defaultWidgetNode: ComfyTextNode })
|
||||
};
|
||||
|
||||
const COMBO: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any): IComfyInputSlot => {
|
||||
const type = inputData[0];
|
||||
const COMBO: WidgetFactory = (node: LGraphNode, inputName: string, inputData: ComfyNodeDefInput): IComfyInputSlot => {
|
||||
const type = inputData[0] as string[];
|
||||
let defaultValue = type[0];
|
||||
if (inputData[1] && inputData[1].default) {
|
||||
defaultValue = inputData[1].default;
|
||||
@@ -59,7 +60,7 @@ const COMBO: WidgetFactory = (node: LGraphNode, inputName: string, inputData: an
|
||||
return addComfyInput(node, inputName, { type: "string", config: { values: type, defaultValue }, defaultWidgetNode: ComfyComboNode })
|
||||
}
|
||||
|
||||
const IMAGEUPLOAD: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any): IComfyInputSlot => {
|
||||
const IMAGEUPLOAD: WidgetFactory = (node: LGraphNode, inputName: string, inputData: ComfyNodeDefInput): IComfyInputSlot => {
|
||||
return addComfyInput(node, inputName, { type: "number", config: {} })
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
lCanvas.draw(true, true);
|
||||
}
|
||||
|
||||
$: if (app != null && app.lGraph && canvasEl != null) {
|
||||
$: if (app?.activeGraph != null && canvasEl != null) {
|
||||
if (!lCanvas) {
|
||||
lCanvas = new ComfyGraphCanvas(app, canvasEl);
|
||||
lCanvas = new ComfyGraphCanvas(app, app.activeGraph, canvasEl);
|
||||
lCanvas.allow_interaction = false;
|
||||
app.lGraph.eventBus.on("afterExecute", () => lCanvas.draw(true))
|
||||
app.activeGraph.eventBus.on("afterExecute", () => lCanvas.draw(true))
|
||||
}
|
||||
resizeCanvas();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user