Merge remote-tracking branch 'upstream/master' into addBatchIndex

This commit is contained in:
flyingshutter 2023-04-09 17:00:39 +02:00
commit f59dcc33d7
13 changed files with 444 additions and 60 deletions

View File

@ -45,6 +45,8 @@ try:
except:
OOM_EXCEPTION = Exception
XFORMERS_VERSION = ""
XFORMERS_ENABLED_VAE = True
if args.disable_xformers:
XFORMERS_IS_AVAILABLE = False
else:
@ -52,6 +54,17 @@ else:
import xformers
import xformers.ops
XFORMERS_IS_AVAILABLE = True
try:
XFORMERS_VERSION = xformers.version.__version__
print("xformers version:", XFORMERS_VERSION)
if XFORMERS_VERSION.startswith("0.0.18"):
print()
print("WARNING: This version of xformers has a major bug where you will get black images when generating high resolution images.")
print("Please downgrade or upgrade xformers to a different version.")
print()
XFORMERS_ENABLED_VAE = False
except:
pass
except:
XFORMERS_IS_AVAILABLE = False
@ -223,13 +236,8 @@ def xformers_enabled_vae():
enabled = xformers_enabled()
if not enabled:
return False
try:
#0.0.18 has a bug where Nan is returned when inputs are too big (1152x1920 res images and above)
if xformers.version.__version__ == "0.0.18":
return False
except:
pass
return enabled
return XFORMERS_ENABLED_VAE
def pytorch_attention_enabled():
return ENABLE_PYTORCH_ATTENTION

View File

@ -88,3 +88,8 @@ class Example:
NODE_CLASS_MAPPINGS = {
"Example": Example
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"Example": "Example Node"
}

View File

@ -1104,6 +1104,54 @@ NODE_CLASS_MAPPINGS = {
"DiffusersLoader": DiffusersLoader,
}
NODE_DISPLAY_NAME_MAPPINGS = {
# Sampling
"KSampler": "KSampler",
"KSamplerAdvanced": "KSampler (Advanced)",
# Loaders
"CheckpointLoader": "Load Checkpoint (With Config)",
"CheckpointLoaderSimple": "Load Checkpoint",
"VAELoader": "Load VAE",
"LoraLoader": "Load LoRA",
"CLIPLoader": "Load CLIP",
"ControlNetLoader": "Load ControlNet Model",
"DiffControlNetLoader": "Load ControlNet Model (diff)",
"StyleModelLoader": "Load Style Model",
"CLIPVisionLoader": "Load CLIP Vision",
"UpscaleModelLoader": "Load Upscale Model",
# Conditioning
"CLIPVisionEncode": "CLIP Vision Encode",
"StyleModelApply": "Apply Style Model",
"CLIPTextEncode": "CLIP Text Encode (Prompt)",
"CLIPSetLastLayer": "CLIP Set Last Layer",
"ConditioningCombine": "Conditioning (Combine)",
"ConditioningSetArea": "Conditioning (Set Area)",
"ControlNetApply": "Apply ControlNet",
# Latent
"VAEEncodeForInpaint": "VAE Encode (for Inpainting)",
"SetLatentNoiseMask": "Set Latent Noise Mask",
"VAEDecode": "VAE Decode",
"VAEEncode": "VAE Encode",
"LatentRotate": "Rotate Latent",
"LatentFlip": "Flip Latent",
"LatentCrop": "Crop Latent",
"EmptyLatentImage": "Empty Latent Image",
"LatentUpscale": "Upscale Latent",
"LatentComposite": "Latent Composite",
# Image
"SaveImage": "Save Image",
"PreviewImage": "Preview Image",
"LoadImage": "Load Image",
"LoadImageMask": "Load Image (as Mask)",
"ImageScale": "Upscale Image",
"ImageUpscaleWithModel": "Upscale Image (using Model)",
"ImageInvert": "Invert Image",
"ImagePadForOutpaint": "Pad Image for Outpainting",
# _for_testing
"VAEDecodeTiled": "VAE Decode (Tiled)",
"VAEEncodeTiled": "VAE Encode (Tiled)",
}
def load_custom_node(module_path):
module_name = os.path.basename(module_path)
if os.path.isfile(module_path):
@ -1119,6 +1167,8 @@ def load_custom_node(module_path):
module_spec.loader.exec_module(module)
if hasattr(module, "NODE_CLASS_MAPPINGS") and getattr(module, "NODE_CLASS_MAPPINGS") is not None:
NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS)
if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS") and getattr(module, "NODE_DISPLAY_NAME_MAPPINGS") is not None:
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
else:
print(f"Skip {module_path} module for custom nodes due to the lack of NODE_CLASS_MAPPINGS.")
except Exception as e:

