From f64db2035a9cc41959a4931d18a0cae86352dd84 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 12 May 2023 13:46:42 -0500 Subject: [PATCH] Queue/history bar --- mobile/index.html | 3 +- src/lib/api.ts | 43 ++-- src/lib/components/ComfyApp.svelte | 6 +- src/lib/components/ComfyApp.ts | 20 +- src/lib/components/ComfyQueue.svelte | 286 +++++++++++++++++--------- src/lib/components/ProgressBar.svelte | 6 +- src/lib/components/Spinner.svelte | 24 +++ src/lib/stores/queueState.ts | 62 ++++-- src/lib/utils.ts | 6 + src/mobile/index.html | 14 -- src/scss/global.scss | 17 ++ 11 files changed, 321 insertions(+), 166 deletions(-) create mode 100644 src/lib/components/Spinner.svelte delete mode 100644 src/mobile/index.html diff --git a/mobile/index.html b/mobile/index.html index fa16259..b206f87 100644 --- a/mobile/index.html +++ b/mobile/index.html @@ -1,4 +1,3 @@ - @@ -9,7 +8,7 @@ -
+
diff --git a/src/lib/api.ts b/src/lib/api.ts index b7d502d..78bd989 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,14 +1,15 @@ -import type { Progress, SerializedPrompt, SerializedPromptOutput, SerializedPromptOutputs } from "./components/ComfyApp"; +import type { Progress, SerializedPrompt, SerializedPromptInputs, SerializedPromptInputsAll, SerializedPromptOutput, SerializedPromptOutputs } from "./components/ComfyApp"; import type TypedEmitter from "typed-emitter"; import EventEmitter from "events"; import type { GalleryOutput } from "./nodes/ComfyWidgetNodes"; +import type { SerializedLGraph } from "@litegraph-ts/core"; -type PromptRequestBody = { - client_id: string, - prompt: any, - extra_data: any, - front: boolean, - number: number | undefined +export type ComfyPromptRequest = { + client_id?: string, + prompt: SerializedPromptInputsAll, + extra_data: ComfyPromptExtraData, + front?: boolean, + number?: number } export type QueueItemType = "queue" | "history"; @@ -34,8 +35,8 @@ export type PromptID = string; // UUID export type ComfyAPIHistoryItem = [ number, // prompt number PromptID, - SerializedPrompt, - any, // extra data + SerializedPromptInputsAll, + ComfyPromptExtraData, NodeID[] // good outputs ] @@ -54,6 +55,16 @@ export type ComfyAPIHistoryResponse = { error?: string } +export type ComfyPromptPNGInfo = { + workflow: SerializedLGraph +} + +export type ComfyPromptExtraData = { + extra_pnginfo?: ComfyPromptPNGInfo, + client_id?: string, // UUID + subgraphs: string[] +} + type ComfyAPIEvents = { status: (status: ComfyAPIStatusResponse | null, error?: Error | null) => void, progress: (progress: Progress) => void, @@ -219,19 +230,11 @@ export default class ComfyAPI { * @param {number} number The index at which to queue the prompt, passing -1 will insert the prompt at the front of the queue * @param {object} prompt The prompt data to queue */ - async queuePrompt(number: number, { output, workflow }, extra_data: any): Promise { - const body: PromptRequestBody = { - client_id: this.clientId, - prompt: output, - extra_data, - front: false, - number: number - }; + async queuePrompt(body: ComfyPromptRequest): Promise { + body.client_id = this.clientId; - if (number === -1) { + if (body.number === -1) { body.front = true; - } else if (number != 0) { - body.number = number; } let postBody = null; diff --git a/src/lib/components/ComfyApp.svelte b/src/lib/components/ComfyApp.svelte index 8eddb03..43cced4 100644 --- a/src/lib/components/ComfyApp.svelte +++ b/src/lib/components/ComfyApp.svelte @@ -78,7 +78,7 @@ } } - let propsSidebarSize = 15; //15; + let propsSidebarSize = 0; function toggleProps() { if (propsSidebarSize == 0) { @@ -90,11 +90,11 @@ } } - let queueSidebarSize = 15; + let queueSidebarSize = 20; function toggleQueue() { if (queueSidebarSize == 0) { - queueSidebarSize = 15; + queueSidebarSize = 20; app.resizeCanvas(); } else { diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 8f8f0c4..80e2991 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -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 type { LConnectionKind, INodeSlot } from "@litegraph-ts/core"; -import ComfyAPI, { type ComfyAPIStatusResponse, type NodeID, type PromptID } from "$lib/api" +import ComfyAPI, { type ComfyAPIStatusResponse, type ComfyPromptExtraData, type ComfyPromptRequest, type NodeID, type PromptID } from "$lib/api" import { getPngMetadata, importA1111 } from "$lib/pnginfo"; import EventEmitter from "events"; import type TypedEmitter from "typed-emitter"; @@ -385,6 +385,7 @@ export default class ComfyApp { const queue = await this.api.getQueue(); const history = await this.api.getHistory(); queueState.queueUpdated(queue); + queueState.historyUpdated(history); } private requestPermissions() { @@ -739,13 +740,24 @@ export default class ComfyApp { const p = await this.graphToPrompt(tag); console.debug(promptToGraphVis(p)) - const extra_data = { extra_pnginfo: { workflow: p.workflow } } + const extraData: ComfyPromptExtraData = { + extra_pnginfo: { + workflow: p.workflow + }, + subgraphs: [tag] + } let error = null; let promptID = null; + const request: ComfyPromptRequest = { + number: num, + extra_data: extraData, + prompt: p.output + } + try { - const response = await this.api.queuePrompt(num, p, extra_data); + const response = await this.api.queuePrompt(request); promptID = response.promptID; error = response.error; } catch (error) { @@ -768,7 +780,7 @@ export default class ComfyApp { } this.lCanvas.draw(true, true); - queueState.afterQueued(promptID, num, p, extra_data) + queueState.afterQueued(promptID, num, p.output, extraData) } } } finally { diff --git a/src/lib/components/ComfyQueue.svelte b/src/lib/components/ComfyQueue.svelte index d32ebba..5911fbe 100644 --- a/src/lib/components/ComfyQueue.svelte +++ b/src/lib/components/ComfyQueue.svelte @@ -1,74 +1,113 @@
{#each _entries as entry}
- {#if entry.images} + {#if entry.images.length > 0} thumbnail {:else} -
+ {/if}
@@ -88,68 +127,79 @@
{/each}
+
+
switchMode("queue")} + class:mode-selected={mode === "queue"}> + Queue +
+
switchMode("history")} + class:mode-selected={mode === "history"}> + History +
+
- {#if $queueState.runningNodeID || $queueState.progress} +
+ {#if inProgress} + +
+ Queued prompts: {$queueState.queueRemaining} +
+ {:else} +
+ Nothing queued. +
+ {/if} +
+ {#if queued}
Node: {getNodeInfo($queueState.runningNodeID)}
- -
- {/if} - {#if typeof $queueState.queueRemaining === "number" && $queueState.queueRemaining > 0} -
-
- Queued prompts: {$queueState.queueRemaining}. -
-
- {:else} -
-
- Nothing queued. -
+
{/if}
diff --git a/src/lib/components/ProgressBar.svelte b/src/lib/components/ProgressBar.svelte index 61d13bf..8d56126 100644 --- a/src/lib/components/ProgressBar.svelte +++ b/src/lib/components/ProgressBar.svelte @@ -24,16 +24,16 @@ diff --git a/src/lib/stores/queueState.ts b/src/lib/stores/queueState.ts index f0eabc8..6d75250 100644 --- a/src/lib/stores/queueState.ts +++ b/src/lib/stores/queueState.ts @@ -1,5 +1,5 @@ -import type { ComfyAPIHistoryItem, ComfyAPIQueueResponse, ComfyAPIStatusResponse, NodeID, PromptID } from "$lib/api"; -import type { Progress, SerializedPrompt, SerializedPromptOutputs } from "$lib/components/ComfyApp"; +import type { ComfyAPIHistoryEntry, ComfyAPIHistoryItem, ComfyAPIHistoryResponse, ComfyAPIQueueResponse, ComfyAPIStatusResponse, ComfyPromptExtraData, NodeID, PromptID } from "$lib/api"; +import type { Progress, SerializedPrompt, SerializedPromptInputsAll, SerializedPromptOutputs } from "$lib/components/ComfyApp"; import type { GalleryOutput } from "$lib/nodes/ComfyWidgetNodes"; import { get, writable, type Writable } from "svelte/store"; @@ -7,33 +7,39 @@ export type QueueItem = { name: string } +export type QueueEntryStatus = "success" | "error" | "all_cached" | "unknown"; + type QueueStateOps = { - queueUpdated: (queue: ComfyAPIQueueResponse) => void, + 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, executionError: (promptID: PromptID, message: string) => void, progressUpdated: (progress: Progress) => void - afterQueued: (promptID: PromptID, number: number, prompt: SerializedPrompt, extraData: any) => void + afterQueued: (promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void onExecuted: (promptID: PromptID, nodeID: NodeID, output: GalleryOutput) => void } export type QueueEntry = { + /* Data preserved on page refresh */ number: number, queuedAt?: Date, finishedAt?: Date, promptID: PromptID, - prompt: SerializedPrompt, - extraData: any, + prompt: SerializedPromptInputsAll, + extraData: ComfyPromptExtraData, goodOutputs: NodeID[], - // Collected while the prompt is still executing + /* Data not sent by Comfy's API, lost on page refresh */ + /* Prompt outputs, collected while the prompt is still executing */ outputs: SerializedPromptOutputs, + } export type CompletedQueueEntry = { entry: QueueEntry, - type: "success" | "error" | "all_cached", + status: QueueEntryStatus, error?: string, } @@ -70,12 +76,31 @@ function toQueueEntry(resp: ComfyAPIHistoryItem): QueueEntry { } } -function queueUpdated(queue: ComfyAPIQueueResponse) { - console.debug("[queueState] queueUpdated", queue.running.length, queue.pending.length) +function toCompletedQueueEntry(resp: ComfyAPIHistoryEntry): CompletedQueueEntry { + const entry = toQueueEntry(resp.prompt) + entry.outputs = resp.outputs; + return { + entry, + status: Object.values(entry.outputs).length > 0 ? "success" : "all_cached", + error: null + } +} + +function queueUpdated(resp: ComfyAPIQueueResponse) { + console.debug("[queueState] queueUpdated", resp.running.length, resp.pending.length) store.update((s) => { - s.queueRunning.set(queue.running.map(toQueueEntry)); - s.queuePending.set(queue.pending.map(toQueueEntry)); - s.queueRemaining = queue.pending.length; + s.queueRunning.set(resp.running.map(toQueueEntry)); + s.queuePending.set(resp.pending.map(toQueueEntry)); + s.queueRemaining = resp.pending.length; + return s + }) +} + +function historyUpdated(resp: ComfyAPIHistoryResponse) { + console.debug("[queueState] historyUpdated", Object.values(resp.history).length) + store.update((s) => { + const values = Object.values(resp.history) // TODO Order by prompt finished date! + s.queueCompleted.set(values.map(toCompletedQueueEntry)); return s }) } @@ -115,7 +140,7 @@ function executingUpdated(promptID: PromptID | null, runningNodeID: NodeID | nul s.queueCompleted.update(qc => { const completed: CompletedQueueEntry = { entry, - type: "success", + status: "success", } qc.push(completed) return qc @@ -142,7 +167,7 @@ function executionCached(promptID: PromptID, nodes: NodeID[]) { s.queueCompleted.update(qc => { const completed: CompletedQueueEntry = { entry, - type: "all_cached", + status: "all_cached", } qc.push(completed) return qc @@ -167,7 +192,7 @@ function executionError(promptID: PromptID, message: string) { s.queueCompleted.update(qc => { const completed: CompletedQueueEntry = { entry, - type: "error", + status: "error", error: message } qc.push(completed) @@ -180,8 +205,8 @@ function executionError(promptID: PromptID, message: string) { }) } -function afterQueued(promptID: PromptID, number: number, prompt: SerializedPrompt, extraData: any) { - console.debug("[queueState] afterQueued", promptID, Object.keys(prompt.workflow.nodes)) +function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) { + console.debug("[queueState] afterQueued", promptID, Object.keys(prompt.nodes)) store.update(s => { const entry: QueueEntry = { number, @@ -215,6 +240,7 @@ const queueStateStore: WritableQueueStateStore = { ...store, queueUpdated, + historyUpdated, statusUpdated, progressUpdated, executingUpdated, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 005dd6a..bbb31f4 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -135,6 +135,12 @@ export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileDat }); } +export function convertComfyOutputToComfyURL(output: GalleryOutputEntry): string { + const params = new URLSearchParams(output) + const url = `http://${location.hostname}:8188` // TODO make configurable + return url + "/view?" + params +} + export function convertFilenameToComfyURL(filename: string, subfolder: string = "", type: "input" | "output" | "temp" = "output"): string { diff --git a/src/mobile/index.html b/src/mobile/index.html deleted file mode 100644 index c2e2ab8..0000000 --- a/src/mobile/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - -
- - - diff --git a/src/scss/global.scss b/src/scss/global.scss index 475fb90..eff88aa 100644 --- a/src/scss/global.scss +++ b/src/scss/global.scss @@ -21,6 +21,12 @@ body { --comfy-dropdown-item-background-hover: var(--neutral-400); --comfy-dropdown-item-color-active: var(--neutral-100); --comfy-dropdown-item-background-active: var(--secondary-600); + --comfy-progress-bar-background: var(--secondary-300); + --comfy-progress-bar-foreground: #var(--body-text-color); + --comfy-node-name-background: var(--color-red-300); + --comfy-node-name-foreground: var(--body-text-color); + --comfy-spinner-main-color: var(--neutral-400); + --comfy-spinner-accent-color: var(--secondary-500); } .dark { @@ -34,6 +40,17 @@ body { --comfy-dropdown-item-background-hover: var(--neutral-600); --comfy-dropdown-item-background-active: var(--secondary-600); --comfy-dropdown-border-color: var(--neutral-600); + --comfy-progress-bar-background: var(--secondary-400); + --comfy-progress-bar-foreground: #var(--body-text-color); + --comfy-node-name-background: var(--primary-500); + --comfy-node-name-foreground: var(--body-text-color); + --comfy-spinner-main-color: var(--neutral-600); + --comfy-spinner-accent-color: var(--secondary-600); +} + +.mobile { + --comfy-progress-bar-background: lightgrey; + --comfy-progress-bar-foreground: #B3D8A9 } @mixin disable-input {