diff --git a/web/scripts/api.js b/web/scripts/api.js index b08f95d6..1b3bbecd 100644 --- a/web/scripts/api.js +++ b/web/scripts/api.js @@ -3,6 +3,9 @@ class ComfyApi extends EventTarget { super(); } + /** + * Poll status for colab and other things that don't support websockets. + */ #pollQueue() { setInterval(async () => { try { @@ -15,6 +18,10 @@ class ComfyApi extends EventTarget { }, 1000); } + /** + * Creates and connects a WebSocket for realtime updates + * @param {boolean} isReconnect If the socket is connection is a reconnect attempt + */ #createSocket(isReconnect) { if (this.socket) { return; @@ -74,15 +81,27 @@ class ComfyApi extends EventTarget { }); } + /** + * Initialises sockets and realtime updates + */ init() { this.#createSocket(); } + /** + * Loads node object definitions for the graph + * @returns The node definitions + */ async getNodeDefs() { const resp = await fetch("object_info", { cache: "no-store" }); return await resp.json(); } + /** + * + * @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, { output, workflow }) { const body = { client_id: this.clientId, @@ -111,6 +130,11 @@ class ComfyApi extends EventTarget { } } + /** + * Loads a list of items (queue or history) + * @param {string} type The type of items to load, queue or history + * @returns The items of the specified type grouped by their status + */ async getItems(type) { if (type === "queue") { return this.getQueue(); @@ -118,13 +142,20 @@ class ComfyApi extends EventTarget { return this.getHistory(); } + /** + * Gets the current state of the queue + * @returns The currently running and queued items + */ async getQueue() { try { const res = await fetch("/queue"); const data = await res.json(); return { // Running action uses a different endpoint for cancelling - Running: data.queue_running.map((prompt) => ({ prompt, remove: { name: "Cancel", cb: () => api.interrupt() } })), + Running: data.queue_running.map((prompt) => ({ + prompt, + remove: { name: "Cancel", cb: () => api.interrupt() }, + })), Pending: data.queue_pending.map((prompt) => ({ prompt })), }; } catch (error) { @@ -133,6 +164,10 @@ class ComfyApi extends EventTarget { } } + /** + * Gets the prompt execution history + * @returns Prompt history including node outputs + */ async getHistory() { try { const res = await fetch("/history"); @@ -143,6 +178,11 @@ class ComfyApi extends EventTarget { } } + /** + * Sends a POST request to the API + * @param {*} type The endpoint to post to + * @param {*} body Optional POST data + */ async #postItem(type, body) { try { await fetch("/" + type, { @@ -157,14 +197,26 @@ class ComfyApi extends EventTarget { } } + /** + * Deletes an item from the specified list + * @param {string} type The type of item to delete, queue or history + * @param {number} id The id of the item to delete + */ async deleteItem(type, id) { await this.#postItem(type, { delete: [id] }); } + /** + * Clears the specified list + * @param {string} type The type of list to clear, queue or history + */ async clearItems(type) { await this.#postItem(type, { clear: true }); } + /** + * Interrupts the execution of the running prompt + */ async interrupt() { await this.#postItem("interrupt", null); } diff --git a/web/scripts/app.js b/web/scripts/app.js index 580482d0..a39d6abe 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -45,6 +45,12 @@ class ComfyApp { console.error("[comfy]", message, ...other); } + /** + * Invoke an extension callback + * @param {string} method The extension callback to execute + * @param {...any} args Any arguments to pass to the callback + * @returns + */ #invokeExtensions(method, ...args) { let results = []; for (const ext of this.extensions) { @@ -64,6 +70,13 @@ class ComfyApp { return results; } + /** + * Invoke an async extension callback + * Each callback will be invoked concurrently + * @param {string} method The extension callback to execute + * @param {...any} args Any arguments to pass to the callback + * @returns + */ async #invokeExtensionsAsync(method, ...args) { return await Promise.all( this.extensions.map(async (ext) => { @@ -83,6 +96,11 @@ class ComfyApp { ); } + /** + * Adds special context menu handling for nodes + * e.g. this adds Open Image functionality for nodes that show images + * @param {*} node The node to add the menu handler + */ #addNodeContextMenuHandler(node) { node.prototype.getExtraMenuOptions = function (_, options) { if (this.imgs) { @@ -105,6 +123,11 @@ class ComfyApp { }; } + /** + * Adds Custom drawing logic for nodes + * e.g. Draws images and handles thumbnail navigation on nodes that output images + * @param {*} node The node to add the draw handler + */ #addDrawBackgroundHandler(node) { const app = this; node.prototype.onDrawBackground = function (ctx) { @@ -294,6 +317,9 @@ class ComfyApp { }; } + /** + * Adds a handler allowing drag+drop of files onto the window to load workflows + */ #addDropHandler() { // Get prompt from dropped PNG or json document.addEventListener("drop", async (event) => { @@ -304,6 +330,9 @@ class ComfyApp { }); } + /** + * Adds a handler on paste that extracts and loads workflows from pasted JSON data + */ #addPasteHandler() { document.addEventListener("paste", (e) => { let data = (e.clipboardData || window.clipboardData).getData("text/plain"); @@ -325,6 +354,9 @@ class ComfyApp { }); } + /** + * Draws currently executing node highlight and progress bar + */ #addDrawNodeProgressHandler() { const orig = LGraphCanvas.prototype.drawNodeShape; const self = this; @@ -373,6 +405,9 @@ class ComfyApp { }; } + /** + * Handles updates from the API socket + */ #addApiUpdateHandlers() { api.addEventListener("status", ({ detail }) => { this.ui.setStatus(detail); @@ -458,6 +493,9 @@ class ComfyApp { await this.#invokeExtensionsAsync("setup"); } + /** + * Registers nodes with the graph + */ async registerNodes() { const app = this; // Load node definitions from the backend @@ -555,6 +593,10 @@ class ComfyApp { } } + /** + * Converts the current graph workflow for sending to the API + * @returns The workflow and node links + */ graphToPrompt() { // TODO: Implement dynamic prompts const workflow = this.graph.serialize(); @@ -619,6 +661,10 @@ class ComfyApp { await this.ui.queue.update(); } + /** + * Loads workflow data from the specified file + * @param {File} file + */ async handleFile(file) { if (file.type === "image/png") { const pngInfo = await getPngMetadata(file);