Merge branch 'master' of github.com:comfyanonymous/ComfyUI

This commit is contained in:
doctorpangloss 2024-10-14 15:26:27 -07:00
commit 8512f361fe
69 changed files with 2371 additions and 330 deletions

View File

@ -161,7 +161,7 @@ def _create_parser() -> EnhancedConfigArgParser:
help="This name will be used by the frontends and workers to exchange prompt requests and replies. Progress updates will be prefixed by the queue name, followed by a '.', then the user ID") help="This name will be used by the frontends and workers to exchange prompt requests and replies. Progress updates will be prefixed by the queue name, followed by a '.', then the user ID")
parser.add_argument("--external-address", required=False, parser.add_argument("--external-address", required=False,
help="Specifies a base URL for external addresses reported by the API, such as for image paths.") help="Specifies a base URL for external addresses reported by the API, such as for image paths.")
parser.add_argument("--verbose", action="store_true", help="Enables more debug prints.") parser.add_argument("--verbose", default='INFO', const='DEBUG', nargs="?", choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], help='Set the logging level')
parser.add_argument("--disable-known-models", action="store_true", help="Disables automatic downloads of known models and prevents them from appearing in the UI.") parser.add_argument("--disable-known-models", action="store_true", help="Disables automatic downloads of known models and prevents them from appearing in the UI.")
parser.add_argument("--max-queue-size", type=int, default=65536, help="The API will reject prompt requests if the queue's size exceeds this value.") parser.add_argument("--max-queue-size", type=int, default=65536, help="The API will reject prompt requests if the queue's size exceeds this value.")
# tracing # tracing

View File

@ -103,7 +103,7 @@ class Configuration(dict):
distributed_queue_worker (bool): Workers will pull requests off the AMQP URL. distributed_queue_worker (bool): Workers will pull requests off the AMQP URL.
distributed_queue_name (str): This name will be used by the frontends and workers to exchange prompt requests and replies. Progress updates will be prefixed by the queue name, followed by a '.', then the user ID. distributed_queue_name (str): This name will be used by the frontends and workers to exchange prompt requests and replies. Progress updates will be prefixed by the queue name, followed by a '.', then the user ID.
external_address (str): Specifies a base URL for external addresses reported by the API, such as for image paths. external_address (str): Specifies a base URL for external addresses reported by the API, such as for image paths.
verbose (bool): Shows extra output for debugging purposes such as import errors of custom nodes. verbose (bool | str): Shows extra output for debugging purposes such as import errors of custom nodes; or, specifies a log level
disable_known_models (bool): Disables automatic downloads of known models and prevents them from appearing in the UI. disable_known_models (bool): Disables automatic downloads of known models and prevents them from appearing in the UI.
max_queue_size (int): The API will reject prompt requests if the queue's size exceeds this value. max_queue_size (int): The API will reject prompt requests if the queue's size exceeds this value.
otel_service_name (str): The name of the service or application that is generating telemetry data. Default: "comfyui". otel_service_name (str): The name of the service or application that is generating telemetry data. Default: "comfyui".
@ -192,6 +192,7 @@ class Configuration(dict):
self.force_channels_last: bool = False self.force_channels_last: bool = False
self.force_hf_local_dir_mode = False self.force_hf_local_dir_mode = False
self.preview_size: int = 512 self.preview_size: int = 512
self.verbose: str | bool = "INFO"
# from guill # from guill
self.cache_lru: int = 0 self.cache_lru: int = 0

View File

@ -117,8 +117,7 @@ def load_clipvision_from_sd(sd, prefix="", convert_keys=False):
keys = list(sd.keys()) keys = list(sd.keys())
for k in keys: for k in keys:
if k not in u: if k not in u:
t = sd.pop(k) sd.pop(k)
del t
return clip return clip
def load(ckpt_path): def load(ckpt_path):

View File

@ -24,7 +24,7 @@ def _base_path():
models_dir = os.path.join(get_base_path(), "models") models_dir = os.path.join(get_base_path(), "models")
folder_names_and_paths = FolderNames(models_dir) folder_names_and_paths: Final[FolderNames] = FolderNames(models_dir)
folder_names_and_paths["checkpoints"] = FolderPathsTuple("checkpoints", [os.path.join(models_dir, "checkpoints")], set(supported_pt_extensions)) folder_names_and_paths["checkpoints"] = FolderPathsTuple("checkpoints", [os.path.join(models_dir, "checkpoints")], set(supported_pt_extensions))
folder_names_and_paths["configs"] = FolderPathsTuple("configs", [os.path.join(models_dir, "configs"), get_package_as_path("comfy.configs")], {".yaml"}) folder_names_and_paths["configs"] = FolderPathsTuple("configs", [os.path.join(models_dir, "configs"), get_package_as_path("comfy.configs")], {".yaml"})
folder_names_and_paths["loras"] = FolderPathsTuple("loras", [os.path.join(models_dir, "loras")], set(supported_pt_extensions)) folder_names_and_paths["loras"] = FolderPathsTuple("loras", [os.path.join(models_dir, "loras")], set(supported_pt_extensions))
@ -239,8 +239,12 @@ def recursive_search(directory, excluded_dir_names=None):
for dirpath, subdirs, filenames in os.walk(directory, followlinks=True, topdown=True): for dirpath, subdirs, filenames in os.walk(directory, followlinks=True, topdown=True):
subdirs[:] = [d for d in subdirs if d not in excluded_dir_names] subdirs[:] = [d for d in subdirs if d not in excluded_dir_names]
for file_name in filenames: for file_name in filenames:
relative_path = os.path.relpath(os.path.join(dirpath, file_name), directory) try:
result.append(relative_path) relative_path = os.path.relpath(os.path.join(dirpath, file_name), directory)
result.append(relative_path)
except:
logging.warning(f"Warning: Unable to access {file_name}. Skipping this file.")
continue
for d in subdirs: for d in subdirs:
path = os.path.join(dirpath, d) path = os.path.join(dirpath, d)

View File

@ -111,9 +111,12 @@ def _create_tracer():
def _configure_logging(): def _configure_logging():
logging_level = logging.INFO if isinstance(args.verbose, str):
if args.verbose: logging_level = args.verbose
elif args.verbose == True:
logging_level = logging.DEBUG logging_level = logging.DEBUG
else:
logging_level = logging.ERROR
logging.basicConfig(format="%(message)s", level=logging_level) logging.basicConfig(format="%(message)s", level=logging_level)

View File

@ -700,6 +700,7 @@ class PromptServer(ExecutorToClientProgress):
# Internal route. Should not be depended upon and is subject to change at any time. # Internal route. Should not be depended upon and is subject to change at any time.
# TODO(robinhuang): Move to internal route table class once we refactor PromptServer to pass around Websocket. # TODO(robinhuang): Move to internal route table class once we refactor PromptServer to pass around Websocket.
# NOTE: This was an experiment and WILL BE REMOVED
@routes.post("/internal/models/download") @routes.post("/internal/models/download")
async def download_handler(request): async def download_handler(request):
async def report_progress(filename: str, status: DownloadModelStatus): async def report_progress(filename: str, status: DownloadModelStatus):
@ -710,10 +711,11 @@ class PromptServer(ExecutorToClientProgress):
data = await request.json() data = await request.json()
url = data.get('url') url = data.get('url')
model_directory = data.get('model_directory') model_directory = data.get('model_directory')
folder_path = data.get('folder_path')
model_filename = data.get('model_filename') model_filename = data.get('model_filename')
progress_interval = data.get('progress_interval', 1.0) # In seconds, how often to report download progress. progress_interval = data.get('progress_interval', 1.0) # In seconds, how often to report download progress.
if not url or not model_directory or not model_filename: if not url or not model_directory or not model_filename or not folder_path:
return web.json_response({"status": "error", "message": "Missing URL or folder path or filename"}, status=400) return web.json_response({"status": "error", "message": "Missing URL or folder path or filename"}, status=400)
session = self.client_session session = self.client_session
@ -721,7 +723,7 @@ class PromptServer(ExecutorToClientProgress):
logging.error("Client session is not initialized") logging.error("Client session is not initialized")
return web.Response(status=500) return web.Response(status=500)
task = asyncio.create_task(download_model(lambda url: session.get(url), model_filename, url, model_directory, report_progress, progress_interval)) task = asyncio.create_task(download_model(lambda url: session.get(url), model_filename, url, model_directory, folder_path, report_progress, progress_interval))
await task await task
return web.json_response(task.result().to_dict()) return web.json_response(task.result().to_dict())

View File

@ -2,9 +2,10 @@ from __future__ import annotations
import dataclasses import dataclasses
import os import os
import typing
from typing import List, Set, Any, Iterator, Sequence, Dict, NamedTuple from typing import List, Set, Any, Iterator, Sequence, Dict, NamedTuple
supported_pt_extensions = frozenset(['.ckpt', '.pt', '.bin', '.pth', '.safetensors', '.pkl', '.sft']) supported_pt_extensions = frozenset(['.ckpt', '.pt', '.bin', '.pth', '.safetensors', '.pkl', '.sft', ".index.json"])
extension_mimetypes_cache = { extension_mimetypes_cache = {
"webp": "image", "webp": "image",
} }
@ -37,6 +38,31 @@ class FolderPathsTuple:
class FolderNames: class FolderNames:
@staticmethod
def from_dict(folder_paths_dict: dict[str, tuple[typing.Sequence[str], Sequence[str]]] = None) -> FolderNames:
"""
Turns a dictionary of
{
"folder_name": (["folder/paths"], {".supported.extensions"})
}
into a FolderNames object
:param folder_paths_dict: A dictionary
:return: A FolderNames object
"""
if folder_paths_dict is None:
return FolderNames(os.getcwd())
fn = FolderNames(os.getcwd())
for folder_name, (paths, extensions) in folder_paths_dict.items():
paths_tuple = FolderPathsTuple(folder_name=folder_name, paths=list(paths), supported_extensions=set(extensions))
if folder_name in fn:
fn[folder_name] += paths_tuple
else:
fn[folder_name] = paths_tuple
return fn
def __init__(self, default_new_folder_path: str): def __init__(self, default_new_folder_path: str):
self.contents: Dict[str, FolderPathsTuple] = dict() self.contents: Dict[str, FolderPathsTuple] = dict()
self.default_new_folder_path = default_new_folder_path self.default_new_folder_path = default_new_folder_path
@ -66,6 +92,9 @@ class FolderNames:
def __delitem__(self, key): def __delitem__(self, key):
del self.contents[key] del self.contents[key]
def __contains__(self, item):
return item in self.contents
def items(self): def items(self):
return self.contents.items() return self.contents.items()

View File

@ -239,6 +239,7 @@ class ControlNet(ControlBase):
if len(self.extra_concat_orig) > 0: if len(self.extra_concat_orig) > 0:
to_concat = [] to_concat = []
for c in self.extra_concat_orig: for c in self.extra_concat_orig:
c = c.to(self.cond_hint.device)
c = utils.common_upscale(c, self.cond_hint.shape[3], self.cond_hint.shape[2], self.upscale_algorithm, "center") c = utils.common_upscale(c, self.cond_hint.shape[3], self.cond_hint.shape[2], self.upscale_algorithm, "center")
to_concat.append(utils.repeat_to_batch_size(c, self.cond_hint.shape[0])) to_concat.append(utils.repeat_to_batch_size(c, self.cond_hint.shape[0]))
self.cond_hint = torch.cat([self.cond_hint] + to_concat, dim=1) self.cond_hint = torch.cat([self.cond_hint] + to_concat, dim=1)

View File

