| class ComfyApi extends EventTarget { |
| #registered = new Set(); |
|
|
| constructor() { |
| super(); |
| this.api_host = location.host; |
| this.api_base = location.pathname.split('/').slice(0, -1).join('/'); |
| this.initialClientId = sessionStorage.getItem("clientId"); |
| } |
|
|
| apiURL(route) { |
| return this.api_base + route; |
| } |
|
|
| fetchApi(route, options) { |
| if (!options) { |
| options = {}; |
| } |
| if (!options.headers) { |
| options.headers = {}; |
| } |
| options.headers["Comfy-User"] = this.user; |
| return fetch(this.apiURL(route), options); |
| } |
|
|
| addEventListener(type, callback, options) { |
| super.addEventListener(type, callback, options); |
| this.#registered.add(type); |
| } |
|
|
| |
| |
| |
| #pollQueue() { |
| setInterval(async () => { |
| try { |
| const resp = await this.fetchApi("/prompt"); |
| const status = await resp.json(); |
| this.dispatchEvent(new CustomEvent("status", { detail: status })); |
| } catch (error) { |
| this.dispatchEvent(new CustomEvent("status", { detail: null })); |
| } |
| }, 1000); |
| } |
|
|
| |
| |
| |
| |
| #createSocket(isReconnect) { |
| if (this.socket) { |
| return; |
| } |
|
|
| let opened = false; |
| let existingSession = window.name; |
| if (existingSession) { |
| existingSession = "?clientId=" + existingSession; |
| } |
| this.socket = new WebSocket( |
| `ws${window.location.protocol === "https:" ? "s" : ""}://${this.api_host}${this.api_base}/ws${existingSession}` |
| ); |
| this.socket.binaryType = "arraybuffer"; |
|
|
| this.socket.addEventListener("open", () => { |
| opened = true; |
| if (isReconnect) { |
| this.dispatchEvent(new CustomEvent("reconnected")); |
| } |
| }); |
|
|
| this.socket.addEventListener("error", () => { |
| if (this.socket) this.socket.close(); |
| if (!isReconnect && !opened) { |
| this.#pollQueue(); |
| } |
| }); |
|
|
| this.socket.addEventListener("close", () => { |
| setTimeout(() => { |
| this.socket = null; |
| this.#createSocket(true); |
| }, 300); |
| if (opened) { |
| this.dispatchEvent(new CustomEvent("status", { detail: null })); |
| this.dispatchEvent(new CustomEvent("reconnecting")); |
| } |
| }); |
|
|
| this.socket.addEventListener("message", (event) => { |
| try { |
| if (event.data instanceof ArrayBuffer) { |
| const view = new DataView(event.data); |
| const eventType = view.getUint32(0); |
| const buffer = event.data.slice(4); |
| switch (eventType) { |
| case 1: |
| const view2 = new DataView(event.data); |
| const imageType = view2.getUint32(0) |
| let imageMime |
| switch (imageType) { |
| case 1: |
| default: |
| imageMime = "image/jpeg"; |
| break; |
| case 2: |
| imageMime = "image/png" |
| } |
| const imageBlob = new Blob([buffer.slice(4)], { type: imageMime }); |
| this.dispatchEvent(new CustomEvent("b_preview", { detail: imageBlob })); |
| break; |
| default: |
| throw new Error(`Unknown binary websocket message of type ${eventType}`); |
| } |
| } |
| else { |
| const msg = JSON.parse(event.data); |
| switch (msg.type) { |
| case "status": |
| if (msg.data.sid) { |
| this.clientId = msg.data.sid; |
| window.name = this.clientId; |
| sessionStorage.setItem("clientId", this.clientId); |
| } |
| this.dispatchEvent(new CustomEvent("status", { detail: msg.data.status })); |
| break; |
| case "progress": |
| this.dispatchEvent(new CustomEvent("progress", { detail: msg.data })); |
| break; |
| case "executing": |
| this.dispatchEvent(new CustomEvent("executing", { detail: msg.data.node })); |
| break; |
| case "executed": |
| this.dispatchEvent(new CustomEvent("executed", { detail: msg.data })); |
| break; |
| case "execution_start": |
| this.dispatchEvent(new CustomEvent("execution_start", { detail: msg.data })); |
| break; |
| case "execution_error": |
| this.dispatchEvent(new CustomEvent("execution_error", { detail: msg.data })); |
| break; |
| case "execution_cached": |
| this.dispatchEvent(new CustomEvent("execution_cached", { detail: msg.data })); |
| break; |
| default: |
| if (this.#registered.has(msg.type)) { |
| this.dispatchEvent(new CustomEvent(msg.type, { detail: msg.data })); |
| } else { |
| throw new Error(`Unknown message type ${msg.type}`); |
| } |
| } |
| } |
| } catch (error) { |
| console.warn("Unhandled message:", event.data, error); |
| } |
| }); |
| } |
|
|
| |
| |
| |
| init() { |
| this.#createSocket(); |
| } |
|
|
| |
| |
| |
| |
| async getExtensions() { |
| const resp = await this.fetchApi("/extensions", { cache: "no-store" }); |
| return await resp.json(); |
| } |
|
|
| |
| |
| |
| |
| async getEmbeddings() { |
| const resp = await this.fetchApi("/embeddings", { cache: "no-store" }); |
| return await resp.json(); |
| } |
|
|
| |
| |
| |
| |
| async getNodeDefs() { |
| const resp = await this.fetchApi("/object_info", { cache: "no-store" }); |
| return await resp.json(); |
| } |
|
|
| |
| |
| |
| |
| |
| async queuePrompt(number, { output, workflow }) { |
| const body = { |
| client_id: this.clientId, |
| prompt: output, |
| extra_data: { extra_pnginfo: { workflow } }, |
| }; |
|
|
| if (number === -1) { |
| body.front = true; |
| } else if (number != 0) { |
| body.number = number; |
| } |
|
|
| const res = await this.fetchApi("/prompt", { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| }, |
| body: JSON.stringify(body), |
| }); |
|
|
| if (res.status !== 200) { |
| throw { |
| response: await res.json(), |
| }; |
| } |
|
|
| return await res.json(); |
| } |
|
|
| |
| |
| |
| |
| |
| async getItems(type) { |
| if (type === "queue") { |
| return this.getQueue(); |
| } |
| return this.getHistory(); |
| } |
|
|
| |
| |
| |
| |
| async getQueue() { |
| try { |
| const res = await this.fetchApi("/queue"); |
| const data = await res.json(); |
| return { |
| |
| Running: data.queue_running.map((prompt) => ({ |
| prompt, |
| remove: { name: "Cancel", cb: () => api.interrupt() }, |
| })), |
| Pending: data.queue_pending.map((prompt) => ({ prompt })), |
| }; |
| } catch (error) { |
| console.error(error); |
| return { Running: [], Pending: [] }; |
| } |
| } |
|
|
| |
| |
| |
| |
| async getHistory(max_items=200) { |
| try { |
| const res = await this.fetchApi(`/history?max_items=${max_items}`); |
| return { History: Object.values(await res.json()) }; |
| } catch (error) { |
| console.error(error); |
| return { History: [] }; |
| } |
| } |
|
|
| |
| |
| |
| |
| async getSystemStats() { |
| const res = await this.fetchApi("/system_stats"); |
| return await res.json(); |
| } |
|
|
| |
| |
| |
| |
| |
| async #postItem(type, body) { |
| try { |
| await this.fetchApi("/" + type, { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| }, |
| body: body ? JSON.stringify(body) : undefined, |
| }); |
| } catch (error) { |
| console.error(error); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| async deleteItem(type, id) { |
| await this.#postItem(type, { delete: [id] }); |
| } |
|
|
| |
| |
| |
| |
| async clearItems(type) { |
| await this.#postItem(type, { clear: true }); |
| } |
|
|
| |
| |
| |
| async interrupt() { |
| await this.#postItem("interrupt", null); |
| } |
|
|
| |
| |
| |
| |
| async getUserConfig() { |
| return (await this.fetchApi("/users")).json(); |
| } |
|
|
| |
| |
| |
| |
| |
| createUser(username) { |
| return this.fetchApi("/users", { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| }, |
| body: JSON.stringify({ username }), |
| }); |
| } |
|
|
| |
| |
| |
| |
| async getSettings() { |
| return (await this.fetchApi("/settings")).json(); |
| } |
|
|
| |
| |
| |
| |
| |
| async getSetting(id) { |
| return (await this.fetchApi(`/settings/${encodeURIComponent(id)}`)).json(); |
| } |
|
|
| |
| |
| |
| |
| |
| async storeSettings(settings) { |
| return this.fetchApi(`/settings`, { |
| method: "POST", |
| body: JSON.stringify(settings) |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async storeSetting(id, value) { |
| return this.fetchApi(`/settings/${encodeURIComponent(id)}`, { |
| method: "POST", |
| body: JSON.stringify(value) |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async getUserData(file, options) { |
| return this.fetchApi(`/userdata/${encodeURIComponent(file)}`, options); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async storeUserData(file, data, options = { stringify: true, throwOnError: true }) { |
| const resp = await this.fetchApi(`/userdata/${encodeURIComponent(file)}`, { |
| method: "POST", |
| body: options?.stringify ? JSON.stringify(data) : data, |
| ...options, |
| }); |
| if (resp.status !== 200) { |
| throw new Error(`Error storing user data file '${file}': ${resp.status} ${(await resp).statusText}`); |
| } |
| } |
| } |
|
|
| export const api = new ComfyApi(); |
|
|