This commit is contained in:
space-nuko
2023-05-18 15:14:18 -05:00
parent e0a81a0569
commit c7ad04b69a
5 changed files with 85 additions and 23 deletions

View File

@@ -45,25 +45,49 @@ if (typeof window !== "undefined") {
nodes.ComfyReroute.setDefaultTextVisibility(!!localStorage["Comfy.ComfyReroute.DefaultVisibility"]); nodes.ComfyReroute.setDefaultTextVisibility(!!localStorage["Comfy.ComfyReroute.DefaultVisibility"]);
} }
type QueueItem = { num: number, batchCount: number } /*
* 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 = {
num: number,
batchCount: number
}
/*
* Represents a single workflow that can be loaded into the program from JSON.
*/
export type SerializedAppState = { export type SerializedAppState = {
/** Program identifier, should always be "ComfyBox" */
createdBy: "ComfyBox", createdBy: "ComfyBox",
/** Serial version, should be incremented on breaking changes */
version: number, version: number,
/** Commit hash if found */
commitHash?: string,
/** Graph state */
workflow: SerializedLGraph, workflow: SerializedLGraph,
/** UI state */
layout: SerializedLayoutState, layout: SerializedLayoutState,
/** Position/offset of the canvas at the time of saving */
canvas: SerializedGraphCanvasState canvas: SerializedGraphCanvasState
} }
/** [link origin, link index] | value */ /** [link_origin, link_slot_index] | input_value */
export type SerializedPromptInput = [ComfyNodeID, number] | any export type SerializedPromptInput = [ComfyNodeID, number] | any
/*
* A single node in the prompt and its input values.
*/
export type SerializedPromptInputs = { export type SerializedPromptInputs = {
/* property name -> value or link */ /* property name -> value or link */
inputs: Record<string, SerializedPromptInput>, inputs: Record<string, SerializedPromptInput>,
class_type: string class_type: string
} }
/*
* All nodes in the graph and their input values.
*/
export type SerializedPromptInputsAll = Record<ComfyNodeID, SerializedPromptInputs> export type SerializedPromptInputsAll = Record<ComfyNodeID, SerializedPromptInputs>
export type SerializedPrompt = { export type SerializedPrompt = {
@@ -71,6 +95,9 @@ export type SerializedPrompt = {
output: SerializedPromptInputsAll output: SerializedPromptInputsAll
} }
/*
* Outputs for each node.
*/
export type SerializedPromptOutputs = Record<ComfyNodeID, ComfyExecutionResult> export type SerializedPromptOutputs = Record<ComfyNodeID, ComfyExecutionResult>
export type Progress = { export type Progress = {
@@ -78,6 +105,10 @@ export type Progress = {
max: number max: number
} }
/*
* A combo node and the backend node that will send an updated config over, for
* refreshing lists of model files
*/
type BackendComboNode = { type BackendComboNode = {
comboNode: ComfyComboNode, comboNode: ComfyComboNode,
comfyInput: IComfyInputSlot, comfyInput: IComfyInputSlot,
@@ -95,6 +126,7 @@ export default class ComfyApp {
nodeOutputs: Record<string, any> = {}; nodeOutputs: Record<string, any> = {};
shiftDown: boolean = false; shiftDown: boolean = false;
ctrlDown: boolean = false;
selectedGroupMoving: boolean = false; selectedGroupMoving: boolean = false;
private queueItems: QueueItem[] = []; private queueItems: QueueItem[] = [];
@@ -406,6 +438,7 @@ export default class ComfyApp {
private addKeyboardHandler() { private addKeyboardHandler() {
window.addEventListener("keydown", (e) => { window.addEventListener("keydown", (e) => {
this.shiftDown = e.shiftKey; this.shiftDown = e.shiftKey;
this.ctrlDown = e.ctrlKey;
// Queue prompt using ctrl or command + enter // Queue prompt using ctrl or command + enter
if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.keyCode === 13 || e.keyCode === 10)) { if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.keyCode === 13 || e.keyCode === 10)) {
@@ -414,6 +447,7 @@ export default class ComfyApp {
}); });
window.addEventListener("keyup", (e) => { window.addEventListener("keyup", (e) => {
this.shiftDown = e.shiftKey; this.shiftDown = e.shiftKey;
this.ctrlDown = e.ctrlKey;
}); });
} }
@@ -561,7 +595,9 @@ export default class ComfyApp {
} }
if (get(layoutState).attrs.queuePromptButtonRunWorkflow) { if (get(layoutState).attrs.queuePromptButtonRunWorkflow) {
this.queuePrompt(0, 1); // Hold control to queue at the front
const num = this.ctrlDown ? -1 : 0;
this.queuePrompt(num, 1);
} }
} }

View File

@@ -113,7 +113,7 @@ export class UpstreamNodeLocator {
} }
// If there are non-target nodes between us and another // If there are non-target nodes between us and another
// backend node, we have to traverse them first. This // target node, we have to traverse them first. This
// behavior is dependent on the type of node. Reroute nodes // behavior is dependent on the type of node. Reroute nodes
// will simply follow their single input, while branching // will simply follow their single input, while branching
// nodes have conditional logic that determines which link // nodes have conditional logic that determines which link

View File

@@ -312,7 +312,9 @@ export class ComfyExecuteSubgraphAction extends ComfyGraphNode {
if (!app) if (!app)
return; return;
app.queuePrompt(0, 1, tag); // Hold control to queue at the front
const num = app.ctrlDown ? -1 : 0;
app.queuePrompt(num, 1, tag);
} }
} }

