diff --git a/main.py b/main.py index 26bad1b8..d82d1d6e 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ if os.name == "nt": if __name__ == "__main__": if '--help' in sys.argv: print("Valid Command line Arguments:") - print("\t--listen\t\t\tListen on 0.0.0.0 so the UI can be accessed from other computers.") + print("\t--listen [ip]\t\t\tListen on ip or 0.0.0.0 if none given so the UI can be accessed from other computers.") print("\t--port 8188\t\t\tSet the listen port.") print("\t--dont-upcast-attention\t\tDisable upcasting of attention \n\t\t\t\t\tcan boost speed but increase the chances of black images.\n") print("\t--use-split-cross-attention\tUse the split cross attention optimization instead of the sub-quadratic one.\n\t\t\t\t\tIgnored when xformers is used.") @@ -92,11 +92,19 @@ if __name__ == "__main__": hijack_progress(server) threading.Thread(target=prompt_worker, daemon=True, args=(q,server,)).start() - if '--listen' in sys.argv: + try: address = '0.0.0.0' - else: + p_index = sys.argv.index('--listen') + try: + ip = sys.argv[p_index + 1] + if ip[:2] != '--': + address = ip + except: + pass + except: address = '127.0.0.1' + dont_print = False if '--dont-print-server' in sys.argv: dont_print = True diff --git a/web/extensions/core/colorPalette.js b/web/extensions/core/colorPalette.js new file mode 100644 index 00000000..e54bc2a3 --- /dev/null +++ b/web/extensions/core/colorPalette.js @@ -0,0 +1,351 @@ +import { app } from "/scripts/app.js"; +import { $el } from "/scripts/ui.js"; +import { api } from "/scripts/api.js"; + +// Manage color palettes + +const colorPalettes = { + "palette_1": { + "id": "palette_1", + "name": "Palette 1", + "colors": { + "node_slot": { + "CLIP": "#FFD500", // bright yellow + "CLIP_VISION": "#A8DADC", // light blue-gray + "CLIP_VISION_OUTPUT": "#ad7452", // rusty brown-orange + "CONDITIONING": "#FFA931", // vibrant orange-yellow + "CONTROL_NET": "#6EE7B7", // soft mint green + "IMAGE": "#64B5F6", // bright sky blue + "LATENT": "#FF9CF9", // light pink-purple + "MASK": "#81C784", // muted green + "MODEL": "#B39DDB", // light lavender-purple + "STYLE_MODEL": "#C2FFAE", // light green-yellow + "VAE": "#FF6E6E", // bright red + } + } + }, + "palette_2": { + "id": "palette_2", + "name": "Palette 2", + "colors": { + "node_slot": { + "CLIP": "#556B2F", // Dark Olive Green + "CLIP_VISION": "#4B0082", // Indigo + "CLIP_VISION_OUTPUT": "#006400", // Green + "CONDITIONING": "#FF1493", // Deep Pink + "CONTROL_NET": "#8B4513", // Saddle Brown + "IMAGE": "#8B0000", // Dark Red + "LATENT": "#00008B", // Dark Blue + "MASK": "#2F4F4F", // Dark Slate Grey + "MODEL": "#FF8C00", // Dark Orange + "STYLE_MODEL": "#004A4A", // Sherpa Blue + "UPSCALE_MODEL": "#4A004A", // Tyrian Purple + "VAE": "#4F394F", // Loulou + } + } + } +}; + +const id = "Comfy.ColorPalette"; +const idCustomColorPalettes = "Comfy.CustomColorPalettes"; +const defaultColorPaletteId = "palette_1"; +const els = {} +// const ctxMenu = LiteGraph.ContextMenu; +app.registerExtension({ + name: id, + init() { + const sortObjectKeys = (unordered) => { + return Object.keys(unordered).sort().reduce((obj, key) => { + obj[key] = unordered[key]; + return obj; + }, {}); + }; + + const getSlotTypes = async () => { + var types = []; + + const defs = await api.getNodeDefs(); + for (const nodeId in defs) { + const nodeData = defs[nodeId]; + + var inputs = nodeData["input"]["required"]; + if (nodeData["input"]["optional"] != undefined){ + inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"]) + } + + for (const inputName in inputs) { + const inputData = inputs[inputName]; + const type = inputData[0]; + + if (!Array.isArray(type)) { + types.push(type); + } + } + + for (const o in nodeData["output"]) { + const output = nodeData["output"][o]; + types.push(output); + } + } + + return types; + }; + + const completeColorPalette = async (colorPalette) => { + var types = await getSlotTypes(); + + for (const type of types) { + if (!colorPalette.colors.node_slot[type]) { + colorPalette.colors.node_slot[type] = ""; + } + } + + colorPalette.colors.node_slot = sortObjectKeys(colorPalette.colors.node_slot); + + return colorPalette; + }; + + const getColorPaletteTemplate = async () => { + let colorPalette = { + "id": "my_color_palette_unique_id", + "name": "My Color Palette", + "colors": { + "node_slot": { + } + } + }; + + return completeColorPalette(colorPalette); + }; + + const getCustomColorPalettes = () => { + return app.ui.settings.getSettingValue(idCustomColorPalettes, {}); + }; + + const setCustomColorPalettes = (customColorPalettes) => { + return app.ui.settings.setSettingValue(idCustomColorPalettes, customColorPalettes); + }; + + const addCustomColorPalette = async (colorPalette) => { + if (typeof(colorPalette) !== "object") { + app.ui.dialog.show("Invalid color palette"); + return; + } + + if (!colorPalette.id) { + app.ui.dialog.show("Color palette missing id"); + return; + } + + if (!colorPalette.name) { + app.ui.dialog.show("Color palette missing name"); + return; + } + + if (!colorPalette.colors) { + app.ui.dialog.show("Color palette missing colors"); + return; + } + + if (colorPalette.colors.node_slot && typeof(colorPalette.colors.node_slot) !== "object") { + app.ui.dialog.show("Invalid color palette colors.node_slot"); + return; + } + + let customColorPalettes = getCustomColorPalettes(); + customColorPalettes[colorPalette.id] = colorPalette; + setCustomColorPalettes(customColorPalettes); + + for (const option of els.select.childNodes) { + if (option.value === "custom_" + colorPalette.id) { + els.select.removeChild(option); + } + } + + els.select.append($el("option", { textContent: colorPalette.name + " (custom)", value: "custom_" + colorPalette.id, selected: true })); + + setColorPalette("custom_" + colorPalette.id); + await loadColorPalette(colorPalette); + }; + + const deleteCustomColorPalette = async (colorPaletteId) => { + let customColorPalettes = getCustomColorPalettes(); + delete customColorPalettes[colorPaletteId]; + setCustomColorPalettes(customColorPalettes); + + for (const option of els.select.childNodes) { + if (option.value === defaultColorPaletteId) { + option.selected = true; + } + + if (option.value === "custom_" + colorPaletteId) { + els.select.removeChild(option); + } + } + + setColorPalette(defaultColorPaletteId); + await loadColorPalette(getColorPalette()); + }; + + const loadColorPalette = async (colorPalette) => { + colorPalette = await completeColorPalette(colorPalette); + if (colorPalette.colors) { + if (colorPalette.colors.node_slot) { + Object.assign(app.canvas.default_connection_color_byType, colorPalette.colors.node_slot); + app.canvas.draw(true, true); + } + } + }; + + const getColorPalette = (colorPaletteId) => { + if (!colorPaletteId) { + colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId); + } + + if (colorPaletteId.startsWith("custom_")) { + colorPaletteId = colorPaletteId.substr(7); + let customColorPalettes = getCustomColorPalettes(); + if (customColorPalettes[colorPaletteId]) { + return customColorPalettes[colorPaletteId]; + } + } + + return colorPalettes[colorPaletteId]; + }; + + const setColorPalette = (colorPaletteId) => { + app.ui.settings.setSettingValue(id, colorPaletteId); + }; + + const fileInput = $el("input", { + type: "file", + accept: ".json", + style: { display: "none" }, + parent: document.body, + onchange: () => { + let file = fileInput.files[0]; + + if (file.type === "application/json" || file.name.endsWith(".json")) { + const reader = new FileReader(); + reader.onload = async () => { + await addCustomColorPalette(JSON.parse(reader.result)); + }; + reader.readAsText(file); + } + }, + }); + + app.ui.settings.addSetting({ + id, + name: "Color Palette", + type: (name, setter, value) => { + let options = []; + + for (const c in colorPalettes) { + const colorPalette = colorPalettes[c]; + options.push($el("option", { textContent: colorPalette.name, value: colorPalette.id, selected: colorPalette.id === value })); + } + + let customColorPalettes = getCustomColorPalettes(); + for (const c in customColorPalettes) { + const colorPalette = customColorPalettes[c]; + options.push($el("option", { textContent: colorPalette.name + " (custom)", value: "custom_" + colorPalette.id, selected: "custom_" + colorPalette.id === value })); + } + + return $el("div", [ + $el("label", { textContent: name || id }, [ + els.select = $el("select", { + onchange: (e) => { + setter(e.target.value); + } + }, options) + ]), + $el("input", { + type: "button", + value: "Export", + onclick: async () => { + const colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId); + const colorPalette = await completeColorPalette(getColorPalette(colorPaletteId)); + const json = JSON.stringify(colorPalette, null, 2); // convert the data to a JSON string + const blob = new Blob([json], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = $el("a", { + href: url, + download: colorPaletteId + ".json", + style: { display: "none" }, + parent: document.body, + }); + a.click(); + setTimeout(function () { + a.remove(); + window.URL.revokeObjectURL(url); + }, 0); + }, + }), + $el("input", { + type: "button", + value: "Import", + onclick: () => { + fileInput.click(); + } + }), + $el("input", { + type: "button", + value: "Template", + onclick: async () => { + const colorPalette = await getColorPaletteTemplate(); + const json = JSON.stringify(colorPalette, null, 2); // convert the data to a JSON string + const blob = new Blob([json], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = $el("a", { + href: url, + download: "color_palette.json", + style: { display: "none" }, + parent: document.body, + }); + a.click(); + setTimeout(function () { + a.remove(); + window.URL.revokeObjectURL(url); + }, 0); + } + }), + $el("input", { + type: "button", + value: "Delete", + onclick: async () => { + let colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId); + + if (colorPalettes[colorPaletteId]) { + app.ui.dialog.show("You cannot delete built-in color palette"); + return; + } + + if (colorPaletteId.startsWith("custom_")) { + colorPaletteId = colorPaletteId.substr(7); + } + + await deleteCustomColorPalette(colorPaletteId); + } + }), + ]); + }, + defaultValue: defaultColorPaletteId, + async onChange(value) { + if (!value) { + return; + } + + if (colorPalettes[value]) { + await loadColorPalette(colorPalettes[value]); + } else if (value.startsWith("custom_")) { + value = value.substr(7); + let customColorPalettes = getCustomColorPalettes(); + if (customColorPalettes[value]) { + await loadColorPalette(customColorPalettes[value]); + } + } + }, + }); + }, +}); diff --git a/web/scripts/app.js b/web/scripts/app.js index 43d7f7b5..609cc4cf 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -576,27 +576,6 @@ class ComfyApp { } } - /** - * Setup slot colors for types - */ - setupSlotColors() { - let colors = { - "CLIP": "#FFD500", // bright yellow - "CLIP_VISION": "#A8DADC", // light blue-gray - "CLIP_VISION_OUTPUT": "#ad7452", // rusty brown-orange - "CONDITIONING": "#FFA931", // vibrant orange-yellow - "CONTROL_NET": "#6EE7B7", // soft mint green - "IMAGE": "#64B5F6", // bright sky blue - "LATENT": "#FF9CF9", // light pink-purple - "MASK": "#81C784", // muted green - "MODEL": "#B39DDB", // light lavender-purple - "STYLE_MODEL": "#C2FFAE", // light green-yellow - "VAE": "#FF6E6E", // bright red - }; - - Object.assign(this.canvas.default_connection_color_byType, colors); - } - /** * Set up the app on the page */ @@ -614,8 +593,6 @@ class ComfyApp { const canvas = (this.canvas = new LGraphCanvas(canvasEl, this.graph)); this.ctx = canvasEl.getContext("2d"); - this.setupSlotColors(); - this.graph.start(); function resizeCanvas() { diff --git a/web/scripts/ui.js b/web/scripts/ui.js index 42476be8..fb206fd7 100644 --- a/web/scripts/ui.js +++ b/web/scripts/ui.js @@ -1,6 +1,6 @@ import { api } from "./api.js"; -function $el(tag, propsOrChildren, children) { +export function $el(tag, propsOrChildren, children) { const split = tag.split("."); const element = document.createElement(split.shift()); element.classList.add(...split); @@ -193,6 +193,17 @@ class ComfySettingsDialog extends ComfyDialog { this.settings = []; } + getSettingValue(id, defaultValue) { + const settingId = "Comfy.Settings." + id; + const v = localStorage[settingId]; + return v == null ? defaultValue : JSON.parse(v); + } + + setSettingValue(id, value) { + const settingId = "Comfy.Settings." + id; + localStorage[settingId] = JSON.stringify(value); + } + addSetting({ id, name, type, defaultValue, onChange }) { if (!id) { throw new Error("Settings must have an ID"); @@ -221,7 +232,7 @@ class ComfySettingsDialog extends ComfyDialog { }; if (typeof type === "function") { - return type(name, setter); + return type(name, setter, value); } switch (type) { diff --git a/web/style.css b/web/style.css index d3168044..7c3d7efa 100644 --- a/web/style.css +++ b/web/style.css @@ -64,6 +64,12 @@ body { margin-bottom: 20px; /* Add some margin between the text and the close button*/ } +.comfy-modal select, +.comfy-modal input[type=button], +.comfy-modal input[type=checkbox] { + margin: 3px 3px 3px 4px; +} + .comfy-modal button { cursor: pointer; color: #aaaaaa;