diff --git a/bundled_packs/ComfyUI-ConditioningKrea2Rebalance/LICENSE b/bundled_packs/ComfyUI-ConditioningKrea2Rebalance/LICENSE new file mode 100644 index 0000000..29f81d8 --- /dev/null +++ b/bundled_packs/ComfyUI-ConditioningKrea2Rebalance/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bundled_packs/ComfyUI-ConditioningKrea2Rebalance/__init__.py b/bundled_packs/ComfyUI-ConditioningKrea2Rebalance/__init__.py new file mode 100644 index 0000000..fdb4f4b --- /dev/null +++ b/bundled_packs/ComfyUI-ConditioningKrea2Rebalance/__init__.py @@ -0,0 +1,11 @@ +from .nodes import ConditioningKrea2Rebalance + +NODE_CLASS_MAPPINGS = { + "ConditioningKrea2Rebalance": ConditioningKrea2Rebalance, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "ConditioningKrea2Rebalance": "Conditioning Krea2 Rebalance", +} + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/bundled_packs/ComfyUI-ConditioningKrea2Rebalance/nodes.py b/bundled_packs/ComfyUI-ConditioningKrea2Rebalance/nodes.py new file mode 100644 index 0000000..719d22b --- /dev/null +++ b/bundled_packs/ComfyUI-ConditioningKrea2Rebalance/nodes.py @@ -0,0 +1,85 @@ +import torch + + +def _scale_cond_tensor(t: torch.Tensor, multiplier, per_layer_weights=None): + """Scale a conditioning tensor, optionally with per-layer weighting. + + Krea2 conditioning arrives as (B, seq, 12*2560) — the 12 Qwen3-VL taps flattened + into the feature dim. When per_layer_weights is given we reshape to + (B, seq, 12, D), apply a different gain to each tap and flatten back. + """ + if per_layer_weights is None: + return t * multiplier + + flat = t.shape[-1] + n_layers = len(per_layer_weights) + if n_layers > 1 and flat % n_layers == 0: + layer_dim = flat // n_layers + orig_dtype = t.dtype + t = t.float() + t = t.view(*t.shape[:-1], n_layers, layer_dim) + gains = torch.tensor(per_layer_weights, dtype=t.dtype, device=t.device) + t = t * gains.view(*([1] * (t.dim() - 2)), n_layers, 1) + t = t.view(*t.shape[:-2], flat) + return t.to(orig_dtype) * multiplier + return t * multiplier + + +def _parse_per_layer(s: str): + """Parse a comma-separated list of floats. Returns None if empty/invalid.""" + if not s: + return None + s = s.strip() + if not s: + return None + try: + vals = [float(x) for x in s.replace(";", ",").split(",") if x.strip() != ""] + except ValueError: + return None + if len(vals) < 2: + return None + return vals + + +def scale_conditioning(structure, multiplier, per_layer_weights=None): + """leaving masks / pooled output intact.""" + if isinstance(structure, list): + out = [] + for item in structure: + if isinstance(item, (list, tuple)) and len(item) == 2 \ + and isinstance(item[0], torch.Tensor) and isinstance(item[1], dict): + cond_t, extras = item + new_cond = _scale_cond_tensor(cond_t, multiplier, per_layer_weights) + out.append([new_cond, dict(extras)]) + else: + out.append(scale_conditioning(item, multiplier, per_layer_weights)) + return out + if isinstance(structure, torch.Tensor): + return _scale_cond_tensor(structure, multiplier, per_layer_weights) + if isinstance(structure, dict): + return {k: scale_conditioning(v, multiplier, per_layer_weights) + for k, v in structure.items()} + return structure + + +class ConditioningKrea2Rebalance: + + DEFAULT_WEIGHTS = "1.0,1.0,1.0,1.0,1.0,1.0,1.0,2.5,5.0,1.1,4.0,1.0" + + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "conditioning": ("CONDITIONING",), + "multiplier": ("FLOAT", {"default": 4.0, "min": -1000000000.0, "max": 1000000000.0, "step": 0.01}), + "per_layer_weights": ("STRING", {"default": cls.DEFAULT_WEIGHTS, "multiline": False}), + }} + + RETURN_TYPES = ("CONDITIONING",) + RETURN_NAMES = ("conditioning",) + FUNCTION = "main" + CATEGORY = "conditioning" + + def main(self, conditioning, multiplier, per_layer_weights=None): + plw = _parse_per_layer(per_layer_weights) if per_layer_weights else None + c = scale_conditioning(conditioning, multiplier, per_layer_weights=plw) + return (c,) diff --git a/bundled_packs/ComfyUI-VAE-Utils/LICENSE b/bundled_packs/ComfyUI-VAE-Utils/LICENSE new file mode 100644 index 0000000..846db00 --- /dev/null +++ b/bundled_packs/ComfyUI-VAE-Utils/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 spacepxl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bundled_packs/ComfyUI-VAE-Utils/__init__.py b/bundled_packs/ComfyUI-VAE-Utils/__init__.py new file mode 100644 index 0000000..e965062 --- /dev/null +++ b/bundled_packs/ComfyUI-VAE-Utils/__init__.py @@ -0,0 +1,9 @@ +from .nodes import COMBINED_MAPPINGS + +NODE_CLASS_MAPPINGS = {} +NODE_DISPLAY_NAME_MAPPINGS = {} +for k, v in COMBINED_MAPPINGS.items(): + NODE_CLASS_MAPPINGS[k] = v[0] + NODE_DISPLAY_NAME_MAPPINGS[k] = v[1] + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] \ No newline at end of file diff --git a/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/latent_projector.py b/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/latent_projector.py new file mode 100644 index 0000000..17b1603 --- /dev/null +++ b/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/latent_projector.py @@ -0,0 +1,56 @@ +import os +import torch +import torch.nn as nn +import torch.nn.functional as F +from safetensors.torch import load_file + + +class CausalLatentProjector(nn.Module): + def __init__( + self, + in_channels = 16, + out_channels = 3, + scale_t = 4, + scale_s = 8, + num_projections = 3, + ): + super().__init__() + self.scale_t = scale_t + self.scale_s = scale_s + + self.projections = nn.ModuleList([ + nn.Linear(in_channels, out_channels * scale_s ** 2) + ]) + + for _ in range(num_projections - 1): + self.projections.append( + nn.Linear(in_channels, out_channels * scale_t * scale_s ** 2) + ) + + def forward(self, latents): + batch, channels, frames, height, width = latents.shape + + pixels = [] + for frame in range(frames): + idx = min(frame, len(self.projections) - 1) + + feat = self.projections[idx]( + latents[:, :, frame].movedim(1, -1) # BHWC + ).movedim(-1, 1) # BCHW + + feat = F.pixel_shuffle(feat, self.scale_s).unsqueeze(2) # BCFHW + + if frame == 0: + pixels.append(feat) + else: + feat = feat.chunk(self.scale_t, dim=1) + pixels.extend(list(feat)) + + return torch.cat(pixels, dim=2) + + +def Wan21_latent_projector(): + model = CausalLatentProjector() + model_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "wan21_latent_projector.safetensors") + model.load_state_dict(load_file(model_path)) + return model \ No newline at end of file diff --git a/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/model.py b/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/model.py new file mode 100644 index 0000000..55f879b --- /dev/null +++ b/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/model.py @@ -0,0 +1,155 @@ +import os +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +from safetensors.torch import load_file + + +class LayerNorm3d(nn.LayerNorm): + def __init__(self, num_channels, eps=1e-6, affine=True): + super().__init__(num_channels, eps=eps, elementwise_affine=affine) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = x.permute(0, 2, 3, 4, 1) + x = F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps) + x = x.permute(0, 4, 1, 2, 3) + return x + + +class DiCoBlock3d(nn.Module): + def __init__(self, hidden_size, mlp_ratio=4.0, kernel_size=3): + super().__init__() + + # self.conv1 = nn.Conv3d(hidden_size, hidden_size, kernel_size=1) + + self.conv2 = nn.Conv3d( + hidden_size, + hidden_size, + kernel_size=(1, kernel_size, kernel_size), + padding=(0, kernel_size//2, kernel_size//2), + # groups=hidden_size, + padding_mode="replicate", + ) + + self.conv3 = nn.Conv3d(hidden_size, hidden_size, kernel_size=1) + + self.cca = nn.Sequential( + nn.AdaptiveAvgPool3d(1), + nn.Conv3d(hidden_size, hidden_size , kernel_size=1), + nn.Sigmoid(), + ) + + ffn_channel = int(mlp_ratio * hidden_size) + self.conv4 = nn.Conv3d(hidden_size, ffn_channel, kernel_size=1) + self.conv5 = nn.Conv3d(ffn_channel, hidden_size, kernel_size=1) + + self.norm1 = LayerNorm3d(hidden_size, affine=False) + self.norm2 = LayerNorm3d(hidden_size, affine=False) + + def forward(self, inp): + x = self.norm1(inp) + # x = F.gelu(self.conv2(self.conv1(x))) + x = F.gelu(self.conv2(x)) + x = self.conv3(x * self.cca(x)) + x = inp + x + + y = self.norm2(x) + y = self.conv5(F.gelu(self.conv4(y))) + return x + y + + +class LatentModel3d(nn.Module): + def __init__( + self, + in_channels = 16, + out_channels = 16, + hidden_size = 128, + kernel_size = 3, + mlp_ratio = 4.0, + depth = 8, + upscale = 2, + ): + super().__init__() + self.upscale = int(upscale) + + self.conv_in = nn.Conv3d( + in_channels, + hidden_size, + kernel_size=(1, 3, 3), + padding=(0, 1, 1), + padding_mode="replicate", + ) + + self.blocks = nn.ModuleList([ + DiCoBlock3d(hidden_size, mlp_ratio, kernel_size) + for _ in range(depth) + ]) + + out_ch = out_channels * self.upscale ** 2 + self.conv_out = nn.Conv3d( + hidden_size, + out_ch, + kernel_size=(1, 3, 3), + padding=(0, 1, 1), + padding_mode="replicate", + ) + + # self.initialize_weights() + + def initialize_weights(self): + def _basic_init(module): + if isinstance(module, nn.Linear) or isinstance(module, nn.Conv3d): + torch.nn.init.xavier_uniform_(module.weight) + if module.bias is not None: + nn.init.constant_(module.bias, 0) + self.apply(_basic_init) + + def forward(self, x): + """ + x: (B, C, F, H, W) tensor of spatial inputs (images or latent representations of images) + """ + hidden = self.conv_in(x) + + for block in self.blocks: + hidden = block(hidden) + + output = self.conv_out(hidden) + + if self.upscale > 1: + output = F.pixel_shuffle(output.movedim(1, 2), self.upscale).movedim(2, 1) + x = F.interpolate(x, scale_factor=(1, self.upscale, self.upscale), mode="nearest-exact") + return output + x + + +def Wan21_latent_upscale_2x(): + model = LatentModel3d( + in_channels = 16, + out_channels = 16, + hidden_size = 128, + kernel_size = 3, + mlp_ratio = 4.0, + depth = 8, + upscale = 2, + ) + model_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "wan21_latent_upscale_2x.safetensors") + model.load_state_dict(load_file(model_path)) + return model + + +latent_upscale_models = { + "Wan 2.1 latent upscale 2x": Wan21_latent_upscale_2x, +} + + +if __name__=="__main__": + model = LatentModel3d(upscale=2).to("cuda") + + x = torch.randn(1, 16, 4, 64, 64).to("cuda") + out = model(x) + print(out.shape) + + total = sum([p.numel() for p in model.parameters() if p.requires_grad]) + print("trainable parameters: %.2f M" % (total / 1e6)) + + print(model) \ No newline at end of file diff --git a/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/wan21_latent_projector.safetensors b/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/wan21_latent_projector.safetensors new file mode 100644 index 0000000..9b465e9 Binary files /dev/null and b/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/wan21_latent_projector.safetensors differ diff --git a/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/wan21_latent_upscale_2x.safetensors b/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/wan21_latent_upscale_2x.safetensors new file mode 100644 index 0000000..85ad83e Binary files /dev/null and b/bundled_packs/ComfyUI-VAE-Utils/latent_upscale/wan21_latent_upscale_2x.safetensors differ diff --git a/bundled_packs/ComfyUI-VAE-Utils/nodes.py b/bundled_packs/ComfyUI-VAE-Utils/nodes.py new file mode 100644 index 0000000..21757fa --- /dev/null +++ b/bundled_packs/ComfyUI-VAE-Utils/nodes.py @@ -0,0 +1,450 @@ +import torch +import torch.nn.functional as F +import copy +import math +from tqdm.auto import tqdm + +import comfy.utils +import comfy.model_management +import comfy.latent_formats +import folder_paths +from nodes import VAELoader +from .src.sd import CustomVAE +from .latent_upscale.model import latent_upscale_models +from .latent_upscale.latent_projector import Wan21_latent_projector + + +class VAEUtils_CustomVAELoader(VAELoader): + @staticmethod + def vae_list(): + return folder_paths.get_filename_list("vae") + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "vae_name": (s.vae_list(), ), + "disable_offload": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("VAE",) + FUNCTION = "load_vae" + CATEGORY = "VAE-Utils" + + def load_vae(self, vae_name, disable_offload): + if vae_name == "pixel_space": + sd = {} + sd["pixel_space_vae"] = torch.tensor(1.0) + elif vae_name in ["taesd", "taesdxl", "taesd3", "taef1"]: + sd = self.load_taesd(vae_name) + else: + vae_path = folder_paths.get_full_path_or_raise("vae", vae_name) + sd = comfy.utils.load_torch_file(vae_path) + vae = CustomVAE(sd=sd) + vae.throw_exception_if_invalid() + vae.disable_offload = disable_offload + return (vae, ) + + +class VAEUtils_DisableVAEOffload: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "vae": ("VAE", ), + "disable_offload": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("VAE",) + FUNCTION = "set_offload" + CATEGORY = "VAE-Utils" + + def set_offload(self, vae, disable_offload): + vae = copy.copy(vae) + vae.disable_offload = disable_offload + return (vae, ) + + +class VAEUtils_VAEDecodeTiled: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "samples": ("LATENT", ), + "vae": ("VAE", ), + "upscale": ("INT", {"default": -1, "min": -1, "tooltip": "Post upscale factor, -1=auto"}), + "tile": ("BOOLEAN", {"default": False}), + "tile_size": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 32}), + "overlap": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32}), + "temporal_size": ("INT", {"default": 4096, "min": 8, "max": 4096, "step": 4, "tooltip": "Only used for video VAEs: Amount of frames to decode at a time."}), + "temporal_overlap": ("INT", {"default": 64, "min": 4, "max": 4096, "step": 4, "tooltip": "Only used for video VAEs: Amount of frames to overlap."}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "decode" + CATEGORY = "VAE-Utils" + + def decode(self, samples, vae, upscale, tile, tile_size, overlap, temporal_size, temporal_overlap): + if tile_size < overlap * 4: + overlap = tile_size // 4 + if temporal_size < temporal_overlap * 2: + temporal_overlap = temporal_overlap // 2 + temporal_compression = vae.temporal_compression_decode() + if temporal_compression is not None: + temporal_size = max(2, temporal_size // temporal_compression) + temporal_overlap = max(1, min(temporal_size // 2, temporal_overlap // temporal_compression)) + else: + temporal_size = None + temporal_overlap = None + + compression = vae.spacial_compression_decode() + + if tile: + images = vae.decode_tiled(samples["samples"], tile_x=tile_size // compression, tile_y=tile_size // compression, overlap=overlap // compression, tile_t=temporal_size, overlap_t=temporal_overlap) + else: + images = vae.decode(samples["samples"]) + + if len(images.shape) == 5: #Combine batches + images = images.reshape(-1, images.shape[-3], images.shape[-2], images.shape[-1]) + + # Safety guard for ComfyUI PRs #11405 / #11406 regression: + # If process_output was not applied through the inherited VAE.decode() path the raw + # decoder output lands in [-1, 1]. Values below 0 are clamped to black by save/preview + # nodes, producing the dark/burned look. Detect and correct here. + # This guard is safe on correctly-decoded images because they are always in [0, 1] + # so min >= 0 and the branch never fires. + if images.min() < -0.1: + images = torch.clamp((images.float() + 1.0) / 2.0, min=0.0, max=1.0) + + if upscale < 1: + ch = images.shape[-1] + if ch == 3: + upscale = 1 + else: + if ch % 3 == 0: + upscale = round((ch // 3) ** 0.5) + else: + raise Exception("Couldn't determine upscale factor, try setting the value manually instead") + + if upscale > 1: + images = F.pixel_shuffle(images.movedim(-1, 1), upscale_factor=int(upscale)).movedim(1, -1) + + return (images, ) + + +class VAEUtils_LatentUpscale: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "samples": ("LATENT", ), + "model": (list(latent_upscale_models.keys()), ), + } + } + + RETURN_TYPES = ("LATENT",) + FUNCTION = "upscale" + CATEGORY = "VAE-Utils" + + def upscale(self, samples, model): + device = comfy.model_management.get_torch_device() + model = latent_upscale_models[model]().to(device) + + latents = samples["samples"].to(dtype=torch.float32, device=device) + upscaled_latents = model(latents).to(comfy.model_management.intermediate_device()) + + samples = copy.deepcopy(samples) + samples["samples"] = upscaled_latents + + return (samples, ) + + +class VAEUtils_WanLatentPreview: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "samples": ("LATENT", ), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "upscale" + CATEGORY = "VAE-Utils" + + def upscale(self, samples): + device = comfy.model_management.intermediate_device() + projector = Wan21_latent_projector().to(device) + + latents = samples["samples"].to(dtype=torch.float32, device=device) + pixels = projector(latents).to(comfy.model_management.intermediate_device()) + pixels = pixels * 0.5 + 0.5 + + f, h, w = pixels.shape[-3:] + pixels = F.interpolate(pixels, size=(f, h//8, w//8), mode="area") + + pixels = [b.movedim(0, -1) for b in pixels] # CFHW -> FHWC + pixels = torch.cat(pixels, dim=0) # (BF)HWC + return (pixels, ) + + +def get_tiles(length, tile_size, min_overlap): + if length <= tile_size: + return [(0, length)] + + max_step = tile_size - min_overlap + total_shiftable = length - tile_size + + gaps_needed = math.ceil(total_shiftable / max_step) if total_shiftable > 0 else 0 + num_tiles = gaps_needed + 1 + + if num_tiles == 1: + raise Exception("this shouldn't happen") + + gap_base = total_shiftable // (num_tiles - 1) + remainder = total_shiftable % (num_tiles - 1) + + starts = [] + acc = 0 + for i in range(num_tiles): + starts.append(acc) + if i < num_tiles - 1: + acc += gap_base + (1 if i < remainder else 0) + + slices = [(s, s + tile_size) for s in starts] + return slices + + +def get_1d_mask(idx, tiles, drop_first=0): + tile_start, tile_end = tiles[idx] + mask = torch.ones(tile_end - tile_start) + + if idx > 0: + prev_end = tiles[idx - 1][1] + size = prev_end - tile_start - drop_first + ramp = (torch.arange(size) + 1) / size + mask[drop_first : size + drop_first] *= ramp + mask[:drop_first] *= 0 + + if idx < (len(tiles) - 1): + next_start = tiles[idx + 1][0] + size = tile_end - next_start + ramp = (torch.flip(torch.arange(size), [0]) + 1) / size + mask[-size:] *= ramp + + return mask + + +class VAEUtils_TileModelPatch: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL", ), + "tile_t": ("INT", {"default": 13, "min": 3, "step": 1}), + "tile_h": ("INT", {"default": 78, "min": 32, "step": 2}), + "tile_w": ("INT", {"default": 78, "min": 32, "step": 2}), + "min_overlap_t": ("INT", {"default": 5, "min": 1, "step": 1}), + "min_overlap_h": ("INT", {"default": 16, "min": 0, "step": 2}), + "min_overlap_w": ("INT", {"default": 16, "min": 0, "step": 2}), + "drop_first_t": ("INT", {"default": 2, "min": 0, "step": 1}), + "patch_memory_estimate": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "patch" + CATEGORY = "VAE-Utils" + + def patch(self, model, tile_t, tile_h, tile_w, min_overlap_t, min_overlap_h, min_overlap_w, drop_first_t, patch_memory_estimate): + model = model.clone() + + def model_function_tile_wrapper(apply_model, args): + # print(args["c"]["transformer_options"]["patches"]["noise_refiner"][0].encoded_image.shape) + + # collect inputs + x = args["input"] + t = args["timestep"] + c = args["c"] + c_concat = c.get("c_concat", None) + vace_context = c.get("vace_context", None) + # transformer_options = c.get("transformer_options", {}) + + if c_concat is not None: + assert c_concat.shape[2:] == x.shape[2:], f"c_concat shape {c_concat.shape} doesn't match x shape {x.shape}" + + t_tiles = get_tiles(x.shape[-3], tile_t, min_overlap_t) if x.ndim == 5 else None + h_tiles = get_tiles(x.shape[-2], tile_h, min_overlap_h) + w_tiles = get_tiles(x.shape[-1], tile_w, min_overlap_w) + + total_tiles = len(h_tiles) * len(w_tiles) + message = f"{len(h_tiles)}h {len(w_tiles)}w" + + if t_tiles is not None: + total_tiles *= len(t_tiles) + message = f"{len(t_tiles)}t " + message + + x_out = torch.zeros_like(x) + x_alpha = torch.zeros_like(x) + progress_bar = tqdm(range(0, total_tiles), desc=message) + + for w_idx, (w_start, w_end) in enumerate(w_tiles): + for h_idx, (h_start, h_end) in enumerate(h_tiles): + if t_tiles is not None: + for t_idx, (t_start, t_end) in enumerate(t_tiles): + # slice inputs (todo: any other conditions? controlnets etc) + x_tile = x[..., t_start:t_end, h_start:h_end, w_start:w_end] + c_tile = c.copy() + + if c_concat is not None: + c_tile["c_concat"] = c_concat[..., t_start:t_end, h_start:h_end, w_start:w_end] + + if vace_context is not None: + c_tile["vace_context"] = vace_context[..., t_start:t_end, h_start:h_end, w_start:w_end] + + tile_out = apply_model(x_tile, t, **c_tile) + + # prepare mask for edge blending + mask_t = get_1d_mask(t_idx, t_tiles, drop_first_t).view(1, 1, -1, 1, 1) + mask_h = get_1d_mask(h_idx, h_tiles).view(1, 1, 1, -1, 1) + mask_w = get_1d_mask(w_idx, w_tiles).view(1, 1, 1, 1, -1) + + mask = torch.ones_like(tile_out) + mask *= mask_t.to(mask) + mask *= mask_h.to(mask) + mask *= mask_w.to(mask) + + # accumulate tile into full image + x_out[:, :, t_start:t_end, h_start:h_end, w_start:w_end] += tile_out * mask + x_alpha[:, :, t_start:t_end, h_start:h_end, w_start:w_end] += mask + progress_bar.update(1) + + else: + x_tile = x[..., h_start:h_end, w_start:w_end] + c_tile = c.copy() + + if c_concat is not None: + c_tile["c_concat"] = c_concat[..., h_start:h_end, w_start:w_end] + + tile_out = apply_model(x_tile, t, **c_tile) + + mask_h = get_1d_mask(h_idx, h_tiles).view(1, 1, -1, 1) + mask_w = get_1d_mask(w_idx, w_tiles).view(1, 1, 1, -1) + + mask = torch.ones_like(tile_out) + mask *= mask_h.to(mask) + mask *= mask_w.to(mask) + + x_out[:, :, h_start:h_end, w_start:w_end] += tile_out * mask + x_alpha[:, :, h_start:h_end, w_start:w_end] += mask + progress_bar.update(1) + + assert x_alpha.min() > 0, "missing tile coverage" + x_out = x_out / x_alpha + return x_out + + model.set_model_unet_function_wrapper(model_function_tile_wrapper) + + def update_input_shape(input_shape): + new_input_shape = input_shape.copy() + if len(new_input_shape) == 5: + new_input_shape = new_input_shape[:2] + [tile_t, tile_h, tile_w] + elif len(new_input_shape) == 4: + new_input_shape = new_input_shape[:2] + [tile_h, tile_w] + return new_input_shape + + original_memory_required = model.model.memory_required + + def memory_required_wrapper(input_shape, cond_shapes={}): + new_input_shape = update_input_shape(input_shape) + + for c in model.model.memory_usage_factor_conds: + shape = cond_shapes.get(c, None) + if shape is not None: + new_shape = update_input_shape(shape) + cond_shapes[c] = new_shape + + return original_memory_required(new_input_shape, cond_shapes) + + if patch_memory_estimate: + model.model.memory_required = memory_required_wrapper + + return (model, ) + + +class VAEUtils_VisualizeTiles: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "length": ("INT", {"default": 21, "min": 1, "step": 1}), + "tile_size": ("INT", {"default": 13, "min": 1, "step": 1}), + "min_overlap": ("INT", {"default": 5, "min": 0, "step": 1}), + "drop_first": ("INT", {"default": 2, "min": 0, "step": 1}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "visualize" + CATEGORY = "VAE-Utils" + + def visualize(self, length, tile_size, min_overlap, drop_first): + tile_slices = get_tiles(length, tile_size, min_overlap) + + image = torch.zeros(1, 3, len(tile_slices), length) + + for idx, (start, end) in enumerate(tile_slices): + print(idx, start, end) + mask = get_1d_mask(idx, tile_slices, drop_first) + image[:, :, idx, start:end] = mask + + image = F.interpolate(image, scale_factor=8, mode="nearest-exact") + image = image.movedim(1, -1) + + return (image, ) + + +latent_formats = {name: obj for name, obj in vars(comfy.latent_formats).items() if isinstance(obj, type)} +del latent_formats["LatentFormat"] + + +class VAEUtils_ScaleLatents: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "latents": ("LATENT", ), + "direction": (["scale", "unscale"], ), + "latent_type": (list(latent_formats.keys()), ), + } + } + + RETURN_TYPES = ("LATENT",) + FUNCTION = "scale" + CATEGORY = "VAE-Utils" + + def scale(self, latents, direction, latent_type): + latents = copy.deepcopy(latents) + latent_format = latent_formats[latent_type]() + + if direction == "scale": + latents["samples"] = latent_format.process_in(latents["samples"]) + else: + latents["samples"] = latent_format.process_out(latents["samples"]) + + return (latents, ) + + +COMBINED_MAPPINGS = { + "VAEUtils_CustomVAELoader": (VAEUtils_CustomVAELoader, "Load VAE (VAE Utils)"), + "VAEUtils_DisableVAEOffload": (VAEUtils_DisableVAEOffload, "Disable VAE Offload (VAE Utils)"), + "VAEUtils_VAEDecodeTiled": (VAEUtils_VAEDecodeTiled, "VAE Decode (VAE Utils)"), + "VAEUtils_LatentUpscale": (VAEUtils_LatentUpscale, "Latent Upscale (VAE Utils)"), + "VAEUtils_WanLatentPreview": (VAEUtils_WanLatentPreview, "Wan Latent Preview (VAE Utils)"), + "VAEUtils_TileModelPatch": (VAEUtils_TileModelPatch, "Tile Model Patch (VAE Utils)"), + "VAEUtils_VisualizeTiles": (VAEUtils_VisualizeTiles, "Visualize Tiles (VAE Utils)"), + "VAEUtils_ScaleLatents": (VAEUtils_ScaleLatents, "Scale/Unscale Latents (VAE Utils)"), +} \ No newline at end of file diff --git a/bundled_packs/ComfyUI-VAE-Utils/src/sd.py b/bundled_packs/ComfyUI-VAE-Utils/src/sd.py new file mode 100644 index 0000000..b5670a8 --- /dev/null +++ b/bundled_packs/ComfyUI-VAE-Utils/src/sd.py @@ -0,0 +1,420 @@ +import math +import json +import torch +import logging + +from comfy import model_management +from comfy.ldm.models.autoencoder import AutoencoderKL, AutoencodingEngine +# from comfy.ldm.cascade.stage_a import StageA +# from comfy.ldm.cascade.stage_c_coder import StageC_coder +# from comfy.ldm.audio.autoencoder import AudioOobleckVAE +# import comfy.ldm.genmo.vae.model +# import comfy.ldm.lightricks.vae.causal_video_autoencoder +# import comfy.ldm.cosmos.vae +from .wan.vae import WanVAE +import comfy.ldm.wan.vae2_2 +# import comfy.ldm.hunyuan3d.vae +# import comfy.ldm.ace.vae.music_dcae_pipeline +# import comfy.ldm.hunyuan_video.vae +# import comfy.ldm.mmaudio.vae.autoencoder +# import comfy.pixel_space_convert + +import comfy.utils + +import comfy.model_patcher +import comfy.taesd.taesd + +from comfy.sd import VAE + +class CustomVAE(VAE): + def __init__(self, sd=None, device=None, config=None, dtype=None, metadata=None): + if model_management.is_amd(): + VAE_KL_MEM_RATIO = 2.73 + else: + VAE_KL_MEM_RATIO = 1.0 + + self.memory_used_encode = lambda shape, dtype: (1767 * shape[2] * shape[3]) * model_management.dtype_size(dtype) * VAE_KL_MEM_RATIO #These are for AutoencoderKL and need tweaking (should be lower) + self.memory_used_decode = lambda shape, dtype: (2178 * shape[2] * shape[3] * 64) * model_management.dtype_size(dtype) * VAE_KL_MEM_RATIO + self.downscale_ratio = 8 + self.upscale_ratio = 8 + self.latent_channels = 4 + self.latent_dim = 2 + self.input_channels = 3 + self.output_channels = 3 + self.real_output_channels = 3 + self.pad_channel_value = None + self.process_input = lambda image: image * 2.0 - 1.0 + self.process_output = lambda image: torch.clamp((image + 1.0) / 2.0, min=0.0, max=1.0) + self.working_dtypes = [torch.bfloat16, torch.float32] + self.disable_offload = False + self.not_video = False + self.size = None + + self.downscale_index_formula = None + self.upscale_index_formula = None + self.extra_1d_channel = None + self.crop_input = True + + if config is None: + if "decoder.mid.block_1.mix_factor" in sd: + encoder_config = {'double_z': True, 'z_channels': 4, 'resolution': 256, 'in_channels': 3, 'out_ch': 3, 'ch': 128, 'ch_mult': [1, 2, 4, 4], 'num_res_blocks': 2, 'attn_resolutions': [], 'dropout': 0.0} + decoder_config = encoder_config.copy() + decoder_config["video_kernel_size"] = [3, 1, 1] + decoder_config["alpha"] = 0.0 + self.first_stage_model = AutoencodingEngine(regularizer_config={'target': "comfy.ldm.models.autoencoder.DiagonalGaussianRegularizer"}, + encoder_config={'target': "comfy.ldm.modules.diffusionmodules.model.Encoder", 'params': encoder_config}, + decoder_config={'target': "comfy.ldm.modules.temporal_ae.VideoDecoder", 'params': decoder_config}) + elif "taesd_decoder.1.weight" in sd: + self.latent_channels = sd["taesd_decoder.1.weight"].shape[1] + self.first_stage_model = comfy.taesd.taesd.TAESD(latent_channels=self.latent_channels) + # elif "vquantizer.codebook.weight" in sd: #VQGan: stage a of stable cascade + # self.first_stage_model = StageA() + # self.downscale_ratio = 4 + # self.upscale_ratio = 4 + # self.process_input = lambda image: image + # self.process_output = lambda image: image + # elif "backbone.1.0.block.0.1.num_batches_tracked" in sd: #effnet: encoder for stage c latent of stable cascade + # self.first_stage_model = StageC_coder() + # self.downscale_ratio = 32 + # self.latent_channels = 16 + # new_sd = {} + # for k in sd: + # new_sd["encoder.{}".format(k)] = sd[k] + # sd = new_sd + # elif "blocks.11.num_batches_tracked" in sd: #previewer: decoder for stage c latent of stable cascade + # self.first_stage_model = StageC_coder() + # self.latent_channels = 16 + # new_sd = {} + # for k in sd: + # new_sd["previewer.{}".format(k)] = sd[k] + # sd = new_sd + # elif "encoder.backbone.1.0.block.0.1.num_batches_tracked" in sd: #combined effnet and previewer for stable cascade + # self.first_stage_model = StageC_coder() + # self.downscale_ratio = 32 + # self.latent_channels = 16 + elif "decoder.conv_in.weight" in sd: + # if sd['decoder.conv_in.weight'].shape[1] == 64: + # ddconfig = {"block_out_channels": [128, 256, 512, 512, 1024, 1024], "in_channels": 3, "out_channels": 3, "num_res_blocks": 2, "ffactor_spatial": 32, "downsample_match_channel": True, "upsample_match_channel": True} + # self.latent_channels = ddconfig['z_channels'] = sd["decoder.conv_in.weight"].shape[1] + # self.downscale_ratio = 32 + # self.upscale_ratio = 32 + # self.working_dtypes = [torch.float16, torch.bfloat16, torch.float32] + # self.first_stage_model = AutoencodingEngine(regularizer_config={'target': "comfy.ldm.models.autoencoder.DiagonalGaussianRegularizer"}, + # encoder_config={'target': "comfy.ldm.hunyuan_video.vae.Encoder", 'params': ddconfig}, + # decoder_config={'target': "comfy.ldm.hunyuan_video.vae.Decoder", 'params': ddconfig}) + + # self.memory_used_encode = lambda shape, dtype: (700 * shape[2] * shape[3]) * model_management.dtype_size(dtype) + # self.memory_used_decode = lambda shape, dtype: (700 * shape[2] * shape[3] * 32 * 32) * model_management.dtype_size(dtype) + # elif sd['decoder.conv_in.weight'].shape[1] == 32: + # ddconfig = {"block_out_channels": [128, 256, 512, 1024, 1024], "in_channels": 3, "out_channels": 3, "num_res_blocks": 2, "ffactor_spatial": 16, "ffactor_temporal": 4, "downsample_match_channel": True, "upsample_match_channel": True, "refiner_vae": False} + # self.latent_channels = ddconfig['z_channels'] = sd["decoder.conv_in.weight"].shape[1] + # self.working_dtypes = [torch.float16, torch.bfloat16, torch.float32] + # self.upscale_ratio = (lambda a: max(0, a * 4 - 3), 16, 16) + # self.upscale_index_formula = (4, 16, 16) + # self.downscale_ratio = (lambda a: max(0, math.floor((a + 3) / 4)), 16, 16) + # self.downscale_index_formula = (4, 16, 16) + # self.latent_dim = 3 + # self.not_video = True + # self.first_stage_model = AutoencodingEngine(regularizer_config={'target': "comfy.ldm.models.autoencoder.DiagonalGaussianRegularizer"}, + # encoder_config={'target': "comfy.ldm.hunyuan_video.vae_refiner.Encoder", 'params': ddconfig}, + # decoder_config={'target': "comfy.ldm.hunyuan_video.vae_refiner.Decoder", 'params': ddconfig}) + + # self.memory_used_encode = lambda shape, dtype: (2800 * shape[-2] * shape[-1]) * model_management.dtype_size(dtype) + # self.memory_used_decode = lambda shape, dtype: (2800 * shape[-3] * shape[-2] * shape[-1] * 16 * 16) * model_management.dtype_size(dtype) + # else: + #default SD1.x/SD2.x VAE parameters + ddconfig = {'double_z': True, 'z_channels': 4, 'resolution': 256, 'in_channels': 3, 'out_ch': 3, 'ch': 128, 'ch_mult': [1, 2, 4, 4], 'num_res_blocks': 2, 'attn_resolutions': [], 'dropout': 0.0} + + if 'encoder.down.2.downsample.conv.weight' not in sd and 'decoder.up.3.upsample.conv.weight' not in sd: #Stable diffusion x4 upscaler VAE + ddconfig['ch_mult'] = [1, 2, 4] + self.downscale_ratio = 4 + self.upscale_ratio = 4 + + self.latent_channels = ddconfig['z_channels'] = sd["decoder.conv_in.weight"].shape[1] + if 'post_quant_conv.weight' in sd: + self.first_stage_model = AutoencoderKL(ddconfig=ddconfig, embed_dim=sd['post_quant_conv.weight'].shape[1]) + else: + self.first_stage_model = AutoencodingEngine(regularizer_config={'target': "comfy.ldm.models.autoencoder.DiagonalGaussianRegularizer"}, + encoder_config={'target': "comfy.ldm.modules.diffusionmodules.model.Encoder", 'params': ddconfig}, + decoder_config={'target': "comfy.ldm.modules.diffusionmodules.model.Decoder", 'params': ddconfig}) + # elif "decoder.layers.1.layers.0.beta" in sd: + # self.first_stage_model = AudioOobleckVAE() + # self.memory_used_encode = lambda shape, dtype: (1000 * shape[2]) * model_management.dtype_size(dtype) + # self.memory_used_decode = lambda shape, dtype: (1000 * shape[2] * 2048) * model_management.dtype_size(dtype) + # self.latent_channels = 64 + # self.output_channels = 2 + # self.upscale_ratio = 2048 + # self.downscale_ratio = 2048 + # self.latent_dim = 1 + # self.process_output = lambda audio: audio + # self.process_input = lambda audio: audio + # self.working_dtypes = [torch.float16, torch.bfloat16, torch.float32] + # self.disable_offload = True + # elif "blocks.2.blocks.3.stack.5.weight" in sd or "decoder.blocks.2.blocks.3.stack.5.weight" in sd or "layers.4.layers.1.attn_block.attn.qkv.weight" in sd or "encoder.layers.4.layers.1.attn_block.attn.qkv.weight" in sd: #genmo mochi vae + # if "blocks.2.blocks.3.stack.5.weight" in sd: + # sd = comfy.utils.state_dict_prefix_replace(sd, {"": "decoder."}) + # if "layers.4.layers.1.attn_block.attn.qkv.weight" in sd: + # sd = comfy.utils.state_dict_prefix_replace(sd, {"": "encoder."}) + # self.first_stage_model = comfy.ldm.genmo.vae.model.VideoVAE() + # self.latent_channels = 12 + # self.latent_dim = 3 + # self.memory_used_decode = lambda shape, dtype: (1000 * shape[2] * shape[3] * shape[4] * (6 * 8 * 8)) * model_management.dtype_size(dtype) + # self.memory_used_encode = lambda shape, dtype: (1.5 * max(shape[2], 7) * shape[3] * shape[4] * (6 * 8 * 8)) * model_management.dtype_size(dtype) + # self.upscale_ratio = (lambda a: max(0, a * 6 - 5), 8, 8) + # self.upscale_index_formula = (6, 8, 8) + # self.downscale_ratio = (lambda a: max(0, math.floor((a + 5) / 6)), 8, 8) + # self.downscale_index_formula = (6, 8, 8) + # self.working_dtypes = [torch.float16, torch.float32] + # elif "decoder.up_blocks.0.res_blocks.0.conv1.conv.weight" in sd: #lightricks ltxv + # tensor_conv1 = sd["decoder.up_blocks.0.res_blocks.0.conv1.conv.weight"] + # version = 0 + # if tensor_conv1.shape[0] == 512: + # version = 0 + # elif tensor_conv1.shape[0] == 1024: + # version = 1 + # if "encoder.down_blocks.1.conv.conv.bias" in sd: + # version = 2 + # vae_config = None + # if metadata is not None and "config" in metadata: + # vae_config = json.loads(metadata["config"]).get("vae", None) + # self.first_stage_model = comfy.ldm.lightricks.vae.causal_video_autoencoder.VideoVAE(version=version, config=vae_config) + # self.latent_channels = 128 + # self.latent_dim = 3 + # self.memory_used_decode = lambda shape, dtype: (900 * shape[2] * shape[3] * shape[4] * (8 * 8 * 8)) * model_management.dtype_size(dtype) + # self.memory_used_encode = lambda shape, dtype: (70 * max(shape[2], 7) * shape[3] * shape[4]) * model_management.dtype_size(dtype) + # self.upscale_ratio = (lambda a: max(0, a * 8 - 7), 32, 32) + # self.upscale_index_formula = (8, 32, 32) + # self.downscale_ratio = (lambda a: max(0, math.floor((a + 7) / 8)), 32, 32) + # self.downscale_index_formula = (8, 32, 32) + # self.working_dtypes = [torch.bfloat16, torch.float32] + # elif "decoder.conv_in.conv.weight" in sd and sd['decoder.conv_in.conv.weight'].shape[1] == 32: + # ddconfig = {"block_out_channels": [128, 256, 512, 1024, 1024], "in_channels": 3, "out_channels": 3, "num_res_blocks": 2, "ffactor_spatial": 16, "ffactor_temporal": 4, "downsample_match_channel": True, "upsample_match_channel": True} + # ddconfig['z_channels'] = sd["decoder.conv_in.conv.weight"].shape[1] + # self.latent_channels = 64 + # self.upscale_ratio = (lambda a: max(0, a * 4 - 3), 16, 16) + # self.upscale_index_formula = (4, 16, 16) + # self.downscale_ratio = (lambda a: max(0, math.floor((a + 3) / 4)), 16, 16) + # self.downscale_index_formula = (4, 16, 16) + # self.latent_dim = 3 + # self.not_video = True + # self.working_dtypes = [torch.float16, torch.bfloat16, torch.float32] + # self.first_stage_model = AutoencodingEngine(regularizer_config={'target': "comfy.ldm.models.autoencoder.EmptyRegularizer"}, + # encoder_config={'target': "comfy.ldm.hunyuan_video.vae_refiner.Encoder", 'params': ddconfig}, + # decoder_config={'target': "comfy.ldm.hunyuan_video.vae_refiner.Decoder", 'params': ddconfig}) + + # self.memory_used_encode = lambda shape, dtype: (1400 * shape[-2] * shape[-1]) * model_management.dtype_size(dtype) + # self.memory_used_decode = lambda shape, dtype: (1400 * shape[-3] * shape[-2] * shape[-1] * 16 * 16) * model_management.dtype_size(dtype) + elif "decoder.conv_in.conv.weight" in sd: + ddconfig = {'double_z': True, 'z_channels': 4, 'resolution': 256, 'in_channels': 3, 'out_ch': 3, 'ch': 128, 'ch_mult': [1, 2, 4, 4], 'num_res_blocks': 2, 'attn_resolutions': [], 'dropout': 0.0} + ddconfig["conv3d"] = True + ddconfig["time_compress"] = 4 + self.upscale_ratio = (lambda a: max(0, a * 4 - 3), 8, 8) + self.upscale_index_formula = (4, 8, 8) + self.downscale_ratio = (lambda a: max(0, math.floor((a + 3) / 4)), 8, 8) + self.downscale_index_formula = (4, 8, 8) + self.latent_dim = 3 + self.latent_channels = ddconfig['z_channels'] = sd["decoder.conv_in.conv.weight"].shape[1] + self.first_stage_model = AutoencoderKL(ddconfig=ddconfig, embed_dim=sd['post_quant_conv.weight'].shape[1]) + self.memory_used_decode = lambda shape, dtype: (1500 * shape[2] * shape[3] * shape[4] * (4 * 8 * 8)) * model_management.dtype_size(dtype) + self.memory_used_encode = lambda shape, dtype: (900 * max(shape[2], 2) * shape[3] * shape[4]) * model_management.dtype_size(dtype) + self.working_dtypes = [torch.bfloat16, torch.float16, torch.float32] + # elif "decoder.unpatcher3d.wavelets" in sd: + # self.upscale_ratio = (lambda a: max(0, a * 8 - 7), 8, 8) + # self.upscale_index_formula = (8, 8, 8) + # self.downscale_ratio = (lambda a: max(0, math.floor((a + 7) / 8)), 8, 8) + # self.downscale_index_formula = (8, 8, 8) + # self.latent_dim = 3 + # self.latent_channels = 16 + # ddconfig = {'z_channels': 16, 'latent_channels': self.latent_channels, 'z_factor': 1, 'resolution': 1024, 'in_channels': 3, 'out_channels': 3, 'channels': 128, 'channels_mult': [2, 4, 4], 'num_res_blocks': 2, 'attn_resolutions': [32], 'dropout': 0.0, 'patch_size': 4, 'num_groups': 1, 'temporal_compression': 8, 'spacial_compression': 8} + # self.first_stage_model = comfy.ldm.cosmos.vae.CausalContinuousVideoTokenizer(**ddconfig) + # TODO: these values are a bit off because this is not a standard VAE + # self.memory_used_decode = lambda shape, dtype: (50 * shape[2] * shape[3] * shape[4] * (8 * 8 * 8)) * model_management.dtype_size(dtype) + # self.memory_used_encode = lambda shape, dtype: (50 * (round((shape[2] + 7) / 8) * 8) * shape[3] * shape[4]) * model_management.dtype_size(dtype) + # self.working_dtypes = [torch.bfloat16, torch.float32] + elif "decoder.middle.0.residual.0.gamma" in sd: + if "decoder.upsamples.0.upsamples.0.residual.2.weight" in sd: # Wan 2.2 VAE + self.upscale_ratio = (lambda a: max(0, a * 4 - 3), 16, 16) + self.upscale_index_formula = (4, 16, 16) + self.downscale_ratio = (lambda a: max(0, math.floor((a + 3) / 4)), 16, 16) + self.downscale_index_formula = (4, 16, 16) + self.latent_dim = 3 + self.latent_channels = 48 + ddconfig = {"dim": 160, "z_dim": self.latent_channels, "dim_mult": [1, 2, 4, 4], "num_res_blocks": 2, "attn_scales": [], "temperal_downsample": [False, True, True], "dropout": 0.0} + self.first_stage_model = comfy.ldm.wan.vae2_2.WanVAE(**ddconfig) + self.working_dtypes = [torch.bfloat16, torch.float16, torch.float32] + self.memory_used_encode = lambda shape, dtype: 3300 * shape[3] * shape[4] * model_management.dtype_size(dtype) + self.memory_used_decode = lambda shape, dtype: 8000 * shape[3] * shape[4] * (16 * 16) * model_management.dtype_size(dtype) + else: # Wan 2.1 VAE + self.upscale_ratio = (lambda a: max(0, a * 4 - 3), 8, 8) + self.upscale_index_formula = (4, 8, 8) + self.downscale_ratio = (lambda a: max(0, math.floor((a + 3) / 4)), 8, 8) + self.downscale_index_formula = (4, 8, 8) + self.input_channels = sd["encoder.conv1.weight"].shape[1] + # self.output_channels = sd["decoder.head.2.weight"].shape[0] + self.output_channels = self.input_channels + self.real_output_channels = sd["decoder.head.2.weight"].shape[0] + self.latent_dim = 3 + self.latent_channels = 16 + self.pad_channel_value = 1.0 + + ddconfig = { + "in_channels": self.input_channels, + # "out_channels": self.output_channels, + "out_channels": self.real_output_channels, + "dim": 96, + "z_dim": self.latent_channels, + "dim_mult": [1, 2, 4, 4], + "num_res_blocks": 2, + "attn_scales": [], + "temperal_downsample": [False, True, True], + "dropout": 0.0, + } + self.first_stage_model = WanVAE(**ddconfig) + self.working_dtypes = [torch.bfloat16, torch.float16, torch.float32] + self.memory_used_encode = lambda shape, dtype: 6000 * shape[3] * shape[4] * model_management.dtype_size(dtype) + self.memory_used_decode = lambda shape, dtype: 7000 * shape[3] * shape[4] * (8 * 8) * model_management.dtype_size(dtype) + # Hunyuan 3d v2 2.0 & 2.1 + # elif "geo_decoder.cross_attn_decoder.ln_1.bias" in sd: + # self.latent_dim = 1 + + # def estimate_memory(shape, dtype, num_layers = 16, kv_cache_multiplier = 2): + # batch, num_tokens, hidden_dim = shape + # dtype_size = model_management.dtype_size(dtype) + + # total_mem = batch * num_tokens * hidden_dim * dtype_size * (1 + kv_cache_multiplier * num_layers) + # return total_mem + + # better memory estimations + # self.memory_used_encode = lambda shape, dtype, num_layers = 8, kv_cache_multiplier = 0:\ + # estimate_memory(shape, dtype, num_layers, kv_cache_multiplier) + + # self.memory_used_decode = lambda shape, dtype, num_layers = 16, kv_cache_multiplier = 2: \ + # estimate_memory(shape, dtype, num_layers, kv_cache_multiplier) + + # self.first_stage_model = comfy.ldm.hunyuan3d.vae.ShapeVAE() + # self.working_dtypes = [torch.float16, torch.bfloat16, torch.float32] + # elif "vocoder.backbone.channel_layers.0.0.bias" in sd: #Ace Step Audio + # self.first_stage_model = comfy.ldm.ace.vae.music_dcae_pipeline.MusicDCAE(source_sample_rate=44100) + # self.memory_used_encode = lambda shape, dtype: (shape[2] * 330) * model_management.dtype_size(dtype) + # self.memory_used_decode = lambda shape, dtype: (shape[2] * shape[3] * 87000) * model_management.dtype_size(dtype) + # self.latent_channels = 8 + # self.output_channels = 2 + # self.upscale_ratio = 4096 + # self.downscale_ratio = 4096 + # self.latent_dim = 2 + # self.process_output = lambda audio: audio + # self.process_input = lambda audio: audio + # self.working_dtypes = [torch.bfloat16, torch.float16, torch.float32] + # self.disable_offload = True + # self.extra_1d_channel = 16 + # elif "pixel_space_vae" in sd: + # self.first_stage_model = comfy.pixel_space_convert.PixelspaceConversionVAE() + # self.memory_used_encode = lambda shape, dtype: (1 * shape[2] * shape[3]) * model_management.dtype_size(dtype) + # self.memory_used_decode = lambda shape, dtype: (1 * shape[2] * shape[3]) * model_management.dtype_size(dtype) + # self.downscale_ratio = 1 + # self.upscale_ratio = 1 + # self.latent_channels = 3 + # self.latent_dim = 2 + # self.output_channels = 3 + # elif "vocoder.activation_post.downsample.lowpass.filter" in sd: #MMAudio VAE + # sample_rate = 16000 + # if sample_rate == 16000: + # mode = '16k' + # else: + # mode = '44k' + + # self.first_stage_model = comfy.ldm.mmaudio.vae.autoencoder.AudioAutoencoder(mode=mode) + # self.memory_used_encode = lambda shape, dtype: (30 * shape[2]) * model_management.dtype_size(dtype) + # self.memory_used_decode = lambda shape, dtype: (90 * shape[2] * 1411.2) * model_management.dtype_size(dtype) + # self.latent_channels = 20 + # self.output_channels = 2 + # self.upscale_ratio = 512 * (44100 / sample_rate) + # self.downscale_ratio = 512 * (44100 / sample_rate) + # self.latent_dim = 1 + # self.process_output = lambda audio: audio + # self.process_input = lambda audio: audio + # self.working_dtypes = [torch.float32] + # self.crop_input = False + else: + logging.warning("WARNING: No VAE weights detected, VAE not initalized.") + self.first_stage_model = None + return + else: + self.first_stage_model = AutoencoderKL(**(config['params'])) + self.first_stage_model = self.first_stage_model.eval() + + m, u = self.first_stage_model.load_state_dict(sd, strict=False) + if len(m) > 0: + logging.warning("Missing VAE keys {}".format(m)) + + if len(u) > 0: + logging.debug("Leftover VAE keys {}".format(u)) + + if device is None: + device = model_management.vae_device() + self.device = device + offload_device = model_management.vae_offload_device() + if dtype is None: + dtype = model_management.vae_dtype(self.device, self.working_dtypes) + self.vae_dtype = dtype + self.first_stage_model.to(self.vae_dtype) + self.output_device = model_management.intermediate_device() + + self.patcher = comfy.model_patcher.ModelPatcher(self.first_stage_model, load_device=self.device, offload_device=offload_device) + logging.info("VAE load device: {}, offload device: {}, dtype: {}".format(self.device, offload_device, self.vae_dtype)) + + def decode(self, samples_in): + """ + Override VAE.decode() to fix a regression introduced in ComfyUI PRs #11405 / #11406 + (merged Dec 18 2025). Those PRs changed the 3D untiled decode path inside + VAE.decode() so that it no longer routes through decode_tiled_3d for subclasses; + it now does inline channel allocation and trimming that breaks CustomVAE because it + only sees self.output_channels (= input_channels = 3) and ignores + self.real_output_channels (the actual decoder head output count, e.g. 12 for the + Wan 2x upscale VAE). process_output was also no longer guaranteed to be applied + through that path. + + For 2D VAEs (latent_dim != 3) we delegate to the parent as before. + For 3D VAEs (WanVAE family: Wan 2.1, Wan 2.2, Qwen-Image, Wan 2x upscale) we do + the model-loading bookkeeping ourselves, then call our own decode_tiled_3d which + already handles real_output_channels and process_output correctly. + """ + if self.latent_dim != 3: + return super().decode(samples_in) + + # Mirror the memory-management preamble from the parent's decode(). + memory_used = self.memory_used_decode(samples_in.shape, self.vae_dtype) + model_management.load_models_gpu([self.patcher], memory_required=memory_used) + + # decode_tiled_3d returns process_output(tiled_scale_multidim(...)) whose shape is + # [B, real_output_channels, T_out, H_out, W_out] (5-D for multi-frame video) + # or [B, real_output_channels, H_out, W_out] (4-D if tiled_scale_multidim + # squeezed the temporal dim) + images = self.decode_tiled_3d(samples_in) + + # Normalise to [B*T, H, W, C] — same layout that the parent returns and that + # VAEUtils_VAEDecodeTiled.decode() expects (it has its own 5-D reshape guard too). + if images.ndim == 5: + B, C, T, H, W = images.shape + images = images.permute(0, 2, 3, 4, 1).reshape(B * T, H, W, C) + elif images.ndim == 4: + # Already [B, C, H, W] — just move channels to last dim. + images = images.movedim(1, -1) + # ndim == 3 or anything unexpected: leave as-is and let downstream handle it. + + return images + + def decode_tiled_3d(self, samples, tile_t=999, tile_x=32, tile_y=32, overlap=(1, 8, 8)): + decode_fn = lambda a: self.first_stage_model.decode(a.to(self.vae_dtype).to(self.device)).float() + return self.process_output( + comfy.utils.tiled_scale_multidim( + samples, + decode_fn, + tile=(tile_t, tile_x, tile_y), + overlap=overlap, + upscale_amount=self.upscale_ratio, + out_channels=self.real_output_channels, + index_formulas=self.upscale_index_formula, + output_device=self.output_device, + ) + ) diff --git a/bundled_packs/ComfyUI-VAE-Utils/src/wan/vae.py b/bundled_packs/ComfyUI-VAE-Utils/src/wan/vae.py new file mode 100644 index 0000000..f01fa87 --- /dev/null +++ b/bundled_packs/ComfyUI-VAE-Utils/src/wan/vae.py @@ -0,0 +1,521 @@ +# original version: https://github.com/Wan-Video/Wan2.1/blob/main/wan/modules/vae.py +# Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved. + +import torch +import torch.nn as nn +import torch.nn.functional as F +from einops import rearrange +from comfy.ldm.modules.diffusionmodules.model import vae_attention + +import comfy.ops +ops = comfy.ops.disable_weight_init + +CACHE_T = 2 + + +class CausalConv3d(ops.Conv3d): + """ + Causal 3d convolusion. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._padding = (self.padding[2], self.padding[2], self.padding[1], + self.padding[1], 2 * self.padding[0], 0) + self.padding = (0, 0, 0) + + def forward(self, x, cache_x=None, cache_list=None, cache_idx=None): + if cache_list is not None: + cache_x = cache_list[cache_idx] + cache_list[cache_idx] = None + + padding = list(self._padding) + if cache_x is not None and self._padding[4] > 0: + cache_x = cache_x.to(x.device) + x = torch.cat([cache_x, x], dim=2) + padding[4] -= cache_x.shape[2] + del cache_x + x = F.pad(x, padding) + + return super().forward(x) + + +class RMS_norm(nn.Module): + + def __init__(self, dim, channel_first=True, images=True, bias=False): + super().__init__() + broadcastable_dims = (1, 1, 1) if not images else (1, 1) + shape = (dim, *broadcastable_dims) if channel_first else (dim,) + + self.channel_first = channel_first + self.scale = dim**0.5 + self.gamma = nn.Parameter(torch.ones(shape)) + self.bias = nn.Parameter(torch.zeros(shape)) if bias else None + + def forward(self, x): + return F.normalize( + x, dim=(1 if self.channel_first else -1)) * self.scale * self.gamma.to(x) + (self.bias.to(x) if self.bias is not None else 0) + + +class Resample(nn.Module): + + def __init__(self, dim, mode): + assert mode in ('none', 'upsample2d', 'upsample3d', 'downsample2d', + 'downsample3d') + super().__init__() + self.dim = dim + self.mode = mode + + # layers + if mode == 'upsample2d': + self.resample = nn.Sequential( + nn.Upsample(scale_factor=(2., 2.), mode='nearest-exact'), + ops.Conv2d(dim, dim // 2, 3, padding=1)) + elif mode == 'upsample3d': + self.resample = nn.Sequential( + nn.Upsample(scale_factor=(2., 2.), mode='nearest-exact'), + ops.Conv2d(dim, dim // 2, 3, padding=1)) + self.time_conv = CausalConv3d( + dim, dim * 2, (3, 1, 1), padding=(1, 0, 0)) + + elif mode == 'downsample2d': + self.resample = nn.Sequential( + nn.ZeroPad2d((0, 1, 0, 1)), + ops.Conv2d(dim, dim, 3, stride=(2, 2))) + elif mode == 'downsample3d': + self.resample = nn.Sequential( + nn.ZeroPad2d((0, 1, 0, 1)), + ops.Conv2d(dim, dim, 3, stride=(2, 2))) + self.time_conv = CausalConv3d( + dim, dim, (3, 1, 1), stride=(2, 1, 1), padding=(0, 0, 0)) + + else: + self.resample = nn.Identity() + + def forward(self, x, feat_cache=None, feat_idx=[0]): + b, c, t, h, w = x.size() + if self.mode == 'upsample3d': + if feat_cache is not None: + idx = feat_idx[0] + if feat_cache[idx] is None: + feat_cache[idx] = 'Rep' + feat_idx[0] += 1 + else: + + cache_x = x[:, :, -CACHE_T:, :, :].clone() + if cache_x.shape[2] < 2 and feat_cache[ + idx] is not None and feat_cache[idx] != 'Rep': + # cache last frame of last two chunk + cache_x = torch.cat([ + feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to( + cache_x.device), cache_x + ], + dim=2) + if cache_x.shape[2] < 2 and feat_cache[ + idx] is not None and feat_cache[idx] == 'Rep': + cache_x = torch.cat([ + torch.zeros_like(cache_x).to(cache_x.device), + cache_x + ], + dim=2) + if feat_cache[idx] == 'Rep': + x = self.time_conv(x) + else: + x = self.time_conv(x, feat_cache[idx]) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + + x = x.reshape(b, 2, c, t, h, w) + x = torch.stack((x[:, 0, :, :, :, :], x[:, 1, :, :, :, :]), + 3) + x = x.reshape(b, c, t * 2, h, w) + t = x.shape[2] + x = rearrange(x, 'b c t h w -> (b t) c h w') + x = self.resample(x) + x = rearrange(x, '(b t) c h w -> b c t h w', t=t) + + if self.mode == 'downsample3d': + if feat_cache is not None: + idx = feat_idx[0] + if feat_cache[idx] is None: + feat_cache[idx] = x.clone() + feat_idx[0] += 1 + else: + + cache_x = x[:, :, -1:, :, :].clone() + # if cache_x.shape[2] < 2 and feat_cache[idx] is not None and feat_cache[idx]!='Rep': + # # cache last frame of last two chunk + # cache_x = torch.cat([feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device), cache_x], dim=2) + + x = self.time_conv( + torch.cat([feat_cache[idx][:, :, -1:, :, :], x], 2)) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + return x + + +class ResidualBlock(nn.Module): + + def __init__(self, in_dim, out_dim, dropout=0.0): + super().__init__() + self.in_dim = in_dim + self.out_dim = out_dim + + # layers + self.residual = nn.Sequential( + RMS_norm(in_dim, images=False), nn.SiLU(), + CausalConv3d(in_dim, out_dim, 3, padding=1), + RMS_norm(out_dim, images=False), nn.SiLU(), nn.Dropout(dropout), + CausalConv3d(out_dim, out_dim, 3, padding=1)) + self.shortcut = CausalConv3d(in_dim, out_dim, 1) \ + if in_dim != out_dim else nn.Identity() + + def forward(self, x, feat_cache=None, feat_idx=[0]): + old_x = x + for layer in self.residual: + if isinstance(layer, CausalConv3d) and feat_cache is not None: + idx = feat_idx[0] + cache_x = x[:, :, -CACHE_T:, :, :].clone() + if cache_x.shape[2] < 2 and feat_cache[idx] is not None: + # cache last frame of last two chunk + cache_x = torch.cat([ + feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to( + cache_x.device), cache_x + ], + dim=2) + x = layer(x, cache_list=feat_cache, cache_idx=idx) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + else: + x = layer(x) + return x + self.shortcut(old_x) + + +class AttentionBlock(nn.Module): + """ + Causal self-attention with a single head. + """ + + def __init__(self, dim): + super().__init__() + self.dim = dim + + # layers + self.norm = RMS_norm(dim) + self.to_qkv = ops.Conv2d(dim, dim * 3, 1) + self.proj = ops.Conv2d(dim, dim, 1) + self.optimized_attention = vae_attention() + + def forward(self, x): + identity = x + b, c, t, h, w = x.size() + x = rearrange(x, 'b c t h w -> (b t) c h w') + x = self.norm(x) + # compute query, key, value + + q, k, v = self.to_qkv(x).chunk(3, dim=1) + x = self.optimized_attention(q, k, v) + + # output + x = self.proj(x) + x = rearrange(x, '(b t) c h w-> b c t h w', t=t) + return x + identity + + +class Encoder3d(nn.Module): + + def __init__(self, + in_channels=3, + dim=128, + z_dim=4, + dim_mult=[1, 2, 4, 4], + num_res_blocks=2, + attn_scales=[], + temperal_downsample=[True, True, False], + dropout=0.0): + super().__init__() + self.in_channels = in_channels + self.dim = dim + self.z_dim = z_dim + self.dim_mult = dim_mult + self.num_res_blocks = num_res_blocks + self.attn_scales = attn_scales + self.temperal_downsample = temperal_downsample + + # dimensions + dims = [dim * u for u in [1] + dim_mult] + scale = 1.0 + + # init block + self.conv1 = CausalConv3d(in_channels, dims[0], 3, padding=1) + + # downsample blocks + downsamples = [] + for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])): + # residual (+attention) blocks + for _ in range(num_res_blocks): + downsamples.append(ResidualBlock(in_dim, out_dim, dropout)) + if scale in attn_scales: + downsamples.append(AttentionBlock(out_dim)) + in_dim = out_dim + + # downsample block + if i != len(dim_mult) - 1: + mode = 'downsample3d' if temperal_downsample[ + i] else 'downsample2d' + downsamples.append(Resample(out_dim, mode=mode)) + scale /= 2.0 + self.downsamples = nn.Sequential(*downsamples) + + # middle blocks + self.middle = nn.Sequential( + ResidualBlock(out_dim, out_dim, dropout), AttentionBlock(out_dim), + ResidualBlock(out_dim, out_dim, dropout)) + + # output blocks + self.head = nn.Sequential( + RMS_norm(out_dim, images=False), nn.SiLU(), + CausalConv3d(out_dim, z_dim, 3, padding=1)) + + def forward(self, x, feat_cache=None, feat_idx=[0]): + if feat_cache is not None: + idx = feat_idx[0] + cache_x = x[:, :, -CACHE_T:, :, :].clone() + if cache_x.shape[2] < 2 and feat_cache[idx] is not None: + # cache last frame of last two chunk + cache_x = torch.cat([ + feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to( + cache_x.device), cache_x + ], + dim=2) + x = self.conv1(x, feat_cache[idx]) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + else: + x = self.conv1(x) + + ## downsamples + for layer in self.downsamples: + if feat_cache is not None: + x = layer(x, feat_cache, feat_idx) + else: + x = layer(x) + + ## middle + for layer in self.middle: + if isinstance(layer, ResidualBlock) and feat_cache is not None: + x = layer(x, feat_cache, feat_idx) + else: + x = layer(x) + + ## head + for layer in self.head: + if isinstance(layer, CausalConv3d) and feat_cache is not None: + idx = feat_idx[0] + cache_x = x[:, :, -CACHE_T:, :, :].clone() + if cache_x.shape[2] < 2 and feat_cache[idx] is not None: + # cache last frame of last two chunk + cache_x = torch.cat([ + feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to( + cache_x.device), cache_x + ], + dim=2) + x = layer(x, feat_cache[idx]) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + else: + x = layer(x) + return x + + +class Decoder3d(nn.Module): + + def __init__(self, + out_channels=3, + dim=128, + z_dim=4, + dim_mult=[1, 2, 4, 4], + num_res_blocks=2, + attn_scales=[], + temperal_upsample=[False, True, True], + dropout=0.0): + super().__init__() + self.out_channels = out_channels + self.dim = dim + self.z_dim = z_dim + self.dim_mult = dim_mult + self.num_res_blocks = num_res_blocks + self.attn_scales = attn_scales + self.temperal_upsample = temperal_upsample + + # dimensions + dims = [dim * u for u in [dim_mult[-1]] + dim_mult[::-1]] + scale = 1.0 / 2**(len(dim_mult) - 2) + + # init block + self.conv1 = CausalConv3d(z_dim, dims[0], 3, padding=1) + + # middle blocks + self.middle = nn.Sequential( + ResidualBlock(dims[0], dims[0], dropout), AttentionBlock(dims[0]), + ResidualBlock(dims[0], dims[0], dropout)) + + # upsample blocks + upsamples = [] + for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])): + # residual (+attention) blocks + if i == 1 or i == 2 or i == 3: + in_dim = in_dim // 2 + for _ in range(num_res_blocks + 1): + upsamples.append(ResidualBlock(in_dim, out_dim, dropout)) + if scale in attn_scales: + upsamples.append(AttentionBlock(out_dim)) + in_dim = out_dim + + # upsample block + if i != len(dim_mult) - 1: + mode = 'upsample3d' if temperal_upsample[i] else 'upsample2d' + upsamples.append(Resample(out_dim, mode=mode)) + scale *= 2.0 + self.upsamples = nn.Sequential(*upsamples) + + # output blocks + self.head = nn.Sequential( + RMS_norm(out_dim, images=False), nn.SiLU(), + CausalConv3d(out_dim, out_channels, 3, padding=1)) + + def forward(self, x, feat_cache=None, feat_idx=[0]): + ## conv1 + if feat_cache is not None: + idx = feat_idx[0] + cache_x = x[:, :, -CACHE_T:, :, :].clone() + if cache_x.shape[2] < 2 and feat_cache[idx] is not None: + # cache last frame of last two chunk + cache_x = torch.cat([ + feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to( + cache_x.device), cache_x + ], + dim=2) + x = self.conv1(x, feat_cache[idx]) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + else: + x = self.conv1(x) + + ## middle + for layer in self.middle: + if isinstance(layer, ResidualBlock) and feat_cache is not None: + x = layer(x, feat_cache, feat_idx) + else: + x = layer(x) + + ## upsamples + for layer in self.upsamples: + if feat_cache is not None: + x = layer(x, feat_cache, feat_idx) + else: + x = layer(x) + + ## head + for layer in self.head: + if isinstance(layer, CausalConv3d) and feat_cache is not None: + idx = feat_idx[0] + cache_x = x[:, :, -CACHE_T:, :, :].clone() + if cache_x.shape[2] < 2 and feat_cache[idx] is not None: + # cache last frame of last two chunk + cache_x = torch.cat([ + feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to( + cache_x.device), cache_x + ], + dim=2) + x = layer(x, feat_cache[idx]) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + else: + x = layer(x) + return x + + +def count_conv3d(model): + count = 0 + for m in model.modules(): + if isinstance(m, CausalConv3d): + count += 1 + return count + + +class WanVAE(nn.Module): + + def __init__(self, + in_channels=3, + out_channels=3, + dim=128, + z_dim=4, + dim_mult=[1, 2, 4, 4], + num_res_blocks=2, + attn_scales=[], + temperal_downsample=[True, True, False], + dropout=0.0): + super().__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.dim = dim + self.z_dim = z_dim + self.dim_mult = dim_mult + self.num_res_blocks = num_res_blocks + self.attn_scales = attn_scales + self.temperal_downsample = temperal_downsample + self.temperal_upsample = temperal_downsample[::-1] + + # modules + self.encoder = Encoder3d(in_channels, dim, z_dim * 2, dim_mult, num_res_blocks, + attn_scales, self.temperal_downsample, dropout) + self.conv1 = CausalConv3d(z_dim * 2, z_dim * 2, 1) + self.conv2 = CausalConv3d(z_dim, z_dim, 1) + self.decoder = Decoder3d(out_channels, dim, z_dim, dim_mult, num_res_blocks, + attn_scales, self.temperal_upsample, dropout) + + def encode(self, x): + conv_idx = [0] + feat_map = [None] * count_conv3d(self.decoder) + ## cache + t = x.shape[2] + iter_ = 1 + (t - 1) // 4 + ## 对encode输入的x,按时间拆分为1、4、4、4.... + for i in range(iter_): + conv_idx = [0] + if i == 0: + out = self.encoder( + x[:, :, :1, :, :], + feat_cache=feat_map, + feat_idx=conv_idx) + else: + out_ = self.encoder( + x[:, :, 1 + 4 * (i - 1):1 + 4 * i, :, :], + feat_cache=feat_map, + feat_idx=conv_idx) + out = torch.cat([out, out_], 2) + mu, log_var = self.conv1(out).chunk(2, dim=1) + return mu + + def decode(self, z): + conv_idx = [0] + feat_map = [None] * count_conv3d(self.decoder) + # z: [b,c,t,h,w] + + iter_ = z.shape[2] + x = self.conv2(z) + for i in range(iter_): + conv_idx = [0] + if i == 0: + out = self.decoder( + x[:, :, i:i + 1, :, :], + feat_cache=feat_map, + feat_idx=conv_idx) + else: + out_ = self.decoder( + x[:, :, i:i + 1, :, :], + feat_cache=feat_map, + feat_idx=conv_idx) + out = torch.cat([out, out_], 2) + return out diff --git a/bundled_packs/_BUNDLED.txt b/bundled_packs/_BUNDLED.txt index ffd3320..4c16cf0 100644 --- a/bundled_packs/_BUNDLED.txt +++ b/bundled_packs/_BUNDLED.txt @@ -22,6 +22,8 @@ Source provenance (owner/repo @ commit — license): ComfyUI-SeedVR2_VideoUpscaler numz/ComfyUI-SeedVR2_VideoUpscaler @ 4490bd1f482e026674543386bb2a4d176da245b9 Apache-2.0 ComfyUI_IPAdapter_plus cubiq/ComfyUI_IPAdapter_plus @ a0f451a5 GPL-3.0 ComfyUI_UltimateSDUpscale ssitu/ComfyUI_UltimateSDUpscale @ d6b575a (patched) GPL-3.0 + ComfyUI-ConditioningKrea2Rebalance nova452/ComfyUI-ConditioningKrea2Rebalance @ 9ab5315e6aa8 Apache-2.0 + ComfyUI-VAE-Utils spacepxl/ComfyUI-VAE-Utils @ d69e7afaa9ed (assets/workflow imgs removed) MIT UltimateSDUpscale local patches (in its __init__.py, both preserved on copy-out): 1. The bundled Auto1111 repositories/ submodule is shipped pre-filled, so the diff --git a/core/install_sections.py b/core/install_sections.py index 2a8e7a7..ea44169 100644 --- a/core/install_sections.py +++ b/core/install_sections.py @@ -57,6 +57,9 @@ SECTIONS: list[dict] = [ {"id": "face_detailer", "label": "Face Detailer", "size": "~6 MB", "desc": "Region-correct face detailing", "members": [_np("RegionalDetailer")]}, + {"id": "krea2_highdetail", "label": "Krea 2 High Detail", "size": "~510 MB", + "desc": "Clean 2× decoder + conditioning rebalance for the Krea 2 High-Detail templates", + "members": [_np("Krea2HighDetail")]}, {"id": "controlnet", "label": "ControlNet", "size": "~5 GB SDXL · up to ~22 GB all", "desc": "Depth / pose / canny / edge guidance (SDXL · Flux · Z-Image)", "members": [_np("ControlNet"), _np("FluxControlNet"), diff --git a/core/model_api.py b/core/model_api.py index cf31a78..ec8a378 100644 --- a/core/model_api.py +++ b/core/model_api.py @@ -98,6 +98,16 @@ async def _api_list_models(request): and model_settings.is_primary_model_file(m["filename"])) or not model_settings.is_catalog_filename(m["filename"]) ] + # The fingerprint scanner assigns architecture from header patterns; newer + # DiT families (e.g. krea2) have no pattern yet and read as "unknown", which + # drops them from the upscale picker's engine list. Their curated config + # (matched by filename) carries the authoritative architecture — adopt it so + # the right re-detail engine is offered. + for m in filtered: + if m.get("architecture") in ("", "unknown", None): + config = model_settings.find_config_by_filename(m["filename"]) + if config and config.get("architecture"): + m["architecture"] = config["architecture"] return web.json_response({"models": filtered}) diff --git a/core/node_install_api.py b/core/node_install_api.py index 5b3a503..c45f27d 100644 --- a/core/node_install_api.py +++ b/core/node_install_api.py @@ -147,6 +147,20 @@ NODE_PACKS: dict[str, dict] = { "dest": "ultralytics/bbox/face_yolov8n.pt"}, ], }, + "Krea2HighDetail": { + "label": "Krea 2 High Detail", + # The High-Detail krea2 templates (t2i + i2i) need two packs: nova452's + # ConditioningKrea2Rebalance (a cond amplifier) and spacepxl's VAE-Utils + # (its VAEUtils_VAEDecodeTiled + CustomVAELoader run the clean 2x decode). + # Neither has pip deps. VAE-Utils ships no model downloader, so we fetch + # the spacepxl decoder weight into models/vae for CustomVAELoader to pick. + "bundle": ["ComfyUI-ConditioningKrea2Rebalance", "ComfyUI-VAE-Utils"], + "proof_nodes": ["ConditioningKrea2Rebalance", "VAEUtils_VAEDecodeTiled"], + "models": [ + {"url": "https://huggingface.co/spacepxl/Wan2.1-VAE-upscale2x/resolve/main/Wan2.1_VAE_upscale2x_imageonly_real_v1.safetensors", + "dest": "vae/Wan2.1_VAE_upscale2x_imageonly_real_v1.safetensors"}, + ], + }, "StyleReference": { "label": "Style Reference", # cubiq's IPAdapter pack is BUNDLED (bundled_packs/ComfyUI_IPAdapter_plus), diff --git a/data/models/krea2_raw.json b/data/models/krea2_raw.json new file mode 100644 index 0000000..5315abf --- /dev/null +++ b/data/models/krea2_raw.json @@ -0,0 +1,164 @@ +{ + "display_name": "Krea 2 - RAW", + "model_name": "Krea 2 RAW", + "version": "RAW", + "architecture": "krea2", + "family": "krea2_raw", + "author": "Krea AI", + "description": "Krea 2 RAW — the undistilled 12B base checkpoint (full-step, real cfg). Krea ships it for fine-tuning/LoRA training, but because it is NOT step-distilled it re-details cleanly at a LOW denoise — which makes it the right model for a faithful, blotch-free TILED upscale (the distilled Turbo can't: low denoise blotches, high denoise seams). Same Qwen3-VL encoder + Qwen Image VAE as Turbo.", + "license": "Krea 2 Community License Agreement v.1 (open weights; commercial use requires an Enterprise License once total annual revenue reaches $1M)", + "tags": [ + "krea", + "krea2", + "dit", + "transformer", + "photorealistic", + "raw", + "base", + "undistilled" + ], + "url": "https://huggingface.co/Comfy-Org/Krea-2", + "release_date": "2026-06-18", + "tag_sources": [], + "tag_format": "spaces", + "prompt_style": "natural", + "trigger": "", + "default_prompt_id": "krea2-default", + "negative": "", + "notes": "Undistilled base: full-step sampling with a REAL cfg. Render recipe (52 steps / cfg 3.5 / er_sde+simple) is DOCUMENTED — Krea's official CLI is `--checkpoint oss_raw --steps 52 --cfg 3.5`. Negatives use the real CFG branch but efficacy on the Qwen3-VL encoder is A/B-pending (may be inert like Qwen-Image). Shift is baked into the model (no ModelSamplingAuraFlow needed; mu~1.15 at 1024). Shares qwen3vl_4b + qwen_image_vae with Turbo. ~13 GB fp8 / ~26 GB bf16. Slow (no distillation) — for UPSCALE re-detail the JS engine uses fewer steps at low denoise, this 52-step recipe is for from-scratch t2i. RESOLUTION: RAW is DOCUMENTED 'up to 1024' — keep BOTH dims <=1024 (1024x1024 is its sweet spot; long edge >1024 like 1152/1216 melts faces — confirmed). Turbo is the 1k-2k model. For bigger output, render <=1024 then upscale (RAW tiles at 1024 = exactly its trained size, why the tiled upscale is so clean). SHIFT FIX (critical): ComfyUI hardcodes flow-shift mu=1.15 (Turbo's value) for the WHOLE krea2 class, but RAW needs RESOLUTION-DYNAMIC shifting (mu~0.906 @ 1024^2, smaller below) — the over-shift compresses the detail tail and melts faces. The raw t2i/i2i templates insert a ModelSamplingFlux node (base_shift 0.5, max_shift 0.90625, width/height = render size) which REPLACES the bad baked shift (verified against ComfyUI master: supported_models.py Krea2 shift=1.15, ModelType.FLUX). SHIP RECIPE (quality-research-tuned): 52 steps / cfg 5.0 / er_sde / beta — DOCUMENTED crispest-photoreal combo; euler/simple is doubly-soft (euler smooths micro-texture, simple under-weights the detail band). cfg 5.0 ~ Krea --cfg 3.5 in ComfyUI's (1+g) convention + diffusers' native 4.5. ModelSamplingFlux width/height MUST match render size or mu over-shifts on non-square buckets (1024^2->0.906, 896x1024->0.852, 832x1024->0.825, 768x1024->0.798). Turbo needs no shift node (its real mu IS 1.15). Without the ModelSamplingFlux node a RAW render melts.", + "files": [ + { + "label": "Diffusion Model", + "folder": "diffusion_models", + "variants": [ + { + "precision": "fp8", + "filename": "krea2_raw_fp8_scaled.safetensors", + "size_bytes": 13141730784, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "diffusion_models/krea2_raw_fp8_scaled.safetensors" + }, + "quick_hash": null, + "sha256": null + }, + { + "precision": "bf16", + "filename": "krea2_raw_bf16.safetensors", + "size_bytes": 26283332608, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "diffusion_models/krea2_raw_bf16.safetensors" + }, + "quick_hash": null, + "sha256": null + } + ] + }, + { + "label": "Text Encoder", + "folder": "text_encoders", + "variants": [ + { + "precision": "fp8", + "filename": "qwen3vl_4b_fp8_scaled.safetensors", + "size_bytes": 5242467968, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "text_encoders/qwen3vl_4b_fp8_scaled.safetensors" + }, + "sha256": null + }, + { + "precision": "bf16", + "filename": "qwen3vl_4b_bf16.safetensors", + "size_bytes": 8875719384, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "text_encoders/qwen3vl_4b_bf16.safetensors" + }, + "sha256": null + } + ] + }, + { + "label": "VAE", + "folder": "vae", + "filename": "qwen_image_vae.safetensors", + "size_bytes": 253806246, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "vae/qwen_image_vae.safetensors" + }, + "sha256": null + } + ], + "nodes": { + "KSampler": { + "steps": 52, + "cfg": 5.0, + "sampler_name": "er_sde", + "scheduler": "beta", + "steps_range": [ + 40, + 60 + ], + "cfg_range": [ + 3.5, + 6.0 + ], + "sampler_name_options": [ + "er_sde", + "dpmpp_2m", + "euler", + "dpmpp_sde", + "euler_ancestral" + ], + "scheduler_options": [ + "beta", + "simple", + "sgm_uniform", + "normal" + ] + }, + "EmptySD3LatentImage": { + "width": 1024, + "height": 1024, + "_resolutions": [ + { + "width": 1024, + "height": 1024 + }, + { + "width": 832, + "height": 1024 + }, + { + "width": 1024, + "height": 832 + }, + { + "width": 768, + "height": 1024 + }, + { + "width": 1024, + "height": 768 + }, + { + "width": 896, + "height": 1024 + }, + { + "width": 1024, + "height": 896 + } + ] + } + } +} \ No newline at end of file diff --git a/data/models/krea2_turbo.json b/data/models/krea2_turbo.json new file mode 100644 index 0000000..c3f35a5 --- /dev/null +++ b/data/models/krea2_turbo.json @@ -0,0 +1,192 @@ +{ + "display_name": "Krea 2 - Turbo", + "model_name": "Krea 2 Turbo", + "version": "Turbo", + "architecture": "krea2", + "family": "krea2_turbo", + "author": "Krea AI", + "description": "Krea 2 Turbo — a distilled 12B dense Diffusion Transformer for fast, high-quality, aesthetic-first text-to-image at native 2K. Uses a Qwen3-VL text encoder and the Qwen Image VAE. 8-step, cfg 1.0. nvfp4 variant is the fastest on Blackwell (RTX 50-series) native FP4.", + "license": "Krea 2 Community License Agreement v.1 (open weights; commercial use requires an Enterprise License once total annual revenue reaches $1M)", + "tags": [ + "krea", + "krea2", + "dit", + "transformer", + "photorealistic", + "aesthetic", + "turbo", + "fast" + ], + "url": "https://huggingface.co/Comfy-Org/Krea-2", + "release_date": "2026-06-18", + "tag_sources": [], + "tag_format": "spaces", + "prompt_style": "natural", + "trigger": "", + "default_prompt_id": "krea2-default", + "negative": "", + "notes": "8 steps, cfg 1.0, euler+simple (the official Krea recipe — default; er_sde+simple is available for extra grain on photoreal skin), denoise 1.0. Distilled turbo — keep cfg at 1.0 (no real negative). Native 2K (supports 1K-2K). Reuses the Qwen Image VAE (qwen_image_vae.safetensors). Requires a ComfyUI build with the 'krea2' CLIP type (Krea 2 release, June 2026 or newer). RAW-trained Krea 2 LoRAs express on Turbo.", + "files": [ + { + "label": "Diffusion Model", + "folder": "diffusion_models", + "variants": [ + { + "precision": "fp8", + "filename": "krea2_turbo_fp8_scaled.safetensors", + "size_bytes": 13141730784, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "diffusion_models/krea2_turbo_fp8_scaled.safetensors" + }, + "quick_hash": null, + "sha256": null + }, + { + "precision": "bf16", + "filename": "krea2_turbo_bf16.safetensors", + "size_bytes": 26283332608, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "diffusion_models/krea2_turbo_bf16.safetensors" + }, + "quick_hash": null, + "sha256": null + }, + { + "precision": "mxfp8", + "filename": "krea2_turbo_mxfp8.safetensors", + "size_bytes": 13532318080, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "diffusion_models/krea2_turbo_mxfp8.safetensors" + }, + "quick_hash": null, + "sha256": null + }, + { + "precision": "nvfp4", + "filename": "krea2_turbo_nvfp4.safetensors", + "size_bytes": 7673668288, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "diffusion_models/krea2_turbo_nvfp4.safetensors" + }, + "quick_hash": null, + "sha256": null + } + ] + }, + { + "label": "Text Encoder", + "folder": "text_encoders", + "variants": [ + { + "precision": "fp8", + "filename": "qwen3vl_4b_fp8_scaled.safetensors", + "size_bytes": 5242467968, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "text_encoders/qwen3vl_4b_fp8_scaled.safetensors" + }, + "sha256": null + }, + { + "precision": "bf16", + "filename": "qwen3vl_4b_bf16.safetensors", + "size_bytes": 8875719384, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "text_encoders/qwen3vl_4b_bf16.safetensors" + }, + "sha256": null + } + ] + }, + { + "label": "VAE", + "folder": "vae", + "filename": "qwen_image_vae.safetensors", + "size_bytes": 253806246, + "source": { + "provider": "huggingface", + "repo": "Comfy-Org/Krea-2", + "path": "vae/qwen_image_vae.safetensors" + }, + "sha256": null + } + ], + "nodes": { + "KSampler": { + "steps": 8, + "cfg": 1.0, + "sampler_name": "euler", + "scheduler": "simple", + "steps_range": [ + 6, + 12 + ], + "cfg_range": [ + 1.0, + 1.5 + ], + "sampler_name_options": [ + "er_sde", + "euler", + "res_multistep", + "dpmpp_2m", + "euler_ancestral" + ], + "scheduler_options": [ + "simple", + "beta", + "sgm_uniform", + "normal" + ] + }, + "EmptySD3LatentImage": { + "width": 1024, + "height": 1536, + "_resolutions": [ + { + "width": 1024, + "height": 1536 + }, + { + "width": 1536, + "height": 1024 + }, + { + "width": 1024, + "height": 1024 + }, + { + "width": 1152, + "height": 1536 + }, + { + "width": 1536, + "height": 1152 + }, + { + "width": 1408, + "height": 1408 + }, + { + "width": 1152, + "height": 2048 + }, + { + "width": 2048, + "height": 1152 + } + ] + } + } +} diff --git a/data/prompts/krea2.json b/data/prompts/krea2.json new file mode 100644 index 0000000..33fa4f3 --- /dev/null +++ b/data/prompts/krea2.json @@ -0,0 +1,1315 @@ +{ + "name": "Krea 2 Prompts", + "prompts": [ + { + "id": "krea2-default", + "name": "Natural Language", + "category": "", + "text": "// Your Tags\n{cursor}", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photography", + "name": "Photography", + "category": "", + "text": "// Your Tags\n{cursor}\n\n// Photography\nPhotographed on 35mm film, soft natural light from a window, crisp focus with a shallow depth of field, realistic skin and fabric texture, true-to-life color.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-cinematic", + "name": "Cinematic", + "category": "", + "text": "// Your Tags\n{cursor}\n\n// Cinematic\nA cinematic film still, dramatic low-key lighting with deep shadows, shot on an anamorphic lens, shallow depth of field, moody teal-and-amber color grade, subtle film grain.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-portrait", + "name": "Portrait", + "category": "", + "text": "// Your Tags\n{cursor}\n\n// Portrait\nA close-up portrait lit by soft diffused window light, shot on an 85mm lens with a shallow depth of field and creamy background blur, sharp eyes and finely detailed skin texture.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-illustration", + "name": "Illustration", + "category": "", + "text": "// Your Tags\n{cursor}\n\n// Illustration\nA detailed digital illustration with clean expressive linework, a rich harmonious color palette and soft painterly shading, dynamic composition with a clear focal point.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d-render", + "name": "3D Render", + "category": "", + "text": "// Your Tags\n{cursor}\n\n// 3D Render\nhigh quality 3d render, smooth skin, ray tracing with rim lighting", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-concept-art", + "name": "Concept Art", + "category": "", + "text": "// Your Tags\n{cursor}\n\n// Concept Art\nSweeping concept art in a painterly matte-painting style, atmospheric haze and depth, dramatic god rays through the scene, an epic cinematic composition with a strong sense of scale.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime", + "name": "Anime", + "category": "", + "text": "// Your Tags\n{cursor}\n\n// Anime\nA vibrant anime illustration with bold clean cel-shaded linework, expressive eyes, saturated color and soft gradient skies, dynamic composition.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-comic-book", + "name": "Comic Book", + "category": "", + "text": "// Your Tags\n{cursor}\n\n// Comic Book\nComic Book Cover titled \"Comic Book\" at top. background of a sunny beach with ocean. In the style of 90s comics", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-stained-glass", + "name": "Stained Glass", + "category": "", + "text": "// Your Tags\n{cursor}\n\n// Stained Glass\nSubject as an elaborate stained glass window, divided into individual pieces of translucent jewel-toned glass, each piece outlined by thick black lead came lines. Bright light rays shine through from behind, making the colored glass glow luminously and casting soft colored light. Intricate leadwork, textured cathedral glass, ornate border, radiant and vivid.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-cartoony", + "name": "Cartoony", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Cartoony\nStylized 3D cartoon style with exaggerated proportions and vibrant colors. Fortnite-inspired aesthetic, chunky silhouettes for visibility. Bold saturated palette, simplified textures with realistic materials. Playful approachable game art style.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-cel_shaded_3d", + "name": "Cel-Shaded 3D", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Cel-Shaded 3D\nCel-shaded 3D render with bold outlines and flat color shading. Borderlands or anime game style, stylized lighting with sharp shadow edges. Clean vector-like appearance, comic book aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-chibi_3d", + "name": "Chibi 3D", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Chibi 3D\nCute chibi-style 3D character with big head and small body. Adorable proportions, soft rounded features, glossy finish. Bright colorful rendering, studio lighting. High quality 3D render.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-clay_render", + "name": "Clay Render", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Clay Render\nA realistic clay figurine with accurate human proportions, sculpted from smooth matte polymer clay. The sculpture has professional realistic handmade quality, displayed in a miniature diorama setting. Macro photography with shallow depth of field.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-claymation", + "name": "Claymation", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Claymation\nA handmade claymation character in stop-motion animation style, sculpted from matte plasticine with visible fingerprint marks and subtle imperfections. Charming stylized proportions with smooth rounded forms, resembling Aardman animations. Miniature set diorama with warm studio lighting, macro photography with shallow depth of field.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-funko_pop", + "name": "Funko Pop", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Funko Pop\nFunko Pop vinyl figure style with oversized square head and small body. Black dot eyes, stylized simplified features. Glossy vinyl material, collectible figure aesthetic. Clean studio render.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-hologram", + "name": "Hologram", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Hologram\nSubject rendered as a faint blue hologram like Princess Leia's message in Star Wars, ghostly transparent figure made of flickering light with horizontal scan-lines throughout. See-through luminous blue form against dark background, barely visible ethereal projection.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-isometric", + "name": "Isometric", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Isometric\nSubject viewed from an isometric perspective at a 45-degree angle above, rendered as a miniature 3D game asset with clean geometric forms. Orthographic projection with no perspective distortion, bright cheerful colors with soft ambient shadows. Tiny diorama scene resembling a mobile game or architectural visualization, sharp detailed render.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-low_poly", + "name": "Low Poly", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Low Poly\nStylized low poly 3D art with visible geometric facets. Minimalist aesthetic, flat shaded triangular surfaces. Modern indie game style, clean colors with subtle gradients. Sharp edges.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-nendoroid", + "name": "Nendoroid", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Nendoroid\nNendoroid chibi figure style with oversized head and small body. Cute collectible figure aesthetic, glossy PVC material look. Clean studio lighting, display stand. High quality 3D render.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-pixar", + "name": "Pixar/Disney", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Pixar/Disney\nPixar 3D animation style with expressive character design. Soft subsurface scattering skin, large expressive eyes, stylized proportions. Cinematic lighting with warm tones, soft shadows. High quality 3D render, sharp focus.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-pixel_art", + "name": "Pixel Art", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Pixel Art\nDetailed pixel art style with visible pixels creating retro game aesthetic. 16-bit or 32-bit era quality, limited color palette with careful dithering. Clean crisp pixels, nostalgic video game look.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-ps1_era", + "name": "PS1 Era", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - PS1 Era\nPlayStation 1 era 3D graphics with low polygon count and texture warping. Affine texture mapping artifacts, vertex jitter on edges. Gouraud shading, limited color palette. 90s retro game aesthetic, nostalgic early 3D look.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-realistic", + "name": "Realistic", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Realistic\n3D render in Unreal Engine 5 style. Video game character with stylized realism, subsurface scattering on skin, rim lighting. AAA game promotional art quality, highly detailed, sharp focus.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-vector_3d", + "name": "Vector", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Vector\nClean vector art style with flat solid colors and crisp geometric shapes. Bold outlines, no gradients or textures. Minimalist graphic design aesthetic, scalable illustration look. Simple clean forms, limited color palette.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-voxel", + "name": "Voxel/Minecraft", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Voxel/Minecraft\nVoxel art style made of 3D cubes and blocks. Minecraft aesthetic, blocky geometric forms. Bright colors, clean edges, isometric or 3D perspective. Sharp render.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-3d_styles-wireframe", + "name": "Wireframe", + "category": "3D Styles", + "text": "// Your Tags\n{cursor}\n\n// 3D Styles - Wireframe\nSubject rendered entirely as a 3D wireframe mesh, the figure constructed from geometric lines connecting vertices with no solid surfaces or textures. The entire form is visible as a transparent polygon grid structure, glowing white or green lines against a dark background. Technical CAD visualization style with mathematical precision.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-90s_anime", + "name": "90s Anime", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - 90s Anime\n90s retro anime style with nostalgic OVA aesthetic. Bold linework, high contrast cel shading, vibrant saturated colors. Film grain, subtle chromatic aberration, VHS texture. Classic hand-drawn animation look, dynamic action poses.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-bishonen", + "name": "Bishonen", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Bishonen\nBishonen pretty boy anime style with handsome androgynous male character. Romantic shoujo aesthetic with sparkles and roses, flowing hair, delicate features. Soft lighting, dreamy atmosphere, elegant poses. Long-haired pretty boy design, emotionally expressive.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-cel_shaded", + "name": "Cel Shaded", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Cel Shaded\nAnime key visual illustration with cel shaded coloring and clean sharp linework. Vibrant colors, expressive character design, detailed background. Masterpiece quality, highly detailed, sharp focus.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-chibi", + "name": "Chibi", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Chibi\nCute chibi anime style with oversized head and small body. Adorable simplified features, big expressive eyes, rounded proportions. Bright cheerful colors, clean linework, kawaii aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-donghua", + "name": "Donghua", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Donghua\nChinese Donghua anime style with elegant flowing aesthetic. Traditional ink wash painting influences mixed with modern anime rendering. Ethereal atmosphere, intricate costume details, xianxia or wuxia influence. Delicate linework, rich color palette.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-hyperrealistic", + "name": "Hyperrealistic", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Hyperrealistic\nHyperrealistic anime style blending photorealism with anime aesthetics. Realistic skin texture with subsurface scattering, expressive anime eyes with realistic reflections. Cinematic studio lighting, detailed hair strands, depth of field. High fidelity rendering, sharp focus.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-irasutoya", + "name": "Irasutoya Clipart", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Irasutoya Clipart\nSubject rendered in Irasutoya Japanese clipart style, a simple cute illustration with flat colors and rounded features. Clean minimalist linework with friendly expressive face, manga-influenced but heavily simplified. Approachable stock illustration aesthetic like Japanese free clipart.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-manga", + "name": "Manga", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Manga\nBlack and white manga illustration with detailed ink linework. Screentone shading, dynamic panel composition, expressive speed lines. High contrast, clean precise lines, professional manga quality.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-semi_realistic", + "name": "Semi-Realistic", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Semi-Realistic\n2.5D semi-realistic anime style blending expressive anime features with realistic detail. Realistic skin texture and lighting, nuanced facial structure with expressive anime eyes. Modern rendering with soft volumetric shadows, cinematic depth of field. Detailed hair strands, masterpiece quality, sharp focus.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-shonen", + "name": "Shonen", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Shonen\nDynamic shonen anime style with action-packed energy. Bold dramatic linework, intense expressions, dynamic poses. High contrast lighting, speed lines, powerful aura effects. Dragon Ball or Naruto inspired aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-studio_ghibli", + "name": "Studio Ghibli", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Studio Ghibli\nStudio Ghibli inspired anime style with soft watercolor aesthetic. Hand-painted backgrounds, gentle color palette, whimsical atmosphere. Miyazaki-esque character design, emotional warmth, detailed environmental storytelling.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-anime-vinyl_sticker", + "name": "Vinyl Sticker", + "category": "Anime", + "text": "// Your Tags\n{cursor}\n\n// Anime - Vinyl Sticker\nVinyl die-cut sticker art style with bold black outlines and white border. Flat vibrant colors, clean vector-like illustration. Designed for cutout shape, simple iconic design. Laptop sticker aesthetic, bold graphic illustration with thick outline.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-bacon", + "name": "Francis Bacon", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Francis Bacon\nSubject rendered in Francis Bacon's disturbing painting style, distorted figure with smeared and blurred features suggesting movement or anguish. Isolated against a dark flat background, raw fleshy paint handling with visible brushwork. Psychological intensity and grotesque distortion, figure appearing trapped or contorted, unsettling expressionist portraiture.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-davinci", + "name": "Da Vinci", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Da Vinci\nSubject rendered in Leonardo da Vinci's painting style with soft sfumato technique, subtle smoky transitions between tones with no harsh edges. Mysterious enigmatic expression, warm earth tones and muted colors. Atmospheric perspective, Renaissance oil painting mastery with delicate glazing layers, Mona Lisa aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-goya", + "name": "Goya", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Goya\nFrancisco Goya painting style with dark romantic atmosphere and psychological intensity. Rich shadows with dramatic lighting, Spanish court portrait elegance or Black Paintings darkness. Earthy palette with deep blacks, haunting emotional depth. Old master technique with modern sensibility.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-hokusai", + "name": "Hokusai", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Hokusai\nKatsushika Hokusai Japanese ukiyo-e woodblock print style. Bold outlines with flat color areas, distinctive wave patterns. Traditional Japanese composition, Mount Fuji or nature subjects. Prussian blue and indigo palette, The Great Wave aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-klimt", + "name": "Klimt", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Klimt\nGustav Klimt Art Nouveau painting with ornate gold leaf patterns and decorative Byzantine motifs. Flat decorative backgrounds with realistic faces, intricate geometric and floral patterns. Rich gold, bronze and jewel tones. Sensual elegance, The Kiss inspired aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-monet", + "name": "Monet", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Monet\nClaude Monet impressionist painting with soft dappled brushstrokes capturing light. Broken color technique, luminous atmospheric quality. Pastel color palette, water reflections, plein air aesthetic. Gentle diffused light, dreamy garden or water lily mood.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-mucha", + "name": "Mucha", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Mucha\nAlphonse Mucha Art Nouveau poster style with elegant decorative borders and flowing organic lines. Beautiful figure with long flowing hair surrounded by floral motifs. Soft pastel colors with gold accents, ornate circular halo frame. Elegant poster art aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-picasso", + "name": "Picasso", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Picasso\nPablo Picasso cubist style with fragmented geometric forms and multiple perspectives. Bold angular shapes, distorted features showing different viewpoints simultaneously. Strong black outlines, muted earth tones or blue period palette. Abstract modernist aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-rembrandt", + "name": "Rembrandt", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Rembrandt\nRembrandt oil painting with dramatic chiaroscuro lighting. Golden brown tones emerging from deep shadows, spotlight illuminating subject. Rich impasto texture, warm earth palette. Baroque master technique, intimate psychological portrait quality.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-van_gogh", + "name": "Van Gogh", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Van Gogh\nPainting in Vincent van Gogh style with bold swirling brushstrokes and thick impasto texture.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-artists-warhol", + "name": "Warhol", + "category": "Artists", + "text": "// Your Tags\n{cursor}\n\n// Artists - Warhol\nAndy Warhol pop art style with bold flat colors and high contrast. Screen print aesthetic, graphic simplified forms. Vibrant neon pinks, electric blues, acid yellows. Celebrity portrait treatment, repetition and mass media influence.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-concept_art-concept_realistic", + "name": "Concept Realistic", + "category": "Concept Art", + "text": "// Your Tags\n{cursor}\n\n// Concept Art - Concept Realistic\nConcept art on toned paper with graphite pencil underdrawing. Subject rendered in hyperrealistic colors with visible pencil linework beneath, detailed color study. Clean background or minimal environment, professional concept art presentation. Sharp focus on subject details.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-concept_art-concept_sketch", + "name": "Concept Sketch", + "category": "Concept Art", + "text": "// Your Tags\n{cursor}\n\n// Concept Art - Concept Sketch\nConcept art sketch on warm toned paper. Charcoal and pencil linework with soft pastel color accents, loose expressive strokes. Visible paper texture, muted color palette with selective highlights. Professional illustration, artist sketchbook study.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-concept_art-concept_soft_watercolor", + "name": "Concept Soft Watercolor", + "category": "Concept Art", + "text": "// Your Tags\n{cursor}\n\n// Concept Art - Concept Soft Watercolor\nSoft concept art with loose watercolor rendering and no visible linework. Ethereal wash-based forms with gentle color bleeding and soft edges. No outlines, shapes defined by color value alone. Minimal background, dreamy painterly character study.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-concept_art-concept_watercolor", + "name": "Concept Watercolor", + "category": "Concept Art", + "text": "// Your Tags\n{cursor}\n\n// Concept Art - Concept Watercolor\nConcept art sketch on toned paper with pencil linework colorized in transparent watercolors. Visible graphite underdrawing with soft watercolor washes building form and color. Character study with minimal background, traditional mixed media aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-concept_art-gouache", + "name": "Gouache", + "category": "Concept Art", + "text": "// Your Tags\n{cursor}\n\n// Concept Art - Gouache\nConcept art painted in gouache, opaque watercolor with matte finish. Classic animation production art style, rich flat color areas with subtle gradients. Visible brushwork, warm color palette. Disney or Ghibli background art influence.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-concept_art-ink_wash", + "name": "Ink Wash", + "category": "Concept Art", + "text": "// Your Tags\n{cursor}\n\n// Concept Art - Ink Wash\nConcept art in ink wash style with Eastern influences. Sumi-e inspired brushwork, fluid ink gradients from dark to light. Selective watercolor accents on subject, visible brush texture. Atmospheric depth, traditional rice paper aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-concept_art-line_art", + "name": "Line Art", + "category": "Concept Art", + "text": "// Your Tags\n{cursor}\n\n// Concept Art - Line Art\nClean concept art linework, precise ink lines with confident strokes. Minimal to no color, focus on form and contour. Professional illustration line drawing, subtle line weight variation. Clear readable silhouette.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-concept_art-marker", + "name": "Marker/Copic", + "category": "Concept Art", + "text": "// Your Tags\n{cursor}\n\n// Concept Art - Marker/Copic\nSubject rendered as a Copic marker illustration on white paper, the entire figure drawn with bold confident marker strokes and smooth color gradients. Visible marker streaks and layered ink blending, bright saturated colors with clean white highlights left as bare paper. Industrial design sketch aesthetic, professional concept art rendering.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-concept_art-thumbnail", + "name": "Thumbnail", + "category": "Concept Art", + "text": "// Your Tags\n{cursor}\n\n// Concept Art - Thumbnail\nSubject rendered as a quick concept art thumbnail sketch, the figure captured with loose gestural brushstrokes showing only essential forms and values. Rough unfinished exploration sketch with minimal detail, focus on silhouette and composition rather than refinement. Rapid ideation style, expressive and intentionally incomplete.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-ballpoint_pen", + "name": "Ballpoint Pen", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Ballpoint Pen\nSubject rendered entirely as a ballpoint pen drawing on white paper, the entire figure drawn in blue ink using layered crosshatching strokes. Monochromatic blue ballpoint illustration with no color, form built through careful pen pressure and hatching density. Hyperrealistic ballpoint art style with fine detailed linework throughout, notebook paper aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-blueprint", + "name": "Blueprint", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Blueprint\nTechnical blueprint schematic drawing with white lines on deep blue background. Precise engineering diagram style, architectural drafting aesthetic. Grid lines, measurements, technical annotations. Cyanotype blueprint reproduction look, technical illustration.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-chalk", + "name": "Chalk", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Chalk\nChalk drawing on dark black paper, white and colored chalk creating forms from darkness. Soft dusty texture, layered strokes building highlights. Dramatic contrast with dark background showing through. Traditional chalkboard or toned paper aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-charcoal", + "name": "Charcoal", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Charcoal\nCharcoal drawing on textured paper with rich velvety blacks and soft gradations. Expressive marks from vine and compressed charcoal, dramatic chiaroscuro lighting. Smudged blending in shadows, crisp edges on highlights. Deep tonal range, atmospheric depth. Traditional fine art charcoal study.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-chinese_ink", + "name": "Chinese Ink", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Chinese Ink\nChinese ink brush painting in traditional sumi-e style on rice paper. Fluid ink wash gradations from deep black to pale gray, expressive brushstrokes. Wet-on-wet technique with soft bleeding edges, empty space as compositional element. Zen aesthetic, Eastern brush painting tradition.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-colored_pencil", + "name": "Colored Pencil", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Colored Pencil\nColored pencil illustration with rich layered colors and visible pencil strokes.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-conte_crayon", + "name": "Conte Crayon", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Conte Crayon\nConte crayon drawing on warm toned paper in classical trois crayons technique. Sanguine red-brown and sepia tones with white chalk highlights. Soft hatching and blending, Renaissance life drawing aesthetic. Warm earth tones on buff or gray paper, classical figure study quality.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-crosshatch", + "name": "Crosshatch", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Crosshatch\nDetailed crosshatching pen and ink drawing with layered parallel lines creating tonal depth. Fine technical hatching following form contours, multiple angles building shadows. High contrast black ink on white paper, engraving-like precision. Classical crosshatch illustration technique.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-davinci_sketch", + "name": "DaVinci Sketch", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - DaVinci Sketch\nSubject rendered as a Leonardo da Vinci anatomical study, the figure drawn in sepia ink and silverpoint on aged yellowed paper. Renaissance master sketch style with precise linework showing anatomical understanding, subtle cross-hatching for shading. Warm brown tones on antique parchment, scientific illustration meets fine art, Da Vinci codex aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-gesture", + "name": "Gesture", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Gesture\nQuick gesture drawing with loose expressive lines capturing movement and energy. Fluid dynamic strokes following line of action, minimal detail maximum expression. Charcoal or graphite on newsprint, figure study aesthetic. Spontaneous confident marks, artistic shorthand for form.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-ink", + "name": "Ink", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Ink\nPen and ink drawing with fine precise linework on white paper. Confident strokes with varied line weight, hatching for tonal values. High contrast black ink on clean white, crisp details. Traditional ink illustration, sharp and graphic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-pastel", + "name": "Pastel", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Pastel\nSoft pastel drawing with rich velvety pigments on textured paper. Smooth blended gradients, visible paper tooth in lighter areas. Chalky matte finish with vibrant colors. Feathered strokes and soft edges, traditional pastel portrait or landscape quality.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-pencil", + "name": "Pencil", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Pencil\nSubject rendered as a graphite pencil drawing on white paper, the entire figure sketched in gray pencil with visible hatching and cross-hatching strokes. No color anywhere, only shades of gray from light to dark. Traditional hand-drawn sketch with pencil texture throughout, fine linework for details and softer blending in shadows.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-drawing-stippling", + "name": "Stippling", + "category": "Drawing", + "text": "// Your Tags\n{cursor}\n\n// Drawing - Stippling\nStippling pointillism drawing made entirely of small dots creating form and shadow. Dense dot clusters in dark areas, sparse dots in highlights. Black ink dots on white paper, incredible patience and detail. Photorealistic stipple art, no lines only dots.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-figurine-acrylic_stand", + "name": "Acrylic Stand", + "category": "Figurine", + "text": "// Your Tags\n{cursor}\n\n// Figurine - Acrylic Stand\n2D anime character printed on clear acrylic standee with base. Flat printed artwork on transparent acrylic panel, vibrant CMYK colors. Clean cut edges, small pedestal base. Anime merchandise style, desk decoration collectible.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-figurine-diorama", + "name": "Diorama", + "category": "Figurine", + "text": "// Your Tags\n{cursor}\n\n// Figurine - Diorama\nDetailed figure diorama with scenic environmental base telling a story. Character figure integrated with themed backdrop elements, environmental props and lighting effects. Display piece with immersive scene design, collector's diorama quality.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-figurine-figure_in_box", + "name": "Figurine in Box", + "category": "Figurine", + "text": "// Your Tags\n{cursor}\n\n// Figurine - Figurine in Box\nSubject rendered as a 1/7 scale PVC realistic anime figurine displayed inside commercial product packaging with clear window display box. The figure has smooth matte skin finish and glossy hair, visible through plastic window. Colorful printed box art, Limited Edition collectible presentation, official merchandise product photography.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-figurine-figure_on_desk", + "name": "Figurine on Desk", + "category": "Figurine", + "text": "// Your Tags\n{cursor}\n\n// Figurine - Figurine on Desk\nSubject rendered as a 1/7 scale PVC realistic anime figurine displayed on wooden desk surface. The figure has smooth matte skin finish and glossy hair, seamless sculpt with precise painted details. Soft natural window lighting, shallow depth of field. Collector's display photography, lifestyle product shot.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-figurine-garage_kit", + "name": "Garage Kit", + "category": "Figurine", + "text": "// Your Tags\n{cursor}\n\n// Figurine - Garage Kit\nUnpainted resin garage kit figure showing raw sculpt detail. Gray or white resin material, visible panel lines and part seams. Model kit aesthetic with professional sculpting quality, hobbyist collectible. Clean studio lighting.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-figurine-marble", + "name": "Marble", + "category": "Figurine", + "text": "// Your Tags\n{cursor}\n\n// Figurine - Marble\nSubject rendered as a polished white marble statue, the entire figure carved from a single block of marble with smooth sculpted surfaces and fine chisel detail. Realistic marble texture with subtle grey veining and a soft translucent sheen, classical Greek and Roman sculpture style. Displayed on a stone pedestal in a museum gallery, dramatic exhibition lighting and soft shadows, photorealistic sculpture photography.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-figurine-marble_bust", + "name": "Marble Bust", + "category": "Figurine", + "text": "// Your Tags\n{cursor}\n\n// Figurine - Marble Bust\nSubject rendered as a classical sculptural bust carved from marble, the figure shown as a stone sculpture depicting only head, neck and upper shoulders on an elegant pedestal base. Smooth polished marble surface with subtle veining, museum lighting casting dramatic shadows. Portrait sculpture with fine carved details, neoclassical statue aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-figurine-miniature", + "name": "Miniature", + "category": "Figurine", + "text": "// Your Tags\n{cursor}\n\n// Figurine - Miniature\nSubject rendered as a tiny miniature figure in a tilt-shift diorama scene, appearing as a small scale model viewed from above at an angle. Extremely shallow depth of field with blur at top and bottom edges, oversaturated toy-like colors making everything look like a miniature playset. The figure appears doll-sized in a model landscape.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-figurine-pvc_figure", + "name": "PVC Figurine", + "category": "Figurine", + "text": "// Your Tags\n{cursor}\n\n// Figurine - PVC Figurine\n1/7 scale PVC realistic anime figurine with smooth matte skin finish and glossy hair. Seamless single-piece sculpt, precise painted details. Soft studio lighting on transparent acrylic display base. Commercial product photography, collectible figurine aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-blue_hour", + "name": "Blue Hour", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Blue Hour\nBlue hour twilight lighting with cool deep blue tones. Soft diffused ambient light, no harsh shadows. Ethereal calm atmosphere, city lights beginning to glow warm against cool sky. Twilight photography aesthetic, dreamlike quality.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-candlelight", + "name": "Candlelight", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Candlelight\nWarm candlelight illumination with soft flickering orange glow. Single point light source, intimate atmosphere with deep warm shadows. Romantic mood, faces softly lit from below or side. Warm color temperature around 1800K, cozy intimate setting.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-dramatic", + "name": "Dramatic", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Dramatic\nDramatic cinematic lighting with strong directional light and deep shadows. High contrast with sculpted highlights, theatrical mood. Single powerful key light, minimal fill. Intense emotional atmosphere, bold light and shadow interplay.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-film_noir", + "name": "Film Noir", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Film Noir\nClassic film noir lighting with dramatic high contrast chiaroscuro. Deep black shadows, stark white highlights, minimal mid-tones. Venetian blind shadows, mysterious atmospheric mood. 1940s black and white cinema aesthetic, low-key dramatic lighting.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-golden_hour", + "name": "Golden Hour", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Golden Hour\nGolden hour lighting with warm sunset tones of orange, yellow, and amber. Soft diffused sunlight from low angle, long shadows, rim lighting on edges. Warm color temperature around 3000K, romantic atmospheric glow. Magic hour photography aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-high_key", + "name": "High Key", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - High Key\nHigh key lighting with bright airy illumination and minimal shadows. Even soft light from multiple sources, white or light backgrounds. Clean fresh aesthetic, optimistic mood. Overexposed feeling, fashion or beauty photography style.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-low_key", + "name": "Low Key", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Low Key\nLow key lighting with predominantly dark tones and selective highlights. Deep shadows dominating frame, small areas of bright illumination. Moody mysterious atmosphere, dramatic sculptural light on subject. Noir-inspired, theatrical mood.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-moonlight", + "name": "Moonlight", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Moonlight\nCool silvery moonlight illumination with soft blue-white tones. Gentle diffused light from above, long soft shadows. Nocturnal atmosphere, stars visible, peaceful night scene. Cool color temperature, ethereal nighttime glow.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-neon", + "name": "Neon", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Neon\nNeon lighting with vibrant colorful glow in cyberpunk aesthetic. Electric pink, cyan blue, and purple neon reflections on wet surfaces. Urban night atmosphere, light bleeding and color mixing. Blade Runner inspired, rain-slicked streets reflecting neon signs.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-rim_light", + "name": "Rim Light", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Rim Light\nRim lighting with bright backlit edge glow outlining subject. Light source behind subject creating glowing halo around edges. Hair and shoulders catching light, separation from dark background. Dramatic silhouette with luminous edge definition.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-studio", + "name": "Studio", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Studio\nProfessional studio lighting with clean three-point setup. Key light, fill light, and backlight creating balanced illumination. Even skin tones, controlled shadows, seamless backdrop. Commercial photography quality, polished professional aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-lighting-volumetric", + "name": "Volumetric", + "category": "Lighting", + "text": "// Your Tags\n{cursor}\n\n// Lighting - Volumetric\nVolumetric lighting with visible light rays cutting through atmosphere. God rays streaming through windows or trees, dust particles catching light. Dramatic shafts of light in hazy air, ethereal divine atmosphere. Cinematic light beams, atmospheric depth.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-material-art_deco", + "name": "Art Deco", + "category": "Material", + "text": "// Your Tags\n{cursor}\n\n// Material - Art Deco\nSubject rendered in Art Deco illustration style, the figure depicted with bold geometric patterns and 1920s glamour aesthetic. Streamlined elegant forms with sunburst motifs, chevrons, and angular designs throughout. Gold, black, and jewel tone color palette with luxurious chrome and metallic accents. Gatsby era poster art sophistication.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-material-chrome", + "name": "Chrome", + "category": "Material", + "text": "// Your Tags\n{cursor}\n\n// Material - Chrome\nSubject rendered as a chrome metal sculpture, the entire figure constructed from highly reflective polished chrome with mirror-like surface reflecting the environment. Liquid metal aesthetic with smooth metallic curves, silver reflections and highlights throughout. Futuristic chrome robot or T-1000 style metallic form.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-material-embroidery", + "name": "Embroidery", + "category": "Material", + "text": "// Your Tags\n{cursor}\n\n// Material - Embroidery\nSubject rendered as an embroidered textile artwork, the entire figure created with visible thread stitches on fabric backing. Cross-stitch and crewel embroidery techniques forming the image, raised textured needlework with thread direction following the form. Base fabric weave visible in background areas, traditional needlepoint craft aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-material-fabric", + "name": "Fabric/Textile", + "category": "Material", + "text": "// Your Tags\n{cursor}\n\n// Material - Fabric/Textile\nSubject rendered as a woven fabric tapestry, the entire figure depicted in textile artwork with visible weave pattern and thread texture throughout. The image appears as a traditional woven cloth with rich textile colors, the form created through stitched threads on fabric. Tapestry or embroidered wall hanging aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-material-glass", + "name": "Glass", + "category": "Material", + "text": "// Your Tags\n{cursor}\n\n// Material - Glass\nSubject rendered as a transparent glass sculpture, the entire figure constructed from clear crystal glass with light refracting through the form. See-through translucent body with visible light caustics and prismatic reflections, delicate glass art aesthetic. Crystal figurine with smooth polished glass surface.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-material-lego", + "name": "LEGO", + "category": "Material", + "text": "// Your Tags\n{cursor}\n\n// Material - LEGO\nMade entirely of LEGO bricks and pieces. Blocky minifigure aesthetic with characteristic yellow skin and simple dot eyes. Plastic brick texture, studs visible on surfaces. Bright primary colors, toy photography style. Everything constructed from interlocking LEGO elements.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-material-mosaic", + "name": "Mosaic", + "category": "Material", + "text": "// Your Tags\n{cursor}\n\n// Material - Mosaic\nSubject rendered as a Byzantine mosaic, the entire figure constructed from small colored tesserae tiles with visible grout lines between each piece. The form is built from hundreds of tiny square stone or glass tiles arranged to create the image, gold leaf accents and rich jewel tones. Ancient Roman church wall mosaic aesthetic, every part of image must be tiny tile stone framgments.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-material-origami", + "name": "Origami", + "category": "Material", + "text": "// Your Tags\n{cursor}\n\n// Material - Origami\nSubject rendered as an origami paper sculpture, the entire figure constructed from crisp geometric folds and clean creases in colored paper. Angular faceted surfaces forming the shape through folded planes, visible fold lines and paper edges throughout. Japanese paper folding art style, the figure appearing as a single folded sheet.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-material-paper_cutting", + "name": "Paper Cutting", + "category": "Material", + "text": "// Your Tags\n{cursor}\n\n// Material - Paper Cutting\nSubject rendered as a paper cutting silhouette artwork, the entire figure depicted as an intricate cutout design in black paper on white background. The form created through delicate cut patterns and negative space, lace-like details throughout with sharp clean edges. Scherenschnitte or Chinese paper cutting aesthetic, flat two-dimensional silhouette art.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-oil_painting-alla_prima", + "name": "Alla Prima", + "category": "Oil Painting", + "text": "// Your Tags\n{cursor}\n\n// Oil Painting - Alla Prima\nAlla prima oil painting, wet-on-wet technique in single session. Loose confident brushstrokes, fresh spontaneous execution. Visible brush marks with soft blended edges, painterly and direct. Bold color mixing on canvas, lively immediate quality.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-oil_painting-classical_realism", + "name": "Classical Realism", + "category": "Oil Painting", + "text": "// Your Tags\n{cursor}\n\n// Oil Painting - Classical Realism\noil painting on canvas, rich visible brush strokes, thick impasto texture in places, layered oil paint application with subtle blending and wet-on-wet edges, realistic oil paint sheen and color depth, textured surface with visible canvas weave in thinner areas, painterly execution, museum-quality fine art reproduction, muted yet vibrant color palette with harmonious glazing, no digital smoothness, authentic traditional oil painting appearance", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-oil_painting-clean_modern", + "name": "Clean Modern", + "category": "Oil Painting", + "text": "// Your Tags\n{cursor}\n\n// Oil Painting - Clean Modern\nOil painting on canvas with rich visible brushstrokes. Thick impasto texture with layered paint application, wet-on-wet blending and subtle glazing. Visible canvas weave in thinner areas, realistic paint sheen and color depth. Museum-quality fine art, authentic traditional oil painting.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-oil_painting-expressionist", + "name": "Expressionist", + "category": "Oil Painting", + "text": "// Your Tags\n{cursor}\n\n// Oil Painting - Expressionist\nExpressionist oil painting with bold swirling brushstrokes and thick impasto texture. Intense emotional colors, Van Gogh-inspired dynamic energy. Visible paint ridges, dramatic movement in brushwork. Raw expressive power, vibrant saturated palette.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-oil_painting-impressionist", + "name": "Impressionist", + "category": "Oil Painting", + "text": "// Your Tags\n{cursor}\n\n// Oil Painting - Impressionist\nImpressionist oil painting with loose visible brushstrokes capturing light and atmosphere. Vibrant broken color, dappled sunlight effects, Monet-inspired palette. Soft edges, painterly texture, plein air quality. Spontaneous brushwork, luminous colors.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-oil_painting-old_masters", + "name": "Old Masters", + "category": "Oil Painting", + "text": "// Your Tags\n{cursor}\n\n// Oil Painting - Old Masters\nOil painting in the style of Old Masters. Dramatic chiaroscuro lighting with deep shadows and luminous highlights, Rembrandt-style golden undertones. Rich dark background, fine glazing layers for skin depth, classical composition. Baroque influence, museum-quality fine art.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-oil_painting-palette_knife", + "name": "Palette Knife", + "category": "Oil Painting", + "text": "// Your Tags\n{cursor}\n\n// Oil Painting - Palette Knife\nOil portrait composed entirely of thick chunky blocks of color, no smooth surfaces anywhere. Face constructed from bold rectangular strokes of paint visible as distinct shapes, features suggested only by color placement not fine detail. Heavy textured surface like a Francoise Nielly painting, every inch covered in visible palette knife marks.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-cinematic", + "name": "Cinematic Dramatic", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Cinematic Dramatic\nCinematic still from a major motion picture. Dramatic chiaroscuro lighting with deep shadows, anamorphic lens with subtle lens flare. Cinematic color grading, film grain texture. Natural skin texture, sharp focus, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-default", + "name": "Default", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Default\nSubject in center and isolated in a dark grey studio background. Real life subject shot on Sony A7RV with 50mm f/1.4 lens, shallow depth of field, cinematic color grading. Photorealistic, natural skin pores and texture, sharp focus, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-documentary", + "name": "Documentary", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Documentary\nNational Geographic documentary photography capturing authentic candid moment. Natural available light, environmental storytelling with rich context. Genuine unguarded expression, photojournalistic quality, sharp focus, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-environmental", + "name": "Environmental", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Environmental\nEnvironmental portrait with rich contextual background telling a story. Dappled light filtering through natural canopy, atmospheric depth. Subject integrated with surroundings, layers of visual interest. Sharp focus, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-epic_landscape", + "name": "Epic Landscape", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Epic Landscape\nEpic landscape photography shot on Phase One IQ4 with 24mm wide angle lens at f/11. Volumetric lighting, atmospheric perspective with depth. Tack sharp throughout, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-fashion_editorial", + "name": "Fashion Editorial", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Fashion Editorial\nHigh-end fashion editorial photography, Vogue magazine quality. Dramatic side lighting with shadow sculpting, strong contrast. Editorial pose, runway elegance, 85mm lens with shallow depth of field. Sharp focus, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-film_noir", + "name": "Film Noir", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Film Noir\nSubject photographed in classic film noir style, dramatic black and white cinematography with stark high contrast lighting. Hard shadows cutting across the face from a single directional light source, deep impenetrable blacks and bright highlights with minimal midtones. Moody 1940s Hollywood atmosphere with venetian blind shadows, mysterious chiaroscuro lighting. Vintage film grain texture, hardboiled detective movie aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-golden_hour_landscape", + "name": "Golden Hour Landscape", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Golden Hour Landscape\nGolden hour landscape photography with wide angle lens. Warm sunset light with pink and gold sky gradients, volumetric light rays, long shadows. Atmospheric haze and depth, cinematic color grading, sharp focus, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-golden_hour_portrait", + "name": "Golden Hour Portrait", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Golden Hour Portrait\nGolden hour portrait photography. Warm sunset light with soft shadows, backlit rim lighting on hair and shoulders. Warm color palette, natural outdoor setting. Natural skin texture with golden glow, sharp focus, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-macro", + "name": "Macro", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Macro\nExtreme macro photography with 100mm macro lens. Shallow depth of field with creamy bokeh, soft backlighting with subtle light rays. Hyper-detailed textures, fine surface details visible, sharp focus on subject.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-medium_format", + "name": "Medium Format", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Medium Format\nShot on Hasselblad X2D medium format camera. Incredibly sharp details with beautiful tonal range, smooth gradients. Rich 16-bit color depth, natural color reproduction. Professional studio quality, sharp focus.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-moody_atmospheric", + "name": "Moody Atmospheric", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Moody Atmospheric\nMoody atmospheric photography with dramatic weather elements. Volumetric fog, soft diffused light, cinematic color grading. Emotional depth and tension, high contrast with soft shadows. Sharp focus, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-product", + "name": "Product", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Product\nProfessional product photography on clean white seamless background. Soft box lighting with subtle shadow and reflection, rim highlights for separation. E-commerce ready, extremely sharp details, commercial studio quality.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-street_portrait", + "name": "Street Portrait", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Street Portrait\nCandid street portrait shot on 35mm lens. Natural available light, documentary photography style. Authentic urban atmosphere, environmental context. Natural skin texture, sharp focus, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-studio_portrait", + "name": "Studio Portrait", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Studio Portrait\nProfessional studio portrait shot on 85mm lens. Clean key light with gentle fill, soft shadows. Shallow depth of field, seamless backdrop. Natural skin texture with fine pores, sharp focus, raw photo.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-photorealistic-vintage_film", + "name": "Vintage Film", + "category": "Photorealistic", + "text": "// Your Tags\n{cursor}\n\n// Photorealistic - Vintage Film\nSubject photographed on 35mm film with authentic analog aesthetic, visible film grain texture throughout the image with soft halation around highlights. Natural Kodak Portra color palette with slightly lifted blacks and gently faded highlights, warm organic skin tones. Soft analog contrast without digital sharpness, the unmistakable look of real film photography.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-watercolor-botanical", + "name": "Botanical", + "category": "Watercolor", + "text": "// Your Tags\n{cursor}\n\n// Watercolor - Botanical\nBotanical watercolor illustration with precise delicate detail. Scientific illustration quality, accurate naturalistic rendering. Soft transparent washes building form, fine brushwork for intricate details. Visible paper texture, muted natural color palette. Traditional botanical art style, museum specimen quality.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-watercolor-impressionistic", + "name": "Impressionistic", + "category": "Watercolor", + "text": "// Your Tags\n{cursor}\n\n// Watercolor - Impressionistic\nImpressionistic watercolor painting capturing light and atmosphere. Loose fluid brushstrokes, Monet-inspired color palette. Soft edges with selective detail, luminous transparent layers. Dappled light effects, plein air quality. Visible paper texture, spontaneous yet controlled execution.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-watercolor-loose_expressive", + "name": "Loose Expressive", + "category": "Watercolor", + "text": "// Your Tags\n{cursor}\n\n// Watercolor - Loose Expressive\nAbstract watercolor painting, fluid transparent layers of vibrant watercolors on textured cold-pressed paper. Energetic action splatter and drips with soft bleeding edges, wet-on-wet blending creating organic color flows and subtle granulation. Translucent washes with visible paper texture underneath, spontaneous expressive brush marks and accidental blooms. Authentic traditional watercolor appearance, no hard digital edges.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-watercolor-portrait", + "name": "Portrait/Realistic", + "category": "Watercolor", + "text": "// Your Tags\n{cursor}\n\n// Watercolor - Portrait/Realistic\nTraditional watercolor portrait with controlled transparent layers. Careful wet-on-dry technique building form gradually, soft skin tones with subtle glazing. Visible paper texture in highlights, delicate color transitions. Classical watercolor portrait style, refined brushwork.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + }, + { + "id": "krea2-watercolor-wet_on_wet", + "name": "Wet-on-Wet", + "category": "Watercolor", + "text": "// Your Tags\n{cursor}\n\n// Watercolor - Wet-on-Wet\nDreamy wet-on-wet watercolor painting with soft flowing transitions. Colors bleeding and blending organically, no hard edges. Ethereal atmospheric quality, gentle gradients. Visible paper texture, unpredictable beautiful blooms. Soft focus effect, romantic watercolor aesthetic.", + "scope": { + "type": "architecture", + "architecture": "krea2" + } + } + ] +} \ No newline at end of file diff --git a/data/tag-builder/tag-builder.db b/data/tag-builder/tag-builder.db index be5b31e..2763880 100644 Binary files a/data/tag-builder/tag-builder.db and b/data/tag-builder/tag-builder.db differ diff --git a/data/templates/krea2-raw-i2i-highdetail.json b/data/templates/krea2-raw-i2i-highdetail.json new file mode 100644 index 0000000..412f5f9 --- /dev/null +++ b/data/templates/krea2-raw-i2i-highdetail.json @@ -0,0 +1,343 @@ +{ + "name": "Image-to-Image (High Detail)", + "category": "Generation", + "order": 7, + "scope": { + "type": "family", + "architecture": "krea2", + "family": "krea2_raw" + }, + "created_at": 1782371000, + "id": "krea2-raw-i2i-highdetail", + "nodes": [ + { + "type": "CLIPTextEncode", + "offset": [ + 900.0, + 0.0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "CLIPTextEncode", + "offset": [ + 900.0, + 180.0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "KSampler", + "offset": [ + 1740.0, + 160.0 + ], + "size": [ + 330, + 474 + ], + "widgets_values": [ + 826502184750291, + "randomize", + 40, + 5.0, + "er_sde", + "beta", + 0.6 + ], + "properties": { + "Node name for S&R": "KSampler" + } + }, + { + "type": "VAEUtils_VAEDecodeTiled", + "offset": [ + 1790.0, + 20.0 + ], + "size": [ + 300, + 220 + ], + "widgets_values": [ + -1, + false, + 512, + 64, + 4096, + 64 + ], + "properties": { + "Node name for S&R": "VAEUtils_VAEDecodeTiled" + } + }, + { + "type": "SaveImage", + "offset": [ + 2150.0, + -10.0 + ], + "size": [ + 276.984375, + 322.984375 + ], + "widgets_values": [ + "Krea2-RAW/img2img-highdetail/%date:yyyy-MM-dd_hhmmss%" + ], + "properties": {} + }, + { + "type": "CLIPLoader", + "offset": [ + 860.0, + 360.0 + ], + "size": [ + 346.78125, + 117.328125 + ], + "widgets_values": [ + "qwen3vl_4b_fp8_scaled.safetensors", + "krea2", + "default" + ], + "properties": { + "Node name for S&R": "CLIPLoader" + } + }, + { + "type": "UNETLoader", + "offset": [ + 1240.0, + 0.0 + ], + "size": [ + 371.65625, + 82.65625 + ], + "widgets_values": [ + "krea2_raw_bf16.safetensors", + "default" + ], + "properties": { + "Node name for S&R": "UNETLoader" + } + }, + { + "type": "LoadImage", + "offset": [ + 1240.0, + 220.0 + ], + "size": [ + 270, + 340 + ], + "widgets_values": [ + "", + "image" + ], + "properties": { + "Node name for S&R": "LoadImage" + } + }, + { + "type": "VAEUtils_CustomVAELoader", + "offset": [ + 1740.0, + 680.0 + ], + "size": [ + 366.40625, + 82.65625 + ], + "widgets_values": [ + "Wan2.1_VAE_upscale2x_imageonly_real_v1.safetensors", + true + ], + "properties": { + "Node name for S&R": "VAEUtils_CustomVAELoader" + } + }, + { + "type": "ConditioningKrea2Rebalance", + "offset": [ + 1180.0, + 0.0 + ], + "size": [ + 300, + 106 + ], + "widgets_values": [ + 2.0, + "1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.6,2.5,1.05,2.0,1.0" + ], + "properties": { + "Node name for S&R": "ConditioningKrea2Rebalance" + } + }, + { + "type": "ModelSamplingFlux", + "offset": [ + 1460.0, + 90.0 + ], + "size": [ + 300, + 130 + ], + "widgets_values": [ + 0.90625, + 0.5, + 1024, + 1024 + ], + "properties": { + "Node name for S&R": "ModelSamplingFlux" + } + }, + { + "type": "VAEEncode", + "offset": [ + 1560.0, + 600.0 + ], + "size": [ + 225, + 72 + ], + "widgets_values": [], + "properties": { + "Node name for S&R": "VAEEncode" + } + }, + { + "type": "VAELoader", + "offset": [ + 1180.0, + 600.0 + ], + "size": [ + 366.40625, + 82.65625 + ], + "widgets_values": [ + "qwen_image_vae.safetensors" + ], + "properties": { + "Node name for S&R": "VAELoader" + } + } + ], + "connections": [ + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 9, + "to_slot": 0 + }, + { + "from_node_idx": 9, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 1 + }, + { + "from_node_idx": 1, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 2 + }, + { + "from_node_idx": 6, + "from_slot": 0, + "to_node_idx": 10, + "to_slot": 0 + }, + { + "from_node_idx": 10, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 0 + }, + { + "from_node_idx": 11, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 3 + }, + { + "from_node_idx": 7, + "from_slot": 0, + "to_node_idx": 11, + "to_slot": 0 + }, + { + "from_node_idx": 12, + "from_slot": 0, + "to_node_idx": 11, + "to_slot": 1 + }, + { + "from_node_idx": 2, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 0 + }, + { + "from_node_idx": 8, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 1 + }, + { + "from_node_idx": 3, + "from_slot": 0, + "to_node_idx": 4, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 0, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 0 + } + ], + "anchorConnections": [ + { + "from_slot": 1, + "to_node_idx": 0, + "to_slot": 1 + }, + { + "from_slot": 2, + "to_node_idx": 1, + "to_slot": 1 + } + ] +} diff --git a/data/templates/krea2-raw-i2i.json b/data/templates/krea2-raw-i2i.json new file mode 100644 index 0000000..dda771d --- /dev/null +++ b/data/templates/krea2-raw-i2i.json @@ -0,0 +1,294 @@ +{ + "name": "Image-to-Image (RAW base)", + "category": "Generation", + "order": 5, + "scope": { + "type": "family", + "architecture": "krea2", + "family": "krea2_raw" + }, + "created_at": 1782328500, + "id": "krea2-raw-i2i", + "nodes": [ + { + "type": "CLIPTextEncode", + "offset": [ + 899.9999977111815, + 0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "CLIPTextEncode", + "offset": [ + 899.9999977111815, + 180.0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "KSampler", + "offset": [ + 1699.9999685974124, + 160.0 + ], + "size": [ + 330, + 474 + ], + "widgets_values": [ + 826502184750291, + "randomize", + 40, + 5.0, + "er_sde", + "beta", + 0.6 + ], + "properties": { + "Node name for S&R": "KSampler" + } + }, + { + "type": "VAEDecode", + "offset": [ + 1750.0000429382326, + 20.0 + ], + "size": [ + 225, + 72 + ], + "widgets_values": [], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "type": "SaveImage", + "offset": [ + 2090.0000285339356, + -10.0 + ], + "size": [ + 276.984375, + 322.984375 + ], + "widgets_values": [ + "Krea2-RAW/img2img/%date:yyyy-MM-dd_hhmmss%" + ], + "properties": {} + }, + { + "type": "CLIPLoader", + "offset": [ + 860.0000519714356, + 340.0 + ], + "size": [ + 346.78125, + 117.328125 + ], + "widgets_values": [ + "qwen3vl_4b_fp8_scaled.safetensors", + "krea2", + "default" + ], + "properties": { + "Node name for S&R": "CLIPLoader" + } + }, + { + "type": "UNETLoader", + "offset": [ + 1239.9999069433584, + 0 + ], + "size": [ + 371.65625, + 82.65625 + ], + "widgets_values": [ + "krea2_raw_fp8_scaled.safetensors", + "default" + ], + "properties": { + "Node name for S&R": "UNETLoader" + } + }, + { + "type": "LoadImage", + "offset": [ + 1240.0, + 210.0 + ], + "size": [ + 270, + 340 + ], + "widgets_values": [ + "", + "image" + ], + "properties": { + "Node name for S&R": "LoadImage" + } + }, + { + "type": "VAELoader", + "offset": [ + 1239.993419293212, + 590.0 + ], + "size": [ + 366.40625, + 82.65625 + ], + "widgets_values": [ + "qwen_image_vae.safetensors" + ], + "properties": { + "Node name for S&R": "VAELoader" + } + }, + { + "type": "VAEEncode", + "offset": [ + 1610.0, + 210.0 + ], + "size": [ + 225, + 72 + ], + "widgets_values": [], + "properties": { + "Node name for S&R": "VAEEncode" + } + }, + { + "type": "ModelSamplingFlux", + "offset": [ + 1450.0, + 90.0 + ], + "size": [ + 300, + 130 + ], + "widgets_values": [ + 0.90625, + 0.5, + 1024, + 1024 + ], + "properties": { + "Node name for S&R": "ModelSamplingFlux" + } + } + ], + "connections": [ + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 1 + }, + { + "from_node_idx": 1, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 2 + }, + { + "from_node_idx": 2, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 0 + }, + { + "from_node_idx": 3, + "from_slot": 0, + "to_node_idx": 4, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 0, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 0 + }, + { + "from_node_idx": 6, + "from_slot": 0, + "to_node_idx": 10, + "to_slot": 0 + }, + { + "from_node_idx": 10, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 0 + }, + { + "from_node_idx": 8, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 1 + }, + { + "from_node_idx": 9, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 3 + }, + { + "from_node_idx": 7, + "from_slot": 0, + "to_node_idx": 9, + "to_slot": 0 + }, + { + "from_node_idx": 8, + "from_slot": 0, + "to_node_idx": 9, + "to_slot": 1 + } + ], + "anchorConnections": [ + { + "from_slot": 1, + "to_node_idx": 0, + "to_slot": 1 + }, + { + "from_slot": 2, + "to_node_idx": 1, + "to_slot": 1 + } + ] +} \ No newline at end of file diff --git a/data/templates/krea2-raw-t2i-highdetail.json b/data/templates/krea2-raw-t2i-highdetail.json new file mode 100644 index 0000000..967bedf --- /dev/null +++ b/data/templates/krea2-raw-t2i-highdetail.json @@ -0,0 +1,300 @@ +{ + "name": "Text-to-Image (High Detail)", + "category": "Generation", + "order": 6, + "scope": { + "type": "family", + "architecture": "krea2", + "family": "krea2_raw" + }, + "created_at": 1782370000, + "id": "krea2-raw-t2i-highdetail", + "nodes": [ + { + "type": "CLIPTextEncode", + "offset": [ + 900.0, + 0.0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "CLIPTextEncode", + "offset": [ + 900.0, + 180.0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "KSampler", + "offset": [ + 1740.0, + 160.0 + ], + "size": [ + 330, + 474 + ], + "widgets_values": [ + 826502184750291, + "randomize", + 52, + 5.0, + "er_sde", + "beta", + 1 + ], + "properties": { + "Node name for S&R": "KSampler" + } + }, + { + "type": "VAEUtils_VAEDecodeTiled", + "offset": [ + 1790.0, + 20.0 + ], + "size": [ + 300, + 220 + ], + "widgets_values": [ + -1, + false, + 512, + 64, + 4096, + 64 + ], + "properties": { + "Node name for S&R": "VAEUtils_VAEDecodeTiled" + } + }, + { + "type": "SaveImage", + "offset": [ + 2150.0, + -10.0 + ], + "size": [ + 276.984375, + 322.984375 + ], + "widgets_values": [ + "Krea2-RAW/highdetail/%date:yyyy-MM-dd_hhmmss%" + ], + "properties": {} + }, + { + "type": "CLIPLoader", + "offset": [ + 860.0, + 360.0 + ], + "size": [ + 346.78125, + 117.328125 + ], + "widgets_values": [ + "qwen3vl_4b_fp8_scaled.safetensors", + "krea2", + "default" + ], + "properties": { + "Node name for S&R": "CLIPLoader" + } + }, + { + "type": "UNETLoader", + "offset": [ + 1240.0, + 0.0 + ], + "size": [ + 371.65625, + 82.65625 + ], + "widgets_values": [ + "krea2_raw_bf16.safetensors", + "default" + ], + "properties": { + "Node name for S&R": "UNETLoader" + } + }, + { + "type": "EmptySD3LatentImage", + "offset": [ + 1250.0, + 220.0 + ], + "size": [ + 324, + 144 + ], + "widgets_values": [ + 1024, + 1024, + 1 + ], + "properties": { + "Node name for S&R": "EmptySD3LatentImage" + } + }, + { + "type": "VAEUtils_CustomVAELoader", + "offset": [ + 1240.0, + 470.0 + ], + "size": [ + 366.40625, + 82.65625 + ], + "widgets_values": [ + "Wan2.1_VAE_upscale2x_imageonly_real_v1.safetensors", + true + ], + "properties": { + "Node name for S&R": "VAEUtils_CustomVAELoader" + } + }, + { + "type": "ConditioningKrea2Rebalance", + "offset": [ + 1180.0, + 0.0 + ], + "size": [ + 300, + 106 + ], + "widgets_values": [ + 2.0, + "1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.6,2.5,1.05,2.0,1.0" + ], + "properties": { + "Node name for S&R": "ConditioningKrea2Rebalance" + } + }, + { + "type": "ModelSamplingFlux", + "offset": [ + 1460.0, + 90.0 + ], + "size": [ + 300, + 130 + ], + "widgets_values": [ + 0.90625, + 0.5, + 1024, + 1024 + ], + "properties": { + "Node name for S&R": "ModelSamplingFlux" + } + } + ], + "connections": [ + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 9, + "to_slot": 0 + }, + { + "from_node_idx": 9, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 1 + }, + { + "from_node_idx": 1, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 2 + }, + { + "from_node_idx": 6, + "from_slot": 0, + "to_node_idx": 10, + "to_slot": 0 + }, + { + "from_node_idx": 10, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 0 + }, + { + "from_node_idx": 7, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 3 + }, + { + "from_node_idx": 2, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 0 + }, + { + "from_node_idx": 8, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 1 + }, + { + "from_node_idx": 3, + "from_slot": 0, + "to_node_idx": 4, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 0, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 0 + } + ], + "anchorConnections": [ + { + "from_slot": 1, + "to_node_idx": 0, + "to_slot": 1 + }, + { + "from_slot": 2, + "to_node_idx": 1, + "to_slot": 1 + } + ] +} diff --git a/data/templates/krea2-raw-t2i.json b/data/templates/krea2-raw-t2i.json new file mode 100644 index 0000000..68156e2 --- /dev/null +++ b/data/templates/krea2-raw-t2i.json @@ -0,0 +1,268 @@ +{ + "name": "Text-to-Image (RAW base)", + "category": "Generation", + "order": 4, + "scope": { + "type": "family", + "architecture": "krea2", + "family": "krea2_raw" + }, + "created_at": 1782328400, + "id": "c5a9e2f0-1b3d-4e6a-8c7f-9d2b4a6e1f08", + "nodes": [ + { + "type": "CLIPTextEncode", + "offset": [ + 899.9999977111815, + 0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "CLIPTextEncode", + "offset": [ + 899.9999977111815, + 180.0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "KSampler", + "offset": [ + 1699.9999685974124, + 160.0 + ], + "size": [ + 330, + 474 + ], + "widgets_values": [ + 826502184750291, + "randomize", + 52, + 5.0, + "er_sde", + "beta", + 1 + ], + "properties": { + "Node name for S&R": "KSampler" + } + }, + { + "type": "VAEDecode", + "offset": [ + 1750.0000429382326, + 20.0 + ], + "size": [ + 225, + 72 + ], + "widgets_values": [], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "type": "SaveImage", + "offset": [ + 2090.0000285339356, + -10.0 + ], + "size": [ + 276.984375, + 322.984375 + ], + "widgets_values": [ + "Krea2-RAW/%date:yyyy-MM-dd_hhmmss%" + ], + "properties": {} + }, + { + "type": "CLIPLoader", + "offset": [ + 860.0000519714356, + 340.0 + ], + "size": [ + 346.78125, + 117.328125 + ], + "widgets_values": [ + "qwen3vl_4b_fp8_scaled.safetensors", + "krea2", + "default" + ], + "properties": { + "Node name for S&R": "CLIPLoader" + } + }, + { + "type": "UNETLoader", + "offset": [ + 1239.9999069433584, + 0 + ], + "size": [ + 371.65625, + 82.65625 + ], + "widgets_values": [ + "krea2_raw_fp8_scaled.safetensors", + "default" + ], + "properties": { + "Node name for S&R": "UNETLoader" + } + }, + { + "type": "EmptySD3LatentImage", + "offset": [ + 1249.9999315600576, + 200.0 + ], + "size": [ + 324, + 144 + ], + "widgets_values": [ + 1024, + 1024, + 1 + ], + "properties": { + "Node name for S&R": "EmptySD3LatentImage" + } + }, + { + "type": "VAELoader", + "offset": [ + 1239.993419293212, + 450.0 + ], + "size": [ + 366.40625, + 82.65625 + ], + "widgets_values": [ + "qwen_image_vae.safetensors" + ], + "properties": { + "Node name for S&R": "VAELoader" + } + }, + { + "type": "ModelSamplingFlux", + "offset": [ + 1450.0, + 90.0 + ], + "size": [ + 300, + 130 + ], + "widgets_values": [ + 0.90625, + 0.5, + 1024, + 1024 + ], + "properties": { + "Node name for S&R": "ModelSamplingFlux" + } + } + ], + "connections": [ + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 1 + }, + { + "from_node_idx": 1, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 2 + }, + { + "from_node_idx": 2, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 0 + }, + { + "from_node_idx": 3, + "from_slot": 0, + "to_node_idx": 4, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 0, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 0 + }, + { + "from_node_idx": 6, + "from_slot": 0, + "to_node_idx": 9, + "to_slot": 0 + }, + { + "from_node_idx": 9, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 0 + }, + { + "from_node_idx": 7, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 3 + }, + { + "from_node_idx": 8, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 1 + } + ], + "anchorConnections": [ + { + "from_slot": 1, + "to_node_idx": 0, + "to_slot": 1 + }, + { + "from_slot": 2, + "to_node_idx": 1, + "to_slot": 1 + } + ] +} \ No newline at end of file diff --git a/data/templates/krea2-turbo-i2i-highdetail.json b/data/templates/krea2-turbo-i2i-highdetail.json new file mode 100644 index 0000000..e0c0bb9 --- /dev/null +++ b/data/templates/krea2-turbo-i2i-highdetail.json @@ -0,0 +1,289 @@ +{ + "name": "Image-to-Image (High Detail)", + "category": "Generation", + "order": 4, + "scope": { + "type": "family", + "architecture": "krea2", + "family": "krea2_turbo" + }, + "created_at": 1782364000, + "id": "b7c3e8a1-4f2d-4a6b-9c0e-2a1f6d3b9f0a", + "nodes": [ + { + "type": "CLIPTextEncode", + "offset": [ + 820.0, + 20.0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "KSampler", + "offset": [ + 1440.0, + 120.0 + ], + "size": [ + 330, + 474 + ], + "widgets_values": [ + 826502184750291, + "randomize", + 8, + 1, + "euler", + "simple", + 0.6 + ], + "properties": { + "Node name for S&R": "KSampler" + } + }, + { + "type": "VAEUtils_VAEDecodeTiled", + "offset": [ + 1800.0, + 20.0 + ], + "size": [ + 300, + 220 + ], + "widgets_values": [ + -1, + false, + 512, + 64, + 4096, + 64 + ], + "properties": { + "Node name for S&R": "VAEUtils_VAEDecodeTiled" + } + }, + { + "type": "SaveImage", + "offset": [ + 2140.0, + -10.0 + ], + "size": [ + 276.984375, + 322.984375 + ], + "widgets_values": [ + "Krea2/img2img-highdetail/%date:yyyy-MM-dd_hhmmss%" + ], + "properties": {} + }, + { + "type": "CLIPLoader", + "offset": [ + 820.0, + 240.0 + ], + "size": [ + 346.78125, + 117.328125 + ], + "widgets_values": [ + "qwen3vl_4b_fp8_scaled.safetensors", + "krea2", + "default" + ], + "properties": { + "Node name for S&R": "CLIPLoader" + } + }, + { + "type": "UNETLoader", + "offset": [ + 1100.0, + 280.0 + ], + "size": [ + 371.65625, + 82.65625 + ], + "widgets_values": [ + "krea2_turbo_bf16.safetensors", + "default" + ], + "properties": { + "Node name for S&R": "UNETLoader" + } + }, + { + "type": "LoadImage", + "offset": [ + 780.0, + 420.0 + ], + "size": [ + 270, + 340 + ], + "widgets_values": [ + "", + "image" + ], + "properties": { + "Node name for S&R": "LoadImage" + } + }, + { + "type": "VAEUtils_CustomVAELoader", + "offset": [ + 1440.0, + 620.0 + ], + "size": [ + 366.40625, + 82.65625 + ], + "widgets_values": [ + "Wan2.1_VAE_upscale2x_imageonly_real_v1.safetensors", + true + ], + "properties": { + "Node name for S&R": "VAEUtils_CustomVAELoader" + } + }, + { + "type": "ConditioningKrea2Rebalance", + "offset": [ + 1100.0, + 20.0 + ], + "size": [ + 300, + 106 + ], + "widgets_values": [ + 2.0, + "1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.6,2.5,1.05,2.0,1.0" + ], + "properties": { + "Node name for S&R": "ConditioningKrea2Rebalance" + } + }, + { + "type": "VAEEncode", + "offset": [ + 1130.0, + 500.0 + ], + "size": [ + 225, + 72 + ], + "widgets_values": [], + "properties": { + "Node name for S&R": "VAEEncode" + } + }, + { + "type": "VAELoader", + "offset": [ + 780.0, + 790.0 + ], + "size": [ + 366.40625, + 82.65625 + ], + "widgets_values": [ + "qwen_image_vae.safetensors" + ], + "properties": { + "Node name for S&R": "VAELoader" + } + } + ], + "connections": [ + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 8, + "to_slot": 0 + }, + { + "from_node_idx": 8, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 1 + }, + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 2 + }, + { + "from_node_idx": 1, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 0 + }, + { + "from_node_idx": 7, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 1 + }, + { + "from_node_idx": 2, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 0 + }, + { + "from_node_idx": 4, + "from_slot": 0, + "to_node_idx": 0, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 0 + }, + { + "from_node_idx": 9, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 3 + }, + { + "from_node_idx": 6, + "from_slot": 0, + "to_node_idx": 9, + "to_slot": 0 + }, + { + "from_node_idx": 10, + "from_slot": 0, + "to_node_idx": 9, + "to_slot": 1 + } + ], + "anchorConnections": [ + { + "from_slot": 1, + "to_node_idx": 0, + "to_slot": 1 + } + ] +} \ No newline at end of file diff --git a/data/templates/krea2-turbo-i2i.json b/data/templates/krea2-turbo-i2i.json new file mode 100644 index 0000000..61bb882 --- /dev/null +++ b/data/templates/krea2-turbo-i2i.json @@ -0,0 +1,240 @@ +{ + "name": "Image-to-Image", + "category": "Generation", + "order": 1, + "scope": { + "type": "family", + "architecture": "krea2", + "family": "krea2_turbo" + }, + "created_at": 1782327800, + "id": "krea2-turbo-i2i", + "nodes": [ + { + "type": "CLIPTextEncode", + "offset": [ + 899.9999977111815, + 0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "KSampler", + "offset": [ + 1699.9999685974124, + 160.0 + ], + "size": [ + 330, + 474 + ], + "widgets_values": [ + 826502184750291, + "randomize", + 8, + 1, + "euler", + "simple", + 0.6 + ], + "properties": { + "Node name for S&R": "KSampler" + } + }, + { + "type": "VAEDecode", + "offset": [ + 1750.0000429382326, + 20.0 + ], + "size": [ + 225, + 72 + ], + "widgets_values": [], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "type": "SaveImage", + "offset": [ + 2090.0000285339356, + -10.0 + ], + "size": [ + 276.984375, + 322.984375 + ], + "widgets_values": [ + "Krea2/img2img/%date:yyyy-MM-dd_hhmmss%" + ], + "properties": {} + }, + { + "type": "CLIPLoader", + "offset": [ + 860.0000519714356, + 200.0 + ], + "size": [ + 346.78125, + 117.328125 + ], + "widgets_values": [ + "qwen3vl_4b_fp8_scaled.safetensors", + "krea2", + "default" + ], + "properties": { + "Node name for S&R": "CLIPLoader" + } + }, + { + "type": "UNETLoader", + "offset": [ + 1239.9999069433584, + 0 + ], + "size": [ + 371.65625, + 82.65625 + ], + "widgets_values": [ + "krea2_turbo_fp8_scaled.safetensors", + "default" + ], + "properties": { + "Node name for S&R": "UNETLoader" + } + }, + { + "type": "LoadImage", + "offset": [ + 1240.0, + 210.0 + ], + "size": [ + 270, + 340 + ], + "widgets_values": [ + "", + "image" + ], + "properties": { + "Node name for S&R": "LoadImage" + } + }, + { + "type": "VAELoader", + "offset": [ + 1239.993419293212, + 390.0 + ], + "size": [ + 366.40625, + 82.65625 + ], + "widgets_values": [ + "qwen_image_vae.safetensors" + ], + "properties": { + "Node name for S&R": "VAELoader" + } + }, + { + "type": "VAEEncode", + "offset": [ + 1610.0, + 210.0 + ], + "size": [ + 225, + 72 + ], + "widgets_values": [], + "properties": { + "Node name for S&R": "VAEEncode" + } + } + ], + "connections": [ + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 1 + }, + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 2 + }, + { + "from_node_idx": 1, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 0 + }, + { + "from_node_idx": 2, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 0 + }, + { + "from_node_idx": 4, + "from_slot": 0, + "to_node_idx": 0, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 0 + }, + { + "from_node_idx": 7, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 1 + }, + { + "from_node_idx": 8, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 3 + }, + { + "from_node_idx": 6, + "from_slot": 0, + "to_node_idx": 8, + "to_slot": 0 + }, + { + "from_node_idx": 7, + "from_slot": 0, + "to_node_idx": 8, + "to_slot": 1 + } + ], + "anchorConnections": [ + { + "from_slot": 1, + "to_node_idx": 0, + "to_slot": 1 + } + ] +} \ No newline at end of file diff --git a/data/templates/krea2-turbo-t2i-3d-regional.json b/data/templates/krea2-turbo-t2i-3d-regional.json new file mode 100644 index 0000000..35681d9 --- /dev/null +++ b/data/templates/krea2-turbo-t2i-3d-regional.json @@ -0,0 +1,270 @@ +{ + "name": "Text-to-Image 3D Regional", + "scope": { + "type": "family", + "architecture": "krea2", + "family": "krea2_turbo" + }, + "created_at": 1781920000, + "nodes": [ + { + "type": "PromptChain_RegionalConditioning", + "offset": [ + 820, + 120 + ], + "size": [ + 300, + 170 + ], + "widgets_values": [ + 22 + ], + "properties": { + "Node name for S&R": "PromptChain_RegionalConditioning" + } + }, + { + "type": "KSampler", + "offset": [ + 1830, + 60 + ], + "size": [ + 344.15625, + 565.328125 + ], + "widgets_values": [ + 674787291728667, + "randomize", + 8, + 1, + "euler", + "simple", + 1 + ], + "properties": { + "Node name for S&R": "KSampler" + } + }, + { + "type": "VAEDecode", + "offset": [ + 2230, + 60 + ], + "size": [ + 225, + 72 + ], + "widgets_values": [], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "type": "SaveImage", + "offset": [ + 2230, + 200 + ], + "size": [ + 247.25, + 419.046875 + ], + "widgets_values": [ + "Krea2/%date:yyyy-MM-dd_hhmmss%" + ], + "properties": {} + }, + { + "type": "CLIPLoader", + "offset": [ + 820, + 320 + ], + "size": [ + 346.78125, + 117.328125 + ], + "widgets_values": [ + "qwen3vl_4b_fp8_scaled.safetensors", + "krea2", + "default" + ], + "properties": { + "Node name for S&R": "CLIPLoader" + } + }, + { + "type": "UNETLoader", + "offset": [ + 820, + 520 + ], + "size": [ + 304.953125, + 82.65625 + ], + "widgets_values": [ + "krea2_turbo_fp8_scaled.safetensors", + "default" + ], + "properties": { + "Node name for S&R": "UNETLoader" + } + }, + { + "type": "EmptySD3LatentImage", + "offset": [ + 1180, + 320 + ], + "size": [ + 270, + 144 + ], + "widgets_values": [ + 1024, + 1536, + 1 + ], + "properties": { + "Node name for S&R": "EmptySD3LatentImage" + } + }, + { + "type": "VAELoader", + "offset": [ + 820, + 680 + ], + "size": [ + 246, + 83 + ], + "widgets_values": [ + "qwen_image_vae.safetensors" + ], + "properties": { + "Node name for S&R": "VAELoader" + } + }, + { + "type": "PromptChain_PoseStudio", + "offset": [ + -1350, + 0 + ], + "size": [ + 360, + 470 + ], + "widgets_values": [ + "", + "{\"version\":3,\"camera\":{\"position\":[-0.11421765907369465,2.2679667675054747,3.7640721683793985],\"target\":[-0.08116832790578885,0.9626632788387957,0.003294416573047413]},\"activeIndex\":1,\"figures\":[{\"uid\":4,\"body\":\"custom\",\"root\":[-0.6544460683107087,1.83557839158723e-7,-0.1296069154697062],\"rootQuat\":[0,0,0,1],\"rootScale\":[1.0847089667745293,1.0847089667745293,1.0847089667745293],\"joints\":{\"root\":[0.530589759349823,0,0,0.8476287722587585],\"shoulderL\":[-0.008142700418829918,0.012219684198498726,-0.33881765604019165,0.9407374858856201],\"elbowL\":[0.3669451177120209,0.0018381300615146756,0.0031449354719370604,0.9302355051040649],\"wristL\":[0.11023613810539246,0.41108429431915283,-0.016057291999459267,0.9047650694847107],\"shoulderR\":[-0.008142700418829918,-0.012219684198498726,0.33881765604019165,0.9407374858856201],\"elbowR\":[0.3669451177120209,-0.0018381300615146756,-0.0031449354719370604,0.9302355051040649],\"wristR\":[0.11023613810539246,-0.41108429431915283,0.016057291999459267,0.9047650694847107],\"neck\":[0.16243939101696014,0,0,0.9867185354232788],\"head\":[-0.20577044785022736,0,0,0.978600263595581],\"hipL\":[-0.9580745100975037,-0.09174012392759323,0.21390390396118164,0.16709932684898376],\"kneeL\":[0.058844611048698425,0.23217718303203583,-0.008530011400580406,0.9708544611930847],\"ankleL\":[-0.5353211164474487,-0.0010657769162207842,0.0007613753550685942,0.8446476459503174],\"hipR\":[-0.9580745100975037,0.09174012392759323,-0.21390390396118164,0.16709932684898376],\"kneeR\":[0.058844611048698425,-0.23217718303203583,0.008530011400580406,0.9708544611930847],\"ankleR\":[-0.5353211164474487,0.0010657769162207842,-0.0007613753550685942,0.8446476459503174],\"chest\":[0.008510730229318142,0,0,0.9999638199806213],\"spine\":[0.3344581127166748,0,0,0.9424106478691101],\"back\":[0,0,0,1]},\"jointPos\":{\"Root\":[0,-0.000562518835067749,6.100162863731384e-8],\"pelvis\":[0,-0.015393643795202175,0.8765072291716933],\"spine_01\":[0,0.08837844353790084,-0.0000035875321676330854],\"spine_02\":[0,0.06657692409741382,-0.0015471302564643939],\"spine_03\":[0,0.07465957864769734,-0.005422550541500943],\"spine_04\":[0,0.1554920266911116,0.008057116608445842],\"clavicle_l\":[0.022795276308897883,0.08298090375804652,0.026856951939407736],\"upperarm_l\":[-0.002402820275165144,0.14708170919523886,-0.0024412409402430058],\"lowerarm_l\":[-0.0036035171942785382,0.2335675825791744,0.007550966273508189],\"hand_l\":[-0.0022912098793312925,0.23001713307652003,0.00286084097691249],\"index_01_l\":[-0.014275551244888145,0.10155431967965949,0.00913081387989223],\"index_02_l\":[-0.00032961389198741625,0.024996880173906295,-0.00044333142189619445],\"index_03_l\":[-0.00028979239747665514,0.02228745664691208,-0.000271643739930975],\"middle_01_l\":[0.0011213888258983697,0.0983148121922568,-0.010979589074850082],\"middle_02_l\":[-0.0004448858817340806,0.032625457237979084,-0.00045865285210311413],\"middle_03_l\":[-0.0003890491255636637,0.026699312448423978,-0.00020532777125481538],\"pinky_01_l\":[0.030837264833583806,0.08091115868228371,-0.0306820534557725],\"pinky_02_l\":[-0.0004330708179622891,0.020605956754783012,-0.0003710941079286083],\"pinky_03_l\":[-0.0002436345233339428,0.015057439972830858,0.000014291528107908793],\"ring_01_l\":[0.014671692488870274,0.09115619229805816,-0.023141342176434897],\"ring_02_l\":[-0.00039252417445823095,0.028744175334092386,-0.0004921690172826243],\"ring_03_l\":[-0.00043109167184714653,0.02398470316741926,-0.0001844997920367557],\"thumb_01_l\":[-0.014337413944303988,0.037947991551694336,0.014606909942813218],\"thumb_02_l\":[0.0002165158900121842,0.029646236808427297,-0.0005050097582473731],\"thumb_03_l\":[0.0001837790356754948,0.035048878259355355,-0.0005737583730175779],\"clavicle_r\":[-0.022795276308897883,0.08298090375804652,0.026856951939407736],\"upperarm_r\":[0.002402820275165144,0.14708170919523886,-0.0024412409402430058],\"lowerarm_r\":[0.0036035171942785382,0.2335675825791744,0.007550966273508189],\"hand_r\":[0.0022912098793312925,0.23001713307652003,0.00286084097691249],\"index_01_r\":[0.014275551244888145,0.10155431967965949,0.00913081387989223],\"index_02_r\":[0.00032961389198741625,0.024996880173906295,-0.00044333142189619445],\"index_03_r\":[0.00028979239747665514,0.02228745664691208,-0.000271643739930975],\"middle_01_r\":[-0.0011213888258983697,0.0983148121922568,-0.010979589074850082],\"middle_02_r\":[0.0004448858817340806,0.032625457237979084,-0.00045865285210311413],\"middle_03_r\":[0.0003890491255636637,0.026699312448423978,-0.00020532777125481538],\"pinky_01_r\":[-0.030837264833583806,0.08091115868228371,-0.0306820534557725],\"pinky_02_r\":[0.0004330708179622891,0.020605956754783012,-0.0003710941079286083],\"pinky_03_r\":[0.0002436345233339428,0.015057439972830858,0.000014291528107908793],\"ring_01_r\":[-0.014671692488870274,0.09115619229805816,-0.023141342176434897],\"ring_02_r\":[0.00039252417445823095,0.028744175334092386,-0.0004921690172826243],\"ring_03_r\":[0.00043109167184714653,0.02398470316741926,-0.0001844997920367557],\"thumb_01_r\":[0.014337413944303988,0.037947991551694336,0.014606909942813218],\"thumb_02_r\":[-0.0002165158900121842,0.029646236808427297,-0.0005050097582473731],\"thumb_03_r\":[-0.0001837790356754948,0.035048878259355355,-0.0005737583730175779],\"neck_01\":[0,0.1554920266911116,0.008057112766740222],\"head\":[0,0.09913267713272944,-0.0024949000217020546],\"thigh_l\":[0.10406423709112762,-0.01035807409789413,-0.015486810940274154],\"calf_l\":[-0.0018221052984396628,0.38333685158674297,0.0019527052451546005],\"foot_l\":[0.0012040060549528171,0.41225113686717424,0.0060748945009739454],\"ball_l\":[-0.004144214297411963,0.1319912777786764,0.0008641794556751847],\"thigh_r\":[-0.10406423709112762,-0.01035807409789413,-0.015486810940274154],\"calf_r\":[0.0018221052984396628,0.38333685158674297,0.0019527052451546005],\"foot_r\":[-0.0012040060549528171,0.41225113686717424,0.0060748945009739454],\"ball_r\":[0.004144214297411963,0.1319912777786764,0.0008641794556751847]},\"digits\":{\"index_01_l\":[0.11009154468774796,0.47919291257858276,-0.0033358719665557146,0.8707714676856995],\"index_02_l\":[0.10867490619421005,0.0028905405197292566,0.006149682216346264,0.9940541386604309],\"index_03_l\":[0.06272030621767044,0.0006448967615142465,0.018671324476599693,0.9978562593460083],\"middle_01_l\":[0.09819573163986206,0.41311776638031006,-0.15238040685653687,0.8924525380134583],\"middle_02_l\":[0.04684380069375038,0.0005204545450396836,0.03666960448026657,0.9982287883758545],\"middle_03_l\":[0.06767940521240234,0.0009195368620567024,-0.005850149318575859,0.9976895451545715],\"pinky_01_l\":[0.04672148823738098,0.29210352897644043,-0.3033210337162018,0.9058084487915039],\"pinky_02_l\":[0.08093646168708801,0.0011062186677008867,0.024598531424999237,0.9964150786399841],\"pinky_03_l\":[-0.008727755397558212,-0.00023478623188566417,-0.01132167037576437,0.9998977780342102],\"ring_01_l\":[0.05214594677090645,0.3895569145679474,-0.2304530292749405,0.890178382396698],\"ring_02_l\":[0.059518661350011826,0.00033943928428925574,0.04314512386918068,0.9972943067550659],\"ring_03_l\":[0.05962633714079857,0.00068375637056306,-0.02453024499118328,0.9979191422462463],\"thumb_01_l\":[0.2744263708591461,0.731201171875,0.43757393956184387,0.44560524821281433],\"thumb_02_l\":[0.06898587942123413,-0.017102878540754318,-0.18289531767368317,0.9805599451065063],\"thumb_03_l\":[0.14205099642276764,-0.006924070883542299,-0.032810747623443604,0.9892911911010742],\"index_01_r\":[0.11009154468774796,-0.47919291257858276,0.0033358719665557146,0.8707714676856995],\"index_02_r\":[0.10867490619421005,-0.0028905405197292566,-0.006149682216346264,0.9940541386604309],\"index_03_r\":[0.06272030621767044,-0.0006448967615142465,-0.018671324476599693,0.9978562593460083],\"middle_01_r\":[0.09819573163986206,-0.41311776638031006,0.15238040685653687,0.8924525380134583],\"middle_02_r\":[0.04684380069375038,-0.0005204545450396836,-0.03666960448026657,0.9982287883758545],\"middle_03_r\":[0.06767940521240234,-0.0009195368620567024,0.005850149318575859,0.9976895451545715],\"pinky_01_r\":[0.04672148823738098,-0.29210352897644043,0.3033210337162018,0.9058084487915039],\"pinky_02_r\":[0.08093646168708801,-0.0011062186677008867,-0.024598531424999237,0.9964150786399841],\"pinky_03_r\":[-0.008727755397558212,0.00023478623188566417,0.01132167037576437,0.9998977780342102],\"ring_01_r\":[0.05214594677090645,-0.3895569145679474,0.2304530292749405,0.890178382396698],\"ring_02_r\":[0.059518661350011826,-0.00033943928428925574,-0.04314512386918068,0.9972943067550659],\"ring_03_r\":[0.05962633714079857,-0.00068375637056306,0.02453024499118328,0.9979191422462463],\"thumb_01_r\":[0.2744263708591461,-0.731201171875,-0.43757393956184387,0.44560524821281433],\"thumb_02_r\":[0.06898587942123413,0.017102878540754318,0.18289531767368317,0.9805599451065063],\"thumb_03_r\":[0.14205099642276764,0.006924070883542299,0.032810747623443604,0.9892911911010742],\"ball_l\":[-0.26220664381980896,0.0072789317928254604,0.029169803485274315,0.964543342590332],\"ball_r\":[-0.26220664381980896,-0.0072789317928254604,-0.029169803485274315,0.964543342590332]},\"spine\":{\"spine_01\":[0.3344581127166748,0,0,0.9424106478691101],\"spine_02\":[-0.10261907428503036,0,0,0.9947206974029541],\"spine_03\":[0.008510730229318142,0,0,0.9999638199806213],\"spine_04\":[0,0,0,1],\"neck_01\":[0.16243939101696014,0,0,0.9867185354232788],\"head\":[-0.20577044785022736,0,0,0.978600263595581]},\"morphs\":{},\"skeletalAmount\":{\"__custom\":1},\"footPins\":{},\"shoes\":false,\"sliders\":{\"gender\":0.5,\"age\":0.5,\"muscle\":0.5,\"weight\":0.5,\"height\":0.5,\"proportions\":0.5,\"african\":0.3333333333333333,\"asian\":0.3333333333333333,\"caucasian\":0.3333333333333333,\"cupsize\":0.5,\"firmness\":0.5,\"breast-trans\":0,\"breast-dist\":0,\"breast-point\":0,\"breast-volume-vert\":0,\"nipple-size\":0,\"nipple-point\":0,\"fem-apple\":0,\"fem-diamond\":0,\"fem-full-hourglass\":0,\"fem-neat-hourglass\":0,\"fem-invert-triangle\":0,\"fem-lean-column\":0,\"fem-rectangle\":0,\"fem-triangle\":0,\"fem-adrenal\":0,\"fem-liver\":0,\"fem-ovary\":0,\"fem-thyroid\":0,\"man-apple\":0,\"man-rectangle\":0,\"man-lean-column\":0,\"man-triangle\":0,\"man-invert-triangle\":0,\"man-trapezoid\":0,\"man-adrenal\":0,\"man-liver\":0,\"man-gonadal\":0,\"man-thyroid\":0,\"torso-scale-depth\":0,\"torso-scale-horiz\":0,\"torso-scale-vert\":0,\"torso-trans-horiz\":0,\"torso-trans-vert\":0,\"torso-trans-depth\":0,\"torso-vshape\":0,\"torso-dorsi\":0,\"torso-pectoral\":0,\"fingers-distance\":0,\"fingers-diameter\":0,\"fingers-length\":0,\"hand-scale\":0,\"hand-position\":0,\"hips\":0,\"glutes\":0,\"stomach\":0,\"neck-circ\":0,\"neck-height\":0,\"head-age\":0,\"head-fat\":0,\"head-angle\":0,\"head-oval\":0,\"head-round\":0,\"head-rectangular\":0,\"head-square\":0,\"head-triangular\":0,\"head-invtriangular\":0,\"head-diamond\":0,\"head-parietal\":0}},{\"uid\":5,\"body\":\"custom\",\"root\":[0.5167425402036215,1.83557839158723e-7,-0.1296069154697062],\"rootQuat\":[0,0,0,1],\"rootScale\":[1.0847089667745293,1.0847089667745293,1.0847089667745293],\"joints\":{\"root\":[0.530589759349823,0,0,0.8476287722587585],\"shoulderL\":[-0.008142700418829918,0.012219684198498726,-0.33881765604019165,0.9407374858856201],\"elbowL\":[0.3669451177120209,0.0018381300615146756,0.0031449354719370604,0.9302355051040649],\"wristL\":[0.11023613810539246,0.41108429431915283,-0.016057291999459267,0.9047650694847107],\"shoulderR\":[-0.008142700418829918,-0.012219684198498726,0.33881765604019165,0.9407374858856201],\"elbowR\":[0.3669451177120209,-0.0018381300615146756,-0.0031449354719370604,0.9302355051040649],\"wristR\":[0.11023613810539246,-0.41108429431915283,0.016057291999459267,0.9047650694847107],\"neck\":[0.16243939101696014,0,0,0.9867185354232788],\"head\":[-0.20577044785022736,0,0,0.978600263595581],\"hipL\":[-0.9580745100975037,-0.09174012392759323,0.21390390396118164,0.16709932684898376],\"kneeL\":[0.058844611048698425,0.23217718303203583,-0.008530011400580406,0.9708544611930847],\"ankleL\":[-0.5353211164474487,-0.0010657769162207842,0.0007613753550685942,0.8446476459503174],\"hipR\":[-0.9580745100975037,0.09174012392759323,-0.21390390396118164,0.16709932684898376],\"kneeR\":[0.058844611048698425,-0.23217718303203583,0.008530011400580406,0.9708544611930847],\"ankleR\":[-0.5353211164474487,0.0010657769162207842,-0.0007613753550685942,0.8446476459503174],\"chest\":[0.008510730229318142,0,0,0.9999638199806213],\"spine\":[0.3344581127166748,0,0,0.9424106478691101],\"back\":[0,0,0,1]},\"jointPos\":{\"Root\":[0,-0.000562518835067749,6.100162863731384e-8],\"pelvis\":[0,-0.015393643795202175,0.8765072291716933],\"spine_01\":[0,0.08837844353790084,-0.0000035875321676330854],\"spine_02\":[0,0.06657692409741382,-0.0015471302564643939],\"spine_03\":[0,0.07465957864769734,-0.005422550541500943],\"spine_04\":[0,0.1554920266911116,0.008057116608445842],\"clavicle_l\":[0.022795276308897883,0.08298090375804652,0.026856951939407736],\"upperarm_l\":[-0.002402820275165144,0.14708170919523886,-0.0024412409402430058],\"lowerarm_l\":[-0.0036035171942785382,0.2335675825791744,0.007550966273508189],\"hand_l\":[-0.0022912098793312925,0.23001713307652003,0.00286084097691249],\"index_01_l\":[-0.014275551244888145,0.10155431967965949,0.00913081387989223],\"index_02_l\":[-0.00032961389198741625,0.024996880173906295,-0.00044333142189619445],\"index_03_l\":[-0.00028979239747665514,0.02228745664691208,-0.000271643739930975],\"middle_01_l\":[0.0011213888258983697,0.0983148121922568,-0.010979589074850082],\"middle_02_l\":[-0.0004448858817340806,0.032625457237979084,-0.00045865285210311413],\"middle_03_l\":[-0.0003890491255636637,0.026699312448423978,-0.00020532777125481538],\"pinky_01_l\":[0.030837264833583806,0.08091115868228371,-0.0306820534557725],\"pinky_02_l\":[-0.0004330708179622891,0.020605956754783012,-0.0003710941079286083],\"pinky_03_l\":[-0.0002436345233339428,0.015057439972830858,0.000014291528107908793],\"ring_01_l\":[0.014671692488870274,0.09115619229805816,-0.023141342176434897],\"ring_02_l\":[-0.00039252417445823095,0.028744175334092386,-0.0004921690172826243],\"ring_03_l\":[-0.00043109167184714653,0.02398470316741926,-0.0001844997920367557],\"thumb_01_l\":[-0.014337413944303988,0.037947991551694336,0.014606909942813218],\"thumb_02_l\":[0.0002165158900121842,0.029646236808427297,-0.0005050097582473731],\"thumb_03_l\":[0.0001837790356754948,0.035048878259355355,-0.0005737583730175779],\"clavicle_r\":[-0.022795276308897883,0.08298090375804652,0.026856951939407736],\"upperarm_r\":[0.002402820275165144,0.14708170919523886,-0.0024412409402430058],\"lowerarm_r\":[0.0036035171942785382,0.2335675825791744,0.007550966273508189],\"hand_r\":[0.0022912098793312925,0.23001713307652003,0.00286084097691249],\"index_01_r\":[0.014275551244888145,0.10155431967965949,0.00913081387989223],\"index_02_r\":[0.00032961389198741625,0.024996880173906295,-0.00044333142189619445],\"index_03_r\":[0.00028979239747665514,0.02228745664691208,-0.000271643739930975],\"middle_01_r\":[-0.0011213888258983697,0.0983148121922568,-0.010979589074850082],\"middle_02_r\":[0.0004448858817340806,0.032625457237979084,-0.00045865285210311413],\"middle_03_r\":[0.0003890491255636637,0.026699312448423978,-0.00020532777125481538],\"pinky_01_r\":[-0.030837264833583806,0.08091115868228371,-0.0306820534557725],\"pinky_02_r\":[0.0004330708179622891,0.020605956754783012,-0.0003710941079286083],\"pinky_03_r\":[0.0002436345233339428,0.015057439972830858,0.000014291528107908793],\"ring_01_r\":[-0.014671692488870274,0.09115619229805816,-0.023141342176434897],\"ring_02_r\":[0.00039252417445823095,0.028744175334092386,-0.0004921690172826243],\"ring_03_r\":[0.00043109167184714653,0.02398470316741926,-0.0001844997920367557],\"thumb_01_r\":[0.014337413944303988,0.037947991551694336,0.014606909942813218],\"thumb_02_r\":[-0.0002165158900121842,0.029646236808427297,-0.0005050097582473731],\"thumb_03_r\":[-0.0001837790356754948,0.035048878259355355,-0.0005737583730175779],\"neck_01\":[0,0.1554920266911116,0.008057112766740222],\"head\":[0,0.09913267713272944,-0.0024949000217020546],\"thigh_l\":[0.10406423709112762,-0.01035807409789413,-0.015486810940274154],\"calf_l\":[-0.0018221052984396628,0.38333685158674297,0.0019527052451546005],\"foot_l\":[0.0012040060549528171,0.41225113686717424,0.0060748945009739454],\"ball_l\":[-0.004144214297411963,0.1319912777786764,0.0008641794556751847],\"thigh_r\":[-0.10406423709112762,-0.01035807409789413,-0.015486810940274154],\"calf_r\":[0.0018221052984396628,0.38333685158674297,0.0019527052451546005],\"foot_r\":[-0.0012040060549528171,0.41225113686717424,0.0060748945009739454],\"ball_r\":[0.004144214297411963,0.1319912777786764,0.0008641794556751847]},\"digits\":{\"index_01_l\":[0.11009154468774796,0.47919291257858276,-0.0033358719665557146,0.8707714676856995],\"index_02_l\":[0.10867490619421005,0.0028905405197292566,0.006149682216346264,0.9940541386604309],\"index_03_l\":[0.06272030621767044,0.0006448967615142465,0.018671324476599693,0.9978562593460083],\"middle_01_l\":[0.09819573163986206,0.41311776638031006,-0.15238040685653687,0.8924525380134583],\"middle_02_l\":[0.04684380069375038,0.0005204545450396836,0.03666960448026657,0.9982287883758545],\"middle_03_l\":[0.06767940521240234,0.0009195368620567024,-0.005850149318575859,0.9976895451545715],\"pinky_01_l\":[0.04672148823738098,0.29210352897644043,-0.3033210337162018,0.9058084487915039],\"pinky_02_l\":[0.08093646168708801,0.0011062186677008867,0.024598531424999237,0.9964150786399841],\"pinky_03_l\":[-0.008727755397558212,-0.00023478623188566417,-0.01132167037576437,0.9998977780342102],\"ring_01_l\":[0.05214594677090645,0.3895569145679474,-0.2304530292749405,0.890178382396698],\"ring_02_l\":[0.059518661350011826,0.00033943928428925574,0.04314512386918068,0.9972943067550659],\"ring_03_l\":[0.05962633714079857,0.00068375637056306,-0.02453024499118328,0.9979191422462463],\"thumb_01_l\":[0.2744263708591461,0.731201171875,0.43757393956184387,0.44560524821281433],\"thumb_02_l\":[0.06898587942123413,-0.017102878540754318,-0.18289531767368317,0.9805599451065063],\"thumb_03_l\":[0.14205099642276764,-0.006924070883542299,-0.032810747623443604,0.9892911911010742],\"index_01_r\":[0.11009154468774796,-0.47919291257858276,0.0033358719665557146,0.8707714676856995],\"index_02_r\":[0.10867490619421005,-0.0028905405197292566,-0.006149682216346264,0.9940541386604309],\"index_03_r\":[0.06272030621767044,-0.0006448967615142465,-0.018671324476599693,0.9978562593460083],\"middle_01_r\":[0.09819573163986206,-0.41311776638031006,0.15238040685653687,0.8924525380134583],\"middle_02_r\":[0.04684380069375038,-0.0005204545450396836,-0.03666960448026657,0.9982287883758545],\"middle_03_r\":[0.06767940521240234,-0.0009195368620567024,0.005850149318575859,0.9976895451545715],\"pinky_01_r\":[0.04672148823738098,-0.29210352897644043,0.3033210337162018,0.9058084487915039],\"pinky_02_r\":[0.08093646168708801,-0.0011062186677008867,-0.024598531424999237,0.9964150786399841],\"pinky_03_r\":[-0.008727755397558212,0.00023478623188566417,0.01132167037576437,0.9998977780342102],\"ring_01_r\":[0.05214594677090645,-0.3895569145679474,0.2304530292749405,0.890178382396698],\"ring_02_r\":[0.059518661350011826,-0.00033943928428925574,-0.04314512386918068,0.9972943067550659],\"ring_03_r\":[0.05962633714079857,-0.00068375637056306,0.02453024499118328,0.9979191422462463],\"thumb_01_r\":[0.2744263708591461,-0.731201171875,-0.43757393956184387,0.44560524821281433],\"thumb_02_r\":[0.06898587942123413,0.017102878540754318,0.18289531767368317,0.9805599451065063],\"thumb_03_r\":[0.14205099642276764,0.006924070883542299,0.032810747623443604,0.9892911911010742],\"ball_l\":[-0.26220664381980896,0.0072789317928254604,0.029169803485274315,0.964543342590332],\"ball_r\":[-0.26220664381980896,-0.0072789317928254604,-0.029169803485274315,0.964543342590332]},\"spine\":{\"spine_01\":[0.3344581127166748,0,0,0.9424106478691101],\"spine_02\":[-0.10261907428503036,0,0,0.9947206974029541],\"spine_03\":[0.008510730229318142,0,0,0.9999638199806213],\"spine_04\":[0,0,0,1],\"neck_01\":[0.16243939101696014,0,0,0.9867185354232788],\"head\":[-0.20577044785022736,0,0,0.978600263595581]},\"morphs\":{},\"skeletalAmount\":{\"__custom\":1},\"footPins\":{},\"shoes\":false,\"sliders\":{\"gender\":0.5,\"age\":0.5,\"muscle\":0.5,\"weight\":0.5,\"height\":0.5,\"proportions\":0.5,\"african\":0.3333333333333333,\"asian\":0.3333333333333333,\"caucasian\":0.3333333333333333,\"cupsize\":0.5,\"firmness\":0.5,\"breast-trans\":0,\"breast-dist\":0,\"breast-point\":0,\"breast-volume-vert\":0,\"nipple-size\":0,\"nipple-point\":0,\"fem-apple\":0,\"fem-diamond\":0,\"fem-full-hourglass\":0,\"fem-neat-hourglass\":0,\"fem-invert-triangle\":0,\"fem-lean-column\":0,\"fem-rectangle\":0,\"fem-triangle\":0,\"fem-adrenal\":0,\"fem-liver\":0,\"fem-ovary\":0,\"fem-thyroid\":0,\"man-apple\":0,\"man-rectangle\":0,\"man-lean-column\":0,\"man-triangle\":0,\"man-invert-triangle\":0,\"man-trapezoid\":0,\"man-adrenal\":0,\"man-liver\":0,\"man-gonadal\":0,\"man-thyroid\":0,\"torso-scale-depth\":0,\"torso-scale-horiz\":0,\"torso-scale-vert\":0,\"torso-trans-horiz\":0,\"torso-trans-vert\":0,\"torso-trans-depth\":0,\"torso-vshape\":0,\"torso-dorsi\":0,\"torso-pectoral\":0,\"fingers-distance\":0,\"fingers-diameter\":0,\"fingers-length\":0,\"hand-scale\":0,\"hand-position\":0,\"hips\":0,\"glutes\":0,\"stomach\":0,\"neck-circ\":0,\"neck-height\":0,\"head-age\":0,\"head-fat\":0,\"head-angle\":0,\"head-oval\":0,\"head-round\":0,\"head-rectangular\":0,\"head-square\":0,\"head-triangular\":0,\"head-invtriangular\":0,\"head-diamond\":0,\"head-parietal\":0}}],\"props\":[],\"attachments\":[],\"heads\":[{\"box\":[0.1916,0.2178,0.3413,0.3343],\"facing\":0.979},{\"box\":[0.6677,0.2175,0.817,0.3336],\"facing\":0.976}],\"regionEntities\":[{\"kind\":\"figure\",\"name\":\"mannequin1\"},{\"kind\":\"figure\",\"name\":\"mannequin2\"}]}", + 832, + 1216, + "" + ], + "properties": { + "Node name for S&R": "PromptChain_PoseStudio", + "pcrOutputMode": "depth", + "pcrTrueDepth": true + } + }, + { + "type": "PreviewImage", + "offset": [ + -945, + 0 + ], + "size": [ + 250, + 360 + ], + "widgets_values": [], + "properties": { + "Node name for S&R": "PreviewImage" + } + } + ], + "connections": [ + { + "from_node_idx": 4, + "from_slot": 0, + "to_node_idx": 0, + "to_slot": 0 + }, + { + "from_node_idx": 8, + "from_slot": 2, + "to_node_idx": 0, + "to_slot": 2 + }, + { + "from_node_idx": 8, + "from_slot": 1, + "to_node_idx": 0, + "to_slot": 3 + }, + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 1 + }, + { + "from_node_idx": 0, + "from_slot": 1, + "to_node_idx": 1, + "to_slot": 2 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 0 + }, + { + "from_node_idx": 6, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 3 + }, + { + "from_node_idx": 1, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 0 + }, + { + "from_node_idx": 7, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 1 + }, + { + "from_node_idx": 2, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 0 + }, + { + "from_node_idx": 8, + "from_slot": 0, + "to_node_idx": 9, + "to_slot": 0 + } + ], + "anchorConnections": [ + { + "from_slot": 3, + "to_node_idx": 0, + "to_slot": 1 + } + ], + "id": "krea2-turbo-t2i-3d-regional", + "category": "Generation", + "order": 2 +} \ No newline at end of file diff --git a/data/templates/krea2-turbo-t2i-highdetail.json b/data/templates/krea2-turbo-t2i-highdetail.json new file mode 100644 index 0000000..d59c50f --- /dev/null +++ b/data/templates/krea2-turbo-t2i-highdetail.json @@ -0,0 +1,246 @@ +{ + "name": "Text-to-Image (High Detail)", + "category": "Generation", + "order": 3, + "scope": { + "type": "family", + "architecture": "krea2", + "family": "krea2_turbo" + }, + "created_at": 1782362000, + "id": "b7c3e8a1-4f2d-4a6b-9c0e-2a1f6d3b9f09", + "nodes": [ + { + "type": "CLIPTextEncode", + "offset": [ + 820.0, + 20.0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "KSampler", + "offset": [ + 1440.0, + 120.0 + ], + "size": [ + 330, + 474 + ], + "widgets_values": [ + 826502184750291, + "randomize", + 8, + 1, + "euler", + "simple", + 1 + ], + "properties": { + "Node name for S&R": "KSampler" + } + }, + { + "type": "VAEUtils_VAEDecodeTiled", + "offset": [ + 1800.0, + 20.0 + ], + "size": [ + 300, + 220 + ], + "widgets_values": [ + -1, + false, + 512, + 64, + 4096, + 64 + ], + "properties": { + "Node name for S&R": "VAEUtils_VAEDecodeTiled" + } + }, + { + "type": "SaveImage", + "offset": [ + 2140.0, + -10.0 + ], + "size": [ + 276.984375, + 322.984375 + ], + "widgets_values": [ + "Krea2/highdetail/%date:yyyy-MM-dd_hhmmss%" + ], + "properties": {} + }, + { + "type": "CLIPLoader", + "offset": [ + 820.0, + 240.0 + ], + "size": [ + 346.78125, + 117.328125 + ], + "widgets_values": [ + "qwen3vl_4b_fp8_scaled.safetensors", + "krea2", + "default" + ], + "properties": { + "Node name for S&R": "CLIPLoader" + } + }, + { + "type": "UNETLoader", + "offset": [ + 1100.0, + 280.0 + ], + "size": [ + 371.65625, + 82.65625 + ], + "widgets_values": [ + "krea2_turbo_bf16.safetensors", + "default" + ], + "properties": { + "Node name for S&R": "UNETLoader" + } + }, + { + "type": "EmptySD3LatentImage", + "offset": [ + 1100.0, + 420.0 + ], + "size": [ + 324, + 144 + ], + "widgets_values": [ + 1024, + 1536, + 1 + ], + "properties": { + "Node name for S&R": "EmptySD3LatentImage" + } + }, + { + "type": "VAEUtils_CustomVAELoader", + "offset": [ + 1440.0, + 620.0 + ], + "size": [ + 366.40625, + 82.65625 + ], + "widgets_values": [ + "Wan2.1_VAE_upscale2x_imageonly_real_v1.safetensors", + true + ], + "properties": { + "Node name for S&R": "VAEUtils_CustomVAELoader" + } + }, + { + "type": "ConditioningKrea2Rebalance", + "offset": [ + 1100.0, + 20.0 + ], + "size": [ + 300, + 106 + ], + "widgets_values": [ + 2.0, + "1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.6,2.5,1.05,2.0,1.0" + ], + "properties": { + "Node name for S&R": "ConditioningKrea2Rebalance" + } + } + ], + "connections": [ + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 8, + "to_slot": 0 + }, + { + "from_node_idx": 8, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 1 + }, + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 2 + }, + { + "from_node_idx": 1, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 0 + }, + { + "from_node_idx": 7, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 1 + }, + { + "from_node_idx": 2, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 0 + }, + { + "from_node_idx": 4, + "from_slot": 0, + "to_node_idx": 0, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 0 + }, + { + "from_node_idx": 6, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 3 + } + ], + "anchorConnections": [ + { + "from_slot": 1, + "to_node_idx": 0, + "to_slot": 1 + } + ] +} \ No newline at end of file diff --git a/data/templates/krea2-turbo-t2i.json b/data/templates/krea2-turbo-t2i.json new file mode 100644 index 0000000..8d7258f --- /dev/null +++ b/data/templates/krea2-turbo-t2i.json @@ -0,0 +1,214 @@ +{ + "name": "Text-to-Image", + "category": "Generation", + "order": 0, + "scope": { + "type": "family", + "architecture": "krea2", + "family": "krea2_turbo" + }, + "created_at": 1782327800, + "id": "b7c3e8a1-4f2d-4a6b-9c0e-2a1f6d3b9e07", + "nodes": [ + { + "type": "CLIPTextEncode", + "offset": [ + 899.9999977111815, + 0 + ], + "size": [ + 225, + 116 + ], + "widgets_values": [ + "" + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + } + }, + { + "type": "KSampler", + "offset": [ + 1699.9999685974124, + 160.0 + ], + "size": [ + 330, + 474 + ], + "widgets_values": [ + 826502184750291, + "randomize", + 8, + 1, + "euler", + "simple", + 1 + ], + "properties": { + "Node name for S&R": "KSampler" + } + }, + { + "type": "VAEDecode", + "offset": [ + 1750.0000429382326, + 20.0 + ], + "size": [ + 225, + 72 + ], + "widgets_values": [], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "type": "SaveImage", + "offset": [ + 2090.0000285339356, + -10.0 + ], + "size": [ + 276.984375, + 322.984375 + ], + "widgets_values": [ + "Krea2/%date:yyyy-MM-dd_hhmmss%" + ], + "properties": {} + }, + { + "type": "CLIPLoader", + "offset": [ + 860.0000519714356, + 200.0 + ], + "size": [ + 346.78125, + 117.328125 + ], + "widgets_values": [ + "qwen3vl_4b_fp8_scaled.safetensors", + "krea2", + "default" + ], + "properties": { + "Node name for S&R": "CLIPLoader" + } + }, + { + "type": "UNETLoader", + "offset": [ + 1239.9999069433584, + 0 + ], + "size": [ + 371.65625, + 82.65625 + ], + "widgets_values": [ + "krea2_turbo_fp8_scaled.safetensors", + "default" + ], + "properties": { + "Node name for S&R": "UNETLoader" + } + }, + { + "type": "EmptySD3LatentImage", + "offset": [ + 1249.9999315600576, + 200.0 + ], + "size": [ + 324, + 144 + ], + "widgets_values": [ + 1024, + 1536, + 1 + ], + "properties": { + "Node name for S&R": "EmptySD3LatentImage" + } + }, + { + "type": "VAELoader", + "offset": [ + 1239.993419293212, + 390.0 + ], + "size": [ + 366.40625, + 82.65625 + ], + "widgets_values": [ + "qwen_image_vae.safetensors" + ], + "properties": { + "Node name for S&R": "VAELoader" + } + } + ], + "connections": [ + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 1 + }, + { + "from_node_idx": 0, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 2 + }, + { + "from_node_idx": 1, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 0 + }, + { + "from_node_idx": 2, + "from_slot": 0, + "to_node_idx": 3, + "to_slot": 0 + }, + { + "from_node_idx": 4, + "from_slot": 0, + "to_node_idx": 0, + "to_slot": 0 + }, + { + "from_node_idx": 5, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 0 + }, + { + "from_node_idx": 6, + "from_slot": 0, + "to_node_idx": 1, + "to_slot": 3 + }, + { + "from_node_idx": 7, + "from_slot": 0, + "to_node_idx": 2, + "to_slot": 1 + } + ], + "anchorConnections": [ + { + "from_slot": 1, + "to_node_idx": 0, + "to_slot": 1 + } + ] +} \ No newline at end of file diff --git a/js/lib/inpaint-background.js b/js/lib/inpaint-background.js index 143942f..38881b1 100644 --- a/js/lib/inpaint-background.js +++ b/js/lib/inpaint-background.js @@ -18,6 +18,7 @@ import { api } from "../../../scripts/api.js"; import { externallyTrackedPrompts, refreshEntryWorkflows, armExternalQueue, disarmExternalQueue } from "./history.js"; import { sourceSavePrefixInfo, fetchSavedUsduSettings, fetchEngineModels, engineAssetSupport, QWEN_ENHANCE_INSTRUCTION, FLUX1_TILE_GUIDANCE } from "./upscale-from-image.js"; import { resolvePre, hasPre } from "./model-settings-bridge.js"; +import { offscreenIdBase } from "./offscreen-graph.js"; function toast(severity, detail) { app.extensionManager?.toast?.add({ severity, summary: "Inpaint", detail, life: 7000 }); @@ -182,6 +183,7 @@ function inpaintArch(entries) { if (has("CheckpointLoaderSimple", "CheckpointLoader")) return "sdxl"; const clipType = String(entries.find((n) => n?.class_type === "CLIPLoader")?.inputs?.type || "").toLowerCase(); if (clipType === "flux2") return "flux2"; + if (clipType === "krea2") return "krea2"; // Krea 2 DiT: UNETLoader+CLIPLoader(type krea2) — must not fall through to flux1 (would mis-wire PuLID) if (has("DualCLIPLoader") || clipType === "flux" || has("UNETLoader", "UnetLoaderGGUF")) return "flux1"; return "other"; } @@ -313,7 +315,11 @@ export async function prepareInpaint(entry) { // (dual clip + flux VAE) are part of that check. engineModels = (await fetchEngineModels()).filter((m) => m.architecture === "sdxl" || (m.architecture === "qwen_edit" && support.qwen) - || (m.architecture === "flux" && support.flux1)); + || (m.architecture === "flux" && support.flux1) + // Krea 2 DiT: UNETLoader + CLIPLoader(type krea2) + qwen_image VAE, all + // self-contained in buildKrea2InpaintPrompt — admit unless the support + // probe explicitly reports the krea2 assets absent. + || (m.architecture === "krea2" && support.krea2 !== false)); } catch { /* engine list is optional — source surgery still works */ } if (!sourceUsable && !engineModels.length) { // Apply would die in buildInpaintPrompt — refuse with the reason up front. @@ -496,19 +502,34 @@ export function buildInpaintPrompt(apiPrompt, data, options, maskName) { // VAE: whatever decodes today, else the anchor's own vae input (MaskedDetail // and USDU carry one; a pruned inpaint graph has no VAEDecode), else the // checkpoint's. - const decId = idByClass("VAEDecode"); - const vaeRef = (decId && isLink(graph[decId].inputs.vae) && graph[decId].inputs.vae) + const decId = idByClass("VAEDecode", "VAEUtils_VAEDecodeTiled", "VAEDecodeTiled"); + let vaeRef = (decId && isLink(graph[decId].inputs.vae) && graph[decId].inputs.vae) || (isLink(src.inputs.vae) && src.inputs.vae) || (ckptId && [ckptId, 2]); - if (!clipRef || !vaeRef) throw new Error("couldn't trace the clip/vae lines"); + if (!clipRef) throw new Error("couldn't trace the clip/vae lines"); - let nextId = 1 + Object.keys(graph).reduce((m, id) => Math.max(m, Number.parseInt(id, 10) || 0), 0); + // Grafted nodes go far above both the source graph's ids and the live canvas's, + // so the background run's execution images never paint onto a live node. + let nextId = offscreenIdBase(); const add = (class_type, inputs) => { const id = String(nextId++); graph[id] = { class_type, inputs }; return id; }; + // The traced VAE may be a decode-only/2x custom loader (the spacepxl VAEUtils + // decoder on High-Detail krea2 renders) that CAN'T encode — inpaint needs both + // encode and decode. If the trace failed or landed on that custom loader, + // graft a fresh stock VAELoader on the shared krea2/qwen latent (prefer + // qwen_image_vae, else the clean wan_2.1_vae). + const tracedVae = isLink(vaeRef) ? graph[String(vaeRef[0])] : null; + if (!vaeRef || (tracedVae && tracedVae.class_type === "VAEUtils_CustomVAELoader")) { + const vaeName = probeComboValue("VAELoader", "vae_name", (o) => /qwen.*image.*vae/i.test(o)) + || probeComboValue("VAELoader", "vae_name", (o) => /wan.*2[._]?1.*vae/i.test(o) && !/upscale|2x/i.test(o)); + if (!vaeName) throw new Error("couldn't trace the clip/vae lines"); + vaeRef = [add("VAELoader", { vae_name: vaeName }), 0]; + } + const loadId = add("LoadImage", { image: data.input_ref }); const maskId = add("LoadImageMask", { image: maskName, channel: "red" }); const pcId = add("PromptChain_PromptChain", { @@ -583,8 +604,8 @@ export function buildInpaintPrompt(apiPrompt, data, options, maskName) { image: [loadId, 0], mask: [maskId, 0], positive: posRef, negative: negRef, seed: Math.floor(Math.random() * 0xffffffffff), - steps: lit(src.inputs.steps) ?? 20, - cfg: lit(src.inputs.cfg) ?? 7, + steps: numOr(options.steps, lit(src.inputs.steps) ?? 20), + cfg: numOr(options.cfg, lit(src.inputs.cfg) ?? 7), sampler_name: options.sampler || lit(src.inputs.sampler_name) || "euler", scheduler: options.scheduler || lit(src.inputs.scheduler) || "normal", denoise: typeof options.denoise === "number" ? options.denoise : 0.5, @@ -626,7 +647,7 @@ export function buildInpaintEnginePrompt(data, options, maskName) { const filename = options.engineModel?.filename; if (!filename) throw new Error("no engine model picked"); const graph = {}; - let nextId = 1; + let nextId = offscreenIdBase(); // render-node ids must stay off the live canvas — else execution images paint onto a live node sharing the id (see offscreen-graph.js) const add = (class_type, inputs) => { const id = String(nextId++); graph[id] = { class_type, inputs }; @@ -656,8 +677,8 @@ export function buildInpaintEnginePrompt(data, options, maskName) { image: [loadId, 0], mask: [maskId, 0], positive: [posId, 0], negative: [negId, 0], seed: Math.floor(Math.random() * 0xffffffffff), - steps: numOr(gen.steps, 25), - cfg: numOr(gen.cfg, 5), + steps: numOr(options.steps, numOr(gen.steps, 25)), + cfg: numOr(options.cfg, numOr(gen.cfg, 5)), sampler_name: options.sampler || gen.sampler || "dpmpp_2m", scheduler: options.scheduler || gen.scheduler || "karras", denoise: typeof options.denoise === "number" ? options.denoise : 0.5, @@ -690,7 +711,7 @@ export function buildFlux1InpaintPrompt(data, options, maskName) { if (!clipL || !t5 || !vaeFile) throw new Error("the FLUX text encoders / VAE aren't installed"); const graph = {}; - let nextId = 1; + let nextId = offscreenIdBase(); // render-node ids must stay off the live canvas — else execution images paint onto a live node sharing the id (see offscreen-graph.js) const add = (class_type, inputs) => { const id = String(nextId++); graph[id] = { class_type, inputs }; @@ -716,7 +737,7 @@ export function buildFlux1InpaintPrompt(data, options, maskName) { image: [loadId, 0], mask: [maskId, 0], positive: [guidId, 0], negative: [negId, 0], seed: Math.floor(Math.random() * 0xffffffffff), - steps: numOr(gen.steps, 25), + steps: numOr(options.steps, numOr(gen.steps, 25)), // cfg pinned, never inherited — a gen cfg would double sampling and burn. cfg: 1.0, sampler_name: options.sampler || gen.sampler || "euler", @@ -730,6 +751,82 @@ export function buildFlux1InpaintPrompt(data, options, maskName) { return { graph, detailId }; } +// KREA 2 engine inpaint: a DiT that wires like FLUX.1 through the same +// MaskedDetail crop path, minus FLUX's guidance regime. Shift is baked into the +// model class (no ModelSampling) and it's cfg-native (no FluxGuidance), so the +// UNET feeds the sampler straight and the empty negative is just the required +// input — cfg is pinned 1.0, the same inert-negative posture as flux1/zimage. +// Self-contained loaders (UNET + single CLIPLoader(type krea2) + qwen_image VAE) +// mean an imported/arbitrary image needs no embedded render data. guide_size +// 1024 / max_size 1536: krea2's native working res, matching the flux1 crop. +export function buildKrea2InpaintPrompt(data, options, maskName) { + const filename = options.engineModel?.filename; + if (!filename) throw new Error("no engine model picked"); + // Resolve the actual installed encoder/VAE (mirrors the flux1 builder + the krea2 + // tile stage) so a differently-named file still binds and a missing one fails early. + const probe = (type, widget) => { + try { + const node = window.LiteGraph?.createNode?.(type); + return node?.widgets?.find((w) => w.name === widget)?.options?.values || []; + } catch { return []; } + }; + const clipOpts = probe("CLIPLoader", "clip_name"); + const clipFile = clipOpts.find((o) => /qwen3vl.*4b/i.test(o)) || clipOpts.find((o) => /qwen3.?vl.*4b/i.test(o)); + const vaeFile = probe("VAELoader", "vae_name").find((o) => /qwen.*image.*vae/i.test(o)); + if (!clipFile || !vaeFile) throw new Error("the Krea 2 text encoder / VAE aren't installed"); + + const graph = {}; + let nextId = offscreenIdBase(); // render-node ids must stay off the live canvas — else execution images paint onto a live node sharing the id (see offscreen-graph.js) + const add = (class_type, inputs) => { + const id = String(nextId++); + graph[id] = { class_type, inputs }; + return id; + }; + const unetId = add("UNETLoader", { unet_name: filename, weight_dtype: "default" }); + const clipId = add("CLIPLoader", { clip_name: clipFile, type: "krea2", device: "default" }); + const vaeId = add("VAELoader", { vae_name: vaeFile }); + const loadId = add("LoadImage", { image: data.input_ref }); + const maskId = add("LoadImageMask", { image: maskName, channel: "red" }); + const pcId = add("PromptChain_PromptChain", { + prompt: options.prompt || "", mode: "combine", switch_index: 1, + locked: false, disabled: false, cached_output: "", cached_neg_output: "", + iterate_index: 0, iterate_cycle: 1, collapsed: false, + wildcard_modes: "", cached_regions: "", + }); + const posId = add("CLIPTextEncode", { text: [pcId, 1], clip: [clipId, 0] }); + // Dummy negative: cfg is pinned 1.0 so it never influences sampling, but + // MaskedDetail requires the input — the UNET feeds the sampler straight + // (no ModelSampling, no FluxGuidance). + const negId = add("CLIPTextEncode", { text: [pcId, 2], clip: [clipId, 0] }); + // RAW is the undistilled base: it needs full steps + a real cfg + the resolution + // shift, NOT Turbo's 8-step / cfg-1 / no-shift recipe (which under-resolves it and + // over-shifts → melted faces). The crop samples at guide_size 1024, so a static + // 1024 ModelSamplingFlux is correct here. Turbo keeps its pinned fast recipe. + const raw = /krea2_raw/i.test(filename); + let modelRef = [unetId, 0]; + if (raw) { + const msfId = add("ModelSamplingFlux", { model: [unetId, 0], max_shift: 0.90625, base_shift: 0.5, width: 1024, height: 1024 }); + modelRef = [msfId, 0]; + } + const detailId = add("PromptChain_MaskedDetail", { + model: modelRef, vae: [vaeId, 0], + image: [loadId, 0], mask: [maskId, 0], + positive: [posId, 0], negative: [negId, 0], + seed: Math.floor(Math.random() * 0xffffffffff), + // Defaults are RAW/Turbo-aware but overridable from the modal (steps/cfg/sampler). + steps: numOr(options.steps, raw ? 30 : 8), + cfg: numOr(options.cfg, raw ? 3.5 : 1.0), + sampler_name: options.sampler || (raw ? "er_sde" : "euler"), + scheduler: options.scheduler || (raw ? "beta" : "simple"), + denoise: typeof options.denoise === "number" ? options.denoise : 0.5, + guide_size: 1024, max_size: 1536, crop_factor: 2.0, + feather: numOr(options.feather, 24), grow: numOr(options.grow, 0), + }); + add("PreviewImage", { images: [detailId, 0] }); + console.log("[PromptChain][inpaint] built krea2 engine prompt:", JSON.stringify(graph, null, 2)); + return { graph, detailId }; +} + const round8 = (v) => Math.max(8, Math.round(v / 8) * 8); // Crop window around the painted mask: bbox grown 2× (MaskedDetail's @@ -785,7 +882,7 @@ export function buildQwenEditInpaintPrompt(data, options, maskName) { const scaled = qwenWorkingSize(crop.w, crop.h); const graph = {}; - let nextId = 1; + let nextId = offscreenIdBase(); // render-node ids must stay off the live canvas — else execution images paint onto a live node sharing the id (see offscreen-graph.js) const add = (class_type, inputs) => { const id = String(nextId++); graph[id] = { class_type, inputs }; @@ -834,8 +931,8 @@ export function buildQwenEditInpaintPrompt(data, options, maskName) { const samplerId = add("KSampler", { model: [normId, 0], positive: [refPosId, 0], negative: [refNegId, 0], latent_image: [noisedId, 0], seed: Math.floor(Math.random() * 0xffffffffff), - steps: numOr(gen.steps, 40), - cfg: numOr(gen.cfg, 4), + steps: numOr(options.steps, numOr(gen.steps, 40)), + cfg: numOr(options.cfg, numOr(gen.cfg, 4)), sampler_name: options.sampler || gen.sampler || "euler", scheduler: options.scheduler || gen.scheduler || "simple", // 1.0 is structural: partial denoise blends the shifted re-render; full @@ -912,9 +1009,11 @@ export function runInpaint(prepared, options = {}) { ? buildQwenEditInpaintPrompt(data, options, maskName) : options.engine === "flux1-unet" && options.engineModel ? buildFlux1InpaintPrompt(data, options, maskName) - : options.engine === "sdxl-ckpt" && options.engineModel - ? buildInpaintEnginePrompt(data, options, maskName) - : buildInpaintPrompt(data.prompt, data, options, maskName); + : options.engine === "krea2-unet" && options.engineModel + ? buildKrea2InpaintPrompt(data, options, maskName) + : options.engine === "sdxl-ckpt" && options.engineModel + ? buildInpaintEnginePrompt(data, options, maskName) + : buildInpaintPrompt(data.prompt, data, options, maskName); } catch (e) { console.error("[PromptChain][inpaint] build failed (rev abc3dbbd+):", e); tracker.emit({ phase: "error", message: `build failed: ${e.message}` }); diff --git a/js/lib/krea2-shift-sync.js b/js/lib/krea2-shift-sync.js new file mode 100644 index 0000000..dcea67d --- /dev/null +++ b/js/lib/krea2-shift-sync.js @@ -0,0 +1,103 @@ +// Krea 2 RAW uses a resolution-dynamic flow-shift: the ModelSamplingFlux node's +// width/height decide mu, and they MUST match the actual render resolution or the +// shift is mis-applied — a 1024² shift on an 832×1024 render over-shifts mu ~10% +// and compresses the detail tail (the same failure mode the node exists to fix, +// milder). The model Settings resolution row mirrors this live for t2i, but that +// misses i2i (size comes from the loaded image — there is no resolution row) and +// any graph whose resolution was set on-canvas or loaded from disk. So re-derive +// every krea2_raw ModelSamplingFlux's width/height from its true render size just +// before each queue — the one chokepoint that catches all cases. Event-driven on +// the queue (not a timer). No-op for any graph without a krea2_raw shift node. + +import { app } from "../../../scripts/app.js"; +import { getLink } from "./slot-utils.js"; + +const SHIFT_TYPE = "ModelSamplingFlux"; +const LATENT_DIM_TYPES = new Set(["EmptySD3LatentImage", "EmptyLatentImage", "EmptyFlux2LatentImage"]); +const IMAGE_TYPES = new Set(["LoadImage"]); + +// Nearest ancestor of `startNode` (walking input links) whose class is in `types`. +function findUpstreamByType(startNode, types) { + const graph = startNode?.graph || app.graph; + if (!graph || !startNode) return null; + const visited = new Set(); + const queue = [startNode]; + while (queue.length) { + const n = queue.shift(); + if (!n || visited.has(n.id)) continue; + visited.add(n.id); + for (const input of n.inputs || []) { + if (input.link == null) continue; + const link = getLink(graph, input.link); + if (!link) continue; + const src = graph.getNodeById(link.origin_id); + if (!src) continue; + if (types.has(src.comfyClass)) return src; + queue.push(src); + } + } + return null; +} + +function widgetValue(node, name) { + return node?.widgets?.find((w) => w.name === name)?.value; +} + +function setWidget(node, name, value) { + const w = node?.widgets?.find((x) => x.name === name); + if (!w || w.value === value) return; + w.value = value; + w.callback?.(value); +} + +// The render resolution feeding a sampler: the latent node's width/height widgets +// (t2i), or — when the latent is encoded from an input image and has no such +// widgets (i2i) — the loaded image's natural pixel dimensions. +function samplerResolution(samplerNode) { + const latent = findUpstreamByType(samplerNode, LATENT_DIM_TYPES); + if (latent) { + const w = widgetValue(latent, "width"); + const h = widgetValue(latent, "height"); + if (typeof w === "number" && typeof h === "number") return { width: w, height: h }; + } + const img = findUpstreamByType(samplerNode, IMAGE_TYPES)?.imgs?.[0]; + if (img?.naturalWidth) return { width: img.naturalWidth, height: img.naturalHeight }; + return null; +} + +// Scope the sync to Krea 2 RAW: only its UNET wants a resolution-tracking shift. +// Turbo bakes a fixed mu (and carries no shift node); Flux's shift is its own +// concern. Gating on the upstream UNET filename keeps every other arch untouched. +function isKrea2RawShift(shiftNode) { + const name = widgetValue(findUpstreamByType(shiftNode, new Set(["UNETLoader"])), "unet_name"); + return typeof name === "string" && /krea2_raw/i.test(name); +} + +// Re-derive every krea2_raw ModelSamplingFlux's width/height from its render size. +export function syncShiftNodes() { + const graph = app?.graph; + if (!graph) return; + for (const sampler of graph._nodes || []) { + const cls = sampler.comfyClass || sampler.type || ""; + if (!cls.includes("KSampler") || cls === "KSamplerSelect") continue; + const shift = findUpstreamByType(sampler, new Set([SHIFT_TYPE])); + if (!shift || !isKrea2RawShift(shift)) continue; + const res = samplerResolution(sampler); + if (!res) continue; + const before = `${widgetValue(shift, "width")}×${widgetValue(shift, "height")}`; + setWidget(shift, "width", res.width); + setWidget(shift, "height", res.height); + const after = `${res.width}×${res.height}`; + if (before !== after) console.log(`[PromptChain] krea2 RAW shift synced ${before} → ${after}`); + } +} + +// Wrap queuePrompt so the shift is corrected for the live graph before it serializes. +export function setupShiftSync() { + const orig = app.queuePrompt?.bind(app); + if (!orig) return; + app.queuePrompt = async function (...args) { + try { syncShiftNodes(); } catch (e) { console.warn("[PromptChain] krea2 shift-sync skipped:", e); } + return orig(...args); + }; +} diff --git a/js/lib/model-settings-bridge.js b/js/lib/model-settings-bridge.js index c9a6368..67478f9 100644 --- a/js/lib/model-settings-bridge.js +++ b/js/lib/model-settings-bridge.js @@ -508,6 +508,16 @@ function findUpstreamByType(startNode, targetTypes) { return null; } +// A ModelSamplingFlux belongs to a Krea 2 RAW graph when its model line traces up +// to a krea2_raw UNET. Keeps the resolution→shift mirror scoped to RAW (Turbo bakes +// a fixed mu; Flux owns its own shift), so only RAW's shift node is slaved. +function isKrea2RawShift(shiftNode) { + const unet = findUpstreamByType(shiftNode, new Set(["UNETLoader"])); + if (!unet) return false; + const name = readWidgetValue(unet, "unet_name"); + return typeof name === "string" && /krea2_raw/i.test(name); +} + // The face-detail leg of a model-patch injectable (PuLID / Style Reference) is a // second Apply node feeding the RegionalDetailer's couple-free model line. It's // an implementation detail, not its own tunable — the settings panel shows one @@ -668,6 +678,17 @@ export function detectSupportedNodes(pcNode) { detected.push({ node: latentNode, config: LATENT_NODES[cls], type: cls }); } + // Krea 2 RAW's ModelSamplingFlux sets the resolution-dynamic flow-shift (mu), + // whose width/height MUST track the render size. Detected only so the resolution + // row can slave its width/height to the latent — mirrorOnly: no visible section + // and never written into editors, so it is never baked back into the saved + // config (the shift must stay dynamic). Gated to RAW (see isKrea2RawShift). + const shiftNode = findUpstreamByType(pivotNode, new Set(["ModelSamplingFlux"])); + if (shiftNode && !seenIds.has(shiftNode.id) && isKrea2RawShift(shiftNode)) { + seenIds.add(shiftNode.id); + detected.push({ node: shiftNode, config: { label: "Shift", widgets: {}, mirrorOnly: true }, type: "ModelSamplingFlux" }); + } + const pulidTypes = new Set(["ApplyPulidAdvanced", "ApplyPulid", "ApplyPulidFlux", "ApplyPuLIDFlux2"]); const pulidNode = findUpstreamByType(pivotNode, pulidTypes); if (pulidNode && !seenIds.has(pulidNode.id)) { @@ -746,6 +767,15 @@ function applyArchSamplerDefaults(defaults, architecture, family) { // high-σ regime where tiles come out clean (heavier repaint is the trade). if ("denoise" in defaults) defaults.denoise = 0.7; } + } else if (arch === "krea2") { + // Krea 2 Turbo: distilled 8-step, guidance baked (cfg 1), euler/simple. Qwen-family + // is quant/prompt-sensitive, so a tile re-render wants a higher denoise floor than + // Z-Image-Base to avoid underbaking the few-step tail (GPU-tunable). + defaults.cfg = 1.0; + defaults.sampler_name = "euler"; + defaults.scheduler = "simple"; + if ("steps" in defaults) defaults.steps = 8; + if ("denoise" in defaults) defaults.denoise = 0.45; } } @@ -1260,9 +1290,13 @@ export function injectUpscaler(pcNode, modelConfig, opts = {}) { // exists (regional or classic), else straight off the decode. const detailNode = downstream.find(n => n.comfyClass === "PromptChain_RegionalDetailer") || downstream.find(n => n.comfyClass === "FaceDetailer"); - const vaeDecodeNode = downstream.find(n => n.comfyClass === "VAEDecode"); + const vaeDecodeNode = downstream.find(n => n.comfyClass === "VAEDecode" + || n.comfyClass === "VAEUtils_VAEDecodeTiled" || n.comfyClass === "VAEDecodeTiled"); const imageSource = detailNode || vaeDecodeNode; - if (!imageSource) return false; + if (!imageSource) { + console.warn(`[PromptChain][graft] FAIL: no decode/detail image source. downstream=[${downstream.map((n) => n.comfyClass).join(",")}]`); + return false; + } // Sampler anchor: classic KSampler graphs, or the Flux 2 split where // SamplerCustomAdvanced samples and a guider (CFGGuider on Klein base, @@ -1357,6 +1391,24 @@ export function injectUpscaler(pcNode, modelConfig, opts = {}) { } splitModelNode = ms; splitVaeNode = findUpstreamByType(vaeDecodeNode, new Set(["VAELoader"])); + if (!splitVaeNode) { + // The source decodes through a custom/tiled VAE (e.g. the spacepxl + // VAEUtils decoder on High-Detail krea2 renders) whose 2x/12-ch output + // can't drive USDU's encode->sample->decode tiling. Graft a fresh stock + // VAELoader on the shared krea2/qwen latent — prefer the clean wan_2.1_vae + // (no grid artifact), else qwen_image_vae, else whatever is installed. + const freshVae = LG.createNode("VAELoader"); + const vaeW = freshVae?.widgets?.find(w => w.name === "vae_name"); + const vaeOpts = vaeW?.options?.values || []; + const pick = vaeOpts.find(o => /wan.*2[._]?1.*vae/i.test(o) && !/upscale|2x/i.test(o)) + || vaeOpts.find(o => /qwen.*image.*vae/i.test(o)) || vaeOpts[0]; + if (freshVae && vaeW && pick) { + vaeW.value = pick; vaeW.callback?.(pick); + freshVae.pos = [vaeDecodeNode.pos[0], vaeDecodeNode.pos[1] + 220]; + graph.add(freshVae); + splitVaeNode = freshVae; + } + } } const loaderNode = LG.createNode("UpscaleModelLoader"); @@ -1847,7 +1899,7 @@ function injectRegional(pcNode, modelConfig) { // $blocks cohere into ONE scene). Other DiTs (Flux/SD3/…) have no ported couple yet, so // they fall back to ComfyUI-native masked conditioning here. const arch = modelConfig?.architecture || ""; - const NATIVE_REGIONAL_ARCH = new Set(["flux", "flux2", "sd3", "hidream", "qwen_image", "qwen_edit"]); + const NATIVE_REGIONAL_ARCH = new Set(["flux", "flux2", "sd3", "hidream", "qwen_image", "qwen_edit", "krea2"]); if (NATIVE_REGIONAL_ARCH.has(arch)) { const rc = LG.createNode("PromptChain_RegionalConditioning"); if (!rc) return false; @@ -2873,6 +2925,17 @@ export async function showModelModal(pcNode, modelInfo, anchorEl, { tab = null } ], }); } + // Krea 2 has no published ControlNet (and ComfyUI's krea2 model class exposes + // no control hook), but native cond-mask regional works — PromptChain_RegionalConditioning + // is model-agnostic. Offer Regional only. + if (arch === "krea2") { + items.push({ + label: "ControlNet", + children: [ + { label: "Regional", value: "ControlNet:regional" }, + ], + }); + } // Z-Image ControlNet (DiffSynth model-patch path; core nodes + aux // preprocessors). The control model is family-specific: Turbo's is // step-distilled, base's isn't, and they're NOT cross-compatible — so a diff --git a/js/lib/svelte/promptchain-model-settings.js b/js/lib/svelte/promptchain-model-settings.js index 535d3a8..1ac1bd8 100644 --- a/js/lib/svelte/promptchain-model-settings.js +++ b/js/lib/svelte/promptchain-model-settings.js @@ -384,14 +384,19 @@ function SettingsTab($$anchor, $$props) { if (!get(editors)[nodeType]) get(editors)[nodeType] = {}; get(editors)[nodeType][widgetName] = val; } - function mirrorIdeogramScheduler(latentNode) { - const sched = detected().find((d) => d.type === "Ideogram4Scheduler"); - if (!sched) return; - for (const dim of ["width", "height"]) { - const v = $$props.readWidgetValue(latentNode, dim); - if (v !== void 0) { - $$props.writeWidgetValue(sched.node, dim, v); - setEditorValue("Ideogram4Scheduler", dim, v); + const RES_DEPENDENTS = [ + { type: "Ideogram4Scheduler", persist: true }, + { type: "ModelSamplingFlux", persist: false } + ]; + function mirrorResolutionDependents(latentNode) { + for (const { type: depType, persist } of RES_DEPENDENTS) { + const dep = detected().find((d) => d.type === depType); + if (!dep) continue; + for (const dim of ["width", "height"]) { + const v = $$props.readWidgetValue(latentNode, dim); + if (v === void 0) continue; + $$props.writeWidgetValue(dep.node, dim, v); + if (persist) setEditorValue(depType, dim, v); } } } @@ -484,7 +489,10 @@ function SettingsTab($$anchor, $$props) { }); } var node_2 = sibling(node_1, 2); - each(node_2, 17, detected, index, ($$anchor2, $$item) => { + each(node_2, 17, () => detected().filter((d) => { + var _a2; + return !((_a2 = d.config) == null ? void 0 : _a2.mirrorOnly); + }), index, ($$anchor2, $$item) => { let node = () => get($$item).node; let config = () => get($$item).config; let type = () => get($$item).type; @@ -634,7 +642,7 @@ function SettingsTab($$anchor, $$props) { $$props.writeWidgetValue(node(), "height", get(p).height); setEditorValue(type(), "width", get(p).width); setEditorValue(type(), "height", get(p).height); - mirrorIdeogramScheduler(node()); + mirrorResolutionDependents(node()); presetMenuOpen[type()] = false; }); delegated("click", span_2, (e) => { @@ -702,13 +710,13 @@ function SettingsTab($$anchor, $$props) { const v = parseInt(e.target.value) || 512; setEditorValue(type(), "width", v); $$props.writeWidgetValue(node(), "width", v); - mirrorIdeogramScheduler(node()); + mirrorResolutionDependents(node()); }); delegated("change", input_2, (e) => { const v = parseInt(e.target.value) || 512; setEditorValue(type(), "height", v); $$props.writeWidgetValue(node(), "height", v); - mirrorIdeogramScheduler(node()); + mirrorResolutionDependents(node()); }); append($$anchor5, div_6); }; diff --git a/js/lib/svelte/promptchain-viewer.js b/js/lib/svelte/promptchain-viewer.js index 28f0a12..fbf0562 100644 --- a/js/lib/svelte/promptchain-viewer.js +++ b/js/lib/svelte/promptchain-viewer.js @@ -570,7 +570,7 @@ function SearchableSelect($$anchor, $$props) { delegate(["click", "keydown"]); var root_2$8 = from_html(``); var root_3$8 = from_html(``); -var root_5$7 = from_html(`
Before
After
`, 1); +var root_5$7 = from_html(`
After
`, 1); var root_7$7 = from_html(``); var root_6$7 = from_html(`
`); var root_4$8 = from_html(`
`, 1); @@ -580,7 +580,7 @@ var root_12$6 = from_html(`
`) var root_11$5 = from_html(`
`, 1); var root_8$7 = from_html(`
`); var root_13$6 = from_html(``); -var root_14$6 = from_html(`

This image has no usable prompt metadata — a plain model upscale (ESRGAN) will be used.

`); +var root_14$6 = from_html(`

No re-detail engine is available for this image — a plain model upscale (ESRGAN) will be used.

`); var root_16$5 = from_html(` `); var root_17$4 = from_html(`re-renders the whole frame at ~2MP from your instruction — composition can shift slightly; the climb model below pushes to the target size`); var root_18$4 = from_html(`deterministic enlargement with the climb model below — add a Restore step for degraded sources (webcam, jpeg, blur)`); @@ -588,16 +588,16 @@ var root_15$5 = from_html(`
E var root_20$3 = from_html(``); var root_21$3 = from_html(`
Depth from
3D pose
This image
`); var root_19$4 = from_html(`
Mode
`); -var root_23$2 = from_html(``); -var root_24$3 = from_html(``); +var root_23$3 = from_html(``); +var root_24$2 = from_html(``); var root_22$2 = from_html(`
Character lock
keeps the character from drifting while tiles re-detail — the image itself is the reference
`); var root_27$3 = from_html(`
`); var root_28$2 = from_html(``); var root_29$3 = from_html(`Workflow prompt (reference) `, 1); var root_25$3 = from_html(`
`); var root_31$3 = from_html(`
This quality is bigger than the region's footprint — how should it land?
The whole document scales up and the region lands pixel-for-pixel — full quality kept. The UltraSharp-enlarged image becomes the new Background, so erasing the upscale layer reveals it.
The render is rescaled back into the region — re-detailed, but softer than 1:1.
`); -var root_32$2 = from_html(``); -var root_33$3 = from_html(`
Denoise
`); +var root_32$3 = from_html(``); +var root_33$2 = from_html(`
Denoise
`); var root_30$3 = from_html(`
Scale
`); var root_34$3 = from_html(`
`); var root_36$3 = from_html(`
`); @@ -608,7 +608,7 @@ var root_40$3 = from_html(``); var root_38$3 = from_html(`
`); var root_42$3 = from_html(` `); var root_44$3 = from_html(``); -var root_47$2 = from_html(``); +var root_47$3 = from_html(``); var root_46$3 = from_html(`
`); var root_48$2 = from_html(`
`); var root_43$3 = from_html(`
`); @@ -720,19 +720,20 @@ function UpscaleOptionsModal($$anchor, $$props) { var _a, _b; return ((_b = (_a = caps()) == null ? void 0 : _a.engineModels) == null ? void 0 : _b.find((m) => m.hash === get(engineSel))) || null; }); - let engineKind = user_derived(() => get(engineEntry) ? get(engineEntry).architecture === "qwen_edit" ? "qwen" : get(engineEntry).architecture === "zimage" ? "zimage" : get(engineEntry).architecture === "flux" ? "flux1" : "sdxl" : get(engineSel) === "source" ? "source" : "plain"); + let engineKind = user_derived(() => get(engineEntry) ? get(engineEntry).architecture === "qwen_edit" ? "qwen" : get(engineEntry).architecture === "zimage" ? "zimage" : get(engineEntry).architecture === "krea2" ? "krea2" : get(engineEntry).architecture === "flux" ? "flux1" : "sdxl" : get(engineSel) === "source" ? "source" : "plain"); let sourceGraftable = user_derived(() => { var _a; return get(engineKind) === "source" && !!((_a = caps()) == null ? void 0 : _a.graftable); }); - let tileEngine = user_derived(() => get(engineKind) === "sdxl" || get(engineKind) === "flux1" || get(engineKind) === "zimage"); + let tileEngine = user_derived(() => get(engineKind) === "sdxl" || get(engineKind) === "flux1" || get(engineKind) === "zimage" || get(engineKind) === "krea2"); let engineGroups = user_derived(() => { - var _a, _b, _c, _d; + var _a, _b, _c, _d, _e; return { sdxl: (((_a = caps()) == null ? void 0 : _a.engineModels) || []).filter((m) => m.architecture === "sdxl"), flux1: (((_b = caps()) == null ? void 0 : _b.engineModels) || []).filter((m) => m.architecture === "flux"), zimage: (((_c = caps()) == null ? void 0 : _c.engineModels) || []).filter((m) => m.architecture === "zimage"), - qwen: (((_d = caps()) == null ? void 0 : _d.engineModels) || []).filter((m) => m.architecture === "qwen_edit") + krea2: (((_d = caps()) == null ? void 0 : _d.engineModels) || []).filter((m) => m.architecture === "krea2"), + qwen: (((_e = caps()) == null ? void 0 : _e.engineModels) || []).filter((m) => m.architecture === "qwen_edit") }; }); let engineChoices = user_derived(() => { @@ -771,6 +772,12 @@ function UpscaleOptionsModal($$anchor, $$props) { options: get(engineGroups).zimage.map((m) => ({ value: m.hash, label: m.displayName })) } ] : [], + ...get(engineGroups).krea2.length ? [ + { + label: "Krea 2 — tiled re-detail", + options: get(engineGroups).krea2.map((m) => ({ value: m.hash, label: m.displayName })) + } + ] : [], ...get(engineGroups).qwen.length ? [ { label: "Qwen Edit — instruction enhance", @@ -790,11 +797,11 @@ function UpscaleOptionsModal($$anchor, $$props) { }); let recSampler = user_derived(() => { var _a; - return get(engineKind) === "sdxl" ? "dpmpp_2m" : get(engineKind) === "zimage" ? "res_multistep" : get(engineKind) === "flux1" || get(engineKind) === "qwen" ? "euler" : (_a = caps()) == null ? void 0 : _a.recommendedSampler; + return get(engineKind) === "sdxl" ? "dpmpp_2m" : get(engineKind) === "zimage" ? "res_multistep" : get(engineKind) === "krea2" ? "euler" : get(engineKind) === "flux1" || get(engineKind) === "qwen" ? "euler" : (_a = caps()) == null ? void 0 : _a.recommendedSampler; }); let recScheduler = user_derived(() => { var _a; - return get(engineKind) === "sdxl" ? "karras" : get(engineKind) === "zimage" ? "beta" : get(engineKind) === "flux1" || get(engineKind) === "qwen" ? "simple" : (_a = caps()) == null ? void 0 : _a.recommendedScheduler; + return get(engineKind) === "sdxl" ? "karras" : get(engineKind) === "zimage" ? "beta" : get(engineKind) === "flux1" || get(engineKind) === "qwen" || get(engineKind) === "krea2" ? "simple" : (_a = caps()) == null ? void 0 : _a.recommendedScheduler; }); let advDefaults = user_derived(() => { var _a, _b; @@ -806,11 +813,11 @@ function UpscaleOptionsModal($$anchor, $$props) { }); let denoiseDefault = user_derived(() => { var _a, _b, _c; - return get(tileEngine) ? ((_b = (_a = get(engineEntry)) == null ? void 0 : _a.defaults) == null ? void 0 : _b.denoise) ?? (get(engineKind) === "flux1" ? 0.2 : 0.15) : (_c = caps()) == null ? void 0 : _c.defaultDenoise; + return get(tileEngine) ? ((_b = (_a = get(engineEntry)) == null ? void 0 : _a.defaults) == null ? void 0 : _b.denoise) ?? (get(engineKind) === "flux1" ? 0.2 : get(engineKind) === "krea2" ? 0.45 : 0.15) : (_c = caps()) == null ? void 0 : _c.defaultDenoise; }); user_effect(() => { if ($$props.open && caps()) untrack(() => { - var _a, _b, _c, _d, _e; + var _a, _b, _c, _d, _e, _f; set(mode, caps().defaultMode || "prompt", true); set(depthSource, caps().defaultDepthSource || "pose", true); set(savePrefix, caps().defaultSavePrefix || "upscale/upscale", true); @@ -827,6 +834,8 @@ function UpscaleOptionsModal($$anchor, $$props) { } set(condition, onUseInEdit() && ((_b = (_a = caps().conditions) == null ? void 0 : _a.ipadapter) == null ? void 0 : _b.ok) ? "ipadapter" : "none", true); memoryPrompt = null; + const rawEngine = ((_c = caps().sourceModelInfo) == null ? void 0 : _c.family) === "krea2_raw" ? (caps().engineModels || []).find((m) => m.architecture === "krea2" && /raw/i.test(m.filename || "") && !/turbo/i.test(m.filename || "")) : null; + const rawHash = (rawEngine == null ? void 0 : rawEngine.hash) || null; set(engineSel, caps().graftable ? "source" : "plain", true); set(prompt, prefillFor(caps().graftable ? "source" : "plain"), true); set(plainEngine, caps().seedvr2Available ? "seedvr2" : "ultrasharp", true); @@ -835,7 +844,7 @@ function UpscaleOptionsModal($$anchor, $$props) { set(preserveDefocus, true); set(advanced, {}, true); set(advancedOpen, false); - const modelKey = ((_c = caps().sourceModelInfo) == null ? void 0 : _c.hash) || ""; + const modelKey = ((_d = caps().sourceModelInfo) == null ? void 0 : _d.hash) || ""; const modelMem = modelKey ? recallModalMemory("upscale-model", modelKey) : null; if ((modelMem == null ? void 0 : modelMem.plainEngine) === "ultrasharp" || (modelMem == null ? void 0 : modelMem.plainEngine) === "seedvr2" && caps().seedvr2Available) { set(plainEngine, modelMem.plainEngine, true); @@ -843,16 +852,17 @@ function UpscaleOptionsModal($$anchor, $$props) { if (typeof (modelMem == null ? void 0 : modelMem.denoise) === "number") set(denoise, modelMem.denoise, true); const mem = recallModalMemory("upscale", imageKey()); const memEngine = (mem == null ? void 0 : mem.engine) === "ultrasharp" || (mem == null ? void 0 : mem.engine) === "seedvr2" ? "plain" : mem == null ? void 0 : mem.engine; - const memEngineValid = memEngine === "source" ? !!caps().graftable : memEngine === "plain" ? true : !!memEngine && !!((_d = caps().engineModels) == null ? void 0 : _d.some((m) => m.hash === memEngine)); + const memEngineValid = memEngine === "source" ? !!caps().graftable : memEngine === "plain" ? true : !!memEngine && !!((_e = caps().engineModels) == null ? void 0 : _e.some((m) => m.hash === memEngine)); if (memEngineValid) { memoryPrompt = typeof mem.prompt === "string" && mem.prompt.trim() ? mem.prompt : null; pickEngine(memEngine); if (memEngine === "source" && typeof (modelMem == null ? void 0 : modelMem.denoise) === "number") set(denoise, modelMem.denoise, true); if (typeof mem.denoise === "number") set(denoise, mem.denoise, true); if ((mem.climbStage === "seedvr2" || mem.engine === "seedvr2") && caps().seedvr2Available) set(climbStage, "seedvr2"); - if (mem.climbModel && ((_e = caps().upscaleModelOptions) == null ? void 0 : _e.includes(mem.climbModel))) set(climbModel, mem.climbModel, true); + if (mem.climbModel && ((_f = caps().upscaleModelOptions) == null ? void 0 : _f.includes(mem.climbModel))) set(climbModel, mem.climbModel, true); if (mem.plainEngine === "ultrasharp" || mem.plainEngine === "seedvr2" && caps().seedvr2Available) set(plainEngine, mem.plainEngine, true); } + if (rawHash && (!memEngineValid || memEngine === "source")) pickEngine(rawHash); requestAnimationFrame(() => confirmBtn == null ? void 0 : confirmBtn.focus()); }); }); @@ -860,7 +870,7 @@ function UpscaleOptionsModal($$anchor, $$props) { function basePrefillFor(kind) { var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k; if (kind === "qwen") return ((_b = (_a = caps()) == null ? void 0 : _a.enginePromptDefaults) == null ? void 0 : _b.qwen) || ""; - if (kind === "sdxl" || kind === "flux1" || kind === "zimage") { + if (kind === "sdxl" || kind === "flux1" || kind === "zimage" || kind === "krea2") { const srcArch = ((_c = caps()) == null ? void 0 : _c.architecture) || ((_e = (_d = caps()) == null ? void 0 : _d.sourceModelInfo) == null ? void 0 : _e.architecture) || ""; const descriptive = srcArch === "qwen_edit" ? "" : (_f = caps()) == null ? void 0 : _f.prefillPrompt; return descriptive || ((_h = (_g = caps()) == null ? void 0 : _g.enginePromptDefaults) == null ? void 0 : _h.sdxlFallback) || ""; @@ -879,7 +889,7 @@ function UpscaleOptionsModal($$anchor, $$props) { var _a, _b, _c, _d, _e, _f, _g; set(engineSel, value, true); const entry = ((_b = (_a = caps()) == null ? void 0 : _a.engineModels) == null ? void 0 : _b.find((m) => m.hash === value)) || null; - const kind = entry ? entry.architecture === "qwen_edit" ? "qwen" : entry.architecture === "zimage" ? "zimage" : entry.architecture === "flux" ? "flux1" : "sdxl" : value === "source" ? "source" : "plain"; + const kind = entry ? entry.architecture === "qwen_edit" ? "qwen" : entry.architecture === "zimage" ? "zimage" : entry.architecture === "krea2" ? "krea2" : entry.architecture === "flux" ? "flux1" : "sdxl" : value === "source" ? "source" : "plain"; set(advanced, {}, true); const d = entry == null ? void 0 : entry.defaults; set(denoise, (d == null ? void 0 : d.denoise) ?? ((_c = caps()) == null ? void 0 : _c.defaultDenoise) ?? 0.3, true); @@ -1144,7 +1154,7 @@ function UpscaleOptionsModal($$anchor, $$props) { base.condition = get(condition); base.engine = get(engineKind) === "source" ? "source" : ( // Consolidated plain entry: Restore decides which floor builds. - get(engineKind) === "plain" ? get(climbStage) === "seedvr2" && ((_b = caps()) == null ? void 0 : _b.seedvr2Available) ? "seedvr2" : "ultrasharp" : get(engineKind) === "qwen" ? "qwen-edit" : get(engineKind) === "zimage" ? "zimage-unet" : get(engineKind) === "flux1" ? "flux1-unet" : "sdxl-ckpt" + get(engineKind) === "plain" ? get(climbStage) === "seedvr2" && ((_b = caps()) == null ? void 0 : _b.seedvr2Available) ? "seedvr2" : "ultrasharp" : get(engineKind) === "qwen" ? "qwen-edit" : get(engineKind) === "zimage" ? "zimage-unet" : get(engineKind) === "krea2" ? "krea2-unet" : get(engineKind) === "flux1" ? "flux1-unet" : "sdxl-ckpt" ); if (get(engineEntry)) base.engineModel = { hash: get(engineEntry).hash, @@ -1158,7 +1168,8 @@ function UpscaleOptionsModal($$anchor, $$props) { } else if (get(prompt).trim() && get(prompt).trim() !== basePrefillFor(get(engineKind)).trim()) { base.prompt = get(prompt); } - if (Object.keys(get(advanced)).length) base.advanced = { ...get(advanced) }; + if (get(engineEntry)) base.advanced = { ...get(advDefaults), ...get(advanced) }; + else if (Object.keys(get(advanced)).length) base.advanced = { ...get(advanced) }; storeModalMemory("upscale", imageKey(), { engine: get(engineKind) === "source" ? "source" : get(engineSel), prompt: get(prompt), @@ -1426,19 +1437,23 @@ function UpscaleOptionsModal($$anchor, $$props) { var div_7 = first_child(fragment_2); var div_8 = child(div_7); var img_2 = child(div_8); - var div_9 = sibling(div_7, 6); + var div_9 = sibling(div_7, 2); + var text_2 = child(div_9); + var div_10 = sibling(div_9, 4); template_effect(() => { + var _a, _b; set_style(div_7, `clip-path: inset(0 calc(100% - ${get(splitX) ?? ""}px) 0 0);`); set_style(div_8, `transform: translate(${get(panX) ?? ""}px, ${get(panY) ?? ""}px) scale(${get(zoom) ?? ""});`); - set_attribute(img_2, "src", previewUrl()); + set_attribute(img_2, "src", ((_a = progress()) == null ? void 0 : _a.enlargedUrl) || previewUrl()); set_attribute(img_2, "width", get(imgNatW)); set_attribute(img_2, "height", get(imgNatH)); - set_style(div_9, `left: ${get(splitX) ?? ""}px;`); + set_text(text_2, ((_b = progress()) == null ? void 0 : _b.enlargedUrl) ? "Enlarged" : "Before"); + set_style(div_10, `left: ${get(splitX) ?? ""}px;`); }); - delegated("pointerdown", div_9, onSplitDown); - delegated("pointermove", div_9, onSplitMove); - delegated("pointerup", div_9, onSplitUp); - event("pointercancel", div_9, onSplitUp); + delegated("pointerdown", div_10, onSplitDown); + delegated("pointermove", div_10, onSplitMove); + delegated("pointerup", div_10, onSplitUp); + event("pointercancel", div_10, onSplitUp); append($$anchor4, fragment_2); }; if_block(node_4, ($$render) => { @@ -1448,9 +1463,9 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_5 = sibling(node_4, 2); { var consequent_4 = ($$anchor4) => { - var div_10 = root_6$7(); - var span_1 = child(div_10); - var text_2 = child(span_1); + var div_11 = root_6$7(); + var span_1 = child(div_11); + var text_3 = child(span_1); var node_6 = sibling(span_1, 2); { var consequent_3 = ($$anchor5) => { @@ -1465,9 +1480,9 @@ function UpscaleOptionsModal($$anchor, $$props) { }); } var button_2 = sibling(node_6, 2); - template_effect(($0) => set_text(text_2, `${$0 ?? ""}%`), [() => Math.round(get(zoom) * 100)]); + template_effect(($0) => set_text(text_3, `${$0 ?? ""}%`), [() => Math.round(get(zoom) * 100)]); delegated("click", button_2, fitView); - append($$anchor4, div_10); + append($$anchor4, div_11); }; if_block(node_5, ($$render) => { if (get(imgNatW)) $$render(consequent_4); @@ -1489,44 +1504,44 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_7 = sibling(div_5, 2); { var consequent_9 = ($$anchor3) => { - var div_11 = root_8$7(); - var node_8 = child(div_11); + var div_12 = root_8$7(); + var node_8 = child(div_12); { var consequent_6 = ($$anchor4) => { - var div_12 = root_9$6(); - var text_3 = child(div_12); - template_effect(() => set_text(text_3, `Done${progress().filename ? ` — ${progress().filename}` : ""}`)); - append($$anchor4, div_12); + var div_13 = root_9$6(); + var text_4 = child(div_13); + template_effect(() => set_text(text_4, `Done${progress().filename ? ` — ${progress().filename}` : ""}`)); + append($$anchor4, div_13); }; var consequent_7 = ($$anchor4) => { - var div_13 = root_10$6(); - var text_4 = child(div_13); - template_effect(() => set_text(text_4, progress().message || "Failed")); - append($$anchor4, div_13); + var div_14 = root_10$6(); + var text_5 = child(div_14); + template_effect(() => set_text(text_5, progress().message || "Failed")); + append($$anchor4, div_14); }; var alternate = ($$anchor4) => { var fragment_3 = root_11$5(); - var div_14 = first_child(fragment_3); - var text_5 = child(div_14); - var div_15 = sibling(div_14, 2); - var div_16 = child(div_15); + var div_15 = first_child(fragment_3); + var text_6 = child(div_15); + var div_16 = sibling(div_15, 2); + var div_17 = child(div_16); let classes_2; - var node_9 = sibling(div_15, 2); + var node_9 = sibling(div_16, 2); { var consequent_8 = ($$anchor5) => { - var div_17 = root_12$6(); - var text_6 = child(div_17); - template_effect(() => set_text(text_6, get(progressLabel))); - append($$anchor5, div_17); + var div_18 = root_12$6(); + var text_7 = child(div_18); + template_effect(() => set_text(text_7, get(progressLabel))); + append($$anchor5, div_18); }; if_block(node_9, ($$render) => { if (get(progressLabel)) $$render(consequent_8); }); } template_effect(() => { - set_text(text_5, get(progressStatus)); - classes_2 = set_class(div_16, 1, "pcr-up-bar-fill svelte-xw7bpl", null, classes_2, { indeterminate: get(progressPercent) == null }); - set_style(div_16, get(progressPercent) != null ? `width: ${get(progressPercent)}%` : ""); + set_text(text_6, get(progressStatus)); + classes_2 = set_class(div_17, 1, "pcr-up-bar-fill svelte-xw7bpl", null, classes_2, { indeterminate: get(progressPercent) == null }); + set_style(div_17, get(progressPercent) != null ? `width: ${get(progressPercent)}%` : ""); }); append($$anchor4, fragment_3); }; @@ -1536,15 +1551,15 @@ function UpscaleOptionsModal($$anchor, $$props) { else $$render(alternate, -1); }); } - append($$anchor3, div_11); + append($$anchor3, div_12); }; if_block(node_7, ($$render) => { if (progress()) $$render(consequent_9); }); } - var div_18 = sibling(div_4, 2); + var div_19 = sibling(div_4, 2); let classes_3; - var node_10 = child(div_18); + var node_10 = child(div_19); { var consequent_10 = ($$anchor3) => { var button_3 = root_13$6(); @@ -1563,15 +1578,15 @@ function UpscaleOptionsModal($$anchor, $$props) { }; if_block(node_11, ($$render) => { var _a; - if (!((_a = caps()) == null ? void 0 : _a.graftable) && get(engineKind) === "plain") $$render(consequent_11); + if (!((_a = caps()) == null ? void 0 : _a.graftable) && get(engineKind) === "plain" && get(engineChoices) <= 1) $$render(consequent_11); }); } var node_12 = sibling(node_11, 2); { var consequent_15 = ($$anchor3) => { - var div_19 = root_15$5(); - var div_20 = sibling(child(div_19), 2); - var node_13 = child(div_20); + var div_20 = root_15$5(); + var div_21 = sibling(child(div_20), 2); + var node_13 = child(div_21); SearchableSelect(node_13, { id: "pcr-up-engine", get value() { @@ -1590,8 +1605,8 @@ function UpscaleOptionsModal($$anchor, $$props) { { var consequent_12 = ($$anchor4) => { var span_2 = root_16$5(); - var text_7 = child(span_2); - template_effect(() => set_text(text_7, get(engineKind) === "flux1" ? "the climb model below pushes to the target, then this FLUX.1 model re-details every tile with the prompt below — low denoise holds structure without a ControlNet" : get(engineKind) === "zimage" ? "the climb model below pushes to the target, then this Z-Image model re-details every tile with the prompt below — Base holds structure at low denoise; Turbo (distilled) needs ~0.7 or it blotches" : "the climb model below pushes to the target, then this checkpoint re-details every tile (structure locked by a tile ControlNet) with the prompt below")); + var text_8 = child(span_2); + template_effect(() => set_text(text_8, get(engineKind) === "flux1" ? "the climb model below pushes to the target, then this FLUX.1 model re-details every tile with the prompt below — low denoise holds structure without a ControlNet" : get(engineKind) === "zimage" ? "the climb model below pushes to the target, then this Z-Image model re-details every tile with the prompt below — Base holds structure at low denoise; Turbo (distilled) needs ~0.7 or it blotches" : get(engineKind) === "krea2" ? "the climb model below pushes to the target, then this Krea 2 model re-details every tile with the prompt below — distilled turbo re-detail at moderate denoise, no ControlNet" : "the climb model below pushes to the target, then this checkpoint re-details every tile (structure locked by a tile ControlNet) with the prompt below")); append($$anchor4, span_2); }; var consequent_13 = ($$anchor4) => { @@ -1608,7 +1623,7 @@ function UpscaleOptionsModal($$anchor, $$props) { else if (get(engineKind) === "plain") $$render(consequent_14, 2); }); } - append($$anchor3, div_19); + append($$anchor3, div_20); }; if_block(node_12, ($$render) => { if (get(engineChoices) > 1) $$render(consequent_15); @@ -1617,17 +1632,17 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_15 = sibling(node_12, 2); { var consequent_17 = ($$anchor3) => { - var div_21 = root_19$4(); - var select = sibling(child(div_21), 2); + var div_22 = root_19$4(); + var select = sibling(child(div_22), 2); each(select, 21, () => MODE_ROWS, index, ($$anchor4, row) => { var option = root_20$3(); - var text_8 = child(option); + var text_9 = child(option); var option_value = {}; template_effect(() => { var _a, _b; option.disabled = !((_a = caps().modes[get(row).key]) == null ? void 0 : _a.ok); set_attribute(option, "title", ((_b = caps().modes[get(row).key]) == null ? void 0 : _b.reason) || ""); - set_text(text_8, get(row).label); + set_text(text_9, get(row).label); if (option_value !== (option_value = get(row).key)) { option.value = (option.__value = get(row).key) ?? ""; } @@ -1636,35 +1651,35 @@ function UpscaleOptionsModal($$anchor, $$props) { }); var select_value; init_select(select); - var div_22 = sibling(select, 2); - var text_9 = child(div_22); - var node_16 = sibling(div_22, 2); + var div_23 = sibling(select, 2); + var text_10 = child(div_23); + var node_16 = sibling(div_23, 2); { var consequent_16 = ($$anchor4) => { - var div_23 = root_21$3(); - var div_24 = sibling(child(div_23), 2); - var div_25 = child(div_24); + var div_24 = root_21$3(); + var div_25 = sibling(child(div_24), 2); + var div_26 = child(div_25); let classes_4; - var div_26 = sibling(div_25, 2); + var div_27 = sibling(div_26, 2); let classes_5; template_effect(() => { - classes_4 = set_class(div_25, 1, "pcr-up-seg-opt svelte-xw7bpl", null, classes_4, { + classes_4 = set_class(div_26, 1, "pcr-up-seg-opt svelte-xw7bpl", null, classes_4, { active: get(depthSource) === "pose", disabled: !caps().sources.pose.ok }); - set_attribute(div_25, "title", caps().sources.pose.ok ? "" : "No 3D pose in this image's workflow"); - classes_5 = set_class(div_26, 1, "pcr-up-seg-opt svelte-xw7bpl", null, classes_5, { + set_attribute(div_26, "title", caps().sources.pose.ok ? "" : "No 3D pose in this image's workflow"); + classes_5 = set_class(div_27, 1, "pcr-up-seg-opt svelte-xw7bpl", null, classes_5, { active: get(depthSource) === "image", disabled: !caps().sources.image.ok }); }); - delegated("click", div_25, () => { + delegated("click", div_26, () => { if (caps().sources.pose.ok) set(depthSource, "pose"); }); - delegated("click", div_26, () => { + delegated("click", div_27, () => { if (caps().sources.image.ok) set(depthSource, "image"); }); - append($$anchor4, div_23); + append($$anchor4, div_24); }; if_block(node_16, ($$render) => { if (get(mode) === "depth") $$render(consequent_16); @@ -1675,10 +1690,10 @@ function UpscaleOptionsModal($$anchor, $$props) { if (select_value !== (select_value = get(mode))) { select.value = (select.__value = get(mode)) ?? "", select_option(select, get(mode)); } - set_text(text_9, ((_a = caps().modes[get(mode)]) == null ? void 0 : _a.ok) ? get(currentModeRow).desc : ((_b = caps().modes[get(mode)]) == null ? void 0 : _b.reason) || get(currentModeRow).desc); + set_text(text_10, ((_a = caps().modes[get(mode)]) == null ? void 0 : _a.ok) ? get(currentModeRow).desc : ((_b = caps().modes[get(mode)]) == null ? void 0 : _b.reason) || get(currentModeRow).desc); }); delegated("change", select, (e) => pickModeKey(e.currentTarget.value)); - append($$anchor3, div_21); + append($$anchor3, div_22); }; if_block(node_15, ($$render) => { var _a; @@ -1688,20 +1703,20 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_17 = sibling(node_15, 2); { var consequent_20 = ($$anchor3) => { - var div_27 = root_22$2(); - var div_28 = sibling(child(div_27), 2); - var select_1 = child(div_28); + var div_28 = root_22$2(); + var div_29 = sibling(child(div_28), 2); + var select_1 = child(div_29); var option_1 = child(select_1); option_1.value = option_1.__value = "none"; var node_18 = sibling(option_1); { var consequent_18 = ($$anchor4) => { - var option_2 = root_23$2(); - var text_10 = child(option_2); + var option_2 = root_23$3(); + var text_11 = child(option_2); option_2.value = option_2.__value = "ipadapter"; template_effect(() => { var _a; - return set_text(text_10, `Style Reference — lock look to this image${((_a = caps().conditions.ipadapter) == null ? void 0 : _a.ok) ? "" : " (installs on apply)"}`); + return set_text(text_11, `Style Reference — lock look to this image${((_a = caps().conditions.ipadapter) == null ? void 0 : _a.ok) ? "" : " (installs on apply)"}`); }); append($$anchor4, option_2); }; @@ -1713,12 +1728,12 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_19 = sibling(node_18); { var consequent_19 = ($$anchor4) => { - var option_3 = root_24$3(); - var text_11 = child(option_3); + var option_3 = root_24$2(); + var text_12 = child(option_3); option_3.value = option_3.__value = "pulid"; template_effect(() => { var _a; - return set_text(text_11, `PuLID — lock the face to this image${((_a = caps().conditions.pulid) == null ? void 0 : _a.ok) ? "" : " (installs on apply)"}`); + return set_text(text_12, `PuLID — lock the face to this image${((_a = caps().conditions.pulid) == null ? void 0 : _a.ok) ? "" : " (installs on apply)"}`); }); append($$anchor4, option_3); }; @@ -1728,7 +1743,7 @@ function UpscaleOptionsModal($$anchor, $$props) { }); } bind_select_value(select_1, () => get(condition), ($$value) => set(condition, $$value)); - append($$anchor3, div_27); + append($$anchor3, div_28); }; if_block(node_17, ($$render) => { var _a, _b, _c, _d, _e, _f, _g, _h; @@ -1738,21 +1753,21 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_20 = sibling(node_17, 2); { var consequent_23 = ($$anchor3) => { - var div_29 = root_25$3(); - var div_30 = child(div_29); - var text_12 = child(div_30); - var div_31 = sibling(div_30, 2); - var span_5 = child(div_31); - var text_13 = child(span_5); + var div_30 = root_25$3(); + var div_31 = child(div_30); + var text_13 = child(div_31); + var div_32 = sibling(div_31, 2); + var span_5 = child(div_32); + var text_14 = child(span_5); var node_21 = sibling(span_5, 2); key(node_21, () => get(promptSeedKey) + ":" + get(promptRestoreNonce), ($$anchor4) => { var fragment_4 = comment(); var node_22 = first_child(fragment_4); { var consequent_21 = ($$anchor5) => { - var div_32 = root_27$3(); - action(div_32, ($$node) => promptEditor == null ? void 0 : promptEditor($$node)); - append($$anchor5, div_32); + var div_33 = root_27$3(); + action(div_33, ($$node) => promptEditor == null ? void 0 : promptEditor($$node)); + append($$anchor5, div_33); }; var alternate_1 = ($$anchor5) => { var textarea = root_28$2(); @@ -1780,23 +1795,23 @@ function UpscaleOptionsModal($$anchor, $$props) { }); } template_effect(() => { - set_text(text_12, get(engineKind) === "qwen" ? "Instruction" : "Prompt"); - set_text(text_13, get(engineKind) === "qwen" ? "how Qwen Edit should enhance" : "what the tiles re-detail with"); + set_text(text_13, get(engineKind) === "qwen" ? "Instruction" : "Prompt"); + set_text(text_14, get(engineKind) === "qwen" ? "how Qwen Edit should enhance" : "what the tiles re-detail with"); }); - append($$anchor3, div_29); + append($$anchor3, div_30); }; if_block(node_20, ($$render) => { if (get(sourceGraftable) && caps().prefillPrompt != null || get(tileEngine) || get(engineKind) === "qwen") $$render(consequent_23); }); } - var div_33 = sibling(node_20, 2); - var node_24 = sibling(child(div_33), 2); + var div_34 = sibling(node_20, 2); + var node_24 = sibling(child(div_34), 2); { var consequent_27 = ($$anchor3) => { - var div_34 = root_30$3(); - var div_35 = child(div_34); + var div_35 = root_30$3(); + var div_36 = child(div_35); let classes_6; - var node_25 = sibling(child(div_35), 2); + var node_25 = sibling(child(div_36), 2); { let $0 = user_derived(() => { var _a; @@ -1824,35 +1839,35 @@ function UpscaleOptionsModal($$anchor, $$props) { } }); } - var div_36 = sibling(div_35, 2); - var text_14 = child(div_36); - var node_26 = sibling(div_36, 2); + var div_37 = sibling(div_36, 2); + var text_15 = child(div_37); + var node_26 = sibling(div_37, 2); { var consequent_24 = ($$anchor4) => { - var div_37 = root_31$3(); - var div_38 = sibling(child(div_37), 2); - let classes_7; + var div_38 = root_31$3(); var div_39 = sibling(child(div_38), 2); - var div_40 = child(div_39); - var text_15 = child(div_40); - var div_41 = sibling(div_38, 2); + let classes_7; + var div_40 = sibling(child(div_39), 2); + var div_41 = child(div_40); + var text_16 = child(div_41); + var div_42 = sibling(div_39, 2); let classes_8; - var div_42 = sibling(child(div_41), 2); - var div_43 = child(div_42); - var text_16 = child(div_43); + var div_43 = sibling(child(div_42), 2); + var div_44 = child(div_43); + var text_17 = child(div_44); template_effect(() => { - classes_7 = set_class(div_38, 1, "pcr-up-mode svelte-xw7bpl", null, classes_7, { selected: get(canvasMode) === "grow" }); - set_text(text_15, `Grow canvas to ${get(grownW) ?? ""}×${get(grownH) ?? ""}`); - classes_8 = set_class(div_41, 1, "pcr-up-mode svelte-xw7bpl", null, classes_8, { selected: get(canvasMode) === "keep" }); - set_text(text_16, `Keep canvas at ${docWidth() ?? ""}×${docHeight() ?? ""}`); + classes_7 = set_class(div_39, 1, "pcr-up-mode svelte-xw7bpl", null, classes_7, { selected: get(canvasMode) === "grow" }); + set_text(text_16, `Grow canvas to ${get(grownW) ?? ""}×${get(grownH) ?? ""}`); + classes_8 = set_class(div_42, 1, "pcr-up-mode svelte-xw7bpl", null, classes_8, { selected: get(canvasMode) === "keep" }); + set_text(text_17, `Keep canvas at ${docWidth() ?? ""}×${docHeight() ?? ""}`); }); - delegated("click", div_38, () => { + delegated("click", div_39, () => { set(canvasMode, "grow"); }); - delegated("click", div_41, () => { + delegated("click", div_42, () => { set(canvasMode, "keep"); }); - append($$anchor4, div_37); + append($$anchor4, div_38); }; if_block(node_26, ($$render) => { if (get(needsGrow)) $$render(consequent_24); @@ -1861,7 +1876,7 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_27 = sibling(node_26, 2); { var consequent_25 = ($$anchor4) => { - var label = root_32$2(); + var label = root_32$3(); var input = child(label); template_effect(() => set_checked(input, get(ultrasharpUnder))); delegated("change", input, (e) => setUltrasharpUnder(e.currentTarget.checked)); @@ -1874,8 +1889,8 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_28 = sibling(node_27, 2); { var consequent_26 = ($$anchor4) => { - var div_44 = root_33$3(); - var node_29 = sibling(child(div_44), 2); + var div_45 = root_33$2(); + var node_29 = sibling(child(div_45), 2); SettingsSlider(node_29, { min: 0.05, get max() { @@ -1892,36 +1907,36 @@ function UpscaleOptionsModal($$anchor, $$props) { set(denoise, v, true); } }); - append($$anchor4, div_44); + append($$anchor4, div_45); }; if_block(node_28, ($$render) => { if (get(sourceGraftable) || get(tileEngine)) $$render(consequent_26); }); } template_effect(() => { - classes_6 = set_class(div_35, 1, "pcr-up-slider-row svelte-xw7bpl", null, classes_6, { "pcr-up-slider-row-ticks": !!get(scaleSlider).ticks }); - set_text(text_14, get(targetRes) ? `→ ${get(targetRes)}` : ""); + classes_6 = set_class(div_36, 1, "pcr-up-slider-row svelte-xw7bpl", null, classes_6, { "pcr-up-slider-row-ticks": !!get(scaleSlider).ticks }); + set_text(text_15, get(targetRes) ? `→ ${get(targetRes)}` : ""); }); - append($$anchor3, div_34); + append($$anchor3, div_35); }; if_block(node_24, ($$render) => { - if (get(sourceGraftable) || get(tileEngine) || get(engineKind) === "qwen") $$render(consequent_27); + if (get(sourceGraftable) || get(tileEngine) || get(engineKind) === "qwen" || get(engineKind) === "plain") $$render(consequent_27); }); } var node_30 = sibling(node_24, 2); { var consequent_28 = ($$anchor3) => { - var div_45 = root_34$3(); - var div_46 = child(div_45); - var select_2 = sibling(child(div_46), 2); + var div_46 = root_34$3(); + var div_47 = child(div_46); + var select_2 = sibling(child(div_47), 2); let classes_9; var option_4 = child(select_2); let styles_1; - var text_17 = child(option_4); + var text_18 = child(option_4); option_4.value = option_4.__value = "ultrasharp"; var option_5 = sibling(option_4); let styles_2; - var text_18 = child(option_5); + var text_19 = child(option_5); option_5.value = option_5.__value = "seedvr2"; template_effect(() => { var _a; @@ -1931,14 +1946,14 @@ function UpscaleOptionsModal($$anchor, $$props) { styles_1 = set_style(option_4, "", styles_1, { color: get(recommendedPlainEngine) === "ultrasharp" ? "#5ed357" : "#999" }); - set_text(text_17, `UltraSharp${get(recommendedPlainEngine) === "ultrasharp" ? " ●" : ""}`); + set_text(text_18, `UltraSharp${get(recommendedPlainEngine) === "ultrasharp" ? " ●" : ""}`); styles_2 = set_style(option_5, "", styles_2, { color: get(recommendedPlainEngine) === "seedvr2" ? "#5ed357" : "#999" }); - set_text(text_18, `SeedVR2 + UltraSharp (slow)${((_a = caps()) == null ? void 0 : _a.seedvr2Available) ? get(recommendedPlainEngine) === "seedvr2" ? " ●" : "" : " (installs on apply)"}`); + set_text(text_19, `SeedVR2 + UltraSharp (slow)${((_a = caps()) == null ? void 0 : _a.seedvr2Available) ? get(recommendedPlainEngine) === "seedvr2" ? " ●" : "" : " (installs on apply)"}`); }); bind_select_value(select_2, () => get(plainEngine), ($$value) => set(plainEngine, $$value)); - append($$anchor3, div_45); + append($$anchor3, div_46); }; if_block(node_30, ($$render) => { if (onUseInEdit() && get(engineKind) !== "plain") $$render(consequent_28); @@ -1947,20 +1962,20 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_31 = sibling(node_30, 2); { var consequent_30 = ($$anchor3) => { - var div_47 = root_35$3(); - var node_32 = child(div_47); + var div_48 = root_35$3(); + var node_32 = child(div_48); { var consequent_29 = ($$anchor4) => { - var div_48 = root_36$3(); - var select_3 = sibling(child(div_48), 2); + var div_49 = root_36$3(); + var select_3 = sibling(child(div_49), 2); let classes_10; var option_6 = child(select_3); let styles_3; - var text_19 = child(option_6); + var text_20 = child(option_6); option_6.value = option_6.__value = "ultrasharp"; var option_7 = sibling(option_6); let styles_4; - var text_20 = child(option_7); + var text_21 = child(option_7); option_7.value = option_7.__value = "seedvr2"; template_effect(() => { var _a; @@ -1970,32 +1985,32 @@ function UpscaleOptionsModal($$anchor, $$props) { styles_3 = set_style(option_6, "", styles_3, { color: (caps().recommendedRestore || "none") === "none" ? "#5ed357" : "#999" }); - set_text(text_19, `None${(caps().recommendedRestore || "none") === "none" ? " ●" : ""}`); + set_text(text_20, `None${(caps().recommendedRestore || "none") === "none" ? " ●" : ""}`); styles_4 = set_style(option_7, "", styles_4, { color: caps().recommendedRestore === "seedvr2" ? "#5ed357" : "#999" }); - set_text(text_20, `SeedVR2 — repair degraded source${((_a = caps()) == null ? void 0 : _a.seedvr2Available) ? caps().recommendedRestore === "seedvr2" ? " ●" : "" : " (installs on apply)"}`); + set_text(text_21, `SeedVR2 — repair degraded source${((_a = caps()) == null ? void 0 : _a.seedvr2Available) ? caps().recommendedRestore === "seedvr2" ? " ●" : "" : " (installs on apply)"}`); }); bind_select_value(select_3, () => get(climbStage), ($$value) => set(climbStage, $$value)); - append($$anchor4, div_48); + append($$anchor4, div_49); }; if_block(node_32, ($$render) => { if (get(engineKind) !== "qwen") $$render(consequent_29); }); } - var div_49 = sibling(node_32, 2); - var select_4 = sibling(child(div_49), 2); + var div_50 = sibling(node_32, 2); + var select_4 = sibling(child(div_50), 2); let classes_11; each(select_4, 21, () => caps().upscaleModelOptions, index, ($$anchor4, opt) => { var option_8 = root_37$3(); let styles_5; - var text_21 = child(option_8); + var text_22 = child(option_8); var option_8_value = {}; template_effect(() => { styles_5 = set_style(option_8, "", styles_5, { color: get(opt) === caps().recommendedUpscaleModel ? "#5ed357" : "#999" }); - set_text(text_21, `${get(opt) ?? ""}${get(opt) === caps().recommendedUpscaleModel ? " ●" : ""}`); + set_text(text_22, `${get(opt) ?? ""}${get(opt) === caps().recommendedUpscaleModel ? " ●" : ""}`); if (option_8_value !== (option_8_value = get(opt))) { option_8.value = (option_8.__value = get(opt)) ?? ""; } @@ -2006,7 +2021,7 @@ function UpscaleOptionsModal($$anchor, $$props) { "at-rec": get(climbModel) === caps().recommendedUpscaleModel })); bind_select_value(select_4, () => get(climbModel), ($$value) => set(climbModel, $$value)); - append($$anchor3, div_47); + append($$anchor3, div_48); }; if_block(node_31, ($$render) => { var _a, _b; @@ -2016,19 +2031,19 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_33 = sibling(node_31, 2); { var consequent_31 = ($$anchor3) => { - var div_50 = root_38$3(); - var div_51 = child(div_50); - var select_5 = sibling(child(div_51), 2); + var div_51 = root_38$3(); + var div_52 = child(div_51); + var select_5 = sibling(child(div_52), 2); let classes_12; each(select_5, 21, () => caps().samplerOptions, index, ($$anchor4, opt) => { var option_9 = root_39$3(); let styles_6; - var text_22 = child(option_9); + var text_23 = child(option_9); var option_9_value = {}; template_effect( ($0) => { styles_6 = set_style(option_9, "", styles_6, $0); - set_text(text_22, `${get(opt) ?? ""}${get(opt) === get(recSampler) ? " ●" : ""}`); + set_text(text_23, `${get(opt) ?? ""}${get(opt) === get(recSampler) ? " ●" : ""}`); if (option_9_value !== (option_9_value = get(opt))) { option_9.value = (option_9.__value = get(opt)) ?? ""; } @@ -2044,18 +2059,18 @@ function UpscaleOptionsModal($$anchor, $$props) { ); append($$anchor4, option_9); }); - var div_52 = sibling(div_51, 2); - var select_6 = sibling(child(div_52), 2); + var div_53 = sibling(div_52, 2); + var select_6 = sibling(child(div_53), 2); let classes_13; each(select_6, 21, () => caps().schedulerOptions, index, ($$anchor4, opt) => { var option_10 = root_40$3(); let styles_7; - var text_23 = child(option_10); + var text_24 = child(option_10); var option_10_value = {}; template_effect( ($0) => { styles_7 = set_style(option_10, "", styles_7, $0); - set_text(text_23, `${get(opt) ?? ""}${get(opt) === get(recScheduler) ? " ●" : ""}`); + set_text(text_24, `${get(opt) ?? ""}${get(opt) === get(recScheduler) ? " ●" : ""}`); if (option_10_value !== (option_10_value = get(opt))) { option_10.value = (option_10.__value = get(opt)) ?? ""; } @@ -2077,7 +2092,7 @@ function UpscaleOptionsModal($$anchor, $$props) { }); bind_select_value(select_5, () => get(sampler), ($$value) => set(sampler, $$value)); bind_select_value(select_6, () => get(scheduler), ($$value) => set(scheduler, $$value)); - append($$anchor3, div_50); + append($$anchor3, div_51); }; if_block(node_33, ($$render) => { var _a; @@ -2088,15 +2103,15 @@ function UpscaleOptionsModal($$anchor, $$props) { { var consequent_36 = ($$anchor3) => { var fragment_6 = root_41$3(); - var div_53 = first_child(fragment_6); - var span_6 = child(div_53); + var div_54 = first_child(fragment_6); + var span_6 = child(div_54); let classes_14; var node_35 = sibling(span_6, 2); { var consequent_32 = ($$anchor4) => { var span_7 = root_42$3(); - var text_24 = child(span_7); - template_effect(($0) => set_text(text_24, `${$0 ?? ""} changed`), [() => Object.keys(get(advanced)).length]); + var text_25 = child(span_7); + template_effect(($0) => set_text(text_25, `${$0 ?? ""} changed`), [() => Object.keys(get(advanced)).length]); append($$anchor4, span_7); }; var d_1 = user_derived(() => Object.keys(get(advanced)).length); @@ -2104,11 +2119,11 @@ function UpscaleOptionsModal($$anchor, $$props) { if (get(d_1)) $$render(consequent_32); }); } - var node_36 = sibling(div_53, 2); + var node_36 = sibling(div_54, 2); { var consequent_35 = ($$anchor4) => { - var div_54 = root_43$3(); - var node_37 = child(div_54); + var div_55 = root_43$3(); + var node_37 = child(div_55); { var consequent_33 = ($$anchor5) => { var label_1 = root_44$3(); @@ -2127,20 +2142,20 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_39 = first_child(fragment_7); { var consequent_34 = ($$anchor6) => { - var div_55 = root_46$3(); - var span_8 = child(div_55); - var text_25 = child(span_8); + var div_56 = root_46$3(); + var span_8 = child(div_56); + var text_26 = child(span_8); var select_7 = sibling(span_8, 2); each(select_7, 21, () => caps()[get(combo).optionsKey], index, ($$anchor7, opt) => { - var option_11 = root_47$2(); + var option_11 = root_47$3(); let styles_8; - var text_26 = child(option_11); + var text_27 = child(option_11); var option_11_value = {}; template_effect(() => { styles_8 = set_style(option_11, "", styles_8, { color: get(opt) === get(advDefaults)[get(combo).name] ? "#5ed357" : "#999" }); - set_text(text_26, `${get(opt) ?? ""}${get(opt) === get(advDefaults)[get(combo).name] ? " ●" : ""}`); + set_text(text_27, `${get(opt) ?? ""}${get(opt) === get(advDefaults)[get(combo).name] ? " ●" : ""}`); if (option_11_value !== (option_11_value = get(opt))) { option_11.value = (option_11.__value = get(opt)) ?? ""; } @@ -2151,7 +2166,7 @@ function UpscaleOptionsModal($$anchor, $$props) { init_select(select_7); template_effect( ($0) => { - set_text(text_25, get(combo).label); + set_text(text_26, get(combo).label); if (select_7_value !== (select_7_value = $0)) { select_7.value = (select_7.__value = $0) ?? "", select_option(select_7, $0); } @@ -2159,7 +2174,7 @@ function UpscaleOptionsModal($$anchor, $$props) { [() => advValue(get(combo).name)] ); delegated("change", select_7, (e) => setAdv(get(combo).name, e.target.value)); - append($$anchor6, div_55); + append($$anchor6, div_56); }; if_block(node_39, ($$render) => { var _a; @@ -2175,9 +2190,9 @@ function UpscaleOptionsModal($$anchor, $$props) { () => get(engineKind) === "qwen" ? ADV_SLIDERS.filter((r) => r.name === "steps" || r.name === "cfg") : ADV_SLIDERS, index, ($$anchor5, row) => { - var div_56 = root_48$2(); - var span_9 = child(div_56); - var text_27 = child(span_9); + var div_57 = root_48$2(); + var span_9 = child(div_57); + var text_28 = child(span_9); var node_41 = sibling(span_9, 2); { let $0 = user_derived(() => Number(advValue(get(row).name) ?? get(row).min)); @@ -2200,18 +2215,18 @@ function UpscaleOptionsModal($$anchor, $$props) { onChange: (v) => setAdv(get(row).name, v) }); } - template_effect(() => set_text(text_27, get(row).label)); - append($$anchor5, div_56); + template_effect(() => set_text(text_28, get(row).label)); + append($$anchor5, div_57); } ); - append($$anchor4, div_54); + append($$anchor4, div_55); }; if_block(node_36, ($$render) => { if (get(advancedOpen)) $$render(consequent_35); }); } template_effect(() => classes_14 = set_class(span_6, 1, "pcr-up-adv-arrow svelte-xw7bpl", null, classes_14, { open: get(advancedOpen) })); - delegated("click", div_53, () => { + delegated("click", div_54, () => { set(advancedOpen, !get(advancedOpen)); }); append($$anchor3, fragment_6); @@ -2223,8 +2238,8 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_42 = sibling(node_34, 2); { var consequent_37 = ($$anchor3) => { - var div_57 = root_49$2(); - var node_43 = sibling(child(div_57), 2); + var div_58 = root_49$2(); + var node_43 = sibling(child(div_58), 2); SavePathInput(node_43, { get value() { return get(savePrefix); @@ -2236,14 +2251,14 @@ function UpscaleOptionsModal($$anchor, $$props) { return fetchApi(); } }); - append($$anchor3, div_57); + append($$anchor3, div_58); }; if_block(node_42, ($$render) => { if (!onUseInEdit()) $$render(consequent_37); }); } - var div_58 = sibling(div_3, 2); - var node_44 = child(div_58); + var div_59 = sibling(div_3, 2); + var node_44 = child(div_59); { var consequent_41 = ($$anchor3) => { var fragment_8 = comment(); @@ -2261,7 +2276,7 @@ function UpscaleOptionsModal($$anchor, $$props) { var fragment_9 = root_52$2(); var button_5 = first_child(fragment_9); var button_6 = sibling(button_5, 2); - var text_28 = child(button_6); + var text_29 = child(button_6); var node_46 = sibling(button_6, 2); { var consequent_39 = ($$anchor5) => { @@ -2280,7 +2295,7 @@ function UpscaleOptionsModal($$anchor, $$props) { else if (progress().phase === "done" && progress().resultHash && onViewResult()) $$render(consequent_40, 1); }); } - template_effect(() => set_text(text_28, progress().phase === "done" ? "Run Again" : "Apply")); + template_effect(() => set_text(text_29, progress().phase === "done" ? "Run Again" : "Apply")); delegated("click", button_5, function(...$$args) { var _a; (_a = $$props.onCancel) == null ? void 0 : _a.apply(this, $$args); @@ -2331,7 +2346,7 @@ function UpscaleOptionsModal($$anchor, $$props) { styles = set_style(div, "", styles, { "z-index": elevated() ? 10006 : null }); set_text(text_1, `${progress() ? get(runActive) ? "Upscaling Image" : progress().phase === "done" ? "Upscale Complete" : progress().phase === "error" ? "Upscale Failed" : "Upscale Cancelled" : "Image Upscale"}${filename() ? ` — ${filename()}` : ""}${width() && height() ? ` · ${width()}×${height()}` : ""}`); classes = set_class(div_5, 1, "pcr-up-stage svelte-xw7bpl", null, classes, { zoomable: !!get(inspectSrc) }); - classes_3 = set_class(div_18, 1, "pcr-up-right svelte-xw7bpl", null, classes_3, { running: get(runActive) }); + classes_3 = set_class(div_19, 1, "pcr-up-right svelte-xw7bpl", null, classes_3, { running: get(runActive) }); }); delegated("keydown", div, handleKeydown); delegated("pointerdown", div_5, onStageDown); @@ -2374,35 +2389,36 @@ var root_6$6 = from_html(` `, 1); var root_13$5 = from_html(``); var root_15$4 = from_html(`
renders the painted region with this checkpoint — no render metadata needed, and PuLID / Style Reference work on any source
`); var root_16$4 = from_html(`
re-renders the painted region with this FLUX.1 model at the denoise below — describe the content, strong on faces and eyes
`); -var root_17$3 = from_html(`
follows your instruction inside the mask only — unmasked pixels stay untouched; the region renders at full model resolution
`); +var root_17$3 = from_html(`
re-renders the painted region with this Krea 2 model at the denoise below — describe the content, sharp detail and clean structure
`); +var root_18$3 = from_html(`
follows your instruction inside the mask only — unmasked pixels stay untouched; the region renders at full model resolution
`); var root_14$5 = from_html(`
Engine
`); -var root_19$3 = from_html(`
Installing pack…
`); -var root_21$2 = from_html(`reference`); -var root_22$1 = from_html(`this image this image (default)`, 1); -var root_25$2 = from_html(``); -var root_24$2 = from_html(`
Start
End
`, 1); -var root_26$2 = from_html(`
Fidelity
Start
End
`, 1); -var root_20$2 = from_html(`
Strength
`, 1); -var root_18$3 = from_html(`
Condition
`); -var root_28$1 = from_html(``); -var root_27$2 = from_html(`
Mode
`); -var root_30$2 = from_html(` `); -var root_31$2 = from_html(``); -var root_29$2 = from_html(`
`, 1); -var root_33$2 = from_html(`
`); -var root_34$2 = from_html(``); -var root_35$2 = from_html(`Workflow prompt (reference) `, 1); -var root_36$2 = from_html(`
Denoise
`); -var root_38$2 = from_html(``); +var root_20$2 = from_html(`
Installing pack…
`); +var root_22$1 = from_html(`reference`); +var root_23$2 = from_html(`this image this image (default)`, 1); +var root_26$2 = from_html(``); +var root_25$2 = from_html(`
Start
End
`, 1); +var root_27$2 = from_html(`
Fidelity
Start
End
`, 1); +var root_21$2 = from_html(`
Strength
`, 1); +var root_19$3 = from_html(`
Condition
`); +var root_29$2 = from_html(``); +var root_28$1 = from_html(`
Mode
`); +var root_31$2 = from_html(` `); +var root_32$2 = from_html(``); +var root_30$2 = from_html(`
`, 1); +var root_34$2 = from_html(`
`); +var root_35$2 = from_html(``); +var root_36$2 = from_html(`Workflow prompt (reference) `, 1); +var root_37$2 = from_html(`
Denoise
`); var root_39$2 = from_html(``); -var root_37$2 = from_html(`
`); -var root_40$2 = from_html(`
Grow
Feather
`, 1); -var root_41$2 = from_html(`Save to `, 1); -var root_42$2 = from_html(`
`); -var root_43$2 = from_html(``); -var root_45$2 = from_html(``); -var root_46$2 = from_html(``); -var root_44$2 = from_html(` `, 1); +var root_40$2 = from_html(``); +var root_38$2 = from_html(`
`); +var root_41$2 = from_html(`
Grow
Feather
Steps
CFG
Pre-filled with this engine's recipe (Turbo 8/1.0 · RAW 30/3.5 · SDXL 25/5). On the source model, 0 = inherit the image's own.
`, 1); +var root_42$2 = from_html(`Save to `, 1); +var root_43$2 = from_html(`
`); +var root_44$2 = from_html(``); +var root_46$2 = from_html(``); +var root_47$2 = from_html(``); +var root_45$2 = from_html(` `, 1); var root_1$7 = from_html(`
`); function InpaintModal($$anchor, $$props) { push($$props, true); @@ -2490,6 +2506,14 @@ function InpaintModal($$anchor, $$props) { let denoise = state(0.5); let sampler = state(""); let scheduler = state(""); + let steps = state( + 0 + // pre-filled to the engine's recipe on open (engineRecipe); 0 = inherit (source) + ); + let cfg = state( + 0 + // pre-filled to the engine's recipe on open; 0 = inherit (source) + ); let grow = state(0); let feather = state(24); let maskAdvancedOpen = state(false); @@ -2520,13 +2544,14 @@ function InpaintModal($$anchor, $$props) { var _a, _b; return ((_b = (_a = caps()) == null ? void 0 : _a.engineModels) == null ? void 0 : _b.find((m) => m.hash === get(engineSel))) || null; }); - let engineKind = user_derived(() => get(engineEntry) ? get(engineEntry).architecture === "qwen_edit" ? "qwen" : get(engineEntry).architecture === "flux" ? "flux1" : "sdxl" : "source"); + let engineKind = user_derived(() => get(engineEntry) ? get(engineEntry).architecture === "qwen_edit" ? "qwen" : get(engineEntry).architecture === "flux" ? "flux1" : get(engineEntry).architecture === "krea2" ? "krea2" : "sdxl" : "source"); let engineGroups = user_derived(() => { - var _a, _b, _c; + var _a, _b, _c, _d; return { sdxl: (((_a = caps()) == null ? void 0 : _a.engineModels) || []).filter((m) => m.architecture === "sdxl"), flux1: (((_b = caps()) == null ? void 0 : _b.engineModels) || []).filter((m) => m.architecture === "flux"), - qwen: (((_c = caps()) == null ? void 0 : _c.engineModels) || []).filter((m) => m.architecture === "qwen_edit") + krea2: (((_c = caps()) == null ? void 0 : _c.engineModels) || []).filter((m) => m.architecture === "krea2"), + qwen: (((_d = caps()) == null ? void 0 : _d.engineModels) || []).filter((m) => m.architecture === "qwen_edit") }; }); let engineSelectGroups = user_derived(() => { @@ -2555,6 +2580,12 @@ function InpaintModal($$anchor, $$props) { options: get(engineGroups).flux1.map((m) => ({ value: m.hash, label: m.displayName })) } ] : [], + ...get(engineGroups).krea2.length ? [ + { + label: "Krea 2 — re-render the masked region", + options: get(engineGroups).krea2.map((m) => ({ value: m.hash, label: m.displayName })) + } + ] : [], ...get(engineGroups).qwen.length ? [ { label: "Qwen Edit — instruction edit (masked)", @@ -2565,15 +2596,27 @@ function InpaintModal($$anchor, $$props) { }); let recSampler = user_derived(() => { var _a, _b, _c, _d, _e; - return get(engineKind) === "qwen" || get(engineKind) === "flux1" ? ((_b = (_a = get(engineEntry)) == null ? void 0 : _a.gen) == null ? void 0 : _b.sampler) || "euler" : ((_d = (_c = get(engineEntry)) == null ? void 0 : _c.gen) == null ? void 0 : _d.sampler) || ((_e = caps()) == null ? void 0 : _e.recommendedSampler); + return get(engineKind) === "qwen" || get(engineKind) === "flux1" || get(engineKind) === "krea2" ? ((_b = (_a = get(engineEntry)) == null ? void 0 : _a.gen) == null ? void 0 : _b.sampler) || "euler" : ((_d = (_c = get(engineEntry)) == null ? void 0 : _c.gen) == null ? void 0 : _d.sampler) || ((_e = caps()) == null ? void 0 : _e.recommendedSampler); }); let recScheduler = user_derived(() => { var _a, _b, _c, _d, _e; - return get(engineKind) === "qwen" || get(engineKind) === "flux1" ? ((_b = (_a = get(engineEntry)) == null ? void 0 : _a.gen) == null ? void 0 : _b.scheduler) || "simple" : ((_d = (_c = get(engineEntry)) == null ? void 0 : _c.gen) == null ? void 0 : _d.scheduler) || ((_e = caps()) == null ? void 0 : _e.recommendedScheduler); + return get(engineKind) === "qwen" || get(engineKind) === "flux1" || get(engineKind) === "krea2" ? ((_b = (_a = get(engineEntry)) == null ? void 0 : _a.gen) == null ? void 0 : _b.scheduler) || "simple" : ((_d = (_c = get(engineEntry)) == null ? void 0 : _c.gen) == null ? void 0 : _d.scheduler) || ((_e = caps()) == null ? void 0 : _e.recommendedScheduler); }); + function engineRecipe(entry) { + var _a, _b, _c, _d, _e; + const kind = entry ? entry.architecture === "qwen_edit" ? "qwen" : entry.architecture === "flux" ? "flux1" : entry.architecture === "krea2" ? "krea2" : "sdxl" : "source"; + const isRaw = kind === "krea2" && /raw/i.test((entry == null ? void 0 : entry.filename) || "") && !/turbo/i.test((entry == null ? void 0 : entry.filename) || ""); + if (kind === "krea2") return { steps: isRaw ? 30 : 8, cfg: isRaw ? 3.5 : 1 }; + if (kind === "sdxl") return { steps: ((_a = entry == null ? void 0 : entry.gen) == null ? void 0 : _a.steps) || 25, cfg: ((_b = entry == null ? void 0 : entry.gen) == null ? void 0 : _b.cfg) ?? 5 }; + if (kind === "qwen") return { steps: ((_c = entry == null ? void 0 : entry.gen) == null ? void 0 : _c.steps) || 40, cfg: ((_d = entry == null ? void 0 : entry.gen) == null ? void 0 : _d.cfg) ?? 4 }; + if (kind === "flux1") return { steps: ((_e = entry == null ? void 0 : entry.gen) == null ? void 0 : _e.steps) || 25, cfg: 1 }; + return { steps: 0, cfg: 0 }; + } + let recSteps = user_derived(() => engineRecipe(get(engineEntry)).steps); + let recCfg = user_derived(() => engineRecipe(get(engineEntry)).cfg); function conditionOk(key2) { var _a, _b, _c; - if (get(engineKind) === "qwen" || get(engineKind) === "flux1") return false; + if (get(engineKind) === "qwen" || get(engineKind) === "flux1" || get(engineKind) === "krea2") return false; return get(engineKind) === "sdxl" || !!((_c = (_b = (_a = caps()) == null ? void 0 : _a.conditions) == null ? void 0 : _b[key2]) == null ? void 0 : _c.ok); } let mode = state("regional"); @@ -2657,9 +2700,9 @@ function InpaintModal($$anchor, $$props) { var _a, _b, _c, _d, _e, _f, _g, _h, _i; set(engineSel, value, true); const entry = ((_b = (_a = caps()) == null ? void 0 : _a.engineModels) == null ? void 0 : _b.find((m) => m.hash === value)) || null; - const kind = entry ? entry.architecture === "qwen_edit" ? "qwen" : entry.architecture === "flux" ? "flux1" : "sdxl" : "source"; + const kind = entry ? entry.architecture === "qwen_edit" ? "qwen" : entry.architecture === "flux" ? "flux1" : entry.architecture === "krea2" ? "krea2" : "sdxl" : "source"; if (entry) { - const fluxLike = kind === "qwen" || kind === "flux1"; + const fluxLike = kind === "qwen" || kind === "flux1" || kind === "krea2"; set(sampler, ((_c = entry.gen) == null ? void 0 : _c.sampler) || (fluxLike ? "euler" : get(sampler)), true); set(scheduler, ((_d = entry.gen) == null ? void 0 : _d.scheduler) || (fluxLike ? "simple" : get(scheduler)), true); if (fluxLike && get(condition) !== "none") set(condition, "none"); @@ -2668,6 +2711,9 @@ function InpaintModal($$anchor, $$props) { set(scheduler, ((_f = caps()) == null ? void 0 : _f.defaultScheduler) || "", true); if (get(condition) !== "none" && !((_i = (_h = (_g = caps()) == null ? void 0 : _g.conditions) == null ? void 0 : _h[get(condition)]) == null ? void 0 : _i.ok)) set(condition, "none"); } + const r = engineRecipe(entry); + set(steps, r.steps, true); + set(cfg, r.cfg, true); memoryPrompt = null; set(prompt, prefillFor(), true); } @@ -2744,6 +2790,9 @@ function InpaintModal($$anchor, $$props) { const preEntry = (_d = caps().engineModels) == null ? void 0 : _d.find((m) => m.hash === get(engineSel)); if ((_e = preEntry == null ? void 0 : preEntry.gen) == null ? void 0 : _e.sampler) set(sampler, preEntry.gen.sampler, true); if ((_f = preEntry == null ? void 0 : preEntry.gen) == null ? void 0 : _f.scheduler) set(scheduler, preEntry.gen.scheduler, true); + const r0 = engineRecipe(preEntry); + set(steps, r0.steps, true); + set(cfg, r0.cfg, true); if (memEngineValid && typeof mem.denoise === "number") set(denoise, mem.denoise, true); set(grow, 0); set(feather, 24); @@ -2816,7 +2865,7 @@ function InpaintModal($$anchor, $$props) { if (typeof s.grow === "number") set(grow, s.grow, true); if (typeof s.feather === "number") set(feather, s.feather, true); if (typeof s.maskOpacity === "number") set(maskOpacity, s.maskOpacity, true); - const fluxLike = get(engineKind) === "flux1" || get(engineKind) === "qwen"; + const fluxLike = get(engineKind) === "flux1" || get(engineKind) === "qwen" || get(engineKind) === "krea2"; if (typeof s.condition === "string" && !(fluxLike && s.condition !== "none") && (s.condition === "none" || conditionOk(s.condition))) { set(condition, s.condition, true); if (typeof s.referenceImage === "string") set(referenceImage, s.referenceImage, true); @@ -3198,12 +3247,14 @@ function InpaintModal($$anchor, $$props) { feather: get(feather), sampler: get(sampler) || null, scheduler: get(scheduler) || null, + steps: get(steps) > 0 ? get(steps) : void 0, + cfg: get(cfg) > 0 ? get(cfg) : void 0, // Source engine carries the mode so a Basic pick forces a flat encode // even if the prompt still has $blocks; engine models ignore it. (Moved // content rides regional + the ImageViewer-injected condOffset, which // shifts the region masks to the content's origin.) mode: get(engineKind) === "source" ? get(mode) : void 0, - engine: get(engineKind) === "sdxl" ? "sdxl-ckpt" : get(engineKind) === "flux1" ? "flux1-unet" : get(engineKind) === "qwen" ? "qwen-edit" : "source", + engine: get(engineKind) === "sdxl" ? "sdxl-ckpt" : get(engineKind) === "flux1" ? "flux1-unet" : get(engineKind) === "krea2" ? "krea2-unet" : get(engineKind) === "qwen" ? "qwen-edit" : "source", engineModel: get(engineEntry) ? { hash: get(engineEntry).hash, filename: get(engineEntry).filename @@ -3322,7 +3373,7 @@ function InpaintModal($$anchor, $$props) { var fragment = comment(); var node_1 = first_child(fragment); { - var consequent_32 = ($$anchor2) => { + var consequent_33 = ($$anchor2) => { var div = root_1$7(); let styles; var div_1 = child(div); @@ -3535,7 +3586,7 @@ function InpaintModal($$anchor, $$props) { } var node_11 = sibling(node_10, 2); { - var consequent_13 = ($$anchor3) => { + var consequent_14 = ($$anchor3) => { var div_26 = root_14$5(); var node_12 = sibling(child(div_26), 2); { @@ -3569,24 +3620,29 @@ function InpaintModal($$anchor, $$props) { var div_29 = root_17$3(); append($$anchor4, div_29); }; + var consequent_13 = ($$anchor4) => { + var div_30 = root_18$3(); + append($$anchor4, div_30); + }; if_block(node_13, ($$render) => { if (get(engineKind) === "sdxl") $$render(consequent_10); else if (get(engineKind) === "flux1") $$render(consequent_11, 1); - else if (get(engineKind) === "qwen") $$render(consequent_12, 2); + else if (get(engineKind) === "krea2") $$render(consequent_12, 2); + else if (get(engineKind) === "qwen") $$render(consequent_13, 3); }); } append($$anchor3, div_26); }; if_block(node_11, ($$render) => { var _a, _b; - if ((_b = (_a = caps()) == null ? void 0 : _a.engineModels) == null ? void 0 : _b.length) $$render(consequent_13); + if ((_b = (_a = caps()) == null ? void 0 : _a.engineModels) == null ? void 0 : _b.length) $$render(consequent_14); }); } var node_14 = sibling(node_11, 2); { - var consequent_19 = ($$anchor3) => { - var div_30 = root_18$3(); - var select = sibling(child(div_30), 2); + var consequent_20 = ($$anchor3) => { + var div_31 = root_19$3(); + var select = sibling(child(div_31), 2); var option = child(select); option.value = option.__value = "none"; var option_1 = sibling(option); @@ -3597,36 +3653,36 @@ function InpaintModal($$anchor, $$props) { init_select(select); var node_15 = sibling(select, 2); { - var consequent_14 = ($$anchor4) => { - var div_31 = root_19$3(); - append($$anchor4, div_31); + var consequent_15 = ($$anchor4) => { + var div_32 = root_20$2(); + append($$anchor4, div_32); }; - var consequent_18 = ($$anchor4) => { - var fragment_5 = root_20$2(); - var div_32 = first_child(fragment_5); - var button_8 = child(div_32); + var consequent_19 = ($$anchor4) => { + var fragment_5 = root_21$2(); + var div_33 = first_child(fragment_5); + var button_8 = child(div_33); var node_16 = sibling(button_8, 2); { - var consequent_15 = ($$anchor5) => { - var img_5 = root_21$2(); + var consequent_16 = ($$anchor5) => { + var img_5 = root_22$1(); template_effect(() => set_attribute(img_5, "src", get(refThumbUrl))); append($$anchor5, img_5); }; var alternate_2 = ($$anchor5) => { - var fragment_6 = root_22$1(); + var fragment_6 = root_23$2(); var img_6 = first_child(fragment_6); template_effect(() => set_attribute(img_6, "src", sourceUrl())); append($$anchor5, fragment_6); }; if_block(node_16, ($$render) => { - if (get(refThumbUrl)) $$render(consequent_15); + if (get(refThumbUrl)) $$render(consequent_16); else $$render(alternate_2, -1); }); } var input_2 = sibling(node_16, 2); bind_this(input_2, ($$value) => refInputEl = $$value, () => refInputEl); - var div_33 = sibling(div_32, 2); - var node_17 = sibling(child(div_33), 2); + var div_34 = sibling(div_33, 2); + var node_17 = sibling(child(div_34), 2); { let $0 = user_derived(() => DEFAULT_WEIGHT[get(condition)] ?? 0.6); SettingsSlider(node_17, { @@ -3644,19 +3700,19 @@ function InpaintModal($$anchor, $$props) { } }); } - var div_34 = sibling(div_33, 2); - var text_4 = child(div_34); - var node_18 = sibling(div_34, 2); + var div_35 = sibling(div_34, 2); + var text_4 = child(div_35); + var node_18 = sibling(div_35, 2); { - var consequent_17 = ($$anchor5) => { + var consequent_18 = ($$anchor5) => { var fragment_7 = comment(); var node_19 = first_child(fragment_7); { - var consequent_16 = ($$anchor6) => { - var fragment_8 = root_24$2(); + var consequent_17 = ($$anchor6) => { + var fragment_8 = root_25$2(); var select_1 = sibling(first_child(fragment_8), 2); each(select_1, 21, () => IPA_WEIGHT_TYPES, index, ($$anchor7, wt) => { - var option_3 = root_25$2(); + var option_3 = root_26$2(); var text_5 = child(option_3); var option_3_value = {}; template_effect(() => { @@ -3667,8 +3723,8 @@ function InpaintModal($$anchor, $$props) { }); append($$anchor7, option_3); }); - var div_35 = sibling(select_1, 2); - var node_20 = sibling(child(div_35), 2); + var div_36 = sibling(select_1, 2); + var node_20 = sibling(child(div_36), 2); SettingsSlider(node_20, { min: 0, max: 1, @@ -3681,8 +3737,8 @@ function InpaintModal($$anchor, $$props) { set(ipaStartAt, v, true); } }); - var div_36 = sibling(div_35, 2); - var node_21 = sibling(child(div_36), 2); + var div_37 = sibling(div_36, 2); + var node_21 = sibling(child(div_37), 2); SettingsSlider(node_21, { min: 0, max: 1, @@ -3699,9 +3755,9 @@ function InpaintModal($$anchor, $$props) { append($$anchor6, fragment_8); }; var alternate_3 = ($$anchor6) => { - var fragment_9 = root_26$2(); - var div_37 = first_child(fragment_9); - var node_22 = sibling(child(div_37), 2); + var fragment_9 = root_27$2(); + var div_38 = first_child(fragment_9); + var node_22 = sibling(child(div_38), 2); SettingsSlider(node_22, { min: 0, max: 10, @@ -3714,8 +3770,8 @@ function InpaintModal($$anchor, $$props) { set(pulidFidelity, v, true); } }); - var div_38 = sibling(div_37, 2); - var node_23 = sibling(child(div_38), 2); + var div_39 = sibling(div_38, 2); + var node_23 = sibling(child(div_39), 2); SettingsSlider(node_23, { min: 0, max: 1, @@ -3728,8 +3784,8 @@ function InpaintModal($$anchor, $$props) { set(pulidStartAt, v, true); } }); - var div_39 = sibling(div_38, 2); - var node_24 = sibling(child(div_39), 2); + var div_40 = sibling(div_39, 2); + var node_24 = sibling(child(div_40), 2); SettingsSlider(node_24, { min: 0, max: 1, @@ -3745,27 +3801,27 @@ function InpaintModal($$anchor, $$props) { append($$anchor6, fragment_9); }; if_block(node_19, ($$render) => { - if (get(condition) === "ipadapter") $$render(consequent_16); + if (get(condition) === "ipadapter") $$render(consequent_17); else $$render(alternate_3, -1); }); } append($$anchor5, fragment_7); }; if_block(node_18, ($$render) => { - if (get(conditionAdvancedOpen)) $$render(consequent_17); + if (get(conditionAdvancedOpen)) $$render(consequent_18); }); } template_effect(() => set_text(text_4, `${get(conditionAdvancedOpen) ? "▾" : "▸"} Advanced`)); delegated("click", button_8, () => refInputEl == null ? void 0 : refInputEl.click()); delegated("change", input_2, chooseReference); - delegated("click", div_34, () => { + delegated("click", div_35, () => { set(conditionAdvancedOpen, !get(conditionAdvancedOpen)); }); append($$anchor4, fragment_5); }; if_block(node_15, ($$render) => { - if (get(installing)) $$render(consequent_14); - else if (get(needsReference)) $$render(consequent_18, 1); + if (get(installing)) $$render(consequent_15); + else if (get(needsReference)) $$render(consequent_19, 1); }); } template_effect( @@ -3793,20 +3849,20 @@ function InpaintModal($$anchor, $$props) { ] ); delegated("change", select, (e) => pickCondition(e.currentTarget.value)); - append($$anchor3, div_30); + append($$anchor3, div_31); }; if_block(node_14, ($$render) => { var _a; - if (((_a = caps()) == null ? void 0 : _a.conditions) && get(engineKind) !== "qwen" && get(engineKind) !== "flux1") $$render(consequent_19); + if (((_a = caps()) == null ? void 0 : _a.conditions) && get(engineKind) !== "qwen" && get(engineKind) !== "flux1" && get(engineKind) !== "krea2") $$render(consequent_20); }); } var node_25 = sibling(node_14, 2); { - var consequent_20 = ($$anchor3) => { - var div_40 = root_27$2(); - var select_2 = sibling(child(div_40), 2); + var consequent_21 = ($$anchor3) => { + var div_41 = root_28$1(); + var select_2 = sibling(child(div_41), 2); each(select_2, 21, () => MODE_ROWS, index, ($$anchor4, row) => { - var option_4 = root_28$1(); + var option_4 = root_29$2(); var text_6 = child(option_4); var option_4_value = {}; template_effect( @@ -3824,8 +3880,8 @@ function InpaintModal($$anchor, $$props) { }); var select_2_value; init_select(select_2); - var div_41 = sibling(select_2, 2); - var text_7 = child(div_41); + var div_42 = sibling(select_2, 2); + var text_7 = child(div_42); template_effect( ($0) => { if (select_2_value !== (select_2_value = get(mode))) { @@ -3838,34 +3894,34 @@ function InpaintModal($$anchor, $$props) { ] ); delegated("change", select_2, (e) => pickModeKey(e.currentTarget.value)); - append($$anchor3, div_40); + append($$anchor3, div_41); }; if_block(node_25, ($$render) => { - if (get(showModeRows)) $$render(consequent_20); + if (get(showModeRows)) $$render(consequent_21); }); } - var div_42 = sibling(node_25, 2); - var div_43 = child(div_42); - var text_8 = child(div_43); - var node_26 = sibling(div_43, 2); + var div_43 = sibling(node_25, 2); + var div_44 = child(div_43); + var text_8 = child(div_44); + var node_26 = sibling(div_44, 2); { - var consequent_22 = ($$anchor3) => { - var fragment_10 = root_29$2(); + var consequent_23 = ($$anchor3) => { + var fragment_10 = root_30$2(); var node_27 = first_child(fragment_10); { - var consequent_21 = ($$anchor4) => { - var span_2 = root_30$2(); + var consequent_22 = ($$anchor4) => { + var span_2 = root_31$2(); var text_9 = child(span_2); template_effect(() => set_text(text_9, `🧍 auto-loaded ${forcedPrefillLabel() ?? ""}'s prompt — switch below or edit`)); append($$anchor4, span_2); }; if_block(node_27, ($$render) => { - if (forcedPrefill() && forcedPrefillLabel() && !get(showModeRows)) $$render(consequent_21); + if (forcedPrefill() && forcedPrefillLabel() && !get(showModeRows)) $$render(consequent_22); }); } - var div_44 = sibling(node_27, 2); - each(div_44, 21, () => regionPrompts().filter((r) => r.text), (r) => r.name, ($$anchor4, r) => { - var button_9 = root_31$2(); + var div_45 = sibling(node_27, 2); + each(div_45, 21, () => regionPrompts().filter((r) => r.text), (r) => r.name, ($$anchor4, r) => { + var button_9 = root_32$2(); let classes_8; var text_10 = child(button_9); template_effect( @@ -3882,7 +3938,7 @@ function InpaintModal($$anchor, $$props) { append($$anchor3, fragment_10); }; if_block(node_26, ($$render) => { - if (regionPrompts().length && get(engineKind) !== "qwen" && !movedContent() && (!get(showModeRows) || !get(isRegionalMode))) $$render(consequent_22); + if (regionPrompts().length && get(engineKind) !== "qwen" && !movedContent() && (!get(showModeRows) || !get(isRegionalMode))) $$render(consequent_23); }); } var node_28 = sibling(node_26, 2); @@ -3890,19 +3946,19 @@ function InpaintModal($$anchor, $$props) { var fragment_11 = comment(); var node_29 = first_child(fragment_11); { - var consequent_23 = ($$anchor4) => { - var div_45 = root_33$2(); - action(div_45, ($$node) => promptEditor == null ? void 0 : promptEditor($$node)); - append($$anchor4, div_45); + var consequent_24 = ($$anchor4) => { + var div_46 = root_34$2(); + action(div_46, ($$node) => promptEditor == null ? void 0 : promptEditor($$node)); + append($$anchor4, div_46); }; var alternate_4 = ($$anchor4) => { - var textarea = root_34$2(); + var textarea = root_35$2(); set_attribute(textarea, "placeholder", "what should appear in the painted region\n\nNegative Prompt:\noptional"); bind_value(textarea, () => get(prompt), ($$value) => set(prompt, $$value)); append($$anchor4, textarea); }; if_block(node_29, ($$render) => { - if (mountPromptEditor()) $$render(consequent_23); + if (mountPromptEditor()) $$render(consequent_24); else $$render(alternate_4, -1); }); } @@ -3910,23 +3966,23 @@ function InpaintModal($$anchor, $$props) { }); var node_30 = sibling(node_28, 2); { - var consequent_24 = ($$anchor3) => { - var fragment_12 = root_35$2(); + var consequent_25 = ($$anchor3) => { + var fragment_12 = root_36$2(); var textarea_1 = sibling(first_child(fragment_12), 2); bind_this(textarea_1, ($$value) => refEl = $$value, () => refEl); template_effect(() => set_value(textarea_1, referencePrompt())); append($$anchor3, fragment_12); }; if_block(node_30, ($$render) => { - if (referencePrompt() && get(engineKind) !== "qwen") $$render(consequent_24); + if (referencePrompt() && get(engineKind) !== "qwen") $$render(consequent_25); }); } - var div_46 = sibling(div_42, 2); - var node_31 = sibling(child(div_46), 2); + var div_47 = sibling(div_43, 2); + var node_31 = sibling(child(div_47), 2); { - var consequent_25 = ($$anchor3) => { - var div_47 = root_36$2(); - var node_32 = sibling(child(div_47), 2); + var consequent_26 = ($$anchor3) => { + var div_48 = root_37$2(); + var node_32 = sibling(child(div_48), 2); { let $0 = user_derived(() => { var _a; @@ -3947,21 +4003,21 @@ function InpaintModal($$anchor, $$props) { } }); } - append($$anchor3, div_47); + append($$anchor3, div_48); }; if_block(node_31, ($$render) => { - if (get(engineKind) !== "qwen") $$render(consequent_25); + if (get(engineKind) !== "qwen") $$render(consequent_26); }); } var node_33 = sibling(node_31, 2); { - var consequent_26 = ($$anchor3) => { - var div_48 = root_37$2(); - var div_49 = child(div_48); - var select_3 = sibling(child(div_49), 2); + var consequent_27 = ($$anchor3) => { + var div_49 = root_38$2(); + var div_50 = child(div_49); + var select_3 = sibling(child(div_50), 2); let classes_9; each(select_3, 21, () => caps().samplerOptions, index, ($$anchor4, opt) => { - var option_5 = root_38$2(); + var option_5 = root_39$2(); let styles_3; var text_11 = child(option_5); var option_5_value = {}; @@ -3984,11 +4040,11 @@ function InpaintModal($$anchor, $$props) { ); append($$anchor4, option_5); }); - var div_50 = sibling(div_49, 2); - var select_4 = sibling(child(div_50), 2); + var div_51 = sibling(div_50, 2); + var select_4 = sibling(child(div_51), 2); let classes_10; each(select_4, 21, () => caps().schedulerOptions, index, ($$anchor4, opt) => { - var option_6 = root_39$2(); + var option_6 = root_40$2(); let styles_4; var text_12 = child(option_6); var option_6_value = {}; @@ -4017,21 +4073,21 @@ function InpaintModal($$anchor, $$props) { }); bind_select_value(select_3, () => get(sampler), ($$value) => set(sampler, $$value)); bind_select_value(select_4, () => get(scheduler), ($$value) => set(scheduler, $$value)); - append($$anchor3, div_48); + append($$anchor3, div_49); }; if_block(node_33, ($$render) => { var _a, _b; - if ((_b = (_a = caps()) == null ? void 0 : _a.samplerOptions) == null ? void 0 : _b.length) $$render(consequent_26); + if ((_b = (_a = caps()) == null ? void 0 : _a.samplerOptions) == null ? void 0 : _b.length) $$render(consequent_27); }); } - var div_51 = sibling(node_33, 2); - var text_13 = child(div_51); - var node_34 = sibling(div_51, 2); + var div_52 = sibling(node_33, 2); + var text_13 = child(div_52); + var node_34 = sibling(div_52, 2); { - var consequent_27 = ($$anchor3) => { - var fragment_13 = root_40$2(); - var div_52 = first_child(fragment_13); - var node_35 = sibling(child(div_52), 2); + var consequent_28 = ($$anchor3) => { + var fragment_13 = root_41$2(); + var div_53 = first_child(fragment_13); + var node_35 = sibling(child(div_53), 2); SettingsSlider(node_35, { min: 0, max: 64, @@ -4044,8 +4100,8 @@ function InpaintModal($$anchor, $$props) { set(grow, v, true); } }); - var div_53 = sibling(div_52, 2); - var node_36 = sibling(child(div_53), 2); + var div_54 = sibling(div_53, 2); + var node_36 = sibling(child(div_54), 2); SettingsSlider(node_36, { min: 0, max: 128, @@ -4058,18 +4114,50 @@ function InpaintModal($$anchor, $$props) { set(feather, v, true); } }); + var div_55 = sibling(div_54, 2); + var node_37 = sibling(child(div_55), 2); + SettingsSlider(node_37, { + min: 0, + max: 60, + step: 1, + get value() { + return get(steps); + }, + get savedValue() { + return get(recSteps); + }, + onChange: (v) => { + set(steps, v, true); + } + }); + var div_56 = sibling(div_55, 2); + var node_38 = sibling(child(div_56), 2); + SettingsSlider(node_38, { + min: 0, + max: 10, + step: 0.1, + get value() { + return get(cfg); + }, + get savedValue() { + return get(recCfg); + }, + onChange: (v) => { + set(cfg, v, true); + } + }); append($$anchor3, fragment_13); }; if_block(node_34, ($$render) => { - if (get(maskAdvancedOpen)) $$render(consequent_27); + if (get(maskAdvancedOpen)) $$render(consequent_28); }); } - var node_37 = sibling(node_34, 2); + var node_39 = sibling(node_34, 2); { - var consequent_28 = ($$anchor3) => { - var fragment_14 = root_41$2(); - var node_38 = sibling(first_child(fragment_14), 2); - SavePathInput(node_38, { + var consequent_29 = ($$anchor3) => { + var fragment_14 = root_42$2(); + var node_40 = sibling(first_child(fragment_14), 2); + SavePathInput(node_40, { get value() { return get(savePrefix); }, @@ -4082,45 +4170,45 @@ function InpaintModal($$anchor, $$props) { }); append($$anchor3, fragment_14); }; - if_block(node_37, ($$render) => { - if (!onUseInEdit()) $$render(consequent_28); - }); - } - var node_39 = sibling(div_3, 2); - { - var consequent_29 = ($$anchor3) => { - var div_54 = root_42$2(); - var text_14 = child(div_54); - template_effect(() => set_text(text_14, `⚠ ${get(errorMsg) ?? ""}`)); - append($$anchor3, div_54); - }; if_block(node_39, ($$render) => { - if (get(errorMsg)) $$render(consequent_29); + if (!onUseInEdit()) $$render(consequent_29); }); } - var div_55 = sibling(node_39, 2); - var node_40 = child(div_55); + var node_41 = sibling(div_3, 2); { var consequent_30 = ($$anchor3) => { - var button_10 = root_43$2(); + var div_57 = root_43$2(); + var text_14 = child(div_57); + template_effect(() => set_text(text_14, `⚠ ${get(errorMsg) ?? ""}`)); + append($$anchor3, div_57); + }; + if_block(node_41, ($$render) => { + if (get(errorMsg)) $$render(consequent_30); + }); + } + var div_58 = sibling(node_41, 2); + var node_42 = child(div_58); + { + var consequent_31 = ($$anchor3) => { + var button_10 = root_44$2(); delegated("click", button_10, stopRun); append($$anchor3, button_10); }; var alternate_6 = ($$anchor3) => { - var fragment_15 = root_44$2(); + var fragment_15 = root_45$2(); var button_11 = first_child(fragment_15); var button_12 = sibling(button_11, 2); var text_15 = child(button_12); - var node_41 = sibling(button_12, 2); + var node_43 = sibling(button_12, 2); { - var consequent_31 = ($$anchor4) => { - var button_13 = root_45$2(); + var consequent_32 = ($$anchor4) => { + var button_13 = root_46$2(); template_effect(() => button_13.disabled = !get(doneState)); delegated("click", button_13, () => onUseInEdit()(get(doneState))); append($$anchor4, button_13); }; var alternate_5 = ($$anchor4) => { - var button_14 = root_46$2(); + var button_14 = root_47$2(); var text_16 = child(button_14); template_effect(() => { button_14.disabled = !get(doneState) || get(saving); @@ -4129,8 +4217,8 @@ function InpaintModal($$anchor, $$props) { delegated("click", button_14, save); append($$anchor4, button_14); }; - if_block(node_41, ($$render) => { - if (onUseInEdit()) $$render(consequent_31); + if_block(node_43, ($$render) => { + if (onUseInEdit()) $$render(consequent_32); else $$render(alternate_5, -1); }); } @@ -4143,8 +4231,8 @@ function InpaintModal($$anchor, $$props) { delegated("click", button_12, apply); append($$anchor3, fragment_15); }; - if_block(node_40, ($$render) => { - if (get(running)) $$render(consequent_30); + if_block(node_42, ($$render) => { + if (get(running)) $$render(consequent_31); else $$render(alternate_6, -1); }); } @@ -4189,13 +4277,13 @@ function InpaintModal($$anchor, $$props) { delegated("contextmenu", div_12, (e) => e.preventDefault()); event("dragstart", div_12, (e) => e.preventDefault()); event("selectstart", div_12, (e) => e.preventDefault()); - delegated("click", div_51, () => { + delegated("click", div_52, () => { set(maskAdvancedOpen, !get(maskAdvancedOpen)); }); append($$anchor2, div); }; if_block(node_1, ($$render) => { - if ($$props.open) $$render(consequent_32); + if ($$props.open) $$render(consequent_33); }); } append($$anchor, fragment); @@ -8889,8 +8977,11 @@ function EditModal($$anchor, $$props) { } function setLayerVisible(i, v) { get(layers)[i] = { ...get(layers)[i], visible: v }; + commitAction("Visibility"); } + let opacityDragStart = null; function setLayerOpacity(i, o) { + if (get(layers)[i].opacity === o) return; get(layers)[i] = { ...get(layers)[i], opacity: o }; } const BLEND_MODES = [ @@ -8993,7 +9084,10 @@ function EditModal($$anchor, $$props) { function commitRename() { const i = get(layers).findIndex((L) => L.id === get(renamingId)); const name = get(renameText).trim(); - if (i >= 0 && name && name !== get(layers)[i].name) get(layers)[i] = { ...get(layers)[i], name }; + if (i >= 0 && name && name !== get(layers)[i].name) { + get(layers)[i] = { ...get(layers)[i], name }; + commitAction("Rename Layer"); + } set(renamingId, null); } function dropLayer(targetId) { @@ -9017,6 +9111,7 @@ function EditModal($$anchor, $$props) { set(layers, rest, true); set(activeIndex, get(layers).findIndex((L) => L.id === activeId), true); dragId = null; + commitAction("Reorder Layers"); } function compositeLayers(idxs) { const c = document.createElement("canvas"); @@ -10795,7 +10890,14 @@ function EditModal($$anchor, $$props) { e.stopPropagation(); setLayerVisible(get(i), !get(L).visible); }); + delegated("pointerdown", input_2, () => { + opacityDragStart = get(L).opacity; + }); delegated("input", input_2, (e) => setLayerOpacity(get(i), +e.currentTarget.value)); + delegated("change", input_2, (e) => { + if (opacityDragStart === null || +e.currentTarget.value !== opacityDragStart) commitAction("Opacity"); + opacityDragStart = null; + }); delegated("click", input_2, (e) => e.stopPropagation()); append($$anchor4, div_72); }); @@ -13571,6 +13673,9 @@ function ImageViewer($$anchor, $$props) { } user_effect(() => { if (fetchApi()) refreshEditDocs(); + const onEditDocsChanged = () => refreshEditDocs(); + window.addEventListener("promptchain:edit-docs-changed", onEditDocsChanged); + return () => window.removeEventListener("promptchain:edit-docs-changed", onEditDocsChanged); }); let lineageData = state(null); let lineageList = user_derived(() => (() => { @@ -14351,7 +14456,7 @@ function ImageViewer($$anchor, $$props) { showNewGenBanner(state2.elapsedSec); viewUpscaleResult(state2.resultHash); } else if (state2.phase === "cancelled") { - cancelUpscale(); + set(upscaleProgress, null); } }); return; @@ -16929,7 +17034,7 @@ ${get(imageInfo).negative}` : "") : ""); var _a2; return (_a2 = get(editPrepared)) == null ? void 0 : _a2.caps; }); - let $5 = user_derived(() => get(editPrepared) ? get(displayedHash) || "" : ""); + let $5 = user_derived(() => get(editPrepared) && /^[0-9a-f]{64}$/.test(get(displayedHash) || "") ? get(displayedHash) : ""); let $6 = user_derived(() => onInpaintRegion() ? openInpaintFromEdit : null); let $7 = user_derived(() => onUpscalePrepare() && onUpscaleBackground() && onInpaintUploadReference() ? openUpscaleFromEdit : null); let $8 = user_derived(() => onReposeCaps() && onReposeRun() && onMountPoser() && onInpaintUploadReference() ? openReposeFromEdit : null); diff --git a/js/lib/upscale-background.js b/js/lib/upscale-background.js index 55bbad7..12ad0e9 100644 --- a/js/lib/upscale-background.js +++ b/js/lib/upscale-background.js @@ -8,6 +8,7 @@ import { api } from "../../../scripts/api.js"; import { getLink } from "./slot-utils.js"; import { buildUpscaleGraph, + logUpscaleRun, pickSeedvr2DitModel, SEEDVR2_STAGE_MAX_SHORT_EDGE, SEEDVR2_INPUT_NOISE, @@ -42,7 +43,7 @@ function createTracker() { // own job counting (ultimate-upscale.py): main pass rows*cols, plus the seam // pass per seam_fix_mode. Lets the modal show OVERALL progress — the ws // progress events are per-tile sampler bars that reset every tile. -function computeTilePlan(graph, data) { +function computeTilePlan(graph, data, options = {}) { const usNode = graph._nodes.find((n) => n.comfyClass === "UltimateSDUpscale" || n.comfyClass === "UltimateSDUpscaleNoUpscale"); if (!usNode) return { steps: 0, totalTiles: 0 }; const w = (name) => usNode.widgets?.find((x) => x.name === name)?.value; @@ -52,13 +53,19 @@ function computeTilePlan(graph, data) { let outW = (data.width || 0) * scale; let outH = (data.height || 0) * scale; if (usNode.comfyClass === "UltimateSDUpscaleNoUpscale") { - // NoUpscale tiles whatever arrives on upscaled_image — the final dims - // live on the exact-fit ImageScale feeding it. - const input = usNode.inputs?.find((i) => i.name === "upscaled_image"); - const link = input?.link != null ? getLink(graph, input.link) : null; - const origin = link && graph.getNodeById(link.origin_id); - outW = Number(origin?.widgets?.find((x) => x.name === "width")?.value) || (data.width || 0); - outH = Number(origin?.widgets?.find((x) => x.name === "height")?.value) || (data.height || 0); + if (options.preEnlargedW && options.preEnlargedH) { + // Cache hit: upscaled_image is a LoadImage of the cached base (no width/ + // height widgets to read), so take the resolved target dims from the cache. + outW = options.preEnlargedW; outH = options.preEnlargedH; + } else { + // NoUpscale tiles whatever arrives on upscaled_image — the final dims + // live on the exact-fit ImageScale feeding it. + const input = usNode.inputs?.find((i) => i.name === "upscaled_image"); + const link = input?.link != null ? getLink(graph, input.link) : null; + const origin = link && graph.getNodeById(link.origin_id); + outW = Number(origin?.widgets?.find((x) => x.name === "width")?.value) || (data.width || 0); + outH = Number(origin?.widgets?.find((x) => x.name === "height")?.value) || (data.height || 0); + } } const rows = Math.ceil(outH / tileH) || 1; const cols = Math.ceil(outW / tileW) || 1; @@ -122,7 +129,7 @@ function collectRecordMeta(graph, data) { }; } -function watchExecution({ promptId, workflowId, meta, plan, startedAt, tracker, run }) { +function watchExecution({ promptId, workflowId, meta, plan, startedAt, tracker, run, enlargedUrl }) { let lastImage = null; // Tile bookkeeping: each sampled tile runs its own progress bar (1..steps), // so a value reset = next tile. Bars with a different max are the model @@ -213,6 +220,9 @@ function watchExecution({ promptId, workflowId, meta, plan, startedAt, tracker, resultUrl: lastImage ? api.apiURL(`/view?filename=${encodeURIComponent(lastImage.filename)}&subfolder=${encodeURIComponent(lastImage.subfolder || "")}&type=${lastImage.type || "output"}&rand=${Math.random()}`) : null, + // The enlarged base the tiles re-detailed — the compare's "Before" (so you see + // the tile pass's contribution over the sharpened base, not vs the raw source). + enlargedUrl: enlargedUrl || null, elapsedSec, }); toast("success", `Upscale finished in ${elapsedSec}s${lastImage ? `: ${lastImage.filename}` : ""}.`); @@ -245,9 +255,13 @@ function comboValues(nodeType, widgetName) { // noise (clean renders carry no degradation for it to invert), and the ESRGAN // climb only happens when the target is still further out. Exported for the // headless build-only harness. -export function buildEnlargePrompt(inputRef, targetW, targetH, engine = "ultrasharp") { +export function buildEnlargePrompt(inputRef, targetW, targetH, engine = "ultrasharp", climbModel = null) { const models = comboValues("UpscaleModelLoader", "model_name"); - const modelName = models.find((o) => /ultrasharp/i.test(o)) || models.find((o) => /^4x/i.test(o)) || models[0]; + // Mirror upscale-from-image.js defaultUpscaleModelPick: exclude UltraSharpV2 (its + // micro-speckle becomes tile-CN cross-hatch) so the cached base matches the fused build. + const modelName = (climbModel && models.includes(climbModel) ? climbModel : null) + || models.find((o) => /ultrasharp(?!v2)/i.test(o)) || models.find((o) => /ultrasharp/i.test(o)) + || models.find((o) => /^4x/i.test(o)) || models[0]; if (!modelName) throw new Error("no upscale model installed"); if (engine === "seedvr2") { @@ -297,10 +311,10 @@ export function buildEnlargePrompt(inputRef, targetW, targetH, engine = "ultrash // to temp. Used by the Edit grow-canvas path to produce the new Background // while the region render diffuses, and for the under-layer pass. Plain JSON // prompt — no workflow tab involved. -export function enlargeImageInBackground(inputRef, targetW, targetH, engine = "ultrasharp") { +export function enlargeImageInBackground(inputRef, targetW, targetH, engine = "ultrasharp", climbModel = null) { let output; try { - output = buildEnlargePrompt(inputRef, targetW, targetH, engine); + output = buildEnlargePrompt(inputRef, targetW, targetH, engine, climbModel); } catch (e) { return Promise.reject(e); } @@ -350,6 +364,61 @@ export function enlargeImageInBackground(inputRef, targetW, targetH, engine = "u }); } +// ── enlarged-base cache (NoUpscale tile engines) ─────────────────────────── +// The UltraSharp/SeedVR2 enlarge ("pre-process") is deterministic, so a retry +// that only changes tile dials reuses the saved base instead of re-running it. +// Keyed on the RESOLVED enlarge identity (input + engine + climb model + target +// dims) — tile dials are excluded by construction. Only the four NoUpscale tile +// engines are eligible; fused/diffusion engines (source/qwen/flux2/plain) build +// the enlarge inseparably and stay on the normal fused path. The whole pre-pass +// is best-effort: any failure falls through to the fused build (never breaks). +const TILE_ENGINES = new Set(["sdxl-ckpt", "flux1-unet", "zimage-unet", "krea2-unet"]); +const _enlargeCache = new Map(); // key -> input-dir ref ("subfolder/name.png") + +function resolveEnlargeTarget(options, data) { + const w = data.width || 0, h = data.height || 0; + const longest = Math.max(w, h); + const scale = typeof options.upscaleBy === "number" && options.upscaleBy >= 1 + ? options.upscaleBy + : longest > 0 ? Math.min(8, Math.max(1, Math.round((4096 / longest) * 100) / 100)) : 2; + return { targetW: Math.round(w * scale) || 2048, targetH: Math.round(h * scale) || 2048, scale }; +} + +function enlargeCacheKey(options, data, targetW, targetH) { + const engine = options.climbStage === "seedvr2" ? "seedvr2" : "ultrasharp"; + return [data.input_ref || "", engine, options.climbModel || "default", targetW, targetH].join("|"); +} + +// Short deterministic hash of the cache key → a stable cache filename, so re-MISSes +// for the same enlarge identity OVERWRITE one file instead of piling new ones up. +function hashKey(s) { + let h = 5381; + for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0; + return h.toString(36); +} + +// A LoadImage reads only the input dir, so copy the freshly-enlarged temp image into +// input/ (under a key-stable name) so the tile graph can load it on a cache hit. +async function copyEnlargedToInput(img, nameBase) { + const url = api.apiURL(`/view?filename=${encodeURIComponent(img.filename)}&subfolder=${encodeURIComponent(img.subfolder || "")}&type=${img.type || "temp"}`); + const blob = await (await fetch(url)).blob(); + const form = new FormData(); + form.append("image", new File([blob], `upcache_${nameBase}.png`, { type: "image/png" })); + form.append("subfolder", "promptchain_upscale_cache"); + form.append("overwrite", "true"); + const res = await api.fetchApi("/upload/image", { method: "POST", body: form }); + const j = await res.json(); + return j.subfolder ? `${j.subfolder}/${j.name}` : j.name; +} + +// /view URL for a cached enlarged base (input-dir ref) — the compare's "Before". +function enlargedRefUrl(ref) { + const slash = ref.lastIndexOf("/"); + const sub = slash >= 0 ? ref.slice(0, slash) : ""; + const name = slash >= 0 ? ref.slice(slash + 1) : ref; + return api.apiURL(`/view?filename=${encodeURIComponent(name)}&subfolder=${encodeURIComponent(sub)}&type=input&rand=${Math.random()}`); +} + export function upscaleImageInBackground(prepared, options = {}) { const tracker = createTracker(); // Cancellation context, shared between the pipeline and tracker.cancel(). @@ -400,6 +469,38 @@ export function upscaleImageInBackground(prepared, options = {}) { return; } + // Enlarge cache (NoUpscale tile engines): pre-enlarge once, reuse on retry so + // only the tiles re-run. Wrapped so ANY failure falls through to the normal + // fused build — this can never break the upscale, only skip the optimization. + try { + // Skip <=1.02: there the fused build does a plain lanczos (no ESRGAN climb), so + // there's nothing expensive to cache AND a cached ESRGAN base would diverge from + // the fused output. Above 1.02 the cached base equals the fused climb (same + // climbModel, same lanczos fit), so the result is unchanged — just cached. + const { targetW, targetH, scale } = TILE_ENGINES.has(options.engine || "source") && !options.preEnlargedRef + ? resolveEnlargeTarget(options, prepared.data) + : { scale: 0 }; + if (scale > 1.02) { + const key = enlargeCacheKey(options, prepared.data, targetW, targetH); + let baseRef = _enlargeCache.get(key); + if (baseRef) { + console.log(`[upscale-cache] HIT — reusing enlarged base, skipping pre-process (${targetW}x${targetH})`); + } else { + tracker.emit({ phase: "running", prep: true }); + const enlEngine = options.climbStage === "seedvr2" ? "seedvr2" : "ultrasharp"; + const temp = await enlargeImageInBackground(prepared.data.input_ref, targetW, targetH, enlEngine, options.climbModel); + if (run.cancelled) return; + baseRef = await copyEnlargedToInput(temp, hashKey(key)); + _enlargeCache.set(key, baseRef); + console.log(`[upscale-cache] MISS — enlarged + cached base (${targetW}x${targetH})`); + } + options = { ...options, preEnlargedRef: baseRef, preEnlargedW: targetW, preEnlargedH: targetH }; + } + } catch (e) { + console.warn("[PromptChain] enlarge cache pre-pass skipped — using fused build:", e); + options = { ...options, preEnlargedRef: null }; + } + tracker.emit({ phase: "building" }); const built = await buildUpscaleGraph(prepared, options); if (!built) { @@ -411,6 +512,7 @@ export function upscaleImageInBackground(prepared, options = {}) { tracker.emit({ phase: "queueing" }); const serialized = await app.graphToPrompt(graph); + logUpscaleRun(serialized.output, options.engine); let promptId = null; // Arm the external-prompt window across the queue round-trip so the main // graph's execution_start gate recognizes this run even if its event beats @@ -438,7 +540,7 @@ export function upscaleImageInBackground(prepared, options = {}) { run.promptId = promptId; const startedAt = Date.now(); const meta = collectRecordMeta(graph, prepared.data); - const plan = computeTilePlan(graph, prepared.data); + const plan = computeTilePlan(graph, prepared.data, options); if (!promptId) { tracker.emit({ phase: "error", message: "queue returned no prompt id" }); @@ -457,7 +559,8 @@ export function upscaleImageInBackground(prepared, options = {}) { return; } tracker.emit({ phase: "running" }); - watchExecution({ promptId, workflowId, meta, plan, startedAt, tracker, run }); + const enlargedUrl = options.preEnlargedRef ? enlargedRefUrl(options.preEnlargedRef) : null; + watchExecution({ promptId, workflowId, meta, plan, startedAt, tracker, run, enlargedUrl }); })().catch((e) => { console.error("[PromptChain] background upscale failed", e); tracker.emit({ phase: "error", message: e?.message || "unexpected failure" }); diff --git a/js/lib/upscale-from-image.js b/js/lib/upscale-from-image.js index c75e91b..1df046e 100644 --- a/js/lib/upscale-from-image.js +++ b/js/lib/upscale-from-image.js @@ -21,6 +21,7 @@ import { app } from "../../../scripts/app.js"; import { api } from "../../../scripts/api.js"; import { getLink } from "./slot-utils.js"; +import { offscreenIdBase } from "./offscreen-graph.js"; import { injectUpscaler, buildRegionalUpscaleCond, @@ -54,6 +55,36 @@ export function reportClientError(where, e, context = null) { } catch { /* reporting must never break the flow it reports on */ } } +// Diagnostic: log exactly what an upscale Apply submitted, into comfyui.log +// (as "[upscale-run]") so a run's model/denoise/recipe is recoverable without +// reverse-engineering the output PNG. apiPrompt is app.graphToPrompt().output. +export function logUpscaleRun(apiPrompt, engine) { + try { + if (!apiPrompt || typeof apiPrompt !== "object") return; + const vals = Object.values(apiPrompt); + const find = (pred) => vals.find(pred) || null; + const unet = find((n) => n.class_type === "UNETLoader")?.inputs?.unet_name + || find((n) => n.class_type === "CheckpointLoaderSimple" || n.class_type === "CheckpointLoader")?.inputs?.ckpt_name + || null; + const us = find((n) => (n.class_type || "").includes("UltimateSDUpscale"))?.inputs + || find((n) => n.class_type === "KSampler" || n.class_type === "PromptChain_MaskedDetail")?.inputs + || {}; + const climb = find((n) => n.class_type === "UpscaleModelLoader")?.inputs?.model_name || null; + const vae = find((n) => n.class_type === "VAELoader")?.inputs?.vae_name + || find((n) => n.class_type === "VAEUtils_CustomVAELoader")?.inputs?.vae_name || null; + const tile = us.tile_width != null ? `${us.tile_width}x${us.tile_height}` : null; + const summary = { + engine: engine || "?", model: unet, vae, climb, + denoise: us.denoise, cfg: us.cfg, steps: us.steps, + sampler: us.sampler_name, scheduler: us.scheduler, + tile, upscale_by: us.upscale_by, seam: us.seam_fix_mode, mode: us.mode_type, + }; + reportClientError("upscale-run", + { message: `engine=${engine} model=${unet} vae=${vae} denoise=${us.denoise} cfg=${us.cfg} steps=${us.steps} sampler=${us.sampler_name}/${us.scheduler} tile=${tile} upscale_by=${us.upscale_by} seam=${us.seam_fix_mode} climb=${climb}` }, + summary); + } catch { /* never break the run */ } +} + function freshWorkflowId() { if (globalThis.crypto?.randomUUID) return crypto.randomUUID(); return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2, 10)}`; @@ -144,10 +175,13 @@ export function analyzeUpscaleCaps(data) { // renders when the SeedVR2 pack is actually installed. const seedvr2Available = !!window.LiteGraph?.registered_node_types?.["SeedVR2VideoUpscaler"]; + // High-Detail krea2 renders decode through VAEUtils_VAEDecodeTiled (spacepxl), + // not the stock VAEDecode — still a graftable sampler->decode pipeline, so + // accept the tiled decoders too or those renders fall to plain ESRGAN only. const graftable = has("UltimateSDUpscale") || ( has("PromptChain_PromptChain") && (has("KSampler") || has("KSamplerAdvanced") || has("SamplerCustomAdvanced")) - && has("VAEDecode") + && (has("VAEDecode") || has("VAEUtils_VAEDecodeTiled") || has("VAEDecodeTiled")) ); if (!graftable) { return { @@ -290,7 +324,13 @@ export function engineAssetSupport() { && comboProbe("UNETLoader", "unet_name").some((o) => /z.?image/i.test(o)) && comboProbe("CLIPLoader", "clip_name").some((o) => /qwen.*3[._]4b|lumina2/i.test(o)) && comboProbe("VAELoader", "vae_name").some((o) => /(^|[\\/])ae\.(safetensors|sft)$/i.test(o)); - return { sdxl, qwen, flux1, zimage }; + // Krea 2 (DiT, mirrors zimage but no ModelSamplingAuraFlow — shift is baked + // into the model class): krea2 UNET + qwen3vl_4b text encoder + qwen_image_vae. + const krea2 = !!reg["UltimateSDUpscaleNoUpscale"] && esrgan + && comboProbe("UNETLoader", "unet_name").some((o) => /krea2/i.test(o)) + && comboProbe("CLIPLoader", "clip_name").some((o) => /qwen3vl.*4b|qwen3.?vl.*4b/i.test(o)) + && comboProbe("VAELoader", "vae_name").some((o) => /qwen.*image.*vae/i.test(o)); + return { sdxl, qwen, flux1, zimage, krea2 }; } // The modal's defaults for a picked engine model. SDXL: the CN-locked tile @@ -356,6 +396,50 @@ function engineModelDefaults(architecture, config) { }, }; } + if (architecture === "krea2") { + // Krea 2 Turbo is step-distilled (cfg 1.0, 8 steps), cfg native (no + // FluxGuidance/ModelSampling). TILE denoise stays LOW: a tile routine needs + // each tile anchored to the source or adjacent tiles regenerate different + // content and won't stitch (visible seams). The catch — at low denoise the + // distilled few-step field under-resolves the tile (some residual voronoi/ + // blotch on smooth skin; NOT a VAE artifact, persists through a clean + // wan_2.1_vae decode). Turbo can't fully win either way in a tile pass; the + // non-distilled RAW krea2 is the proper clean+coherent tile upscaler. + // Turbo defaults to euler+simple (the official Krea recipe; suits the smooth + // matte-PVC figurine finish). RAW keeps er_sde (grain-preserving for photoreal). + const usdu = nodes.UltimateSDUpscale || {}; + // RAW = the undistilled base checkpoint. Non-distilled, so it resolves a LOW + // denoise tile cleanly (no blotch) while staying anchored to the source (no + // seams) — the proper clean+coherent tiled upscaler. It runs a REAL cfg + + // full steps; Turbo stays the soft-but-coherent 0.45 / cfg 1.0 / 8-step + // compromise. (Same wan_2.1_vae decode + qwen3vl CLIP either way.) + const fn = config?.filename || ""; + const raw = /raw/i.test(fn) && !/turbo/i.test(fn); + return { + denoise: raw ? 0.35 : 0.45, + // No tile ControlNet exists for Krea 2 (standalone DiT, not Flux), so each tile + // is anchored only by the img2img floor — push denoise too far and a tile that + // contains the whole subject regenerates it (doubling). Cap to the CN-less safe + // band; Turbo can't go much below 0.45 without blotching, so 0.45/0.5 is its + // narrow usable window. (Stronger re-detail without doubling needs smaller tiles.) + denoiseMax: raw ? 0.45 : 0.5, + sampler: raw ? "er_sde" : "euler", + scheduler: "simple", + advanced: { + steps: usdu.steps ?? (raw ? 30 : 8), + cfg: raw ? 3.5 : 1.0, + tile_width: usdu.tile_width ?? 1024, + tile_height: usdu.tile_height ?? 1024, + mask_blur: 32, + tile_padding: 64, + mode_type: "Chess", // checkerboard order — neighbours redraw before a tile's seam (cleaner than Linear) + seam_fix_mode: raw ? "Half Tile" : "None", + // RAW's Half-Tile seam pass must NOT full-repaint (the node default is 1.0, which + // makes seam bands hallucinate content that won't match the lightly-detailed tiles). + seam_fix_denoise: raw ? 0.3 : undefined, + }, + }; + } const usdu = nodes.UltimateSDUpscale || {}; return { denoise: 0.15, @@ -395,7 +479,7 @@ export async function fetchEngineModels() { if (!listRes.ok) return []; const models = ((await listRes.json())?.models || []) .filter((m) => m.architecture === "sdxl" || m.architecture === "qwen_edit" - || m.architecture === "zimage" + || m.architecture === "zimage" || m.architecture === "krea2" // flux = the whole FLUX.1 family; edit/control/util variants don't // belong in a faithful tile pass (kontext re-renders, schnell is // step-distilled, fill/canny/depth/redux are tool models). @@ -481,6 +565,7 @@ function recommendedSamplerFor(architecture) { const arch = architecture || ""; if (arch === "flux" || arch === "flux2") return { sampler: "euler", scheduler: "simple" }; if (arch === "zimage") return { sampler: "res_multistep", scheduler: "beta" }; + if (arch === "krea2") return { sampler: "euler", scheduler: "simple" }; return { sampler: "dpmpp_2m_sde", scheduler: "karras" }; } @@ -533,6 +618,10 @@ function usduBuildDefaults(architecture, saved) { const arch = architecture || ""; if (arch === "flux" || arch === "flux2") d.cfg = 1.0; else if (arch === "zimage") d.cfg = 1.3; + // Krea 2 carries cfg natively at 1.0 (no FluxGuidance); euler/simple/0.45 + // denoise are applied in buildKrea2TileStage's defaults, not here (this dict + // only holds USDU shape knobs). + else if (arch === "krea2") d.cfg = 1.0; for (const key of Object.keys(d)) { if (saved && saved[key] !== undefined) d[key] = saved[key]; } @@ -570,13 +659,16 @@ export async function prepareUpscale(entry) { const recommended = recommendedSamplerFor(architecture); // The modal's prompt editor scopes templates/autocomplete to this when // the source engine is selected (a picked engine model overrides it). - caps.sourceModelInfo = hash ? { hash, architecture } : null; - // Z-Image Turbo is step-distilled: at low denoise ComfyUI truncates the sigma - // schedule to its low-σ tail, which the few-step field can't resolve -> blotchy - // skin. 0.7 lands back in its trained high-σ regime (clean, heavier repaint). - // Base is non-distilled and resolves a low-denoise tail fine. + caps.sourceModelInfo = hash ? { hash, architecture, family } : null; + // Krea 2 Turbo source-graft is a TILE pass: denoise stays LOW so each tile + // anchors to the source and they stitch (high denoise = each tile a full + // re-render = seams). The distillation means low-denoise tiles still carry + // some residual blotch on smooth skin — that's a Turbo limitation a tile + // routine can't escape; the non-distilled RAW model is the real clean+ + // coherent upscaler. caps.defaultDenoise = typeof settings.denoise === "number" ? settings.denoise - : (architecture === "zimage" ? (family === "zimage_base" ? 0.15 : 0.7) : 0.3); + : (architecture === "zimage" ? (family === "zimage_base" ? 0.15 : 0.7) + : architecture === "krea2" ? 0.45 : 0.3); // Recommendation chain: a USDU-specific saved value > the MODEL's own // recommended gen sampler (its config's KSampler section) > generic arch // default. The model knows itself better than our tiling default does. @@ -586,9 +678,14 @@ export async function prepareUpscale(entry) { caps.samplerAlternates = ksampler.sampler_name_options || []; caps.schedulerAlternates = ksampler.scheduler_options || []; // …but the SELECTED default is what the image was generated with, so a - // deliberate deviation carries through to the upscale. + // deliberate deviation carries through to the upscale — EXCEPT Krea 2, whose + // re-detail recipe is its own (euler is the house default). Inheriting a + // stale gen sampler from an older er_sde render would silently override that + // default; same deliberate skip as the flux2 refine below. const inherited = workflowSamplerChoice(data.workflow, combos.samplers, combos.schedulers); - caps.defaultSampler = inherited?.sampler || caps.recommendedSampler; + caps.defaultSampler = architecture === "krea2" + ? caps.recommendedSampler + : (inherited?.sampler || caps.recommendedSampler); caps.defaultScheduler = inherited?.scheduler || caps.recommendedScheduler; caps.advancedDefaults = usduBuildDefaults(architecture, settings); caps.architecture = architecture; @@ -620,7 +717,8 @@ export async function prepareUpscale(entry) { (m.architecture === "sdxl" && support.sdxl) || (m.architecture === "qwen_edit" && support.qwen) || (m.architecture === "flux" && support.flux1) - || (m.architecture === "zimage" && support.zimage)); + || (m.architecture === "zimage" && support.zimage) + || (m.architecture === "krea2" && support.krea2)); caps.enginePromptDefaults = { qwen: QWEN_ENHANCE_INSTRUCTION, sdxlFallback: SDXL_TILE_NEUTRAL_POSITIVE }; // Climb-model picker: the whole installed ESRGAN-family list, with the // empirically-settled v1 pick as the recommendation. Degradation-trained @@ -629,8 +727,15 @@ export async function prepareUpscale(entry) { caps.recommendedUpscaleModel = defaultUpscaleModelPick(caps.upscaleModelOptions); // Restore recommendation, by SOURCE: a jpeg or small input likely carries // degradation a plain GAN climb would smear (the 736px webcam eye-smear); - // clean renders gain nothing from the restore round-trip. - caps.recommendedRestore = caps.seedvr2Available + // clean renders gain nothing from the restore round-trip. A GENERATED render + // (embedded sampler) is clean by construction — a small/square one (1024²) is + // NOT degraded, so don't let the size rule false-trigger SeedVR2 on it; only + // raw imported photos (no generation workflow) get the restore recommendation. + // Matches the prior A/B: UltraSharp beats SeedVR2 on clean renders. + const srcNodes = data.workflow?.nodes || []; + const isRender = srcNodes.some((n) => n.type === "KSampler" || n.type === "KSamplerAdvanced" + || n.type === "SamplerCustom" || n.type === "SamplerCustomAdvanced"); + caps.recommendedRestore = caps.seedvr2Available && !isRender && (/\.jpe?g$/i.test(data.filename || "") || Math.max(data.width || 0, data.height || 0) < 1100) ? "seedvr2" : "none"; // DIAGNOSTIC: why the engine picker may show only "Model climb only". If @@ -1108,10 +1213,11 @@ function setNamedWidget(node, name, value) { // SeedVR2 floor: restore at trained scale, then the classic ESRGAN climb to // the same 4× target the UltraSharp floor produces, lanczos-fit to exact dims. -function buildSeedvr2FloorGraph(data, savePrefix = "", previewOnly = false, climbModel = null) { +function buildSeedvr2FloorGraph(data, savePrefix = "", previewOnly = false, climbModel = null, scale = 0) { const LG = window.LiteGraph; const workflowId = freshWorkflowId(); const graph = new LG.LGraph(structuredClone({ ...EMPTY_WORKFLOW, id: workflowId })); + graph.last_node_id = offscreenIdBase(); // render-node ids must stay off the live canvas — see offscreen-graph.js const load = LG.createNode("LoadImage"); const dit = LG.createNode("SeedVR2LoadDiTModel"); const vae = LG.createNode("SeedVR2LoadVAEModel"); @@ -1119,8 +1225,9 @@ function buildSeedvr2FloorGraph(data, savePrefix = "", previewOnly = false, clim const save = LG.createNode(previewOnly ? "PreviewImage" : "SaveImage"); if (!load || !dit || !vae || !up || !save) return null; - const targetW = (data.width || 0) * 4; - const targetH = (data.height || 0) * 4; + const eff = scale > 0 ? scale : 4; // honor the Scale slider; default to 4x when none given + const targetW = (data.width || 0) * eff; + const targetH = (data.height || 0) * eff; const targetShort = Math.min(targetW, targetH); const stageShort = targetShort > 0 ? Math.min(SEEDVR2_STAGE_MAX_SHORT_EDGE, targetShort) @@ -1447,7 +1554,11 @@ function buildFlux2RefineGraph(graph, workflowId, data, options, config) { const prefix = (options.savePrefix || "").trim() || "upscale/upscale"; if (prefixW) { prefixW.value = prefix; prefixW.callback?.(prefix); } } - if (typeof options.prompt === "string" && options.prompt.trim()) { + // A cleared prompt (empty string) is an explicit choice — override with it so + // the upscale drops the source render's character conditioning instead of + // silently re-detailing every tile with it (the "Ryu's face on a cropped arm" + // bug). Only skip when the modal passed no prompt at all (undefined). + if (typeof options.prompt === "string") { overrideGraphPrompt(graph, options.prompt); } return { graph, workflowId }; @@ -1457,14 +1568,15 @@ function buildFlux2RefineGraph(graph, workflowId, data, options, config) { // images with no usable metadata (external files, non-PromptChain workflows). // engine "seedvr2" puts a restoration stage in front; it falls back to the // plain pair if the cluster can't build (pack uninstalled mid-session). -function buildEsrganFloorGraph(data, savePrefix = "", previewOnly = false, engine = "ultrasharp", climbModel = null) { +function buildEsrganFloorGraph(data, savePrefix = "", previewOnly = false, engine = "ultrasharp", climbModel = null, scale = 0) { if (engine === "seedvr2") { - const built = buildSeedvr2FloorGraph(data, savePrefix, previewOnly, climbModel); + const built = buildSeedvr2FloorGraph(data, savePrefix, previewOnly, climbModel, scale); if (built) return built; } const LG = window.LiteGraph; const workflowId = freshWorkflowId(); const graph = new LG.LGraph(structuredClone({ ...EMPTY_WORKFLOW, id: workflowId })); + graph.last_node_id = offscreenIdBase(); // render-node ids must stay off the live canvas — see offscreen-graph.js const load = LG.createNode("LoadImage"); const loader = LG.createNode("UpscaleModelLoader"); const apply = LG.createNode("ImageUpscaleWithModel"); @@ -1480,15 +1592,34 @@ function buildEsrganFloorGraph(data, savePrefix = "", previewOnly = false, engin load.pos = [80, 200]; loader.pos = [80, 540]; apply.pos = [500, 240]; - save.pos = [780, 240]; - graph.add(load); graph.add(loader); graph.add(apply); graph.add(save); + graph.add(load); graph.add(loader); graph.add(apply); setImageWidget(load, data.input_ref); pickUpscaleModel(loader, climbModel); - const prefix = save.widgets?.find((w) => w.name === "filename_prefix"); - if (prefix) { prefix.value = savePrefix || "upscale/upscale"; prefix.callback?.(prefix.value); } load.connect(0, apply, inputIdx(apply, "image", 1)); loader.connect(0, apply, inputIdx(apply, "upscale_model", 0)); - apply.connect(0, save, 0); + + // The model upscales by its own fixed factor (e.g. 4x); a lanczos fit lets the + // Scale slider actually target a size (ESRGAN climbs to its factor, then fits + // up/down to source*scale). No scale given → keep the raw model-factor output. + let out = apply; + const w = data.width || 0, h = data.height || 0; + if (scale > 0 && w && h) { + const fit = LG.createNode("ImageScale"); + if (fit) { + fit.pos = [780, 240]; + graph.add(fit); + setNamedWidget(fit, "upscale_method", "lanczos"); + setNamedWidget(fit, "width", Math.round(w * scale)); + setNamedWidget(fit, "height", Math.round(h * scale)); + apply.connect(0, fit, inputIdx(fit, "image", 0)); + out = fit; + } + } + save.pos = [out.pos[0] + 280, 240]; + graph.add(save); + const prefix = save.widgets?.find((w) => w.name === "filename_prefix"); + if (prefix) { prefix.value = savePrefix || "upscale/upscale"; prefix.callback?.(prefix.value); } + out.connect(0, save, 0); return { graph, workflowId }; } @@ -1595,7 +1726,9 @@ function buildFlux1TileStage(graph, LG, place, opts) { let unetFile = opts.unetName || null; if (unetFile && Array.isArray(unetOpts) && !unetOpts.includes(unetFile)) unetOpts.push(unetFile); if (!unetFile) { - unetFile = unetOpts.find((o) => /krea/i.test(o)) + // /krea(?!2)/ matches the FLUX.1 "flux1-krea-dev" UNET but must NOT grab + // the separate "krea2_turbo_*" DiT (its own engine, different VAE/CLIP). + unetFile = unetOpts.find((o) => /krea(?!2)/i.test(o)) || unetOpts.find((o) => /flux1/i.test(o) && !/kontext|schnell|fill|canny|depth|redux/i.test(o)); } const clip = LG.createNode("DualCLIPLoader"); @@ -1721,6 +1854,76 @@ function buildZImageTileStage(graph, LG, place, opts) { return { usdu, sideInputIds: [unet.id, clip.id, vae.id, aura.id, posEnc.id, negEnc.id] }; } +// Krea 2 tile cluster (DiT, closest to Z-Image): UNET feeds the USDU model +// DIRECTLY — Krea 2's schedule shift is baked into the model class, so there's +// NO ModelSamplingAuraFlow. CLIP (qwen3vl_4b, type "krea2") drives a neutral +// positive + empty negative; krea2 carries cfg natively (cfg 1.0), so NO +// FluxGuidance and NO tile ControlNet. Turbo-distilled: euler/simple/8 steps. +function buildKrea2TileStage(graph, LG, place, opts) { + const reg = LG.registered_node_types || {}; + if (!reg["UltimateSDUpscaleNoUpscale"]) return null; + const unet = LG.createNode("UNETLoader"); + const unetW = unet?.widgets?.find((x) => x.name === "unet_name"); + const unetOpts = unetW?.options?.values || []; + let unetFile = opts.unetName || null; + if (unetFile && Array.isArray(unetOpts) && !unetOpts.includes(unetFile)) unetOpts.push(unetFile); + if (!unetFile) unetFile = unetOpts.find((o) => /krea2/i.test(o)); + const clip = LG.createNode("CLIPLoader"); + const clipFile = (clip?.widgets?.find((x) => x.name === "clip_name")?.options?.values || []) + .find((o) => /qwen3vl.*4b|qwen3.?vl.*4b/i.test(o)); + const vae = LG.createNode("VAELoader"); + // Use Krea 2's native VAE (qwen_image_vae) for the tile encode AND decode. A + // re-detailed section is composited back into pixels that were generated and + // decoded with qwen_image_vae; decoding the section through any other VAE + // (e.g. wan_2.1_vae) shifts its colour/texture rendition and leaves an + // unblendable seam — and encoding through it lands the tile in a latent the + // Krea 2 UNET reads slightly wrong. A ground-truth plain i2i on the same crop + // is clean with this VAE. Fall back to a Wan 2.1 VAE only if it isn't present. + const vaeOpts = vae?.widgets?.find((x) => x.name === "vae_name")?.options?.values || []; + const vaeFile = vaeOpts.find((o) => /qwen.*image.*vae/i.test(o)) + || vaeOpts.find((o) => /wan.*2[._]?1.*vae/i.test(o) && !/upscale|2x/i.test(o)); + const posEnc = LG.createNode("CLIPTextEncode"); + const negEnc = LG.createNode("CLIPTextEncode"); + const usdu = LG.createNode("UltimateSDUpscaleNoUpscale"); + if (!(unet && unetFile && clip && clipFile && vae && vaeFile && posEnc && negEnc && usdu)) return null; + place(unet, 0, 0); + place(clip, 0, 180); + place(vae, 0, 360); + place(posEnc, 300, -80); + place(negEnc, 300, 100); + place(usdu, 860, 280); + setNamedWidget(unet, "unet_name", unetFile); + setNamedWidget(unet, "weight_dtype", "default"); + setNamedWidget(clip, "clip_name", clipFile); + setNamedWidget(clip, "type", "krea2"); + setNamedWidget(vae, "vae_name", vaeFile); + setNamedWidget(posEnc, "text", opts.positiveText || SDXL_TILE_NEUTRAL_POSITIVE); + setNamedWidget(negEnc, "text", ""); + clip.connect(0, posEnc, inputIdx(posEnc, "clip", 0)); + clip.connect(0, negEnc, inputIdx(negEnc, "clip", 0)); + opts.imageNode.connect(opts.imageSlot || 0, usdu, inputIdx(usdu, "upscaled_image", 0)); + unet.connect(0, usdu, inputIdx(usdu, "model", 1)); + posEnc.connect(0, usdu, inputIdx(usdu, "positive", 2)); + negEnc.connect(0, usdu, inputIdx(usdu, "negative", 3)); + vae.connect(0, usdu, inputIdx(usdu, "vae", 4)); + setNamedWidget(usdu, "seed", Math.floor(Math.random() * 2 ** 32)); + setNamedWidget(usdu, "steps", opts.steps ?? 8); + setNamedWidget(usdu, "cfg", opts.cfg ?? 1.0); + setNamedWidget(usdu, "sampler_name", opts.sampler || "er_sde"); + setNamedWidget(usdu, "scheduler", opts.scheduler || "simple"); + setNamedWidget(usdu, "denoise", typeof opts.denoise === "number" ? opts.denoise : 0.45); + setNamedWidget(usdu, "tile_width", opts.tileWidth ?? 1024); + setNamedWidget(usdu, "tile_height", opts.tileHeight ?? 1024); + setNamedWidget(usdu, "mask_blur", opts.maskBlur ?? 32); + setNamedWidget(usdu, "tile_padding", opts.tilePadding ?? 64); + setNamedWidget(usdu, "seam_fix_mode", opts.seamFixMode || "None"); + if (opts.modeType) setNamedWidget(usdu, "mode_type", opts.modeType); + if (typeof opts.seamFixDenoise === "number") setNamedWidget(usdu, "seam_fix_denoise", opts.seamFixDenoise); + setNamedWidget(usdu, "force_uniform_tiles", true); + setNamedWidget(usdu, "tiled_decode", false); + return { usdu, sideInputIds: [unet.id, clip.id, vae.id, posEnc.id, negEnc.id] }; +} + // The modal's tuning knobs in tile-stage shape — shared by every tile engine. function tileStageOpts(options) { return { @@ -1749,13 +1952,10 @@ function buildTiledEngineGraph(data, options = {}, buildStage) { const LG = window.LiteGraph; const workflowId = freshWorkflowId(); const graph = new LG.LGraph(structuredClone({ ...EMPTY_WORKFLOW, id: workflowId })); + graph.last_node_id = offscreenIdBase(); // render-node ids must stay off the live canvas — see offscreen-graph.js const placeAt = (node, x, y) => { node.pos = [x, y]; graph.add(node); }; - const load = LG.createNode("LoadImage"); const save = LG.createNode(options.previewOnly ? "PreviewImage" : "SaveImage"); - if (!load || !save) return null; - if (data.filename) load.title = data.filename; - placeAt(load, 80, 200); - setImageWidget(load, data.input_ref); + if (!save) return null; const w = data.width || 0; const h = data.height || 0; @@ -1766,54 +1966,74 @@ function buildTiledEngineGraph(data, options = {}, buildStage) { const targetW = Math.round(w * chosenScale) || 2048; const targetH = Math.round(h * chosenScale) || 2048; - let imageSource = load; - let needsModelClimb = chosenScale > 1.02; - // Optional SeedVR2 restoration in front of the climb: degraded sources - // (webcam blur, jpeg) smear through a plain GAN climb — A/B'd on a 736px - // jpeg, the eye region smeared via UltraSharp but came back clean when - // SeedVR2 rebuilt structure at its trained scale first. The tile CN then - // pins restored structure instead of pinning the smear. - if (options.climbStage === "seedvr2" && LG.registered_node_types?.["SeedVR2VideoUpscaler"]) { - const dit = LG.createNode("SeedVR2LoadDiTModel"); - const seedvrVae = LG.createNode("SeedVR2LoadVAEModel"); - const restore = LG.createNode("SeedVR2VideoUpscaler"); - if (dit && seedvrVae && restore) { - placeAt(dit, 80, 460); - placeAt(seedvrVae, 80, 660); - placeAt(restore, 400, 200); - const ditModels = dit.widgets?.find((x) => x.name === "model")?.options?.values || []; - if (ditModels.length) setNamedWidget(dit, "model", pickSeedvr2DitModel(ditModels)); - const targetShort = Math.min(targetW, targetH); - const stageShort = Math.min(SEEDVR2_STAGE_MAX_SHORT_EDGE, targetShort); - setNamedWidget(restore, "resolution", stageShort); - setNamedWidget(restore, "batch_size", 1); - setNamedWidget(restore, "input_noise_scale", SEEDVR2_INPUT_NOISE); - load.connect(0, restore, inputIdx(restore, "image", 0)); - dit.connect(0, restore, inputIdx(restore, "dit", 1)); - seedvrVae.connect(0, restore, inputIdx(restore, "vae", 2)); - imageSource = restore; - needsModelClimb = targetShort > stageShort; + // `fit` is the image (already at final/target dims) the engine stage tiles. + // Normally we climb the source here (optional SeedVR2 restore → UltraSharp → + // exact-fit lanczos). On a cache hit the enlarge already ran in a prior + // pre-pass, so load that saved base directly and skip the whole climb. + let fit; + if (options.preEnlargedRef) { + fit = LG.createNode("LoadImage"); + if (!fit) return null; + fit.title = "enlarged base (cached)"; + placeAt(fit, 740, 200); + setImageWidget(fit, options.preEnlargedRef); + } else { + const load = LG.createNode("LoadImage"); + if (!load) return null; + if (data.filename) load.title = data.filename; + placeAt(load, 80, 200); + setImageWidget(load, data.input_ref); + + let imageSource = load; + let needsModelClimb = chosenScale > 1.02; + // Optional SeedVR2 restoration in front of the climb: degraded sources + // (webcam blur, jpeg) smear through a plain GAN climb — A/B'd on a 736px + // jpeg, the eye region smeared via UltraSharp but came back clean when + // SeedVR2 rebuilt structure at its trained scale first. The tile CN then + // pins restored structure instead of pinning the smear. + if (options.climbStage === "seedvr2" && LG.registered_node_types?.["SeedVR2VideoUpscaler"]) { + const dit = LG.createNode("SeedVR2LoadDiTModel"); + const seedvrVae = LG.createNode("SeedVR2LoadVAEModel"); + const restore = LG.createNode("SeedVR2VideoUpscaler"); + if (dit && seedvrVae && restore) { + placeAt(dit, 80, 460); + placeAt(seedvrVae, 80, 660); + placeAt(restore, 400, 200); + const ditModels = dit.widgets?.find((x) => x.name === "model")?.options?.values || []; + if (ditModels.length) setNamedWidget(dit, "model", pickSeedvr2DitModel(ditModels)); + const targetShort = Math.min(targetW, targetH); + const stageShort = Math.min(SEEDVR2_STAGE_MAX_SHORT_EDGE, targetShort); + setNamedWidget(restore, "resolution", stageShort); + setNamedWidget(restore, "batch_size", 1); + setNamedWidget(restore, "input_noise_scale", SEEDVR2_INPUT_NOISE); + setNamedWidget(restore, "seed", 42); // match buildEnlargePrompt so the cached base == this fused base + load.connect(0, restore, inputIdx(restore, "image", 0)); + dit.connect(0, restore, inputIdx(restore, "dit", 1)); + seedvrVae.connect(0, restore, inputIdx(restore, "vae", 2)); + imageSource = restore; + needsModelClimb = targetShort > stageShort; + } } + if (needsModelClimb) { + const upLoader = LG.createNode("UpscaleModelLoader"); + const upApply = LG.createNode("ImageUpscaleWithModel"); + if (!upLoader || !upApply) return null; + placeAt(upLoader, 80, 880); + placeAt(upApply, 460, 460); + pickUpscaleModel(upLoader, options.climbModel); + imageSource.connect(0, upApply, inputIdx(upApply, "image", 1)); + upLoader.connect(0, upApply, inputIdx(upApply, "upscale_model", 0)); + imageSource = upApply; + } + // The tile pass runs AT final resolution — exact-fit first, then re-detail. + fit = LG.createNode("ImageScale"); + if (!fit) return null; + placeAt(fit, 740, 200); + setNamedWidget(fit, "upscale_method", "lanczos"); + setNamedWidget(fit, "width", targetW); + setNamedWidget(fit, "height", targetH); + imageSource.connect(0, fit, inputIdx(fit, "image", 0)); } - if (needsModelClimb) { - const upLoader = LG.createNode("UpscaleModelLoader"); - const upApply = LG.createNode("ImageUpscaleWithModel"); - if (!upLoader || !upApply) return null; - placeAt(upLoader, 80, 880); - placeAt(upApply, 460, 460); - pickUpscaleModel(upLoader, options.climbModel); - imageSource.connect(0, upApply, inputIdx(upApply, "image", 1)); - upLoader.connect(0, upApply, inputIdx(upApply, "upscale_model", 0)); - imageSource = upApply; - } - // The tile pass runs AT final resolution — exact-fit first, then re-detail. - const fit = LG.createNode("ImageScale"); - if (!fit) return null; - placeAt(fit, 740, 200); - setNamedWidget(fit, "upscale_method", "lanczos"); - setNamedWidget(fit, "width", targetW); - setNamedWidget(fit, "height", targetH); - imageSource.connect(0, fit, inputIdx(fit, "image", 0)); const stage = buildStage(graph, LG, (node, dx, dy) => placeAt(node, 1040 + dx, 480 + dy), fit); @@ -1867,6 +2087,19 @@ function buildZImageEngineGraph(data, options = {}) { })); } +// "Re-detail with Krea 2": picked Krea 2 Turbo UNET in the cfg-1/euler/simple +// 8-step regime, model wired straight to the USDU (no ModelSampling, no +// FluxGuidance, no tile CN). 0.45 denoise re-details while holding structure. +function buildKrea2EngineGraph(data, options = {}) { + return buildTiledEngineGraph(data, options, (graph, LG, place, fit) => + buildKrea2TileStage(graph, LG, place, { + imageNode: fit, + unetName: options.engineModel?.filename || null, + positiveText: (options.prompt || "").trim() || SDXL_TILE_NEUTRAL_POSITIVE, + ...tileStageOpts(options), + })); +} + // Standalone Qwen-Image-Edit engine: instruction-driven whole-frame re-render // at ~2MP (the model's working size — bigger grows plastic skin, and the // stock encoder downsamples its conditioning copy regardless), then the @@ -1879,6 +2112,7 @@ function buildQwenEditEngineGraph(data, options = {}) { || !reg["ModelSamplingAuraFlow"] || !reg["CFGNorm"]) return null; const workflowId = freshWorkflowId(); const graph = new LG.LGraph(structuredClone({ ...EMPTY_WORKFLOW, id: workflowId })); + graph.last_node_id = offscreenIdBase(); // render-node ids must stay off the live canvas — see offscreen-graph.js const placeAt = (node, x, y) => { node.pos = [x, y]; graph.add(node); }; const load = LG.createNode("LoadImage"); @@ -2022,14 +2256,17 @@ export async function buildUpscaleGraph(prepared, options = {}) { const engine = options.engine || "source"; // Picker engines build standalone graphs — any source image works, // graftable or not. - if (engine === "sdxl-ckpt" || engine === "qwen-edit" || engine === "flux1-unet" || engine === "zimage-unet") { + if (engine === "sdxl-ckpt" || engine === "qwen-edit" || engine === "flux1-unet" + || engine === "zimage-unet" || engine === "krea2-unet") { const built = engine === "sdxl-ckpt" ? buildSdxlCkptEngineGraph(data, { ...options, savePrefix }) : engine === "flux1-unet" ? buildFlux1EngineGraph(data, { ...options, savePrefix }) : engine === "zimage-unet" ? buildZImageEngineGraph(data, { ...options, savePrefix }) - : buildQwenEditEngineGraph(data, { ...options, savePrefix }); + : engine === "krea2-unet" + ? buildKrea2EngineGraph(data, { ...options, savePrefix }) + : buildQwenEditEngineGraph(data, { ...options, savePrefix }); if (built) return built; toast("warn", "Couldn't build that upscale engine — using a plain model upscale instead."); return buildEsrganFloorGraph(data, savePrefix, options.previewOnly, "ultrasharp", options.climbModel); @@ -2037,7 +2274,11 @@ export async function buildUpscaleGraph(prepared, options = {}) { // Plain engines are explicit main-engine choices now; non-graftable // images keep them as their only option. if (!caps.graftable || engine === "ultrasharp" || engine === "seedvr2") { - return buildEsrganFloorGraph(data, savePrefix, options.previewOnly, engine, options.climbModel); + const longest = Math.max(data.width || 0, data.height || 0); + const scale = typeof options.upscaleBy === "number" && options.upscaleBy >= 1 + ? options.upscaleBy + : longest > 0 ? Math.min(8, Math.max(1, Math.round((4096 / longest) * 100) / 100)) : 2; + return buildEsrganFloorGraph(data, savePrefix, options.previewOnly, engine, options.climbModel, scale); } const mode = options.mode || caps.defaultMode || "prompt"; @@ -2069,6 +2310,7 @@ export async function buildUpscaleGraph(prepared, options = {}) { return buildEsrganFloorGraph(data, savePrefix, options.previewOnly, options.engine, options.climbModel); } if (!pcNode || !injectUpscaler(pcNode, config, { regional: mode === "regional", pinFlux2TileModel: true })) { + console.warn(`[PromptChain][upscale] source graft -> PLAIN fallback: pcNode=${pcNode?.id ?? "NONE"} arch=${config?.architecture || caps.architecture} graphTypes=[${graph._nodes.map((n) => n.comfyClass).join(",")}]`); toast("warn", "No attachment point in this image's workflow — using a plain model upscale instead."); return buildEsrganFloorGraph(data, savePrefix, options.previewOnly, options.engine, options.climbModel); } @@ -2076,6 +2318,25 @@ export async function buildUpscaleGraph(prepared, options = {}) { } else if (mode === "regional") { ensureRegionalConditioning(graph, LG, usNode); } + // Krea 2 renders carry the Qwen-Image VAE, whose decoder bakes a periodic + // grid/scale artifact into every tile USDU re-decodes. The Wan 2.1 VAE + // shares the SAME frozen encoder (tiles encode to an identical Krea2- + // compatible latent) but its decoder has no grid artifact — repoint the + // grafted decode to it so the source-model upscale comes out clean, same as + // the standalone Krea2 tile engine. Excludes the 2x variant (12-ch/2x output + // would break tiling); only fires for a Krea 2 source on the Qwen VAE. + const isKrea2Source = caps.architecture === "krea2" + || graph._nodes.some((n) => n.comfyClass === "CLIPLoader" + && n.widgets?.find((w) => w.name === "type")?.value === "krea2"); + if (isKrea2Source) { + for (const vl of graph._nodes.filter((n) => n.comfyClass === "VAELoader")) { + const vaeW = vl.widgets?.find((w) => w.name === "vae_name"); + if (!vaeW || !/qwen.*image.*vae/i.test(vaeW.value || "")) continue; + const wan = (vaeW.options?.values || []) + .find((o) => /wan.*2[._]?1.*vae/i.test(o) && !/upscale|2x/i.test(o)); + if (wan) { vaeW.value = wan; vaeW.callback?.(wan); } + } + } if (mode === "regional" && usNode) { // The installed USDU port's crop_mask only remaps the FIRST crop region // of a batch — batched tiles would smear every region mask across the diff --git a/js/main.js b/js/main.js index ea0005e..05431af 100644 --- a/js/main.js +++ b/js/main.js @@ -23,6 +23,7 @@ import { createHighlightExtension } from "./lib/active-label-highlight.js"; import { createWildcardBadgeExtension } from "./lib/wildcard-badge.js"; import { updateDynamicOutputs, initializeOutputSlots, healRegionsLinks, invalidateFluxCache, autoConnectToSampler } from "./lib/dynamic-outputs.js"; import { setupIterateListeners, advanceAndCascade } from "./lib/iterate-hierarchy.js"; +import { setupShiftSync } from "./lib/krea2-shift-sync.js"; import { getSwitchOptions, countLabeledOptions } from "./lib/label-utils.js"; import { getWorkflowId, installSaveInterceptor, healWorkflowIdDrift } from "./lib/workflow-id.js"; import { recordGeneration, fetchWorkflowImages, fetchWorkflowCount, subscribe as subscribeHistory, invalidateCache, getCachedImages, externallyTrackedPrompts, isExternalPrompt } from "./lib/history.js"; @@ -366,8 +367,19 @@ function setupImagePreviewListeners() { // here describe the user's active graph, not that prompt's. if (detail.prompt_id && externallyTrackedPrompts.has(detail.prompt_id)) return; const nodeId = Number(detail.node); - const pcNodes = _outputToPC.get(nodeId); - if (!pcNodes?.length) return; + let pcNodes = _outputToPC.get(nodeId); + let wfId = _executionWorkflowId; + if (!pcNodes?.length) { + // The SaveImage didn't map to a PromptChain node via the execution_start + // graph scan (trace miss / subgraph / workflow-id drift). Don't silently + // drop the generation — diagnose, then attribute it to the active graph's + // PromptChain node so the generation panel still catches it. + if (!wfId) wfId = getWorkflowId(); + const fallbackPC = (app.graph?._nodes || []).find((n) => n.comfyClass === NODE_TYPE); + console.warn(`[PromptChain][record] unmapped output node=${detail.node}->${nodeId} mapKeys=[${[..._outputToPC.keys()]}] wfId=${wfId} graphNodes=${app.graph?._nodes?.length} rootGraph=${!!app.rootGraph} fallbackPC=${fallbackPC?.id ?? "none"}`); + if (!fallbackPC || !wfId) return; + pcNodes = [fallbackPC]; + } const img = detail.output.images[detail.output.images.length - 1]; const params = new URLSearchParams({ @@ -404,7 +416,7 @@ function setupImagePreviewListeners() { // the panel awaits it on filename-click to resolve the exact hash even // if the click lands before the POST response writes the entry to cache. const genPromise = recordGeneration( - _executionWorkflowId, + wfId, img.filename, img.subfolder || "", img.type || "output", @@ -882,6 +894,7 @@ app.registerExtension({ setup() { setupIterateListeners(); + setupShiftSync(); // krea2 RAW: keep the flow-shift node's w/h matched to render size setupImagePreviewListeners(); checkOnboarding(); loadSvelteNode(); // preload Svelte module in background diff --git a/pyproject.toml b/pyproject.toml index e731838..33e2eb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [project] name = "comfyui-promptchain" -version = "0.1.3" +version = "0.1.4" description = "Prompt-authoring and image-iteration suite for ComfyUI: structured prompts, 3D posing, model awareness, regional control, and layered editing." license = { file = "LICENSE" } requires-python = ">=3.10" diff --git a/svelte-src/components/EditModal.svelte b/svelte-src/components/EditModal.svelte index 07c2854..6f9464e 100644 --- a/svelte-src/components/EditModal.svelte +++ b/svelte-src/components/EditModal.svelte @@ -316,8 +316,9 @@ // clicks skip the re-flatten AND let the server reuse its encoder cache. let flatBlobCache = null; // { blob, key } function flatKey() { - // histIndex covers committed pixel edits; the rest covers uncommitted - // visibility/opacity/blend toggles (they never enter history) + // histIndex covers committed edits; the per-layer suffix also keys on + // visibility/opacity/blend/offset so the cache stays correct mid-drag, + // before an opacity release commits. return `${histIndex}|${activeIndex}|${layers.map((L) => `${L.id}:${L.visible}:${L.opacity}:${L.blend || ""}:${L.ox || 0}:${L.oy || 0}`).join(",")}`; } async function flattenedBlobCached() { @@ -3456,8 +3457,12 @@ loadActiveBitmap(); commitAction("Duplicate Layer"); } - function setLayerVisible(i, v) { layers[i] = { ...layers[i], visible: v }; } - function setLayerOpacity(i, o) { layers[i] = { ...layers[i], opacity: o }; } + function setLayerVisible(i, v) { layers[i] = { ...layers[i], visible: v }; commitAction("Visibility"); } + // Opacity drags fire on every tick — mutate in place for the live preview and + // commit ONCE on release (see the slider's onpointerdown/onchange) so the + // change is undoable and dirties the doc without flooding the history ring. + let opacityDragStart = null; + function setLayerOpacity(i, o) { if (layers[i].opacity === o) return; layers[i] = { ...layers[i], opacity: o }; } // Blend modes — CSS mix-blend-mode keywords double as canvas // globalCompositeOperation names, so the live stacked-canvas view and every @@ -3523,7 +3528,7 @@ function commitRename() { const i = layers.findIndex((L) => L.id === renamingId); const name = renameText.trim(); - if (i >= 0 && name && name !== layers[i].name) layers[i] = { ...layers[i], name }; + if (i >= 0 && name && name !== layers[i].name) { layers[i] = { ...layers[i], name }; commitAction("Rename Layer"); } renamingId = null; } @@ -3546,6 +3551,7 @@ layers = rest; activeIndex = layers.findIndex((L) => L.id === activeId); dragId = null; + commitAction("Reorder Layers"); } // Composite a set of layer indices (z-order) into one ImageData. @@ -4684,7 +4690,10 @@ ondblclick={(e) => { e.stopPropagation(); startRename(L); }}>{L.name}{#if L.mask}▦{/if}{#if layerBlendCss(L)}{L.blend}{/if} {/if} setLayerOpacity(i, +e.currentTarget.value)} + value={L.opacity} + onpointerdown={() => { opacityDragStart = L.opacity; }} + oninput={(e) => setLayerOpacity(i, +e.currentTarget.value)} + onchange={(e) => { if (opacityDragStart === null || +e.currentTarget.value !== opacityDragStart) commitAction("Opacity"); opacityDragStart = null; }} onclick={(e) => e.stopPropagation()} title="Opacity" />
{/each} diff --git a/svelte-src/components/ImageViewer.svelte b/svelte-src/components/ImageViewer.svelte index 389c166..00d9d70 100644 --- a/svelte-src/components/ImageViewer.svelte +++ b/svelte-src/components/ImageViewer.svelte @@ -196,7 +196,16 @@ .then((d) => { editDocLayers = d?.docs || {}; }) .catch(() => {}); } - $effect(() => { if (fetchApi) refreshEditDocs(); }); + // A "Save edits" inside the editor writes the sidecar and dispatches this + // event; without the listener the viewer's own ▦ layers badge stayed stale + // until remount (only the gallery listened), so an in-place save looked like + // it did nothing. + $effect(() => { + if (fetchApi) refreshEditDocs(); + const onEditDocsChanged = () => refreshEditDocs(); + window.addEventListener("promptchain:edit-docs-changed", onEditDocsChanged); + return () => window.removeEventListener("promptchain:edit-docs-changed", onEditDocsChanged); + }); // lineage state let lineageData = $state(null); @@ -1173,7 +1182,10 @@ showNewGenBanner(state.elapsedSec); viewUpscaleResult(state.resultHash); } else if (state.phase === "cancelled") { - cancelUpscale(); // back to the viewer exactly as it was + // Back to the setup form with settings intact — don't kill the whole + // modal on a cancelled run (matches inpaint/repose/extend/smooth). The + // explicit Close button still calls cancelUpscale for a full exit. + upscaleProgress = null; } }); return; @@ -2946,7 +2958,7 @@ height={editPrepared?.data?.height || 0} filename={editPrepared?.data?.filename || ""} caps={editPrepared?.caps} - editDocHash={editPrepared ? (displayedHash || "") : ""} + editDocHash={editPrepared && /^[0-9a-f]{64}$/.test(displayedHash || "") ? displayedHash : ""} {fetchApi} {apiURL} onSave={(blob, prefix) => onEditSave(editPrepared, blob, prefix)} diff --git a/svelte-src/components/InpaintModal.svelte b/svelte-src/components/InpaintModal.svelte index f827bed..0a3a6b2 100644 --- a/svelte-src/components/InpaintModal.svelte +++ b/svelte-src/components/InpaintModal.svelte @@ -46,6 +46,8 @@ let denoise = $state(0.5); let sampler = $state(""); let scheduler = $state(""); + let steps = $state(0); // pre-filled to the engine's recipe on open (engineRecipe); 0 = inherit (source) + let cfg = $state(0); // pre-filled to the engine's recipe on open; 0 = inherit (source) // Mask shaping (Advanced): grow dilates the painted mask, feather softens its // edge — both forwarded to PromptChain_MaskedDetail. let grow = $state(0); @@ -76,11 +78,12 @@ let engineSel = $state("source"); let engineEntry = $derived(caps?.engineModels?.find((m) => m.hash === engineSel) || null); let engineKind = $derived(engineEntry - ? (engineEntry.architecture === "qwen_edit" ? "qwen" : engineEntry.architecture === "flux" ? "flux1" : "sdxl") + ? (engineEntry.architecture === "qwen_edit" ? "qwen" : engineEntry.architecture === "flux" ? "flux1" : engineEntry.architecture === "krea2" ? "krea2" : "sdxl") : "source"); let engineGroups = $derived({ sdxl: (caps?.engineModels || []).filter((m) => m.architecture === "sdxl"), flux1: (caps?.engineModels || []).filter((m) => m.architecture === "flux"), + krea2: (caps?.engineModels || []).filter((m) => m.architecture === "krea2"), qwen: (caps?.engineModels || []).filter((m) => m.architecture === "qwen_edit"), }); // SearchableSelect feed — same rows/groups the native