From d7638c47fc36ea7366d970f56c005216e4793e82 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Fri, 4 Aug 2023 03:22:47 -0400 Subject: [PATCH 01/18] Fix ui inconsistency. --- web/scripts/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/scripts/ui.js b/web/scripts/ui.js index 5d4e92542..03a4035b1 100644 --- a/web/scripts/ui.js +++ b/web/scripts/ui.js @@ -480,7 +480,7 @@ class ComfyList { hide() { this.element.style.display = "none"; - this.button.textContent = "See " + this.#text; + this.button.textContent = "View " + this.#text; } toggle() { From 0bbd9dd4d9cc3749f4ec0903f33c5ffb9d7aecfd Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Fri, 4 Aug 2023 08:29:25 +0100 Subject: [PATCH 02/18] add system info to stats endpoint --- server.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server.py b/server.py index f61b11a97..fab33be3e 100644 --- a/server.py +++ b/server.py @@ -345,6 +345,11 @@ class PromptServer(): vram_total, torch_vram_total = comfy.model_management.get_total_memory(device, torch_total_too=True) vram_free, torch_vram_free = comfy.model_management.get_free_memory(device, torch_free_too=True) system_stats = { + "system": { + "os": os.name, + "python_version": sys.version, + "embedded_python": os.path.split(os.path.split(sys.executable)[0])[1] == "python_embeded" + }, "devices": [ { "name": device_name, From 43ae9fe7216198ff044ea33ac6f6d21a9ca9c2af Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Fri, 4 Aug 2023 08:29:51 +0100 Subject: [PATCH 03/18] add system stats function --- web/scripts/api.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/web/scripts/api.js b/web/scripts/api.js index d3d15e47e..b1d245d73 100644 --- a/web/scripts/api.js +++ b/web/scripts/api.js @@ -264,6 +264,15 @@ class ComfyApi extends EventTarget { } } + /** + * Gets system & device stats + * @returns System stats such as python version, OS, per device info + */ + async getSystemStats() { + const res = await this.fetchApi("/system_stats"); + return await res.json(); + } + /** * Sends a POST request to the API * @param {*} type The endpoint to post to From b2ea0cbd5c5dfbb734f375acd042bd49cabe84ec Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Fri, 4 Aug 2023 08:30:01 +0100 Subject: [PATCH 04/18] add logging --- web/scripts/app.js | 6 + web/scripts/logging.js | 367 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 web/scripts/logging.js diff --git a/web/scripts/app.js b/web/scripts/app.js index 8c9e7a27f..11903a2d4 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -1,3 +1,4 @@ +import { ComfyLogging } from "./logging.js"; import { ComfyWidgets } from "./widgets.js"; import { ComfyUI, $el } from "./ui.js"; import { api } from "./api.js"; @@ -31,6 +32,7 @@ export class ComfyApp { constructor() { this.ui = new ComfyUI(this); + this.logging = new ComfyLogging(this); /** * List of extensions that are registered with the app @@ -1023,6 +1025,7 @@ export class ComfyApp { */ async #loadExtensions() { const extensions = await api.getExtensions(); + this.logging.addEntry("Comfy.App", "debug", { Extensions: extensions }); for (const ext of extensions) { try { await import(api.apiURL(ext)); @@ -1306,6 +1309,9 @@ export class ComfyApp { (t) => `
  • ${t}
  • ` ).join("")}Nodes that have failed to load will show as red on the graph.` ); + this.logging.addEntry("Comfy.App", "warn", { + MissingNodes: nodes, + }); } } diff --git a/web/scripts/logging.js b/web/scripts/logging.js new file mode 100644 index 000000000..c73462e1e --- /dev/null +++ b/web/scripts/logging.js @@ -0,0 +1,367 @@ +import { $el, ComfyDialog } from "./ui.js"; +import { api } from "./api.js"; + +$el("style", { + textContent: ` + .comfy-logging-logs { + display: grid; + color: var(--fg-color); + white-space: pre-wrap; + } + .comfy-logging-log { + display: contents; + } + .comfy-logging-title { + background: var(--tr-even-bg-color); + font-weight: bold; + margin-bottom: 5px; + text-align: center; + } + .comfy-logging-log div { + background: var(--row-bg); + padding: 5px; + } + `, + parent: document.body, +}); + +// Stringify function supporting max depth and removal of circular references +// https://stackoverflow.com/a/57193345 +function stringify(val, depth, replacer, space, onGetObjID) { + depth = isNaN(+depth) ? 1 : depth; + var recursMap = new WeakMap(); + function _build(val, depth, o, a, r) { + // (JSON.stringify() has it's own rules, which we respect here by using it for property iteration) + return !val || typeof val != "object" + ? val + : ((r = recursMap.has(val)), + recursMap.set(val, true), + (a = Array.isArray(val)), + r + ? (o = (onGetObjID && onGetObjID(val)) || null) + : JSON.stringify(val, function (k, v) { + if (a || depth > 0) { + if (replacer) v = replacer(k, v); + if (!k) return (a = Array.isArray(v)), (val = v); + !o && (o = a ? [] : {}); + o[k] = _build(v, a ? depth : depth - 1); + } + }), + o === void 0 ? (a ? [] : {}) : o); + } + return JSON.stringify(_build(val, depth), null, space); +} + +const jsonReplacer = (k, v, ui) => { + if (v instanceof Array && v.length === 1) { + v = v[0]; + } + if (v instanceof Date) { + v = v.toISOString(); + if (ui) { + v = v.split("T")[1]; + } + } + if (v instanceof Error) { + let err = ""; + if (v.name) err += v.name + "\n"; + if (v.message) err += v.message + "\n"; + if (v.stack) err += v.stack + "\n"; + if (!err) { + err = v.toString(); + } + v = err; + } + return v; +}; + +const fileInput = $el("input", { + type: "file", + accept: ".json", + style: { display: "none" }, + parent: document.body, +}); + +class ComfyLoggingDialog extends ComfyDialog { + constructor(logging) { + super(); + this.logging = logging; + } + + clear() { + this.logging.clear(); + this.show(); + } + + export() { + const blob = new Blob([stringify([...this.logging.entries], 20, jsonReplacer, "\t")], { + type: "application/json", + }); + const url = URL.createObjectURL(blob); + const a = $el("a", { + href: url, + download: `comfyui-logs-${Date.now()}.json`, + style: { display: "none" }, + parent: document.body, + }); + a.click(); + setTimeout(function () { + a.remove(); + window.URL.revokeObjectURL(url); + }, 0); + } + + import() { + fileInput.onchange = () => { + const reader = new FileReader(); + reader.onload = () => { + fileInput.remove(); + try { + const obj = JSON.parse(reader.result); + if (obj instanceof Array) { + this.show(obj); + } else { + throw new Error("Invalid file selected."); + } + } catch (error) { + alert("Unable to load logs: " + error.message); + } + }; + reader.readAsText(fileInput.files[0]); + }; + fileInput.click(); + } + + createButtons() { + return [ + $el("button", { + type: "button", + textContent: "Clear", + onclick: () => this.clear(), + }), + $el("button", { + type: "button", + textContent: "Export logs...", + onclick: () => this.export(), + }), + $el("button", { + type: "button", + textContent: "View exported logs...", + onclick: () => this.import(), + }), + ...super.createButtons(), + ]; + } + + getTypeColor(type) { + switch (type) { + case "error": + return "red"; + case "warn": + return "orange"; + case "debug": + return "dodgerblue"; + } + } + + show(entries) { + if (!entries) entries = this.logging.entries; + this.element.style.width = "100%"; + const cols = { + source: "Source", + type: "Type", + timestamp: "Timestamp", + message: "Message", + }; + const keys = Object.keys(cols); + const headers = Object.values(cols).map((title) => + $el("div.comfy-logging-title", { + textContent: title, + }) + ); + const rows = entries.map((entry, i) => { + return $el( + "div.comfy-logging-log", + { + $: (el) => el.style.setProperty("--row-bg", `var(--tr-${i % 2 ? "even" : "odd"}-bg-color)`), + }, + keys.map((key) => { + let v = entry[key]; + let color; + if (key === "type") { + color = this.getTypeColor(v); + } else { + v = jsonReplacer(key, v, true); + + if (typeof v === "object") { + v = stringify(v, 5, jsonReplacer, " "); + } + } + + return $el("div", { + style: { + color, + }, + textContent: v, + }); + }) + ); + }); + + const grid = $el( + "div.comfy-logging-logs", + { + style: { + gridTemplateColumns: `repeat(${headers.length}, 1fr)`, + }, + }, + [...headers, ...rows] + ); + const els = [grid]; + if (!this.logging.enabled) { + els.unshift( + $el("h3", { + style: { textAlign: "center" }, + textContent: "Logging is disabled", + }) + ); + } + super.show($el("div", els)); + } +} + +export class ComfyLogging { + /** + * @type Array<{ source: string, type: string, timestamp: Date, message: any }> + */ + entries = []; + + #enabled; + #console = {}; + + get enabled() { + return this.#enabled; + } + + set enabled(value) { + if (value === this.#enabled) return; + if (value) { + this.patchConsole(); + } else { + this.unpatchConsole(); + } + this.#enabled = value; + } + + constructor(app) { + this.app = app; + + this.dialog = new ComfyLoggingDialog(this); + this.addSetting(); + this.catchUnhandled(); + this.addInitData(); + } + + addSetting() { + const settingId = "Comfy.Logging.Enabled"; + const htmlSettingId = settingId.replaceAll(".", "-"); + const setting = this.app.ui.settings.addSetting({ + id: settingId, + name: settingId, + defaultValue: true, + type: (name, setter, value) => { + return $el("tr", [ + $el("td", [ + $el("label", { + textContent: "Logging", + for: htmlSettingId, + }), + ]), + $el("td", [ + $el("input", { + id: htmlSettingId, + type: "checkbox", + checked: value, + onchange: (event) => { + setter((this.enabled = event.target.checked)); + }, + }), + $el("button", { + textContent: "View Logs", + onclick: () => { + this.app.ui.settings.element.close(); + this.dialog.show(); + }, + style: { + fontSize: "14px", + display: "block", + marginTop: "5px", + }, + }), + ]), + ]); + }, + }); + this.enabled = setting.value; + } + + patchConsole() { + // Capture common console outputs + const self = this; + for (const type of ["log", "warn", "error", "debug"]) { + const orig = console[type]; + this.#console[type] = orig; + console[type] = function () { + orig.apply(console, arguments); + self.addEntry("console", type, ...arguments); + }; + } + } + + unpatchConsole() { + // Restore original console functions + for (const type of Object.keys(this.#console)) { + console[type] = this.#console[type]; + } + this.#console = {}; + } + + catchUnhandled() { + // Capture uncaught errors + window.addEventListener("error", (e) => { + this.addEntry("window", "error", e.error ?? "Unknown error"); + return false; + }); + + window.addEventListener("unhandledrejection", (e) => { + this.addEntry("unhandledrejection", "error", e.reason ?? "Unknown error"); + }); + } + + clear() { + this.entries = []; + } + + addEntry(source, type, ...args) { + if (this.enabled) { + this.entries.push({ + source, + type, + timestamp: new Date(), + message: args, + }); + } + } + + log(source, ...args) { + this.addEntry(source, "log", ...args); + } + + async addInitData() { + if (!this.enabled) return; + const source = "ComfyUI.Logging"; + this.addEntry(source, "debug", { UserAgent: navigator.userAgent }); + const systemStats = await api.getSystemStats(); + this.addEntry(source, "debug", systemStats); + } +} From 3d614dde499d7c7fcb29696ce4999967b51757c2 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Fri, 4 Aug 2023 03:47:45 -0400 Subject: [PATCH 05/18] Fix bug with reroutes and bypass. --- web/scripts/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/scripts/app.js b/web/scripts/app.js index 8c9e7a27f..c4d593f2c 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -1356,7 +1356,7 @@ export class ComfyApp { if (parent.isVirtualNode) { link = parent.getInputLink(link.origin_slot); if (link) { - parent = parent.getInputNode(link.origin_slot); + parent = parent.getInputNode(link.target_slot); if (parent) { found = true; } From 1ce0d8ad68e15c58a0e9793eb873f0238f741f4c Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Fri, 4 Aug 2023 12:08:45 -0400 Subject: [PATCH 06/18] Add CMP 30HX card to the nvidia_16_series list. --- comfy/model_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comfy/model_management.py b/comfy/model_management.py index 0ffca06da..4dd15b41c 100644 --- a/comfy/model_management.py +++ b/comfy/model_management.py @@ -535,7 +535,7 @@ def should_use_fp16(device=None, model_params=0): return False #FP16 is just broken on these cards - nvidia_16_series = ["1660", "1650", "1630", "T500", "T550", "T600", "MX550", "MX450"] + nvidia_16_series = ["1660", "1650", "1630", "T500", "T550", "T600", "MX550", "MX450", "CMP 30HX"] for x in nvidia_16_series: if x in props.name: return False From 8918f1085ca18b7a4d90a4120eec2e8df9062979 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:26:11 +0100 Subject: [PATCH 07/18] Add setting to change link render mode Add support for combo settings --- web/extensions/core/linkRenderMode.js | 25 ++++++++++++++++++++++++ web/scripts/ui.js | 28 ++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 web/extensions/core/linkRenderMode.js diff --git a/web/extensions/core/linkRenderMode.js b/web/extensions/core/linkRenderMode.js new file mode 100644 index 000000000..8b8d4e01f --- /dev/null +++ b/web/extensions/core/linkRenderMode.js @@ -0,0 +1,25 @@ +import { app } from "/scripts/app.js"; + +const id = "Comfy.LinkRenderMode"; +const ext = { + name: id, + async setup(app) { + app.ui.settings.addSetting({ + id, + name: "Link Render Mode", + defaultValue: 2, + type: "combo", + options: LiteGraph.LINK_RENDER_MODES.map((m, i) => ({ + value: i, + text: m, + selected: i == app.canvas.links_render_mode, + })), + onChange(value) { + app.canvas.links_render_mode = +value; + app.graph.setDirtyCanvas(true); + }, + }); + }, +}; + +app.registerExtension(ext); diff --git a/web/scripts/ui.js b/web/scripts/ui.js index 03a4035b1..86e2a1c41 100644 --- a/web/scripts/ui.js +++ b/web/scripts/ui.js @@ -234,7 +234,7 @@ class ComfySettingsDialog extends ComfyDialog { localStorage[settingId] = JSON.stringify(value); } - addSetting({id, name, type, defaultValue, onChange, attrs = {}, tooltip = "",}) { + addSetting({id, name, type, defaultValue, onChange, attrs = {}, tooltip = "", options = undefined}) { if (!id) { throw new Error("Settings must have an ID"); } @@ -347,6 +347,32 @@ class ComfySettingsDialog extends ComfyDialog { ]), ]); break; + case "combo": + element = $el("tr", [ + labelCell, + $el("td", [ + $el( + "select", + { + oninput: (e) => { + setter(e.target.value); + }, + }, + (typeof options === "function" ? options(value) : options || []).map((opt) => { + if (typeof opt === "string") { + opt = { text: opt }; + } + const v = opt.value ?? opt.text; + return $el("option", { + value: v, + textContent: opt.text, + selected: value + "" === v + "", + }); + }) + ), + ]), + ]); + break; case "text": default: if (type !== "text") { From 5a90d3cea57d1507227a6324ae9efb5e77410cea Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Fri, 4 Aug 2023 21:44:37 -0400 Subject: [PATCH 08/18] GeForce MX110 + MX130 are maxwell. --- cuda_malloc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cuda_malloc.py b/cuda_malloc.py index a808b2071..e586d3eff 100644 --- a/cuda_malloc.py +++ b/cuda_malloc.py @@ -40,7 +40,8 @@ def cuda_malloc_supported(): blacklist = {"GeForce GTX TITAN X", "GeForce GTX 980", "GeForce GTX 970", "GeForce GTX 960", "GeForce GTX 950", "GeForce 945M", "GeForce 940M", "GeForce 930M", "GeForce 920M", "GeForce 910M", "GeForce GTX 750", "GeForce GTX 745", "Quadro K620", "Quadro K1200", "Quadro K2200", "Quadro M500", "Quadro M520", "Quadro M600", "Quadro M620", "Quadro M1000", - "Quadro M1200", "Quadro M2000", "Quadro M2200", "Quadro M3000", "Quadro M4000", "Quadro M5000", "Quadro M5500", "Quadro M6000"} + "Quadro M1200", "Quadro M2000", "Quadro M2200", "Quadro M3000", "Quadro M4000", "Quadro M5000", "Quadro M5500", "Quadro M6000", + "GeForce MX110", "GeForce MX130"} try: names = get_gpu_names() From c5d7593ccfb4dd3a97175e01b9fa883086f5d8b4 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Sat, 5 Aug 2023 01:40:24 -0400 Subject: [PATCH 09/18] Support loras in diffusers format. --- comfy/sd.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/comfy/sd.py b/comfy/sd.py index 922cbf21e..7511bb501 100644 --- a/comfy/sd.py +++ b/comfy/sd.py @@ -70,13 +70,22 @@ def load_lora(lora, to_load): alpha = lora[alpha_name].item() loaded_keys.add(alpha_name) - A_name = "{}.lora_up.weight".format(x) - B_name = "{}.lora_down.weight".format(x) - mid_name = "{}.lora_mid.weight".format(x) + regular_lora = "{}.lora_up.weight".format(x) + diffusers_lora = "{}_lora.up.weight".format(x) + A_name = None - if A_name in lora.keys(): + if regular_lora in lora.keys(): + A_name = regular_lora + B_name = "{}.lora_down.weight".format(x) + mid_name = "{}.lora_mid.weight".format(x) + elif diffusers_lora in lora.keys(): + A_name = diffusers_lora + B_name = "{}_lora.down.weight".format(x) + mid_name = None + + if A_name is not None: mid = None - if mid_name in lora.keys(): + if mid_name is not None and mid_name in lora.keys(): mid = lora[mid_name] loaded_keys.add(mid_name) patch_dict[to_load[x]] = (lora[A_name], lora[B_name], alpha, mid) @@ -202,6 +211,11 @@ def model_lora_keys_unet(model, key_map={}): if k.endswith(".weight"): key_lora = k[:-len(".weight")].replace(".", "_") key_map["lora_unet_{}".format(key_lora)] = "diffusion_model.{}".format(diffusers_keys[k]) + + diffusers_lora_key = "unet.{}".format(k[:-len(".weight")].replace(".to_", ".processor.to_")) + if diffusers_lora_key.endswith(".to_out.0"): + diffusers_lora_key = diffusers_lora_key[:-2] + key_map[diffusers_lora_key] = "diffusion_model.{}".format(diffusers_keys[k]) return key_map def set_attr(obj, attr, value): From 32e115b81817e4f3512e0391da8b0b8a9754de10 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sat, 5 Aug 2023 11:00:18 +0100 Subject: [PATCH 10/18] prevent crashing if the widget cant be found --- web/extensions/core/contextMenuFilter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/extensions/core/contextMenuFilter.js b/web/extensions/core/contextMenuFilter.js index e0e8854b3..0b2256e8c 100644 --- a/web/extensions/core/contextMenuFilter.js +++ b/web/extensions/core/contextMenuFilter.js @@ -27,10 +27,10 @@ const ext = { const clickedComboValue = currentNode.widgets .filter(w => w.type === "combo" && w.options.values.length === values.length) .find(w => w.options.values.every((v, i) => v === values[i])) - .value; + ?.value; - let selectedIndex = values.findIndex(v => v === clickedComboValue); - let selectedItem = displayedItems?.[selectedIndex]; + let selectedIndex = clickedComboValue ? values.findIndex(v => v === clickedComboValue) : 0; + let selectedItem = displayedItems[selectedIndex]; updateSelected(); // Apply highlighting to the selected item From b948b2cf41cd8b4b1c925a6ef9e689615f86e8ad Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sat, 5 Aug 2023 11:04:04 +0100 Subject: [PATCH 11/18] handle value missing --- web/extensions/core/contextMenuFilter.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/extensions/core/contextMenuFilter.js b/web/extensions/core/contextMenuFilter.js index 0b2256e8c..152cd7043 100644 --- a/web/extensions/core/contextMenuFilter.js +++ b/web/extensions/core/contextMenuFilter.js @@ -30,6 +30,9 @@ const ext = { ?.value; let selectedIndex = clickedComboValue ? values.findIndex(v => v === clickedComboValue) : 0; + if (selectedIndex < 0) { + selectedIndex = 0; + } let selectedItem = displayedItems[selectedIndex]; updateSelected(); From 435577457a8576386910c62662eddb8a82efddb0 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Sat, 5 Aug 2023 17:18:45 -0400 Subject: [PATCH 12/18] Add a way to use cloudflared tunnel to the colab notebook. --- notebooks/comfyui_colab.ipynb | 53 ++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/notebooks/comfyui_colab.ipynb b/notebooks/comfyui_colab.ipynb index 1bb90f7d0..84f2cf403 100644 --- a/notebooks/comfyui_colab.ipynb +++ b/notebooks/comfyui_colab.ipynb @@ -159,13 +159,64 @@ "\n" ] }, + { + "cell_type": "markdown", + "metadata": { + "id": "kkkkkkkkkkkkkkk" + }, + "source": [ + "### Run ComfyUI with cloudflared (Recommended Way)\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "jjjjjjjjjjjjjj" + }, + "outputs": [], + "source": [ + "!wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb\n", + "!dpkg -i cloudflared-linux-amd64.deb\n" + "\n", + "import subprocess\n", + "import threading\n", + "import time\n", + "import socket\n", + "import urllib.request\n", + "\n", + "def iframe_thread(port):\n", + " while True:\n", + " time.sleep(0.5)\n", + " sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", + " result = sock.connect_ex(('127.0.0.1', port))\n", + " if result == 0:\n", + " break\n", + " sock.close()\n", + " print(\"\\nComfyUI finished loading, trying to launch cloudflared (if it gets stuck here cloudflared is having issues)\\n\")\n", + "\n", + " p = subprocess.Popen([\"cloudflared\", \"tunnel\", \"--url\", \"http://127.0.0.1:{}\".format(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " for line in p.stderr:\n", + " l = line.decode()\n", + " if \"trycloudflare.com \" in l:\n", + " print(\"This is the URL to access ComfyUI:\", l[l.find(\"http\"):], end='')\n", + " #print(l, end='')\n", + "\n", + "\n", + "threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n", + "\n", + "!python main.py --dont-print-server" + ] + }, { "cell_type": "markdown", "metadata": { "id": "kkkkkkkkkkkkkk" }, "source": [ - "### Run ComfyUI with localtunnel (Recommended Way)\n", + "### Run ComfyUI with localtunnel\n", "\n", "\n" ] From c9ef919e29cc2454419eb3454e334b7a4c7814a6 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Sat, 5 Aug 2023 17:20:35 -0400 Subject: [PATCH 13/18] Formatting issue. --- notebooks/comfyui_colab.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/comfyui_colab.ipynb b/notebooks/comfyui_colab.ipynb index 84f2cf403..b1c487101 100644 --- a/notebooks/comfyui_colab.ipynb +++ b/notebooks/comfyui_colab.ipynb @@ -179,7 +179,7 @@ "outputs": [], "source": [ "!wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb\n", - "!dpkg -i cloudflared-linux-amd64.deb\n" + "!dpkg -i cloudflared-linux-amd64.deb\n", "\n", "import subprocess\n", "import threading\n", From fc71cf656e1f26e6577c0a211b7460fc078b0c39 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Sat, 5 Aug 2023 21:53:25 -0400 Subject: [PATCH 14/18] Add some 800M gpus to cuda malloc blacklist. --- cuda_malloc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuda_malloc.py b/cuda_malloc.py index e586d3eff..d033529cc 100644 --- a/cuda_malloc.py +++ b/cuda_malloc.py @@ -41,7 +41,7 @@ def cuda_malloc_supported(): "GeForce 940M", "GeForce 930M", "GeForce 920M", "GeForce 910M", "GeForce GTX 750", "GeForce GTX 745", "Quadro K620", "Quadro K1200", "Quadro K2200", "Quadro M500", "Quadro M520", "Quadro M600", "Quadro M620", "Quadro M1000", "Quadro M1200", "Quadro M2000", "Quadro M2200", "Quadro M3000", "Quadro M4000", "Quadro M5000", "Quadro M5500", "Quadro M6000", - "GeForce MX110", "GeForce MX130"} + "GeForce MX110", "GeForce MX130", "GeForce 830M", "GeForce 840M", "GeForce GTX 850M", "GeForce GTX 860M"} try: names = get_gpu_names() From 0cb14a33f607da8e93c4ab02047170deb6a33dae Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Sat, 5 Aug 2023 21:53:57 -0400 Subject: [PATCH 15/18] Fix issue with logging missing nodes. --- web/scripts/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/scripts/app.js b/web/scripts/app.js index 61324406d..40156abc3 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -1310,7 +1310,7 @@ export class ComfyApp { ).join("")}Nodes that have failed to load will show as red on the graph.` ); this.logging.addEntry("Comfy.App", "warn", { - MissingNodes: nodes, + MissingNodes: missingNodeTypes, }); } } From d8e58f0a7ea914377c56f9b32e449ef4c65da572 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Sun, 6 Aug 2023 14:08:59 -0400 Subject: [PATCH 16/18] Detect hint_channels from controlnet. --- comfy/sd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comfy/sd.py b/comfy/sd.py index 7511bb501..2996a938b 100644 --- a/comfy/sd.py +++ b/comfy/sd.py @@ -878,7 +878,7 @@ def load_controlnet(ckpt_path, model=None): use_fp16 = model_management.should_use_fp16() controlnet_config = model_detection.model_config_from_unet(controlnet_data, prefix, use_fp16).unet_config controlnet_config.pop("out_channels") - controlnet_config["hint_channels"] = 3 + controlnet_config["hint_channels"] = controlnet_data["{}input_hint_block.0.weight".format(prefix)].shape[1] control_model = cldm.ControlNet(**controlnet_config) if pth: From 0ce8a540cefa0b61705134b1eb4f1d67c6f3f4ba Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Sun, 6 Aug 2023 14:36:43 -0400 Subject: [PATCH 17/18] Update litegraph to latest. --- web/lib/litegraph.core.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/web/lib/litegraph.core.js b/web/lib/litegraph.core.js index 2a33bd4a7..2682ff309 100644 --- a/web/lib/litegraph.core.js +++ b/web/lib/litegraph.core.js @@ -9835,7 +9835,11 @@ LGraphNode.prototype.executeAction = function(action) ctx.textAlign = "center"; ctx.fillStyle = text_color; ctx.fillText( - w.label || w.name + " " + Number(w.value).toFixed(3), + w.label || w.name + " " + Number(w.value).toFixed( + w.options.precision != null + ? w.options.precision + : 3 + ), widget_width * 0.5, y + H * 0.7 ); @@ -13835,7 +13839,7 @@ LGraphNode.prototype.executeAction = function(action) if (!disabled) { element.addEventListener("click", inner_onclick); } - if (options.autoopen) { + if (!disabled && options.autoopen) { LiteGraph.pointerListenerAdd(element,"enter",inner_over); } From 2542a6bf93ecb27054f639517621142fcc1d8a62 Mon Sep 17 00:00:00 2001 From: xbol0 Date: Tue, 8 Aug 2023 07:50:57 +0000 Subject: [PATCH 18/18] init i18n extension --- .gitignore | 1 + web/extensions/core/colorPalette.js | 10 +- web/extensions/core/i18n.js | 46 + web/extensions/core/widgetInputs.js | 1 + web/i18n/en_US.js | 125 ++ web/i18n/zh_CN.js | 125 ++ web/index.html | 2 + web/lib/i18next.js | 2265 +++++++++++++++++++++ web/lib/i18nextBrowserLanguageDetector.js | 422 ++++ web/lib/litegraph.core.js | 8 +- web/scripts/app.js | 132 +- web/scripts/ui.js | 80 +- 12 files changed, 3109 insertions(+), 108 deletions(-) create mode 100644 web/extensions/core/i18n.js create mode 100644 web/i18n/en_US.js create mode 100644 web/i18n/zh_CN.js create mode 100644 web/lib/i18next.js create mode 100644 web/lib/i18nextBrowserLanguageDetector.js diff --git a/.gitignore b/.gitignore index 0177e1d7d..6b28b976f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ venv/ web/extensions/* !web/extensions/logging.js.example !web/extensions/core/ +!web/extensions/i18n/ diff --git a/web/extensions/core/colorPalette.js b/web/extensions/core/colorPalette.js index 3695b08e2..d414d4298 100644 --- a/web/extensions/core/colorPalette.js +++ b/web/extensions/core/colorPalette.js @@ -435,7 +435,7 @@ app.registerExtension({ $el("td", [ $el("label", { for: id.replaceAll(".", "-"), - textContent: "Color palette", + textContent: i18next.t("settings.Comfy.ColorPalette"), }), ]), $el("td", [ @@ -449,7 +449,7 @@ app.registerExtension({ }, [ $el("input", { type: "button", - value: "Export", + value: i18next.t("settings.Comfy.ColorPalette.export"), onclick: async () => { const colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId); const colorPalette = await completeColorPalette(getColorPalette(colorPaletteId)); @@ -471,14 +471,14 @@ app.registerExtension({ }), $el("input", { type: "button", - value: "Import", + value: i18next.t("settings.Comfy.ColorPalette.import"), onclick: () => { fileInput.click(); } }), $el("input", { type: "button", - value: "Template", + value: i18next.t("settings.Comfy.ColorPalette.template"), onclick: async () => { const colorPalette = await getColorPaletteTemplate(); const json = JSON.stringify(colorPalette, null, 2); // convert the data to a JSON string @@ -499,7 +499,7 @@ app.registerExtension({ }), $el("input", { type: "button", - value: "Delete", + value: i18next.t("settings.Comfy.ColorPalette.delete"), onclick: async () => { let colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId); diff --git a/web/extensions/core/i18n.js b/web/extensions/core/i18n.js new file mode 100644 index 000000000..883fed988 --- /dev/null +++ b/web/extensions/core/i18n.js @@ -0,0 +1,46 @@ +import { app } from "../../scripts/app.js"; + +app.registerExtension({ + name: "i18next", + addCustomNodeDefs(defs) { + for (const k in defs) { + defs[k].display_name = i18next.t(`node.title.${k}`) + + if ("input" in defs[k] && defs[k].input.required) { + for (const i in defs[k].input.required) { + if (defs[k].input.required[i].length > 1) { + defs[k].input.required[i][1].label = i18next.t(`node.input.${k}.${i}`) + } else { + defs[k].input.required[i].push({ label: i18next.t(`node.input.${k}.${i}`) }) + } + } + } + } + }, + nodeCreated(node) { + if ("inputs" in node) { + for (const item of node.inputs) { + item.label = i18next.t(`node.input.${node.comfyClass}.${item.name}`) + } + } + + if ("widgets" in node) { + for (const item of node.widgets) { + item.label = i18next.t(`node.input.${node.comfyClass}.${item.name}`) + } + } + + if ("outputs" in node) { + for (const item of node.outputs) { + item.label = i18next.t(`node.output.${node.comfyClass}.${item.name}`) + } + } + }, + afterNodesRegistrations() { + const defs = LiteGraph.registered_node_types + + for (const k in defs) { + defs[k].category = i18next.t(`category.${defs[k].category}`) + } + } +}) \ No newline at end of file diff --git a/web/extensions/core/widgetInputs.js b/web/extensions/core/widgetInputs.js index d9eaf8a0c..e4ab088dd 100644 --- a/web/extensions/core/widgetInputs.js +++ b/web/extensions/core/widgetInputs.js @@ -57,6 +57,7 @@ function convertToInput(node, widget, config) { const sz = node.size; node.addInput(widget.name, linkType, { widget: { name: widget.name, config }, + label: config?.[1] ? i18next.t(config?.[1].label) : void 0, }); for (const widget of node.widgets) { diff --git a/web/i18n/en_US.js b/web/i18n/en_US.js new file mode 100644 index 000000000..4012f47a6 --- /dev/null +++ b/web/i18n/en_US.js @@ -0,0 +1,125 @@ +export default { + translation: { + "ui.queue_btn": "Queue Prompt", + "ui.queue_front_btn": "Queue Front", + "ui.view_queue_btn": "View Queue", + "ui.view_history_btn": "View History", + "ui.save_btn": "Save", + "ui.load_btn": "Load", + "ui.refresh_btn": "Refresh", + "ui.clipspace_btn": "Clipspace", + "ui.clear_btn": "Clear", + "ui.load_default_btn": "Load Default", + "ui.close_btn": "Close", + "ui.queue_size": "Queue size: ", + "ui.extra_options": "Extra options", + + "ui.settings.title": "Settings", + + "ui.canvas_menu_add_node": "Add Node", + "ui.canvas_menu_add_group": "Add Group", + + "ui.node_panel.header.properties": "Properties", + "ui.node_panel.header.title": "Title", + "ui.node_panel.header.mode": "Mode", + "ui.node_panel.header.color": "Color", + + "node.title.KSampler": "KSampler", + "node.title.KSamplerAdvanced": "KSampler (Advanced)", + // Loaders + "node.title.CheckpointLoader": "Load Checkpoint (With Config)", + "node.title.CheckpointLoaderSimple": "Load Checkpoint", + "node.title.VAELoader": "Load VAE", + "node.title.LoraLoader": "Load LoRA", + "node.title.CLIPLoader": "Load CLIP", + "node.title.ControlNetLoader": "Load ControlNet Model", + "node.title.DiffControlNetLoader": "Load ControlNet Model (diff)", + "node.title.StyleModelLoader": "Load Style Model", + "node.title.CLIPVisionLoader": "Load CLIP Vision", + "node.title.UpscaleModelLoader": "Load Upscale Model", + // Conditioning + "node.title.CLIPVisionEncode": "CLIP Vision Encode", + "node.title.StyleModelApply": "Apply Style Model", + "node.title.CLIPTextEncode": "CLIP Text Encode (Prompt)", + "node.title.CLIPSetLastLayer": "CLIP Set Last Layer", + "node.title.ConditioningCombine": "Conditioning (Combine)", + "node.title.ConditioningAverage ": "Conditioning (Average)", + "node.title.ConditioningConcat": "Conditioning (Concat)", + "node.title.ConditioningSetArea": "Conditioning (Set Area)", + "node.title.ConditioningSetMask": "Conditioning (Set Mask)", + "node.title.ControlNetApply": "Apply ControlNet", + "node.title.ControlNetApplyAdvanced": "Apply ControlNet (Advanced)", + // Latent + "node.title.VAEEncodeForInpaint": "VAE Encode (for Inpainting)", + "node.title.SetLatentNoiseMask": "Set Latent Noise Mask", + "node.title.VAEDecode": "VAE Decode", + "node.title.VAEEncode": "VAE Encode", + "node.title.LatentRotate": "Rotate Latent", + "node.title.LatentFlip": "Flip Latent", + "node.title.LatentCrop": "Crop Latent", + "node.title.EmptyLatentImage": "Empty Latent Image", + "node.title.LatentUpscale": "Upscale Latent", + "node.title.LatentUpscaleBy": "Upscale Latent By", + "node.title.LatentComposite": "Latent Composite", + "node.title.LatentBlend": "Latent Blend", + "LatentFromBatch": "Latent From Batch", + "node.title.RepeatLatentBatch": "Repeat Latent Batch", + // Image + "node.title.SaveImage": "Save Image", + "node.title.PreviewImage": "Preview Image", + "node.title.LoadImage": "Load Image", + "node.title.LoadImageMask": "Load Image (as Mask)", + "node.title.ImageScale": "Upscale Image", + "node.title.ImageScaleBy": "Upscale Image By", + "node.title.ImageUpscaleWithModel": "Upscale Image (using Model)", + "node.title.ImageInvert": "Invert Image", + "node.title.ImagePadForOutpaint": "Pad Image for Outpainting", + // _for_testing + "node.title.VAEDecodeTiled": "VAE Decode (Tiled)", + "node.title.VAEEncodeTiled": "VAE Encode (Tiled)", + + "node.input.SaveImage.filename_prefix": "filename_prefix", + "node.input.SaveImage.images": "images", + + "node.output.CheckpointLoaderSimple.MODEL": "MODEL", + + "category.conditioning": "conditioning", + "category.loaders": "loaders", + "category.latent": "latent", + "category.latent/inpaint": "latent/inpaint", + "category.latent/batch": "latent/batch", + "category.image": "image", + "category.mask": "mask", + "category.image/upscaling": "image/upscaling", + "category.sampling": "sampling", + "category._for_testing": "_for_testing", + "category.latent/transform": "latent/transform", + "category.advanced/loaders": "advanced/loaders", + "category.conditioning/style_model": "conditioning/style_model", + "category.conditioning/gligen": "conditioning/gligen", + "category.advanced/loaders/deprecated": "advanced/loaders/deprecated", + "category.advanced/conditioning": "advanced/conditioning", + "category.image/postprocessing": "image/postprocessing", + "category.advanced/model_merging": "advanced/model_merging", + "category.image/preprocessors": "image/preprocessors", + "category.utils": "utils", + + "settings.Comfy.ConfirmClear": "Require confirmation when clearing workflow", + "settings.Comfy.PromptFilename": "Prompt for filename when saving workflow", + "settings.Comfy.PreviewFormat": "When displaying a preview in the image widget, convert it to a lightweight image, e.g. webp, jpeg, webp;50, etc.", + "settings.Comfy.DisableSliders": "Disable sliders.", + "settings.Comfy.DevMode": "Enable Dev mode Options", + "settings.Comfy.ColorPalette": "Color Palette", + "settings.Comfy.EditAttention.Delta": "Ctrl+up/down precision", + "settings.Comfy.InvertMenuScrolling": "Invert Menu Scrolling", + "settings.Comfy.LinkRenderMode": "Link Render Mode", + "settings.Comfy.NodeSuggestions.number": "Number of nodes suggestions", + "settings.Comfy.SnapToGrid.GridSize": "Grid Size", + "settings.Comfy.Logging.Enabled": "Comfy.Logging.Enabled", + "settings.Comfy.MenuPosition": "Save menu position", + "settings.Comfy.ColorPalette.export": "Export", + "settings.Comfy.ColorPalette.import": "Import", + "settings.Comfy.ColorPalette.template": "Template", + "settings.Comfy.ColorPalette.delete": "Delete", + } +} \ No newline at end of file diff --git a/web/i18n/zh_CN.js b/web/i18n/zh_CN.js new file mode 100644 index 000000000..b018a668e --- /dev/null +++ b/web/i18n/zh_CN.js @@ -0,0 +1,125 @@ +export default { + translation: { + "ui.queue_btn": "冲冲冲", + "ui.queue_front_btn": "插队冲冲冲", + "ui.view_queue_btn": "查看队列", + "ui.view_history_btn": "查看历史", + "ui.save_btn": "保存", + "ui.load_btn": "加载", + "ui.refresh_btn": "刷新", + "ui.clipspace_btn": "Clipspace", + "ui.clear_btn": "清空", + "ui.load_default_btn": "加载默认配置", + "ui.close_btn": "关闭", + "ui.queue_size": "队列数量: ", + "ui.extra_options": "额外选项", + + "ui.settings.title": "设置", + + "ui.canvas_menu_add_node": "添加节点", + "ui.canvas_menu_add_group": "添加组", + + "ui.node_panel.header.properties": "属性", + "ui.node_panel.header.title": "标题", + "ui.node_panel.header.mode": "模式", + "ui.node_panel.header.color": "颜色", + + "node.title.KSampler": "采样器", + "node.title.KSamplerAdvanced": "采样器 (高级)", + // Loaders + "node.title.CheckpointLoader": "Load Checkpoint (With Config)", + "node.title.CheckpointLoaderSimple": "加载模型", + "node.title.VAELoader": "Load VAE", + "node.title.LoraLoader": "Load LoRA", + "node.title.CLIPLoader": "Load CLIP", + "node.title.ControlNetLoader": "Load ControlNet Model", + "node.title.DiffControlNetLoader": "Load ControlNet Model (diff)", + "node.title.StyleModelLoader": "Load Style Model", + "node.title.CLIPVisionLoader": "Load CLIP Vision", + "node.title.UpscaleModelLoader": "Load Upscale Model", + // Conditioning + "node.title.CLIPVisionEncode": "CLIP Vision Encode", + "node.title.StyleModelApply": "Apply Style Model", + "node.title.CLIPTextEncode": "CLIP Text Encode (Prompt)", + "node.title.CLIPSetLastLayer": "CLIP Set Last Layer", + "node.title.ConditioningCombine": "Conditioning (Combine)", + "node.title.ConditioningAverage ": "Conditioning (Average)", + "node.title.ConditioningConcat": "Conditioning (Concat)", + "node.title.ConditioningSetArea": "Conditioning (Set Area)", + "node.title.ConditioningSetMask": "Conditioning (Set Mask)", + "node.title.ControlNetApply": "Apply ControlNet", + "node.title.ControlNetApplyAdvanced": "Apply ControlNet (Advanced)", + // Latent + "node.title.VAEEncodeForInpaint": "VAE Encode (for Inpainting)", + "node.title.SetLatentNoiseMask": "Set Latent Noise Mask", + "node.title.VAEDecode": "VAE Decode", + "node.title.VAEEncode": "VAE Encode", + "node.title.LatentRotate": "Rotate Latent", + "node.title.LatentFlip": "Flip Latent", + "node.title.LatentCrop": "Crop Latent", + "node.title.EmptyLatentImage": "Empty Latent Image", + "node.title.LatentUpscale": "Upscale Latent", + "node.title.LatentUpscaleBy": "Upscale Latent By", + "node.title.LatentComposite": "Latent Composite", + "node.title.LatentBlend": "Latent Blend", + "LatentFromBatch": "Latent From Batch", + "node.title.RepeatLatentBatch": "Repeat Latent Batch", + // Image + "node.title.SaveImage": "Save Image", + "node.title.PreviewImage": "Preview Image", + "node.title.LoadImage": "Load Image", + "node.title.LoadImageMask": "Load Image (as Mask)", + "node.title.ImageScale": "Upscale Image", + "node.title.ImageScaleBy": "Upscale Image By", + "node.title.ImageUpscaleWithModel": "Upscale Image (using Model)", + "node.title.ImageInvert": "Invert Image", + "node.title.ImagePadForOutpaint": "Pad Image for Outpainting", + // _for_testing + "node.title.VAEDecodeTiled": "VAE Decode (Tiled)", + "node.title.VAEEncodeTiled": "VAE Encode (Tiled)", + + "node.input.SaveImage.filename_prefix": "文件名前缀", + "node.input.SaveImage.images": "图片", + + "node.output.CheckpointLoaderSimple.MODEL": "模型", + + "category.conditioning": "可调参数", + "category.loaders": "加载器", + "category.latent": "潜在", + "category.latent/inpaint": "潜在/修复", + "category.latent/batch": "潜在/批量", + "category.image": "图像", + "category.mask": "遮罩", + "category.image/upscaling": "图像/外扩", + "category.sampling": "采样", + "category._for_testing": "测试", + "category.latent/transform": "潜在/转换", + "category.advanced/loaders": "高级/加载器", + "category.conditioning/style_model": "可调参数/风格模型", + "category.conditioning/gligen": "可调参数/gligen", + "category.advanced/loaders/deprecated": "高级/加载器/已弃用", + "category.advanced/conditioning": "高级/可调参数", + "category.image/postprocessing": "图像/后期处理", + "category.advanced/model_merging": "高级/模型合并", + "category.image/preprocessors": "图像/前期处理", + "category.utils": "工具", + + "settings.Comfy.ConfirmClear": "清空工作流需要确认", + "settings.Comfy.PromptFilename": "Prompt for filename when saving workflow", + "settings.Comfy.PreviewFormat": "预览图格式和压缩尺寸, e.g. webp, jpeg, webp;50, etc.", + "settings.Comfy.DisableSliders": "Disable sliders.", + "settings.Comfy.DevMode": "启用开发模式", + "settings.Comfy.ColorPalette": "主题", + "settings.Comfy.EditAttention.Delta": "Ctrl+up/down precision", + "settings.Comfy.InvertMenuScrolling": "反转滚动", + "settings.Comfy.LinkRenderMode": "链接渲染模式", + "settings.Comfy.NodeSuggestions.number": "Number of nodes suggestions", + "settings.Comfy.SnapToGrid.GridSize": "单元格尺寸", + "settings.Comfy.Logging.Enabled": "记录日志", + "settings.Comfy.MenuPosition": "保存菜单位置", + "settings.Comfy.ColorPalette.export": "导出", + "settings.Comfy.ColorPalette.import": "导入", + "settings.Comfy.ColorPalette.template": "模版", + "settings.Comfy.ColorPalette.delete": "删除", + } +} \ No newline at end of file diff --git a/web/index.html b/web/index.html index 71067d993..c3620a134 100644 --- a/web/index.html +++ b/web/index.html @@ -6,6 +6,8 @@ + +