Super hacky

Load all the remote JS files in the most unconventional way to work locally.  Still not perfect.  Most of the interface and custom nodes can load, but it's obviously still broken.  The next way I'll go about this is a hack to have local versions of the custom nodes and during setup import from local... Or, create a script to download and save the files locally from remote before local startup
This commit is contained in:
Andre Molnar 2023-12-01 22:31:21 -05:00
parent 86bcd911a4
commit d25af394ec
2 changed files with 2584 additions and 2333 deletions

View File

@ -1,324 +1,348 @@
class ComfyApi extends EventTarget { class ComfyApi extends EventTarget {
#registered = new Set(); #registered = new Set();
constructor() { constructor() {
super(); super();
this.api_host = location.host; this.api_host = location.host;
this.api_base = location.pathname.split('/').slice(0, -1).join('/'); this.api_base = location.pathname.split("/").slice(0, -1).join("/");
} }
set apiBase(apiBase) { set apiBase(apiBase) {
this.api_base = apiBase; this.api_base = apiBase;
} this.api_host = this.api_base.replace(/^(https?:|)\/\//, "");
}
apiURL(route) { apiURL(route) {
return this.api_base + route; return this.api_base + route;
} }
fetchApi(route, options) { fetchApi(route, options) {
return fetch(this.apiURL(route), options); return fetch(this.apiURL(route), options);
} }
addEventListener(type, callback, options) { addEventListener(type, callback, options) {
super.addEventListener(type, callback, options); super.addEventListener(type, callback, options);
this.#registered.add(type); this.#registered.add(type);
} }
/** /**
* Poll status for colab and other things that don't support websockets. * Poll status for colab and other things that don't support websockets.
*/ */
#pollQueue() { #pollQueue() {
setInterval(async () => { setInterval(async () => {
try { try {
const resp = await this.fetchApi("/prompt"); const resp = await this.fetchApi("/prompt");
const status = await resp.json(); const status = await resp.json();
this.dispatchEvent(new CustomEvent("status", { detail: status })); this.dispatchEvent(new CustomEvent("status", { detail: status }));
} catch (error) { } catch (error) {
this.dispatchEvent(new CustomEvent("status", { detail: null })); this.dispatchEvent(new CustomEvent("status", { detail: null }));
} }
}, 1000); }, 1000);
} }
/** /**
* Creates and connects a WebSocket for realtime updates * Creates and connects a WebSocket for realtime updates
* @param {boolean} isReconnect If the socket is connection is a reconnect attempt * @param {boolean} isReconnect If the socket is connection is a reconnect attempt
*/ */
#createSocket(isReconnect) { #createSocket(isReconnect) {
if (this.socket) { if (this.socket) {
return; return;
} }
let opened = false; let opened = false;
let existingSession = window.name; let existingSession = window.name;
if (existingSession) { if (existingSession) {
existingSession = "?clientId=" + existingSession; existingSession = "?clientId=" + existingSession;
} }
this.socket = new WebSocket( this.socket = new WebSocket(
`ws${window.location.protocol === "https:" ? "s" : ""}://${this.api_host}${this.api_base}/ws${existingSession}` `ws${window.location.protocol === "https:" ? "s" : ""}://${
); this.api_host
this.socket.binaryType = "arraybuffer"; }/ws${existingSession}`
);
this.socket.binaryType = "arraybuffer";
this.socket.addEventListener("open", () => { this.socket.addEventListener("open", () => {
opened = true; opened = true;
if (isReconnect) { if (isReconnect) {
this.dispatchEvent(new CustomEvent("reconnected")); this.dispatchEvent(new CustomEvent("reconnected"));
} }
}); });
this.socket.addEventListener("error", () => { this.socket.addEventListener("error", () => {
if (this.socket) this.socket.close(); if (this.socket) this.socket.close();
if (!isReconnect && !opened) { if (!isReconnect && !opened) {
this.#pollQueue(); this.#pollQueue();
} }
}); });
this.socket.addEventListener("close", () => { this.socket.addEventListener("close", () => {
setTimeout(() => { setTimeout(() => {
this.socket = null; this.socket = null;
this.#createSocket(true); this.#createSocket(true);
}, 300); }, 300);
if (opened) { if (opened) {
this.dispatchEvent(new CustomEvent("status", { detail: null })); this.dispatchEvent(new CustomEvent("status", { detail: null }));
this.dispatchEvent(new CustomEvent("reconnecting")); this.dispatchEvent(new CustomEvent("reconnecting"));
} }
}); });
this.socket.addEventListener("message", (event) => { this.socket.addEventListener("message", (event) => {
try { try {
if (event.data instanceof ArrayBuffer) { if (event.data instanceof ArrayBuffer) {
const view = new DataView(event.data); const view = new DataView(event.data);
const eventType = view.getUint32(0); const eventType = view.getUint32(0);
const buffer = event.data.slice(4); const buffer = event.data.slice(4);
switch (eventType) { switch (eventType) {
case 1: case 1:
const view2 = new DataView(event.data); const view2 = new DataView(event.data);
const imageType = view2.getUint32(0) const imageType = view2.getUint32(0);
let imageMime let imageMime;
switch (imageType) { switch (imageType) {
case 1: case 1:
default: default:
imageMime = "image/jpeg"; imageMime = "image/jpeg";
break; break;
case 2: case 2:
imageMime = "image/png" imageMime = "image/png";
} }
const imageBlob = new Blob([buffer.slice(4)], { type: imageMime }); const imageBlob = new Blob([buffer.slice(4)], {
this.dispatchEvent(new CustomEvent("b_preview", { detail: imageBlob })); type: imageMime,
break; });
default: this.dispatchEvent(
throw new Error(`Unknown binary websocket message of type ${eventType}`); new CustomEvent("b_preview", { detail: imageBlob })
} );
} break;
else { default:
const msg = JSON.parse(event.data); throw new Error(
switch (msg.type) { `Unknown binary websocket message of type ${eventType}`
case "status": );
if (msg.data.sid) { }
this.clientId = msg.data.sid; } else {
window.name = this.clientId; const msg = JSON.parse(event.data);
} switch (msg.type) {
this.dispatchEvent(new CustomEvent("status", { detail: msg.data.status })); case "status":
break; if (msg.data.sid) {
case "progress": this.clientId = msg.data.sid;
this.dispatchEvent(new CustomEvent("progress", { detail: msg.data })); window.name = this.clientId;
break; }
case "executing": this.dispatchEvent(
this.dispatchEvent(new CustomEvent("executing", { detail: msg.data.node })); new CustomEvent("status", { detail: msg.data.status })
break; );
case "executed": break;
this.dispatchEvent(new CustomEvent("executed", { detail: msg.data })); case "progress":
break; this.dispatchEvent(
case "execution_start": new CustomEvent("progress", { detail: msg.data })
this.dispatchEvent(new CustomEvent("execution_start", { detail: msg.data })); );
break; break;
case "execution_error": case "executing":
this.dispatchEvent(new CustomEvent("execution_error", { detail: msg.data })); this.dispatchEvent(
break; new CustomEvent("executing", { detail: msg.data.node })
case "execution_cached": );
this.dispatchEvent(new CustomEvent("execution_cached", { detail: msg.data })); break;
break; case "executed":
default: this.dispatchEvent(
if (this.#registered.has(msg.type)) { new CustomEvent("executed", { detail: msg.data })
this.dispatchEvent(new CustomEvent(msg.type, { detail: msg.data })); );
} else { break;
throw new Error(`Unknown message type ${msg.type}`); case "execution_start":
} this.dispatchEvent(
} new CustomEvent("execution_start", { detail: msg.data })
} );
} catch (error) { break;
console.warn("Unhandled message:", event.data, error); 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);
}
});
}
/** /**
* Initialises sockets and realtime updates * Initialises sockets and realtime updates
*/ */
init() { init() {
this.#createSocket(); this.#createSocket();
} }
/** /**
* Gets a list of extension urls * Gets a list of extension urls
* @returns An array of script urls to import * @returns An array of script urls to import
*/ */
async getExtensions() { async getExtensions() {
const resp = await this.fetchApi("/extensions", { cache: "no-store" }); const resp = await this.fetchApi("/extensions", { cache: "no-store" });
return await resp.json(); return await resp.json();
} }
/** /**
* Gets a list of embedding names * Gets a list of embedding names
* @returns An array of script urls to import * @returns An array of script urls to import
*/ */
async getEmbeddings() { async getEmbeddings() {
const resp = await this.fetchApi("/embeddings", { cache: "no-store" }); const resp = await this.fetchApi("/embeddings", { cache: "no-store" });
return await resp.json(); return await resp.json();
} }
/** /**
* Loads node object definitions for the graph * Loads node object definitions for the graph
* @returns The node definitions * @returns The node definitions
*/ */
async getNodeDefs() { async getNodeDefs() {
const resp = await this.fetchApi("/object_info", { cache: "no-store" }); const resp = await this.fetchApi("/object_info", { cache: "no-store" });
return await resp.json(); 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 {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 * @param {object} prompt The prompt data to queue
*/ */
async queuePrompt(number, { output, workflow }) { async queuePrompt(number, { output, workflow }) {
const body = { const body = {
client_id: this.clientId, client_id: this.clientId,
prompt: output, prompt: output,
extra_data: { extra_pnginfo: { workflow } }, extra_data: { extra_pnginfo: { workflow } },
}; };
if (number === -1) { if (number === -1) {
body.front = true; body.front = true;
} else if (number != 0) { } else if (number != 0) {
body.number = number; body.number = number;
} }
const res = await this.fetchApi("/prompt", { const res = await this.fetchApi("/prompt", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
if (res.status !== 200) { if (res.status !== 200) {
throw { throw {
response: await res.json(), response: await res.json(),
}; };
} }
return await res.json(); return await res.json();
} }
/** /**
* Loads a list of items (queue or history) * Loads a list of items (queue or history)
* @param {string} type The type of items to load, 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 * @returns The items of the specified type grouped by their status
*/ */
async getItems(type) { async getItems(type) {
if (type === "queue") { if (type === "queue") {
return this.getQueue(); return this.getQueue();
} }
return this.getHistory(); return this.getHistory();
} }
/** /**
* Gets the current state of the queue * Gets the current state of the queue
* @returns The currently running and queued items * @returns The currently running and queued items
*/ */
async getQueue() { async getQueue() {
try { try {
const res = await this.fetchApi("/queue"); const res = await this.fetchApi("/queue");
const data = await res.json(); const data = await res.json();
return { return {
// Running action uses a different endpoint for cancelling // Running action uses a different endpoint for cancelling
Running: data.queue_running.map((prompt) => ({ Running: data.queue_running.map((prompt) => ({
prompt, prompt,
remove: { name: "Cancel", cb: () => api.interrupt() }, remove: { name: "Cancel", cb: () => api.interrupt() },
})), })),
Pending: data.queue_pending.map((prompt) => ({ prompt })), Pending: data.queue_pending.map((prompt) => ({ prompt })),
}; };
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return { Running: [], Pending: [] }; return { Running: [], Pending: [] };
} }
} }
/** /**
* Gets the prompt execution history * Gets the prompt execution history
* @returns Prompt history including node outputs * @returns Prompt history including node outputs
*/ */
async getHistory(max_items=200) { async getHistory(max_items = 200) {
try { try {
const res = await this.fetchApi(`/history?max_items=${max_items}`); const res = await this.fetchApi(`/history?max_items=${max_items}`);
return { History: Object.values(await res.json()) }; return { History: Object.values(await res.json()) };
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return { History: [] }; return { History: [] };
} }
} }
/** /**
* Gets system & device stats * Gets system & device stats
* @returns System stats such as python version, OS, per device info * @returns System stats such as python version, OS, per device info
*/ */
async getSystemStats() { async getSystemStats() {
const res = await this.fetchApi("/system_stats"); const res = await this.fetchApi("/system_stats");
return await res.json(); return await res.json();
} }
/** /**
* Sends a POST request to the API * Sends a POST request to the API
* @param {*} type The endpoint to post to * @param {*} type The endpoint to post to
* @param {*} body Optional POST data * @param {*} body Optional POST data
*/ */
async #postItem(type, body) { async #postItem(type, body) {
try { try {
await this.fetchApi("/" + type, { await this.fetchApi("/" + type, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: body ? JSON.stringify(body) : undefined, body: body ? JSON.stringify(body) : undefined,
}); });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
} }
/** /**
* Deletes an item from the specified list * Deletes an item from the specified list
* @param {string} type The type of item to delete, queue or history * @param {string} type The type of item to delete, queue or history
* @param {number} id The id of the item to delete * @param {number} id The id of the item to delete
*/ */
async deleteItem(type, id) { async deleteItem(type, id) {
await this.#postItem(type, { delete: [id] }); await this.#postItem(type, { delete: [id] });
} }
/** /**
* Clears the specified list * Clears the specified list
* @param {string} type The type of list to clear, queue or history * @param {string} type The type of list to clear, queue or history
*/ */
async clearItems(type) { async clearItems(type) {
await this.#postItem(type, { clear: true }); await this.#postItem(type, { clear: true });
} }
/** /**
* Interrupts the execution of the running prompt * Interrupts the execution of the running prompt
*/ */
async interrupt() { async interrupt() {
await this.#postItem("interrupt", null); await this.#postItem("interrupt", null);
} }
} }
export const api = new ComfyApi(); export const api = new ComfyApi();

File diff suppressed because it is too large Load Diff