View File

@@ -39,8 +39,10 @@ export type LayoutAttributes = {
queuePromptButtonName: string, queuePromptButtonName: string,
/* /*
* If true, clicking the "Queue Prompt" button will run the default subgraph. * If true, clicking the "Queue Prompt" button will run the default
* Set this to false if you need special behavior before running any subgraphs. * subgraph. Set this to false if you need special behavior before running
* any subgraphs, and instead use the `onDefaultQueueAction` event of the
* Comfy.QueueEvents node.
*/ */
queuePromptButtonRunWorkflow: boolean, queuePromptButtonRunWorkflow: boolean,
} }
@@ -84,6 +86,9 @@ export type LayoutState = {
*/ */
attrs: LayoutAttributes attrs: LayoutAttributes
/*
* Increment to force Svelte to re-render the props panel
*/
refreshPropsPanel: Writable<number> refreshPropsPanel: Writable<number>
} }
@@ -102,7 +107,7 @@ export type Attributes = {
title: string, title: string,
/* /*
* List of classes to apply to the component. * List of CSS classes to apply to the component.
*/ */
classes: string, classes: string,
@@ -204,22 +209,22 @@ export type AttributesSpec = {
values?: string[], values?: string[],
/* /*
* If `type` is "number", step for the slider * If `type` is "number", step for the slider that edits this attribute
*/ */
step?: number, step?: number,
/* /*
* If `type` is "number", min for the slider * If `type` is "number", min for the slider that edits this attribute
*/ */
min?: number, min?: number,
/* /*
* If `type` is "number", max for the slider * If `type` is "number", max for the slider that edits this attribute
*/ */
max?: number, max?: number,
/* /*
* If `type` is "string", display as a textarea. * If `type` is "string", display as a textarea instead of an input.
*/ */
multiline?: boolean, multiline?: boolean,
@@ -863,8 +868,9 @@ function nodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) {
let prevWidget = state.allItemsByNode[node.id] let prevWidget = state.allItemsByNode[node.id]
if (prevWidget == null) { if (prevWidget == null) {
// If a subgraph was cloned, try looking for the original widget node corresponding to the new widget node being added. // If a subgraph was cloned, try looking for the original widget node corresponding to the new widget node being added.
// `node` is the new ComfyWidgetNode instance to copy attrs to. // `node` is the new ComfyWidgetNode instance to copy layout attrs to.
// `options.cloneData` should contain the results of Subgraph.clone(), called "subgraphNewIDMapping". // `options.cloneData` should contain the results of Subgraph.clone(), which is named "subgraphNewIDMapping" in an
// entry of the `forNode` Record.
// `options.cloneData` is attached to the onNodeAdded options if a node is added to a graph after being // `options.cloneData` is attached to the onNodeAdded options if a node is added to a graph after being
// selection-cloned or pasted, as they both call clone() internally. // selection-cloned or pasted, as they both call clone() internally.
const cloneData = options.cloneData.forNode[options.prevNodeID] const cloneData = options.cloneData.forNode[options.prevNodeID]
@@ -879,7 +885,7 @@ function nodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) {
if (nodeIDInLayoutState) { if (nodeIDInLayoutState) {
// Gottem. // Gottem.
prevWidget = state.allItemsByNode[nodeIDInLayoutState] prevWidget = state.allItemsByNode[nodeIDInLayoutState]
console.warn("FOUND CLONED SUBGRAPH NODE", node.id, "=>", nodeIDInLayoutState, prevWidget) // console.warn("FOUND CLONED SUBGRAPH NODE", node.id, "=>", nodeIDInLayoutState, prevWidget)
} }
} }
} }