View File

@ -47,7 +47,7 @@
" !git pull\n",
"\n",
"!echo -= Install dependencies =-\n",
"!pip install xformers -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cu118"
"!pip install xformers!=0.0.18 -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cu118"
]
},
{

View File

@ -177,7 +177,8 @@ class PromptServer():
info['input'] = obj_class.INPUT_TYPES()
info['output'] = obj_class.RETURN_TYPES
info['output_name'] = obj_class.RETURN_NAMES if hasattr(obj_class, 'RETURN_NAMES') else info['output']
info['name'] = x #TODO
info['name'] = x
info['display_name'] = nodes.NODE_DISPLAY_NAME_MAPPINGS[x] if x in nodes.NODE_DISPLAY_NAME_MAPPINGS.keys() else x
info['description'] = ''
info['category'] = 'sd'
if hasattr(obj_class, 'CATEGORY'):

View File

@ -21,28 +21,74 @@ const colorPalettes = {
"MODEL": "#B39DDB", // light lavender-purple
"STYLE_MODEL": "#C2FFAE", // light green-yellow
"VAE": "#FF6E6E", // bright red
}
}
},
"litegraph_base": {
"NODE_TITLE_COLOR": "#999",
"NODE_SELECTED_TITLE_COLOR": "#FFF",
"NODE_TEXT_SIZE": 14,
"NODE_TEXT_COLOR": "#AAA",
"NODE_SUBTEXT_SIZE": 12,
"NODE_DEFAULT_COLOR": "#333",
"NODE_DEFAULT_BGCOLOR": "#353535",
"NODE_DEFAULT_BOXCOLOR": "#666",
"NODE_DEFAULT_SHAPE": "box",
"NODE_BOX_OUTLINE_COLOR": "#FFF",
"DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)",
"DEFAULT_GROUP_FONT": 24,
"WIDGET_BGCOLOR": "#222",
"WIDGET_OUTLINE_COLOR": "#666",
"WIDGET_TEXT_COLOR": "#DDD",
"WIDGET_SECONDARY_TEXT_COLOR": "#999",
"LINK_COLOR": "#9A9",
"EVENT_LINK_COLOR": "#A86",
"CONNECTING_LINK_COLOR": "#AFA",
},
},
},
"palette_2": {
"id": "palette_2",
"name": "Palette 2",
"solarized": {
"id": "solarized",
"name": "Solarized",
"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
}
}
"CLIP": "#859900", // Green
"CLIP_VISION": "#6c71c4", // Indigo
"CLIP_VISION_OUTPUT": "#859900", // Green
"CONDITIONING": "#d33682", // Magenta
"CONTROL_NET": "#cb4b16", // Orange
"IMAGE": "#dc322f", // Red
"LATENT": "#268bd2", // Blue
"MASK": "#073642", // Base02
"MODEL": "#cb4b16", // Orange
"STYLE_MODEL": "#073642", // Base02
"UPSCALE_MODEL": "#6c71c4", // Indigo
"VAE": "#586e75", // Base1
},
"litegraph_base": {
"NODE_TITLE_COLOR": "#fdf6e3",
"NODE_SELECTED_TITLE_COLOR": "#b58900",
"NODE_TEXT_SIZE": 14,
"NODE_TEXT_COLOR": "#657b83",
"NODE_SUBTEXT_SIZE": 12,
"NODE_DEFAULT_COLOR": "#586e75",
"NODE_DEFAULT_BGCOLOR": "#073642",
"NODE_DEFAULT_BOXCOLOR": "#839496",
"NODE_DEFAULT_SHAPE": "box",
"NODE_BOX_OUTLINE_COLOR": "#fdf6e3",
"DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)",
"DEFAULT_GROUP_FONT": 24,
"WIDGET_BGCOLOR": "#002b36",
"WIDGET_OUTLINE_COLOR": "#839496",
"WIDGET_TEXT_COLOR": "#fdf6e3",
"WIDGET_SECONDARY_TEXT_COLOR": "#93a1a1",
"LINK_COLOR": "#2aa198",
"EVENT_LINK_COLOR": "#268bd2",
"CONNECTING_LINK_COLOR": "#859900",
},
},
}
};
@ -192,8 +238,20 @@ app.registerExtension({
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);
Object.assign(LGraphCanvas.link_type_colors, colorPalette.colors.node_slot);
}
if (colorPalette.colors.litegraph_base) {
// Everything updates correctly in the loop, except the Node Title and Link Color for some reason
app.canvas.node_title_color = colorPalette.colors.litegraph_base.NODE_TITLE_COLOR;
app.canvas.default_link_color = colorPalette.colors.litegraph_base.LINK_COLOR;
for (const key in colorPalette.colors.litegraph_base) {
if (colorPalette.colors.litegraph_base.hasOwnProperty(key) && LiteGraph.hasOwnProperty(key)) {
LiteGraph[key] = colorPalette.colors.litegraph_base[key];
}
}
}
app.canvas.draw(true, true);
}
};

