Compare commits

...

11 Commits

Author SHA1 Message Date
guill
0c7c98a965 Nodes using UNIQUE_ID as input are NOT_IDEMPOTENT (#4793)
As suggested by @ltdrdata, we can automatically consider nodes that take
the UNIQUE_ID hidden input to be NOT_IDEMPOTENT.
2024-09-05 19:33:02 -04:00
comfyanonymous
dc2eb75b85 Update stable release workflow to latest pytorch with cuda 12.4. 2024-09-05 19:21:52 -04:00
Chenlei Hu
fa34efe3bd Update frontend to v1.2.47 (#4798)
* Update web content to release v1.2.47

* Update shortcut list
2024-09-05 18:56:01 -04:00
comfyanonymous
5cbaa9e07c Mistoline flux controlnet support. 2024-09-05 00:05:17 -04:00
comfyanonymous
c7427375ee Prioritize freeing partially offloaded models first. 2024-09-04 19:47:32 -04:00
comfyanonymous
22d1241a50 Add an experimental LoraSave node to extract model loras.
The model_diff input should be connected to the output of a
ModelMergeSubtract node.
2024-09-04 16:38:38 -04:00
Jedrzej Kosinski
f04229b84d Add emb_patch support to UNetModel forward (#4779) 2024-09-04 14:35:15 -04:00
Silver
f067ad15d1 Make live preview size a configurable launch argument (#4649)
* Make live preview size a configurable launch argument

* Remove import from testing phase

* Update cli_args.py
2024-09-03 19:16:38 -04:00
comfyanonymous
483004dd1d Support newer glora format. 2024-09-03 17:02:19 -04:00
comfyanonymous
00a5d08103 Lower fp8 lora memory usage. 2024-09-03 01:25:05 -04:00
comfyanonymous
d043997d30 Flux onetrainer lora. 2024-09-02 08:22:15 -04:00
61 changed files with 8282 additions and 7347 deletions

View File

@@ -12,7 +12,7 @@ on:
description: 'CUDA version' description: 'CUDA version'
required: true required: true
type: string type: string
default: "121" default: "124"
python_minor: python_minor:
description: 'Python minor version' description: 'Python minor version'
required: true required: true

View File

@@ -94,6 +94,8 @@ Workflow examples can be found on the [Examples page](https://comfyanonymous.git
| Alt + `+` | Canvas Zoom in | | Alt + `+` | Canvas Zoom in |
| Alt + `-` | Canvas Zoom out | | Alt + `-` | Canvas Zoom out |
| Ctrl + Shift + LMB + Vertical drag | Canvas Zoom in/out | | Ctrl + Shift + LMB + Vertical drag | Canvas Zoom in/out |
| P | Pin/Unpin selected nodes |
| Ctrl + G | Group selected nodes |
| Q | Toggle visibility of the queue | | Q | Toggle visibility of the queue |
| H | Toggle visibility of history | | H | Toggle visibility of history |
| R | Refresh graph | | R | Refresh graph |

View File

@@ -92,6 +92,8 @@ class LatentPreviewMethod(enum.Enum):
parser.add_argument("--preview-method", type=LatentPreviewMethod, default=LatentPreviewMethod.NoPreviews, help="Default preview method for sampler nodes.", action=EnumAction) parser.add_argument("--preview-method", type=LatentPreviewMethod, default=LatentPreviewMethod.NoPreviews, help="Default preview method for sampler nodes.", action=EnumAction)
parser.add_argument("--preview-size", type=int, default=512, help="Sets the maximum preview size for sampler nodes.")
cache_group = parser.add_mutually_exclusive_group() cache_group = parser.add_mutually_exclusive_group()
cache_group.add_argument("--cache-classic", action="store_true", help="Use the old style (aggressive) caching.") cache_group.add_argument("--cache-classic", action="store_true", help="Use the old style (aggressive) caching.")
cache_group.add_argument("--cache-lru", type=int, default=0, help="Use LRU caching with a maximum of N node results cached. May use more RAM/VRAM.") cache_group.add_argument("--cache-lru", type=int, default=0, help="Use LRU caching with a maximum of N node results cached. May use more RAM/VRAM.")

View File

@@ -430,9 +430,9 @@ def load_controlnet_hunyuandit(controlnet_data):
control = ControlNet(control_model, compression_ratio=1, latent_format=latent_format, load_device=load_device, manual_cast_dtype=manual_cast_dtype, extra_conds=extra_conds, strength_type=StrengthType.CONSTANT) control = ControlNet(control_model, compression_ratio=1, latent_format=latent_format, load_device=load_device, manual_cast_dtype=manual_cast_dtype, extra_conds=extra_conds, strength_type=StrengthType.CONSTANT)
return control return control
def load_controlnet_flux_xlabs(sd): def load_controlnet_flux_xlabs_mistoline(sd, mistoline=False):
model_config, operations, load_device, unet_dtype, manual_cast_dtype, offload_device = controlnet_config(sd) model_config, operations, load_device, unet_dtype, manual_cast_dtype, offload_device = controlnet_config(sd)
control_model = comfy.ldm.flux.controlnet.ControlNetFlux(operations=operations, device=offload_device, dtype=unet_dtype, **model_config.unet_config) control_model = comfy.ldm.flux.controlnet.ControlNetFlux(mistoline=mistoline, operations=operations, device=offload_device, dtype=unet_dtype, **model_config.unet_config)
control_model = controlnet_load_state_dict(control_model, sd) control_model = controlnet_load_state_dict(control_model, sd)
extra_conds = ['y', 'guidance'] extra_conds = ['y', 'guidance']
control = ControlNet(control_model, load_device=load_device, manual_cast_dtype=manual_cast_dtype, extra_conds=extra_conds) control = ControlNet(control_model, load_device=load_device, manual_cast_dtype=manual_cast_dtype, extra_conds=extra_conds)
@@ -457,6 +457,10 @@ def load_controlnet_flux_instantx(sd):
control = ControlNet(control_model, compression_ratio=1, latent_format=latent_format, load_device=load_device, manual_cast_dtype=manual_cast_dtype, extra_conds=extra_conds) control = ControlNet(control_model, compression_ratio=1, latent_format=latent_format, load_device=load_device, manual_cast_dtype=manual_cast_dtype, extra_conds=extra_conds)
return control return control
def convert_mistoline(sd):
return comfy.utils.state_dict_prefix_replace(sd, {"single_controlnet_blocks.": "controlnet_single_blocks."})
def load_controlnet(ckpt_path, model=None): def load_controlnet(ckpt_path, model=None):
controlnet_data = comfy.utils.load_torch_file(ckpt_path, safe_load=True) controlnet_data = comfy.utils.load_torch_file(ckpt_path, safe_load=True)
if 'after_proj_list.18.bias' in controlnet_data.keys(): #Hunyuan DiT if 'after_proj_list.18.bias' in controlnet_data.keys(): #Hunyuan DiT
@@ -518,13 +522,15 @@ def load_controlnet(ckpt_path, model=None):
if len(leftover_keys) > 0: if len(leftover_keys) > 0:
logging.warning("leftover keys: {}".format(leftover_keys)) logging.warning("leftover keys: {}".format(leftover_keys))
controlnet_data = new_sd controlnet_data = new_sd
elif "controlnet_blocks.0.weight" in controlnet_data: #SD3 diffusers format elif "controlnet_blocks.0.weight" in controlnet_data:
if "double_blocks.0.img_attn.norm.key_norm.scale" in controlnet_data: if "double_blocks.0.img_attn.norm.key_norm.scale" in controlnet_data:
return load_controlnet_flux_xlabs(controlnet_data) return load_controlnet_flux_xlabs_mistoline(controlnet_data)
elif "pos_embed_input.proj.weight" in controlnet_data: elif "pos_embed_input.proj.weight" in controlnet_data:
return load_controlnet_mmdit(controlnet_data) return load_controlnet_mmdit(controlnet_data) #SD3 diffusers controlnet
elif "controlnet_x_embedder.weight" in controlnet_data: elif "controlnet_x_embedder.weight" in controlnet_data:
return load_controlnet_flux_instantx(controlnet_data) return load_controlnet_flux_instantx(controlnet_data)
elif "controlnet_blocks.0.linear.weight" in controlnet_data: #mistoline flux
return load_controlnet_flux_xlabs_mistoline(convert_mistoline(controlnet_data), mistoline=True)
pth_key = 'control_model.zero_convs.0.0.weight' pth_key = 'control_model.zero_convs.0.0.weight'
pth = False pth = False

View File

@@ -41,9 +41,8 @@ def manual_stochastic_round_to_float8(x, dtype, generator=None):
(2.0 ** (exponent - EXPONENT_BIAS)) * (1.0 + abs_x), (2.0 ** (exponent - EXPONENT_BIAS)) * (1.0 + abs_x),
(2.0 ** (-EXPONENT_BIAS + 1)) * abs_x (2.0 ** (-EXPONENT_BIAS + 1)) * abs_x
) )
del abs_x
return sign.to(dtype=dtype) return sign
@@ -57,6 +56,11 @@ def stochastic_rounding(value, dtype, seed=0):
if dtype == torch.float8_e4m3fn or dtype == torch.float8_e5m2: if dtype == torch.float8_e4m3fn or dtype == torch.float8_e5m2:
generator = torch.Generator(device=value.device) generator = torch.Generator(device=value.device)
generator.manual_seed(seed) generator.manual_seed(seed)
return manual_stochastic_round_to_float8(value, dtype, generator=generator) output = torch.empty_like(value, dtype=dtype)
num_slices = max(1, (value.numel() / (4096 * 4096)))
slice_size = max(1, round(value.shape[0] / num_slices))
for i in range(0, value.shape[0], slice_size):
output[i:i+slice_size].copy_(manual_stochastic_round_to_float8(value[i:i+slice_size], dtype, generator=generator))
return output
return value.to(dtype=dtype) return value.to(dtype=dtype)

View File

@@ -1,4 +1,5 @@
#Original code can be found on: https://github.com/XLabs-AI/x-flux/blob/main/src/flux/controlnet.py #Original code can be found on: https://github.com/XLabs-AI/x-flux/blob/main/src/flux/controlnet.py
#modified to support different types of flux controlnets
import torch import torch
import math import math
@@ -12,22 +13,65 @@ from .layers import (DoubleStreamBlock, EmbedND, LastLayer,
from .model import Flux from .model import Flux
import comfy.ldm.common_dit import comfy.ldm.common_dit
class MistolineCondDownsamplBlock(nn.Module):
def __init__(self, dtype=None, device=None, operations=None):
super().__init__()
self.encoder = nn.Sequential(
operations.Conv2d(3, 16, 3, padding=1, dtype=dtype, device=device),
nn.SiLU(),
operations.Conv2d(16, 16, 1, dtype=dtype, device=device),
nn.SiLU(),
operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device),
nn.SiLU(),
operations.Conv2d(16, 16, 3, padding=1, stride=2, dtype=dtype, device=device),
nn.SiLU(),
operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device),
nn.SiLU(),
operations.Conv2d(16, 16, 3, padding=1, stride=2, dtype=dtype, device=device),
nn.SiLU(),
operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device),
nn.SiLU(),
operations.Conv2d(16, 16, 3, padding=1, stride=2, dtype=dtype, device=device),
nn.SiLU(),
operations.Conv2d(16, 16, 1, dtype=dtype, device=device),
nn.SiLU(),
operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device)
)
def forward(self, x):
return self.encoder(x)
class MistolineControlnetBlock(nn.Module):
def __init__(self, hidden_size, dtype=None, device=None, operations=None):
super().__init__()
self.linear = operations.Linear(hidden_size, hidden_size, dtype=dtype, device=device)
self.act = nn.SiLU()
def forward(self, x):
return self.act(self.linear(x))
class ControlNetFlux(Flux): class ControlNetFlux(Flux):
def __init__(self, latent_input=False, num_union_modes=0, image_model=None, dtype=None, device=None, operations=None, **kwargs): def __init__(self, latent_input=False, num_union_modes=0, mistoline=False, image_model=None, dtype=None, device=None, operations=None, **kwargs):
super().__init__(final_layer=False, dtype=dtype, device=device, operations=operations, **kwargs) super().__init__(final_layer=False, dtype=dtype, device=device, operations=operations, **kwargs)
self.main_model_double = 19 self.main_model_double = 19
self.main_model_single = 38 self.main_model_single = 38
self.mistoline = mistoline
# add ControlNet blocks # add ControlNet blocks
if self.mistoline:
control_block = lambda : MistolineControlnetBlock(self.hidden_size, dtype=dtype, device=device, operations=operations)
else:
control_block = lambda : operations.Linear(self.hidden_size, self.hidden_size, dtype=dtype, device=device)
self.controlnet_blocks = nn.ModuleList([]) self.controlnet_blocks = nn.ModuleList([])
for _ in range(self.params.depth): for _ in range(self.params.depth):
controlnet_block = operations.Linear(self.hidden_size, self.hidden_size, dtype=dtype, device=device) self.controlnet_blocks.append(control_block())
self.controlnet_blocks.append(controlnet_block)
self.controlnet_single_blocks = nn.ModuleList([]) self.controlnet_single_blocks = nn.ModuleList([])
for _ in range(self.params.depth_single_blocks): for _ in range(self.params.depth_single_blocks):
self.controlnet_single_blocks.append(operations.Linear(self.hidden_size, self.hidden_size, dtype=dtype, device=device)) self.controlnet_single_blocks.append(control_block())
self.num_union_modes = num_union_modes self.num_union_modes = num_union_modes
self.controlnet_mode_embedder = None self.controlnet_mode_embedder = None
@@ -38,23 +82,26 @@ class ControlNetFlux(Flux):
self.latent_input = latent_input self.latent_input = latent_input
self.pos_embed_input = operations.Linear(self.in_channels, self.hidden_size, bias=True, dtype=dtype, device=device) self.pos_embed_input = operations.Linear(self.in_channels, self.hidden_size, bias=True, dtype=dtype, device=device)
if not self.latent_input: if not self.latent_input:
self.input_hint_block = nn.Sequential( if self.mistoline:
operations.Conv2d(3, 16, 3, padding=1, dtype=dtype, device=device), self.input_cond_block = MistolineCondDownsamplBlock(dtype=dtype, device=device, operations=operations)
nn.SiLU(), else:
operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device), self.input_hint_block = nn.Sequential(
nn.SiLU(), operations.Conv2d(3, 16, 3, padding=1, dtype=dtype, device=device),
operations.Conv2d(16, 16, 3, padding=1, stride=2, dtype=dtype, device=device), nn.SiLU(),
nn.SiLU(), operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device),
operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device), nn.SiLU(),
nn.SiLU(), operations.Conv2d(16, 16, 3, padding=1, stride=2, dtype=dtype, device=device),
operations.Conv2d(16, 16, 3, padding=1, stride=2, dtype=dtype, device=device), nn.SiLU(),
nn.SiLU(), operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device),
operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device), nn.SiLU(),
nn.SiLU(), operations.Conv2d(16, 16, 3, padding=1, stride=2, dtype=dtype, device=device),
operations.Conv2d(16, 16, 3, padding=1, stride=2, dtype=dtype, device=device), nn.SiLU(),
nn.SiLU(), operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device),
operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device) nn.SiLU(),
) operations.Conv2d(16, 16, 3, padding=1, stride=2, dtype=dtype, device=device),
nn.SiLU(),
operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device)
)
def forward_orig( def forward_orig(
self, self,
@@ -73,9 +120,6 @@ class ControlNetFlux(Flux):
# running on sequences img # running on sequences img
img = self.img_in(img) img = self.img_in(img)
if not self.latent_input:
controlnet_cond = self.input_hint_block(controlnet_cond)
controlnet_cond = rearrange(controlnet_cond, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=2, pw=2)
controlnet_cond = self.pos_embed_input(controlnet_cond) controlnet_cond = self.pos_embed_input(controlnet_cond)
img = img + controlnet_cond img = img + controlnet_cond
@@ -131,9 +175,14 @@ class ControlNetFlux(Flux):
patch_size = 2 patch_size = 2
if self.latent_input: if self.latent_input:
hint = comfy.ldm.common_dit.pad_to_patch_size(hint, (patch_size, patch_size)) hint = comfy.ldm.common_dit.pad_to_patch_size(hint, (patch_size, patch_size))
hint = rearrange(hint, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=patch_size, pw=patch_size) elif self.mistoline:
hint = hint * 2.0 - 1.0
hint = self.input_cond_block(hint)
else: else:
hint = hint * 2.0 - 1.0 hint = hint * 2.0 - 1.0
hint = self.input_hint_block(hint)
hint = rearrange(hint, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=patch_size, pw=patch_size)
bs, c, h, w = x.shape bs, c, h, w = x.shape
x = comfy.ldm.common_dit.pad_to_patch_size(x, (patch_size, patch_size)) x = comfy.ldm.common_dit.pad_to_patch_size(x, (patch_size, patch_size))

View File

@@ -842,6 +842,11 @@ class UNetModel(nn.Module):
t_emb = timestep_embedding(timesteps, self.model_channels, repeat_only=False).to(x.dtype) t_emb = timestep_embedding(timesteps, self.model_channels, repeat_only=False).to(x.dtype)
emb = self.time_embed(t_emb) emb = self.time_embed(t_emb)
if "emb_patch" in transformer_patches:
patch = transformer_patches["emb_patch"]
for p in patch:
emb = p(emb, self.model_channels, transformer_options)
if self.num_classes is not None: if self.num_classes is not None:
assert y.shape[0] == x.shape[0] assert y.shape[0] == x.shape[0]
emb = emb + self.label_emb(y) emb = emb + self.label_emb(y)

View File

@@ -324,6 +324,7 @@ def model_lora_keys_unet(model, key_map={}):
to = diffusers_keys[k] to = diffusers_keys[k]
key_map["transformer.{}".format(k[:-len(".weight")])] = to #simpletrainer and probably regular diffusers flux lora format key_map["transformer.{}".format(k[:-len(".weight")])] = to #simpletrainer and probably regular diffusers flux lora format
key_map["lycoris_{}".format(k[:-len(".weight")].replace(".", "_"))] = to #simpletrainer lycoris key_map["lycoris_{}".format(k[:-len(".weight")].replace(".", "_"))] = to #simpletrainer lycoris
key_map["lora_transformer_{}".format(k[:-len(".weight")].replace(".", "_"))] = to #onetrainer
return key_map return key_map
@@ -527,20 +528,40 @@ def calculate_weight(patches, weight, key, intermediate_dtype=torch.float32):
except Exception as e: except Exception as e:
logging.error("ERROR {} {} {}".format(patch_type, key, e)) logging.error("ERROR {} {} {}".format(patch_type, key, e))
elif patch_type == "glora": elif patch_type == "glora":
if v[4] is not None:
alpha = v[4] / v[0].shape[0]
else:
alpha = 1.0
dora_scale = v[5] dora_scale = v[5]
old_glora = False
if v[3].shape[1] == v[2].shape[0] == v[0].shape[0] == v[1].shape[1]:
rank = v[0].shape[0]
old_glora = True
if v[3].shape[0] == v[2].shape[1] == v[0].shape[1] == v[1].shape[0]:
if old_glora and v[1].shape[0] == weight.shape[0] and weight.shape[0] == weight.shape[1]:
pass
else:
old_glora = False
rank = v[1].shape[0]
a1 = comfy.model_management.cast_to_device(v[0].flatten(start_dim=1), weight.device, intermediate_dtype) a1 = comfy.model_management.cast_to_device(v[0].flatten(start_dim=1), weight.device, intermediate_dtype)
a2 = comfy.model_management.cast_to_device(v[1].flatten(start_dim=1), weight.device, intermediate_dtype) a2 = comfy.model_management.cast_to_device(v[1].flatten(start_dim=1), weight.device, intermediate_dtype)
b1 = comfy.model_management.cast_to_device(v[2].flatten(start_dim=1), weight.device, intermediate_dtype) b1 = comfy.model_management.cast_to_device(v[2].flatten(start_dim=1), weight.device, intermediate_dtype)
b2 = comfy.model_management.cast_to_device(v[3].flatten(start_dim=1), weight.device, intermediate_dtype) b2 = comfy.model_management.cast_to_device(v[3].flatten(start_dim=1), weight.device, intermediate_dtype)
if v[4] is not None:
alpha = v[4] / rank
else:
alpha = 1.0
try: try:
lora_diff = (torch.mm(b2, b1) + torch.mm(torch.mm(weight.flatten(start_dim=1).to(dtype=intermediate_dtype), a2), a1)).reshape(weight.shape) if old_glora:
lora_diff = (torch.mm(b2, b1) + torch.mm(torch.mm(weight.flatten(start_dim=1).to(dtype=intermediate_dtype), a2), a1)).reshape(weight.shape) #old lycoris glora
else:
if weight.dim() > 2:
lora_diff = torch.einsum("o i ..., i j -> o j ...", torch.einsum("o i ..., i j -> o j ...", weight.to(dtype=intermediate_dtype), a1), a2).reshape(weight.shape)
else:
lora_diff = torch.mm(torch.mm(weight.to(dtype=intermediate_dtype), a1), a2).reshape(weight.shape)
lora_diff += torch.mm(b1, b2).reshape(weight.shape)
if dora_scale is not None: if dora_scale is not None:
weight = function(weight_decompose(dora_scale, weight, lora_diff, alpha, strength, intermediate_dtype)) weight = function(weight_decompose(dora_scale, weight, lora_diff, alpha, strength, intermediate_dtype))
else: else:

View File

@@ -426,7 +426,7 @@ def free_memory(memory_required, device, keep_loaded=[]):
shift_model = current_loaded_models[i] shift_model = current_loaded_models[i]
if shift_model.device == device: if shift_model.device == device:
if shift_model not in keep_loaded: if shift_model not in keep_loaded:
can_unload.append((sys.getrefcount(shift_model.model), shift_model.model_memory(), i)) can_unload.append((-shift_model.model_offloaded_memory(), sys.getrefcount(shift_model.model), shift_model.model_memory(), i))
shift_model.currently_used = False shift_model.currently_used = False
for x in sorted(can_unload): for x in sorted(can_unload):

View File

@@ -98,7 +98,7 @@ class CacheKeySetInputSignature(CacheKeySet):
class_type = node["class_type"] class_type = node["class_type"]
class_def = nodes.NODE_CLASS_MAPPINGS[class_type] class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
signature = [class_type, self.is_changed_cache.get(node_id)] signature = [class_type, self.is_changed_cache.get(node_id)]
if self.include_node_id_in_input() or (hasattr(class_def, "NOT_IDEMPOTENT") and class_def.NOT_IDEMPOTENT): if self.include_node_id_in_input() or (hasattr(class_def, "NOT_IDEMPOTENT") and class_def.NOT_IDEMPOTENT) or "UNIQUE_ID" in class_def.INPUT_TYPES().get("hidden", {}).values():
signature.append(node_id) signature.append(node_id)
inputs = node["inputs"] inputs = node["inputs"]
for key in sorted(inputs.keys()): for key in sorted(inputs.keys()):

View File

@@ -0,0 +1,91 @@
import torch
import comfy.model_management
import comfy.utils
import folder_paths
import os
import logging
CLAMP_QUANTILE = 0.99
def extract_lora(diff, rank):
conv2d = (len(diff.shape) == 4)
kernel_size = None if not conv2d else diff.size()[2:4]
conv2d_3x3 = conv2d and kernel_size != (1, 1)
out_dim, in_dim = diff.size()[0:2]
rank = min(rank, in_dim, out_dim)
if conv2d:
if conv2d_3x3:
diff = diff.flatten(start_dim=1)
else:
diff = diff.squeeze()
U, S, Vh = torch.linalg.svd(diff.float())
U = U[:, :rank]
S = S[:rank]
U = U @ torch.diag(S)
Vh = Vh[:rank, :]
dist = torch.cat([U.flatten(), Vh.flatten()])
hi_val = torch.quantile(dist, CLAMP_QUANTILE)
low_val = -hi_val
U = U.clamp(low_val, hi_val)
Vh = Vh.clamp(low_val, hi_val)
if conv2d:
U = U.reshape(out_dim, rank, 1, 1)
Vh = Vh.reshape(rank, in_dim, kernel_size[0], kernel_size[1])
return (U, Vh)
class LoraSave:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
@classmethod
def INPUT_TYPES(s):
return {"required": {"filename_prefix": ("STRING", {"default": "loras/ComfyUI_extracted_lora"}),
"rank": ("INT", {"default": 8, "min": 1, "max": 1024, "step": 1}),
},
"optional": {"model_diff": ("MODEL",),},
}
RETURN_TYPES = ()
FUNCTION = "save"
OUTPUT_NODE = True
CATEGORY = "_for_testing"
def save(self, filename_prefix, rank, model_diff=None):
if model_diff is None:
return {}
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
output_sd = {}
prefix_key = "diffusion_model."
stored = set()
comfy.model_management.load_models_gpu([model_diff], force_patch_weights=True)
sd = model_diff.model_state_dict(filter_prefix=prefix_key)
for k in sd:
if k.endswith(".weight"):
weight_diff = sd[k]
if weight_diff.ndim < 2:
continue
try:
out = extract_lora(weight_diff, rank)
output_sd["{}.lora_up.weight".format(k[:-7])] = out[0].contiguous().half().cpu()
output_sd["{}.lora_down.weight".format(k[:-7])] = out[1].contiguous().half().cpu()
except:
logging.warning("Could not generate lora weights for key {}, is the weight difference a zero?".format(k))
output_checkpoint = f"{filename}_{counter:05}_.safetensors"
output_checkpoint = os.path.join(full_output_folder, output_checkpoint)
comfy.utils.save_torch_file(output_sd, output_checkpoint, metadata=None)
return {}
NODE_CLASS_MAPPINGS = {
"LoraSave": LoraSave
}

View File

@@ -9,7 +9,7 @@ import folder_paths
import comfy.utils import comfy.utils
import logging import logging
MAX_PREVIEW_RESOLUTION = 512 MAX_PREVIEW_RESOLUTION = args.preview_size
def preview_to_image(latent_image): def preview_to_image(latent_image):
latents_ubyte = (((latent_image + 1.0) / 2.0).clamp(0, 1) # change scale from -1..1 to 0..1 latents_ubyte = (((latent_image + 1.0) / 2.0).clamp(0, 1) # change scale from -1..1 to 0..1

View File

@@ -247,6 +247,7 @@ if __name__ == "__main__":
folder_paths.add_model_folder_path("clip", os.path.join(folder_paths.get_output_directory(), "clip")) folder_paths.add_model_folder_path("clip", os.path.join(folder_paths.get_output_directory(), "clip"))
folder_paths.add_model_folder_path("vae", os.path.join(folder_paths.get_output_directory(), "vae")) folder_paths.add_model_folder_path("vae", os.path.join(folder_paths.get_output_directory(), "vae"))
folder_paths.add_model_folder_path("diffusion_models", os.path.join(folder_paths.get_output_directory(), "diffusion_models")) folder_paths.add_model_folder_path("diffusion_models", os.path.join(folder_paths.get_output_directory(), "diffusion_models"))
folder_paths.add_model_folder_path("loras", os.path.join(folder_paths.get_output_directory(), "loras"))
if args.input_directory: if args.input_directory:
input_dir = os.path.abspath(args.input_directory) input_dir = os.path.abspath(args.input_directory)

View File

@@ -2101,6 +2101,7 @@ def init_builtin_extra_nodes():
"nodes_controlnet.py", "nodes_controlnet.py",
"nodes_hunyuan.py", "nodes_hunyuan.py",
"nodes_flux.py", "nodes_flux.py",
"nodes_lora_extract.py",
] ]
import_failed = [] import_failed = []

1
web/assets/index-BD-Ia1C4.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
var __defProp = Object.defineProperty; var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import { C as ComfyDialog, $ as $el, a as ComfyApp, b as app, L as LGraphCanvas, c as LiteGraph, d as LGraphNode, e as applyTextReplacements, f as ComfyWidgets, g as addValueControlWidgets, D as DraggableList, h as api, u as useToastStore, i as LGraphGroup } from "./index-CI3N807S.js"; import { C as ComfyDialog, $ as $el, a as ComfyApp, b as app, L as LGraphCanvas, c as LiteGraph, d as LGraphNode, e as applyTextReplacements, f as ComfyWidgets, g as addValueControlWidgets, D as DraggableList, h as api, i as LGraphGroup, u as useToastStore } from "./index-Dfv2aLsq.js";
class ClipspaceDialog extends ComfyDialog { class ClipspaceDialog extends ComfyDialog {
static { static {
__name(this, "ClipspaceDialog"); __name(this, "ClipspaceDialog");
@@ -3650,7 +3650,7 @@ app.registerExtension({
content: "Add Group For Selected Nodes", content: "Add Group For Selected Nodes",
disabled: !Object.keys(app.canvas.selected_nodes || {}).length, disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
callback: /* @__PURE__ */ __name(() => { callback: /* @__PURE__ */ __name(() => {
var group2 = new LiteGraph.LGraphGroup(); const group2 = new LGraphGroup();
addNodesToGroup(group2, this.selected_nodes); addNodesToGroup(group2, this.selected_nodes);
app.canvas.graph.add(group2); app.canvas.graph.add(group2);
this.graph.change(); this.graph.change();
@@ -5088,7 +5088,7 @@ app.registerExtension({
data = JSON.parse(data); data = JSON.parse(data);
const nodeIds = Object.keys(app.canvas.selected_nodes); const nodeIds = Object.keys(app.canvas.selected_nodes);
for (let i = 0; i < nodeIds.length; i++) { for (let i = 0; i < nodeIds.length; i++) {
const node = app.graph.getNodeById(Number.parseInt(nodeIds[i])); const node = app.graph.getNodeById(nodeIds[i]);
const nodeData = node?.constructor.nodeData; const nodeData = node?.constructor.nodeData;
let groupData = GroupNodeHandler.getGroupData(node); let groupData = GroupNodeHandler.getGroupData(node);
if (groupData) { if (groupData) {
@@ -5955,7 +5955,7 @@ app.registerExtension({
}, },
onNodeOutputsUpdated(nodeOutputs) { onNodeOutputsUpdated(nodeOutputs) {
for (const [nodeId, output] of Object.entries(nodeOutputs)) { for (const [nodeId, output] of Object.entries(nodeOutputs)) {
const node = app.graph.getNodeById(Number.parseInt(nodeId)); const node = app.graph.getNodeById(nodeId);
if ("audio" in output) { if ("audio" in output) {
const audioUIWidget = node.widgets.find( const audioUIWidget = node.widgets.find(
(w) => w.name === "audioUI" (w) => w.name === "audioUI"
@@ -6026,4 +6026,4 @@ app.registerExtension({
}; };
} }
}); });
//# sourceMappingURL=index-BD-Ia1C4.js.map //# sourceMappingURL=index-CrROdkG4.js.map

1
web/assets/index-CrROdkG4.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1475,21 +1475,21 @@
width: 5rem !important; width: 5rem !important;
} }
.info-chip[data-v-25bd5f50] { .info-chip[data-v-ffbfdf57] {
background: transparent; background: transparent;
} }
.setting-item[data-v-25bd5f50] { .setting-item[data-v-ffbfdf57] {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.setting-label[data-v-25bd5f50] { .setting-label[data-v-ffbfdf57] {
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1; flex: 1;
} }
.setting-input[data-v-25bd5f50] { .setting-input[data-v-ffbfdf57] {
flex: 1; flex: 1;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
@@ -1497,19 +1497,19 @@
} }
/* Ensure PrimeVue components take full width of their container */ /* Ensure PrimeVue components take full width of their container */
.setting-input[data-v-25bd5f50] .p-inputtext, .setting-input[data-v-ffbfdf57] .p-inputtext,
.setting-input[data-v-25bd5f50] .input-slider, .setting-input[data-v-ffbfdf57] .input-slider,
.setting-input[data-v-25bd5f50] .p-select, .setting-input[data-v-ffbfdf57] .p-select,
.setting-input[data-v-25bd5f50] .p-togglebutton { .setting-input[data-v-ffbfdf57] .p-togglebutton {
width: 100%; width: 100%;
max-width: 200px; max-width: 200px;
} }
.setting-input[data-v-25bd5f50] .p-inputtext { .setting-input[data-v-ffbfdf57] .p-inputtext {
max-width: unset; max-width: unset;
} }
/* Special case for ToggleSwitch to align it to the right */ /* Special case for ToggleSwitch to align it to the right */
.setting-input[data-v-25bd5f50] .p-toggleswitch { .setting-input[data-v-ffbfdf57] .p-toggleswitch {
margin-left: auto; margin-left: auto;
} }
@@ -1655,21 +1655,21 @@
margin-left: 0.5rem; margin-left: 0.5rem;
} }
.comfy-error-report[data-v-12539d86] { .comfy-error-report[data-v-a103fd62] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
} }
.action-container[data-v-12539d86] { .action-container[data-v-a103fd62] {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
justify-content: flex-end; justify-content: flex-end;
} }
.wrapper-pre[data-v-12539d86] { .wrapper-pre[data-v-a103fd62] {
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
} }
.no-results-placeholder[data-v-12539d86] { .no-results-placeholder[data-v-a103fd62] {
padding-top: 0; padding-top: 0;
} }
.lds-ring { .lds-ring {
@@ -3158,7 +3158,7 @@ body {
overflow: hidden; overflow: hidden;
grid-template-columns: auto 1fr auto; grid-template-columns: auto 1fr auto;
grid-template-rows: auto 1fr auto; grid-template-rows: auto 1fr auto;
background-color: var(--bg-color); background: var(--bg-color) var(--bg-img);
color: var(--fg-color); color: var(--fg-color);
min-height: -webkit-fill-available; min-height: -webkit-fill-available;
max-height: -webkit-fill-available; max-height: -webkit-fill-available;
@@ -3833,17 +3833,19 @@ audio.comfy-audio.empty-audio-widget {
box-sizing: border-box; box-sizing: border-box;
} }
.node-title-editor[data-v-77799b26] { .group-title-editor.node-title-editor[data-v-f0cbabc5] {
z-index: 9999; z-index: 9999;
padding: 0.25rem; padding: 0.25rem;
} }
[data-v-77799b26] .editable-text { [data-v-f0cbabc5] .editable-text {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
[data-v-77799b26] .editable-text input { [data-v-f0cbabc5] .editable-text input {
width: 100%; width: 100%;
height: 100%; height: 100%;
/* Override the default font size */
font-size: inherit;
} }
.side-bar-button-icon { .side-bar-button-icon {
@@ -4086,26 +4088,26 @@ audio.comfy-audio.empty-audio-widget {
color: var(--error-text); color: var(--error-text);
} }
.comfy-vue-node-search-container[data-v-077af1a9] { .comfy-vue-node-search-container[data-v-d28bffc4] {
display: flex; display: flex;
width: 100%; width: 100%;
min-width: 24rem; min-width: 24rem;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.comfy-vue-node-search-container[data-v-077af1a9] * { .comfy-vue-node-search-container[data-v-d28bffc4] * {
pointer-events: auto; pointer-events: auto;
} }
.comfy-vue-node-preview-container[data-v-077af1a9] { .comfy-vue-node-preview-container[data-v-d28bffc4] {
position: absolute; position: absolute;
left: -350px; left: -350px;
top: 50px; top: 50px;
} }
.comfy-vue-node-search-box[data-v-077af1a9] { .comfy-vue-node-search-box[data-v-d28bffc4] {
z-index: 10; z-index: 10;
flex-grow: 1; flex-grow: 1;
} }
.option-container[data-v-077af1a9] { .option-container[data-v-d28bffc4] {
display: flex; display: flex;
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
@@ -4117,12 +4119,12 @@ audio.comfy-audio.empty-audio-widget {
padding-top: 0px; padding-top: 0px;
padding-bottom: 0px; padding-bottom: 0px;
} }
.option-display-name[data-v-077af1a9] { .option-display-name[data-v-d28bffc4] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-weight: 600; font-weight: 600;
} }
.option-category[data-v-077af1a9] { .option-category[data-v-d28bffc4] {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 0.875rem; font-size: 0.875rem;
@@ -4133,7 +4135,7 @@ audio.comfy-audio.empty-audio-widget {
/* Keeps the text on a single line by default */ /* Keeps the text on a single line by default */
white-space: nowrap; white-space: nowrap;
} }
[data-v-077af1a9] .highlight { [data-v-d28bffc4] .highlight {
background-color: var(--p-primary-color); background-color: var(--p-primary-color);
color: var(--p-primary-contrast-color); color: var(--p-primary-contrast-color);
font-weight: bold; font-weight: bold;
@@ -4141,10 +4143,10 @@ audio.comfy-audio.empty-audio-widget {
padding: 0rem 0.125rem; padding: 0rem 0.125rem;
margin: -0.125rem 0.125rem; margin: -0.125rem 0.125rem;
} }
._filter-button[data-v-077af1a9] { ._filter-button[data-v-d28bffc4] {
z-index: 10; z-index: 10;
} }
._dialog[data-v-077af1a9] { ._dialog[data-v-d28bffc4] {
min-width: 24rem; min-width: 24rem;
} }
@@ -4353,28 +4355,60 @@ img.galleria-image {
gap: 0.5rem; gap: 0.5rem;
} }
.node-tree-leaf[data-v-adf5f221] { .tree-node[data-v-d4b7b060] {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
} }
.node-content[data-v-adf5f221] { .leaf-count-badge[data-v-d4b7b060] {
margin-left: 0.5rem;
}
.node-content[data-v-d4b7b060] {
display: flex; display: flex;
align-items: center; align-items: center;
flex-grow: 1; flex-grow: 1;
} }
.node-label[data-v-adf5f221] { .leaf-label[data-v-d4b7b060] {
margin-left: 0.5rem; margin-left: 0.5rem;
} }
.bookmark-button[data-v-adf5f221] { [data-v-d4b7b060] .editable-text span {
width: unset; word-break: break-all;
padding: 0.25rem;
} }
.node-tree-folder[data-v-f2d72e9b] { [data-v-9d3310b9] .tree-explorer-node-label {
width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: var(--p-tree-node-gap);
flex-grow: 1;
}
/*
* The following styles are necessary to avoid layout shift when dragging nodes over folders.
* By setting the position to relative on the parent and using an absolutely positioned pseudo-element,
* we can create a visual indicator for the drop target without affecting the layout of other elements.
*/
[data-v-9d3310b9] .p-tree-node-content:has(.tree-folder) {
position: relative;
}
[data-v-9d3310b9] .p-tree-node-content:has(.tree-folder.can-drop)::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid var(--p-content-color);
pointer-events: none;
}
.node-lib-node-container[data-v-3238e135] {
height: 100%;
width: 100%;
}
.bookmark-button[data-v-3238e135] {
width: unset;
padding: 0.25rem;
} }
.p-selectbutton .p-button[data-v-91077f2a] { .p-selectbutton .p-button[data-v-91077f2a] {
@@ -4394,45 +4428,33 @@ img.galleria-image {
gap: 0.5rem; gap: 0.5rem;
} }
.node-lib-tree-node-label {
display: flex;
align-items: center;
margin-left: var(--p-tree-node-gap);
flex-grow: 1;
}
.node-lib-filter-popup { .node-lib-filter-popup {
margin-left: -13px; margin-left: -13px;
} }
[data-v-87967891] .node-lib-search-box { [data-v-85688f44] .node-lib-search-box {
margin-left: 1rem; margin-left: 1rem;
margin-right: 1rem; margin-right: 1rem;
margin-top: 1rem; margin-top: 1rem;
} }
[data-v-87967891] .comfy-vue-side-bar-body { [data-v-85688f44] .comfy-vue-side-bar-body {
background: var(--p-tree-background); background: var(--p-tree-background);
} }
[data-v-85688f44] .node-lib-bookmark-tree-explorer {
/* padding-bottom: 2px;
* The following styles are necessary to avoid layout shift when dragging nodes over folders.
* By setting the position to relative on the parent and using an absolutely positioned pseudo-element,
* we can create a visual indicator for the drop target without affecting the layout of other elements.
*/
[data-v-87967891] .p-tree-node-content:has(.node-tree-folder) {
position: relative;
} }
[data-v-87967891] .p-tree-node-content:has(.node-tree-folder.can-drop)::after { [data-v-85688f44] .node-lib-tree-explorer {
content: ''; padding-top: 2px;
position: absolute; }
top: 0; [data-v-85688f44] .p-divider {
left: 0; margin: var(--comfy-tree-explorer-item-padding) 0px;
right: 0;
bottom: 0;
border: 1px solid var(--p-content-color);
pointer-events: none;
} }
.spinner[data-v-8616e7a1] { .p-tree-node-content {
padding: var(--comfy-tree-explorer-item-padding) !important;
}
.spinner[data-v-75e4840f] {
position: absolute; position: absolute;
inset: 0px; inset: 0px;
display: flex; display: flex;

View File

@@ -1,6 +1,6 @@
var __defProp = Object.defineProperty; var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import { j as createSpinner, h as api, $ as $el } from "./index-CI3N807S.js"; import { j as createSpinner, h as api, $ as $el } from "./index-Dfv2aLsq.js";
class UserSelectionScreen { class UserSelectionScreen {
static { static {
__name(this, "UserSelectionScreen"); __name(this, "UserSelectionScreen");
@@ -117,4 +117,4 @@ window.comfyAPI.userSelection.UserSelectionScreen = UserSelectionScreen;
export { export {
UserSelectionScreen UserSelectionScreen
}; };
//# sourceMappingURL=userSelection-CyXKCVy3.js.map //# sourceMappingURL=userSelection-DSpF-zVD.js.map

File diff suppressed because one or more lines are too long

BIN
web/dist.zip vendored Normal file

Binary file not shown.

View File

@@ -1,2 +1,2 @@
// Shim for extensions\core\clipspace.ts // Shim for extensions/core/clipspace.ts
export const ClipspaceDialog = window.comfyAPI.clipspace.ClipspaceDialog; export const ClipspaceDialog = window.comfyAPI.clipspace.ClipspaceDialog;

View File

@@ -1,3 +1,3 @@
// Shim for extensions\core\groupNode.ts // Shim for extensions/core/groupNode.ts
export const GroupNodeConfig = window.comfyAPI.groupNode.GroupNodeConfig; export const GroupNodeConfig = window.comfyAPI.groupNode.GroupNodeConfig;
export const GroupNodeHandler = window.comfyAPI.groupNode.GroupNodeHandler; export const GroupNodeHandler = window.comfyAPI.groupNode.GroupNodeHandler;

View File

@@ -1,2 +1,2 @@
// Shim for extensions\core\groupNodeManage.ts // Shim for extensions/core/groupNodeManage.ts
export const ManageGroupDialog = window.comfyAPI.groupNodeManage.ManageGroupDialog; export const ManageGroupDialog = window.comfyAPI.groupNodeManage.ManageGroupDialog;

View File

@@ -1,4 +1,4 @@
// Shim for extensions\core\widgetInputs.ts // Shim for extensions/core/widgetInputs.ts
export const getWidgetConfig = window.comfyAPI.widgetInputs.getWidgetConfig; export const getWidgetConfig = window.comfyAPI.widgetInputs.getWidgetConfig;
export const setWidgetConfig = window.comfyAPI.widgetInputs.setWidgetConfig; export const setWidgetConfig = window.comfyAPI.widgetInputs.setWidgetConfig;
export const mergeIfValid = window.comfyAPI.widgetInputs.mergeIfValid; export const mergeIfValid = window.comfyAPI.widgetInputs.mergeIfValid;

100
web/index.html vendored
View File

@@ -1,50 +1,50 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>ComfyUI</title> <title>ComfyUI</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<!-- Browser Test Fonts --> <!-- Browser Test Fonts -->
<!-- <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"> <!-- <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<style> <style>
* { * {
font-family: 'Roboto Mono', 'Noto Color Emoji'; font-family: 'Roboto Mono', 'Noto Color Emoji';
} }
</style> --> </style> -->
<link rel="stylesheet" type="text/css" href="user.css" />
<link rel="stylesheet" type="text/css" href="materialdesignicons.min.css" />
<script type="module" crossorigin src="./assets/index-Dfv2aLsq.js"></script>
<link rel="stylesheet" type="text/css" href="user.css" /> <link rel="stylesheet" crossorigin href="./assets/index-W4jP-SrU.css">
<link rel="stylesheet" type="text/css" href="materialdesignicons.min.css" /> </head>
<script type="module" crossorigin src="./assets/index-CI3N807S.js"></script> <body class="litegraph">
<link rel="stylesheet" crossorigin href="./assets/index-_5czGnTA.css"> <div id="vue-app"></div>
</head> <div id="comfy-user-selection" class="comfy-user-selection" style="display: none;">
<body class="litegraph"> <main class="comfy-user-selection-inner">
<div id="vue-app"></div> <h1>ComfyUI</h1>
<div id="comfy-user-selection" class="comfy-user-selection" style="display: none;"> <form>
<main class="comfy-user-selection-inner"> <section>
<h1>ComfyUI</h1> <label>New user:
<form> <input placeholder="Enter a username" />
<section> </label>
<label>New user: </section>
<input placeholder="Enter a username" /> <div class="comfy-user-existing">
</label> <span class="or-separator">OR</span>
</section> <section>
<div class="comfy-user-existing"> <label>
<span class="or-separator">OR</span> Existing user:
<section> <select>
<label> <option hidden disabled selected value> Select a user </option>
Existing user: </select>
<select> </label>
<option hidden disabled selected value> Select a user </option> </section>
</select> </div>
</label> <footer>
</section> <span class="comfy-user-error">&nbsp;</span>
</div> <button class="comfy-btn comfy-user-button-next">Next</button>
<footer> </footer>
<span class="comfy-user-error">&nbsp;</span> </form>
<button class="comfy-btn comfy-user-button-next">Next</button> </main>
</footer> </div>
</form> </body>
</main> </html>

2
web/scripts/api.js vendored
View File

@@ -1,2 +1,2 @@
// Shim for scripts\api.ts // Shim for scripts/api.ts
export const api = window.comfyAPI.api.api; export const api = window.comfyAPI.api.api;

2
web/scripts/app.js vendored
View File

@@ -1,4 +1,4 @@
// Shim for scripts\app.ts // Shim for scripts/app.ts
export const ANIM_PREVIEW_WIDGET = window.comfyAPI.app.ANIM_PREVIEW_WIDGET; export const ANIM_PREVIEW_WIDGET = window.comfyAPI.app.ANIM_PREVIEW_WIDGET;
export const ComfyApp = window.comfyAPI.app.ComfyApp; export const ComfyApp = window.comfyAPI.app.ComfyApp;
export const app = window.comfyAPI.app.app; export const app = window.comfyAPI.app.app;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\changeTracker.ts // Shim for scripts/changeTracker.ts
export const ChangeTracker = window.comfyAPI.changeTracker.ChangeTracker; export const ChangeTracker = window.comfyAPI.changeTracker.ChangeTracker;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\defaultGraph.ts // Shim for scripts/defaultGraph.ts
export const defaultGraph = window.comfyAPI.defaultGraph.defaultGraph; export const defaultGraph = window.comfyAPI.defaultGraph.defaultGraph;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\domWidget.ts // Shim for scripts/domWidget.ts
export const addDomClippingSetting = window.comfyAPI.domWidget.addDomClippingSetting; export const addDomClippingSetting = window.comfyAPI.domWidget.addDomClippingSetting;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\logging.ts // Shim for scripts/logging.ts
export const ComfyLogging = window.comfyAPI.logging.ComfyLogging; export const ComfyLogging = window.comfyAPI.logging.ComfyLogging;

View File

@@ -1,3 +1,3 @@
// Shim for scripts\metadata\flac.ts // Shim for scripts/metadata/flac.ts
export const getFromFlacBuffer = window.comfyAPI.flac.getFromFlacBuffer; export const getFromFlacBuffer = window.comfyAPI.flac.getFromFlacBuffer;
export const getFromFlacFile = window.comfyAPI.flac.getFromFlacFile; export const getFromFlacFile = window.comfyAPI.flac.getFromFlacFile;

View File

@@ -1,3 +1,3 @@
// Shim for scripts\metadata\png.ts // Shim for scripts/metadata/png.ts
export const getFromPngBuffer = window.comfyAPI.png.getFromPngBuffer; export const getFromPngBuffer = window.comfyAPI.png.getFromPngBuffer;
export const getFromPngFile = window.comfyAPI.png.getFromPngFile; export const getFromPngFile = window.comfyAPI.png.getFromPngFile;

View File

@@ -1,4 +1,4 @@
// Shim for scripts\pnginfo.ts // Shim for scripts/pnginfo.ts
export const getPngMetadata = window.comfyAPI.pnginfo.getPngMetadata; export const getPngMetadata = window.comfyAPI.pnginfo.getPngMetadata;
export const getFlacMetadata = window.comfyAPI.pnginfo.getFlacMetadata; export const getFlacMetadata = window.comfyAPI.pnginfo.getFlacMetadata;
export const getWebpMetadata = window.comfyAPI.pnginfo.getWebpMetadata; export const getWebpMetadata = window.comfyAPI.pnginfo.getWebpMetadata;

2
web/scripts/ui.js vendored
View File

@@ -1,4 +1,4 @@
// Shim for scripts\ui.ts // Shim for scripts/ui.ts
export const ComfyDialog = window.comfyAPI.ui.ComfyDialog; export const ComfyDialog = window.comfyAPI.ui.ComfyDialog;
export const $el = window.comfyAPI.ui.$el; export const $el = window.comfyAPI.ui.$el;
export const ComfyUI = window.comfyAPI.ui.ComfyUI; export const ComfyUI = window.comfyAPI.ui.ComfyUI;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\components\asyncDialog.ts // Shim for scripts/ui/components/asyncDialog.ts
export const ComfyAsyncDialog = window.comfyAPI.asyncDialog.ComfyAsyncDialog; export const ComfyAsyncDialog = window.comfyAPI.asyncDialog.ComfyAsyncDialog;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\components\button.ts // Shim for scripts/ui/components/button.ts
export const ComfyButton = window.comfyAPI.button.ComfyButton; export const ComfyButton = window.comfyAPI.button.ComfyButton;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\components\buttonGroup.ts // Shim for scripts/ui/components/buttonGroup.ts
export const ComfyButtonGroup = window.comfyAPI.buttonGroup.ComfyButtonGroup; export const ComfyButtonGroup = window.comfyAPI.buttonGroup.ComfyButtonGroup;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\components\popup.ts // Shim for scripts/ui/components/popup.ts
export const ComfyPopup = window.comfyAPI.popup.ComfyPopup; export const ComfyPopup = window.comfyAPI.popup.ComfyPopup;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\components\splitButton.ts // Shim for scripts/ui/components/splitButton.ts
export const ComfySplitButton = window.comfyAPI.splitButton.ComfySplitButton; export const ComfySplitButton = window.comfyAPI.splitButton.ComfySplitButton;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\dialog.ts // Shim for scripts/ui/dialog.ts
export const ComfyDialog = window.comfyAPI.dialog.ComfyDialog; export const ComfyDialog = window.comfyAPI.dialog.ComfyDialog;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\draggableList.ts // Shim for scripts/ui/draggableList.ts
export const DraggableList = window.comfyAPI.draggableList.DraggableList; export const DraggableList = window.comfyAPI.draggableList.DraggableList;

View File

@@ -1,3 +1,3 @@
// Shim for scripts\ui\imagePreview.ts // Shim for scripts/ui/imagePreview.ts
export const calculateImageGrid = window.comfyAPI.imagePreview.calculateImageGrid; export const calculateImageGrid = window.comfyAPI.imagePreview.calculateImageGrid;
export const createImageHost = window.comfyAPI.imagePreview.createImageHost; export const createImageHost = window.comfyAPI.imagePreview.createImageHost;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\menu\index.ts // Shim for scripts/ui/menu/index.ts
export const ComfyAppMenu = window.comfyAPI.index.ComfyAppMenu; export const ComfyAppMenu = window.comfyAPI.index.ComfyAppMenu;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\menu\interruptButton.ts // Shim for scripts/ui/menu/interruptButton.ts
export const getInterruptButton = window.comfyAPI.interruptButton.getInterruptButton; export const getInterruptButton = window.comfyAPI.interruptButton.getInterruptButton;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\menu\queueButton.ts // Shim for scripts/ui/menu/queueButton.ts
export const ComfyQueueButton = window.comfyAPI.queueButton.ComfyQueueButton; export const ComfyQueueButton = window.comfyAPI.queueButton.ComfyQueueButton;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\menu\queueOptions.ts // Shim for scripts/ui/menu/queueOptions.ts
export const ComfyQueueOptions = window.comfyAPI.queueOptions.ComfyQueueOptions; export const ComfyQueueOptions = window.comfyAPI.queueOptions.ComfyQueueOptions;

View File

@@ -1,3 +1,3 @@
// Shim for scripts\ui\menu\workflows.ts // Shim for scripts/ui/menu/workflows.ts
export const ComfyWorkflowsMenu = window.comfyAPI.workflows.ComfyWorkflowsMenu; export const ComfyWorkflowsMenu = window.comfyAPI.workflows.ComfyWorkflowsMenu;
export const ComfyWorkflowsContent = window.comfyAPI.workflows.ComfyWorkflowsContent; export const ComfyWorkflowsContent = window.comfyAPI.workflows.ComfyWorkflowsContent;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\settings.ts // Shim for scripts/ui/settings.ts
export const ComfySettingsDialog = window.comfyAPI.settings.ComfySettingsDialog; export const ComfySettingsDialog = window.comfyAPI.settings.ComfySettingsDialog;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\spinner.ts // Shim for scripts/ui/spinner.ts
export const createSpinner = window.comfyAPI.spinner.createSpinner; export const createSpinner = window.comfyAPI.spinner.createSpinner;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\toggleSwitch.ts // Shim for scripts/ui/toggleSwitch.ts
export const toggleSwitch = window.comfyAPI.toggleSwitch.toggleSwitch; export const toggleSwitch = window.comfyAPI.toggleSwitch.toggleSwitch;

View File

@@ -1,2 +1,2 @@
// Shim for scripts\ui\userSelection.ts // Shim for scripts/ui/userSelection.ts
export const UserSelectionScreen = window.comfyAPI.userSelection.UserSelectionScreen; export const UserSelectionScreen = window.comfyAPI.userSelection.UserSelectionScreen;

View File

@@ -1,3 +1,3 @@
// Shim for scripts\ui\utils.ts // Shim for scripts/ui/utils.ts
export const applyClasses = window.comfyAPI.utils.applyClasses; export const applyClasses = window.comfyAPI.utils.applyClasses;
export const toggleElement = window.comfyAPI.utils.toggleElement; export const toggleElement = window.comfyAPI.utils.toggleElement;

View File

@@ -1,4 +1,4 @@
// Shim for scripts\utils.ts // Shim for scripts/utils.ts
export const clone = window.comfyAPI.utils.clone; export const clone = window.comfyAPI.utils.clone;
export const applyTextReplacements = window.comfyAPI.utils.applyTextReplacements; export const applyTextReplacements = window.comfyAPI.utils.applyTextReplacements;
export const addStylesheet = window.comfyAPI.utils.addStylesheet; export const addStylesheet = window.comfyAPI.utils.addStylesheet;

View File

@@ -1,4 +1,4 @@
// Shim for scripts\widgets.ts // Shim for scripts/widgets.ts
export const updateControlWidgetLabel = window.comfyAPI.widgets.updateControlWidgetLabel; export const updateControlWidgetLabel = window.comfyAPI.widgets.updateControlWidgetLabel;
export const addValueControlWidget = window.comfyAPI.widgets.addValueControlWidget; export const addValueControlWidget = window.comfyAPI.widgets.addValueControlWidget;
export const addValueControlWidgets = window.comfyAPI.widgets.addValueControlWidgets; export const addValueControlWidgets = window.comfyAPI.widgets.addValueControlWidgets;

View File

@@ -1,4 +1,4 @@
// Shim for scripts\workflows.ts // Shim for scripts/workflows.ts
export const trimJsonExt = window.comfyAPI.workflows.trimJsonExt; export const trimJsonExt = window.comfyAPI.workflows.trimJsonExt;
export const ComfyWorkflowManager = window.comfyAPI.workflows.ComfyWorkflowManager; export const ComfyWorkflowManager = window.comfyAPI.workflows.ComfyWorkflowManager;
export const ComfyWorkflow = window.comfyAPI.workflows.ComfyWorkflow; export const ComfyWorkflow = window.comfyAPI.workflows.ComfyWorkflow;