View File

@@ -4,10 +4,6 @@ import type { ComfyExecutionResult } from "$lib/nodes/ComfyWidgetNodes";
import notify from "$lib/notify"; import notify from "$lib/notify";
import { get, writable, type Writable } from "svelte/store"; import { get, writable, type Writable } from "svelte/store";
export type QueueItem = {
name: string
}
export type QueueEntryStatus = "success" | "error" | "interrupted" | "all_cached" | "unknown"; export type QueueEntryStatus = "success" | "error" | "interrupted" | "all_cached" | "unknown";
type QueueStateOps = { type QueueStateOps = {
@@ -23,8 +19,13 @@ type QueueStateOps = {
onExecuted: (promptID: PromptID, nodeID: ComfyNodeID, output: ComfyExecutionResult) => void onExecuted: (promptID: PromptID, nodeID: ComfyNodeID, output: ComfyExecutionResult) => void
} }
/*
* Single job that the backend keeps track of.
*/
export type QueueEntry = { export type QueueEntry = {
/* Data preserved on page refresh */ /*** Data preserved on page refresh ***/
/** Priority of the prompt. -1 means to queue at the front. */
number: number, number: number,
queuedAt?: Date, queuedAt?: Date,
finishedAt?: Date, finishedAt?: Date,
@@ -33,23 +34,34 @@ export type QueueEntry = {
extraData: ComfyBoxPromptExtraData, extraData: ComfyBoxPromptExtraData,
goodOutputs: ComfyNodeID[], goodOutputs: ComfyNodeID[],
/* Data not sent by ComfyUI's API, lost on page refresh */ /*** Data not sent by ComfyUI's API, lost on page refresh ***/
/* Prompt outputs, collected while the prompt is still executing */ /* Prompt outputs, collected while the prompt is still executing */
outputs: SerializedPromptOutputs, outputs: SerializedPromptOutputs,
/* Nodes of the workflow that have finished running so far. */
/* Nodes in of the workflow that have finished running so far. */
nodesRan: Set<ComfyNodeID>, nodesRan: Set<ComfyNodeID>,
/* Nodes of the workflow the backend reported as cached. */
cachedNodes: Set<ComfyNodeID> cachedNodes: Set<ComfyNodeID>
} }
/*
* Represents a queue entry that has finished executing (suceeded or failed) and
* has been moved to the history.
*/
export type CompletedQueueEntry = { export type CompletedQueueEntry = {
/** Corresponding entry in the queue, for the prompt/extra data */
entry: QueueEntry, entry: QueueEntry,
/** The result of this prompt, success/failed/cached */
status: QueueEntryStatus, status: QueueEntryStatus,
/** Message to display in the frontend */
message?: string, message?: string,
/** Detailed error/stacktrace, perhaps inspectible with a popup */
error?: string, error?: string,
} }
/*
* Keeps track of queued and completed (history) prompts.
*/
export type QueueState = { export type QueueState = {
queueRunning: Writable<QueueEntry[]>, queueRunning: Writable<QueueEntry[]>,
queuePending: Writable<QueueEntry[]>, queuePending: Writable<QueueEntry[]>,
@@ -57,6 +69,11 @@ export type QueueState = {
queueRemaining: number | "X" | null; queueRemaining: number | "X" | null;
runningNodeID: ComfyNodeID | null; runningNodeID: ComfyNodeID | null;
progress: Progress | null, progress: Progress | null,
/**
* If true, user pressed the "Interrupt" button in the frontend. Disable the
* button and wait until the next prompt starts running to re-enable it
* again
*/
isInterrupting: boolean isInterrupting: boolean
} }
type WritableQueueStateStore = Writable<QueueState> & QueueStateOps; type WritableQueueStateStore = Writable<QueueState> & QueueStateOps;
@@ -159,6 +176,7 @@ function moveToCompleted(index: number, queue: Writable<QueueEntry[]>, status: Q
return qc return qc
}) })
state.isInterrupting = false;
store.set(state) store.set(state)
} }