View File

@ -0,0 +1,184 @@
import { app } from "/scripts/app.js";
import { ComfyDialog, $el } from "/scripts/ui.js";
// Adds the ability to save and add multiple nodes as a template
// To save:
// Select multiple nodes (ctrl + drag to select a region or ctrl+click individual nodes)
// Right click the canvas
// Save Node Template -> give it a name
//
// To add:
// Right click the canvas
// Node templates -> click the one to add
//
// To delete/rename:
// Right click the canvas
// Node templates -> Manage
const id = "Comfy.NodeTemplates";
class ManageTemplates extends ComfyDialog {
constructor() {
super();
this.element.classList.add("comfy-manage-templates");
this.templates = this.load();
}
createButtons() {
const btns = super.createButtons();
btns[0].textContent = "Cancel";
btns.unshift(
$el("button", {
type: "button",
textContent: "Save",
onclick: () => this.save(),
})
);
return btns;
}
load() {
const templates = localStorage.getItem(id);
if (templates) {
return JSON.parse(templates);
} else {
return [];
}
}
save() {
// Find all visible inputs and save them as our new list
const inputs = this.element.querySelectorAll("input");
const updated = [];
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i];
if (input.parentElement.style.display !== "none") {
const t = this.templates[i];
t.name = input.value.trim() || input.getAttribute("data-name");
updated.push(t);
}
}
this.templates = updated;
this.store();
this.close();
}
store() {
localStorage.setItem(id, JSON.stringify(this.templates));
}
show() {
// Show list of template names + delete button
super.show(
$el(
"div",
{
style: {
display: "grid",
gridTemplateColumns: "1fr auto",
gap: "5px",
},
},
this.templates.flatMap((t) => {
let nameInput;
return [
$el(
"label",
{
textContent: "Name: ",
},
[
$el("input", {
value: t.name,
dataset: { name: t.name },
$: (el) => (nameInput = el),
}),
]
),
$el("button", {
textContent: "Delete",
style: {
fontSize: "12px",
color: "red",
fontWeight: "normal",
},
onclick: (e) => {
nameInput.value = "";
e.target.style.display = "none";
e.target.previousElementSibling.style.display = "none";
},
}),
];
})
)
);
}
}
app.registerExtension({
name: id,
setup() {
const manage = new ManageTemplates();
const clipboardAction = (cb) => {
// We use the clipboard functions but dont want to overwrite the current user clipboard
// Restore it after we've run our callback
const old = localStorage.getItem("litegrapheditor_clipboard");
cb();
localStorage.setItem("litegrapheditor_clipboard", old);
};
const orig = LGraphCanvas.prototype.getCanvasMenuOptions;
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
const options = orig.apply(this, arguments);
options.push(null);
options.push({
content: `Save Selected as Template`,
disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
callback: () => {
const name = prompt("Enter name");
if (!name || !name.trim()) return;
clipboardAction(() => {
app.canvas.copyToClipboard();
manage.templates.push({
name,
data: localStorage.getItem("litegrapheditor_clipboard"),
});
manage.store();
});
},
});
// Map each template to a menu item
const subItems = manage.templates.map((t) => ({
content: t.name,
callback: () => {
clipboardAction(() => {
localStorage.setItem("litegrapheditor_clipboard", t.data);
app.canvas.pasteFromClipboard();
});
},
}));
if (subItems.length) {
subItems.push(null, {
content: "Manage",
callback: () => manage.show(),
});
options.push({
content: "Node Templates",
submenu: {
options: subItems,
},
});
}
return options;
};
},
});