@ -1,5 +1,4 @@
import torch import torch
import math
def calc_mantissa(abs_x, exponent, normal_mask, MANTISSA_BITS, EXPONENT_BIAS, generator=None): def calc_mantissa(abs_x, exponent, normal_mask, MANTISSA_BITS, EXPONENT_BIAS, generator=None):
mantissa_scaled = torch.where( mantissa_scaled = torch.where(

View File

@ -1198,3 +1198,36 @@ def sample_dpmpp_2s_ancestral_cfg_pp(model, x, sigmas, extra_args=None, callback
if sigmas[i + 1] > 0: if sigmas[i + 1] > 0:
x = x + noise_sampler(sigmas[i], sigmas[i + 1]) * s_noise * sigma_up x = x + noise_sampler(sigmas[i], sigmas[i + 1]) * s_noise * sigma_up
return x return x
@torch.no_grad()
def sample_dpmpp_2m_cfg_pp(model, x, sigmas, extra_args=None, callback=None, disable=None):
"""DPM-Solver++(2M)."""
extra_args = {} if extra_args is None else extra_args
s_in = x.new_ones([x.shape[0]])
t_fn = lambda sigma: sigma.log().neg()
old_uncond_denoised = None
uncond_denoised = None
def post_cfg_function(args):
nonlocal uncond_denoised
uncond_denoised = args["uncond_denoised"]
return args["denoised"]
model_options = extra_args.get("model_options", {}).copy()
extra_args["model_options"] = model_patcher.set_model_options_post_cfg_function(model_options, post_cfg_function, disable_cfg1_optimization=True)
for i in trange(len(sigmas) - 1, disable=disable):
denoised = model(x, sigmas[i] * s_in, **extra_args)
if callback is not None:
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised})
t, t_next = t_fn(sigmas[i]), t_fn(sigmas[i + 1])
h = t_next - t
if old_uncond_denoised is None or sigmas[i + 1] == 0:
denoised_mix = -torch.exp(-h) * uncond_denoised
else:
h_last = t - t_fn(sigmas[i - 1])
r = h_last / h
denoised_mix = -torch.exp(-h) * uncond_denoised - torch.expm1(-h) * (1 / (2 * r)) * (denoised - old_uncond_denoised)
x = denoised + denoised_mix + torch.exp(-h) * x
old_uncond_denoised = uncond_denoised
return x

View File

@ -31,11 +31,13 @@ class SDXL(LatentFormat):
def __init__(self): def __init__(self):
self.latent_rgb_factors = [ self.latent_rgb_factors = [
# R G B # R G B
[ 0.3920, 0.4054, 0.4549], [ 0.3651, 0.4232, 0.4341],
[-0.2634, -0.0196, 0.0653], [-0.2533, -0.0042, 0.1068],
[ 0.0568, 0.1687, -0.0755], [ 0.1076, 0.1111, -0.0362],
[-0.3112, -0.2359, -0.2076] [-0.3165, -0.2492, -0.2188]
] ]
self.latent_rgb_factors_bias = [ 0.1084, -0.0175, -0.0011]
self.taesd_decoder_name = "taesdxl_decoder" self.taesd_decoder_name = "taesdxl_decoder"
class SDXL_Playground_2_5(LatentFormat): class SDXL_Playground_2_5(LatentFormat):
@ -113,23 +115,24 @@ class SD3(LatentFormat):
self.scale_factor = 1.5305 self.scale_factor = 1.5305
self.shift_factor = 0.0609 self.shift_factor = 0.0609
self.latent_rgb_factors = [ self.latent_rgb_factors = [
[-0.0645, 0.0177, 0.1052], [-0.0922, -0.0175, 0.0749],
[ 0.0028, 0.0312, 0.0650], [ 0.0311, 0.0633, 0.0954],
[ 0.1848, 0.0762, 0.0360], [ 0.1994, 0.0927, 0.0458],
[ 0.0944, 0.0360, 0.0889], [ 0.0856, 0.0339, 0.0902],
[ 0.0897, 0.0506, -0.0364], [ 0.0587, 0.0272, -0.0496],
[-0.0020, 0.1203, 0.0284], [-0.0006, 0.1104, 0.0309],
[ 0.0855, 0.0118, 0.0283], [ 0.0978, 0.0306, 0.0427],
[-0.0539, 0.0658, 0.1047], [-0.0042, 0.1038, 0.1358],
[-0.0057, 0.0116, 0.0700], [-0.0194, 0.0020, 0.0669],
[-0.0412, 0.0281, -0.0039], [-0.0488, 0.0130, -0.0268],
[ 0.1106, 0.1171, 0.1220], [ 0.0922, 0.0988, 0.0951],
[-0.0248, 0.0682, -0.0481], [-0.0278, 0.0524, -0.0542],
[ 0.0815, 0.0846, 0.1207], [ 0.0332, 0.0456, 0.0895],
[-0.0120, -0.0055, -0.0867], [-0.0069, -0.0030, -0.0810],
[-0.0749, -0.0634, -0.0456], [-0.0596, -0.0465, -0.0293],
[-0.1418, -0.1457, -0.1259] [-0.1448, -0.1463, -0.1189]
] ]
self.latent_rgb_factors_bias = [0.2394, 0.2135, 0.1925]
self.taesd_decoder_name = "taesd3_decoder" self.taesd_decoder_name = "taesd3_decoder"
def process_in(self, latent): def process_in(self, latent):

View File

@ -109,7 +109,7 @@ class Flux(nn.Module):
raise ValueError("Didn't get guidance strength for guidance distilled model.") raise ValueError("Didn't get guidance strength for guidance distilled model.")
vec = vec + self.guidance_in(timestep_embedding(guidance, 256).to(img.dtype)) vec = vec + self.guidance_in(timestep_embedding(guidance, 256).to(img.dtype))
vec = vec + self.vector_in(y) vec = vec + self.vector_in(y[:,:self.params.vec_in_dim])
txt = self.txt_in(txt) txt = self.txt_in(txt)
ids = torch.cat((txt_ids, img_ids), dim=1) ids = torch.cat((txt_ids, img_ids), dim=1)

View File

@ -1 +1 @@
FLUX_WEIGHT_DTYPES = ["default", "fp8_e4m3fn", "fp8_e5m2"] FLUX_WEIGHT_DTYPES = ["default", "fp8_e4m3fn", "fp8_e5m2", "fp8_e4m3fn_fast"]

View File

@ -292,6 +292,7 @@ def model_lora_keys_unet(model, key_map={}):
unet_key = "diffusion_model.{}".format(diffusers_keys[k]) unet_key = "diffusion_model.{}".format(diffusers_keys[k])
key_lora = k[:-len(".weight")].replace(".", "_") key_lora = k[:-len(".weight")].replace(".", "_")
key_map["lora_unet_{}".format(key_lora)] = unet_key key_map["lora_unet_{}".format(key_lora)] = unet_key
key_map["lycoris_{}".format(key_lora)] = unet_key #simpletuner lycoris format
diffusers_lora_prefix = ["", "unet."] diffusers_lora_prefix = ["", "unet."]
for p in diffusers_lora_prefix: for p in diffusers_lora_prefix:

View File

@ -108,7 +108,7 @@ class BaseModel(torch.nn.Module):
if not unet_config.get("disable_unet_model_creation", False): if not unet_config.get("disable_unet_model_creation", False):
if model_config.custom_operations is None: if model_config.custom_operations is None:
operations = ops.pick_operations(unet_config.get("dtype", None), self.manual_cast_dtype) operations = ops.pick_operations(unet_config.get("dtype", None), self.manual_cast_dtype, fp8_optimizations=model_config.optimizations.get("fp8", False))
else: else:
operations = model_config.custom_operations operations = model_config.custom_operations
self.diffusion_model = unet_model(**unet_config, device=device, operations=operations) self.diffusion_model = unet_model(**unet_config, device=device, operations=operations)

View File

@ -1,2 +1,2 @@
# model_manager/__init__.py # model_manager/__init__.py
from .download_models import download_model, DownloadModelStatus, DownloadStatusType, create_model_path, check_file_exists, track_download_progress, validate_model_subdirectory, validate_filename from .download_models import download_model, DownloadModelStatus, DownloadStatusType, create_model_path, check_file_exists, track_download_progress, validate_filename

View File

@ -1,3 +1,4 @@
# NOTE: This was an experiment and WILL BE REMOVED
from __future__ import annotations from __future__ import annotations
import logging import logging
@ -11,7 +12,7 @@ from typing import Callable, Any, Optional, Awaitable, Dict
import aiohttp import aiohttp
from ..cmd.folder_paths import models_dir from ..cmd.folder_paths import folder_names_and_paths, get_folder_paths
class DownloadStatusType(Enum): class DownloadStatusType(Enum):
@ -46,36 +47,31 @@ class DownloadModelStatus():
async def download_model(model_download_request: Callable[[str], Awaitable[aiohttp.ClientResponse]], async def download_model(model_download_request: Callable[[str], Awaitable[aiohttp.ClientResponse]],
model_name: str, model_name: str,
model_url: str, model_url: str,
model_sub_directory: str, model_directory: str,
folder_path: str,
progress_callback: Callable[[str, DownloadModelStatus], Awaitable[Any]], progress_callback: Callable[[str, DownloadModelStatus], Awaitable[Any]],
progress_interval: float = 1.0) -> DownloadModelStatus: progress_interval: float = 1.0) -> DownloadModelStatus:
""" """
Download a model file from a given URL into the models directory. Download a model file from a given URL into the models directory.
Args: Args:
model_download_request (Callable[[str], Awaitable[aiohttp.ClientResponse]]): model_download_request (Callable[[str], Awaitable[aiohttp.ClientResponse]]):
A function that makes an HTTP request. This makes it easier to mock in unit tests. A function that makes an HTTP request. This makes it easier to mock in unit tests.
model_name (str): model_name (str):
The name of the model file to be downloaded. This will be the filename on disk. The name of the model file to be downloaded. This will be the filename on disk.
model_url (str): model_url (str):
The URL from which to download the model. The URL from which to download the model.
model_sub_directory (str): model_directory (str):
The subdirectory within the main models directory where the model The subdirectory within the main models directory where the model
should be saved (e.g., 'checkpoints', 'loras', etc.). should be saved (e.g., 'checkpoints', 'loras', etc.).
progress_callback (Callable[[str, DownloadModelStatus], Awaitable[Any]]): progress_callback (Callable[[str, DownloadModelStatus], Awaitable[Any]]):
An asynchronous function to call with progress updates. An asynchronous function to call with progress updates.
folder_path (str);
Path to which model folder should be used as the root.
Returns: Returns:
DownloadModelStatus: The result of the download operation. DownloadModelStatus: The result of the download operation.
""" """
if not validate_model_subdirectory(model_sub_directory):
return DownloadModelStatus(
DownloadStatusType.ERROR,
0,
"Invalid model subdirectory",
False
)
if not validate_filename(model_name): if not validate_filename(model_name):
return DownloadModelStatus( return DownloadModelStatus(
DownloadStatusType.ERROR, DownloadStatusType.ERROR,
@ -84,52 +80,67 @@ async def download_model(model_download_request: Callable[[str], Awaitable[aioht
False False
) )
file_path, relative_path = create_model_path(model_name, model_sub_directory, models_dir) if not model_directory in folder_names_and_paths:
existing_file = await check_file_exists(file_path, model_name, progress_callback, relative_path) return DownloadModelStatus(
DownloadStatusType.ERROR,
0,
"Invalid or unrecognized model directory. model_directory must be a known model type (eg 'checkpoints'). If you are seeing this error for a custom model type, ensure the relevant custom nodes are installed and working.",
False
)
if not folder_path in get_folder_paths(model_directory):
return DownloadModelStatus(
DownloadStatusType.ERROR,
0,
f"Invalid folder path '{folder_path}', does not match the list of known directories ({get_folder_paths(model_directory)}). If you're seeing this in the downloader UI, you may need to refresh the page.",
False
)
file_path = create_model_path(model_name, folder_path)
existing_file = await check_file_exists(file_path, model_name, progress_callback)
if existing_file: if existing_file:
return existing_file return existing_file
try: try:
logging.info(f"Downloading {model_name} from {model_url}")
status = DownloadModelStatus(DownloadStatusType.PENDING, 0, f"Starting download of {model_name}", False) status = DownloadModelStatus(DownloadStatusType.PENDING, 0, f"Starting download of {model_name}", False)
await progress_callback(relative_path, status) await progress_callback(model_name, status)
response = await model_download_request(model_url) response = await model_download_request(model_url)
if response.status != 200: if response.status != 200:
error_message = f"Failed to download {model_name}. Status code: {response.status}" error_message = f"Failed to download {model_name}. Status code: {response.status}"
logging.error(error_message) logging.error(error_message)
status = DownloadModelStatus(DownloadStatusType.ERROR, 0, error_message, False) status = DownloadModelStatus(DownloadStatusType.ERROR, 0, error_message, False)
await progress_callback(relative_path, status) await progress_callback(model_name, status)
return DownloadModelStatus(DownloadStatusType.ERROR, 0, error_message, False) return DownloadModelStatus(DownloadStatusType.ERROR, 0, error_message, False)
return await track_download_progress(response, file_path, model_name, progress_callback, relative_path, progress_interval) return await track_download_progress(response, file_path, model_name, progress_callback, progress_interval)
except Exception as e: except Exception as e:
logging.error(f"Error in downloading model: {e}") logging.error(f"Error in downloading model: {e}")
return await handle_download_error(e, model_name, progress_callback, relative_path) return await handle_download_error(e, model_name, progress_callback)
def create_model_path(model_name: str, model_directory: str, models_base_dir: str) -> tuple[str, str]: def create_model_path(model_name: str, folder_path: str) -> str:
full_model_dir = os.path.join(models_base_dir, model_directory) os.makedirs(folder_path, exist_ok=True)
os.makedirs(full_model_dir, exist_ok=True) file_path = os.path.join(folder_path, model_name)
file_path = os.path.join(full_model_dir, model_name)
# Ensure the resulting path is still within the base directory # Ensure the resulting path is still within the base directory
abs_file_path = os.path.abspath(file_path) abs_file_path = os.path.abspath(file_path)
abs_base_dir = os.path.abspath(str(models_base_dir)) abs_base_dir = os.path.abspath(folder_path)
if os.path.commonprefix([abs_file_path, abs_base_dir]) != abs_base_dir: if os.path.commonprefix([abs_file_path, abs_base_dir]) != abs_base_dir:
raise Exception(f"Invalid model directory: {model_directory}/{model_name}") raise Exception(f"Invalid model directory: {folder_path}/{model_name}")
relative_path = '/'.join([model_directory, model_name]) return file_path
return file_path, relative_path
async def check_file_exists(file_path: str, async def check_file_exists(file_path: str,
model_name: str, model_name: str,
progress_callback: Callable[[str, DownloadModelStatus], Awaitable[Any]], progress_callback: Callable[[str, DownloadModelStatus], Awaitable[Any]]
relative_path: str) -> Optional[DownloadModelStatus]: ) -> Optional[DownloadModelStatus]:
if os.path.exists(file_path): if os.path.exists(file_path):
status = DownloadModelStatus(DownloadStatusType.COMPLETED, 100, f"{model_name} already exists", True) status = DownloadModelStatus(DownloadStatusType.COMPLETED, 100, f"{model_name} already exists", True)
await progress_callback(relative_path, status) await progress_callback(model_name, status)
return status return status
return None return None
@ -138,7 +149,7 @@ async def track_download_progress(response: aiohttp.ClientResponse,
file_path: str, file_path: str,
model_name: str, model_name: str,
progress_callback: Callable[[str, DownloadModelStatus], Awaitable[Any]], progress_callback: Callable[[str, DownloadModelStatus], Awaitable[Any]],
relative_path: str,
interval: float = 1.0) -> DownloadModelStatus: interval: float = 1.0) -> DownloadModelStatus:
try: try:
total_size = int(response.headers.get('Content-Length', 0)) total_size = int(response.headers.get('Content-Length', 0))
@ -149,10 +160,11 @@ async def track_download_progress(response: aiohttp.ClientResponse,
nonlocal last_update_time nonlocal last_update_time
progress = (downloaded / total_size) * 100 if total_size > 0 else 0 progress = (downloaded / total_size) * 100 if total_size > 0 else 0
status = DownloadModelStatus(DownloadStatusType.IN_PROGRESS, progress, f"Downloading {model_name}", False) status = DownloadModelStatus(DownloadStatusType.IN_PROGRESS, progress, f"Downloading {model_name}", False)
await progress_callback(relative_path, status) await progress_callback(model_name, status)
last_update_time = time.time() last_update_time = time.time()
with open(file_path, 'wb') as f: temp_file_path = file_path + '.tmp'
with open(temp_file_path, 'wb') as f:
chunk_iterator = response.content.iter_chunked(8192) chunk_iterator = response.content.iter_chunked(8192)
while True: while True:
try: try:
@ -165,57 +177,35 @@ async def track_download_progress(response: aiohttp.ClientResponse,
if time.time() - last_update_time >= interval: if time.time() - last_update_time >= interval:
await update_progress() await update_progress()
os.rename(temp_file_path, file_path)
await update_progress() await update_progress()
logging.info(f"Successfully downloaded {model_name}. Total downloaded: {downloaded}") logging.info(f"Successfully downloaded {model_name}. Total downloaded: {downloaded}")
status = DownloadModelStatus(DownloadStatusType.COMPLETED, 100, f"Successfully downloaded {model_name}", False) status = DownloadModelStatus(DownloadStatusType.COMPLETED, 100, f"Successfully downloaded {model_name}", False)
await progress_callback(relative_path, status) await progress_callback(model_name, status)
return status return status
except Exception as e: except Exception as e:
logging.error(f"Error in track_download_progress: {e}") logging.error(f"Error in track_download_progress: {e}")
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
return await handle_download_error(e, model_name, progress_callback, relative_path) return await handle_download_error(e, model_name, progress_callback)
async def handle_download_error(e: Exception, async def handle_download_error(e: Exception,
model_name: str, model_name: str,
progress_callback: Callable[[str, DownloadModelStatus], Any], progress_callback: Callable[[str, DownloadModelStatus], Any]
relative_path: str) -> DownloadModelStatus: ) -> DownloadModelStatus:
error_message = f"Error downloading {model_name}: {str(e)}" error_message = f"Error downloading {model_name}: {str(e)}"
status = DownloadModelStatus(DownloadStatusType.ERROR, 0, error_message, False) status = DownloadModelStatus(DownloadStatusType.ERROR, 0, error_message, False)
await progress_callback(relative_path, status) await progress_callback(model_name, status)
return status return status
def validate_model_subdirectory(model_subdirectory: str) -> bool:
"""
Validate that the model subdirectory is safe to install into.
Must not contain relative paths, nested paths or special characters
other than underscores and hyphens.
Args:
model_subdirectory (str): The subdirectory for the specific model type.
Returns:
bool: True if the subdirectory is safe, False otherwise.
"""
if len(model_subdirectory) > 50:
return False
if '..' in model_subdirectory or '/' in model_subdirectory:
return False
if not re.match(r'^[a-zA-Z0-9_-]+$', model_subdirectory):
return False
return True
def validate_filename(filename: str) -> bool: def validate_filename(filename: str) -> bool:
""" """
Validate a filename to ensure it's safe and doesn't contain any path traversal attempts. Validate a filename to ensure it's safe and doesn't contain any path traversal attempts.
Args: Args:
filename (str): The filename to validate filename (str): The filename to validate

View File

@ -173,7 +173,7 @@ total_ram = psutil.virtual_memory().total / (1024 * 1024)
logging.debug("Total VRAM {:0.0f} MB, total RAM {:0.0f} MB".format(total_vram, total_ram)) logging.debug("Total VRAM {:0.0f} MB, total RAM {:0.0f} MB".format(total_vram, total_ram))
try: try:
logging.debug("pytorch version: {}".format(torch.version.__version__)) logging.debug("pytorch version: {}".format(torch_version))
except: except:
pass pass
@ -1059,7 +1059,7 @@ def force_upcast_attention_dtype():
upcast = args.force_upcast_attention upcast = args.force_upcast_attention
try: try:
macos_version = tuple(int(n) for n in platform.mac_ver()[0].split(".")) macos_version = tuple(int(n) for n in platform.mac_ver()[0].split("."))
if (14, 5) <= macos_version < (14, 7): # black image bug on recent versions of MacOS if (14, 5) <= macos_version <= (15, 0, 1): # black image bug on recent versions of macOS
upcast = True upcast = True
except: except:
pass pass
@ -1244,6 +1244,9 @@ def should_use_bf16(device=None, model_params=0, prioritize_performance=True, ma
def supports_fp8_compute(device=None): def supports_fp8_compute(device=None):
if not is_nvidia():
return False
props = torch.cuda.get_device_properties(device) props = torch.cuda.get_device_properties(device)
if props.major >= 9: if props.major >= 9:
return True return True
@ -1251,6 +1254,14 @@ def supports_fp8_compute(device=None):
return False return False
if props.minor < 9: if props.minor < 9:
return False return False
if int(torch_version[0]) < 2 or (int(torch_version[0]) == 2 and int(torch_version[2]) < 3):
return False
if WINDOWS:
if (int(torch_version[0]) == 2 and int(torch_version[2]) < 4):
return False
return True return True

View File

@ -97,7 +97,11 @@ class LowVramPatch:
self.patches = patches self.patches = patches
def __call__(self, weight): def __call__(self, weight):
return lora.calculate_weight(self.patches[self.key], weight, self.key, intermediate_dtype=weight.dtype) intermediate_dtype = weight.dtype
if intermediate_dtype not in [torch.float32, torch.float16, torch.bfloat16]: # intermediate_dtype has to be one that is supported in math ops
intermediate_dtype = torch.float32
return stochastic_rounding(lora.calculate_weight(self.patches[self.key], weight.to(intermediate_dtype), self.key, intermediate_dtype=intermediate_dtype), weight.dtype, seed=string_to_seed(self.key))
return lora.calculate_weight(self.patches[self.key], weight, self.key, intermediate_dtype=intermediate_dtype)
class ModelPatcher(ModelManageable): class ModelPatcher(ModelManageable):

View File

@ -773,11 +773,7 @@ class ControlNetLoaderWeights:
def load_controlnet(self, control_net_name, weight_dtype): def load_controlnet(self, control_net_name, weight_dtype):
controlnet_path = get_or_download("controlnet", control_net_name, KNOWN_CONTROLNETS) controlnet_path = get_or_download("controlnet", control_net_name, KNOWN_CONTROLNETS)
model_options = {} model_options = get_model_options_for_dtype(weight_dtype)
if weight_dtype == "float8_e5m2":
model_options["dtype"] = torch.float8_e5m2
elif weight_dtype == "float8_e4m3fn":
model_options["dtype"] = torch.float8_e4m3fn
controlnet_ = controlnet.load_controlnet(controlnet_path, model_options=model_options) controlnet_ = controlnet.load_controlnet(controlnet_path, model_options=model_options)
return (controlnet_,) return (controlnet_,)
@ -885,6 +881,9 @@ def get_model_options_for_dtype(weight_dtype):
model_options["dtype"] = torch.float8_e4m3fn model_options["dtype"] = torch.float8_e4m3fn
elif weight_dtype == "fp8_e5m2": elif weight_dtype == "fp8_e5m2":
model_options["dtype"] = torch.float8_e5m2 model_options["dtype"] = torch.float8_e5m2
elif weight_dtype == "fp8_e4m3fn_fast":
model_options["dtype"] = torch.float8_e4m3fn
model_options["fp8_optimizations"] = True
return model_options return model_options

View File

@ -350,10 +350,13 @@ class fp8_ops(manual_cast):
return torch.nn.functional.linear(input, weight, bias) return torch.nn.functional.linear(input, weight, bias)
def pick_operations(weight_dtype, compute_dtype, load_device=None, disable_fast_fp8=False, inference_mode: Optional[bool] = None): def pick_operations(weight_dtype, compute_dtype, load_device=None, disable_fast_fp8=False, fp8_optimizations=False, inference_mode: Optional[bool] = None):
if inference_mode is None: if inference_mode is None:
# todo: check a context here, since this isn't being used by any callers yet # todo: check a context here, since this isn't being used by any callers yet
inference_mode = current_execution_context().inference_mode inference_mode = current_execution_context().inference_mode
if model_management.supports_fp8_compute(load_device):
if (fp8_optimizations or args.fast) and not disable_fast_fp8:
return fp8_ops
if compute_dtype is None or weight_dtype == compute_dtype: if compute_dtype is None or weight_dtype == compute_dtype:
# disable_weight_init seems to interact poorly with some other optimization code # disable_weight_init seems to interact poorly with some other optimization code
return disable_weight_init if inference_mode else skip_init return disable_weight_init if inference_mode else skip_init

View File

@ -583,7 +583,7 @@ class Sampler:
KSAMPLER_NAMES = ["euler", "euler_cfg_pp", "euler_ancestral", "euler_ancestral_cfg_pp", "heun", "heunpp2","dpm_2", "dpm_2_ancestral", KSAMPLER_NAMES = ["euler", "euler_cfg_pp", "euler_ancestral", "euler_ancestral_cfg_pp", "heun", "heunpp2","dpm_2", "dpm_2_ancestral",
"lms", "dpm_fast", "dpm_adaptive", "dpmpp_2s_ancestral", "dpmpp_2s_ancestral_cfg_pp", "dpmpp_sde", "dpmpp_sde_gpu", "lms", "dpm_fast", "dpm_adaptive", "dpmpp_2s_ancestral", "dpmpp_2s_ancestral_cfg_pp", "dpmpp_sde", "dpmpp_sde_gpu",
"dpmpp_2m", "dpmpp_2m_sde", "dpmpp_2m_sde_gpu", "dpmpp_3m_sde", "dpmpp_3m_sde_gpu", "ddpm", "lcm", "dpmpp_2m", "dpmpp_2m_cfg_pp", "dpmpp_2m_sde", "dpmpp_2m_sde_gpu", "dpmpp_3m_sde", "dpmpp_3m_sde_gpu", "ddpm", "lcm",
"ipndm", "ipndm_v", "deis"] "ipndm", "ipndm_v", "deis"]
class KSAMPLER(Sampler): class KSAMPLER(Sampler):

View File

@ -30,10 +30,10 @@ from .taesd import taesd
from .text_encoders import aura_t5 from .text_encoders import aura_t5
from .text_encoders import flux from .text_encoders import flux
from .text_encoders import hydit from .text_encoders import hydit
from .text_encoders import long_clipl
from .text_encoders import sa_t5 from .text_encoders import sa_t5
from .text_encoders import sd2_clip from .text_encoders import sd2_clip
from .text_encoders import sd3_clip from .text_encoders import sd3_clip
from .text_encoders import long_clipl
def load_lora_for_models(model, clip, _lora, strength_model, strength_clip): def load_lora_for_models(model, clip, _lora, strength_model, strength_clip):
@ -359,7 +359,7 @@ class VAE:
memory_used = self.memory_used_encode(pixel_samples.shape, self.vae_dtype) memory_used = self.memory_used_encode(pixel_samples.shape, self.vae_dtype)
model_management.load_models_gpu([self.patcher], memory_required=memory_used) model_management.load_models_gpu([self.patcher], memory_required=memory_used)
free_memory = model_management.get_free_memory(self.device) free_memory = model_management.get_free_memory(self.device)
batch_number = int(free_memory / memory_used) batch_number = int(free_memory / max(1, memory_used))
batch_number = max(1, batch_number) batch_number = max(1, batch_number)
samples = torch.empty((pixel_samples.shape[0], self.latent_channels) + tuple(map(lambda a: a // self.downscale_ratio, pixel_samples.shape[2:])), device=self.output_device) samples = torch.empty((pixel_samples.shape[0], self.latent_channels) + tuple(map(lambda a: a // self.downscale_ratio, pixel_samples.shape[2:])), device=self.output_device)
for x in range(0, pixel_samples.shape[0], batch_number): for x in range(0, pixel_samples.shape[0], batch_number):
@ -430,8 +430,49 @@ def load_clip(ckpt_paths, embedding_directory=None, clip_type=CLIPType.STABLE_DI
clip_data.append(utils.load_torch_file(p, safe_load=True)) clip_data.append(utils.load_torch_file(p, safe_load=True))
return load_text_encoder_state_dicts(clip_data, embedding_directory=embedding_directory, clip_type=clip_type, model_options=model_options, textmodel_json_config=textmodel_json_config) return load_text_encoder_state_dicts(clip_data, embedding_directory=embedding_directory, clip_type=clip_type, model_options=model_options, textmodel_json_config=textmodel_json_config)
class TEModel(Enum):
CLIP_L = 1
CLIP_H = 2
CLIP_G = 3
T5_XXL = 4
T5_XL = 5
T5_BASE = 6
def detect_te_model(sd):
if "text_model.encoder.layers.30.mlp.fc1.weight" in sd:
return TEModel.CLIP_G
if "text_model.encoder.layers.22.mlp.fc1.weight" in sd:
return TEModel.CLIP_H
if "text_model.encoder.layers.0.mlp.fc1.weight" in sd:
return TEModel.CLIP_L
if "encoder.block.23.layer.1.DenseReluDense.wi_1.weight" in sd:
weight = sd["encoder.block.23.layer.1.DenseReluDense.wi_1.weight"]
if weight.shape[-1] == 4096:
return TEModel.T5_XXL
elif weight.shape[-1] == 2048:
return TEModel.T5_XL
if "encoder.block.0.layer.0.SelfAttention.k.weight" in sd:
return TEModel.T5_BASE
return None
def t5xxl_weight_dtype(clip_data):
weight_name = "encoder.block.23.layer.1.DenseReluDense.wi_1.weight"
dtype_t5 = None
for sd in clip_data:
weight = sd.get(weight_name, None)
if weight is not None:
dtype_t5 = weight.dtype
break
return dtype_t5
def load_text_encoder_state_dicts(state_dicts=[], embedding_directory=None, clip_type=CLIPType.STABLE_DIFFUSION, model_options={}, textmodel_json_config=None): def load_text_encoder_state_dicts(state_dicts=[], embedding_directory=None, clip_type=CLIPType.STABLE_DIFFUSION, model_options={}, textmodel_json_config=None):
clip_data = state_dicts clip_data = state_dicts
class EmptyClass: class EmptyClass:
pass pass
@ -445,53 +486,52 @@ def load_text_encoder_state_dicts(state_dicts=[], embedding_directory=None, clip
clip_target = CLIPTarget() clip_target = CLIPTarget()
clip_target.params = {} clip_target.params = {}
if len(clip_data) == 1: if len(clip_data) == 1:
if "text_model.encoder.layers.30.mlp.fc1.weight" in clip_data[0]: te_model = detect_te_model(clip_data[0])
if te_model == TEModel.CLIP_G:
if clip_type == CLIPType.STABLE_CASCADE: if clip_type == CLIPType.STABLE_CASCADE:
clip_target.clip = sdxl_clip.StableCascadeClipModel clip_target.clip = sdxl_clip.StableCascadeClipModel
clip_target.tokenizer = sdxl_clip.StableCascadeTokenizer clip_target.tokenizer = sdxl_clip.StableCascadeTokenizer
elif clip_type == CLIPType.SD3:
clip_target.clip = sd3_clip.sd3_clip(clip_l=False, clip_g=True, t5=False)
clip_target.tokenizer = sd3_clip.SD3Tokenizer
else: else:
clip_target.clip = sdxl_clip.SDXLRefinerClipModel clip_target.clip = sdxl_clip.SDXLRefinerClipModel
clip_target.tokenizer = sdxl_clip.SDXLTokenizer clip_target.tokenizer = sdxl_clip.SDXLTokenizer
elif "text_model.encoder.layers.22.mlp.fc1.weight" in clip_data[0]: elif te_model == TEModel.CLIP_H:
clip_target.clip = sd2_clip.SD2ClipModel clip_target.clip = sd2_clip.SD2ClipModel
clip_target.tokenizer = sd2_clip.SD2Tokenizer clip_target.tokenizer = sd2_clip.SD2Tokenizer
elif "encoder.block.23.layer.1.DenseReluDense.wi_1.weight" in clip_data[0]: elif te_model == TEModel.T5_XXL:
weight = clip_data[0]["encoder.block.23.layer.1.DenseReluDense.wi_1.weight"] clip_target.clip = sd3_clip.sd3_clip(clip_l=False, clip_g=False, t5=True, dtype_t5=t5xxl_weight_dtype(clip_data))
dtype_t5 = weight.dtype clip_target.tokenizer = sd3_clip.SD3Tokenizer
if weight.shape[-1] == 4096: elif te_model == TEModel.T5_XL:
clip_target.clip = sd3_clip.sd3_clip(clip_l=False, clip_g=False, t5=True, dtype_t5=dtype_t5) clip_target.clip = aura_t5.AuraT5Model
clip_target.tokenizer = sd3_clip.SD3Tokenizer clip_target.tokenizer = aura_t5.AuraT5Tokenizer
elif weight.shape[-1] == 2048: elif te_model == TEModel.T5_BASE:
clip_target.clip = aura_t5.AuraT5Model
clip_target.tokenizer = aura_t5.AuraT5Tokenizer
elif "encoder.block.0.layer.0.SelfAttention.k.weight" in clip_data[0]:
clip_target.clip = sa_t5.SAT5Model clip_target.clip = sa_t5.SAT5Model
clip_target.tokenizer = sa_t5.SAT5Tokenizer clip_target.tokenizer = sa_t5.SAT5Tokenizer
else: else:
w = clip_data[0].get("text_model.embeddings.position_embedding.weight", None) if clip_type == CLIPType.SD3:
clip_target.clip = sd1_clip.SD1ClipModel clip_target.clip = sd3_clip.sd3_clip(clip_l=True, clip_g=False, t5=False)
clip_target.tokenizer = sd1_clip.SD1Tokenizer clip_target.tokenizer = sd3_clip.SD3Tokenizer
else:
clip_target.clip = sd1_clip.SD1ClipModel
clip_target.tokenizer = sd1_clip.SD1Tokenizer
elif len(clip_data) == 2: elif len(clip_data) == 2:
if clip_type == CLIPType.SD3: if clip_type == CLIPType.SD3:
clip_target.clip = sd3_clip.sd3_clip(clip_l=True, clip_g=True, t5=False) te_models = [detect_te_model(clip_data[0]), detect_te_model(clip_data[1])]
clip_target.clip = sd3_clip.sd3_clip(clip_l=TEModel.CLIP_L in te_models, clip_g=TEModel.CLIP_G in te_models, t5=TEModel.T5_XXL in te_models, dtype_t5=t5xxl_weight_dtype(clip_data))
clip_target.tokenizer = sd3_clip.SD3Tokenizer clip_target.tokenizer = sd3_clip.SD3Tokenizer
elif clip_type == CLIPType.HUNYUAN_DIT: elif clip_type == CLIPType.HUNYUAN_DIT:
clip_target.clip = hydit.HyditModel clip_target.clip = hydit.HyditModel
clip_target.tokenizer = hydit.HyditTokenizer clip_target.tokenizer = hydit.HyditTokenizer
elif clip_type == CLIPType.FLUX: elif clip_type == CLIPType.FLUX:
weight_name = "encoder.block.23.layer.1.DenseReluDense.wi_1.weight" clip_target.clip = flux.flux_clip(dtype_t5=t5xxl_weight_dtype(clip_data))
weight = clip_data[0].get(weight_name, clip_data[1].get(weight_name, None))
dtype_t5 = None
if weight is not None:
dtype_t5 = weight.dtype
clip_target.clip = flux.flux_clip(dtype_t5=dtype_t5)
clip_target.tokenizer = flux.FluxTokenizer clip_target.tokenizer = flux.FluxTokenizer
else: else:
clip_target.clip = sdxl_clip.SDXLClipModel clip_target.clip = sdxl_clip.SDXLClipModel
clip_target.tokenizer = sdxl_clip.SDXLTokenizer clip_target.tokenizer = sdxl_clip.SDXLTokenizer
elif len(clip_data) == 3: elif len(clip_data) == 3:
clip_target.clip = sd3_clip.SD3ClipModel clip_target.clip = sd3_clip.sd3_clip(dtype_t5=t5xxl_weight_dtype(clip_data))
clip_target.tokenizer = sd3_clip.SD3Tokenizer clip_target.tokenizer = sd3_clip.SD3Tokenizer
parameters = 0 parameters = 0
@ -546,6 +586,7 @@ def load_checkpoint(config_path=None, ckpt_path=None, output_vae=True, output_cl
return (model, clip, vae) return (model, clip, vae)
def load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, output_clipvision=False, embedding_directory=None, output_model=True, model_options=None, te_model_options=None): def load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, output_clipvision=False, embedding_directory=None, output_model=True, model_options=None, te_model_options=None):
if te_model_options is None: if te_model_options is None:
te_model_options = {} te_model_options = {}
@ -557,6 +598,7 @@ def load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, o
raise RuntimeError("Could not detect model type of: {}".format(ckpt_path)) raise RuntimeError("Could not detect model type of: {}".format(ckpt_path))
return out return out
def load_state_dict_guess_config(sd, output_vae=True, output_clip=True, output_clipvision=False, embedding_directory=None, output_model=True, model_options=None, te_model_options=None, ckpt_path=""): def load_state_dict_guess_config(sd, output_vae=True, output_clip=True, output_clipvision=False, embedding_directory=None, output_model=True, model_options=None, te_model_options=None, ckpt_path=""):
if te_model_options is None: if te_model_options is None:
te_model_options = {} te_model_options = {}
@ -583,7 +625,7 @@ def load_state_dict_guess_config(sd, output_vae=True, output_clip=True, output_c
unet_weight_dtype.append(weight_dtype) unet_weight_dtype.append(weight_dtype)
model_config.custom_operations = model_options.get("custom_operations", None) model_config.custom_operations = model_options.get("custom_operations", None)
unet_dtype = model_options.get("weight_dtype", None) unet_dtype = model_options.get("dtype", model_options.get("weight_dtype", None))
if unet_dtype is None: if unet_dtype is None:
unet_dtype = model_management.unet_dtype(model_params=parameters, supported_dtypes=unet_weight_dtype) unet_dtype = model_management.unet_dtype(model_params=parameters, supported_dtypes=unet_weight_dtype)
@ -597,7 +639,6 @@ def load_state_dict_guess_config(sd, output_vae=True, output_clip=True, output_c
if output_model: if output_model:
inital_load_device = model_management.unet_initial_load_device(parameters, unet_dtype) inital_load_device = model_management.unet_initial_load_device(parameters, unet_dtype)
offload_device = model_management.unet_offload_device()
model = model_config.get_model(sd, diffusion_model_prefix, device=inital_load_device) model = model_config.get_model(sd, diffusion_model_prefix, device=inital_load_device)
model.load_model_weights(sd, diffusion_model_prefix) model.load_model_weights(sd, diffusion_model_prefix)
@ -638,7 +679,7 @@ def load_state_dict_guess_config(sd, output_vae=True, output_clip=True, output_c
return (_model_patcher, clip, vae, clipvision) return (_model_patcher, clip, vae, clipvision)
def load_diffusion_model_state_dict(sd, model_options: dict = None, ckpt_path: Optional[str]=""): # load unet in diffusers or regular format def load_diffusion_model_state_dict(sd, model_options: dict = None, ckpt_path: Optional[str] = ""): # load unet in diffusers or regular format
if model_options is None: if model_options is None:
model_options = {} model_options = {}
dtype = model_options.get("dtype", None) dtype = model_options.get("dtype", None)
@ -684,6 +725,9 @@ def load_diffusion_model_state_dict(sd, model_options: dict = None, ckpt_path: O
manual_cast_dtype = model_management.unet_manual_cast(unet_dtype, load_device, model_config.supported_inference_dtypes) manual_cast_dtype = model_management.unet_manual_cast(unet_dtype, load_device, model_config.supported_inference_dtypes)
model_config.set_inference_dtype(unet_dtype, manual_cast_dtype) model_config.set_inference_dtype(unet_dtype, manual_cast_dtype)
model_config.custom_operations = model_options.get("custom_operations", model_config.custom_operations) model_config.custom_operations = model_options.get("custom_operations", model_config.custom_operations)
if model_options.get("fp8_optimizations", False):
model_config.optimizations["fp8"] = True
model = model_config.get_model(new_sd, "") model = model_config.get_model(new_sd, "")
model = model.to(offload_device) model = model.to(offload_device)
model.load_model_weights(new_sd, "") model.load_model_weights(new_sd, "")

View File

@ -100,7 +100,7 @@ class SDClipModel(torch.nn.Module, ClipTokenWeightEncoder):
"hidden" "hidden"
] ]
def __init__(self, version="openai/clip-vit-large-patch14", device="cpu", max_length=77, def __init__(self, device="cpu", max_length=77,
freeze=True, layer="last", layer_idx=None, textmodel_json_config: str | dict | None = None, dtype=None, model_class=clip_model.CLIPTextModel, freeze=True, layer="last", layer_idx=None, textmodel_json_config: str | dict | None = None, dtype=None, model_class=clip_model.CLIPTextModel,
special_tokens=None, layer_norm_hidden_state=True, enable_attention_masks=False, zero_out_masked=False, special_tokens=None, layer_norm_hidden_state=True, enable_attention_masks=False, zero_out_masked=False,
return_projected_pooled=True, return_attention_masks=False, model_options={}): # clip-vit-base-patch32 return_projected_pooled=True, return_attention_masks=False, model_options={}): # clip-vit-base-patch32

View File

@ -49,6 +49,7 @@ class BASE:
manual_cast_dtype = None manual_cast_dtype = None
custom_operations = None custom_operations = None
optimizations = {"fp8": False}
@classmethod @classmethod
def matches(s, unet_config, state_dict=None): def matches(s, unet_config, state_dict=None):
@ -71,6 +72,7 @@ class BASE:
self.unet_config = unet_config.copy() self.unet_config = unet_config.copy()
self.sampling_settings = self.sampling_settings.copy() self.sampling_settings = self.sampling_settings.copy()
self.latent_format = self.latent_format() self.latent_format = self.latent_format()
self.optimizations = self.optimizations.copy()
for x in self.unet_extra_config: for x in self.unet_extra_config:
self.unet_config[x] = self.unet_extra_config[x] self.unet_config[x] = self.unet_extra_config[x]

View File

@ -21,7 +21,7 @@ class T5XXLTokenizer(sd1_clip.SDTokenizer):
if tokenizer_data is None: if tokenizer_data is None:
tokenizer_data = dict() tokenizer_data = dict()
tokenizer_path = files.get_package_as_path("comfy.text_encoders.t5_tokenizer") tokenizer_path = files.get_package_as_path("comfy.text_encoders.t5_tokenizer")
super().__init__(tokenizer_path, pad_with_end=False, embedding_size=4096, embedding_key='t5xxl', tokenizer_class=T5TokenizerFast, has_start_token=False, pad_to_max_length=False, max_length=99999999, min_length=256) super().__init__(tokenizer_path, embedding_directory=embedding_directory, pad_with_end=False, embedding_size=4096, embedding_key='t5xxl', tokenizer_class=T5TokenizerFast, has_start_token=False, pad_to_max_length=False, max_length=99999999, min_length=256)
class FluxTokenizer: class FluxTokenizer:

View File

@ -11,7 +11,9 @@ from ..component_model import files
class T5XXLModel(sd1_clip.SDClipModel): class T5XXLModel(sd1_clip.SDClipModel):
def __init__(self, device="cpu", layer="last", layer_idx=None, dtype=None, textmodel_json_config=None, model_options={}): def __init__(self, device="cpu", layer="last", layer_idx=None, dtype=None, attention_mask=False, textmodel_json_config=None, model_options=None):
if model_options is None:
model_options = {}
textmodel_json_config = files.get_path_as_dict(textmodel_json_config, "t5_config_xxl.json", package=__package__) textmodel_json_config = files.get_path_as_dict(textmodel_json_config, "t5_config_xxl.json", package=__package__)
super().__init__(device=device, layer=layer, layer_idx=layer_idx, textmodel_json_config=textmodel_json_config, dtype=dtype, special_tokens={"end": 1, "pad": 0}, model_class=T5, model_options=model_options) super().__init__(device=device, layer=layer, layer_idx=layer_idx, textmodel_json_config=textmodel_json_config, dtype=dtype, special_tokens={"end": 1, "pad": 0}, model_class=T5, model_options=model_options)
@ -51,7 +53,7 @@ class SD3Tokenizer:
class SD3ClipModel(torch.nn.Module): class SD3ClipModel(torch.nn.Module):
def __init__(self, clip_l=True, clip_g=True, t5=True, dtype_t5=None, device="cpu", dtype=None, model_options={}): def __init__(self, clip_l=True, clip_g=True, t5=True, dtype_t5=None, t5_attention_mask=False, device="cpu", dtype=None, model_options={}):
super().__init__() super().__init__()
self.dtypes = set() self.dtypes = set()
if clip_l: if clip_l:
@ -69,7 +71,8 @@ class SD3ClipModel(torch.nn.Module):
if t5: if t5:
dtype_t5 = model_management.pick_weight_dtype(dtype_t5, dtype, device) dtype_t5 = model_management.pick_weight_dtype(dtype_t5, dtype, device)
self.t5xxl = T5XXLModel(device=device, dtype=dtype_t5, model_options=model_options) self.t5_attention_mask = t5_attention_mask
self.t5xxl = T5XXLModel(device=device, dtype=dtype_t5, model_options=model_options, attention_mask=self.t5_attention_mask)
self.dtypes.add(dtype_t5) self.dtypes.add(dtype_t5)
else: else:
self.t5xxl = None self.t5xxl = None
@ -99,6 +102,7 @@ class SD3ClipModel(torch.nn.Module):
lg_out = None lg_out = None
pooled = None pooled = None
out = None out = None
extra = {}
if len(token_weight_pairs_g) > 0 or len(token_weight_pairs_l) > 0: if len(token_weight_pairs_g) > 0 or len(token_weight_pairs_l) > 0:
if self.clip_l is not None: if self.clip_l is not None:
@ -123,7 +127,11 @@ class SD3ClipModel(torch.nn.Module):
pooled = torch.cat((l_pooled, g_pooled), dim=-1) pooled = torch.cat((l_pooled, g_pooled), dim=-1)
if self.t5xxl is not None: if self.t5xxl is not None:
t5_out, t5_pooled = self.t5xxl.encode_token_weights(token_weight_pairs_t5) t5_output = self.t5xxl.encode_token_weights(token_weight_pairs_t5)
t5_out, t5_pooled = t5_output[:2]
if self.t5_attention_mask:
extra["attention_mask"] = t5_output[2]["attention_mask"]
if lg_out is not None: if lg_out is not None:
out = torch.cat([lg_out, t5_out], dim=-2) out = torch.cat([lg_out, t5_out], dim=-2)
else: else:
@ -135,7 +143,7 @@ class SD3ClipModel(torch.nn.Module):
if pooled is None: if pooled is None:
pooled = torch.zeros((1, 768 + 1280), device=model_management.intermediate_device()) pooled = torch.zeros((1, 768 + 1280), device=model_management.intermediate_device())
return out, pooled return out, pooled, extra
def load_sd(self, sd): def load_sd(self, sd):
if "text_model.encoder.layers.30.mlp.fc1.weight" in sd: if "text_model.encoder.layers.30.mlp.fc1.weight" in sd:
@ -146,11 +154,10 @@ class SD3ClipModel(torch.nn.Module):
return self.t5xxl.load_sd(sd) return self.t5xxl.load_sd(sd)
def sd3_clip(clip_l=True, clip_g=True, t5=True, dtype_t5=None): def sd3_clip(clip_l=True, clip_g=True, t5=True, dtype_t5=None, t5_attention_mask=False):
class SD3ClipModel_(SD3ClipModel): class SD3ClipModel_(SD3ClipModel):
def __init__(self, device="cpu", dtype=None, model_options=None): def __init__(self, device="cpu", dtype=None, model_options=None):
if model_options is None: if model_options is None:
model_options = {} model_options = {}
super().__init__(clip_l=clip_l, clip_g=clip_g, t5=t5, dtype_t5=dtype_t5, device=device, dtype=dtype, model_options=model_options) super().__init__(clip_l=clip_l, clip_g=clip_g, t5=t5, dtype_t5=dtype_t5, t5_attention_mask=t5_attention_mask, device=device, dtype=dtype, model_options=model_options)
return SD3ClipModel_ return SD3ClipModel_

View File

@ -1,8 +1,8 @@
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 { d as defineComponent, bJ as useExtensionStore, u as useSettingStore, r as ref, o as onMounted, q as computed, g as openBlock, h as createElementBlock, i as createVNode, y as withCtx, z as unref, bK as script$1, A as createBaseVNode, x as createBlock, M as Fragment, N as renderList, ak as toDisplayString, an as createTextVNode, j as createCommentVNode, D as script$4 } from "./index-BX7lxWeb.js"; import { d as defineComponent, bJ as useExtensionStore, u as useSettingStore, r as ref, o as onMounted, q as computed, g as openBlock, h as createElementBlock, i as createVNode, y as withCtx, z as unref, bK as script$1, A as createBaseVNode, x as createBlock, M as Fragment, N as renderList, ak as toDisplayString, an as createTextVNode, j as createCommentVNode, D as script$4 } from "./index-CFrRuGBA.js";
import { s as script, a as script$2, b as script$3 } from "./index-DC9toz42.js"; import { s as script, a as script$2, b as script$3 } from "./index-CN90wNx3.js";
import "./index-Dwxl_tUs.js"; import "./index-CaUteDIK.js";
const _hoisted_1 = { class: "extension-panel" }; const _hoisted_1 = { class: "extension-panel" };
const _hoisted_2 = { class: "mt-4" }; const _hoisted_2 = { class: "mt-4" };
const _sfc_main = /* @__PURE__ */ defineComponent({ const _sfc_main = /* @__PURE__ */ defineComponent({
@ -100,4 +100,4 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
export { export {
_sfc_main as default _sfc_main as default
}; };
//# sourceMappingURL=ExtensionPanel-BL3a6wkg.js.map //# sourceMappingURL=ExtensionPanel-DSQ2O8Z9.js.map

View File

@ -1 +1 @@
{"version":3,"file":"ExtensionPanel-BL3a6wkg.js","sources":["../../src/components/dialog/content/setting/ExtensionPanel.vue"],"sourcesContent":["<template>\n <div class=\"extension-panel\">\n <DataTable :value=\"extensionStore.extensions\" stripedRows size=\"small\">\n <Column field=\"name\" :header=\"$t('extensionName')\" sortable></Column>\n <Column\n :pt=\"{\n bodyCell: 'flex items-center justify-end'\n }\"\n >\n <template #body=\"slotProps\">\n <ToggleSwitch\n v-model=\"editingEnabledExtensions[slotProps.data.name]\"\n @change=\"updateExtensionStatus\"\n />\n </template>\n </Column>\n </DataTable>\n <div class=\"mt-4\">\n <Message v-if=\"hasChanges\" severity=\"info\">\n <ul>\n <li v-for=\"ext in changedExtensions\" :key=\"ext.name\">\n <span>\n {{ extensionStore.isExtensionEnabled(ext.name) ? '[-]' : '[+]' }}\n </span>\n {{ ext.name }}\n </li>\n </ul>\n </Message>\n <Button\n :label=\"$t('reloadToApplyChanges')\"\n icon=\"pi pi-refresh\"\n @click=\"applyChanges\"\n :disabled=\"!hasChanges\"\n text\n fluid\n severity=\"danger\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted } from 'vue'\nimport { useExtensionStore } from '@/stores/extensionStore'\nimport { useSettingStore } from '@/stores/settingStore'\nimport DataTable from 'primevue/datatable'\nimport Column from 'primevue/column'\nimport ToggleSwitch from 'primevue/toggleswitch'\nimport Button from 'primevue/button'\nimport Message from 'primevue/message'\n\nconst extensionStore = useExtensionStore()\nconst settingStore = useSettingStore()\n\nconst editingEnabledExtensions = ref<Record<string, boolean>>({})\n\nonMounted(() => {\n extensionStore.extensions.forEach((ext) => {\n editingEnabledExtensions.value[ext.name] =\n extensionStore.isExtensionEnabled(ext.name)\n })\n})\n\nconst changedExtensions = computed(() => {\n return extensionStore.extensions.filter(\n (ext) =>\n editingEnabledExtensions.value[ext.name] !==\n extensionStore.isExtensionEnabled(ext.name)\n )\n})\n\nconst hasChanges = computed(() => {\n return changedExtensions.value.length > 0\n})\n\nconst updateExtensionStatus = () => {\n const editingDisabledExtensionNames = Object.entries(\n editingEnabledExtensions.value\n )\n .filter(([_, enabled]) => !enabled)\n .map(([name]) => name)\n\n settingStore.set('Comfy.Extension.Disabled', [\n ...extensionStore.inactiveDisabledExtensionNames,\n ...editingDisabledExtensionNames\n ])\n}\n\nconst applyChanges = () => {\n // Refresh the page to apply changes\n window.location.reload()\n}\n</script>\n"],"names":[],"mappings":";;;;;;;;;;AAmDA,UAAM,iBAAiB;AACvB,UAAM,eAAe;AAEf,UAAA,2BAA2B,IAA6B,CAAA,CAAE;AAEhE,cAAU,MAAM;AACC,qBAAA,WAAW,QAAQ,CAAC,QAAQ;AACzC,iCAAyB,MAAM,IAAI,IAAI,IACrC,eAAe,mBAAmB,IAAI,IAAI;AAAA,MAAA,CAC7C;AAAA,IAAA,CACF;AAEK,UAAA,oBAAoB,SAAS,MAAM;AACvC,aAAO,eAAe,WAAW;AAAA,QAC/B,CAAC,QACC,yBAAyB,MAAM,IAAI,IAAI,MACvC,eAAe,mBAAmB,IAAI,IAAI;AAAA,MAAA;AAAA,IAC9C,CACD;AAEK,UAAA,aAAa,SAAS,MAAM;AACzB,aAAA,kBAAkB,MAAM,SAAS;AAAA,IAAA,CACzC;AAED,UAAM,wBAAwB,6BAAM;AAClC,YAAM,gCAAgC,OAAO;AAAA,QAC3C,yBAAyB;AAAA,MAExB,EAAA,OAAO,CAAC,CAAC,GAAG,OAAO,MAAM,CAAC,OAAO,EACjC,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AAEvB,mBAAa,IAAI,4BAA4B;AAAA,QAC3C,GAAG,eAAe;AAAA,QAClB,GAAG;AAAA,MAAA,CACJ;AAAA,IAAA,GAV2B;AAa9B,UAAM,eAAe,6BAAM;AAEzB,aAAO,SAAS;IAAO,GAFJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} {"version":3,"file":"ExtensionPanel-DSQ2O8Z9.js","sources":["../../src/components/dialog/content/setting/ExtensionPanel.vue"],"sourcesContent":["<template>\n <div class=\"extension-panel\">\n <DataTable :value=\"extensionStore.extensions\" stripedRows size=\"small\">\n <Column field=\"name\" :header=\"$t('extensionName')\" sortable></Column>\n <Column\n :pt=\"{\n bodyCell: 'flex items-center justify-end'\n }\"\n >\n <template #body=\"slotProps\">\n <ToggleSwitch\n v-model=\"editingEnabledExtensions[slotProps.data.name]\"\n @change=\"updateExtensionStatus\"\n />\n </template>\n </Column>\n </DataTable>\n <div class=\"mt-4\">\n <Message v-if=\"hasChanges\" severity=\"info\">\n <ul>\n <li v-for=\"ext in changedExtensions\" :key=\"ext.name\">\n <span>\n {{ extensionStore.isExtensionEnabled(ext.name) ? '[-]' : '[+]' }}\n </span>\n {{ ext.name }}\n </li>\n </ul>\n </Message>\n <Button\n :label=\"$t('reloadToApplyChanges')\"\n icon=\"pi pi-refresh\"\n @click=\"applyChanges\"\n :disabled=\"!hasChanges\"\n text\n fluid\n severity=\"danger\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted } from 'vue'\nimport { useExtensionStore } from '@/stores/extensionStore'\nimport { useSettingStore } from '@/stores/settingStore'\nimport DataTable from 'primevue/datatable'\nimport Column from 'primevue/column'\nimport ToggleSwitch from 'primevue/toggleswitch'\nimport Button from 'primevue/button'\nimport Message from 'primevue/message'\n\nconst extensionStore = useExtensionStore()\nconst settingStore = useSettingStore()\n\nconst editingEnabledExtensions = ref<Record<string, boolean>>({})\n\nonMounted(() => {\n extensionStore.extensions.forEach((ext) => {\n editingEnabledExtensions.value[ext.name] =\n extensionStore.isExtensionEnabled(ext.name)\n })\n})\n\nconst changedExtensions = computed(() => {\n return extensionStore.extensions.filter(\n (ext) =>\n editingEnabledExtensions.value[ext.name] !==\n extensionStore.isExtensionEnabled(ext.name)\n )\n})\n\nconst hasChanges = computed(() => {\n return changedExtensions.value.length > 0\n})\n\nconst updateExtensionStatus = () => {\n const editingDisabledExtensionNames = Object.entries(\n editingEnabledExtensions.value\n )\n .filter(([_, enabled]) => !enabled)\n .map(([name]) => name)\n\n settingStore.set('Comfy.Extension.Disabled', [\n ...extensionStore.inactiveDisabledExtensionNames,\n ...editingDisabledExtensionNames\n ])\n}\n\nconst applyChanges = () => {\n // Refresh the page to apply changes\n window.location.reload()\n}\n</script>\n"],"names":[],"mappings":";;;;;;;;;;AAmDA,UAAM,iBAAiB;AACvB,UAAM,eAAe;AAEf,UAAA,2BAA2B,IAA6B,CAAA,CAAE;AAEhE,cAAU,MAAM;AACC,qBAAA,WAAW,QAAQ,CAAC,QAAQ;AACzC,iCAAyB,MAAM,IAAI,IAAI,IACrC,eAAe,mBAAmB,IAAI,IAAI;AAAA,MAAA,CAC7C;AAAA,IAAA,CACF;AAEK,UAAA,oBAAoB,SAAS,MAAM;AACvC,aAAO,eAAe,WAAW;AAAA,QAC/B,CAAC,QACC,yBAAyB,MAAM,IAAI,IAAI,MACvC,eAAe,mBAAmB,IAAI,IAAI;AAAA,MAAA;AAAA,IAC9C,CACD;AAEK,UAAA,aAAa,SAAS,MAAM;AACzB,aAAA,kBAAkB,MAAM,SAAS;AAAA,IAAA,CACzC;AAED,UAAM,wBAAwB,6BAAM;AAClC,YAAM,gCAAgC,OAAO;AAAA,QAC3C,yBAAyB;AAAA,MAExB,EAAA,OAAO,CAAC,CAAC,GAAG,OAAO,MAAM,CAAC,OAAO,EACjC,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AAEvB,mBAAa,IAAI,4BAA4B;AAAA,QAC3C,GAAG,eAAe;AAAA,QAClB,GAAG;AAAA,MAAA,CACJ;AAAA,IAAA,GAV2B;AAa9B,UAAM,eAAe,6BAAM;AAEzB,aAAO,SAAS;IAAO,GAFJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
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 { d as defineComponent, u as useSettingStore, r as ref, a as useTitleEditorStore, b as useCanvasStore, w as watch, L as LGraphGroup, c as app, e as LGraphNode, o as onMounted, f as onUnmounted, g as openBlock, h as createElementBlock, i as createVNode, E as EditableText, n as normalizeStyle, j as createCommentVNode, k as LiteGraph, _ as _export_sfc, B as BaseStyle, s as script$e, l as resolveComponent, m as mergeProps, p as renderSlot, q as computed, t as resolveDirective, v as withDirectives, x as createBlock, y as withCtx, z as unref, A as createBaseVNode, C as normalizeClass, D as script$f, F as useCommandStore, G as useDialogStore, S as SettingDialogHeader, H as SettingDialogContent, I as useWorkspaceStore, J as onBeforeUnmount, K as useKeybindingStore, M as Fragment, N as renderList, T as Teleport, O as resolveDynamicComponent, P as script$g, Q as getWidth, R as getHeight, U as getOuterWidth, V as getOuterHeight, W as getVNodeProp, X as isArray, Y as vShow, Z as isNotEmpty, $ as UniqueComponentId, a0 as ZIndex, a1 as resolveFieldData, a2 as focus, a3 as OverlayEventBus, a4 as isEmpty, a5 as addStyle, a6 as relativePosition, a7 as absolutePosition, a8 as ConnectedOverlayScrollHandler, a9 as isTouchDevice, aa as equals, ab as findLastIndex, ac as findSingle, ad as script$h, ae as script$i, af as script$j, ag as script$k, ah as script$l, ai as script$m, aj as Ripple, ak as toDisplayString, al as Transition, am as createSlots, an as createTextVNode, ao as useNodeFrequencyStore, ap as useNodeBookmarkStore, aq as highlightQuery, ar as script$n, as as formatNumberWithSuffix, at as NodeSourceType, au as useI18n, av as useNodeDefStore, aw as NodePreview, ax as NodeSearchFilter, ay as script$o, az as SearchFilterChip, aA as watchEffect, aB as toRaw, aC as LinkReleaseTriggerAction, aD as useEventListener, aE as nextTick, aF as markRaw, aG as useModelToNodeStore, aH as usePragmaticDroppable, aI as ComfyNodeDefImpl, aJ as ComfyModelDef, aK as LGraph, aL as LLink, aM as DragAndScale, aN as LGraphCanvas, aO as ContextMenu, aP as LGraphBadge, aQ as script$p, aR as script$q, aS as script$r, aT as script$s, aU as normalizeProps, aV as ToastEventBus, aW as setAttribute, aX as TransitionGroup, aY as useToast, aZ as useToastStore, a_ as useExecutionStore, a$ as useWorkflowStore, b0 as useTitle, b1 as withModifiers, b2 as script$t, b3 as resolve, b4 as script$u, b5 as script$v, b6 as isPrintableCharacter, b7 as guardReactiveProps, b8 as useMenuItemStore, b9 as script$y, ba as nestedPosition, bb as useQueueSettingsStore, bc as storeToRefs, bd as isRef, be as script$z, bf as useQueuePendingTaskCountStore, bg as useLocalStorage, bh as useDraggable, bi as watchDebounced, bj as inject, bk as useElementBounding, bl as script$A, bm as lodashExports, bn as useEventBus, bo as provide, bp as api, bq as i18n, br as useWorkflowBookmarkStore, bs as useSidebarTabStore } from "./index-BX7lxWeb.js"; import { d as defineComponent, u as useSettingStore, r as ref, a as useTitleEditorStore, b as useCanvasStore, w as watch, L as LGraphGroup, c as app, e as LGraphNode, o as onMounted, f as onUnmounted, g as openBlock, h as createElementBlock, i as createVNode, E as EditableText, n as normalizeStyle, j as createCommentVNode, k as LiteGraph, _ as _export_sfc, B as BaseStyle, s as script$e, l as resolveComponent, m as mergeProps, p as renderSlot, q as computed, t as resolveDirective, v as withDirectives, x as createBlock, y as withCtx, z as unref, A as createBaseVNode, C as normalizeClass, D as script$f, F as useCommandStore, G as useDialogStore, S as SettingDialogHeader, H as SettingDialogContent, I as useWorkspaceStore, J as onBeforeUnmount, K as useKeybindingStore, M as Fragment, N as renderList, T as Teleport, O as resolveDynamicComponent, P as script$g, Q as getWidth, R as getHeight, U as getOuterWidth, V as getOuterHeight, W as getVNodeProp, X as isArray, Y as vShow, Z as isNotEmpty, $ as UniqueComponentId, a0 as ZIndex, a1 as resolveFieldData, a2 as focus, a3 as OverlayEventBus, a4 as isEmpty, a5 as addStyle, a6 as relativePosition, a7 as absolutePosition, a8 as ConnectedOverlayScrollHandler, a9 as isTouchDevice, aa as equals, ab as findLastIndex, ac as findSingle, ad as script$h, ae as script$i, af as script$j, ag as script$k, ah as script$l, ai as script$m, aj as Ripple, ak as toDisplayString, al as Transition, am as createSlots, an as createTextVNode, ao as useNodeFrequencyStore, ap as useNodeBookmarkStore, aq as highlightQuery, ar as script$n, as as formatNumberWithSuffix, at as NodeSourceType, au as useI18n, av as useNodeDefStore, aw as NodePreview, ax as NodeSearchFilter, ay as script$o, az as SearchFilterChip, aA as watchEffect, aB as toRaw, aC as LinkReleaseTriggerAction, aD as useEventListener, aE as nextTick, aF as markRaw, aG as useModelToNodeStore, aH as usePragmaticDroppable, aI as ComfyNodeDefImpl, aJ as ComfyModelDef, aK as LGraph, aL as LLink, aM as DragAndScale, aN as LGraphCanvas, aO as ContextMenu, aP as LGraphBadge, aQ as script$p, aR as script$q, aS as script$r, aT as script$s, aU as normalizeProps, aV as ToastEventBus, aW as setAttribute, aX as TransitionGroup, aY as useToast, aZ as useToastStore, a_ as useExecutionStore, a$ as useWorkflowStore, b0 as useTitle, b1 as withModifiers, b2 as script$t, b3 as resolve, b4 as script$u, b5 as script$v, b6 as isPrintableCharacter, b7 as guardReactiveProps, b8 as useMenuItemStore, b9 as script$y, ba as nestedPosition, bb as useQueueSettingsStore, bc as storeToRefs, bd as isRef, be as script$z, bf as useQueuePendingTaskCountStore, bg as useLocalStorage, bh as useDraggable, bi as watchDebounced, bj as inject, bk as useElementBounding, bl as script$A, bm as lodashExports, bn as useEventBus, bo as provide, bp as api, bq as i18n, br as useWorkflowBookmarkStore, bs as useSidebarTabStore } from "./index-CFrRuGBA.js";
import { s as script$w, a as script$x } from "./index-Dwxl_tUs.js"; import { s as script$w, a as script$x } from "./index-CaUteDIK.js";
const _sfc_main$m = /* @__PURE__ */ defineComponent({ const _sfc_main$m = /* @__PURE__ */ defineComponent({
__name: "TitleEditor", __name: "TitleEditor",
setup(__props) { setup(__props) {
@ -7417,4 +7417,4 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
export { export {
_sfc_main as default _sfc_main as default
}; };
//# sourceMappingURL=GraphView-BzS8s24m.js.map //# sourceMappingURL=GraphView-vodtEF4p.js.map

1
comfy/web/assets/GraphView-vodtEF4p.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,8 @@
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 { d as defineComponent, q as computed, g as openBlock, h as createElementBlock, M as Fragment, N as renderList, i as createVNode, y as withCtx, an as createTextVNode, ak as toDisplayString, z as unref, ar as script, j as createCommentVNode, r as ref, bG as FilterMatchMode, K as useKeybindingStore, F as useCommandStore, aA as watchEffect, aY as useToast, t as resolveDirective, bH as SearchBox, A as createBaseVNode, D as script$2, x as createBlock, ad as script$4, b1 as withModifiers, ay as script$6, v as withDirectives, bx as KeyComboImpl, bI as KeybindingImpl, _ as _export_sfc } from "./index-BX7lxWeb.js"; import { d as defineComponent, q as computed, g as openBlock, h as createElementBlock, M as Fragment, N as renderList, i as createVNode, y as withCtx, an as createTextVNode, ak as toDisplayString, z as unref, ar as script, j as createCommentVNode, r as ref, bG as FilterMatchMode, K as useKeybindingStore, F as useCommandStore, aA as watchEffect, aY as useToast, t as resolveDirective, bH as SearchBox, A as createBaseVNode, D as script$2, x as createBlock, ad as script$4, b1 as withModifiers, ay as script$6, v as withDirectives, bx as KeyComboImpl, bI as KeybindingImpl, _ as _export_sfc } from "./index-CFrRuGBA.js";
import { s as script$1, a as script$3, b as script$5 } from "./index-DC9toz42.js"; import { s as script$1, a as script$3, b as script$5 } from "./index-CN90wNx3.js";
import "./index-Dwxl_tUs.js"; import "./index-CaUteDIK.js";
const _hoisted_1$1 = { const _hoisted_1$1 = {
key: 0, key: 0,
class: "px-2" class: "px-2"
@ -260,4 +260,4 @@ const KeybindingPanel = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "d
export { export {
KeybindingPanel as default KeybindingPanel as default
}; };
//# sourceMappingURL=KeybindingPanel-bEYceDa7.js.map //# sourceMappingURL=KeybindingPanel-Cwwh2R-I.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./KeybindingPanel-bEYceDa7.js","./index-DC9toz42.js","./index-Dwxl_tUs.js","./KeybindingPanel-BNYKhW1k.css","./ExtensionPanel-BL3a6wkg.js","./index-Dq1J85oq.js","./widgetInputs-D_n9bPiG.js","./index-DjWyclij.css","./userSelection-DTtYMe59.js","./userSelection-5XIfeQVB.css","./GraphView-BzS8s24m.js","./GraphView-DCFfls4D.css"])))=>i.map(i=>d[i]); const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./KeybindingPanel-Cwwh2R-I.js","./index-CN90wNx3.js","./index-CaUteDIK.js","./KeybindingPanel-BNYKhW1k.css","./ExtensionPanel-DSQ2O8Z9.js","./index-DTOGNau5.js","./widgetInputs-B4bHTYzE.js","./index-DjWyclij.css","./userSelection-vhU1ykfH.js","./userSelection-5XIfeQVB.css","./GraphView-vodtEF4p.js","./GraphView-DCFfls4D.css"])))=>i.map(i=>d[i]);
var __defProp2 = Object.defineProperty; var __defProp2 = Object.defineProperty;
var __name = (target, value3) => __defProp2(target, "name", { value: value3, configurable: true }); var __name = (target, value3) => __defProp2(target, "name", { value: value3, configurable: true });
(/* @__PURE__ */ __name(function polyfill() { (/* @__PURE__ */ __name(function polyfill() {
@ -13153,7 +13153,7 @@ const vue_runtime_esmBundler = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Obj
}, Symbol.toStringTag, { value: "Module" })); }, Symbol.toStringTag, { value: "Module" }));
const config = { const config = {
app_title: "ComfyUI", app_title: "ComfyUI",
app_version: "1.3.17" app_version: "1.3.19"
}; };
var isVue2 = false; var isVue2 = false;
var isVue3 = true; var isVue3 = true;
@ -29532,7 +29532,7 @@ function buildTree(items2, key) {
root: root23 root: root23
}; };
for (const item2 of items2) { for (const item2 of items2) {
const keys2 = typeof key === "string" ? item2[key] : key(item2); const keys2 = key(item2);
let parent = root23; let parent = root23;
for (let i2 = 0; i2 < keys2.length; i2++) { for (let i2 = 0; i2 < keys2.length; i2++) {
const k = keys2[i2]; const k = keys2[i2];
@ -29546,7 +29546,7 @@ function buildTree(items2, key) {
children: [] children: []
}; };
map2[id3] = node3; map2[id3] = node3;
parent.children.push(node3); parent.children?.push(node3);
} }
parent = map2[id3]; parent = map2[id3];
} }
@ -29573,7 +29573,7 @@ function sortedTree(node3) {
}; };
if (node3.children) { if (node3.children) {
const sortedChildren = [...node3.children].sort( const sortedChildren = [...node3.children].sort(
(a, b) => a.label.localeCompare(b.label) (a, b) => (a.label ?? "").localeCompare(b.label ?? "")
); );
newNode.children = []; newNode.children = [];
for (const child of sortedChildren) { for (const child of sortedChildren) {
@ -30446,8 +30446,9 @@ const useDialogStore = /* @__PURE__ */ defineStore("dialog", {
showDialog(options3) { showDialog(options3) {
this.isVisible = true; this.isVisible = true;
nextTick(() => { nextTick(() => {
this.title = options3.title; this.title = options3.title ?? "";
this.headerComponent = options3.headerComponent ? markRaw(options3.headerComponent) : null; this.headerComponent = options3.headerComponent ? markRaw(options3.headerComponent) : null;
this.component = markRaw(options3.component);
this.props = options3.props || {}; this.props = options3.props || {};
this.dialogComponentProps = options3.dialogComponentProps || {}; this.dialogComponentProps = options3.dialogComponentProps || {};
}); });
@ -43686,10 +43687,10 @@ const _sfc_main$v = /* @__PURE__ */ defineComponent({
__name: "SettingDialogContent", __name: "SettingDialogContent",
setup(__props) { setup(__props) {
const KeybindingPanel = /* @__PURE__ */ defineAsyncComponent( const KeybindingPanel = /* @__PURE__ */ defineAsyncComponent(
() => __vitePreload(() => import("./KeybindingPanel-bEYceDa7.js"), true ? __vite__mapDeps([0,1,2,3]) : void 0, import.meta.url) () => __vitePreload(() => import("./KeybindingPanel-Cwwh2R-I.js"), true ? __vite__mapDeps([0,1,2,3]) : void 0, import.meta.url)
); );
const ExtensionPanel = /* @__PURE__ */ defineAsyncComponent( const ExtensionPanel = /* @__PURE__ */ defineAsyncComponent(
() => __vitePreload(() => import("./ExtensionPanel-BL3a6wkg.js"), true ? __vite__mapDeps([4,1,2]) : void 0, import.meta.url) () => __vitePreload(() => import("./ExtensionPanel-DSQ2O8Z9.js"), true ? __vite__mapDeps([4,1,2]) : void 0, import.meta.url)
); );
const aboutPanelNode = { const aboutPanelNode = {
key: "about", key: "about",
@ -59830,7 +59831,6 @@ const messages = {
searchKeybindings: "Search Keybindings", searchKeybindings: "Search Keybindings",
noResultsFound: "No Results Found", noResultsFound: "No Results Found",
searchFailedMessage: "We couldn't find any settings matching your search. Try adjusting your search terms.", searchFailedMessage: "We couldn't find any settings matching your search. Try adjusting your search terms.",
noContent: "(No Content)",
noTasksFound: "No Tasks Found", noTasksFound: "No Tasks Found",
noTasksFoundMessage: "There are no tasks in the queue.", noTasksFoundMessage: "There are no tasks in the queue.",
newFolder: "New Folder", newFolder: "New Folder",
@ -59926,7 +59926,7 @@ const messages = {
delete: "删除", delete: "删除",
rename: "重命名", rename: "重命名",
customize: "定制", customize: "定制",
experimental: "测试", experimental: "BETA",
deprecated: "弃用", deprecated: "弃用",
loadWorkflow: "加载工作流", loadWorkflow: "加载工作流",
goToNode: "前往节点", goToNode: "前往节点",
@ -78288,13 +78288,17 @@ class ComfyModelDef {
__name(this, "ComfyModelDef"); __name(this, "ComfyModelDef");
} }
/** Proper filename of the model */ /** Proper filename of the model */
file_name = ""; file_name;
/** Normalized filename of the model, with all backslashes replaced with forward slashes */
normalized_file_name;
/** Directory containing the model, eg 'checkpoints' */ /** Directory containing the model, eg 'checkpoints' */
directory = ""; directory;
/** Simplified copy of name, used as a default title. Excludes the directory and the '.safetensors' file extension */ /** Simplified copy of name, used as a default title. Excludes the directory and the '.safetensors' file extension */
simplified_file_name = ""; simplified_file_name;
/** Key for the model, used to uniquely identify the model. */
key;
/** Title / display name of the model, sometimes same as the name but not always */ /** Title / display name of the model, sometimes same as the name but not always */
title = ""; title;
/** Metadata: architecture ID for the model, such as 'stable-diffusion-xl-v1-base' */ /** Metadata: architecture ID for the model, such as 'stable-diffusion-xl-v1-base' */
architecture_id = ""; architecture_id = "";
/** Metadata: author of the model */ /** Metadata: author of the model */
@ -78321,7 +78325,8 @@ class ComfyModelDef {
searchable = ""; searchable = "";
constructor(name, directory) { constructor(name, directory) {
this.file_name = name; this.file_name = name;
this.simplified_file_name = name.replaceAll("\\", "/").split("/").pop(); this.normalized_file_name = name.replaceAll("\\", "/");
this.simplified_file_name = this.normalized_file_name.split("/").pop() ?? "";
if (this.simplified_file_name.endsWith(".safetensors")) { if (this.simplified_file_name.endsWith(".safetensors")) {
this.simplified_file_name = this.simplified_file_name.slice( this.simplified_file_name = this.simplified_file_name.slice(
0, 0,
@ -78330,6 +78335,7 @@ class ComfyModelDef {
} }
this.title = this.simplified_file_name; this.title = this.simplified_file_name;
this.directory = directory; this.directory = directory;
this.key = `${directory}/${this.normalized_file_name}`;
this.updateSearchable(); this.updateSearchable();
} }
updateSearchable() { updateSearchable() {
@ -78423,7 +78429,7 @@ const useModelStore = /* @__PURE__ */ defineStore("modelStore", {
} }
const store = new ModelStore(folder, models); const store = new ModelStore(folder, models);
this.modelStoreMap[folder] = store; this.modelStoreMap[folder] = store;
this.isLoading[folder] = false; this.isLoading[folder] = null;
return store; return store;
}); });
this.isLoading[folder] = promise; this.isLoading[folder] = promise;
@ -80405,7 +80411,7 @@ class ComfyApp {
async (e) => { async (e) => {
if (e.detail.subType === "connectingWidgetLink") { if (e.detail.subType === "connectingWidgetLink") {
const { convertToInput } = await __vitePreload(async () => { const { convertToInput } = await __vitePreload(async () => {
const { convertToInput: convertToInput2 } = await import("./widgetInputs-D_n9bPiG.js"); const { convertToInput: convertToInput2 } = await import("./widgetInputs-B4bHTYzE.js");
return { convertToInput: convertToInput2 }; return { convertToInput: convertToInput2 };
}, true ? [] : void 0, import.meta.url); }, true ? [] : void 0, import.meta.url);
const { node: node3, link, widget } = e.detail; const { node: node3, link, widget } = e.detail;
@ -80447,7 +80453,7 @@ class ComfyApp {
useExtensionStore().loadDisabledExtensionNames(); useExtensionStore().loadDisabledExtensionNames();
const extensions = await api.getExtensions(); const extensions = await api.getExtensions();
this.logging.addEntry("Comfy.App", "debug", { Extensions: extensions }); this.logging.addEntry("Comfy.App", "debug", { Extensions: extensions });
await __vitePreload(() => import("./index-Dq1J85oq.js"), true ? __vite__mapDeps([5,6,7]) : void 0, import.meta.url); await __vitePreload(() => import("./index-DTOGNau5.js"), true ? __vite__mapDeps([5,6,7]) : void 0, import.meta.url);
await Promise.all( await Promise.all(
extensions.filter((extension) => !extension.includes("extensions/core")).map(async (ext) => { extensions.filter((extension) => !extension.includes("extensions/core")).map(async (ext) => {
try { try {
@ -80490,7 +80496,7 @@ class ComfyApp {
if (!user || !users[user]) { if (!user || !users[user]) {
if (this.vueAppReady) useWorkspaceStore().spinner = false; if (this.vueAppReady) useWorkspaceStore().spinner = false;
const { UserSelectionScreen } = await __vitePreload(async () => { const { UserSelectionScreen } = await __vitePreload(async () => {
const { UserSelectionScreen: UserSelectionScreen2 } = await import("./userSelection-DTtYMe59.js"); const { UserSelectionScreen: UserSelectionScreen2 } = await import("./userSelection-vhU1ykfH.js");
return { UserSelectionScreen: UserSelectionScreen2 }; return { UserSelectionScreen: UserSelectionScreen2 };
}, true ? __vite__mapDeps([8,9]) : void 0, import.meta.url); }, true ? __vite__mapDeps([8,9]) : void 0, import.meta.url);
this.ui.menuContainer.style.display = "none"; this.ui.menuContainer.style.display = "none";
@ -86783,7 +86789,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
if (Object.values(models.models).length) { if (Object.values(models.models).length) {
modelList.push(...Object.values(models.models)); modelList.push(...Object.values(models.models));
} else { } else {
const fakeModel = new ComfyModelDef("(No Content)", folder); const fakeModel = new ComfyModelDef("", folder);
fakeModel.is_fake_object = true; fakeModel.is_fake_object = true;
modelList.push(fakeModel); modelList.push(fakeModel);
} }
@ -86799,12 +86805,10 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
return model.searchable.includes(search2); return model.searchable.includes(search2);
}); });
} }
const tree = buildTree(modelList, (model) => { const tree = buildTree(
return [ modelList,
model.directory, (model) => model.key.split("/")
...model.file_name.replaceAll("\\", "/").split("/") );
];
});
return tree; return tree;
}); });
const renderedRoot = computed(() => { const renderedRoot = computed(() => {
@ -86813,18 +86817,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
const children = node3.children?.map(fillNodeInfo); const children = node3.children?.map(fillNodeInfo);
const model = node3.leaf && node3.data ? node3.data : null; const model = node3.leaf && node3.data ? node3.data : null;
if (model?.is_fake_object) { if (model?.is_fake_object) {
if (model.file_name === "(No Content)") { if (model.file_name === "Loading") {
return {
key: node3.key,
label: t("noContent"),
leaf: true,
data: node3.data,
getIcon: /* @__PURE__ */ __name((node22) => {
return "pi pi-file";
}, "getIcon"),
children: []
};
} else {
return { return {
key: node3.key, key: node3.key,
label: t("loading") + "...", label: t("loading") + "...",
@ -86857,9 +86850,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
if (node22.children?.length === 1) { if (node22.children?.length === 1) {
const onlyChild = node22.children[0]; const onlyChild = node22.children[0];
if (onlyChild.data?.is_fake_object) { if (onlyChild.data?.is_fake_object) {
if (onlyChild.data.file_name === "(No Content)") { if (onlyChild.data.file_name === "Loading") {
return "0";
} else if (onlyChild.data.file_name === "Loading") {
return ""; return "";
} }
} }
@ -86943,7 +86934,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
}; };
} }
}); });
const ModelLibrarySidebarTab = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-6867e466"]]); const ModelLibrarySidebarTab = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-db5d3bcc"]]);
const useModelLibrarySidebarTab = /* @__PURE__ */ __name(() => { const useModelLibrarySidebarTab = /* @__PURE__ */ __name(() => {
const { t } = useI18n(); const { t } = useI18n();
return { return {
@ -97329,7 +97320,7 @@ const router = createRouter({
children: [ children: [
{ {
path: "", path: "",
component: /* @__PURE__ */ __name(() => __vitePreload(() => import("./GraphView-BzS8s24m.js"), true ? __vite__mapDeps([10,2,11]) : void 0, import.meta.url), "component") component: /* @__PURE__ */ __name(() => __vitePreload(() => import("./GraphView-vodtEF4p.js"), true ? __vite__mapDeps([10,2,11]) : void 0, import.meta.url), "component")
} }
] ]
} }
@ -103483,4 +103474,4 @@ export {
withCtx as y, withCtx as y,
unref as z unref as z
}; };
//# sourceMappingURL=index-BX7lxWeb.js.map //# sourceMappingURL=index-CFrRuGBA.js.map

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

File diff suppressed because one or more lines are too long

View File

@ -725,7 +725,7 @@
vertical-align: top; vertical-align: top;
} }
[data-v-6867e466] .pi-fake-spacer { [data-v-db5d3bcc] .pi-fake-spacer {
height: 1px; height: 1px;
width: 16px; width: 16px;
} }

View File

@ -1,7 +1,7 @@
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 { aQ as script$s, g as openBlock, h as createElementBlock, m as mergeProps, A as createBaseVNode, B as BaseStyle, P as script$t, ak as toDisplayString, aj as Ripple, t as resolveDirective, v as withDirectives, x as createBlock, O as resolveDynamicComponent, bL as script$u, l as resolveComponent, C as normalizeClass, am as createSlots, y as withCtx, be as script$v, b4 as script$w, M as Fragment, N as renderList, an as createTextVNode, aW as setAttribute, aU as normalizeProps, p as renderSlot, j as createCommentVNode, aa as equals, aR as script$x, bM as script$y, bN as getFirstFocusableElement, a3 as OverlayEventBus, W as getVNodeProp, a1 as resolveFieldData, bO as invokeElementMethod, bP as getAttribute, bQ as getNextElementSibling, U as getOuterWidth, bR as getPreviousElementSibling, D as script$z, ag as script$A, bS as script$B, aT as script$D, Z as isNotEmpty, b1 as withModifiers, V as getOuterHeight, $ as UniqueComponentId, bT as _default, a0 as ZIndex, a2 as focus, a5 as addStyle, a7 as absolutePosition, a8 as ConnectedOverlayScrollHandler, a9 as isTouchDevice, bU as FilterOperator, af as script$E, bV as FocusTrap, i as createVNode, al as Transition, bW as withKeys, bX as getIndex, s as script$G, bY as isClickable, bZ as clearSelection, b_ as localeComparator, b$ as sort, c0 as FilterService, bG as FilterMatchMode, ac as findSingle, c1 as findIndexInList, c2 as find, c3 as exportCSV, c4 as getOffset, c5 as getHiddenElementOuterWidth, c6 as getHiddenElementOuterHeight, c7 as reorderArray, c8 as removeClass, c9 as addClass, a4 as isEmpty, ae as script$H, ah as script$I, Y as vShow } from "./index-BX7lxWeb.js"; import { aQ as script$s, g as openBlock, h as createElementBlock, m as mergeProps, A as createBaseVNode, B as BaseStyle, P as script$t, ak as toDisplayString, aj as Ripple, t as resolveDirective, v as withDirectives, x as createBlock, O as resolveDynamicComponent, bL as script$u, l as resolveComponent, C as normalizeClass, am as createSlots, y as withCtx, be as script$v, b4 as script$w, M as Fragment, N as renderList, an as createTextVNode, aW as setAttribute, aU as normalizeProps, p as renderSlot, j as createCommentVNode, aa as equals, aR as script$x, bM as script$y, bN as getFirstFocusableElement, a3 as OverlayEventBus, W as getVNodeProp, a1 as resolveFieldData, bO as invokeElementMethod, bP as getAttribute, bQ as getNextElementSibling, U as getOuterWidth, bR as getPreviousElementSibling, D as script$z, ag as script$A, bS as script$B, aT as script$D, Z as isNotEmpty, b1 as withModifiers, V as getOuterHeight, $ as UniqueComponentId, bT as _default, a0 as ZIndex, a2 as focus, a5 as addStyle, a7 as absolutePosition, a8 as ConnectedOverlayScrollHandler, a9 as isTouchDevice, bU as FilterOperator, af as script$E, bV as FocusTrap, i as createVNode, al as Transition, bW as withKeys, bX as getIndex, s as script$G, bY as isClickable, bZ as clearSelection, b_ as localeComparator, b$ as sort, c0 as FilterService, bG as FilterMatchMode, ac as findSingle, c1 as findIndexInList, c2 as find, c3 as exportCSV, c4 as getOffset, c5 as getHiddenElementOuterWidth, c6 as getHiddenElementOuterHeight, c7 as reorderArray, c8 as removeClass, c9 as addClass, a4 as isEmpty, ae as script$H, ah as script$I, Y as vShow } from "./index-CFrRuGBA.js";
import { s as script$C, a as script$F } from "./index-Dwxl_tUs.js"; import { s as script$C, a as script$F } from "./index-CaUteDIK.js";
var script$r = { var script$r = {
name: "ArrowDownIcon", name: "ArrowDownIcon",
"extends": script$s "extends": script$s
@ -8930,4 +8930,4 @@ export {
script as b, script as b,
script$2 as s script$2 as s
}; };
//# sourceMappingURL=index-DC9toz42.js.map //# sourceMappingURL=index-CN90wNx3.js.map

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 { aQ as script$2, g as openBlock, h as createElementBlock, m as mergeProps, A as createBaseVNode } from "./index-BX7lxWeb.js"; import { aQ as script$2, g as openBlock, h as createElementBlock, m as mergeProps, A as createBaseVNode } from "./index-CFrRuGBA.js";
var script$1 = { var script$1 = {
name: "BarsIcon", name: "BarsIcon",
"extends": script$2 "extends": script$2
@ -43,4 +43,4 @@ export {
script as a, script as a,
script$1 as s script$1 as s
}; };
//# sourceMappingURL=index-Dwxl_tUs.js.map //# sourceMappingURL=index-CaUteDIK.js.map

View File

@ -1 +1 @@
{"version":3,"file":"index-Dwxl_tUs.js","sources":["../../../../../node_modules/@primevue/icons/bars/index.mjs","../../../../../node_modules/@primevue/icons/plus/index.mjs"],"sourcesContent":["import BaseIcon from '@primevue/icons/baseicon';\nimport { openBlock, createElementBlock, mergeProps, createElementVNode } from 'vue';\n\nvar script = {\n name: 'BarsIcon',\n \"extends\": BaseIcon\n};\n\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n return openBlock(), createElementBlock(\"svg\", mergeProps({\n width: \"14\",\n height: \"14\",\n viewBox: \"0 0 14 14\",\n fill: \"none\",\n xmlns: \"http://www.w3.org/2000/svg\"\n }, _ctx.pti()), _cache[0] || (_cache[0] = [createElementVNode(\"path\", {\n \"fill-rule\": \"evenodd\",\n \"clip-rule\": \"evenodd\",\n d: \"M13.3226 3.6129H0.677419C0.497757 3.6129 0.325452 3.54152 0.198411 3.41448C0.0713707 3.28744 0 3.11514 0 2.93548C0 2.75581 0.0713707 2.58351 0.198411 2.45647C0.325452 2.32943 0.497757 2.25806 0.677419 2.25806H13.3226C13.5022 2.25806 13.6745 2.32943 13.8016 2.45647C13.9286 2.58351 14 2.75581 14 2.93548C14 3.11514 13.9286 3.28744 13.8016 3.41448C13.6745 3.54152 13.5022 3.6129 13.3226 3.6129ZM13.3226 7.67741H0.677419C0.497757 7.67741 0.325452 7.60604 0.198411 7.479C0.0713707 7.35196 0 7.17965 0 6.99999C0 6.82033 0.0713707 6.64802 0.198411 6.52098C0.325452 6.39394 0.497757 6.32257 0.677419 6.32257H13.3226C13.5022 6.32257 13.6745 6.39394 13.8016 6.52098C13.9286 6.64802 14 6.82033 14 6.99999C14 7.17965 13.9286 7.35196 13.8016 7.479C13.6745 7.60604 13.5022 7.67741 13.3226 7.67741ZM0.677419 11.7419H13.3226C13.5022 11.7419 13.6745 11.6706 13.8016 11.5435C13.9286 11.4165 14 11.2442 14 11.0645C14 10.8848 13.9286 10.7125 13.8016 10.5855C13.6745 10.4585 13.5022 10.3871 13.3226 10.3871H0.677419C0.497757 10.3871 0.325452 10.4585 0.198411 10.5855C0.0713707 10.7125 0 10.8848 0 11.0645C0 11.2442 0.0713707 11.4165 0.198411 11.5435C0.325452 11.6706 0.497757 11.7419 0.677419 11.7419Z\",\n fill: \"currentColor\"\n }, null, -1)]), 16);\n}\n\nscript.render = render;\n\nexport { script as default };\n//# sourceMappingURL=index.mjs.map\n","import BaseIcon from '@primevue/icons/baseicon';\nimport { openBlock, createElementBlock, mergeProps, createElementVNode } from 'vue';\n\nvar script = {\n name: 'PlusIcon',\n \"extends\": BaseIcon\n};\n\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n return openBlock(), createElementBlock(\"svg\", mergeProps({\n width: \"14\",\n height: \"14\",\n viewBox: \"0 0 14 14\",\n fill: \"none\",\n xmlns: \"http://www.w3.org/2000/svg\"\n }, _ctx.pti()), _cache[0] || (_cache[0] = [createElementVNode(\"path\", {\n d: \"M7.67742 6.32258V0.677419C7.67742 0.497757 7.60605 0.325452 7.47901 0.198411C7.35197 0.0713707 7.17966 0 7 0C6.82034 0 6.64803 0.0713707 6.52099 0.198411C6.39395 0.325452 6.32258 0.497757 6.32258 0.677419V6.32258H0.677419C0.497757 6.32258 0.325452 6.39395 0.198411 6.52099C0.0713707 6.64803 0 6.82034 0 7C0 7.17966 0.0713707 7.35197 0.198411 7.47901C0.325452 7.60605 0.497757 7.67742 0.677419 7.67742H6.32258V13.3226C6.32492 13.5015 6.39704 13.6725 6.52358 13.799C6.65012 13.9255 6.82106 13.9977 7 14C7.17966 14 7.35197 13.9286 7.47901 13.8016C7.60605 13.6745 7.67742 13.5022 7.67742 13.3226V7.67742H13.3226C13.5022 7.67742 13.6745 7.60605 13.8016 7.47901C13.9286 7.35197 14 7.17966 14 7C13.9977 6.82106 13.9255 6.65012 13.799 6.52358C13.6725 6.39704 13.5015 6.32492 13.3226 6.32258H7.67742Z\",\n fill: \"currentColor\"\n }, null, -1)]), 16);\n}\n\nscript.render = render;\n\nexport { script as default };\n//# sourceMappingURL=index.mjs.map\n"],"names":["script","BaseIcon","render","createElementVNode"],"mappings":";;;AAGG,IAACA,WAAS;AAAA,EACX,MAAM;AAAA,EACN,WAAWC;AACb;AAEA,SAASC,SAAO,MAAM,QAAQ,QAAQ,QAAQ,OAAO,UAAU;AAC7D,SAAO,UAAW,GAAE,mBAAmB,OAAO,WAAW;AAAA,IACvD,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,EACR,GAAE,KAAK,KAAK,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAACC,gBAAmB,QAAQ;AAAA,IACpE,aAAa;AAAA,IACb,aAAa;AAAA,IACb,GAAG;AAAA,IACH,MAAM;AAAA,EACP,GAAE,MAAM,EAAE,CAAC,IAAI,EAAE;AACpB;AAbSD;AAeTF,SAAO,SAASE;ACpBb,IAAC,SAAS;AAAA,EACX,MAAM;AAAA,EACN,WAAWD;AACb;AAEA,SAAS,OAAO,MAAM,QAAQ,QAAQ,QAAQ,OAAO,UAAU;AAC7D,SAAO,UAAW,GAAE,mBAAmB,OAAO,WAAW;AAAA,IACvD,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,EACR,GAAE,KAAK,KAAK,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAACE,gBAAmB,QAAQ;AAAA,IACpE,GAAG;AAAA,IACH,MAAM;AAAA,EACP,GAAE,MAAM,EAAE,CAAC,IAAI,EAAE;AACpB;AAXS;AAaT,OAAO,SAAS;","x_google_ignoreList":[0,1]} {"version":3,"file":"index-CaUteDIK.js","sources":["../../../../../node_modules/@primevue/icons/bars/index.mjs","../../../../../node_modules/@primevue/icons/plus/index.mjs"],"sourcesContent":["import BaseIcon from '@primevue/icons/baseicon';\nimport { openBlock, createElementBlock, mergeProps, createElementVNode } from 'vue';\n\nvar script = {\n name: 'BarsIcon',\n \"extends\": BaseIcon\n};\n\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n return openBlock(), createElementBlock(\"svg\", mergeProps({\n width: \"14\",\n height: \"14\",\n viewBox: \"0 0 14 14\",\n fill: \"none\",\n xmlns: \"http://www.w3.org/2000/svg\"\n }, _ctx.pti()), _cache[0] || (_cache[0] = [createElementVNode(\"path\", {\n \"fill-rule\": \"evenodd\",\n \"clip-rule\": \"evenodd\",\n d: \"M13.3226 3.6129H0.677419C0.497757 3.6129 0.325452 3.54152 0.198411 3.41448C0.0713707 3.28744 0 3.11514 0 2.93548C0 2.75581 0.0713707 2.58351 0.198411 2.45647C0.325452 2.32943 0.497757 2.25806 0.677419 2.25806H13.3226C13.5022 2.25806 13.6745 2.32943 13.8016 2.45647C13.9286 2.58351 14 2.75581 14 2.93548C14 3.11514 13.9286 3.28744 13.8016 3.41448C13.6745 3.54152 13.5022 3.6129 13.3226 3.6129ZM13.3226 7.67741H0.677419C0.497757 7.67741 0.325452 7.60604 0.198411 7.479C0.0713707 7.35196 0 7.17965 0 6.99999C0 6.82033 0.0713707 6.64802 0.198411 6.52098C0.325452 6.39394 0.497757 6.32257 0.677419 6.32257H13.3226C13.5022 6.32257 13.6745 6.39394 13.8016 6.52098C13.9286 6.64802 14 6.82033 14 6.99999C14 7.17965 13.9286 7.35196 13.8016 7.479C13.6745 7.60604 13.5022 7.67741 13.3226 7.67741ZM0.677419 11.7419H13.3226C13.5022 11.7419 13.6745 11.6706 13.8016 11.5435C13.9286 11.4165 14 11.2442 14 11.0645C14 10.8848 13.9286 10.7125 13.8016 10.5855C13.6745 10.4585 13.5022 10.3871 13.3226 10.3871H0.677419C0.497757 10.3871 0.325452 10.4585 0.198411 10.5855C0.0713707 10.7125 0 10.8848 0 11.0645C0 11.2442 0.0713707 11.4165 0.198411 11.5435C0.325452 11.6706 0.497757 11.7419 0.677419 11.7419Z\",\n fill: \"currentColor\"\n }, null, -1)]), 16);\n}\n\nscript.render = render;\n\nexport { script as default };\n//# sourceMappingURL=index.mjs.map\n","import BaseIcon from '@primevue/icons/baseicon';\nimport { openBlock, createElementBlock, mergeProps, createElementVNode } from 'vue';\n\nvar script = {\n name: 'PlusIcon',\n \"extends\": BaseIcon\n};\n\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n return openBlock(), createElementBlock(\"svg\", mergeProps({\n width: \"14\",\n height: \"14\",\n viewBox: \"0 0 14 14\",\n fill: \"none\",\n xmlns: \"http://www.w3.org/2000/svg\"\n }, _ctx.pti()), _cache[0] || (_cache[0] = [createElementVNode(\"path\", {\n d: \"M7.67742 6.32258V0.677419C7.67742 0.497757 7.60605 0.325452 7.47901 0.198411C7.35197 0.0713707 7.17966 0 7 0C6.82034 0 6.64803 0.0713707 6.52099 0.198411C6.39395 0.325452 6.32258 0.497757 6.32258 0.677419V6.32258H0.677419C0.497757 6.32258 0.325452 6.39395 0.198411 6.52099C0.0713707 6.64803 0 6.82034 0 7C0 7.17966 0.0713707 7.35197 0.198411 7.47901C0.325452 7.60605 0.497757 7.67742 0.677419 7.67742H6.32258V13.3226C6.32492 13.5015 6.39704 13.6725 6.52358 13.799C6.65012 13.9255 6.82106 13.9977 7 14C7.17966 14 7.35197 13.9286 7.47901 13.8016C7.60605 13.6745 7.67742 13.5022 7.67742 13.3226V7.67742H13.3226C13.5022 7.67742 13.6745 7.60605 13.8016 7.47901C13.9286 7.35197 14 7.17966 14 7C13.9977 6.82106 13.9255 6.65012 13.799 6.52358C13.6725 6.39704 13.5015 6.32492 13.3226 6.32258H7.67742Z\",\n fill: \"currentColor\"\n }, null, -1)]), 16);\n}\n\nscript.render = render;\n\nexport { script as default };\n//# sourceMappingURL=index.mjs.map\n"],"names":["script","BaseIcon","render","createElementVNode"],"mappings":";;;AAGG,IAACA,WAAS;AAAA,EACX,MAAM;AAAA,EACN,WAAWC;AACb;AAEA,SAASC,SAAO,MAAM,QAAQ,QAAQ,QAAQ,OAAO,UAAU;AAC7D,SAAO,UAAW,GAAE,mBAAmB,OAAO,WAAW;AAAA,IACvD,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,EACR,GAAE,KAAK,KAAK,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAACC,gBAAmB,QAAQ;AAAA,IACpE,aAAa;AAAA,IACb,aAAa;AAAA,IACb,GAAG;AAAA,IACH,MAAM;AAAA,EACP,GAAE,MAAM,EAAE,CAAC,IAAI,EAAE;AACpB;AAbSD;AAeTF,SAAO,SAASE;ACpBb,IAAC,SAAS;AAAA,EACX,MAAM;AAAA,EACN,WAAWD;AACb;AAEA,SAAS,OAAO,MAAM,QAAQ,QAAQ,QAAQ,OAAO,UAAU;AAC7D,SAAO,UAAW,GAAE,mBAAmB,OAAO,WAAW;AAAA,IACvD,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,EACR,GAAE,KAAK,KAAK,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAACE,gBAAmB,QAAQ;AAAA,IACpE,GAAG;AAAA,IACH,MAAM;AAAA,EACP,GAAE,MAAM,EAAE,CAAC,IAAI,EAAE;AACpB;AAXS;AAaT,OAAO,SAAS;","x_google_ignoreList":[0,1]}

View File

@ -1,7 +1,7 @@
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 { bt as ComfyDialog, bu as $el, bv as ComfyApp, c as app, k as LiteGraph, aN as LGraphCanvas, bw as DraggableList, aZ as useToastStore, av as useNodeDefStore, bp as api, L as LGraphGroup, bx as KeyComboImpl, K as useKeybindingStore, F as useCommandStore, e as LGraphNode, by as ComfyWidgets, bz as applyTextReplacements, at as NodeSourceType, bA as NodeBadgeMode, u as useSettingStore, q as computed, bB as getColorPalette, w as watch, bC as BadgePosition, aP as LGraphBadge, bD as _, bE as defaultColorPalette } from "./index-BX7lxWeb.js"; import { bt as ComfyDialog, bu as $el, bv as ComfyApp, c as app, k as LiteGraph, aN as LGraphCanvas, bw as DraggableList, aZ as useToastStore, av as useNodeDefStore, bp as api, L as LGraphGroup, bx as KeyComboImpl, K as useKeybindingStore, F as useCommandStore, e as LGraphNode, by as ComfyWidgets, bz as applyTextReplacements, at as NodeSourceType, bA as NodeBadgeMode, u as useSettingStore, q as computed, bB as getColorPalette, w as watch, bC as BadgePosition, aP as LGraphBadge, bD as _, bE as defaultColorPalette } from "./index-CFrRuGBA.js";
import { mergeIfValid, getWidgetConfig, setWidgetConfig } from "./widgetInputs-D_n9bPiG.js"; import { mergeIfValid, getWidgetConfig, setWidgetConfig } from "./widgetInputs-B4bHTYzE.js";
class ClipspaceDialog extends ComfyDialog { class ClipspaceDialog extends ComfyDialog {
static { static {
__name(this, "ClipspaceDialog"); __name(this, "ClipspaceDialog");
@ -4731,4 +4731,4 @@ class NodeBadgeExtension {
} }
} }
app.registerExtension(new NodeBadgeExtension()); app.registerExtension(new NodeBadgeExtension());
//# sourceMappingURL=index-Dq1J85oq.js.map //# sourceMappingURL=index-DTOGNau5.js.map

1
comfy/web/assets/index-DTOGNau5.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

@ -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 { bp as api, bu as $el } from "./index-BX7lxWeb.js"; import { bp as api, bu as $el } from "./index-CFrRuGBA.js";
function createSpinner() { function createSpinner() {
const div = document.createElement("div"); const div = document.createElement("div");
div.innerHTML = `<div class="lds-ring"><div></div><div></div><div></div><div></div></div>`; div.innerHTML = `<div class="lds-ring"><div></div><div></div><div></div><div></div></div>`;
@ -126,4 +126,4 @@ window.comfyAPI.userSelection.UserSelectionScreen = UserSelectionScreen;
export { export {
UserSelectionScreen UserSelectionScreen
}; };
//# sourceMappingURL=userSelection-DTtYMe59.js.map //# sourceMappingURL=userSelection-vhU1ykfH.js.map

1
comfy/web/assets/userSelection-vhU1ykfH.js.map generated vendored Normal file

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 { e as LGraphNode, c as app, bz as applyTextReplacements, by as ComfyWidgets, bF as addValueControlWidgets, k as LiteGraph } from "./index-BX7lxWeb.js"; import { e as LGraphNode, c as app, bz as applyTextReplacements, by as ComfyWidgets, bF as addValueControlWidgets, k as LiteGraph } from "./index-CFrRuGBA.js";
const CONVERTED_TYPE = "converted-widget"; const CONVERTED_TYPE = "converted-widget";
const VALID_TYPES = [ const VALID_TYPES = [
"STRING", "STRING",
@ -753,4 +753,4 @@ export {
mergeIfValid, mergeIfValid,
setWidgetConfig setWidgetConfig
}; };
//# sourceMappingURL=widgetInputs-D_n9bPiG.js.map //# sourceMappingURL=widgetInputs-B4bHTYzE.js.map

1
comfy/web/assets/widgetInputs-B4bHTYzE.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

View File

@ -6,8 +6,8 @@
<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">
<link rel="stylesheet" type="text/css" href="user.css" /> <link rel="stylesheet" type="text/css" href="user.css" />
<link rel="stylesheet" type="text/css" href="materialdesignicons.min.css" /> <link rel="stylesheet" type="text/css" href="materialdesignicons.min.css" />
<script type="module" crossorigin src="./assets/index-BX7lxWeb.js"></script> <script type="module" crossorigin src="./assets/index-CFrRuGBA.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-D6olYiyq.css"> <link rel="stylesheet" crossorigin href="./assets/index-CKl4cvVy.css">
</head> </head>
<body class="litegraph grid"> <body class="litegraph grid">
<div id="vue-app"></div> <div id="vue-app"></div>

View File

@ -22,15 +22,16 @@ class EmptyLatentAudio:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": {"seconds": ("FLOAT", {"default": 47.6, "min": 1.0, "max": 1000.0, "step": 0.1})}} return {"required": {"seconds": ("FLOAT", {"default": 47.6, "min": 1.0, "max": 1000.0, "step": 0.1}), },
# mark as optional to not break existing workflows
"optional": {"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}),
}}
RETURN_TYPES = ("LATENT",) RETURN_TYPES = ("LATENT",)
FUNCTION = "generate" FUNCTION = "generate"
CATEGORY = "latent/audio" CATEGORY = "latent/audio"
def generate(self, seconds): def generate(self, seconds:float, batch_size:int=1):
batch_size = 1
length = round((seconds * 44100 / 2048) / 2) * 2 length = round((seconds * 44100 / 2048) / 2) * 2
latent = torch.zeros([batch_size, 64, length], device=self.device) latent = torch.zeros([batch_size, 64, length], device=self.device)
return ({"samples": latent, "type": "audio"},) return ({"samples": latent, "type": "audio"},)

View File

@ -17,7 +17,7 @@ class PatchModelAddDownscale:
RETURN_TYPES = ("MODEL",) RETURN_TYPES = ("MODEL",)
FUNCTION = "patch" FUNCTION = "patch"
CATEGORY = "_for_testing" CATEGORY = "model_patches/unet"
def patch(self, model, block_number, downscale_factor, start_percent, end_percent, downscale_after_skip, downscale_method, upscale_method): def patch(self, model, block_number, downscale_factor, start_percent, end_percent, downscale_after_skip, downscale_method, upscale_method):
model_sampling = model.get_model_object("model_sampling") model_sampling = model.get_model_object("model_sampling")

View File

@ -116,6 +116,7 @@ class StableCascade_SuperResolutionControlnet:
RETURN_NAMES = ("controlnet_input", "stage_c", "stage_b") RETURN_NAMES = ("controlnet_input", "stage_c", "stage_b")
FUNCTION = "generate" FUNCTION = "generate"
EXPERIMENTAL = True
CATEGORY = "_for_testing/stable_cascade" CATEGORY = "_for_testing/stable_cascade"
def generate(self, image, vae): def generate(self, image, vae):

View File

@ -154,7 +154,7 @@ class TomePatchModel:
RETURN_TYPES = ("MODEL",) RETURN_TYPES = ("MODEL",)
FUNCTION = "patch" FUNCTION = "patch"
CATEGORY = "_for_testing" CATEGORY = "model_patches/unet"
def patch(self, model, ratio): def patch(self, model, ratio):
self.u = None self.u = None

View File

@ -108,7 +108,7 @@ class VideoTriangleCFGGuidance:
return (m, ) return (m, )
class ImageOnlyCheckpointSave(nodes_model_merging.CheckpointSave): class ImageOnlyCheckpointSave(nodes_model_merging.CheckpointSave):
CATEGORY = "_for_testing" CATEGORY = "advanced/model_merging"
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):

View File

@ -0,0 +1,21 @@
import torch
class TorchCompileModel:
@classmethod
def INPUT_TYPES(s):
return {"required": { "model": ("MODEL",),
}}
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
CATEGORY = "_for_testing"
EXPERIMENTAL = True
def patch(self, model):
m = model.clone()
m.add_object_patch("diffusion_model", torch.compile(model=m.get_model_object("diffusion_model")))
return (m, )
NODE_CLASS_MAPPINGS = {
"TorchCompileModel": TorchCompileModel,
}

View File

@ -7,7 +7,7 @@ import pytest
from comfy.api.components.schema.prompt import Prompt from comfy.api.components.schema.prompt import Prompt
from comfy.client.embedded_comfy_client import EmbeddedComfyClient from comfy.client.embedded_comfy_client import EmbeddedComfyClient
from comfy.model_downloader import add_known_models, KNOWN_LORAS from comfy.model_downloader import add_known_models, KNOWN_LORAS
from comfy.model_downloader_types import CivitFile from comfy.model_downloader_types import CivitFile, HuggingFile
from comfy_extras.nodes.nodes_audio import TorchAudioNotFoundError from comfy_extras.nodes.nodes_audio import TorchAudioNotFoundError
from comfy_extras.nodes.nodes_nf4 import BitsAndBytesNotFoundError from comfy_extras.nodes.nodes_nf4 import BitsAndBytesNotFoundError
from . import workflows from . import workflows
@ -22,6 +22,7 @@ async def client(tmp_path_factory) -> EmbeddedComfyClient:
def _prepare_for_workflows() -> dict[str, Traversable]: def _prepare_for_workflows() -> dict[str, Traversable]:
add_known_models("loras", KNOWN_LORAS, CivitFile(13941, 16576, "epi_noiseoffset2.safetensors")) add_known_models("loras", KNOWN_LORAS, CivitFile(13941, 16576, "epi_noiseoffset2.safetensors"))
add_known_models("checkpoints", HuggingFile("autismanon/modeldump", "cardosAnime_v20.safetensors"))
return {f.name: f for f in importlib.resources.files(workflows).iterdir() if f.is_file() and f.name.endswith(".json")} return {f.name: f for f in importlib.resources.files(workflows).iterdir() if f.is_file() and f.name.endswith(".json")}

View File

@ -1,15 +1,24 @@
import itertools import itertools
import os import os
import tempfile
from unittest.mock import AsyncMock, patch, MagicMock from unittest.mock import AsyncMock, patch, MagicMock
import aiohttp import aiohttp
import pytest import pytest
from aiohttp import ClientResponse from aiohttp import ClientResponse
from comfy.model_filemanager import download_model, validate_model_subdirectory, track_download_progress, \ from comfy.cmd import folder_paths
from comfy.component_model.folder_path_types import FolderNames, FolderPathsTuple, supported_pt_extensions
from comfy.model_filemanager import download_model, track_download_progress, \
create_model_path, check_file_exists, DownloadStatusType, DownloadModelStatus, validate_filename create_model_path, check_file_exists, DownloadStatusType, DownloadModelStatus, validate_filename
@pytest.fixture
def temp_dir():
with tempfile.TemporaryDirectory() as tmpdirname:
yield tmpdirname
class AsyncIteratorMock: class AsyncIteratorMock:
""" """
A mock class that simulates an asynchronous iterator. A mock class that simulates an asynchronous iterator.
@ -50,7 +59,7 @@ class ContentMock:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_download_model_success(): async def test_download_model_success(temp_dir):
mock_response = AsyncMock(spec=aiohttp.ClientResponse) mock_response = AsyncMock(spec=aiohttp.ClientResponse)
mock_response.status = 200 mock_response.status = 200
mock_response.headers = {'Content-Length': '1000'} mock_response.headers = {'Content-Length': '1000'}
@ -61,15 +70,13 @@ async def test_download_model_success():
mock_make_request = AsyncMock(return_value=mock_response) mock_make_request = AsyncMock(return_value=mock_response)
mock_progress_callback = AsyncMock() mock_progress_callback = AsyncMock()
# Mock file operations
mock_open = MagicMock()
mock_file = MagicMock()
mock_open.return_value.__enter__.return_value = mock_file
time_values = itertools.count(0, 0.1) time_values = itertools.count(0, 0.1)
with patch('comfy.model_filemanager.create_model_path', return_value=('models/checkpoints/model.sft', 'checkpoints/model.sft')), \ fake_paths = FolderNames.from_dict({'checkpoints': ([temp_dir], folder_paths.supported_pt_extensions)})
with patch('comfy.model_filemanager.create_model_path', return_value=('models/checkpoints/model.sft', 'model.sft')), \
patch('comfy.model_filemanager.check_file_exists', return_value=None), \ patch('comfy.model_filemanager.check_file_exists', return_value=None), \
patch('builtins.open', mock_open), \ patch('comfy.cmd.folder_paths.folder_names_and_paths', fake_paths), \
patch('time.time', side_effect=time_values): # Simulate time passing patch('time.time', side_effect=time_values): # Simulate time passing
result = await download_model( result = await download_model(
@ -77,6 +84,7 @@ async def test_download_model_success():
'model.sft', 'model.sft',
'http://example.com/model.sft', 'http://example.com/model.sft',
'checkpoints', 'checkpoints',
temp_dir,
mock_progress_callback mock_progress_callback
) )
@ -91,45 +99,49 @@ async def test_download_model_success():
# Check initial call # Check initial call
mock_progress_callback.assert_any_call( mock_progress_callback.assert_any_call(
'checkpoints/model.sft', 'model.sft',
DownloadModelStatus(DownloadStatusType.PENDING, 0, "Starting download of model.sft", False) DownloadModelStatus(DownloadStatusType.PENDING, 0, "Starting download of model.sft", False)
) )
# Check final call # Check final call
mock_progress_callback.assert_any_call( mock_progress_callback.assert_any_call(
'checkpoints/model.sft', 'model.sft',
DownloadModelStatus(DownloadStatusType.COMPLETED, 100, "Successfully downloaded model.sft", False) DownloadModelStatus(DownloadStatusType.COMPLETED, 100, "Successfully downloaded model.sft", False)
) )
# Verify file writing mock_file_path = os.path.join(temp_dir, 'model.sft')
mock_file.write.assert_any_call(b'a' * 500) assert os.path.exists(mock_file_path)
mock_file.write.assert_any_call(b'b' * 300) with open(mock_file_path, 'rb') as mock_file:
mock_file.write.assert_any_call(b'c' * 200) assert mock_file.read() == b''.join(chunks)
os.remove(mock_file_path)
# Verify request was made # Verify request was made
mock_make_request.assert_called_once_with('http://example.com/model.sft') mock_make_request.assert_called_once_with('http://example.com/model.sft')
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_download_model_url_request_failure(): async def test_download_model_url_request_failure(temp_dir):
# Mock dependencies # Mock dependencies
mock_response = AsyncMock(spec=ClientResponse) mock_response = AsyncMock(spec=ClientResponse)
mock_response.status = 404 # Simulate a "Not Found" error mock_response.status = 404 # Simulate a "Not Found" error
mock_get = AsyncMock(return_value=mock_response) mock_get = AsyncMock(return_value=mock_response)
mock_progress_callback = AsyncMock() mock_progress_callback = AsyncMock()
fake_paths = FolderNames.from_dict({'checkpoints': ([temp_dir], folder_paths.supported_pt_extensions)})
# Mock the create_model_path function # Mock the create_model_path function
with patch('comfy.model_filemanager.create_model_path', return_value=('/mock/path/model.safetensors', 'mock/path/model.safetensors')): with patch('comfy.model_filemanager.create_model_path', return_value='/mock/path/model.safetensors'), \
# Mock the check_file_exists function to return None (file doesn't exist) patch('comfy.model_filemanager.check_file_exists', return_value=None), \
with patch('comfy.model_filemanager.check_file_exists', return_value=None): patch('comfy.cmd.folder_paths.folder_names_and_paths', fake_paths):
# Call the function # Call the function
result = await download_model( result = await download_model(
mock_get, mock_get,
'model.safetensors', 'model.safetensors',
'http://example.com/model.safetensors', 'http://example.com/model.safetensors',
'mock_directory', 'checkpoints',
mock_progress_callback temp_dir,
) mock_progress_callback
)
# Assert the expected behavior # Assert the expected behavior
assert isinstance(result, DownloadModelStatus) assert isinstance(result, DownloadModelStatus)
@ -139,7 +151,7 @@ async def test_download_model_url_request_failure():
# Check that progress_callback was called with the correct arguments # Check that progress_callback was called with the correct arguments
mock_progress_callback.assert_any_call( mock_progress_callback.assert_any_call(
'mock_directory/model.safetensors', 'model.safetensors',
DownloadModelStatus( DownloadModelStatus(
status=DownloadStatusType.PENDING, status=DownloadStatusType.PENDING,
progress_percentage=0, progress_percentage=0,
@ -148,7 +160,7 @@ async def test_download_model_url_request_failure():
) )
) )
mock_progress_callback.assert_called_with( mock_progress_callback.assert_called_with(
'mock_directory/model.safetensors', 'model.safetensors',
DownloadModelStatus( DownloadModelStatus(
status=DownloadStatusType.ERROR, status=DownloadStatusType.ERROR,
progress_percentage=0, progress_percentage=0,
@ -171,30 +183,53 @@ async def test_download_model_invalid_model_subdirectory():
'model.sft', 'model.sft',
'http://example.com/model.sft', 'http://example.com/model.sft',
'../bad_path', '../bad_path',
'../bad_path',
mock_progress_callback mock_progress_callback
) )
# Assert the result # Assert the result
assert isinstance(result, DownloadModelStatus) assert isinstance(result, DownloadModelStatus)
assert result.message == 'Invalid model subdirectory' assert result.message.startswith('Invalid or unrecognized model directory')
assert result.status == 'error'
assert result.already_existed is False
@pytest.mark.asyncio
async def test_download_model_invalid_folder_path():
mock_make_request = AsyncMock()
mock_progress_callback = AsyncMock()
result = await download_model(
mock_make_request,
'model.sft',
'http://example.com/model.sft',
'checkpoints',
'invalid_path',
mock_progress_callback
)
# Assert the result
assert isinstance(result, DownloadModelStatus)
assert result.message.startswith("Invalid folder path")
assert result.status == 'error' assert result.status == 'error'
assert result.already_existed is False assert result.already_existed is False
# For create_model_path function
def test_create_model_path(tmp_path, monkeypatch): def test_create_model_path(tmp_path, monkeypatch):
mock_models_dir = tmp_path / "models" model_name = "model.safetensors"
monkeypatch.setattr('folder_paths.models_dir', str(mock_models_dir)) folder_path = os.path.join(tmp_path, "mock_dir")
model_name = "test_model.sft" file_path = create_model_path(model_name, folder_path)
model_directory = "test_dir"
file_path, relative_path = create_model_path(model_name, model_directory, mock_models_dir) assert file_path == os.path.join(folder_path, "model.safetensors")
assert file_path == str(mock_models_dir / model_directory / model_name)
assert relative_path == f"{model_directory}/{model_name}"
assert os.path.exists(os.path.dirname(file_path)) assert os.path.exists(os.path.dirname(file_path))
with pytest.raises(Exception, match="Invalid model directory"):
create_model_path("../path_traversal.safetensors", folder_path)
with pytest.raises(Exception, match="Invalid model directory"):
create_model_path("/etc/some_root_path", folder_path)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_check_file_exists_when_file_exists(tmp_path): async def test_check_file_exists_when_file_exists(tmp_path):
@ -203,15 +238,14 @@ async def test_check_file_exists_when_file_exists(tmp_path):
mock_callback = AsyncMock() mock_callback = AsyncMock()
result = await check_file_exists(str(file_path), "existing_model.sft", mock_callback, "test/existing_model.sft") result = await check_file_exists(str(file_path), "existing_model.sft", mock_callback)
assert result is not None assert result is not None
assert result.status == "completed" assert result.status == "completed"
assert result.message == "existing_model.sft already exists" assert result.message == "existing_model.sft already exists"
assert result.already_existed is True assert result.already_existed is True
mock_callback.assert_called_once_with( mock_callback.assert_called_once_with(
"test/existing_model.sft", "existing_model.sft",
DownloadModelStatus(DownloadStatusType.COMPLETED, 100, "existing_model.sft already exists", already_existed=True) DownloadModelStatus(DownloadStatusType.COMPLETED, 100, "existing_model.sft already exists", already_existed=True)
) )
@ -222,40 +256,47 @@ async def test_check_file_exists_when_file_does_not_exist(tmp_path):
mock_callback = AsyncMock() mock_callback = AsyncMock()
result = await check_file_exists(str(file_path), "non_existing_model.sft", mock_callback, "test/non_existing_model.sft") result = await check_file_exists(str(file_path), "non_existing_model.sft", mock_callback)
assert result is None assert result is None
mock_callback.assert_not_called() mock_callback.assert_not_called()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_track_download_progress_no_content_length(): async def test_track_download_progress_no_content_length(temp_dir):
mock_response = AsyncMock(spec=aiohttp.ClientResponse) mock_response = AsyncMock(spec=aiohttp.ClientResponse)
mock_response.headers = {} # No Content-Length header mock_response.headers = {} # No Content-Length header
mock_response.content.iter_chunked.return_value = AsyncIteratorMock([b'a' * 500, b'b' * 500]) chunks = [b'a' * 500, b'b' * 500]
mock_response.content.iter_chunked.return_value = AsyncIteratorMock(chunks)
mock_callback = AsyncMock() mock_callback = AsyncMock()
mock_open = MagicMock(return_value=MagicMock())
with patch('builtins.open', mock_open): full_path = os.path.join(temp_dir, 'model.sft')
result = await track_download_progress(
mock_response, '/mock/path/model.sft', 'model.sft', result = await track_download_progress(
mock_callback, 'models/model.sft', interval=0.1 mock_response, full_path, 'model.sft',
) mock_callback, interval=0.1
)
assert result.status == "completed" assert result.status == "completed"
assert os.path.exists(full_path)
with open(full_path, 'rb') as f:
assert f.read() == b''.join(chunks)
os.remove(full_path)
# Check that progress was reported even without knowing the total size # Check that progress was reported even without knowing the total size
mock_callback.assert_any_call( mock_callback.assert_any_call(
'models/model.sft', 'model.sft',
DownloadModelStatus(DownloadStatusType.IN_PROGRESS, 0, "Downloading model.sft", already_existed=False) DownloadModelStatus(DownloadStatusType.IN_PROGRESS, 0, "Downloading model.sft", already_existed=False)
) )
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_track_download_progress_interval(): async def test_track_download_progress_interval(temp_dir):
mock_response = AsyncMock(spec=aiohttp.ClientResponse) mock_response = AsyncMock(spec=aiohttp.ClientResponse)
mock_response.headers = {'Content-Length': '1000'} mock_response.headers = {'Content-Length': '1000'}
mock_response.content.iter_chunked.return_value = AsyncIteratorMock([b'a' * 100] * 10) chunks = [b'a' * 100] * 10
mock_response.content.iter_chunked.return_value = AsyncIteratorMock(chunks)
mock_callback = AsyncMock() mock_callback = AsyncMock()
mock_open = MagicMock(return_value=MagicMock()) mock_open = MagicMock(return_value=MagicMock())
@ -264,18 +305,18 @@ async def test_track_download_progress_interval():
mock_time = MagicMock() mock_time = MagicMock()
mock_time.side_effect = [i * 0.5 for i in range(30)] # This should be enough for 10 chunks mock_time.side_effect = [i * 0.5 for i in range(30)] # This should be enough for 10 chunks
with patch('builtins.open', mock_open), \ full_path = os.path.join(temp_dir, 'model.sft')
patch('time.time', mock_time):
with patch('time.time', mock_time):
await track_download_progress( await track_download_progress(
mock_response, '/mock/path/model.sft', 'model.sft', mock_response, full_path, 'model.sft',
mock_callback, 'models/model.sft', interval=1.0 mock_callback, interval=1.0
) )
# Print out the actual call count and the arguments of each call for debugging assert os.path.exists(full_path)
print(f"mock_callback was called {mock_callback.call_count} times") with open(full_path, 'rb') as f:
for i, call in enumerate(mock_callback.call_args_list): assert f.read() == b''.join(chunks)
args, kwargs = call os.remove(full_path)
print(f"Call {i + 1}: {args[1].status}, Progress: {args[1].progress_percentage:.2f}%")
# Assert that progress was updated at least 3 times (start, at least one interval, and end) # Assert that progress was updated at least 3 times (start, at least one interval, and end)
assert mock_callback.call_count >= 3, f"Expected at least 3 calls, but got {mock_callback.call_count}" assert mock_callback.call_count >= 3, f"Expected at least 3 calls, but got {mock_callback.call_count}"
@ -291,34 +332,6 @@ async def test_track_download_progress_interval():
assert last_call[0][1].progress_percentage == 100 assert last_call[0][1].progress_percentage == 100
def test_valid_subdirectory():
assert validate_model_subdirectory("valid-model123") is True
def test_subdirectory_too_long():
assert validate_model_subdirectory("a" * 51) is False
def test_subdirectory_with_double_dots():
assert validate_model_subdirectory("model/../unsafe") is False
def test_subdirectory_with_slash():
assert validate_model_subdirectory("model/unsafe") is False
def test_subdirectory_with_special_characters():
assert validate_model_subdirectory("model@unsafe") is False
def test_subdirectory_with_underscore_and_dash():
assert validate_model_subdirectory("valid_model-name") is True
def test_empty_subdirectory():
assert validate_model_subdirectory("") is False
@pytest.mark.parametrize("filename, expected", [ @pytest.mark.parametrize("filename, expected", [
("valid_model.safetensors", True), ("valid_model.safetensors", True),
("valid_model.sft", True), ("valid_model.sft", True),

View File

@ -0,0 +1,3 @@
// Shim for scripts/changeTracker.ts
export const ChangeTracker = window.comfyAPI.changeTracker.ChangeTracker;
export const globalTracker = window.comfyAPI.changeTracker.globalTracker;

BIN
web/templates/default.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

356
web/templates/default.json Normal file
View File

@ -0,0 +1,356 @@
{
"last_node_id": 9,
"last_link_id": 9,
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
413,
389
],
"size": [
425.27801513671875,
180.6060791015625
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6
],
"slot_index": 0
}
],
"properties": {},
"widgets_values": [
"text, watermark"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
415,
186
],
"size": [
422.84503173828125,
164.31304931640625
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4
],
"slot_index": 0
}
],
"properties": {},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
473,
609
],
"size": [
315,
106
],
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
2
],
"slot_index": 0
}
],
"properties": {},
"widgets_values": [
512,
512,
1
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
863,
186
],
"size": [
315,
262
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
],
"slot_index": 0
}
],
"properties": {},
"widgets_values": [
156680208700286,
true,
20,
8,
"euler",
"normal",
1
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1209,
188
],
"size": [
210,
46
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {}
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1451,
189
],
"size": [
210,
26
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {}
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
26,
474
],
"size": [
315,
98
],
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
1
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
3,
5
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
8
],
"slot_index": 2
}
],
"properties": {},
"widgets_values": [
"v1-5-pruned-emaonly.safetensors"
]
}
],
"links": [
[
1,
4,
0,
3,
0,
"MODEL"
],
[
2,
5,
0,
3,
3,
"LATENT"
],
[
3,
4,
1,
6,
0,
"CLIP"
],
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
5,
4,
1,
7,
0,
"CLIP"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
8,
4,
2,
8,
1,
"VAE"
],
[
9,
8,
0,
9,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4,
"models": [{
"name": "v1-5-pruned-emaonly.safetensors",
"url": "https://huggingface.co/Comfy-Org/stable-diffusion-v1-5-archive/resolve/main/v1-5-pruned-emaonly.safetensors?download=true",
"directory": "checkpoints"
}]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,420 @@
{
"last_node_id": 36,
"last_link_id": 58,
"nodes": [
{
"id": 33,
"type": "CLIPTextEncode",
"pos": [
390,
400
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {
"collapsed": true
},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 54,
"slot_index": 0
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
55
],
"slot_index": 0
}
],
"title": "CLIP Text Encode (Negative Prompt)",
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
],
"color": "#322",
"bgcolor": "#533"
},
{
"id": 27,
"type": "EmptySD3LatentImage",
"pos": [
471,
455
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
51
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptySD3LatentImage"
},
"widgets_values": [
1024,
1024,
1
],
"color": "#323",
"bgcolor": "#535"
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1151,
195
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 52
},
{
"name": "vae",
"type": "VAE",
"link": 46
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1375,
194
],
"size": {
"0": 985.3012084960938,
"1": 1060.3828125
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 31,
"type": "KSampler",
"pos": [
816,
192
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 47
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 58
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 55
},
{
"name": "latent_image",
"type": "LATENT",
"link": 51
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
52
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
173805153958730,
"randomize",
4,
1,
"euler",
"simple",
1
]
},
{
"id": 30,
"type": "CheckpointLoaderSimple",
"pos": [
48,
192
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
47
],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
45,
54
],
"shape": 3,
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
46
],
"shape": 3,
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"flux1-schnell-fp8.safetensors"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
384,
192
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 45
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
58
],
"slot_index": 0
}
],
"title": "CLIP Text Encode (Positive Prompt)",
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"a bottle with a beautiful rainbow galaxy inside it on top of a wooden table in the middle of a modern kitchen beside a plate of vegetables and mushrooms and a wine glasse that contains a planet earth with a plate with a half eaten apple pie on it"
],
"color": "#232",
"bgcolor": "#353"
},
{
"id": 34,
"type": "Note",
"pos": [
831,
501
],
"size": {
"0": 282.8617858886719,
"1": 164.08004760742188
},
"flags": {},
"order": 2,
"mode": 0,
"properties": {
"text": ""
},
"widgets_values": [
"Note that Flux dev and schnell do not have any negative prompt so CFG should be set to 1.0. Setting CFG to 1.0 means the negative prompt is ignored.\n\nThe schnell model is a distilled model that can generate a good image with only 4 steps."
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
9,
8,
0,
9,
0,
"IMAGE"
],
[
45,
30,
1,
6,
0,
"CLIP"
],
[
46,
30,
2,
8,
1,
"VAE"
],
[
47,
30,
0,
31,
0,
"MODEL"
],
[
51,
27,
0,
31,
3,
"LATENT"
],
[
52,
31,
0,
8,
0,
"LATENT"
],
[
54,
30,
1,
33,
0,
"CLIP"
],
[
55,
33,
0,
31,
2,
"CONDITIONING"
],
[
58,
6,
0,
31,
1,
"CONDITIONING"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.1,
"offset": [
0.6836674124529055,
1.8290357611967831
]
}
},
"models": [
{
"name": "flux1-schnell-fp8.safetensors",
"url": "https://huggingface.co/Comfy-Org/flux1-schnell/resolve/main/flux1-schnell-fp8.safetensors?download=true",
"directory": "checkpoints"
}
],
"version": 0.4
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,447 @@
{
"last_node_id": 14,
"last_link_id": 17,
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
413,
389
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 15
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"watermark, text\n"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
415,
186
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 14
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"photograph of victorian woman with wings, sky clouds, meadow grass\n"
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1209,
188
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 17
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1451,
189
],
"size": {
"0": 210,
"1": 58
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 10,
"type": "LoadImage",
"pos": [
215.9799597167969,
703.6800268554688
],
"size": [
315,
314.00002670288086
],
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
10
],
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
},
{
"id": 12,
"type": "VAEEncode",
"pos": [
614.979959716797,
707.6800268554688
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "pixels",
"type": "IMAGE",
"link": 10
},
{
"name": "vae",
"type": "VAE",
"link": 16
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
11
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEEncode"
}
},
{
"id": 3,
"type": "KSampler",
"pos": [
863,
186
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 13
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 11
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
280823642470253,
"randomize",
20,
8,
"dpmpp_2m",
"normal",
0.8700000000000001
]
},
{
"id": 14,
"type": "CheckpointLoaderSimple",
"pos": [
19,
433
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
13
],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
14,
15
],
"shape": 3,
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
16,
17
],
"shape": 3,
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"v1-5-pruned-emaonly.safetensors"
]
}
],
"links": [
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
9,
8,
0,
9,
0,
"IMAGE"
],
[
10,
10,
0,
12,
0,
"IMAGE"
],
[
11,
12,
0,
3,
3,
"LATENT"
],
[
13,
14,
0,
3,
0,
"MODEL"
],
[
14,
14,
1,
6,
0,
"CLIP"
],
[
15,
14,
1,
7,
0,
"CLIP"
],
[
16,
14,
2,
12,
1,
"VAE"
],
[
17,
14,
2,
8,
1,
"VAE"
]
],
"groups": [
{
"title": "Loading images",
"bounding": [
150,
630,
726,
171
],
"color": "#3f789e"
}
],
"config": {},
"extra": {},
"version": 0.4,
"models": [{
"name": "v1-5-pruned-emaonly.safetensors",
"url": "https://huggingface.co/Comfy-Org/stable-diffusion-v1-5-archive/resolve/main/v1-5-pruned-emaonly.safetensors?download=true",
"directory": "checkpoints"
}]
}

BIN
web/templates/upscale.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

652
web/templates/upscale.json Normal file
View File

@ -0,0 +1,652 @@
{
"last_node_id": 16,
"last_link_id": 23,
"nodes": [
{
"id": 8,
"type": "VAEDecode",
"pos": [
1235.7215957031258,
577.1878720703122
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 21
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 10,
"type": "LatentUpscale",
"pos": [
1238,
170
],
"size": {
"0": 315,
"1": 130
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 10
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
14
]
}
],
"properties": {
"Node name for S&R": "LatentUpscale"
},
"widgets_values": [
"nearest-exact",
1152,
1152,
"disabled"
]
},
{
"id": 13,
"type": "VAEDecode",
"pos": [
1961,
125
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 15
},
{
"name": "vae",
"type": "VAE",
"link": 22
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
17
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
374,
171
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 19
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4,
12
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"masterpiece HDR victorian portrait painting of woman, blonde hair, mountain nature, blue sky\n"
]
},
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
377,
381
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 20
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6,
13
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"bad hands, text, watermark\n"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
435,
600
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
2
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
768,
768,
1
]
},
{
"id": 11,
"type": "KSampler",
"pos": [
1585,
114
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 23,
"slot_index": 0
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 12,
"slot_index": 1
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 13,
"slot_index": 2
},
{
"name": "latent_image",
"type": "LATENT",
"link": 14,
"slot_index": 3
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
15
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
469771404043268,
"randomize",
14,
8,
"dpmpp_2m",
"simple",
0.5
]
},
{
"id": 12,
"type": "SaveImage",
"pos": [
2203,
123
],
"size": {
"0": 407.53717041015625,
"1": 468.13226318359375
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 17
}
],
"properties": {},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
845,
172
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 18
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7,
10
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
89848141647836,
"randomize",
12,
8,
"dpmpp_sde",
"normal",
1
]
},
{
"id": 16,
"type": "CheckpointLoaderSimple",
"pos": [
24,
315
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
18,
23
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
19,
20
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
21,
22
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"v2-1_768-ema-pruned.safetensors"
]
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1495.7215957031258,
576.1878720703122
],
"size": [
232.9403301043692,
282.4336258387117
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {},
"widgets_values": [
"ComfyUI"
]
}
],
"links": [
[
2,
5,
0,
3,
3,
"LATENT"
],
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
9,
8,
0,
9,
0,
"IMAGE"
],
[
10,
3,
0,
10,
0,
"LATENT"
],
[
12,
6,
0,
11,
1,
"CONDITIONING"
],
[
13,
7,
0,
11,
2,
"CONDITIONING"
],
[
14,
10,
0,
11,
3,
"LATENT"
],
[
15,
11,
0,
13,
0,
"LATENT"
],
[
17,
13,
0,
12,
0,
"IMAGE"
],
[
18,
16,
0,
3,
0,
"MODEL"
],
[
19,
16,
1,
6,
0,
"CLIP"
],
[
20,
16,
1,
7,
0,
"CLIP"
],
[
21,
16,
2,
8,
1,
"VAE"
],
[
22,
16,
2,
13,
1,
"VAE"
],
[
23,
16,
0,
11,
0,
"MODEL"
]
],
"groups": [
{
"title": "Txt2Img",
"bounding": [
-1,
30,
1211,
708
],
"color": "#a1309b"
},
{
"title": "Save Intermediate Image",
"bounding": [
1225,
500,
516,
196
],
"color": "#3f789e"
},
{
"title": "Hires Fix",
"bounding": [
1224,
29,
710,
464
],
"color": "#b58b2a"
},
{
"title": "Save Final Image",
"bounding": [
1949,
31,
483,
199
],
"color": "#3f789e"
}
],
"config": {},
"extra": {},
"version": 0.4,
"models": [
{
"name": "v2-1_768-ema-pruned.safetensors",
"url": "https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors?download=true",
"directory": "checkpoints"
}
]
}