View File

@ -7481,8 +7481,8 @@ LGraphNode.prototype.executeAction = function(action)
clientY_rel = e.clientY;
}
// e.deltaX = clientX_rel - this.last_mouse_position[0];
// e.deltaY = clientY_rel- this.last_mouse_position[1];
e.deltaX = clientX_rel - this.last_mouse_position[0];
e.deltaY = clientY_rel- this.last_mouse_position[1];
this.last_mouse_position[0] = clientX_rel;
this.last_mouse_position[1] = clientY_rel;
@ -9923,7 +9923,14 @@ LGraphNode.prototype.executeAction = function(action)
case "number":
case "combo":
var old_value = w.value;
if (event.type == LiteGraph.pointerevents_method+"move" && w.type == "number") {
var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0;
var allow_scroll = true;
if (delta) {
if (x > -3 && x < widget_width + 3) {
allow_scroll = false;
}
}
if (allow_scroll && event.type == LiteGraph.pointerevents_method+"move" && w.type == "number") {
if(event.deltaX)
w.value += event.deltaX * 0.1 * (w.options.step || 1);
if ( w.options.min != null && w.value < w.options.min ) {

View File

@ -1,5 +1,5 @@
import { ComfyWidgets } from "./widgets.js";
import { ComfyUI } from "./ui.js";
import { ComfyUI, $el } from "./ui.js";
import { api } from "./api.js";
import { defaultGraph } from "./defaultGraph.js";
import { getPngMetadata, importA1111 } from "./pnginfo.js";
@ -835,7 +835,7 @@ class ComfyApp {
app.#invokeExtensionsAsync("nodeCreated", this);
},
{
title: nodeData.name,
title: nodeData.display_name || nodeData.name,
comfyClass: nodeData.name,
}
);
@ -864,12 +864,62 @@ class ComfyApp {
graphData = structuredClone(defaultGraph);
}
// Patch T2IAdapterLoader to ControlNetLoader since they are the same node now
const missingNodeTypes = [];
for (let n of graphData.nodes) {
// Patch T2IAdapterLoader to ControlNetLoader since they are the same node now
if (n.type == "T2IAdapterLoader") n.type = "ControlNetLoader";
// Find missing node types
if (!(n.type in LiteGraph.registered_node_types)) {
missingNodeTypes.push(n.type);
}
}
this.graph.configure(graphData);
try {
this.graph.configure(graphData);
} catch (error) {
let errorHint = [];
// Try extracting filename to see if it was caused by an extension script
const filename = error.fileName || (error.stack || "").match(/(\/extensions\/.*\.js)/)?.[1];
const pos = (filename || "").indexOf("/extensions/");
if (pos > -1) {
errorHint.push(
$el("span", { textContent: "This may be due to the following script:" }),
$el("br"),
$el("span", {
style: {
fontWeight: "bold",
},
textContent: filename.substring(pos),
})
);
}
// Show dialog to let the user know something went wrong loading the data
this.ui.dialog.show(
$el("div", [
$el("p", { textContent: "Loading aborted due to error reloading workflow data" }),
$el("pre", {
style: { padding: "5px", backgroundColor: "rgba(255,0,0,0.2)" },
textContent: error.toString(),
}),
$el("pre", {
style: {
padding: "5px",
color: "#ccc",
fontSize: "10px",
maxHeight: "50vh",
overflow: "auto",
backgroundColor: "rgba(0,0,0,0.2)",
},
textContent: error.stack || "No stacktrace available",
}),
...errorHint,
]).outerHTML
);
return;
}
for (const node of this.graph._nodes) {
const size = node.computeSize();
@ -893,6 +943,14 @@ class ComfyApp {
this.#invokeExtensions("loadedGraphNode", node);
}
if (missingNodeTypes.length) {
this.ui.dialog.show(
`When loading the graph, the following node types were not found: <ul>${Array.from(new Set(missingNodeTypes)).map(
(t) => `<li>${t}</li>`
).join("")}</ul>Nodes that have failed to load will show as red on the graph.`
);
}
}
/**

View File

@ -13,7 +13,7 @@ export const defaultGraph = {
inputs: [{ name: "clip", type: "CLIP", link: 5 }],
outputs: [{ name: "CONDITIONING", type: "CONDITIONING", links: [6], slot_index: 0 }],
properties: {},
widgets_values: ["bad hands"],
widgets_values: ["text, watermark"],
},
{
id: 6,
@ -26,7 +26,7 @@ export const defaultGraph = {
inputs: [{ name: "clip", type: "CLIP", link: 3 }],
outputs: [{ name: "CONDITIONING", type: "CONDITIONING", links: [4], slot_index: 0 }],
properties: {},
widgets_values: ["masterpiece best quality girl"],
widgets_values: ["beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"],
},
{
id: 5,
@ -56,7 +56,7 @@ export const defaultGraph = {
],
outputs: [{ name: "LATENT", type: "LATENT", links: [7], slot_index: 0 }],
properties: {},
widgets_values: [8566257, true, 20, 8, "euler", "normal", 1],
widgets_values: [156680208700286, true, 20, 8, "euler", "normal", 1],
},
{
id: 8,

View File

@ -32,8 +32,9 @@ export function getPngMetadata(file) {
}
const keyword = String.fromCharCode(...pngData.slice(offset + 8, keyword_end));
// Get the text
const text = String.fromCharCode(...pngData.slice(keyword_end + 1, offset + 8 + length));
txt_chunks[keyword] = text;
const contentArraySegment = pngData.slice(keyword_end + 1, offset + 8 + length);
const contentJson = Array.from(contentArraySegment).map(s=>String.fromCharCode(s)).join('')
txt_chunks[keyword] = contentJson;
}
offset += 12 + length;

View File

@ -8,14 +8,18 @@ export function $el(tag, propsOrChildren, children) {
if (Array.isArray(propsOrChildren)) {
element.append(...propsOrChildren);
} else {
const parent = propsOrChildren.parent;
const { parent, $: cb, dataset, style } = propsOrChildren;
delete propsOrChildren.parent;
const cb = propsOrChildren.$;
delete propsOrChildren.$;
delete propsOrChildren.dataset;
delete propsOrChildren.style;
if (propsOrChildren.style) {
Object.assign(element.style, propsOrChildren.style);
delete propsOrChildren.style;
if (style) {
Object.assign(element.style, style);
}
if (dataset) {
Object.assign(element.dataset, dataset);
}
Object.assign(element, propsOrChildren);
@ -76,7 +80,7 @@ function dragElement(dragEl, settings) {
dragEl.style.left = newPosX + "px";
dragEl.style.right = "unset";
}
dragEl.style.top = newPosY + "px";
dragEl.style.bottom = "unset";
@ -145,7 +149,7 @@ function dragElement(dragEl, settings) {
}
window.addEventListener("resize", () => {
ensureInBounds();
ensureInBounds();
});
function closeDragElement() {
@ -155,26 +159,33 @@ function dragElement(dragEl, settings) {
}
}
class ComfyDialog {
export class ComfyDialog {
constructor() {
this.element = $el("div.comfy-modal", { parent: document.body }, [
$el("div.comfy-modal-content", [
$el("p", { $: (p) => (this.textElement = p) }),
$el("button", {
type: "button",
textContent: "Close",
onclick: () => this.close(),
}),
]),
$el("div.comfy-modal-content", [$el("p", { $: (p) => (this.textElement = p) }), ...this.createButtons()]),
]);
}
createButtons() {
return [
$el("button", {
type: "button",
textContent: "Close",
onclick: () => this.close(),
}),
];
}
close() {
this.element.style.display = "none";
}
show(html) {
this.textElement.innerHTML = html;
if (typeof html === "string") {
this.textElement.innerHTML = html;
} else {
this.textElement.replaceChildren(html);
}
this.element.style.display = "flex";
}
}
@ -419,7 +430,7 @@ export class ComfyUI {
type: "boolean",
defaultValue: true,
});
const fileInput = $el("input", {
type: "file",
accept: ".json,image/png",

View File

@ -202,7 +202,8 @@ button.comfy-queue-btn {
margin: 6px 0 !important;
}
.comfy-modal.comfy-settings {
.comfy-modal.comfy-settings,
.comfy-modal.comfy-manage-templates {
text-align: center;
font-family: sans-serif;
color: #999;