diff --git a/docker/Dockerfile b/docker/Dockerfile index 864bc5eb60..7ea078af0d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -55,6 +55,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \ FROM node:20-slim AS web-builder ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" +RUN corepack use pnpm@8.x RUN corepack enable WORKDIR /build diff --git a/invokeai/app/api/routers/model_manager.py b/invokeai/app/api/routers/model_manager.py index f73b7a86b1..d19b028cb6 100644 --- a/invokeai/app/api/routers/model_manager.py +++ b/invokeai/app/api/routers/model_manager.py @@ -6,7 +6,7 @@ import pathlib import traceback from copy import deepcopy from tempfile import TemporaryDirectory -from typing import Any, Dict, List, Optional, Type +from typing import List, Optional, Type from fastapi import Body, Path, Query, Response, UploadFile from fastapi.responses import FileResponse, HTMLResponse @@ -430,13 +430,11 @@ async def delete_model_image( async def install_model( source: str = Query(description="Model source to install, can be a local path, repo_id, or remote URL"), inplace: Optional[bool] = Query(description="Whether or not to install a local model in place", default=False), - # TODO(MM2): Can we type this? - config: Optional[Dict[str, Any]] = Body( - description="Dict of fields that override auto-probed values in the model config record, such as name, description and prediction_type ", - default=None, + access_token: Optional[str] = Query(description="access token for the remote resource", default=None), + config: ModelRecordChanges = Body( + description="Object containing fields that override auto-probed values in the model config record, such as name, description and prediction_type ", example={"name": "string", "description": "string"}, ), - access_token: Optional[str] = None, ) -> ModelInstallJob: """Install a model using a string identifier. @@ -451,8 +449,9 @@ async def install_model( - model/name:fp16:path/to/model.safetensors - model/name::path/to/model.safetensors - `config` is an optional dict containing model configuration values that will override - the ones that are probed automatically. + `config` is a ModelRecordChanges object. Fields in this object will override + the ones that are probed automatically. Pass an empty object to accept + all the defaults. `access_token` is an optional access token for use with Urls that require authentication. @@ -737,7 +736,7 @@ async def convert_model( # write the converted file to the convert path raw_model = converted_model.model assert hasattr(raw_model, "save_pretrained") - raw_model.save_pretrained(convert_path) + raw_model.save_pretrained(convert_path) # type: ignore assert convert_path.exists() # temporarily rename the original safetensors file so that there is no naming conflict @@ -750,12 +749,12 @@ async def convert_model( try: new_key = installer.install_path( convert_path, - config={ - "name": original_name, - "description": model_config.description, - "hash": model_config.hash, - "source": model_config.source, - }, + config=ModelRecordChanges( + name=original_name, + description=model_config.description, + hash=model_config.hash, + source=model_config.source, + ), ) except Exception as e: logger.error(str(e)) diff --git a/invokeai/app/invocations/denoise_latents.py b/invokeai/app/invocations/denoise_latents.py index c502234e5e..560bc9003c 100644 --- a/invokeai/app/invocations/denoise_latents.py +++ b/invokeai/app/invocations/denoise_latents.py @@ -39,7 +39,7 @@ from invokeai.backend.ip_adapter.ip_adapter import IPAdapter from invokeai.backend.lora import LoRAModelRaw from invokeai.backend.model_manager import BaseModelType, ModelVariantType from invokeai.backend.model_patcher import ModelPatcher -from invokeai.backend.stable_diffusion import PipelineIntermediateState, set_seamless +from invokeai.backend.stable_diffusion import PipelineIntermediateState from invokeai.backend.stable_diffusion.denoise_context import DenoiseContext, DenoiseInputs from invokeai.backend.stable_diffusion.diffusers_pipeline import ( ControlNetData, @@ -58,9 +58,14 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ( from invokeai.backend.stable_diffusion.diffusion.custom_atttention import CustomAttnProcessor2_0 from invokeai.backend.stable_diffusion.diffusion_backend import StableDiffusionBackend from invokeai.backend.stable_diffusion.extension_callback_type import ExtensionCallbackType +from invokeai.backend.stable_diffusion.extensions.controlnet import ControlNetExt +from invokeai.backend.stable_diffusion.extensions.freeu import FreeUExt from invokeai.backend.stable_diffusion.extensions.inpaint import InpaintExt from invokeai.backend.stable_diffusion.extensions.inpaint_model import InpaintModelExt from invokeai.backend.stable_diffusion.extensions.preview import PreviewExt +from invokeai.backend.stable_diffusion.extensions.rescale_cfg import RescaleCFGExt +from invokeai.backend.stable_diffusion.extensions.seamless import SeamlessExt +from invokeai.backend.stable_diffusion.extensions.t2i_adapter import T2IAdapterExt from invokeai.backend.stable_diffusion.extensions_manager import ExtensionsManager from invokeai.backend.stable_diffusion.schedulers import SCHEDULER_MAP from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_NAME_VALUES @@ -465,6 +470,65 @@ class DenoiseLatentsInvocation(BaseInvocation): return controlnet_data + @staticmethod + def parse_controlnet_field( + exit_stack: ExitStack, + context: InvocationContext, + control_input: ControlField | list[ControlField] | None, + ext_manager: ExtensionsManager, + ) -> None: + # Normalize control_input to a list. + control_list: list[ControlField] + if isinstance(control_input, ControlField): + control_list = [control_input] + elif isinstance(control_input, list): + control_list = control_input + elif control_input is None: + control_list = [] + else: + raise ValueError(f"Unexpected control_input type: {type(control_input)}") + + for control_info in control_list: + model = exit_stack.enter_context(context.models.load(control_info.control_model)) + ext_manager.add_extension( + ControlNetExt( + model=model, + image=context.images.get_pil(control_info.image.image_name), + weight=control_info.control_weight, + begin_step_percent=control_info.begin_step_percent, + end_step_percent=control_info.end_step_percent, + control_mode=control_info.control_mode, + resize_mode=control_info.resize_mode, + ) + ) + + @staticmethod + def parse_t2i_adapter_field( + exit_stack: ExitStack, + context: InvocationContext, + t2i_adapters: Optional[Union[T2IAdapterField, list[T2IAdapterField]]], + ext_manager: ExtensionsManager, + ) -> None: + if t2i_adapters is None: + return + + # Handle the possibility that t2i_adapters could be a list or a single T2IAdapterField. + if isinstance(t2i_adapters, T2IAdapterField): + t2i_adapters = [t2i_adapters] + + for t2i_adapter_field in t2i_adapters: + ext_manager.add_extension( + T2IAdapterExt( + node_context=context, + model_id=t2i_adapter_field.t2i_adapter_model, + image=context.images.get_pil(t2i_adapter_field.image.image_name), + weight=t2i_adapter_field.weight, + begin_step_percent=t2i_adapter_field.begin_step_percent, + end_step_percent=t2i_adapter_field.end_step_percent, + resize_mode=t2i_adapter_field.resize_mode, + ) + ) + def prep_ip_adapter_image_prompts( self, context: InvocationContext, @@ -773,6 +837,18 @@ class DenoiseLatentsInvocation(BaseInvocation): ext_manager.add_extension(PreviewExt(step_callback)) + ### cfg rescale + if self.cfg_rescale_multiplier > 0: + ext_manager.add_extension(RescaleCFGExt(self.cfg_rescale_multiplier)) + + ### freeu + if self.unet.freeu_config: + ext_manager.add_extension(FreeUExt(self.unet.freeu_config)) + + ### seamless + if self.unet.seamless_axes: + ext_manager.add_extension(SeamlessExt(self.unet.seamless_axes)) + ### inpaint mask, masked_latents, is_gradient_mask = self.prep_inpaint_mask(context, latents) # NOTE: We used to identify inpainting models by inpecting the shape of the loaded UNet model weights. Now we @@ -788,7 +864,6 @@ class DenoiseLatentsInvocation(BaseInvocation): latents = latents.to(device=device, dtype=dtype) if noise is not None: noise = noise.to(device=device, dtype=dtype) - denoise_ctx = DenoiseContext( inputs=DenoiseInputs( orig_latents=latents, @@ -804,22 +879,31 @@ class DenoiseLatentsInvocation(BaseInvocation): scheduler=scheduler, ) - # ext: t2i/ip adapter - ext_manager.run_callback(ExtensionCallbackType.SETUP, denoise_ctx) + # context for loading additional models + with ExitStack() as exit_stack: + # later should be smth like: + # for extension_field in self.extensions: + # ext = extension_field.to_extension(exit_stack, context, ext_manager) + # ext_manager.add_extension(ext) + self.parse_controlnet_field(exit_stack, context, self.control, ext_manager) + self.parse_t2i_adapter_field(exit_stack, context, self.t2i_adapter, ext_manager) - unet_info = context.models.load(self.unet.unet) - assert isinstance(unet_info.model, UNet2DConditionModel) - with ( - unet_info.model_on_device() as (model_state_dict, unet), - ModelPatcher.patch_unet_attention_processor(unet, denoise_ctx.inputs.attention_processor_cls), - # ext: controlnet - ext_manager.patch_extensions(unet), - # ext: freeu, seamless, ip adapter, lora - ext_manager.patch_unet(model_state_dict, unet), - ): - sd_backend = StableDiffusionBackend(unet, scheduler) - denoise_ctx.unet = unet - result_latents = sd_backend.latents_from_embeddings(denoise_ctx, ext_manager) + # ext: t2i/ip adapter + ext_manager.run_callback(ExtensionCallbackType.SETUP, denoise_ctx) + + unet_info = context.models.load(self.unet.unet) + assert isinstance(unet_info.model, UNet2DConditionModel) + with ( + unet_info.model_on_device() as (cached_weights, unet), + ModelPatcher.patch_unet_attention_processor(unet, denoise_ctx.inputs.attention_processor_cls), + # ext: controlnet + ext_manager.patch_extensions(denoise_ctx), + # ext: freeu, seamless, ip adapter, lora + ext_manager.patch_unet(unet, cached_weights), + ): + sd_backend = StableDiffusionBackend(unet, scheduler) + denoise_ctx.unet = unet + result_latents = sd_backend.latents_from_embeddings(denoise_ctx, ext_manager) # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 result_latents = result_latents.detach().to("cpu") @@ -882,7 +966,7 @@ class DenoiseLatentsInvocation(BaseInvocation): ExitStack() as exit_stack, unet_info.model_on_device() as (model_state_dict, unet), ModelPatcher.apply_freeu(unet, self.unet.freeu_config), - set_seamless(unet, self.unet.seamless_axes), # FIXME + SeamlessExt.static_patch_model(unet, self.unet.seamless_axes), # FIXME # Apply the LoRA after unet has been moved to its target device for faster patching. ModelPatcher.apply_lora_unet( unet, diff --git a/invokeai/app/invocations/latents_to_image.py b/invokeai/app/invocations/latents_to_image.py index cc8a9c44a3..35b8483f2c 100644 --- a/invokeai/app/invocations/latents_to_image.py +++ b/invokeai/app/invocations/latents_to_image.py @@ -24,7 +24,7 @@ from invokeai.app.invocations.fields import ( from invokeai.app.invocations.model import VAEField from invokeai.app.invocations.primitives import ImageOutput from invokeai.app.services.shared.invocation_context import InvocationContext -from invokeai.backend.stable_diffusion import set_seamless +from invokeai.backend.stable_diffusion.extensions.seamless import SeamlessExt from invokeai.backend.stable_diffusion.vae_tiling import patch_vae_tiling_params from invokeai.backend.util.devices import TorchDevice @@ -59,7 +59,7 @@ class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithBoard): vae_info = context.models.load(self.vae.vae) assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny)) - with set_seamless(vae_info.model, self.vae.seamless_axes), vae_info as vae: + with SeamlessExt.static_patch_model(vae_info.model, self.vae.seamless_axes), vae_info as vae: assert isinstance(vae, (AutoencoderKL, AutoencoderTiny)) latents = latents.to(vae.device) if self.fp32: diff --git a/invokeai/app/invocations/spandrel_image_to_image.py b/invokeai/app/invocations/spandrel_image_to_image.py index bbe31af644..ae4f48ef77 100644 --- a/invokeai/app/invocations/spandrel_image_to_image.py +++ b/invokeai/app/invocations/spandrel_image_to_image.py @@ -1,3 +1,5 @@ +from typing import Callable + import numpy as np import torch from PIL import Image @@ -21,7 +23,7 @@ from invokeai.backend.tiles.tiles import calc_tiles_min_overlap from invokeai.backend.tiles.utils import TBLR, Tile -@invocation("spandrel_image_to_image", title="Image-to-Image", tags=["upscale"], category="upscale", version="1.1.0") +@invocation("spandrel_image_to_image", title="Image-to-Image", tags=["upscale"], category="upscale", version="1.3.0") class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard): """Run any spandrel image-to-image model (https://github.com/chaiNNer-org/spandrel).""" @@ -35,7 +37,8 @@ class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard): default=512, description="The tile size for tiled image-to-image. Set to 0 to disable tiling." ) - def _scale_tile(self, tile: Tile, scale: int) -> Tile: + @classmethod + def scale_tile(cls, tile: Tile, scale: int) -> Tile: return Tile( coords=TBLR( top=tile.coords.top * scale, @@ -51,20 +54,22 @@ class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard): ), ) - @torch.inference_mode() - def invoke(self, context: InvocationContext) -> ImageOutput: - # Images are converted to RGB, because most models don't support an alpha channel. In the future, we may want to - # revisit this. - image = context.images.get_pil(self.image.image_name, mode="RGB") - + @classmethod + def upscale_image( + cls, + image: Image.Image, + tile_size: int, + spandrel_model: SpandrelImageToImageModel, + is_canceled: Callable[[], bool], + ) -> Image.Image: # Compute the image tiles. - if self.tile_size > 0: + if tile_size > 0: min_overlap = 20 tiles = calc_tiles_min_overlap( image_height=image.height, image_width=image.width, - tile_height=self.tile_size, - tile_width=self.tile_size, + tile_height=tile_size, + tile_width=tile_size, min_overlap=min_overlap, ) else: @@ -85,60 +90,164 @@ class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard): # Prepare input image for inference. image_tensor = SpandrelImageToImageModel.pil_to_tensor(image) - # Load the model. - spandrel_model_info = context.models.load(self.image_to_image_model) + # Scale the tiles for re-assembling the final image. + scale = spandrel_model.scale + scaled_tiles = [cls.scale_tile(tile, scale=scale) for tile in tiles] + + # Prepare the output tensor. + _, channels, height, width = image_tensor.shape + output_tensor = torch.zeros( + (height * scale, width * scale, channels), dtype=torch.uint8, device=torch.device("cpu") + ) + + image_tensor = image_tensor.to(device=spandrel_model.device, dtype=spandrel_model.dtype) # Run the model on each tile. - with spandrel_model_info as spandrel_model: - assert isinstance(spandrel_model, SpandrelImageToImageModel) + for tile, scaled_tile in tqdm(list(zip(tiles, scaled_tiles, strict=True)), desc="Upscaling Tiles"): + # Exit early if the invocation has been canceled. + if is_canceled(): + raise CanceledException - # Scale the tiles for re-assembling the final image. - scale = spandrel_model.scale - scaled_tiles = [self._scale_tile(tile, scale=scale) for tile in tiles] + # Extract the current tile from the input tensor. + input_tile = image_tensor[ + :, :, tile.coords.top : tile.coords.bottom, tile.coords.left : tile.coords.right + ].to(device=spandrel_model.device, dtype=spandrel_model.dtype) - # Prepare the output tensor. - _, channels, height, width = image_tensor.shape - output_tensor = torch.zeros( - (height * scale, width * scale, channels), dtype=torch.uint8, device=torch.device("cpu") - ) + # Run the model on the tile. + output_tile = spandrel_model.run(input_tile) - image_tensor = image_tensor.to(device=spandrel_model.device, dtype=spandrel_model.dtype) + # Convert the output tile into the output tensor's format. + # (N, C, H, W) -> (C, H, W) + output_tile = output_tile.squeeze(0) + # (C, H, W) -> (H, W, C) + output_tile = output_tile.permute(1, 2, 0) + output_tile = output_tile.clamp(0, 1) + output_tile = (output_tile * 255).to(dtype=torch.uint8, device=torch.device("cpu")) - for tile, scaled_tile in tqdm(list(zip(tiles, scaled_tiles, strict=True)), desc="Upscaling Tiles"): - # Exit early if the invocation has been canceled. - if context.util.is_canceled(): - raise CanceledException - - # Extract the current tile from the input tensor. - input_tile = image_tensor[ - :, :, tile.coords.top : tile.coords.bottom, tile.coords.left : tile.coords.right - ].to(device=spandrel_model.device, dtype=spandrel_model.dtype) - - # Run the model on the tile. - output_tile = spandrel_model.run(input_tile) - - # Convert the output tile into the output tensor's format. - # (N, C, H, W) -> (C, H, W) - output_tile = output_tile.squeeze(0) - # (C, H, W) -> (H, W, C) - output_tile = output_tile.permute(1, 2, 0) - output_tile = output_tile.clamp(0, 1) - output_tile = (output_tile * 255).to(dtype=torch.uint8, device=torch.device("cpu")) - - # Merge the output tile into the output tensor. - # We only keep half of the overlap on the top and left side of the tile. We do this in case there are - # edge artifacts. We don't bother with any 'blending' in the current implementation - for most upscalers - # it seems unnecessary, but we may find a need in the future. - top_overlap = scaled_tile.overlap.top // 2 - left_overlap = scaled_tile.overlap.left // 2 - output_tensor[ - scaled_tile.coords.top + top_overlap : scaled_tile.coords.bottom, - scaled_tile.coords.left + left_overlap : scaled_tile.coords.right, - :, - ] = output_tile[top_overlap:, left_overlap:, :] + # Merge the output tile into the output tensor. + # We only keep half of the overlap on the top and left side of the tile. We do this in case there are + # edge artifacts. We don't bother with any 'blending' in the current implementation - for most upscalers + # it seems unnecessary, but we may find a need in the future. + top_overlap = scaled_tile.overlap.top // 2 + left_overlap = scaled_tile.overlap.left // 2 + output_tensor[ + scaled_tile.coords.top + top_overlap : scaled_tile.coords.bottom, + scaled_tile.coords.left + left_overlap : scaled_tile.coords.right, + :, + ] = output_tile[top_overlap:, left_overlap:, :] # Convert the output tensor to a PIL image. np_image = output_tensor.detach().numpy().astype(np.uint8) pil_image = Image.fromarray(np_image) + + return pil_image + + @torch.inference_mode() + def invoke(self, context: InvocationContext) -> ImageOutput: + # Images are converted to RGB, because most models don't support an alpha channel. In the future, we may want to + # revisit this. + image = context.images.get_pil(self.image.image_name, mode="RGB") + + # Load the model. + spandrel_model_info = context.models.load(self.image_to_image_model) + + # Do the upscaling. + with spandrel_model_info as spandrel_model: + assert isinstance(spandrel_model, SpandrelImageToImageModel) + + # Upscale the image + pil_image = self.upscale_image(image, self.tile_size, spandrel_model, context.util.is_canceled) + + image_dto = context.images.save(image=pil_image) + return ImageOutput.build(image_dto) + + +@invocation( + "spandrel_image_to_image_autoscale", + title="Image-to-Image (Autoscale)", + tags=["upscale"], + category="upscale", + version="1.0.0", +) +class SpandrelImageToImageAutoscaleInvocation(SpandrelImageToImageInvocation): + """Run any spandrel image-to-image model (https://github.com/chaiNNer-org/spandrel) until the target scale is reached.""" + + scale: float = InputField( + default=4.0, + gt=0.0, + le=16.0, + description="The final scale of the output image. If the model does not upscale the image, this will be ignored.", + ) + fit_to_multiple_of_8: bool = InputField( + default=False, + description="If true, the output image will be resized to the nearest multiple of 8 in both dimensions.", + ) + + @torch.inference_mode() + def invoke(self, context: InvocationContext) -> ImageOutput: + # Images are converted to RGB, because most models don't support an alpha channel. In the future, we may want to + # revisit this. + image = context.images.get_pil(self.image.image_name, mode="RGB") + + # Load the model. + spandrel_model_info = context.models.load(self.image_to_image_model) + + # The target size of the image, determined by the provided scale. We'll run the upscaler until we hit this size. + # Later, we may mutate this value if the model doesn't upscale the image or if the user requested a multiple of 8. + target_width = int(image.width * self.scale) + target_height = int(image.height * self.scale) + + # Do the upscaling. + with spandrel_model_info as spandrel_model: + assert isinstance(spandrel_model, SpandrelImageToImageModel) + + # First pass of upscaling. Note: `pil_image` will be mutated. + pil_image = self.upscale_image(image, self.tile_size, spandrel_model, context.util.is_canceled) + + # Some models don't upscale the image, but we have no way to know this in advance. We'll check if the model + # upscaled the image and run the loop below if it did. We'll require the model to upscale both dimensions + # to be considered an upscale model. + is_upscale_model = pil_image.width > image.width and pil_image.height > image.height + + if is_upscale_model: + # This is an upscale model, so we should keep upscaling until we reach the target size. + iterations = 1 + while pil_image.width < target_width or pil_image.height < target_height: + pil_image = self.upscale_image(pil_image, self.tile_size, spandrel_model, context.util.is_canceled) + iterations += 1 + + # Sanity check to prevent excessive or infinite loops. All known upscaling models are at least 2x. + # Our max scale is 16x, so with a 2x model, we should never exceed 16x == 2^4 -> 4 iterations. + # We'll allow one extra iteration "just in case" and bail at 5 upscaling iterations. In practice, + # we should never reach this limit. + if iterations >= 5: + context.logger.warning( + "Upscale loop reached maximum iteration count of 5, stopping upscaling early." + ) + break + else: + # This model doesn't upscale the image. We should ignore the scale parameter, modifying the output size + # to be the same as the processed image size. + + # The output size is now the size of the processed image. + target_width = pil_image.width + target_height = pil_image.height + + # Warn the user if they requested a scale greater than 1. + if self.scale > 1: + context.logger.warning( + "Model does not increase the size of the image, but a greater scale than 1 was requested. Image will not be scaled." + ) + + # We may need to resize the image to a multiple of 8. Use floor division to ensure we don't scale the image up + # in the final resize + if self.fit_to_multiple_of_8: + target_width = int(target_width // 8 * 8) + target_height = int(target_height // 8 * 8) + + # Final resize. Per PIL documentation, Lanczos provides the best quality for both upscale and downscale. + # See: https://pillow.readthedocs.io/en/stable/handbook/concepts.html#filters-comparison-table + pil_image = pil_image.resize((target_width, target_height), resample=Image.Resampling.LANCZOS) + image_dto = context.images.save(image=pil_image) return ImageOutput.build(image_dto) diff --git a/invokeai/app/services/model_install/model_install_base.py b/invokeai/app/services/model_install/model_install_base.py index 20afaeaa50..27578dd5d6 100644 --- a/invokeai/app/services/model_install/model_install_base.py +++ b/invokeai/app/services/model_install/model_install_base.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import List, Optional, Union from pydantic.networks import AnyHttpUrl @@ -12,7 +12,7 @@ from invokeai.app.services.download import DownloadQueueServiceBase from invokeai.app.services.events.events_base import EventServiceBase from invokeai.app.services.invoker import Invoker from invokeai.app.services.model_install.model_install_common import ModelInstallJob, ModelSource -from invokeai.app.services.model_records import ModelRecordServiceBase +from invokeai.app.services.model_records import ModelRecordChanges, ModelRecordServiceBase from invokeai.backend.model_manager import AnyModelConfig @@ -64,7 +64,7 @@ class ModelInstallServiceBase(ABC): def register_path( self, model_path: Union[Path, str], - config: Optional[Dict[str, Any]] = None, + config: Optional[ModelRecordChanges] = None, ) -> str: """ Probe and register the model at model_path. @@ -72,7 +72,7 @@ class ModelInstallServiceBase(ABC): This keeps the model in its current location. :param model_path: Filesystem Path to the model. - :param config: Dict of attributes that will override autoassigned values. + :param config: ModelRecordChanges object that will override autoassigned model record values. :returns id: The string ID of the registered model. """ @@ -92,7 +92,7 @@ class ModelInstallServiceBase(ABC): def install_path( self, model_path: Union[Path, str], - config: Optional[Dict[str, Any]] = None, + config: Optional[ModelRecordChanges] = None, ) -> str: """ Probe, register and install the model in the models directory. @@ -101,7 +101,7 @@ class ModelInstallServiceBase(ABC): the models directory handled by InvokeAI. :param model_path: Filesystem Path to the model. - :param config: Dict of attributes that will override autoassigned values. + :param config: ModelRecordChanges object that will override autoassigned model record values. :returns id: The string ID of the registered model. """ @@ -109,14 +109,14 @@ class ModelInstallServiceBase(ABC): def heuristic_import( self, source: str, - config: Optional[Dict[str, Any]] = None, + config: Optional[ModelRecordChanges] = None, access_token: Optional[str] = None, inplace: Optional[bool] = False, ) -> ModelInstallJob: r"""Install the indicated model using heuristics to interpret user intentions. :param source: String source - :param config: Optional dict. Any fields in this dict + :param config: Optional ModelRecordChanges object. Any fields in this object will override corresponding autoassigned probe fields in the model's config record as described in `import_model()`. :param access_token: Optional access token for remote sources. @@ -147,7 +147,7 @@ class ModelInstallServiceBase(ABC): def import_model( self, source: ModelSource, - config: Optional[Dict[str, Any]] = None, + config: Optional[ModelRecordChanges] = None, ) -> ModelInstallJob: """Install the indicated model. diff --git a/invokeai/app/services/model_install/model_install_common.py b/invokeai/app/services/model_install/model_install_common.py index c1538f543d..8cbe31597f 100644 --- a/invokeai/app/services/model_install/model_install_common.py +++ b/invokeai/app/services/model_install/model_install_common.py @@ -2,13 +2,14 @@ import re import traceback from enum import Enum from pathlib import Path -from typing import Any, Dict, Literal, Optional, Set, Union +from typing import Literal, Optional, Set, Union from pydantic import BaseModel, Field, PrivateAttr, field_validator from pydantic.networks import AnyHttpUrl from typing_extensions import Annotated from invokeai.app.services.download import DownloadJob, MultiFileDownloadJob +from invokeai.app.services.model_records import ModelRecordChanges from invokeai.backend.model_manager import AnyModelConfig, ModelRepoVariant from invokeai.backend.model_manager.config import ModelSourceType from invokeai.backend.model_manager.metadata import AnyModelRepoMetadata @@ -133,8 +134,9 @@ class ModelInstallJob(BaseModel): id: int = Field(description="Unique ID for this job") status: InstallStatus = Field(default=InstallStatus.WAITING, description="Current status of install process") error_reason: Optional[str] = Field(default=None, description="Information about why the job failed") - config_in: Dict[str, Any] = Field( - default_factory=dict, description="Configuration information (e.g. 'description') to apply to model." + config_in: ModelRecordChanges = Field( + default_factory=ModelRecordChanges, + description="Configuration information (e.g. 'description') to apply to model.", ) config_out: Optional[AnyModelConfig] = Field( default=None, description="After successful installation, this will hold the configuration object." diff --git a/invokeai/app/services/model_install/model_install_default.py b/invokeai/app/services/model_install/model_install_default.py index 5e19a349ad..e1d784f5bf 100644 --- a/invokeai/app/services/model_install/model_install_default.py +++ b/invokeai/app/services/model_install/model_install_default.py @@ -163,26 +163,27 @@ class ModelInstallService(ModelInstallServiceBase): def register_path( self, model_path: Union[Path, str], - config: Optional[Dict[str, Any]] = None, + config: Optional[ModelRecordChanges] = None, ) -> str: # noqa D102 model_path = Path(model_path) - config = config or {} - if not config.get("source"): - config["source"] = model_path.resolve().as_posix() - config["source_type"] = ModelSourceType.Path + config = config or ModelRecordChanges() + if not config.source: + config.source = model_path.resolve().as_posix() + config.source_type = ModelSourceType.Path return self._register(model_path, config) def install_path( self, model_path: Union[Path, str], - config: Optional[Dict[str, Any]] = None, + config: Optional[ModelRecordChanges] = None, ) -> str: # noqa D102 model_path = Path(model_path) - config = config or {} + config = config or ModelRecordChanges() + info: AnyModelConfig = ModelProbe.probe( + Path(model_path), config.model_dump(), hash_algo=self._app_config.hashing_algorithm + ) # type: ignore - info: AnyModelConfig = ModelProbe.probe(Path(model_path), config, hash_algo=self._app_config.hashing_algorithm) - - if preferred_name := config.get("name"): + if preferred_name := config.name: preferred_name = Path(preferred_name).with_suffix(model_path.suffix) dest_path = ( @@ -204,7 +205,7 @@ class ModelInstallService(ModelInstallServiceBase): def heuristic_import( self, source: str, - config: Optional[Dict[str, Any]] = None, + config: Optional[ModelRecordChanges] = None, access_token: Optional[str] = None, inplace: Optional[bool] = False, ) -> ModelInstallJob: @@ -216,7 +217,7 @@ class ModelInstallService(ModelInstallServiceBase): source_obj.access_token = access_token return self.import_model(source_obj, config) - def import_model(self, source: ModelSource, config: Optional[Dict[str, Any]] = None) -> ModelInstallJob: # noqa D102 + def import_model(self, source: ModelSource, config: Optional[ModelRecordChanges] = None) -> ModelInstallJob: # noqa D102 similar_jobs = [x for x in self.list_jobs() if x.source == source and not x.in_terminal_state] if similar_jobs: self._logger.warning(f"There is already an active install job for {source}. Not enqueuing.") @@ -318,16 +319,17 @@ class ModelInstallService(ModelInstallServiceBase): model_path = self._app_config.models_path / model_path model_path = model_path.resolve() - config: dict[str, Any] = {} - config["name"] = model_name - config["description"] = stanza.get("description") + config = ModelRecordChanges( + name=model_name, + description=stanza.get("description"), + ) legacy_config_path = stanza.get("config") if legacy_config_path: # In v3, these paths were relative to the root. Migrate them to be relative to the legacy_conf_dir. legacy_config_path = self._app_config.root_path / legacy_config_path if legacy_config_path.is_relative_to(self._app_config.legacy_conf_path): legacy_config_path = legacy_config_path.relative_to(self._app_config.legacy_conf_path) - config["config_path"] = str(legacy_config_path) + config.config_path = str(legacy_config_path) try: id = self.register_path(model_path=model_path, config=config) self._logger.info(f"Migrated {model_name} with id {id}") @@ -500,11 +502,11 @@ class ModelInstallService(ModelInstallServiceBase): job.total_bytes = self._stat_size(job.local_path) job.bytes = job.total_bytes self._signal_job_running(job) - job.config_in["source"] = str(job.source) - job.config_in["source_type"] = MODEL_SOURCE_TO_TYPE_MAP[job.source.__class__] + job.config_in.source = str(job.source) + job.config_in.source_type = MODEL_SOURCE_TO_TYPE_MAP[job.source.__class__] # enter the metadata, if there is any if isinstance(job.source_metadata, (HuggingFaceMetadata)): - job.config_in["source_api_response"] = job.source_metadata.api_response + job.config_in.source_api_response = job.source_metadata.api_response if job.inplace: key = self.register_path(job.local_path, job.config_in) @@ -639,11 +641,11 @@ class ModelInstallService(ModelInstallServiceBase): return new_path def _register( - self, model_path: Path, config: Optional[Dict[str, Any]] = None, info: Optional[AnyModelConfig] = None + self, model_path: Path, config: Optional[ModelRecordChanges] = None, info: Optional[AnyModelConfig] = None ) -> str: - config = config or {} + config = config or ModelRecordChanges() - info = info or ModelProbe.probe(model_path, config, hash_algo=self._app_config.hashing_algorithm) + info = info or ModelProbe.probe(model_path, config.model_dump(), hash_algo=self._app_config.hashing_algorithm) # type: ignore model_path = model_path.resolve() @@ -674,11 +676,13 @@ class ModelInstallService(ModelInstallServiceBase): precision = TorchDevice.choose_torch_dtype() return ModelRepoVariant.FP16 if precision == torch.float16 else None - def _import_local_model(self, source: LocalModelSource, config: Optional[Dict[str, Any]]) -> ModelInstallJob: + def _import_local_model( + self, source: LocalModelSource, config: Optional[ModelRecordChanges] = None + ) -> ModelInstallJob: return ModelInstallJob( id=self._next_id(), source=source, - config_in=config or {}, + config_in=config or ModelRecordChanges(), local_path=Path(source.path), inplace=source.inplace or False, ) @@ -686,7 +690,7 @@ class ModelInstallService(ModelInstallServiceBase): def _import_from_hf( self, source: HFModelSource, - config: Optional[Dict[str, Any]] = None, + config: Optional[ModelRecordChanges] = None, ) -> ModelInstallJob: # Add user's cached access token to HuggingFace requests if source.access_token is None: @@ -702,7 +706,7 @@ class ModelInstallService(ModelInstallServiceBase): def _import_from_url( self, source: URLModelSource, - config: Optional[Dict[str, Any]], + config: Optional[ModelRecordChanges] = None, ) -> ModelInstallJob: remote_files, metadata = self._remote_files_from_source(source) return self._import_remote_model( @@ -717,7 +721,7 @@ class ModelInstallService(ModelInstallServiceBase): source: HFModelSource | URLModelSource, remote_files: List[RemoteModelFile], metadata: Optional[AnyModelRepoMetadata], - config: Optional[Dict[str, Any]], + config: Optional[ModelRecordChanges], ) -> ModelInstallJob: if len(remote_files) == 0: raise ValueError(f"{source}: No downloadable files found") @@ -730,7 +734,7 @@ class ModelInstallService(ModelInstallServiceBase): install_job = ModelInstallJob( id=self._next_id(), source=source, - config_in=config or {}, + config_in=config or ModelRecordChanges(), source_metadata=metadata, local_path=destdir, # local path may change once the download has started due to content-disposition handling bytes=0, diff --git a/invokeai/app/services/model_records/model_records_base.py b/invokeai/app/services/model_records/model_records_base.py index 57531cf3c1..46d11d4ddf 100644 --- a/invokeai/app/services/model_records/model_records_base.py +++ b/invokeai/app/services/model_records/model_records_base.py @@ -18,6 +18,7 @@ from invokeai.backend.model_manager.config import ( ControlAdapterDefaultSettings, MainModelDefaultSettings, ModelFormat, + ModelSourceType, ModelType, ModelVariantType, SchedulerPredictionType, @@ -66,10 +67,16 @@ class ModelRecordChanges(BaseModelExcludeNull): """A set of changes to apply to a model.""" # Changes applicable to all models + source: Optional[str] = Field(description="original source of the model", default=None) + source_type: Optional[ModelSourceType] = Field(description="type of model source", default=None) + source_api_response: Optional[str] = Field(description="metadata from remote source", default=None) name: Optional[str] = Field(description="Name of the model.", default=None) path: Optional[str] = Field(description="Path to the model.", default=None) description: Optional[str] = Field(description="Model description", default=None) base: Optional[BaseModelType] = Field(description="The base model.", default=None) + type: Optional[ModelType] = Field(description="Type of model", default=None) + key: Optional[str] = Field(description="Database ID for this model", default=None) + hash: Optional[str] = Field(description="hash of model file", default=None) trigger_phrases: Optional[set[str]] = Field(description="Set of trigger phrases for this model", default=None) default_settings: Optional[MainModelDefaultSettings | ControlAdapterDefaultSettings] = Field( description="Default settings for this model", default=None diff --git a/invokeai/app/services/workflow_records/default_workflows/MultiDiffusion SD1.5.json b/invokeai/app/services/workflow_records/default_workflows/MultiDiffusion SD1.5.json new file mode 100644 index 0000000000..7bc2810c75 --- /dev/null +++ b/invokeai/app/services/workflow_records/default_workflows/MultiDiffusion SD1.5.json @@ -0,0 +1,1430 @@ +{ + "name": "MultiDiffusion SD1.5", + "author": "Invoke", + "description": "A workflow to upscale an input image with tiled upscaling, using SD1.5 based models.", + "version": "1.0.0", + "contact": "invoke@invoke.ai", + "tags": "tiled, upscaling, sdxl", + "notes": "", + "exposedFields": [ + { + "nodeId": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "fieldName": "image" + }, + { + "nodeId": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "fieldName": "scale" + }, + { + "nodeId": "c3b60a50-8039-4924-90e3-8c608e1fecb5", + "fieldName": "board" + }, + { + "nodeId": "1dd915a3-6756-48ed-b68b-ee3b4bd06c1d", + "fieldName": "a" + }, + { + "nodeId": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", + "fieldName": "a" + }, + { + "nodeId": "14469dfe-9f49-4a13-89a7-eb4d45794b2b", + "fieldName": "prompt" + }, + { + "nodeId": "33fe76a0-5efd-4482-a7f0-e2abf1223dc2", + "fieldName": "prompt" + }, + { + "nodeId": "009b38e3-4e17-4ac5-958c-14891991ae28", + "fieldName": "model" + }, + { + "nodeId": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "fieldName": "image_to_image_model" + }, + { + "nodeId": "f936ebb3-6902-4df9-a775-6a68bac2da70", + "fieldName": "model" + } + ], + "meta": { + "version": "3.0.0", + "category": "default" + }, + "id": "e5b5fb01-8906-463a-963a-402dbc42f79b", + "nodes": [ + { + "id": "33fe76a0-5efd-4482-a7f0-e2abf1223dc2", + "type": "invocation", + "data": { + "id": "33fe76a0-5efd-4482-a7f0-e2abf1223dc2", + "type": "compel", + "version": "1.2.0", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "prompt": { + "name": "prompt", + "label": "Negative Prompt (Optional)", + "value": "blurry painting, art, sketch" + }, + "clip": { + "name": "clip", + "label": "" + }, + "mask": { + "name": "mask", + "label": "" + } + } + }, + "position": { + "x": -3550, + "y": -2725 + } + }, + { + "id": "14469dfe-9f49-4a13-89a7-eb4d45794b2b", + "type": "invocation", + "data": { + "id": "14469dfe-9f49-4a13-89a7-eb4d45794b2b", + "type": "compel", + "version": "1.2.0", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "prompt": { + "name": "prompt", + "label": "Positive Prompt (Optional)", + "value": "high quality studio lighting, photo" + }, + "clip": { + "name": "clip", + "label": "" + }, + "mask": { + "name": "mask", + "label": "" + } + } + }, + "position": { + "x": -3550, + "y": -3025 + } + }, + { + "id": "009b38e3-4e17-4ac5-958c-14891991ae28", + "type": "invocation", + "data": { + "id": "009b38e3-4e17-4ac5-958c-14891991ae28", + "type": "main_model_loader", + "version": "1.0.3", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "model": { + "name": "model", + "label": "", + "value": { + "key": "e7b402e5-62e5-4acb-8c39-bee6bdb758ab", + "hash": "c8659e796168d076368256b57edbc1b48d6dafc1712f1bb37cc57c7c06889a6b", + "name": "526mix", + "base": "sd-1", + "type": "main" + } + } + } + }, + "position": { + "x": -4025, + "y": -3050 + } + }, + { + "id": "71a116e1-c631-48b3-923d-acea4753b887", + "type": "invocation", + "data": { + "id": "71a116e1-c631-48b3-923d-acea4753b887", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "ADD" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.3 + } + } + }, + "position": { + "x": -3050, + "y": -1550 + } + }, + { + "id": "00e2c587-f047-4413-ad15-bd31ea53ce22", + "type": "invocation", + "data": { + "id": "00e2c587-f047-4413-ad15-bd31ea53ce22", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "MUL" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.025 + } + } + }, + "position": { + "x": -3050, + "y": -1575 + } + }, + { + "id": "96e1bcd0-326b-4b67-8b14-239da2440aec", + "type": "invocation", + "data": { + "id": "96e1bcd0-326b-4b67-8b14-239da2440aec", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "MUL" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.45 + } + } + }, + "position": { + "x": -3050, + "y": -1200 + } + }, + { + "id": "75a89685-0f82-40ed-9b88-e583673be9fc", + "type": "invocation", + "data": { + "id": "75a89685-0f82-40ed-9b88-e583673be9fc", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "ADD" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.15 + } + } + }, + "position": { + "x": -3050, + "y": -1225 + } + }, + { + "id": "1ed88043-3519-41d5-a895-07944f03de70", + "type": "invocation", + "data": { + "id": "1ed88043-3519-41d5-a895-07944f03de70", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "ADD" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.3 + } + } + }, + "position": { + "x": -3050, + "y": -1650 + } + }, + { + "id": "9b281506-4079-4a3d-ab40-b386156fcd21", + "type": "invocation", + "data": { + "id": "9b281506-4079-4a3d-ab40-b386156fcd21", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "MUL" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.032 + } + } + }, + "position": { + "x": -3050, + "y": -1850 + } + }, + { + "id": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "type": "invocation", + "data": { + "id": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "type": "spandrel_image_to_image_autoscale", + "version": "1.0.0", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "board": { + "name": "board", + "label": "" + }, + "metadata": { + "name": "metadata", + "label": "" + }, + "image": { + "name": "image", + "label": "Image to Upscale", + "value": { + "image_name": "ee7009f7-a35d-488b-a2a6-21237ef5ae05.png" + } + }, + "image_to_image_model": { + "name": "image_to_image_model", + "label": "", + "value": { + "key": "38bb1a29-8ede-42ba-b77f-64b3478896eb", + "hash": "blake3:e52fdbee46a484ebe9b3b20ea0aac0a35a453ab6d0d353da00acfd35ce7a91ed", + "name": "4xNomosWebPhoto_esrgan", + "base": "sdxl", + "type": "spandrel_image_to_image" + } + }, + "tile_size": { + "name": "tile_size", + "label": "", + "value": 512 + }, + "scale": { + "name": "scale", + "label": "Scale (2x, 4x, 8x, 16x)", + "value": 2 + }, + "fit_to_multiple_of_8": { + "name": "fit_to_multiple_of_8", + "label": "", + "value": true + } + } + }, + "position": { + "x": -4750, + "y": -2125 + } + }, + { + "id": "f936ebb3-6902-4df9-a775-6a68bac2da70", + "type": "invocation", + "data": { + "id": "f936ebb3-6902-4df9-a775-6a68bac2da70", + "type": "model_identifier", + "version": "1.0.0", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "model": { + "name": "model", + "label": "ControlNet Model - Choose a Tile ControlNet", + "value": { + "key": "20645e4d-ef97-4c5a-9243-b834a3483925", + "hash": "f0812e13758f91baf4e54b7dbb707b70642937d3b2098cd2b94cc36d3eba308e", + "name": "tile", + "base": "sd-1", + "type": "controlnet" + } + } + } + }, + "position": { + "x": -3450, + "y": -1450 + } + }, + { + "id": "00239057-20d4-4cd2-a010-28727b256ea2", + "type": "invocation", + "data": { + "id": "00239057-20d4-4cd2-a010-28727b256ea2", + "type": "rand_int", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": false, + "inputs": { + "low": { + "name": "low", + "label": "", + "value": 0 + }, + "high": { + "name": "high", + "label": "", + "value": 2147483647 + } + } + }, + "position": { + "x": -4025, + "y": -2075 + } + }, + { + "id": "094bc4ed-5c68-4342-84f4-51056c755796", + "type": "invocation", + "data": { + "id": "094bc4ed-5c68-4342-84f4-51056c755796", + "type": "boolean", + "version": "1.0.1", + "label": "Tiled Option", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "value": { + "name": "value", + "label": "Tiled VAE (Saves VRAM, Color Inconsistency)", + "value": true + } + } + }, + "position": { + "x": -2675, + "y": -2475 + } + }, + { + "id": "1dd915a3-6756-48ed-b68b-ee3b4bd06c1d", + "type": "invocation", + "data": { + "id": "1dd915a3-6756-48ed-b68b-ee3b4bd06c1d", + "type": "float_math", + "version": "1.0.1", + "label": "Creativity Input", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "MUL" + }, + "a": { + "name": "a", + "label": "Creativity Control (-10 to 10)", + "value": 0 + }, + "b": { + "name": "b", + "label": "", + "value": -1 + } + } + }, + "position": { + "x": -3500, + "y": -2350 + } + }, + { + "id": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03", + "type": "invocation", + "data": { + "id": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "DIV" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 100 + } + } + }, + "position": { + "x": -3500, + "y": -1975 + } + }, + { + "id": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c", + "type": "invocation", + "data": { + "id": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "ADD" + }, + "a": { + "name": "a", + "label": "A", + "value": 0 + }, + "b": { + "name": "b", + "label": "", + "value": 10 + } + } + }, + "position": { + "x": -3500, + "y": -2075 + } + }, + { + "id": "49a8cc12-aa19-48c5-b6b3-04e0b603b384", + "type": "invocation", + "data": { + "id": "49a8cc12-aa19-48c5-b6b3-04e0b603b384", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "MUL" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 4.99 + } + } + }, + "position": { + "x": -3500, + "y": -2025 + } + }, + { + "id": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", + "type": "invocation", + "data": { + "id": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", + "type": "float_math", + "version": "1.0.1", + "label": "Structural Input", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "ADD" + }, + "a": { + "name": "a", + "label": "Structural Control (-10 to 10)", + "value": 0 + }, + "b": { + "name": "b", + "label": "", + "value": 10 + } + } + }, + "position": { + "x": -3050, + "y": -2100 + } + }, + { + "id": "6636a27a-f130-4a13-b3e5-50b44e4a566f", + "type": "invocation", + "data": { + "id": "6636a27a-f130-4a13-b3e5-50b44e4a566f", + "type": "collect", + "version": "1.0.0", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "item": { + "name": "item", + "label": "" + } + } + }, + "position": { + "x": -2275, + "y": -2075 + } + }, + { + "id": "b78f53b6-2eae-4956-97b4-7e73768d1491", + "type": "invocation", + "data": { + "id": "b78f53b6-2eae-4956-97b4-7e73768d1491", + "type": "controlnet", + "version": "1.1.2", + "label": "Initial Control (Use Tile)", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "image": { + "name": "image", + "label": "" + }, + "control_model": { + "name": "control_model", + "label": "" + }, + "control_weight": { + "name": "control_weight", + "label": "", + "value": 0.6 + }, + "begin_step_percent": { + "name": "begin_step_percent", + "label": "", + "value": 0 + }, + "end_step_percent": { + "name": "end_step_percent", + "label": "", + "value": 0.5 + }, + "control_mode": { + "name": "control_mode", + "label": "", + "value": "balanced" + }, + "resize_mode": { + "name": "resize_mode", + "label": "", + "value": "just_resize" + } + } + }, + "position": { + "x": -2675, + "y": -1775 + } + }, + { + "id": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "type": "invocation", + "data": { + "id": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "type": "unsharp_mask", + "version": "1.2.2", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "board": { + "name": "board", + "label": "" + }, + "metadata": { + "name": "metadata", + "label": "" + }, + "image": { + "name": "image", + "label": "" + }, + "radius": { + "name": "radius", + "label": "", + "value": 2 + }, + "strength": { + "name": "strength", + "label": "", + "value": 50 + } + } + }, + "position": { + "x": -4400, + "y": -2125 + } + }, + { + "id": "117f982a-03da-49b1-bf9f-29711160ac02", + "type": "invocation", + "data": { + "id": "117f982a-03da-49b1-bf9f-29711160ac02", + "type": "i2l", + "version": "1.1.0", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "image": { + "name": "image", + "label": "" + }, + "vae": { + "name": "vae", + "label": "" + }, + "tiled": { + "name": "tiled", + "label": "", + "value": false + }, + "tile_size": { + "name": "tile_size", + "label": "", + "value": 0 + }, + "fp32": { + "name": "fp32", + "label": "", + "value": false + } + } + }, + "position": { + "x": -4025, + "y": -2125 + } + }, + { + "id": "c3b60a50-8039-4924-90e3-8c608e1fecb5", + "type": "invocation", + "data": { + "id": "c3b60a50-8039-4924-90e3-8c608e1fecb5", + "type": "l2i", + "version": "1.3.0", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": false, + "useCache": true, + "inputs": { + "board": { + "name": "board", + "label": "Output Board" + }, + "metadata": { + "name": "metadata", + "label": "" + }, + "latents": { + "name": "latents", + "label": "" + }, + "vae": { + "name": "vae", + "label": "" + }, + "tiled": { + "name": "tiled", + "label": "", + "value": false + }, + "tile_size": { + "name": "tile_size", + "label": "", + "value": 0 + }, + "fp32": { + "name": "fp32", + "label": "", + "value": false + } + } + }, + "position": { + "x": -2675, + "y": -2825 + } + }, + { + "id": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "type": "invocation", + "data": { + "id": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "type": "tiled_multi_diffusion_denoise_latents", + "version": "1.0.0", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "positive_conditioning": { + "name": "positive_conditioning", + "label": "" + }, + "negative_conditioning": { + "name": "negative_conditioning", + "label": "" + }, + "noise": { + "name": "noise", + "label": "" + }, + "latents": { + "name": "latents", + "label": "" + }, + "tile_height": { + "name": "tile_height", + "label": "", + "value": 768 + }, + "tile_width": { + "name": "tile_width", + "label": "", + "value": 768 + }, + "tile_overlap": { + "name": "tile_overlap", + "label": "", + "value": 128 + }, + "steps": { + "name": "steps", + "label": "", + "value": 25 + }, + "cfg_scale": { + "name": "cfg_scale", + "label": "", + "value": 5 + }, + "denoising_start": { + "name": "denoising_start", + "label": "", + "value": 0.6 + }, + "denoising_end": { + "name": "denoising_end", + "label": "", + "value": 1 + }, + "scheduler": { + "name": "scheduler", + "label": "", + "value": "kdpm_2" + }, + "unet": { + "name": "unet", + "label": "" + }, + "cfg_rescale_multiplier": { + "name": "cfg_rescale_multiplier", + "label": "", + "value": 0 + }, + "control": { + "name": "control", + "label": "" + } + } + }, + "position": { + "x": -3050, + "y": -2825 + } + }, + { + "id": "be4082d6-e238-40ea-a9df-fc0d725e8895", + "type": "invocation", + "data": { + "id": "be4082d6-e238-40ea-a9df-fc0d725e8895", + "type": "controlnet", + "version": "1.1.2", + "label": "Second Phase Control (Use Tile)", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "image": { + "name": "image", + "label": "" + }, + "control_model": { + "name": "control_model", + "label": "" + }, + "control_weight": { + "name": "control_weight", + "label": "", + "value": 0.25 + }, + "begin_step_percent": { + "name": "begin_step_percent", + "label": "", + "value": 0.5 + }, + "end_step_percent": { + "name": "end_step_percent", + "label": "", + "value": 0.85 + }, + "control_mode": { + "name": "control_mode", + "label": "Control Mode", + "value": "balanced" + }, + "resize_mode": { + "name": "resize_mode", + "label": "", + "value": "just_resize" + } + } + }, + "position": { + "x": -2675, + "y": -1325 + } + }, + { + "id": "8923451b-5a27-4395-b7f2-dce875fca6f5", + "type": "invocation", + "data": { + "id": "8923451b-5a27-4395-b7f2-dce875fca6f5", + "type": "noise", + "version": "1.0.2", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "seed": { + "name": "seed", + "label": "", + "value": 3 + }, + "width": { + "name": "width", + "label": "", + "value": 512 + }, + "height": { + "name": "height", + "label": "", + "value": 512 + }, + "use_cpu": { + "name": "use_cpu", + "label": "", + "value": true + } + } + }, + "position": { + "x": -4025, + "y": -2025 + } + } + ], + "edges": [ + { + "id": "reactflow__edge-009b38e3-4e17-4ac5-958c-14891991ae28vae-117f982a-03da-49b1-bf9f-29711160ac02vae", + "type": "default", + "source": "009b38e3-4e17-4ac5-958c-14891991ae28", + "target": "117f982a-03da-49b1-bf9f-29711160ac02", + "sourceHandle": "vae", + "targetHandle": "vae" + }, + { + "id": "reactflow__edge-009b38e3-4e17-4ac5-958c-14891991ae28vae-c3b60a50-8039-4924-90e3-8c608e1fecb5vae", + "type": "default", + "source": "009b38e3-4e17-4ac5-958c-14891991ae28", + "target": "c3b60a50-8039-4924-90e3-8c608e1fecb5", + "sourceHandle": "vae", + "targetHandle": "vae" + }, + { + "id": "reactflow__edge-33fe76a0-5efd-4482-a7f0-e2abf1223dc2conditioning-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7anegative_conditioning", + "type": "default", + "source": "33fe76a0-5efd-4482-a7f0-e2abf1223dc2", + "target": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "sourceHandle": "conditioning", + "targetHandle": "negative_conditioning" + }, + { + "id": "reactflow__edge-009b38e3-4e17-4ac5-958c-14891991ae28clip-33fe76a0-5efd-4482-a7f0-e2abf1223dc2clip", + "type": "default", + "source": "009b38e3-4e17-4ac5-958c-14891991ae28", + "target": "33fe76a0-5efd-4482-a7f0-e2abf1223dc2", + "sourceHandle": "clip", + "targetHandle": "clip" + }, + { + "id": "reactflow__edge-14469dfe-9f49-4a13-89a7-eb4d45794b2bconditioning-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7apositive_conditioning", + "type": "default", + "source": "14469dfe-9f49-4a13-89a7-eb4d45794b2b", + "target": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "sourceHandle": "conditioning", + "targetHandle": "positive_conditioning" + }, + { + "id": "reactflow__edge-009b38e3-4e17-4ac5-958c-14891991ae28clip-14469dfe-9f49-4a13-89a7-eb4d45794b2bclip", + "type": "default", + "source": "009b38e3-4e17-4ac5-958c-14891991ae28", + "target": "14469dfe-9f49-4a13-89a7-eb4d45794b2b", + "sourceHandle": "clip", + "targetHandle": "clip" + }, + { + "id": "reactflow__edge-009b38e3-4e17-4ac5-958c-14891991ae28unet-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7aunet", + "type": "default", + "source": "009b38e3-4e17-4ac5-958c-14891991ae28", + "target": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "sourceHandle": "unet", + "targetHandle": "unet" + }, + { + "id": "9b281506-4079-4a3d-ab40-b386156fcd21-75a89685-0f82-40ed-9b88-e583673be9fc-collapsed", + "type": "collapsed", + "source": "9b281506-4079-4a3d-ab40-b386156fcd21", + "target": "75a89685-0f82-40ed-9b88-e583673be9fc" + }, + { + "id": "9b281506-4079-4a3d-ab40-b386156fcd21-1ed88043-3519-41d5-a895-07944f03de70-collapsed", + "type": "collapsed", + "source": "9b281506-4079-4a3d-ab40-b386156fcd21", + "target": "1ed88043-3519-41d5-a895-07944f03de70" + }, + { + "id": "49a8cc12-aa19-48c5-b6b3-04e0b603b384-c8f5c671-8c87-4d96-a75e-a9937ac6bc03-collapsed", + "type": "collapsed", + "source": "49a8cc12-aa19-48c5-b6b3-04e0b603b384", + "target": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03" + }, + { + "id": "reactflow__edge-c8f5c671-8c87-4d96-a75e-a9937ac6bc03value-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7adenoising_start", + "type": "default", + "source": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03", + "target": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "sourceHandle": "value", + "targetHandle": "denoising_start" + }, + { + "id": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c-49a8cc12-aa19-48c5-b6b3-04e0b603b384-collapsed", + "type": "collapsed", + "source": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c", + "target": "49a8cc12-aa19-48c5-b6b3-04e0b603b384" + }, + { + "id": "75a89685-0f82-40ed-9b88-e583673be9fc-96e1bcd0-326b-4b67-8b14-239da2440aec-collapsed", + "type": "collapsed", + "source": "75a89685-0f82-40ed-9b88-e583673be9fc", + "target": "96e1bcd0-326b-4b67-8b14-239da2440aec" + }, + { + "id": "00e2c587-f047-4413-ad15-bd31ea53ce22-71a116e1-c631-48b3-923d-acea4753b887-collapsed", + "type": "collapsed", + "source": "00e2c587-f047-4413-ad15-bd31ea53ce22", + "target": "71a116e1-c631-48b3-923d-acea4753b887" + }, + { + "id": "reactflow__edge-71a116e1-c631-48b3-923d-acea4753b887value-be4082d6-e238-40ea-a9df-fc0d725e8895begin_step_percent", + "type": "default", + "source": "71a116e1-c631-48b3-923d-acea4753b887", + "target": "be4082d6-e238-40ea-a9df-fc0d725e8895", + "sourceHandle": "value", + "targetHandle": "begin_step_percent" + }, + { + "id": "reactflow__edge-71a116e1-c631-48b3-923d-acea4753b887value-b78f53b6-2eae-4956-97b4-7e73768d1491end_step_percent", + "type": "default", + "source": "71a116e1-c631-48b3-923d-acea4753b887", + "target": "b78f53b6-2eae-4956-97b4-7e73768d1491", + "sourceHandle": "value", + "targetHandle": "end_step_percent" + }, + { + "id": "reactflow__edge-00e2c587-f047-4413-ad15-bd31ea53ce22value-71a116e1-c631-48b3-923d-acea4753b887a", + "type": "default", + "source": "00e2c587-f047-4413-ad15-bd31ea53ce22", + "target": "71a116e1-c631-48b3-923d-acea4753b887", + "sourceHandle": "value", + "targetHandle": "a", + "hidden": true + }, + { + "id": "reactflow__edge-bd094e2f-41e5-4b61-9f7b-56cf337d53favalue-00e2c587-f047-4413-ad15-bd31ea53ce22a", + "type": "default", + "source": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", + "target": "00e2c587-f047-4413-ad15-bd31ea53ce22", + "sourceHandle": "value", + "targetHandle": "a" + }, + { + "id": "reactflow__edge-96e1bcd0-326b-4b67-8b14-239da2440aecvalue-be4082d6-e238-40ea-a9df-fc0d725e8895control_weight", + "type": "default", + "source": "96e1bcd0-326b-4b67-8b14-239da2440aec", + "target": "be4082d6-e238-40ea-a9df-fc0d725e8895", + "sourceHandle": "value", + "targetHandle": "control_weight" + }, + { + "id": "reactflow__edge-75a89685-0f82-40ed-9b88-e583673be9fcvalue-96e1bcd0-326b-4b67-8b14-239da2440aeca", + "type": "default", + "source": "75a89685-0f82-40ed-9b88-e583673be9fc", + "target": "96e1bcd0-326b-4b67-8b14-239da2440aec", + "sourceHandle": "value", + "targetHandle": "a", + "hidden": true + }, + { + "id": "reactflow__edge-9b281506-4079-4a3d-ab40-b386156fcd21value-75a89685-0f82-40ed-9b88-e583673be9fca", + "type": "default", + "source": "9b281506-4079-4a3d-ab40-b386156fcd21", + "target": "75a89685-0f82-40ed-9b88-e583673be9fc", + "sourceHandle": "value", + "targetHandle": "a", + "hidden": true + }, + { + "id": "reactflow__edge-1ed88043-3519-41d5-a895-07944f03de70value-b78f53b6-2eae-4956-97b4-7e73768d1491control_weight", + "type": "default", + "source": "1ed88043-3519-41d5-a895-07944f03de70", + "target": "b78f53b6-2eae-4956-97b4-7e73768d1491", + "sourceHandle": "value", + "targetHandle": "control_weight" + }, + { + "id": "reactflow__edge-9b281506-4079-4a3d-ab40-b386156fcd21value-1ed88043-3519-41d5-a895-07944f03de70a", + "type": "default", + "source": "9b281506-4079-4a3d-ab40-b386156fcd21", + "target": "1ed88043-3519-41d5-a895-07944f03de70", + "sourceHandle": "value", + "targetHandle": "a", + "hidden": true + }, + { + "id": "reactflow__edge-bd094e2f-41e5-4b61-9f7b-56cf337d53favalue-9b281506-4079-4a3d-ab40-b386156fcd21a", + "type": "default", + "source": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", + "target": "9b281506-4079-4a3d-ab40-b386156fcd21", + "sourceHandle": "value", + "targetHandle": "a" + }, + { + "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeheight-8923451b-5a27-4395-b7f2-dce875fca6f5height", + "type": "default", + "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "target": "8923451b-5a27-4395-b7f2-dce875fca6f5", + "sourceHandle": "height", + "targetHandle": "height" + }, + { + "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebewidth-8923451b-5a27-4395-b7f2-dce875fca6f5width", + "type": "default", + "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "target": "8923451b-5a27-4395-b7f2-dce875fca6f5", + "sourceHandle": "width", + "targetHandle": "width" + }, + { + "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeimage-b78f53b6-2eae-4956-97b4-7e73768d1491image", + "type": "default", + "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "target": "b78f53b6-2eae-4956-97b4-7e73768d1491", + "sourceHandle": "image", + "targetHandle": "image" + }, + { + "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeimage-be4082d6-e238-40ea-a9df-fc0d725e8895image", + "type": "default", + "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "target": "be4082d6-e238-40ea-a9df-fc0d725e8895", + "sourceHandle": "image", + "targetHandle": "image" + }, + { + "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeimage-117f982a-03da-49b1-bf9f-29711160ac02image", + "type": "default", + "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "target": "117f982a-03da-49b1-bf9f-29711160ac02", + "sourceHandle": "image", + "targetHandle": "image" + }, + { + "id": "reactflow__edge-011039f6-04cf-4607-8eb1-3304eb819c8cimage-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeimage", + "type": "default", + "source": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "target": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "sourceHandle": "image", + "targetHandle": "image" + }, + { + "id": "reactflow__edge-f936ebb3-6902-4df9-a775-6a68bac2da70model-be4082d6-e238-40ea-a9df-fc0d725e8895control_model", + "type": "default", + "source": "f936ebb3-6902-4df9-a775-6a68bac2da70", + "target": "be4082d6-e238-40ea-a9df-fc0d725e8895", + "sourceHandle": "model", + "targetHandle": "control_model" + }, + { + "id": "reactflow__edge-f936ebb3-6902-4df9-a775-6a68bac2da70model-b78f53b6-2eae-4956-97b4-7e73768d1491control_model", + "type": "default", + "source": "f936ebb3-6902-4df9-a775-6a68bac2da70", + "target": "b78f53b6-2eae-4956-97b4-7e73768d1491", + "sourceHandle": "model", + "targetHandle": "control_model" + }, + { + "id": "reactflow__edge-00239057-20d4-4cd2-a010-28727b256ea2value-8923451b-5a27-4395-b7f2-dce875fca6f5seed", + "type": "default", + "source": "00239057-20d4-4cd2-a010-28727b256ea2", + "target": "8923451b-5a27-4395-b7f2-dce875fca6f5", + "sourceHandle": "value", + "targetHandle": "seed" + }, + { + "id": "reactflow__edge-094bc4ed-5c68-4342-84f4-51056c755796value-c3b60a50-8039-4924-90e3-8c608e1fecb5tiled", + "type": "default", + "source": "094bc4ed-5c68-4342-84f4-51056c755796", + "target": "c3b60a50-8039-4924-90e3-8c608e1fecb5", + "sourceHandle": "value", + "targetHandle": "tiled" + }, + { + "id": "reactflow__edge-094bc4ed-5c68-4342-84f4-51056c755796value-117f982a-03da-49b1-bf9f-29711160ac02tiled", + "type": "default", + "source": "094bc4ed-5c68-4342-84f4-51056c755796", + "target": "117f982a-03da-49b1-bf9f-29711160ac02", + "sourceHandle": "value", + "targetHandle": "tiled" + }, + { + "id": "reactflow__edge-1dd915a3-6756-48ed-b68b-ee3b4bd06c1dvalue-14e65dbe-4249-4b25-9a63-3a10cfaeb61ca", + "type": "default", + "source": "1dd915a3-6756-48ed-b68b-ee3b4bd06c1d", + "target": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c", + "sourceHandle": "value", + "targetHandle": "a" + }, + { + "id": "reactflow__edge-49a8cc12-aa19-48c5-b6b3-04e0b603b384value-c8f5c671-8c87-4d96-a75e-a9937ac6bc03a", + "type": "default", + "source": "49a8cc12-aa19-48c5-b6b3-04e0b603b384", + "target": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03", + "sourceHandle": "value", + "targetHandle": "a", + "hidden": true + }, + { + "id": "reactflow__edge-14e65dbe-4249-4b25-9a63-3a10cfaeb61cvalue-49a8cc12-aa19-48c5-b6b3-04e0b603b384a", + "type": "default", + "source": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c", + "target": "49a8cc12-aa19-48c5-b6b3-04e0b603b384", + "sourceHandle": "value", + "targetHandle": "a", + "hidden": true + }, + { + "id": "reactflow__edge-6636a27a-f130-4a13-b3e5-50b44e4a566fcollection-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7acontrol", + "type": "default", + "source": "6636a27a-f130-4a13-b3e5-50b44e4a566f", + "target": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "sourceHandle": "collection", + "targetHandle": "control" + }, + { + "id": "reactflow__edge-b78f53b6-2eae-4956-97b4-7e73768d1491control-6636a27a-f130-4a13-b3e5-50b44e4a566fitem", + "type": "default", + "source": "b78f53b6-2eae-4956-97b4-7e73768d1491", + "target": "6636a27a-f130-4a13-b3e5-50b44e4a566f", + "sourceHandle": "control", + "targetHandle": "item" + }, + { + "id": "reactflow__edge-be4082d6-e238-40ea-a9df-fc0d725e8895control-6636a27a-f130-4a13-b3e5-50b44e4a566fitem", + "type": "default", + "source": "be4082d6-e238-40ea-a9df-fc0d725e8895", + "target": "6636a27a-f130-4a13-b3e5-50b44e4a566f", + "sourceHandle": "control", + "targetHandle": "item" + }, + { + "id": "reactflow__edge-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7alatents-c3b60a50-8039-4924-90e3-8c608e1fecb5latents", + "type": "default", + "source": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "target": "c3b60a50-8039-4924-90e3-8c608e1fecb5", + "sourceHandle": "latents", + "targetHandle": "latents" + }, + { + "id": "reactflow__edge-117f982a-03da-49b1-bf9f-29711160ac02latents-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7alatents", + "type": "default", + "source": "117f982a-03da-49b1-bf9f-29711160ac02", + "target": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "sourceHandle": "latents", + "targetHandle": "latents" + }, + { + "id": "reactflow__edge-8923451b-5a27-4395-b7f2-dce875fca6f5noise-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7anoise", + "type": "default", + "source": "8923451b-5a27-4395-b7f2-dce875fca6f5", + "target": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "sourceHandle": "noise", + "targetHandle": "noise" + } + ] +} \ No newline at end of file diff --git a/invokeai/app/services/workflow_records/default_workflows/MultiDiffusion SDXL (Beta).json b/invokeai/app/services/workflow_records/default_workflows/MultiDiffusion SDXL.json similarity index 63% rename from invokeai/app/services/workflow_records/default_workflows/MultiDiffusion SDXL (Beta).json rename to invokeai/app/services/workflow_records/default_workflows/MultiDiffusion SDXL.json index b2842315c4..876ca6f8e6 100644 --- a/invokeai/app/services/workflow_records/default_workflows/MultiDiffusion SDXL (Beta).json +++ b/invokeai/app/services/workflow_records/default_workflows/MultiDiffusion SDXL.json @@ -1,44 +1,32 @@ { - "name": "MultiDiffusion SDXL (Beta)", + "name": "MultiDiffusion SDXL", "author": "Invoke", "description": "A workflow to upscale an input image with tiled upscaling, using SDXL based models.", - "version": "1.0.0", + "version": "1.1.0", "contact": "invoke@invoke.ai", "tags": "tiled, upscaling, sdxl", "notes": "", "exposedFields": [ { - "nodeId": "1ba845a6-eb88-49a1-a490-5fe6754f3ec9", - "fieldName": "value" + "nodeId": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "fieldName": "image" + }, + { + "nodeId": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "fieldName": "scale" }, { "nodeId": "c3b60a50-8039-4924-90e3-8c608e1fecb5", "fieldName": "board" }, - { - "nodeId": "5ca87ace-edf9-49c7-a424-cd42416b86a7", - "fieldName": "image" - }, { "nodeId": "1dd915a3-6756-48ed-b68b-ee3b4bd06c1d", "fieldName": "a" }, - { - "nodeId": "696de0e1-cdd2-42e8-abeb-57a926bc6df6", - "fieldName": "a" - }, { "nodeId": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", "fieldName": "a" }, - { - "nodeId": "e277e4b7-01cd-4daa-86ab-7bfa3cdcd9fd", - "fieldName": "model" - }, - { - "nodeId": "f0cd0d2f-9614-43f7-9944-a75b8d5ccd65", - "fieldName": "model_name" - }, { "nodeId": "c26bff37-4f12-482f-ba45-3a5d729b4c4f", "fieldName": "value" @@ -48,13 +36,17 @@ "fieldName": "value" }, { - "nodeId": "094bc4ed-5c68-4342-84f4-51056c755796", - "fieldName": "value" + "nodeId": "e277e4b7-01cd-4daa-86ab-7bfa3cdcd9fd", + "fieldName": "model" }, { "nodeId": "100b3143-b3fb-4ff3-bb3c-8d4d3f89ae3a", "fieldName": "vae_model" }, + { + "nodeId": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "fieldName": "image_to_image_model" + }, { "nodeId": "f936ebb3-6902-4df9-a775-6a68bac2da70", "fieldName": "model" @@ -64,7 +56,276 @@ "version": "3.0.0", "category": "default" }, + "id": "dd607062-9e1b-48b9-89ad-9762cdfbb8f4", "nodes": [ + { + "id": "71a116e1-c631-48b3-923d-acea4753b887", + "type": "invocation", + "data": { + "id": "71a116e1-c631-48b3-923d-acea4753b887", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "ADD" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.3 + } + } + }, + "position": { + "x": -3050, + "y": -1550 + } + }, + { + "id": "00e2c587-f047-4413-ad15-bd31ea53ce22", + "type": "invocation", + "data": { + "id": "00e2c587-f047-4413-ad15-bd31ea53ce22", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "MUL" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.025 + } + } + }, + "position": { + "x": -3050, + "y": -1575 + } + }, + { + "id": "96e1bcd0-326b-4b67-8b14-239da2440aec", + "type": "invocation", + "data": { + "id": "96e1bcd0-326b-4b67-8b14-239da2440aec", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "MUL" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.45 + } + } + }, + "position": { + "x": -3050, + "y": -1200 + } + }, + { + "id": "75a89685-0f82-40ed-9b88-e583673be9fc", + "type": "invocation", + "data": { + "id": "75a89685-0f82-40ed-9b88-e583673be9fc", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "ADD" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.15 + } + } + }, + "position": { + "x": -3050, + "y": -1225 + } + }, + { + "id": "1ed88043-3519-41d5-a895-07944f03de70", + "type": "invocation", + "data": { + "id": "1ed88043-3519-41d5-a895-07944f03de70", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "ADD" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.3 + } + } + }, + "position": { + "x": -3050, + "y": -1650 + } + }, + { + "id": "9b281506-4079-4a3d-ab40-b386156fcd21", + "type": "invocation", + "data": { + "id": "9b281506-4079-4a3d-ab40-b386156fcd21", + "type": "float_math", + "version": "1.0.1", + "label": "", + "notes": "", + "isOpen": false, + "isIntermediate": true, + "useCache": true, + "inputs": { + "operation": { + "name": "operation", + "label": "", + "value": "MUL" + }, + "a": { + "name": "a", + "label": "", + "value": 1 + }, + "b": { + "name": "b", + "label": "", + "value": 0.032 + } + } + }, + "position": { + "x": -3050, + "y": -1850 + } + }, + { + "id": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "type": "invocation", + "data": { + "id": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "type": "spandrel_image_to_image_autoscale", + "version": "1.0.0", + "label": "", + "notes": "", + "isOpen": true, + "isIntermediate": true, + "useCache": true, + "inputs": { + "board": { + "name": "board", + "label": "" + }, + "metadata": { + "name": "metadata", + "label": "" + }, + "image": { + "name": "image", + "label": "Image to Upscale" + }, + "image_to_image_model": { + "name": "image_to_image_model", + "label": "", + "value": { + "key": "38bb1a29-8ede-42ba-b77f-64b3478896eb", + "hash": "blake3:e52fdbee46a484ebe9b3b20ea0aac0a35a453ab6d0d353da00acfd35ce7a91ed", + "name": "4xNomosWebPhoto_esrgan", + "base": "sdxl", + "type": "spandrel_image_to_image" + } + }, + "tile_size": { + "name": "tile_size", + "label": "", + "value": 512 + }, + "scale": { + "name": "scale", + "label": "Scale (2x, 4x, 8x, 16x)", + "value": 2 + }, + "fit_to_multiple_of_8": { + "name": "fit_to_multiple_of_8", + "label": "", + "value": true + } + } + }, + "position": { + "x": -4750, + "y": -2125 + } + }, { "id": "f936ebb3-6902-4df9-a775-6a68bac2da70", "type": "invocation", @@ -80,10 +341,10 @@ "inputs": { "model": { "name": "model", - "label": "ControlNet Model - choose xinsir's tile ControlNet", + "label": "ControlNet Model - Choose a Tile ControlNet", "value": { - "key": "845b6959-1657-4164-be33-fe0f63ad1752", - "hash": "random:3b602344599a53b4e4c80a2259362e122543e6f9e8e428be76ab910f9368704b", + "key": "74f4651f-0ace-4b7b-b616-e98360257797", + "hash": "blake3:167a5b84583aaed3e5c8d660b45830e82e1c602743c689d3c27773c6c8b85b4a", "name": "controlnet-tile-sdxl-1.0", "base": "sdxl", "type": "controlnet" @@ -92,8 +353,8 @@ } }, "position": { - "x": -3983.6167650620723, - "y": -1329.1431151846386 + "x": -3450, + "y": -1450 } }, { @@ -122,8 +383,8 @@ } }, "position": { - "x": -4000, - "y": -1800 + "x": -4025, + "y": -2075 } }, { @@ -142,13 +403,13 @@ "value": { "name": "value", "label": "Tiled VAE (Saves VRAM, Color Inconsistency)", - "value": false + "value": true } } }, "position": { - "x": -2746.0467136971292, - "y": -2219.070070545694 + "x": -2675, + "y": -2475 } }, { @@ -172,7 +433,7 @@ } }, "position": { - "x": -3525, + "x": -3500, "y": -2525 } }, @@ -197,115 +458,10 @@ } }, "position": { - "x": -3525, + "x": -3500, "y": -2825 } }, - { - "id": "6daa9526-382b-491d-964f-f53fc308664f", - "type": "invocation", - "data": { - "id": "6daa9526-382b-491d-964f-f53fc308664f", - "type": "float_math", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "ADD" - }, - "a": { - "name": "a", - "label": "", - "value": 0.35 - }, - "b": { - "name": "b", - "label": "", - "value": 100 - } - } - }, - "position": { - "x": -3500, - "y": -1450 - } - }, - { - "id": "f1afd295-860f-48b6-a76a-90609bf2cc11", - "type": "invocation", - "data": { - "id": "f1afd295-860f-48b6-a76a-90609bf2cc11", - "type": "float_math", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "MUL" - }, - "a": { - "name": "a", - "label": "", - "value": 1 - }, - "b": { - "name": "b", - "label": "", - "value": 0.013 - } - } - }, - "position": { - "x": -3500, - "y": -1550 - } - }, - { - "id": "88ae723e-4933-4371-b52d-3ada52a59d36", - "type": "invocation", - "data": { - "id": "88ae723e-4933-4371-b52d-3ada52a59d36", - "type": "float_math", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "ADD" - }, - "a": { - "name": "a", - "label": "", - "value": 0 - }, - "b": { - "name": "b", - "label": "", - "value": 100 - } - } - }, - "position": { - "x": -3500, - "y": -1500 - } - }, { "id": "1dd915a3-6756-48ed-b68b-ee3b4bd06c1d", "type": "invocation", @@ -327,7 +483,7 @@ "a": { "name": "a", "label": "Creativity Control (-10 to 10)", - "value": 5 + "value": 0 }, "b": { "name": "b", @@ -446,146 +602,6 @@ "y": -2025 } }, - { - "id": "e4d5ca7c-8fcf-4c59-9c58-67194c80dc73", - "type": "invocation", - "data": { - "id": "e4d5ca7c-8fcf-4c59-9c58-67194c80dc73", - "type": "float_math", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "ADD" - }, - "a": { - "name": "a", - "label": "", - "value": 0 - }, - "b": { - "name": "b", - "label": "", - "value": 1 - } - } - }, - "position": { - "x": -3500, - "y": -1925 - } - }, - { - "id": "696de0e1-cdd2-42e8-abeb-57a926bc6df6", - "type": "invocation", - "data": { - "id": "696de0e1-cdd2-42e8-abeb-57a926bc6df6", - "type": "float_math", - "version": "1.0.1", - "label": "Sharpness Input", - "notes": "", - "isOpen": true, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "ADD" - }, - "a": { - "name": "a", - "label": "Sharpness Control (-10 to 10)", - "value": 0 - }, - "b": { - "name": "b", - "label": "", - "value": 10 - } - } - }, - "position": { - "x": -4750, - "y": -2275 - } - }, - { - "id": "79390b60-4077-4f94-ad0a-4229cc73ddb2", - "type": "invocation", - "data": { - "id": "79390b60-4077-4f94-ad0a-4229cc73ddb2", - "type": "float_math", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "MUL" - }, - "a": { - "name": "a", - "label": "", - "value": 1 - }, - "b": { - "name": "b", - "label": "", - "value": 3.75 - } - } - }, - "position": { - "x": -4750, - "y": -2000 - } - }, - { - "id": "4950132a-2d06-4571-b2c0-55cb37a31e9b", - "type": "invocation", - "data": { - "id": "4950132a-2d06-4571-b2c0-55cb37a31e9b", - "type": "float_math", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "ADD" - }, - "a": { - "name": "a", - "label": "", - "value": 25 - }, - "b": { - "name": "b", - "label": "", - "value": 1 - } - } - }, - "position": { - "x": -4750, - "y": -1950 - } - }, { "id": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", "type": "invocation", @@ -595,7 +611,7 @@ "version": "1.0.1", "label": "Structural Input", "notes": "", - "isOpen": false, + "isOpen": true, "isIntermediate": true, "useCache": true, "inputs": { @@ -617,78 +633,8 @@ } }, "position": { - "x": -3500, - "y": -1700 - } - }, - { - "id": "bc53651f-208b-440c-be30-f93f72ae700e", - "type": "invocation", - "data": { - "id": "bc53651f-208b-440c-be30-f93f72ae700e", - "type": "float_math", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "MUL" - }, - "a": { - "name": "a", - "label": "", - "value": 1 - }, - "b": { - "name": "b", - "label": "", - "value": 0.025 - } - } - }, - "position": { - "x": -3500, - "y": -1650 - } - }, - { - "id": "67346654-cac0-446a-8cde-9af4b5a029a6", - "type": "invocation", - "data": { - "id": "67346654-cac0-446a-8cde-9af4b5a029a6", - "type": "float_math", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "ADD" - }, - "a": { - "name": "a", - "label": "", - "value": 0.3 - }, - "b": { - "name": "b", - "label": "", - "value": 1 - } - } - }, - "position": { - "x": -3500, - "y": -1600 + "x": -3050, + "y": -2100 } }, { @@ -711,8 +657,8 @@ } }, "position": { - "x": -3125, - "y": -1500 + "x": -2275, + "y": -2075 } }, { @@ -722,7 +668,7 @@ "id": "b78f53b6-2eae-4956-97b4-7e73768d1491", "type": "controlnet", "version": "1.1.2", - "label": "ControlNet (use xinsir's tile ControlNet)", + "label": "Initial Control (Use Tile)", "notes": "", "isOpen": true, "isIntermediate": true, @@ -764,8 +710,8 @@ } }, "position": { - "x": -3493.4229674963885, - "y": -1359.2223984776113 + "x": -2675, + "y": -1775 } }, { @@ -836,7 +782,7 @@ } }, "position": { - "x": -3525, + "x": -3500, "y": -2300 } }, @@ -857,9 +803,9 @@ "name": "vae_model", "label": "", "value": { - "key": "4bc2bddf-94d9-4efe-a8e2-5eda28710f4c", - "hash": "random:67e47a77a1fcef9c0f5cd5d889d71c191f07383a0bf587f1849b2bc3f359440a", - "name": "sdxl-vae-fp16-fix", + "key": "ff926845-090e-4d46-b81e-30289ee47474", + "hash": "9705ab1c31fa96b308734214fb7571a958621c7a9247eed82b7d277145f8d9fa", + "name": "VAEFix", "base": "sdxl", "type": "vae" } @@ -867,7 +813,7 @@ } }, "position": { - "x": -4000, + "x": -4025, "y": -2575 } }, @@ -886,12 +832,19 @@ "inputs": { "model": { "name": "model", - "label": "SDXL Model" + "label": "SDXL Model", + "value": { + "key": "ab191f73-68d2-492c-8aec-b438a8cf0f45", + "hash": "blake3:2d50e940627e3bf555f015280ec0976d5c1fa100f7bc94e95ffbfc770e98b6fe", + "name": "CustomXLv7", + "base": "sdxl", + "type": "main" + } } } }, "position": { - "x": -4000, + "x": -4025, "y": -2825 } }, @@ -963,7 +916,7 @@ } }, "position": { - "x": -3525, + "x": -3500, "y": -2600 } }, @@ -976,7 +929,7 @@ "version": "1.2.2", "label": "", "notes": "", - "isOpen": false, + "isOpen": true, "isIntermediate": true, "useCache": true, "inputs": { @@ -1006,49 +959,7 @@ }, "position": { "x": -4400, - "y": -1875 - } - }, - { - "id": "53c2d5fd-863d-4950-93e0-628f3d61b493", - "type": "invocation", - "data": { - "id": "53c2d5fd-863d-4950-93e0-628f3d61b493", - "type": "unsharp_mask", - "version": "1.2.2", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "board": { - "name": "board", - "label": "" - }, - "metadata": { - "name": "metadata", - "label": "" - }, - "image": { - "name": "image", - "label": "" - }, - "radius": { - "name": "radius", - "label": "", - "value": 2 - }, - "strength": { - "name": "strength", - "label": "", - "value": 50 - } - } - }, - "position": { - "x": -4750, - "y": -1875 + "y": -2125 } }, { @@ -1090,8 +1001,8 @@ } }, "position": { - "x": -4000, - "y": -1875 + "x": -4025, + "y": -2125 } }, { @@ -1141,8 +1052,8 @@ } }, "position": { - "x": -2750, - "y": -2575 + "x": -2675, + "y": -2825 } }, { @@ -1230,150 +1141,8 @@ } }, "position": { - "x": -3125, - "y": -2575 - } - }, - { - "id": "1ba845a6-eb88-49a1-a490-5fe6754f3ec9", - "type": "invocation", - "data": { - "id": "1ba845a6-eb88-49a1-a490-5fe6754f3ec9", - "type": "integer", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": true, - "isIntermediate": true, - "useCache": true, - "inputs": { - "value": { - "name": "value", - "label": "Scale (2x, 4x)", - "value": 2 - } - } - }, - "position": { - "x": -4400, - "y": -2175 - } - }, - { - "id": "d350feac-9686-4e0d-bd46-a96bd2630818", - "type": "invocation", - "data": { - "id": "d350feac-9686-4e0d-bd46-a96bd2630818", - "type": "integer_math", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "MUL" - }, - "a": { - "name": "a", - "label": "", - "value": 1 - }, - "b": { - "name": "b", - "label": "", - "value": 1 - } - } - }, - "position": { - "x": -4400, - "y": -1950 - } - }, - { - "id": "5b256f14-caab-40ff-b8f0-9679cd542163", - "type": "invocation", - "data": { - "id": "5b256f14-caab-40ff-b8f0-9679cd542163", - "type": "integer_math", - "version": "1.0.1", - "label": "", - "notes": "", - "isOpen": false, - "isIntermediate": true, - "useCache": true, - "inputs": { - "operation": { - "name": "operation", - "label": "", - "value": "MUL" - }, - "a": { - "name": "a", - "label": "", - "value": 1 - }, - "b": { - "name": "b", - "label": "", - "value": 1 - } - } - }, - "position": { - "x": -4400, - "y": -2000 - } - }, - { - "id": "7671553a-cd4b-4e25-8332-9d5667e64493", - "type": "invocation", - "data": { - "id": "7671553a-cd4b-4e25-8332-9d5667e64493", - "type": "img_resize", - "version": "1.2.2", - "label": "", - "notes": "", - "isOpen": true, - "isIntermediate": true, - "useCache": true, - "inputs": { - "board": { - "name": "board", - "label": "" - }, - "metadata": { - "name": "metadata", - "label": "" - }, - "image": { - "name": "image", - "label": "" - }, - "width": { - "name": "width", - "label": "", - "value": 512 - }, - "height": { - "name": "height", - "label": "", - "value": 512 - }, - "resample_mode": { - "name": "resample_mode", - "label": "", - "value": "lanczos" - } - } - }, - "position": { - "x": -4375, - "y": -1825 + "x": -3050, + "y": -2825 } }, { @@ -1383,7 +1152,7 @@ "id": "be4082d6-e238-40ea-a9df-fc0d725e8895", "type": "controlnet", "version": "1.1.2", - "label": "ControlNet (use xinsir's tile ControlNet)", + "label": "Second Phase Control (Use Tile)", "notes": "", "isOpen": true, "isIntermediate": true, @@ -1410,7 +1179,7 @@ "end_step_percent": { "name": "end_step_percent", "label": "", - "value": 0.8 + "value": 0.85 }, "control_mode": { "name": "control_mode", @@ -1425,8 +1194,8 @@ } }, "position": { - "x": -3131.577032503611, - "y": -1392.1075609956667 + "x": -2675, + "y": -1325 } }, { @@ -1465,148 +1234,194 @@ } }, "position": { - "x": -4000, - "y": -1750 - } - }, - { - "id": "f0cd0d2f-9614-43f7-9944-a75b8d5ccd65", - "type": "invocation", - "data": { - "id": "f0cd0d2f-9614-43f7-9944-a75b8d5ccd65", - "type": "esrgan", - "version": "1.3.2", - "label": "", - "notes": "", - "isOpen": true, - "isIntermediate": true, - "useCache": true, - "inputs": { - "board": { - "name": "board", - "label": "" - }, - "metadata": { - "name": "metadata", - "label": "" - }, - "image": { - "name": "image", - "label": "" - }, - "model_name": { - "name": "model_name", - "label": "Upscaling Model", - "value": "RealESRGAN_x4plus.pth" - }, - "tile_size": { - "name": "tile_size", - "label": "", - "value": 500 - } - } - }, - "position": { - "x": -4750, - "y": -1825 - } - }, - { - "id": "7dbb756b-7d79-431c-a46d-d8f7b082c127", - "type": "invocation", - "data": { - "id": "7dbb756b-7d79-431c-a46d-d8f7b082c127", - "version": "1.0.1", - "label": "", - "notes": "", - "type": "float_to_int", - "inputs": { - "value": { - "name": "value", - "label": "", - "value": 0 - }, - "multiple": { - "name": "multiple", - "label": "", - "value": 8 - }, - "method": { - "name": "method", - "label": "", - "value": "Floor" - } - }, - "isOpen": false, - "isIntermediate": true, - "useCache": true - }, - "position": { - "x": -4000, - "y": -1950 - } - }, - { - "id": "5ca87ace-edf9-49c7-a424-cd42416b86a7", - "type": "invocation", - "data": { - "id": "5ca87ace-edf9-49c7-a424-cd42416b86a7", - "version": "1.0.2", - "label": "", - "notes": "", - "type": "image", - "inputs": { - "image": { - "name": "image", - "label": "Image to Upscale" - } - }, - "isOpen": true, - "isIntermediate": true, - "useCache": true - }, - "position": { - "x": -4750, - "y": -2850 - } - }, - { - "id": "f5d9bf3b-2646-4b17-9894-20fd2b4218ea", - "type": "invocation", - "data": { - "id": "f5d9bf3b-2646-4b17-9894-20fd2b4218ea", - "version": "1.0.1", - "label": "", - "notes": "", - "type": "float_to_int", - "inputs": { - "value": { - "name": "value", - "label": "", - "value": 8 - }, - "multiple": { - "name": "multiple", - "label": "", - "value": 8 - }, - "method": { - "name": "method", - "label": "", - "value": "Floor" - } - }, - "isOpen": false, - "isIntermediate": true, - "useCache": true - }, - "position": { - "x": -4000, - "y": -2000 + "x": -4025, + "y": -2025 } } ], "edges": [ + { + "id": "9b281506-4079-4a3d-ab40-b386156fcd21-75a89685-0f82-40ed-9b88-e583673be9fc-collapsed", + "type": "collapsed", + "source": "9b281506-4079-4a3d-ab40-b386156fcd21", + "target": "75a89685-0f82-40ed-9b88-e583673be9fc" + }, + { + "id": "9b281506-4079-4a3d-ab40-b386156fcd21-1ed88043-3519-41d5-a895-07944f03de70-collapsed", + "type": "collapsed", + "source": "9b281506-4079-4a3d-ab40-b386156fcd21", + "target": "1ed88043-3519-41d5-a895-07944f03de70" + }, + { + "id": "49a8cc12-aa19-48c5-b6b3-04e0b603b384-c8f5c671-8c87-4d96-a75e-a9937ac6bc03-collapsed", + "type": "collapsed", + "source": "49a8cc12-aa19-48c5-b6b3-04e0b603b384", + "target": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03" + }, + { + "id": "reactflow__edge-c8f5c671-8c87-4d96-a75e-a9937ac6bc03value-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7adenoising_start", + "type": "default", + "source": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03", + "target": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", + "sourceHandle": "value", + "targetHandle": "denoising_start" + }, + { + "id": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c-49a8cc12-aa19-48c5-b6b3-04e0b603b384-collapsed", + "type": "collapsed", + "source": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c", + "target": "49a8cc12-aa19-48c5-b6b3-04e0b603b384" + }, + { + "id": "1dd915a3-6756-48ed-b68b-ee3b4bd06c1d-14e65dbe-4249-4b25-9a63-3a10cfaeb61c-collapsed", + "type": "collapsed", + "source": "1dd915a3-6756-48ed-b68b-ee3b4bd06c1d", + "target": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c" + }, + { + "id": "75a89685-0f82-40ed-9b88-e583673be9fc-96e1bcd0-326b-4b67-8b14-239da2440aec-collapsed", + "type": "collapsed", + "source": "75a89685-0f82-40ed-9b88-e583673be9fc", + "target": "96e1bcd0-326b-4b67-8b14-239da2440aec" + }, + { + "id": "00e2c587-f047-4413-ad15-bd31ea53ce22-71a116e1-c631-48b3-923d-acea4753b887-collapsed", + "type": "collapsed", + "source": "00e2c587-f047-4413-ad15-bd31ea53ce22", + "target": "71a116e1-c631-48b3-923d-acea4753b887" + }, + { + "id": "reactflow__edge-71a116e1-c631-48b3-923d-acea4753b887value-be4082d6-e238-40ea-a9df-fc0d725e8895begin_step_percent", + "type": "default", + "source": "71a116e1-c631-48b3-923d-acea4753b887", + "target": "be4082d6-e238-40ea-a9df-fc0d725e8895", + "sourceHandle": "value", + "targetHandle": "begin_step_percent" + }, + { + "id": "reactflow__edge-71a116e1-c631-48b3-923d-acea4753b887value-b78f53b6-2eae-4956-97b4-7e73768d1491end_step_percent", + "type": "default", + "source": "71a116e1-c631-48b3-923d-acea4753b887", + "target": "b78f53b6-2eae-4956-97b4-7e73768d1491", + "sourceHandle": "value", + "targetHandle": "end_step_percent" + }, + { + "id": "reactflow__edge-00e2c587-f047-4413-ad15-bd31ea53ce22value-71a116e1-c631-48b3-923d-acea4753b887a", + "type": "default", + "source": "00e2c587-f047-4413-ad15-bd31ea53ce22", + "target": "71a116e1-c631-48b3-923d-acea4753b887", + "sourceHandle": "value", + "targetHandle": "a", + "hidden": true + }, + { + "id": "reactflow__edge-bd094e2f-41e5-4b61-9f7b-56cf337d53favalue-00e2c587-f047-4413-ad15-bd31ea53ce22a", + "type": "default", + "source": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", + "target": "00e2c587-f047-4413-ad15-bd31ea53ce22", + "sourceHandle": "value", + "targetHandle": "a" + }, + { + "id": "reactflow__edge-96e1bcd0-326b-4b67-8b14-239da2440aecvalue-be4082d6-e238-40ea-a9df-fc0d725e8895control_weight", + "type": "default", + "source": "96e1bcd0-326b-4b67-8b14-239da2440aec", + "target": "be4082d6-e238-40ea-a9df-fc0d725e8895", + "sourceHandle": "value", + "targetHandle": "control_weight" + }, + { + "id": "reactflow__edge-75a89685-0f82-40ed-9b88-e583673be9fcvalue-96e1bcd0-326b-4b67-8b14-239da2440aeca", + "type": "default", + "source": "75a89685-0f82-40ed-9b88-e583673be9fc", + "target": "96e1bcd0-326b-4b67-8b14-239da2440aec", + "sourceHandle": "value", + "targetHandle": "a", + "hidden": true + }, + { + "id": "reactflow__edge-9b281506-4079-4a3d-ab40-b386156fcd21value-75a89685-0f82-40ed-9b88-e583673be9fca", + "type": "default", + "source": "9b281506-4079-4a3d-ab40-b386156fcd21", + "target": "75a89685-0f82-40ed-9b88-e583673be9fc", + "sourceHandle": "value", + "targetHandle": "a", + "hidden": true + }, + { + "id": "reactflow__edge-1ed88043-3519-41d5-a895-07944f03de70value-b78f53b6-2eae-4956-97b4-7e73768d1491control_weight", + "type": "default", + "source": "1ed88043-3519-41d5-a895-07944f03de70", + "target": "b78f53b6-2eae-4956-97b4-7e73768d1491", + "sourceHandle": "value", + "targetHandle": "control_weight" + }, + { + "id": "reactflow__edge-9b281506-4079-4a3d-ab40-b386156fcd21value-1ed88043-3519-41d5-a895-07944f03de70a", + "type": "default", + "source": "9b281506-4079-4a3d-ab40-b386156fcd21", + "target": "1ed88043-3519-41d5-a895-07944f03de70", + "sourceHandle": "value", + "targetHandle": "a", + "hidden": true + }, + { + "id": "reactflow__edge-bd094e2f-41e5-4b61-9f7b-56cf337d53favalue-9b281506-4079-4a3d-ab40-b386156fcd21a", + "type": "default", + "source": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", + "target": "9b281506-4079-4a3d-ab40-b386156fcd21", + "sourceHandle": "value", + "targetHandle": "a" + }, + { + "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeheight-8923451b-5a27-4395-b7f2-dce875fca6f5height", + "type": "default", + "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "target": "8923451b-5a27-4395-b7f2-dce875fca6f5", + "sourceHandle": "height", + "targetHandle": "height" + }, + { + "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebewidth-8923451b-5a27-4395-b7f2-dce875fca6f5width", + "type": "default", + "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "target": "8923451b-5a27-4395-b7f2-dce875fca6f5", + "sourceHandle": "width", + "targetHandle": "width" + }, + { + "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeimage-b78f53b6-2eae-4956-97b4-7e73768d1491image", + "type": "default", + "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "target": "b78f53b6-2eae-4956-97b4-7e73768d1491", + "sourceHandle": "image", + "targetHandle": "image" + }, + { + "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeimage-be4082d6-e238-40ea-a9df-fc0d725e8895image", + "type": "default", + "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "target": "be4082d6-e238-40ea-a9df-fc0d725e8895", + "sourceHandle": "image", + "targetHandle": "image" + }, + { + "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeimage-117f982a-03da-49b1-bf9f-29711160ac02image", + "type": "default", + "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "target": "117f982a-03da-49b1-bf9f-29711160ac02", + "sourceHandle": "image", + "targetHandle": "image" + }, + { + "id": "reactflow__edge-011039f6-04cf-4607-8eb1-3304eb819c8cimage-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeimage", + "type": "default", + "source": "011039f6-04cf-4607-8eb1-3304eb819c8c", + "target": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", + "sourceHandle": "image", + "targetHandle": "image" + }, { "id": "reactflow__edge-f936ebb3-6902-4df9-a775-6a68bac2da70model-be4082d6-e238-40ea-a9df-fc0d725e8895control_model", "type": "default", @@ -1623,24 +1438,6 @@ "sourceHandle": "model", "targetHandle": "control_model" }, - { - "id": "49a8cc12-aa19-48c5-b6b3-04e0b603b384-c8f5c671-8c87-4d96-a75e-a9937ac6bc03-collapsed", - "type": "collapsed", - "source": "49a8cc12-aa19-48c5-b6b3-04e0b603b384", - "target": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03" - }, - { - "id": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c-49a8cc12-aa19-48c5-b6b3-04e0b603b384-collapsed", - "type": "collapsed", - "source": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c", - "target": "49a8cc12-aa19-48c5-b6b3-04e0b603b384" - }, - { - "id": "1dd915a3-6756-48ed-b68b-ee3b4bd06c1d-14e65dbe-4249-4b25-9a63-3a10cfaeb61c-collapsed", - "type": "collapsed", - "source": "1dd915a3-6756-48ed-b68b-ee3b4bd06c1d", - "target": "14e65dbe-4249-4b25-9a63-3a10cfaeb61c" - }, { "id": "reactflow__edge-00239057-20d4-4cd2-a010-28727b256ea2value-8923451b-5a27-4395-b7f2-dce875fca6f5seed", "type": "default", @@ -1665,36 +1462,6 @@ "sourceHandle": "value", "targetHandle": "tiled" }, - { - "id": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03-e4d5ca7c-8fcf-4c59-9c58-67194c80dc73-collapsed", - "type": "collapsed", - "source": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03", - "target": "e4d5ca7c-8fcf-4c59-9c58-67194c80dc73" - }, - { - "id": "d350feac-9686-4e0d-bd46-a96bd2630818-7dbb756b-7d79-431c-a46d-d8f7b082c127-collapsed", - "type": "collapsed", - "source": "d350feac-9686-4e0d-bd46-a96bd2630818", - "target": "7dbb756b-7d79-431c-a46d-d8f7b082c127" - }, - { - "id": "5b256f14-caab-40ff-b8f0-9679cd542163-f5d9bf3b-2646-4b17-9894-20fd2b4218ea-collapsed", - "type": "collapsed", - "source": "5b256f14-caab-40ff-b8f0-9679cd542163", - "target": "f5d9bf3b-2646-4b17-9894-20fd2b4218ea" - }, - { - "id": "4950132a-2d06-4571-b2c0-55cb37a31e9b-041c59cc-f9e4-4dc9-8b31-84648c5f3ebe-collapsed", - "type": "collapsed", - "source": "4950132a-2d06-4571-b2c0-55cb37a31e9b", - "target": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe" - }, - { - "id": "4950132a-2d06-4571-b2c0-55cb37a31e9b-53c2d5fd-863d-4950-93e0-628f3d61b493-collapsed", - "type": "collapsed", - "source": "4950132a-2d06-4571-b2c0-55cb37a31e9b", - "target": "53c2d5fd-863d-4950-93e0-628f3d61b493" - }, { "id": "reactflow__edge-f5ca24ee-21c5-4c8c-8d3c-371b5079b086value-27215391-b20e-412a-b854-7fa5927f5437style", "type": "default", @@ -1727,104 +1494,6 @@ "sourceHandle": "value", "targetHandle": "prompt" }, - { - "id": "88ae723e-4933-4371-b52d-3ada52a59d36-6daa9526-382b-491d-964f-f53fc308664f-collapsed", - "type": "collapsed", - "source": "88ae723e-4933-4371-b52d-3ada52a59d36", - "target": "6daa9526-382b-491d-964f-f53fc308664f" - }, - { - "id": "f1afd295-860f-48b6-a76a-90609bf2cc11-88ae723e-4933-4371-b52d-3ada52a59d36-collapsed", - "type": "collapsed", - "source": "f1afd295-860f-48b6-a76a-90609bf2cc11", - "target": "88ae723e-4933-4371-b52d-3ada52a59d36" - }, - { - "id": "bc53651f-208b-440c-be30-f93f72ae700e-67346654-cac0-446a-8cde-9af4b5a029a6-collapsed", - "type": "collapsed", - "source": "bc53651f-208b-440c-be30-f93f72ae700e", - "target": "67346654-cac0-446a-8cde-9af4b5a029a6" - }, - { - "id": "reactflow__edge-67346654-cac0-446a-8cde-9af4b5a029a6value-be4082d6-e238-40ea-a9df-fc0d725e8895begin_step_percent", - "type": "default", - "source": "67346654-cac0-446a-8cde-9af4b5a029a6", - "target": "be4082d6-e238-40ea-a9df-fc0d725e8895", - "sourceHandle": "value", - "targetHandle": "begin_step_percent" - }, - { - "id": "reactflow__edge-67346654-cac0-446a-8cde-9af4b5a029a6value-b78f53b6-2eae-4956-97b4-7e73768d1491end_step_percent", - "type": "default", - "source": "67346654-cac0-446a-8cde-9af4b5a029a6", - "target": "b78f53b6-2eae-4956-97b4-7e73768d1491", - "sourceHandle": "value", - "targetHandle": "end_step_percent" - }, - { - "id": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa-f1afd295-860f-48b6-a76a-90609bf2cc11-collapsed", - "type": "collapsed", - "source": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", - "target": "f1afd295-860f-48b6-a76a-90609bf2cc11" - }, - { - "id": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa-bc53651f-208b-440c-be30-f93f72ae700e-collapsed", - "type": "collapsed", - "source": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", - "target": "bc53651f-208b-440c-be30-f93f72ae700e" - }, - { - "id": "reactflow__edge-bc53651f-208b-440c-be30-f93f72ae700evalue-67346654-cac0-446a-8cde-9af4b5a029a6b", - "type": "default", - "source": "bc53651f-208b-440c-be30-f93f72ae700e", - "target": "67346654-cac0-446a-8cde-9af4b5a029a6", - "sourceHandle": "value", - "targetHandle": "b", - "hidden": true - }, - { - "id": "reactflow__edge-6daa9526-382b-491d-964f-f53fc308664fvalue-b78f53b6-2eae-4956-97b4-7e73768d1491control_weight", - "type": "default", - "source": "6daa9526-382b-491d-964f-f53fc308664f", - "target": "b78f53b6-2eae-4956-97b4-7e73768d1491", - "sourceHandle": "value", - "targetHandle": "control_weight" - }, - { - "id": "reactflow__edge-88ae723e-4933-4371-b52d-3ada52a59d36value-6daa9526-382b-491d-964f-f53fc308664fb", - "type": "default", - "source": "88ae723e-4933-4371-b52d-3ada52a59d36", - "target": "6daa9526-382b-491d-964f-f53fc308664f", - "sourceHandle": "value", - "targetHandle": "b", - "hidden": true - }, - { - "id": "reactflow__edge-88ae723e-4933-4371-b52d-3ada52a59d36value-be4082d6-e238-40ea-a9df-fc0d725e8895control_weight", - "type": "default", - "source": "88ae723e-4933-4371-b52d-3ada52a59d36", - "target": "be4082d6-e238-40ea-a9df-fc0d725e8895", - "sourceHandle": "value", - "targetHandle": "control_weight" - }, - { - "id": "reactflow__edge-f1afd295-860f-48b6-a76a-90609bf2cc11value-88ae723e-4933-4371-b52d-3ada52a59d36b", - "type": "default", - "source": "f1afd295-860f-48b6-a76a-90609bf2cc11", - "target": "88ae723e-4933-4371-b52d-3ada52a59d36", - "sourceHandle": "value", - "targetHandle": "b", - "hidden": true - }, - { - "id": "reactflow__edge-bd094e2f-41e5-4b61-9f7b-56cf337d53favalue-f1afd295-860f-48b6-a76a-90609bf2cc11a", - "type": "default", - "source": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", - "target": "f1afd295-860f-48b6-a76a-90609bf2cc11", - "sourceHandle": "value", - "targetHandle": "a", - "hidden": true - }, { "id": "reactflow__edge-1dd915a3-6756-48ed-b68b-ee3b4bd06c1dvalue-14e65dbe-4249-4b25-9a63-3a10cfaeb61ca", "type": "default", @@ -1834,23 +1503,6 @@ "targetHandle": "a", "hidden": true }, - { - "id": "reactflow__edge-e4d5ca7c-8fcf-4c59-9c58-67194c80dc73value-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7adenoising_start", - "type": "default", - "source": "e4d5ca7c-8fcf-4c59-9c58-67194c80dc73", - "target": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", - "sourceHandle": "value", - "targetHandle": "denoising_start" - }, - { - "id": "reactflow__edge-c8f5c671-8c87-4d96-a75e-a9937ac6bc03value-e4d5ca7c-8fcf-4c59-9c58-67194c80dc73b", - "type": "default", - "source": "c8f5c671-8c87-4d96-a75e-a9937ac6bc03", - "target": "e4d5ca7c-8fcf-4c59-9c58-67194c80dc73", - "sourceHandle": "value", - "targetHandle": "b", - "hidden": true - }, { "id": "reactflow__edge-49a8cc12-aa19-48c5-b6b3-04e0b603b384value-c8f5c671-8c87-4d96-a75e-a9937ac6bc03a", "type": "default", @@ -1869,56 +1521,6 @@ "targetHandle": "a", "hidden": true }, - { - "id": "79390b60-4077-4f94-ad0a-4229cc73ddb2-4950132a-2d06-4571-b2c0-55cb37a31e9b-collapsed", - "type": "collapsed", - "source": "79390b60-4077-4f94-ad0a-4229cc73ddb2", - "target": "4950132a-2d06-4571-b2c0-55cb37a31e9b" - }, - { - "id": "reactflow__edge-4950132a-2d06-4571-b2c0-55cb37a31e9bvalue-041c59cc-f9e4-4dc9-8b31-84648c5f3ebestrength", - "type": "default", - "source": "4950132a-2d06-4571-b2c0-55cb37a31e9b", - "target": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", - "sourceHandle": "value", - "targetHandle": "strength", - "hidden": true - }, - { - "id": "reactflow__edge-4950132a-2d06-4571-b2c0-55cb37a31e9bvalue-53c2d5fd-863d-4950-93e0-628f3d61b493strength", - "type": "default", - "source": "4950132a-2d06-4571-b2c0-55cb37a31e9b", - "target": "53c2d5fd-863d-4950-93e0-628f3d61b493", - "sourceHandle": "value", - "targetHandle": "strength", - "hidden": true - }, - { - "id": "reactflow__edge-79390b60-4077-4f94-ad0a-4229cc73ddb2value-4950132a-2d06-4571-b2c0-55cb37a31e9bb", - "type": "default", - "source": "79390b60-4077-4f94-ad0a-4229cc73ddb2", - "target": "4950132a-2d06-4571-b2c0-55cb37a31e9b", - "sourceHandle": "value", - "targetHandle": "b", - "hidden": true - }, - { - "id": "reactflow__edge-696de0e1-cdd2-42e8-abeb-57a926bc6df6value-79390b60-4077-4f94-ad0a-4229cc73ddb2a", - "type": "default", - "source": "696de0e1-cdd2-42e8-abeb-57a926bc6df6", - "target": "79390b60-4077-4f94-ad0a-4229cc73ddb2", - "sourceHandle": "value", - "targetHandle": "a" - }, - { - "id": "reactflow__edge-bd094e2f-41e5-4b61-9f7b-56cf337d53favalue-bc53651f-208b-440c-be30-f93f72ae700ea", - "type": "default", - "source": "bd094e2f-41e5-4b61-9f7b-56cf337d53fa", - "target": "bc53651f-208b-440c-be30-f93f72ae700e", - "sourceHandle": "value", - "targetHandle": "a", - "hidden": true - }, { "id": "reactflow__edge-6636a27a-f130-4a13-b3e5-50b44e4a566fcollection-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7acontrol", "type": "default", @@ -1943,14 +1545,6 @@ "sourceHandle": "control", "targetHandle": "item" }, - { - "id": "reactflow__edge-7671553a-cd4b-4e25-8332-9d5667e64493image-b78f53b6-2eae-4956-97b4-7e73768d1491image", - "type": "default", - "source": "7671553a-cd4b-4e25-8332-9d5667e64493", - "target": "b78f53b6-2eae-4956-97b4-7e73768d1491", - "sourceHandle": "image", - "targetHandle": "image" - }, { "id": "reactflow__edge-e277e4b7-01cd-4daa-86ab-7bfa3cdcd9fdclip2-27215391-b20e-412a-b854-7fa5927f5437clip2", "type": "default", @@ -2023,38 +1617,6 @@ "sourceHandle": "clip", "targetHandle": "clip" }, - { - "id": "reactflow__edge-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeimage-7671553a-cd4b-4e25-8332-9d5667e64493image", - "type": "default", - "source": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", - "target": "7671553a-cd4b-4e25-8332-9d5667e64493", - "sourceHandle": "image", - "targetHandle": "image" - }, - { - "id": "reactflow__edge-f0cd0d2f-9614-43f7-9944-a75b8d5ccd65image-041c59cc-f9e4-4dc9-8b31-84648c5f3ebeimage", - "type": "default", - "source": "f0cd0d2f-9614-43f7-9944-a75b8d5ccd65", - "target": "041c59cc-f9e4-4dc9-8b31-84648c5f3ebe", - "sourceHandle": "image", - "targetHandle": "image" - }, - { - "id": "reactflow__edge-53c2d5fd-863d-4950-93e0-628f3d61b493image-f0cd0d2f-9614-43f7-9944-a75b8d5ccd65image", - "type": "default", - "source": "53c2d5fd-863d-4950-93e0-628f3d61b493", - "target": "f0cd0d2f-9614-43f7-9944-a75b8d5ccd65", - "sourceHandle": "image", - "targetHandle": "image" - }, - { - "id": "reactflow__edge-5ca87ace-edf9-49c7-a424-cd42416b86a7image-53c2d5fd-863d-4950-93e0-628f3d61b493image", - "type": "default", - "source": "5ca87ace-edf9-49c7-a424-cd42416b86a7", - "target": "53c2d5fd-863d-4950-93e0-628f3d61b493", - "sourceHandle": "image", - "targetHandle": "image" - }, { "id": "reactflow__edge-8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7alatents-c3b60a50-8039-4924-90e3-8c608e1fecb5latents", "type": "default", @@ -2078,104 +1640,6 @@ "target": "8dba0d37-cd2e-4fe5-ae9f-5464b85a8a7a", "sourceHandle": "noise", "targetHandle": "noise" - }, - { - "id": "reactflow__edge-d350feac-9686-4e0d-bd46-a96bd2630818value-7dbb756b-7d79-431c-a46d-d8f7b082c127value", - "type": "default", - "source": "d350feac-9686-4e0d-bd46-a96bd2630818", - "target": "7dbb756b-7d79-431c-a46d-d8f7b082c127", - "sourceHandle": "value", - "targetHandle": "value", - "hidden": true - }, - { - "id": "reactflow__edge-5b256f14-caab-40ff-b8f0-9679cd542163value-f5d9bf3b-2646-4b17-9894-20fd2b4218eavalue", - "type": "default", - "source": "5b256f14-caab-40ff-b8f0-9679cd542163", - "target": "f5d9bf3b-2646-4b17-9894-20fd2b4218ea", - "sourceHandle": "value", - "targetHandle": "value", - "hidden": true - }, - { - "id": "reactflow__edge-7671553a-cd4b-4e25-8332-9d5667e64493height-8923451b-5a27-4395-b7f2-dce875fca6f5height", - "type": "default", - "source": "7671553a-cd4b-4e25-8332-9d5667e64493", - "target": "8923451b-5a27-4395-b7f2-dce875fca6f5", - "sourceHandle": "height", - "targetHandle": "height" - }, - { - "id": "reactflow__edge-7671553a-cd4b-4e25-8332-9d5667e64493width-8923451b-5a27-4395-b7f2-dce875fca6f5width", - "type": "default", - "source": "7671553a-cd4b-4e25-8332-9d5667e64493", - "target": "8923451b-5a27-4395-b7f2-dce875fca6f5", - "sourceHandle": "width", - "targetHandle": "width" - }, - { - "id": "reactflow__edge-7671553a-cd4b-4e25-8332-9d5667e64493image-117f982a-03da-49b1-bf9f-29711160ac02image", - "type": "default", - "source": "7671553a-cd4b-4e25-8332-9d5667e64493", - "target": "117f982a-03da-49b1-bf9f-29711160ac02", - "sourceHandle": "image", - "targetHandle": "image" - }, - { - "id": "reactflow__edge-7671553a-cd4b-4e25-8332-9d5667e64493image-be4082d6-e238-40ea-a9df-fc0d725e8895image", - "type": "default", - "source": "7671553a-cd4b-4e25-8332-9d5667e64493", - "target": "be4082d6-e238-40ea-a9df-fc0d725e8895", - "sourceHandle": "image", - "targetHandle": "image" - }, - { - "id": "reactflow__edge-7dbb756b-7d79-431c-a46d-d8f7b082c127value-7671553a-cd4b-4e25-8332-9d5667e64493height", - "type": "default", - "source": "7dbb756b-7d79-431c-a46d-d8f7b082c127", - "target": "7671553a-cd4b-4e25-8332-9d5667e64493", - "sourceHandle": "value", - "targetHandle": "height" - }, - { - "id": "reactflow__edge-f5d9bf3b-2646-4b17-9894-20fd2b4218eavalue-7671553a-cd4b-4e25-8332-9d5667e64493width", - "type": "default", - "source": "f5d9bf3b-2646-4b17-9894-20fd2b4218ea", - "target": "7671553a-cd4b-4e25-8332-9d5667e64493", - "sourceHandle": "value", - "targetHandle": "width" - }, - { - "id": "reactflow__edge-5ca87ace-edf9-49c7-a424-cd42416b86a7height-d350feac-9686-4e0d-bd46-a96bd2630818a", - "type": "default", - "source": "5ca87ace-edf9-49c7-a424-cd42416b86a7", - "target": "d350feac-9686-4e0d-bd46-a96bd2630818", - "sourceHandle": "height", - "targetHandle": "a" - }, - { - "id": "reactflow__edge-1ba845a6-eb88-49a1-a490-5fe6754f3ec9value-d350feac-9686-4e0d-bd46-a96bd2630818b", - "type": "default", - "source": "1ba845a6-eb88-49a1-a490-5fe6754f3ec9", - "target": "d350feac-9686-4e0d-bd46-a96bd2630818", - "sourceHandle": "value", - "targetHandle": "b" - }, - { - "id": "reactflow__edge-1ba845a6-eb88-49a1-a490-5fe6754f3ec9value-5b256f14-caab-40ff-b8f0-9679cd542163b", - "type": "default", - "source": "1ba845a6-eb88-49a1-a490-5fe6754f3ec9", - "target": "5b256f14-caab-40ff-b8f0-9679cd542163", - "sourceHandle": "value", - "targetHandle": "b" - }, - { - "id": "reactflow__edge-5ca87ace-edf9-49c7-a424-cd42416b86a7width-5b256f14-caab-40ff-b8f0-9679cd542163a", - "type": "default", - "source": "5ca87ace-edf9-49c7-a424-cd42416b86a7", - "target": "5b256f14-caab-40ff-b8f0-9679cd542163", - "sourceHandle": "width", - "targetHandle": "a" } ] -} +} \ No newline at end of file diff --git a/invokeai/backend/model_manager/config.py b/invokeai/backend/model_manager/config.py index f6cc5929c8..332ac6c8fa 100644 --- a/invokeai/backend/model_manager/config.py +++ b/invokeai/backend/model_manager/config.py @@ -354,7 +354,7 @@ class CLIPVisionDiffusersConfig(DiffusersConfigBase): """Model config for CLIPVision.""" type: Literal[ModelType.CLIPVision] = ModelType.CLIPVision - format: Literal[ModelFormat.Diffusers] + format: Literal[ModelFormat.Diffusers] = ModelFormat.Diffusers @staticmethod def get_tag() -> Tag: @@ -365,7 +365,7 @@ class T2IAdapterConfig(DiffusersConfigBase, ControlAdapterConfigBase): """Model config for T2I.""" type: Literal[ModelType.T2IAdapter] = ModelType.T2IAdapter - format: Literal[ModelFormat.Diffusers] + format: Literal[ModelFormat.Diffusers] = ModelFormat.Diffusers @staticmethod def get_tag() -> Tag: diff --git a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py index d90352f0e6..33ce4abc4d 100644 --- a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +++ b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py @@ -98,6 +98,9 @@ class StableDiffusionDiffusersModel(GenericDiffusersLoader): ModelVariantType.Normal: StableDiffusionXLPipeline, ModelVariantType.Inpaint: StableDiffusionXLInpaintPipeline, }, + BaseModelType.StableDiffusionXLRefiner: { + ModelVariantType.Normal: StableDiffusionXLPipeline, + }, } assert isinstance(config, MainCheckpointConfig) try: diff --git a/invokeai/backend/model_manager/starter_models.py b/invokeai/backend/model_manager/starter_models.py index e1526392f6..c460a5e86e 100644 --- a/invokeai/backend/model_manager/starter_models.py +++ b/invokeai/backend/model_manager/starter_models.py @@ -187,164 +187,171 @@ STARTER_MODELS: list[StarterModel] = [ # endregion # region ControlNet StarterModel( - name="QRCode Monster", + name="QRCode Monster v2 (SD1.5)", base=BaseModelType.StableDiffusion1, - source="monster-labs/control_v1p_sd15_qrcode_monster", - description="Controlnet model that generates scannable creative QR codes", + source="monster-labs/control_v1p_sd15_qrcode_monster::v2", + description="ControlNet model that generates scannable creative QR codes", + type=ModelType.ControlNet, + ), + StarterModel( + name="QRCode Monster (SDXL)", + base=BaseModelType.StableDiffusionXL, + source="monster-labs/control_v1p_sdxl_qrcode_monster", + description="ControlNet model that generates scannable creative QR codes", type=ModelType.ControlNet, ), StarterModel( name="canny", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11p_sd15_canny", - description="Controlnet weights trained on sd-1.5 with canny conditioning.", + description="ControlNet weights trained on sd-1.5 with canny conditioning.", type=ModelType.ControlNet, ), StarterModel( name="inpaint", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11p_sd15_inpaint", - description="Controlnet weights trained on sd-1.5 with canny conditioning, inpaint version", + description="ControlNet weights trained on sd-1.5 with canny conditioning, inpaint version", type=ModelType.ControlNet, ), StarterModel( name="mlsd", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11p_sd15_mlsd", - description="Controlnet weights trained on sd-1.5 with canny conditioning, MLSD version", + description="ControlNet weights trained on sd-1.5 with canny conditioning, MLSD version", type=ModelType.ControlNet, ), StarterModel( name="depth", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11f1p_sd15_depth", - description="Controlnet weights trained on sd-1.5 with depth conditioning", + description="ControlNet weights trained on sd-1.5 with depth conditioning", type=ModelType.ControlNet, ), StarterModel( name="normal_bae", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11p_sd15_normalbae", - description="Controlnet weights trained on sd-1.5 with normalbae image conditioning", + description="ControlNet weights trained on sd-1.5 with normalbae image conditioning", type=ModelType.ControlNet, ), StarterModel( name="seg", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11p_sd15_seg", - description="Controlnet weights trained on sd-1.5 with seg image conditioning", + description="ControlNet weights trained on sd-1.5 with seg image conditioning", type=ModelType.ControlNet, ), StarterModel( name="lineart", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11p_sd15_lineart", - description="Controlnet weights trained on sd-1.5 with lineart image conditioning", + description="ControlNet weights trained on sd-1.5 with lineart image conditioning", type=ModelType.ControlNet, ), StarterModel( name="lineart_anime", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11p_sd15s2_lineart_anime", - description="Controlnet weights trained on sd-1.5 with anime image conditioning", + description="ControlNet weights trained on sd-1.5 with anime image conditioning", type=ModelType.ControlNet, ), StarterModel( name="openpose", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11p_sd15_openpose", - description="Controlnet weights trained on sd-1.5 with openpose image conditioning", + description="ControlNet weights trained on sd-1.5 with openpose image conditioning", type=ModelType.ControlNet, ), StarterModel( name="scribble", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11p_sd15_scribble", - description="Controlnet weights trained on sd-1.5 with scribble image conditioning", + description="ControlNet weights trained on sd-1.5 with scribble image conditioning", type=ModelType.ControlNet, ), StarterModel( name="softedge", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11p_sd15_softedge", - description="Controlnet weights trained on sd-1.5 with soft edge conditioning", + description="ControlNet weights trained on sd-1.5 with soft edge conditioning", type=ModelType.ControlNet, ), StarterModel( name="shuffle", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11e_sd15_shuffle", - description="Controlnet weights trained on sd-1.5 with shuffle image conditioning", + description="ControlNet weights trained on sd-1.5 with shuffle image conditioning", type=ModelType.ControlNet, ), StarterModel( name="tile", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11f1e_sd15_tile", - description="Controlnet weights trained on sd-1.5 with tiled image conditioning", + description="ControlNet weights trained on sd-1.5 with tiled image conditioning", type=ModelType.ControlNet, ), StarterModel( name="ip2p", base=BaseModelType.StableDiffusion1, source="lllyasviel/control_v11e_sd15_ip2p", - description="Controlnet weights trained on sd-1.5 with ip2p conditioning.", + description="ControlNet weights trained on sd-1.5 with ip2p conditioning.", type=ModelType.ControlNet, ), StarterModel( name="canny-sdxl", base=BaseModelType.StableDiffusionXL, - source="xinsir/controlnet-canny-sdxl-1.0", - description="Controlnet weights trained on sdxl-1.0 with canny conditioning, by Xinsir.", + source="xinsir/controlNet-canny-sdxl-1.0", + description="ControlNet weights trained on sdxl-1.0 with canny conditioning, by Xinsir.", type=ModelType.ControlNet, ), StarterModel( name="depth-sdxl", base=BaseModelType.StableDiffusionXL, - source="diffusers/controlnet-depth-sdxl-1.0", - description="Controlnet weights trained on sdxl-1.0 with depth conditioning.", + source="diffusers/controlNet-depth-sdxl-1.0", + description="ControlNet weights trained on sdxl-1.0 with depth conditioning.", type=ModelType.ControlNet, ), StarterModel( name="softedge-dexined-sdxl", base=BaseModelType.StableDiffusionXL, - source="SargeZT/controlnet-sd-xl-1.0-softedge-dexined", - description="Controlnet weights trained on sdxl-1.0 with dexined soft edge preprocessing.", + source="SargeZT/controlNet-sd-xl-1.0-softedge-dexined", + description="ControlNet weights trained on sdxl-1.0 with dexined soft edge preprocessing.", type=ModelType.ControlNet, ), StarterModel( name="depth-16bit-zoe-sdxl", base=BaseModelType.StableDiffusionXL, - source="SargeZT/controlnet-sd-xl-1.0-depth-16bit-zoe", - description="Controlnet weights trained on sdxl-1.0 with Zoe's preprocessor (16 bits).", + source="SargeZT/controlNet-sd-xl-1.0-depth-16bit-zoe", + description="ControlNet weights trained on sdxl-1.0 with Zoe's preprocessor (16 bits).", type=ModelType.ControlNet, ), StarterModel( name="depth-zoe-sdxl", base=BaseModelType.StableDiffusionXL, - source="diffusers/controlnet-zoe-depth-sdxl-1.0", - description="Controlnet weights trained on sdxl-1.0 with Zoe's preprocessor (32 bits).", + source="diffusers/controlNet-zoe-depth-sdxl-1.0", + description="ControlNet weights trained on sdxl-1.0 with Zoe's preprocessor (32 bits).", type=ModelType.ControlNet, ), StarterModel( name="openpose-sdxl", base=BaseModelType.StableDiffusionXL, - source="xinsir/controlnet-openpose-sdxl-1.0", - description="Controlnet weights trained on sdxl-1.0 compatible with the DWPose processor by Xinsir.", + source="xinsir/controlNet-openpose-sdxl-1.0", + description="ControlNet weights trained on sdxl-1.0 compatible with the DWPose processor by Xinsir.", type=ModelType.ControlNet, ), StarterModel( name="scribble-sdxl", base=BaseModelType.StableDiffusionXL, - source="xinsir/controlnet-scribble-sdxl-1.0", - description="Controlnet weights trained on sdxl-1.0 compatible with various lineart processors and black/white sketches by Xinsir.", + source="xinsir/controlNet-scribble-sdxl-1.0", + description="ControlNet weights trained on sdxl-1.0 compatible with various lineart processors and black/white sketches by Xinsir.", type=ModelType.ControlNet, ), StarterModel( name="tile-sdxl", base=BaseModelType.StableDiffusionXL, - source="xinsir/controlnet-tile-sdxl-1.0", - description="Controlnet weights trained on sdxl-1.0 with tiled image conditioning", + source="xinsir/controlNet-tile-sdxl-1.0", + description="ControlNet weights trained on sdxl-1.0 with tiled image conditioning", type=ModelType.ControlNet, ), # endregion diff --git a/invokeai/backend/stable_diffusion/__init__.py b/invokeai/backend/stable_diffusion/__init__.py index 440cb4410b..6a6f2ebc49 100644 --- a/invokeai/backend/stable_diffusion/__init__.py +++ b/invokeai/backend/stable_diffusion/__init__.py @@ -7,11 +7,9 @@ from invokeai.backend.stable_diffusion.diffusers_pipeline import ( # noqa: F401 StableDiffusionGeneratorPipeline, ) from invokeai.backend.stable_diffusion.diffusion import InvokeAIDiffuserComponent # noqa: F401 -from invokeai.backend.stable_diffusion.seamless import set_seamless # noqa: F401 __all__ = [ "PipelineIntermediateState", "StableDiffusionGeneratorPipeline", "InvokeAIDiffuserComponent", - "set_seamless", ] diff --git a/invokeai/backend/stable_diffusion/denoise_context.py b/invokeai/backend/stable_diffusion/denoise_context.py index 2b43d3fb0f..9060d54977 100644 --- a/invokeai/backend/stable_diffusion/denoise_context.py +++ b/invokeai/backend/stable_diffusion/denoise_context.py @@ -83,47 +83,47 @@ class DenoiseContext: unet: Optional[UNet2DConditionModel] = None # Current state of latent-space image in denoising process. - # None until `pre_denoise_loop` callback. + # None until `PRE_DENOISE_LOOP` callback. # Shape: [batch, channels, latent_height, latent_width] latents: Optional[torch.Tensor] = None # Current denoising step index. - # None until `pre_step` callback. + # None until `PRE_STEP` callback. step_index: Optional[int] = None # Current denoising step timestep. - # None until `pre_step` callback. + # None until `PRE_STEP` callback. timestep: Optional[torch.Tensor] = None # Arguments which will be passed to UNet model. - # Available in `pre_unet`/`post_unet` callbacks, otherwise will be None. + # Available in `PRE_UNET`/`POST_UNET` callbacks, otherwise will be None. unet_kwargs: Optional[UNetKwargs] = None # SchedulerOutput class returned from step function(normally, generated by scheduler). - # Supposed to be used only in `post_step` callback, otherwise can be None. + # Supposed to be used only in `POST_STEP` callback, otherwise can be None. step_output: Optional[SchedulerOutput] = None # Scaled version of `latents`, which will be passed to unet_kwargs initialization. - # Available in events inside step(between `pre_step` and `post_stop`). + # Available in events inside step(between `PRE_STEP` and `POST_STEP`). # Shape: [batch, channels, latent_height, latent_width] latent_model_input: Optional[torch.Tensor] = None # [TMP] Defines on which conditionings current unet call will be runned. - # Available in `pre_unet`/`post_unet` callbacks, otherwise will be None. + # Available in `PRE_UNET`/`POST_UNET` callbacks, otherwise will be None. conditioning_mode: Optional[ConditioningMode] = None # [TMP] Noise predictions from negative conditioning. - # Available in `apply_cfg` and `post_apply_cfg` callbacks, otherwise will be None. + # Available in `POST_COMBINE_NOISE_PREDS` callback, otherwise will be None. # Shape: [batch, channels, latent_height, latent_width] negative_noise_pred: Optional[torch.Tensor] = None # [TMP] Noise predictions from positive conditioning. - # Available in `apply_cfg` and `post_apply_cfg` callbacks, otherwise will be None. + # Available in `POST_COMBINE_NOISE_PREDS` callback, otherwise will be None. # Shape: [batch, channels, latent_height, latent_width] positive_noise_pred: Optional[torch.Tensor] = None # Combined noise prediction from passed conditionings. - # Available in `apply_cfg` and `post_apply_cfg` callbacks, otherwise will be None. + # Available in `POST_COMBINE_NOISE_PREDS` callback, otherwise will be None. # Shape: [batch, channels, latent_height, latent_width] noise_pred: Optional[torch.Tensor] = None diff --git a/invokeai/backend/stable_diffusion/diffusion_backend.py b/invokeai/backend/stable_diffusion/diffusion_backend.py index 806deb5e03..4191db734f 100644 --- a/invokeai/backend/stable_diffusion/diffusion_backend.py +++ b/invokeai/backend/stable_diffusion/diffusion_backend.py @@ -76,12 +76,12 @@ class StableDiffusionBackend: both_noise_pred = self.run_unet(ctx, ext_manager, ConditioningMode.Both) ctx.negative_noise_pred, ctx.positive_noise_pred = both_noise_pred.chunk(2) - # ext: override apply_cfg - ctx.noise_pred = self.apply_cfg(ctx) + # ext: override combine_noise_preds + ctx.noise_pred = self.combine_noise_preds(ctx) # ext: cfg_rescale [modify_noise_prediction] # TODO: rename - ext_manager.run_callback(ExtensionCallbackType.POST_APPLY_CFG, ctx) + ext_manager.run_callback(ExtensionCallbackType.POST_COMBINE_NOISE_PREDS, ctx) # compute the previous noisy sample x_t -> x_t-1 step_output = ctx.scheduler.step(ctx.noise_pred, ctx.timestep, ctx.latents, **ctx.inputs.scheduler_step_kwargs) @@ -95,13 +95,15 @@ class StableDiffusionBackend: return step_output @staticmethod - def apply_cfg(ctx: DenoiseContext) -> torch.Tensor: + def combine_noise_preds(ctx: DenoiseContext) -> torch.Tensor: guidance_scale = ctx.inputs.conditioning_data.guidance_scale if isinstance(guidance_scale, list): guidance_scale = guidance_scale[ctx.step_index] - return torch.lerp(ctx.negative_noise_pred, ctx.positive_noise_pred, guidance_scale) - # return ctx.negative_noise_pred + guidance_scale * (ctx.positive_noise_pred - ctx.negative_noise_pred) + # Note: Although this `torch.lerp(...)` line is logically equivalent to the current CFG line, it seems to result + # in slightly different outputs. It is suspected that this is caused by small precision differences. + # return torch.lerp(ctx.negative_noise_pred, ctx.positive_noise_pred, guidance_scale) + return ctx.negative_noise_pred + guidance_scale * (ctx.positive_noise_pred - ctx.negative_noise_pred) def run_unet(self, ctx: DenoiseContext, ext_manager: ExtensionsManager, conditioning_mode: ConditioningMode): sample = ctx.latent_model_input diff --git a/invokeai/backend/stable_diffusion/extension_callback_type.py b/invokeai/backend/stable_diffusion/extension_callback_type.py index aaefbd7ed0..e4c365007b 100644 --- a/invokeai/backend/stable_diffusion/extension_callback_type.py +++ b/invokeai/backend/stable_diffusion/extension_callback_type.py @@ -9,4 +9,4 @@ class ExtensionCallbackType(Enum): POST_STEP = "post_step" PRE_UNET = "pre_unet" POST_UNET = "post_unet" - POST_APPLY_CFG = "post_apply_cfg" + POST_COMBINE_NOISE_PREDS = "post_combine_noise_preds" diff --git a/invokeai/backend/stable_diffusion/extensions/base.py b/invokeai/backend/stable_diffusion/extensions/base.py index 802af86e6d..820d5d32a3 100644 --- a/invokeai/backend/stable_diffusion/extensions/base.py +++ b/invokeai/backend/stable_diffusion/extensions/base.py @@ -2,7 +2,7 @@ from __future__ import annotations from contextlib import contextmanager from dataclasses import dataclass -from typing import TYPE_CHECKING, Callable, Dict, List +from typing import TYPE_CHECKING, Callable, Dict, List, Optional import torch from diffusers import UNet2DConditionModel @@ -52,9 +52,9 @@ class ExtensionBase: return self._callbacks @contextmanager - def patch_extension(self, context: DenoiseContext): + def patch_extension(self, ctx: DenoiseContext): yield None @contextmanager - def patch_unet(self, state_dict: Dict[str, torch.Tensor], unet: UNet2DConditionModel): + def patch_unet(self, unet: UNet2DConditionModel, cached_weights: Optional[Dict[str, torch.Tensor]] = None): yield None diff --git a/invokeai/backend/stable_diffusion/extensions/controlnet.py b/invokeai/backend/stable_diffusion/extensions/controlnet.py new file mode 100644 index 0000000000..a48a681af3 --- /dev/null +++ b/invokeai/backend/stable_diffusion/extensions/controlnet.py @@ -0,0 +1,158 @@ +from __future__ import annotations + +import math +from contextlib import contextmanager +from typing import TYPE_CHECKING, List, Optional, Union + +import torch +from PIL.Image import Image + +from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR +from invokeai.app.util.controlnet_utils import CONTROLNET_MODE_VALUES, CONTROLNET_RESIZE_VALUES, prepare_control_image +from invokeai.backend.stable_diffusion.denoise_context import UNetKwargs +from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningMode +from invokeai.backend.stable_diffusion.extension_callback_type import ExtensionCallbackType +from invokeai.backend.stable_diffusion.extensions.base import ExtensionBase, callback + +if TYPE_CHECKING: + from invokeai.backend.stable_diffusion.denoise_context import DenoiseContext + from invokeai.backend.util.hotfixes import ControlNetModel + + +class ControlNetExt(ExtensionBase): + def __init__( + self, + model: ControlNetModel, + image: Image, + weight: Union[float, List[float]], + begin_step_percent: float, + end_step_percent: float, + control_mode: CONTROLNET_MODE_VALUES, + resize_mode: CONTROLNET_RESIZE_VALUES, + ): + super().__init__() + self._model = model + self._image = image + self._weight = weight + self._begin_step_percent = begin_step_percent + self._end_step_percent = end_step_percent + self._control_mode = control_mode + self._resize_mode = resize_mode + + self._image_tensor: Optional[torch.Tensor] = None + + @contextmanager + def patch_extension(self, ctx: DenoiseContext): + original_processors = self._model.attn_processors + try: + self._model.set_attn_processor(ctx.inputs.attention_processor_cls()) + + yield None + finally: + self._model.set_attn_processor(original_processors) + + @callback(ExtensionCallbackType.PRE_DENOISE_LOOP) + def resize_image(self, ctx: DenoiseContext): + _, _, latent_height, latent_width = ctx.latents.shape + image_height = latent_height * LATENT_SCALE_FACTOR + image_width = latent_width * LATENT_SCALE_FACTOR + + self._image_tensor = prepare_control_image( + image=self._image, + do_classifier_free_guidance=False, + width=image_width, + height=image_height, + device=ctx.latents.device, + dtype=ctx.latents.dtype, + control_mode=self._control_mode, + resize_mode=self._resize_mode, + ) + + @callback(ExtensionCallbackType.PRE_UNET) + def pre_unet_step(self, ctx: DenoiseContext): + # skip if model not active in current step + total_steps = len(ctx.inputs.timesteps) + first_step = math.floor(self._begin_step_percent * total_steps) + last_step = math.ceil(self._end_step_percent * total_steps) + if ctx.step_index < first_step or ctx.step_index > last_step: + return + + # convert mode to internal flags + soft_injection = self._control_mode in ["more_prompt", "more_control"] + cfg_injection = self._control_mode in ["more_control", "unbalanced"] + + # no negative conditioning in cfg_injection mode + if cfg_injection: + if ctx.conditioning_mode == ConditioningMode.Negative: + return + down_samples, mid_sample = self._run(ctx, soft_injection, ConditioningMode.Positive) + + if ctx.conditioning_mode == ConditioningMode.Both: + # add zeros as samples for negative conditioning + down_samples = [torch.cat([torch.zeros_like(d), d]) for d in down_samples] + mid_sample = torch.cat([torch.zeros_like(mid_sample), mid_sample]) + + else: + down_samples, mid_sample = self._run(ctx, soft_injection, ctx.conditioning_mode) + + if ( + ctx.unet_kwargs.down_block_additional_residuals is None + and ctx.unet_kwargs.mid_block_additional_residual is None + ): + ctx.unet_kwargs.down_block_additional_residuals = down_samples + ctx.unet_kwargs.mid_block_additional_residual = mid_sample + else: + # add controlnet outputs together if have multiple controlnets + ctx.unet_kwargs.down_block_additional_residuals = [ + samples_prev + samples_curr + for samples_prev, samples_curr in zip( + ctx.unet_kwargs.down_block_additional_residuals, down_samples, strict=True + ) + ] + ctx.unet_kwargs.mid_block_additional_residual += mid_sample + + def _run(self, ctx: DenoiseContext, soft_injection: bool, conditioning_mode: ConditioningMode): + total_steps = len(ctx.inputs.timesteps) + + model_input = ctx.latent_model_input + image_tensor = self._image_tensor + if conditioning_mode == ConditioningMode.Both: + model_input = torch.cat([model_input] * 2) + image_tensor = torch.cat([image_tensor] * 2) + + cn_unet_kwargs = UNetKwargs( + sample=model_input, + timestep=ctx.timestep, + encoder_hidden_states=None, # set later by conditioning + cross_attention_kwargs=dict( # noqa: C408 + percent_through=ctx.step_index / total_steps, + ), + ) + + ctx.inputs.conditioning_data.to_unet_kwargs(cn_unet_kwargs, conditioning_mode=conditioning_mode) + + # get static weight, or weight corresponding to current step + weight = self._weight + if isinstance(weight, list): + weight = weight[ctx.step_index] + + tmp_kwargs = vars(cn_unet_kwargs) + + # Remove kwargs not related to ControlNet unet + # ControlNet guidance fields + del tmp_kwargs["down_block_additional_residuals"] + del tmp_kwargs["mid_block_additional_residual"] + + # T2i Adapter guidance fields + del tmp_kwargs["down_intrablock_additional_residuals"] + + # controlnet(s) inference + down_samples, mid_sample = self._model( + controlnet_cond=image_tensor, + conditioning_scale=weight, # controlnet specific, NOT the guidance scale + guess_mode=soft_injection, # this is still called guess_mode in diffusers ControlNetModel + return_dict=False, + **vars(cn_unet_kwargs), + ) + + return down_samples, mid_sample diff --git a/invokeai/backend/stable_diffusion/extensions/freeu.py b/invokeai/backend/stable_diffusion/extensions/freeu.py new file mode 100644 index 0000000000..6ec4fea3fa --- /dev/null +++ b/invokeai/backend/stable_diffusion/extensions/freeu.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from contextlib import contextmanager +from typing import TYPE_CHECKING, Dict, Optional + +import torch +from diffusers import UNet2DConditionModel + +from invokeai.backend.stable_diffusion.extensions.base import ExtensionBase + +if TYPE_CHECKING: + from invokeai.app.shared.models import FreeUConfig + + +class FreeUExt(ExtensionBase): + def __init__( + self, + freeu_config: FreeUConfig, + ): + super().__init__() + self._freeu_config = freeu_config + + @contextmanager + def patch_unet(self, unet: UNet2DConditionModel, cached_weights: Optional[Dict[str, torch.Tensor]] = None): + unet.enable_freeu( + b1=self._freeu_config.b1, + b2=self._freeu_config.b2, + s1=self._freeu_config.s1, + s2=self._freeu_config.s2, + ) + + try: + yield + finally: + unet.disable_freeu() diff --git a/invokeai/backend/stable_diffusion/extensions/rescale_cfg.py b/invokeai/backend/stable_diffusion/extensions/rescale_cfg.py new file mode 100644 index 0000000000..7cccbb8a2b --- /dev/null +++ b/invokeai/backend/stable_diffusion/extensions/rescale_cfg.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import torch + +from invokeai.backend.stable_diffusion.extension_callback_type import ExtensionCallbackType +from invokeai.backend.stable_diffusion.extensions.base import ExtensionBase, callback + +if TYPE_CHECKING: + from invokeai.backend.stable_diffusion.denoise_context import DenoiseContext + + +class RescaleCFGExt(ExtensionBase): + def __init__(self, rescale_multiplier: float): + super().__init__() + self._rescale_multiplier = rescale_multiplier + + @staticmethod + def _rescale_cfg(total_noise_pred: torch.Tensor, pos_noise_pred: torch.Tensor, multiplier: float = 0.7): + """Implementation of Algorithm 2 from https://arxiv.org/pdf/2305.08891.pdf.""" + ro_pos = torch.std(pos_noise_pred, dim=(1, 2, 3), keepdim=True) + ro_cfg = torch.std(total_noise_pred, dim=(1, 2, 3), keepdim=True) + + x_rescaled = total_noise_pred * (ro_pos / ro_cfg) + x_final = multiplier * x_rescaled + (1.0 - multiplier) * total_noise_pred + return x_final + + @callback(ExtensionCallbackType.POST_COMBINE_NOISE_PREDS) + def rescale_noise_pred(self, ctx: DenoiseContext): + if self._rescale_multiplier > 0: + ctx.noise_pred = self._rescale_cfg( + ctx.noise_pred, + ctx.positive_noise_pred, + self._rescale_multiplier, + ) diff --git a/invokeai/backend/stable_diffusion/extensions/seamless.py b/invokeai/backend/stable_diffusion/extensions/seamless.py new file mode 100644 index 0000000000..a96ea6e4d2 --- /dev/null +++ b/invokeai/backend/stable_diffusion/extensions/seamless.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from contextlib import contextmanager +from typing import Callable, Dict, List, Optional, Tuple + +import torch +import torch.nn as nn +from diffusers import UNet2DConditionModel +from diffusers.models.lora import LoRACompatibleConv + +from invokeai.backend.stable_diffusion.extensions.base import ExtensionBase + + +class SeamlessExt(ExtensionBase): + def __init__( + self, + seamless_axes: List[str], + ): + super().__init__() + self._seamless_axes = seamless_axes + + @contextmanager + def patch_unet(self, unet: UNet2DConditionModel, cached_weights: Optional[Dict[str, torch.Tensor]] = None): + with self.static_patch_model( + model=unet, + seamless_axes=self._seamless_axes, + ): + yield + + @staticmethod + @contextmanager + def static_patch_model( + model: torch.nn.Module, + seamless_axes: List[str], + ): + if not seamless_axes: + yield + return + + x_mode = "circular" if "x" in seamless_axes else "constant" + y_mode = "circular" if "y" in seamless_axes else "constant" + + # override conv_forward + # https://github.com/huggingface/diffusers/issues/556#issuecomment-1993287019 + def _conv_forward_asymmetric( + self, input: torch.Tensor, weight: torch.Tensor, bias: Optional[torch.Tensor] = None + ): + self.paddingX = (self._reversed_padding_repeated_twice[0], self._reversed_padding_repeated_twice[1], 0, 0) + self.paddingY = (0, 0, self._reversed_padding_repeated_twice[2], self._reversed_padding_repeated_twice[3]) + working = torch.nn.functional.pad(input, self.paddingX, mode=x_mode) + working = torch.nn.functional.pad(working, self.paddingY, mode=y_mode) + return torch.nn.functional.conv2d( + working, weight, bias, self.stride, torch.nn.modules.utils._pair(0), self.dilation, self.groups + ) + + original_layers: List[Tuple[nn.Conv2d, Callable]] = [] + try: + for layer in model.modules(): + if not isinstance(layer, torch.nn.Conv2d): + continue + + if isinstance(layer, LoRACompatibleConv) and layer.lora_layer is None: + layer.lora_layer = lambda *x: 0 + original_layers.append((layer, layer._conv_forward)) + layer._conv_forward = _conv_forward_asymmetric.__get__(layer, torch.nn.Conv2d) + + yield + + finally: + for layer, orig_conv_forward in original_layers: + layer._conv_forward = orig_conv_forward diff --git a/invokeai/backend/stable_diffusion/extensions/t2i_adapter.py b/invokeai/backend/stable_diffusion/extensions/t2i_adapter.py new file mode 100644 index 0000000000..5c290ea4e7 --- /dev/null +++ b/invokeai/backend/stable_diffusion/extensions/t2i_adapter.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +import math +from typing import TYPE_CHECKING, List, Optional, Union + +import torch +from diffusers import T2IAdapter +from PIL.Image import Image + +from invokeai.app.util.controlnet_utils import prepare_control_image +from invokeai.backend.model_manager import BaseModelType +from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningMode +from invokeai.backend.stable_diffusion.extension_callback_type import ExtensionCallbackType +from invokeai.backend.stable_diffusion.extensions.base import ExtensionBase, callback + +if TYPE_CHECKING: + from invokeai.app.invocations.model import ModelIdentifierField + from invokeai.app.services.shared.invocation_context import InvocationContext + from invokeai.app.util.controlnet_utils import CONTROLNET_RESIZE_VALUES + from invokeai.backend.stable_diffusion.denoise_context import DenoiseContext + + +class T2IAdapterExt(ExtensionBase): + def __init__( + self, + node_context: InvocationContext, + model_id: ModelIdentifierField, + image: Image, + weight: Union[float, List[float]], + begin_step_percent: float, + end_step_percent: float, + resize_mode: CONTROLNET_RESIZE_VALUES, + ): + super().__init__() + self._node_context = node_context + self._model_id = model_id + self._image = image + self._weight = weight + self._resize_mode = resize_mode + self._begin_step_percent = begin_step_percent + self._end_step_percent = end_step_percent + + self._adapter_state: Optional[List[torch.Tensor]] = None + + # The max_unet_downscale is the maximum amount that the UNet model downscales the latent image internally. + model_config = self._node_context.models.get_config(self._model_id.key) + if model_config.base == BaseModelType.StableDiffusion1: + self._max_unet_downscale = 8 + elif model_config.base == BaseModelType.StableDiffusionXL: + self._max_unet_downscale = 4 + else: + raise ValueError(f"Unexpected T2I-Adapter base model type: '{model_config.base}'.") + + @callback(ExtensionCallbackType.SETUP) + def setup(self, ctx: DenoiseContext): + t2i_model: T2IAdapter + with self._node_context.models.load(self._model_id) as t2i_model: + _, _, latents_height, latents_width = ctx.inputs.orig_latents.shape + + self._adapter_state = self._run_model( + model=t2i_model, + image=self._image, + latents_height=latents_height, + latents_width=latents_width, + ) + + def _run_model( + self, + model: T2IAdapter, + image: Image, + latents_height: int, + latents_width: int, + ): + # Resize the T2I-Adapter input image. + # We select the resize dimensions so that after the T2I-Adapter's total_downscale_factor is applied, the + # result will match the latent image's dimensions after max_unet_downscale is applied. + input_height = latents_height // self._max_unet_downscale * model.total_downscale_factor + input_width = latents_width // self._max_unet_downscale * model.total_downscale_factor + + # Note: We have hard-coded `do_classifier_free_guidance=False`. This is because we only want to prepare + # a single image. If CFG is enabled, we will duplicate the resultant tensor after applying the + # T2I-Adapter model. + # + # Note: We re-use the `prepare_control_image(...)` from ControlNet for T2I-Adapter, because it has many + # of the same requirements (e.g. preserving binary masks during resize). + t2i_image = prepare_control_image( + image=image, + do_classifier_free_guidance=False, + width=input_width, + height=input_height, + num_channels=model.config["in_channels"], + device=model.device, + dtype=model.dtype, + resize_mode=self._resize_mode, + ) + + return model(t2i_image) + + @callback(ExtensionCallbackType.PRE_UNET) + def pre_unet_step(self, ctx: DenoiseContext): + # skip if model not active in current step + total_steps = len(ctx.inputs.timesteps) + first_step = math.floor(self._begin_step_percent * total_steps) + last_step = math.ceil(self._end_step_percent * total_steps) + if ctx.step_index < first_step or ctx.step_index > last_step: + return + + weight = self._weight + if isinstance(weight, list): + weight = weight[ctx.step_index] + + adapter_state = self._adapter_state + if ctx.conditioning_mode == ConditioningMode.Both: + adapter_state = [torch.cat([v] * 2) for v in adapter_state] + + if ctx.unet_kwargs.down_intrablock_additional_residuals is None: + ctx.unet_kwargs.down_intrablock_additional_residuals = [v * weight for v in adapter_state] + else: + for i, value in enumerate(adapter_state): + ctx.unet_kwargs.down_intrablock_additional_residuals[i] += value * weight diff --git a/invokeai/backend/stable_diffusion/extensions_manager.py b/invokeai/backend/stable_diffusion/extensions_manager.py index 1cae2e4219..c8d585406a 100644 --- a/invokeai/backend/stable_diffusion/extensions_manager.py +++ b/invokeai/backend/stable_diffusion/extensions_manager.py @@ -52,20 +52,24 @@ class ExtensionsManager: cb.function(ctx) @contextmanager - def patch_extensions(self, context: DenoiseContext): + def patch_extensions(self, ctx: DenoiseContext): if self._is_canceled and self._is_canceled(): raise CanceledException with ExitStack() as exit_stack: for ext in self._extensions: - exit_stack.enter_context(ext.patch_extension(context)) + exit_stack.enter_context(ext.patch_extension(ctx)) yield None @contextmanager - def patch_unet(self, state_dict: Dict[str, torch.Tensor], unet: UNet2DConditionModel): + def patch_unet(self, unet: UNet2DConditionModel, cached_weights: Optional[Dict[str, torch.Tensor]] = None): if self._is_canceled and self._is_canceled(): raise CanceledException - # TODO: create logic in PR with extension which uses it - yield None + # TODO: create weight patch logic in PR with extension which uses it + with ExitStack() as exit_stack: + for ext in self._extensions: + exit_stack.enter_context(ext.patch_unet(unet, cached_weights)) + + yield None diff --git a/invokeai/backend/stable_diffusion/seamless.py b/invokeai/backend/stable_diffusion/seamless.py deleted file mode 100644 index 23ed978c6d..0000000000 --- a/invokeai/backend/stable_diffusion/seamless.py +++ /dev/null @@ -1,51 +0,0 @@ -from contextlib import contextmanager -from typing import Callable, List, Optional, Tuple, Union - -import torch -import torch.nn as nn -from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL -from diffusers.models.autoencoders.autoencoder_tiny import AutoencoderTiny -from diffusers.models.lora import LoRACompatibleConv -from diffusers.models.unets.unet_2d_condition import UNet2DConditionModel - - -@contextmanager -def set_seamless(model: Union[UNet2DConditionModel, AutoencoderKL, AutoencoderTiny], seamless_axes: List[str]): - if not seamless_axes: - yield - return - - # override conv_forward - # https://github.com/huggingface/diffusers/issues/556#issuecomment-1993287019 - def _conv_forward_asymmetric(self, input: torch.Tensor, weight: torch.Tensor, bias: Optional[torch.Tensor] = None): - self.paddingX = (self._reversed_padding_repeated_twice[0], self._reversed_padding_repeated_twice[1], 0, 0) - self.paddingY = (0, 0, self._reversed_padding_repeated_twice[2], self._reversed_padding_repeated_twice[3]) - working = torch.nn.functional.pad(input, self.paddingX, mode=x_mode) - working = torch.nn.functional.pad(working, self.paddingY, mode=y_mode) - return torch.nn.functional.conv2d( - working, weight, bias, self.stride, torch.nn.modules.utils._pair(0), self.dilation, self.groups - ) - - original_layers: List[Tuple[nn.Conv2d, Callable]] = [] - - try: - x_mode = "circular" if "x" in seamless_axes else "constant" - y_mode = "circular" if "y" in seamless_axes else "constant" - - conv_layers: List[torch.nn.Conv2d] = [] - - for module in model.modules(): - if isinstance(module, torch.nn.Conv2d): - conv_layers.append(module) - - for layer in conv_layers: - if isinstance(layer, LoRACompatibleConv) and layer.lora_layer is None: - layer.lora_layer = lambda *x: 0 - original_layers.append((layer, layer._conv_forward)) - layer._conv_forward = _conv_forward_asymmetric.__get__(layer, torch.nn.Conv2d) - - yield - - finally: - for layer, orig_conv_forward in original_layers: - layer._conv_forward = orig_conv_forward diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index f2210e4c68..879985accd 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -155,5 +155,8 @@ "vite-plugin-eslint": "^1.8.1", "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.6.0" + }, + "engines": { + "pnpm": "8" } } diff --git a/invokeai/frontend/web/public/locales/ar.json b/invokeai/frontend/web/public/locales/ar.json index ee370d1e42..bb04edfa8b 100644 --- a/invokeai/frontend/web/public/locales/ar.json +++ b/invokeai/frontend/web/public/locales/ar.json @@ -77,10 +77,6 @@ "title": "استعادة الوجوه", "desc": "استعادة الصورة الحالية" }, - "upscale": { - "title": "تحسين الحجم", - "desc": "تحسين حجم الصورة الحالية" - }, "showInfo": { "title": "عرض المعلومات", "desc": "عرض معلومات البيانات الخاصة بالصورة الحالية" @@ -255,8 +251,6 @@ "type": "نوع", "strength": "قوة", "upscaling": "تصغير", - "upscale": "تصغير", - "upscaleImage": "تصغير الصورة", "scale": "مقياس", "imageFit": "ملائمة الصورة الأولية لحجم الخرج", "scaleBeforeProcessing": "تحجيم قبل المعالجة", diff --git a/invokeai/frontend/web/public/locales/de.json b/invokeai/frontend/web/public/locales/de.json index 352b6ffcbf..b8a204df1d 100644 --- a/invokeai/frontend/web/public/locales/de.json +++ b/invokeai/frontend/web/public/locales/de.json @@ -187,10 +187,6 @@ "title": "Gesicht restaurieren", "desc": "Das aktuelle Bild restaurieren" }, - "upscale": { - "title": "Hochskalieren", - "desc": "Das aktuelle Bild hochskalieren" - }, "showInfo": { "title": "Info anzeigen", "desc": "Metadaten des aktuellen Bildes anzeigen" @@ -433,8 +429,6 @@ "type": "Art", "strength": "Stärke", "upscaling": "Hochskalierung", - "upscale": "Hochskalieren (Shift + U)", - "upscaleImage": "Bild hochskalieren", "scale": "Maßstab", "imageFit": "Ausgangsbild an Ausgabegröße anpassen", "scaleBeforeProcessing": "Skalieren vor der Verarbeitung", diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 91a36bcee3..659df78d9b 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -32,12 +32,14 @@ "deleteBoardAndImages": "Delete Board and Images", "deleteBoardOnly": "Delete Board Only", "deletedBoardsCannotbeRestored": "Deleted boards cannot be restored", + "hideBoards": "Hide Boards", "loading": "Loading...", "menuItemAutoAdd": "Auto-add to this Board", "move": "Move", "movingImagesToBoard_one": "Moving {{count}} image to board:", "movingImagesToBoard_other": "Moving {{count}} images to board:", "myBoard": "My Board", + "noBoards": "No {{boardType}} Boards", "noMatching": "No matching Boards", "private": "Private Boards", "searchBoard": "Search Boards...", @@ -46,6 +48,7 @@ "topMessage": "This board contains images used in the following features:", "unarchiveBoard": "Unarchive Board", "uncategorized": "Uncategorized", + "viewBoards": "View Boards", "downloadBoard": "Download Board", "imagesWithCount_one": "{{count}} image", "imagesWithCount_other": "{{count}} images", @@ -102,6 +105,7 @@ "negativePrompt": "Negative Prompt", "discordLabel": "Discord", "dontAskMeAgain": "Don't ask me again", + "dontShowMeThese": "Don't show me these", "editor": "Editor", "error": "Error", "file": "File", @@ -373,10 +377,14 @@ "displayBoardSearch": "Display Board Search", "displaySearch": "Display Search", "download": "Download", + "exitBoardSearch": "Exit Board Search", + "exitSearch": "Exit Search", "featuresWillReset": "If you delete this image, those features will immediately be reset.", "galleryImageSize": "Image Size", "gallerySettings": "Gallery Settings", + "go": "Go", "image": "image", + "jump": "Jump", "loading": "Loading", "loadMore": "Load More", "newestFirst": "Newest First", @@ -636,9 +644,9 @@ "title": "Undo Stroke" }, "unifiedCanvasHotkeys": "Unified Canvas", - "upscale": { - "desc": "Upscale the current image", - "title": "Upscale" + "postProcess": { + "desc": "Process the current image using the selected post-processing model", + "title": "Process Image" }, "toggleViewer": { "desc": "Switches between the Image Viewer and workspace for the current tab.", @@ -1027,6 +1035,7 @@ "imageActions": "Image Actions", "sendToImg2Img": "Send to Image to Image", "sendToUnifiedCanvas": "Send To Unified Canvas", + "sendToUpscale": "Send To Upscale", "showOptionsPanel": "Show Side Panel (O or T)", "shuffle": "Shuffle Seed", "steps": "Steps", @@ -1034,8 +1043,8 @@ "symmetry": "Symmetry", "tileSize": "Tile Size", "type": "Type", - "upscale": "Upscale (Shift + U)", - "upscaleImage": "Upscale Image", + "postProcessing": "Post-Processing (Shift + U)", + "processImage": "Process Image", "upscaling": "Upscaling", "useAll": "Use All", "useSize": "Use Size", @@ -1091,6 +1100,8 @@ "displayInProgress": "Display Progress Images", "enableImageDebugging": "Enable Image Debugging", "enableInformationalPopovers": "Enable Informational Popovers", + "informationalPopoversDisabled": "Informational Popovers Disabled", + "informationalPopoversDisabledDesc": "Informational popovers have been disabled. Enable them in Settings.", "enableInvisibleWatermark": "Enable Invisible Watermark", "enableNSFWChecker": "Enable NSFW Checker", "general": "General", @@ -1498,6 +1509,30 @@ "seamlessTilingYAxis": { "heading": "Seamless Tiling Y Axis", "paragraphs": ["Seamlessly tile an image along the vertical axis."] + }, + "upscaleModel": { + "heading": "Upscale Model", + "paragraphs": [ + "The upscale model scales the image to the output size before details are added. Any supported upscale model may be used, but some are specialized for different kinds of images, like photos or line drawings." + ] + }, + "scale": { + "heading": "Scale", + "paragraphs": [ + "Scale controls the output image size, and is based on a multiple of the input image resolution. For example a 2x upscale on a 1024x1024 image would produce a 2048 x 2048 output." + ] + }, + "creativity": { + "heading": "Creativity", + "paragraphs": [ + "Creativity controls the amount of freedom granted to the model when adding details. Low creativity stays close to the original image, while high creativity allows for more change. When using a prompt, high creativity increases the influence of the prompt." + ] + }, + "structure": { + "heading": "Structure", + "paragraphs": [ + "Structure controls how closely the output image will keep to the layout of the original. Low structure allows major changes, while high structure strictly maintains the original composition and layout." + ] } }, "unifiedCanvas": { @@ -1640,6 +1675,27 @@ "layers_one": "Layer", "layers_other": "Layers" }, + "upscaling": { + "creativity": "Creativity", + "structure": "Structure", + "upscaleModel": "Upscale Model", + "postProcessingModel": "Post-Processing Model", + "scale": "Scale", + "postProcessingMissingModelWarning": "Visit the Model Manager to install a post-processing (image to image) model.", + "missingModelsWarning": "Visit the Model Manager to install the required models:", + "mainModelDesc": "Main model (SD1.5 or SDXL architecture)", + "tileControlNetModelDesc": "Tile ControlNet model for the chosen main model architecture", + "upscaleModelDesc": "Upscale (image to image) model", + "missingUpscaleInitialImage": "Missing initial image for upscaling", + "missingUpscaleModel": "Missing upscale model", + "missingTileControlNetModel": "No valid tile ControlNet models installed" + }, + "upsell": { + "inviteTeammates": "Invite Teammates", + "professional": "Professional", + "professionalUpsell": "Available in Invoke’s Professional Edition. Click here or visit invoke.com/pricing for more details.", + "shareAccess": "Share Access" + }, "ui": { "tabs": { "generation": "Generation", @@ -1651,7 +1707,9 @@ "models": "Models", "modelsTab": "$t(ui.tabs.models) $t(common.tab)", "queue": "Queue", - "queueTab": "$t(ui.tabs.queue) $t(common.tab)" + "queueTab": "$t(ui.tabs.queue) $t(common.tab)", + "upscaling": "Upscaling", + "upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)" } } } diff --git a/invokeai/frontend/web/public/locales/es.json b/invokeai/frontend/web/public/locales/es.json index 52ee3b5fe3..fa87dd0c76 100644 --- a/invokeai/frontend/web/public/locales/es.json +++ b/invokeai/frontend/web/public/locales/es.json @@ -151,10 +151,6 @@ "title": "Restaurar rostros", "desc": "Restaurar rostros en la imagen actual" }, - "upscale": { - "title": "Aumentar resolución", - "desc": "Aumentar la resolución de la imagen actual" - }, "showInfo": { "title": "Mostrar información", "desc": "Mostar metadatos de la imagen actual" @@ -360,8 +356,6 @@ "type": "Tipo", "strength": "Fuerza", "upscaling": "Aumento de resolución", - "upscale": "Aumentar resolución", - "upscaleImage": "Aumentar la resolución de la imagen", "scale": "Escala", "imageFit": "Ajuste tamaño de imagen inicial al tamaño objetivo", "scaleBeforeProcessing": "Redimensionar antes de procesar", @@ -408,7 +402,12 @@ "showProgressInViewer": "Mostrar las imágenes del progreso en el visor", "ui": "Interfaz del usuario", "generation": "Generación", - "beta": "Beta" + "beta": "Beta", + "reloadingIn": "Recargando en", + "intermediatesClearedFailed": "Error limpiando los intermediarios", + "intermediatesCleared_one": "Borrado {{count}} intermediario", + "intermediatesCleared_many": "Borrados {{count}} intermediarios", + "intermediatesCleared_other": "Borrados {{count}} intermediarios" }, "toast": { "uploadFailed": "Error al subir archivo", @@ -426,7 +425,12 @@ "parameterSet": "Conjunto de parámetros", "parameterNotSet": "Parámetro no configurado", "problemCopyingImage": "No se puede copiar la imagen", - "errorCopied": "Error al copiar" + "errorCopied": "Error al copiar", + "baseModelChanged": "Modelo base cambiado", + "addedToBoard": "Añadido al tablero", + "baseModelChangedCleared_one": "Borrado o desactivado {{count}} submodelo incompatible", + "baseModelChangedCleared_many": "Borrados o desactivados {{count}} submodelos incompatibles", + "baseModelChangedCleared_other": "Borrados o desactivados {{count}} submodelos incompatibles" }, "tooltip": { "feature": { @@ -540,7 +544,13 @@ "downloadBoard": "Descargar panel", "deleteBoardOnly": "Borrar solo el panel", "myBoard": "Mi panel", - "noMatching": "No hay paneles que coincidan" + "noMatching": "No hay paneles que coincidan", + "imagesWithCount_one": "{{count}} imagen", + "imagesWithCount_many": "{{count}} imágenes", + "imagesWithCount_other": "{{count}} imágenes", + "assetsWithCount_one": "{{count}} activo", + "assetsWithCount_many": "{{count}} activos", + "assetsWithCount_other": "{{count}} activos" }, "accordions": { "compositing": { @@ -590,6 +600,27 @@ "balanced": "Equilibrado", "beginEndStepPercent": "Inicio / Final Porcentaje de pasos", "detectResolution": "Detectar resolución", - "beginEndStepPercentShort": "Inicio / Final %" + "beginEndStepPercentShort": "Inicio / Final %", + "t2i_adapter": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.t2iAdapter))", + "controlnet": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.controlNet))", + "ip_adapter": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.ipAdapter))", + "addControlNet": "Añadir $t(common.controlNet)", + "addIPAdapter": "Añadir $t(common.ipAdapter)", + "controlAdapter_one": "Adaptador de control", + "controlAdapter_many": "Adaptadores de control", + "controlAdapter_other": "Adaptadores de control", + "addT2IAdapter": "Añadir $t(common.t2iAdapter)" + }, + "queue": { + "back": "Atrás", + "front": "Delante", + "batchQueuedDesc_one": "Se agregó {{count}} sesión a {{direction}} la cola", + "batchQueuedDesc_many": "Se agregaron {{count}} sesiones a {{direction}} la cola", + "batchQueuedDesc_other": "Se agregaron {{count}} sesiones a {{direction}} la cola" + }, + "upsell": { + "inviteTeammates": "Invitar compañeros de equipo", + "shareAccess": "Compartir acceso", + "professionalUpsell": "Disponible en la edición profesional de Invoke. Haz clic aquí o visita invoke.com/pricing para obtener más detalles." } } diff --git a/invokeai/frontend/web/public/locales/fr.json b/invokeai/frontend/web/public/locales/fr.json index 558545b2f4..efc181c59a 100644 --- a/invokeai/frontend/web/public/locales/fr.json +++ b/invokeai/frontend/web/public/locales/fr.json @@ -130,10 +130,6 @@ "title": "Restaurer les visages", "desc": "Restaurer l'image actuelle" }, - "upscale": { - "title": "Agrandir", - "desc": "Agrandir l'image actuelle" - }, "showInfo": { "title": "Afficher les informations", "desc": "Afficher les informations de métadonnées de l'image actuelle" @@ -308,8 +304,6 @@ "type": "Type", "strength": "Force", "upscaling": "Agrandissement", - "upscale": "Agrandir", - "upscaleImage": "Image en Agrandissement", "scale": "Echelle", "imageFit": "Ajuster Image Initiale à la Taille de Sortie", "scaleBeforeProcessing": "Echelle Avant Traitement", diff --git a/invokeai/frontend/web/public/locales/he.json b/invokeai/frontend/web/public/locales/he.json index dbbb3cbec4..def170fdbc 100644 --- a/invokeai/frontend/web/public/locales/he.json +++ b/invokeai/frontend/web/public/locales/he.json @@ -90,10 +90,6 @@ "desc": "שחזור התמונה הנוכחית", "title": "שחזור פרצופים" }, - "upscale": { - "title": "הגדלת קנה מידה", - "desc": "הגדל את התמונה הנוכחית" - }, "showInfo": { "title": "הצג מידע", "desc": "הצגת פרטי מטא-נתונים של התמונה הנוכחית" @@ -263,8 +259,6 @@ "seed": "זרע", "type": "סוג", "strength": "חוזק", - "upscale": "הגדלת קנה מידה", - "upscaleImage": "הגדלת קנה מידת התמונה", "denoisingStrength": "חוזק מנטרל הרעש", "scaleBeforeProcessing": "שנה קנה מידה לפני עיבוד", "scaledWidth": "קנה מידה לאחר שינוי W", diff --git a/invokeai/frontend/web/public/locales/it.json b/invokeai/frontend/web/public/locales/it.json index eced64a1e3..7637a41310 100644 --- a/invokeai/frontend/web/public/locales/it.json +++ b/invokeai/frontend/web/public/locales/it.json @@ -150,7 +150,11 @@ "showArchivedBoards": "Mostra le bacheche archiviate", "searchImages": "Ricerca per metadati", "displayBoardSearch": "Mostra la ricerca nelle Bacheche", - "displaySearch": "Mostra la ricerca" + "displaySearch": "Mostra la ricerca", + "selectAllOnPage": "Seleziona tutto nella pagina", + "selectAllOnBoard": "Seleziona tutto nella bacheca", + "exitBoardSearch": "Esci da Ricerca bacheca", + "exitSearch": "Esci dalla ricerca" }, "hotkeys": { "keyboardShortcuts": "Tasti di scelta rapida", @@ -210,10 +214,6 @@ "title": "Restaura volti", "desc": "Restaura l'immagine corrente" }, - "upscale": { - "title": "Amplia", - "desc": "Amplia l'immagine corrente" - }, "showInfo": { "title": "Mostra informazioni", "desc": "Mostra le informazioni sui metadati dell'immagine corrente" @@ -377,6 +377,10 @@ "toggleViewer": { "title": "Attiva/disattiva il visualizzatore di immagini", "desc": "Passa dal visualizzatore immagini all'area di lavoro per la scheda corrente." + }, + "postProcess": { + "desc": "Elabora l'immagine corrente utilizzando il modello di post-elaborazione selezionato", + "title": "Elabora immagine" } }, "modelManager": { @@ -505,8 +509,6 @@ "type": "Tipo", "strength": "Forza", "upscaling": "Ampliamento", - "upscale": "Amplia (Shift + U)", - "upscaleImage": "Amplia Immagine", "scale": "Scala", "imageFit": "Adatta l'immagine iniziale alle dimensioni di output", "scaleBeforeProcessing": "Scala prima dell'elaborazione", @@ -591,7 +593,10 @@ "infillColorValue": "Colore di riempimento", "globalSettings": "Impostazioni globali", "globalPositivePromptPlaceholder": "Prompt positivo globale", - "globalNegativePromptPlaceholder": "Prompt negativo globale" + "globalNegativePromptPlaceholder": "Prompt negativo globale", + "processImage": "Elabora Immagine", + "sendToUpscale": "Invia a Ampliare", + "postProcessing": "Post-elaborazione (Shift + U)" }, "settings": { "models": "Modelli", @@ -964,7 +969,10 @@ "boards": "Bacheche", "private": "Bacheche private", "shared": "Bacheche condivise", - "addPrivateBoard": "Aggiungi una Bacheca Privata" + "addPrivateBoard": "Aggiungi una Bacheca Privata", + "noBoards": "Nessuna bacheca {{boardType}}", + "hideBoards": "Nascondi bacheche", + "viewBoards": "Visualizza bacheche" }, "controlnet": { "contentShuffleDescription": "Rimescola il contenuto di un'immagine", @@ -1684,7 +1692,30 @@ "models": "Modelli", "modelsTab": "$t(ui.tabs.models) $t(common.tab)", "queue": "Coda", - "queueTab": "$t(ui.tabs.queue) $t(common.tab)" + "queueTab": "$t(ui.tabs.queue) $t(common.tab)", + "upscaling": "Ampliamento", + "upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)" } + }, + "upscaling": { + "creativity": "Creatività", + "structure": "Struttura", + "upscaleModel": "Modello di Ampliamento", + "scale": "Scala", + "missingModelsWarning": "Visita Gestione modelli per installare i modelli richiesti:", + "mainModelDesc": "Modello principale (architettura SD1.5 o SDXL)", + "tileControlNetModelDesc": "Modello Tile ControlNet per l'architettura del modello principale scelto", + "upscaleModelDesc": "Modello per l'ampliamento (da immagine a immagine)", + "missingUpscaleInitialImage": "Immagine iniziale mancante per l'ampliamento", + "missingUpscaleModel": "Modello per l’ampliamento mancante", + "missingTileControlNetModel": "Nessun modello ControlNet Tile valido installato", + "postProcessingModel": "Modello di post-elaborazione", + "postProcessingMissingModelWarning": "Visita Gestione modelli per installare un modello di post-elaborazione (da immagine a immagine)." + }, + "upsell": { + "inviteTeammates": "Invita collaboratori", + "shareAccess": "Condividi l'accesso", + "professional": "Professionale", + "professionalUpsell": "Disponibile nell'edizione Professional di Invoke. Fai clic qui o visita invoke.com/pricing per ulteriori dettagli." } } diff --git a/invokeai/frontend/web/public/locales/ja.json b/invokeai/frontend/web/public/locales/ja.json index 6d5775ecd6..e7bc11d34e 100644 --- a/invokeai/frontend/web/public/locales/ja.json +++ b/invokeai/frontend/web/public/locales/ja.json @@ -199,10 +199,6 @@ "title": "顔の修復", "desc": "現在の画像を修復" }, - "upscale": { - "title": "アップスケール", - "desc": "現在の画像をアップスケール" - }, "showInfo": { "title": "情報を見る", "desc": "現在の画像のメタデータ情報を表示" @@ -427,8 +423,6 @@ "shuffle": "シャッフル", "strength": "強度", "upscaling": "アップスケーリング", - "upscale": "アップスケール", - "upscaleImage": "画像をアップスケール", "scale": "Scale", "scaleBeforeProcessing": "処理前のスケール", "scaledWidth": "幅のスケール", diff --git a/invokeai/frontend/web/public/locales/ko.json b/invokeai/frontend/web/public/locales/ko.json index db9cd0ca67..ed745da8aa 100644 --- a/invokeai/frontend/web/public/locales/ko.json +++ b/invokeai/frontend/web/public/locales/ko.json @@ -258,10 +258,6 @@ "desc": "캔버스 브러시를 선택", "title": "브러시 선택" }, - "upscale": { - "desc": "현재 이미지를 업스케일", - "title": "업스케일" - }, "previousImage": { "title": "이전 이미지", "desc": "갤러리에 이전 이미지 표시" diff --git a/invokeai/frontend/web/public/locales/nl.json b/invokeai/frontend/web/public/locales/nl.json index afcce62163..2c38353240 100644 --- a/invokeai/frontend/web/public/locales/nl.json +++ b/invokeai/frontend/web/public/locales/nl.json @@ -168,10 +168,6 @@ "title": "Herstel gezichten", "desc": "Herstelt de huidige afbeelding" }, - "upscale": { - "title": "Schaal op", - "desc": "Schaalt de huidige afbeelding op" - }, "showInfo": { "title": "Toon info", "desc": "Toont de metagegevens van de huidige afbeelding" @@ -412,8 +408,6 @@ "type": "Soort", "strength": "Sterkte", "upscaling": "Opschalen", - "upscale": "Vergroot (Shift + U)", - "upscaleImage": "Schaal afbeelding op", "scale": "Schaal", "imageFit": "Pas initiële afbeelding in uitvoergrootte", "scaleBeforeProcessing": "Schalen voor verwerking", diff --git a/invokeai/frontend/web/public/locales/pl.json b/invokeai/frontend/web/public/locales/pl.json index b7592c3fae..0fccbded4a 100644 --- a/invokeai/frontend/web/public/locales/pl.json +++ b/invokeai/frontend/web/public/locales/pl.json @@ -78,10 +78,6 @@ "title": "Popraw twarze", "desc": "Uruchamia proces poprawiania twarzy dla aktywnego obrazu" }, - "upscale": { - "title": "Powiększ", - "desc": "Uruchamia proces powiększania aktywnego obrazu" - }, "showInfo": { "title": "Pokaż informacje", "desc": "Pokazuje metadane zapisane w aktywnym obrazie" @@ -232,8 +228,6 @@ "type": "Metoda", "strength": "Siła", "upscaling": "Powiększanie", - "upscale": "Powiększ", - "upscaleImage": "Powiększ obraz", "scale": "Skala", "imageFit": "Przeskaluj oryginalny obraz", "scaleBeforeProcessing": "Tryb skalowania", diff --git a/invokeai/frontend/web/public/locales/pt.json b/invokeai/frontend/web/public/locales/pt.json index 3003a1732b..a8a675f22f 100644 --- a/invokeai/frontend/web/public/locales/pt.json +++ b/invokeai/frontend/web/public/locales/pt.json @@ -160,10 +160,6 @@ "title": "Restaurar Rostos", "desc": "Restaurar a imagem atual" }, - "upscale": { - "title": "Redimensionar", - "desc": "Redimensionar a imagem atual" - }, "showInfo": { "title": "Mostrar Informações", "desc": "Mostrar metadados de informações da imagem atual" @@ -275,8 +271,6 @@ "showOptionsPanel": "Mostrar Painel de Opções", "strength": "Força", "upscaling": "Redimensionando", - "upscale": "Redimensionar", - "upscaleImage": "Redimensionar Imagem", "scaleBeforeProcessing": "Escala Antes do Processamento", "images": "Imagems", "steps": "Passos", diff --git a/invokeai/frontend/web/public/locales/pt_BR.json b/invokeai/frontend/web/public/locales/pt_BR.json index c966c6db50..1572d4fe0b 100644 --- a/invokeai/frontend/web/public/locales/pt_BR.json +++ b/invokeai/frontend/web/public/locales/pt_BR.json @@ -80,10 +80,6 @@ "title": "Restaurar Rostos", "desc": "Restaurar a imagem atual" }, - "upscale": { - "title": "Redimensionar", - "desc": "Redimensionar a imagem atual" - }, "showInfo": { "title": "Mostrar Informações", "desc": "Mostrar metadados de informações da imagem atual" @@ -268,8 +264,6 @@ "type": "Tipo", "strength": "Força", "upscaling": "Redimensionando", - "upscale": "Redimensionar", - "upscaleImage": "Redimensionar Imagem", "scale": "Escala", "imageFit": "Caber Imagem Inicial No Tamanho de Saída", "scaleBeforeProcessing": "Escala Antes do Processamento", diff --git a/invokeai/frontend/web/public/locales/ru.json b/invokeai/frontend/web/public/locales/ru.json index ecceaf42aa..644c30fd05 100644 --- a/invokeai/frontend/web/public/locales/ru.json +++ b/invokeai/frontend/web/public/locales/ru.json @@ -214,10 +214,6 @@ "title": "Восстановить лица", "desc": "Восстановить лица на текущем изображении" }, - "upscale": { - "title": "Увеличение", - "desc": "Увеличить текущеее изображение" - }, "showInfo": { "title": "Показать метаданные", "desc": "Показать метаданные из текущего изображения" @@ -512,8 +508,6 @@ "type": "Тип", "strength": "Сила", "upscaling": "Увеличение", - "upscale": "Увеличить", - "upscaleImage": "Увеличить изображение", "scale": "Масштаб", "imageFit": "Уместить изображение", "scaleBeforeProcessing": "Масштабировать", diff --git a/invokeai/frontend/web/public/locales/sv.json b/invokeai/frontend/web/public/locales/sv.json index 2c51027244..a9ebd9f491 100644 --- a/invokeai/frontend/web/public/locales/sv.json +++ b/invokeai/frontend/web/public/locales/sv.json @@ -90,10 +90,6 @@ "title": "Återskapa ansikten", "desc": "Återskapa nuvarande bild" }, - "upscale": { - "title": "Skala upp", - "desc": "Skala upp nuvarande bild" - }, "showInfo": { "title": "Visa info", "desc": "Visa metadata för nuvarande bild" diff --git a/invokeai/frontend/web/public/locales/tr.json b/invokeai/frontend/web/public/locales/tr.json index 415bd2d744..75efaf8294 100644 --- a/invokeai/frontend/web/public/locales/tr.json +++ b/invokeai/frontend/web/public/locales/tr.json @@ -416,10 +416,6 @@ "desc": "Maske/Taban katmanları arasında geçiş yapar", "title": "Katmanı Gizle-Göster" }, - "upscale": { - "title": "Büyüt", - "desc": "Seçili görseli büyüt" - }, "setSeed": { "title": "Tohumu Kullan", "desc": "Seçili görselin tohumunu kullan" @@ -641,7 +637,6 @@ "copyImage": "Görseli Kopyala", "height": "Boy", "width": "En", - "upscale": "Büyüt (Shift + U)", "useSize": "Boyutu Kullan", "symmetry": "Bakışım", "tileSize": "Döşeme Boyutu", @@ -657,7 +652,6 @@ "showOptionsPanel": "Yan Paneli Göster (O ya da T)", "shuffle": "Kar", "usePrompt": "İstemi Kullan", - "upscaleImage": "Görseli Büyüt", "setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (çok küçük olabilir)", "setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (çok büyük olabilir)", "cfgRescaleMultiplier": "CFG Rescale Çarpanı", diff --git a/invokeai/frontend/web/public/locales/uk.json b/invokeai/frontend/web/public/locales/uk.json index 9bb38c21b3..0ade5c7a1f 100644 --- a/invokeai/frontend/web/public/locales/uk.json +++ b/invokeai/frontend/web/public/locales/uk.json @@ -85,10 +85,6 @@ "title": "Відновити обличчя", "desc": "Відновити обличчя на поточному зображенні" }, - "upscale": { - "title": "Збільшення", - "desc": "Збільшити поточне зображення" - }, "showInfo": { "title": "Показати метадані", "desc": "Показати метадані з поточного зображення" @@ -276,8 +272,6 @@ "type": "Тип", "strength": "Сила", "upscaling": "Збільшення", - "upscale": "Збільшити", - "upscaleImage": "Збільшити зображення", "scale": "Масштаб", "imageFit": "Вмістити зображення", "scaleBeforeProcessing": "Масштабувати", diff --git a/invokeai/frontend/web/public/locales/zh_CN.json b/invokeai/frontend/web/public/locales/zh_CN.json index c4bb52e7cc..aa1c723814 100644 --- a/invokeai/frontend/web/public/locales/zh_CN.json +++ b/invokeai/frontend/web/public/locales/zh_CN.json @@ -193,10 +193,6 @@ "title": "面部修复", "desc": "对当前图像进行面部修复" }, - "upscale": { - "title": "放大", - "desc": "对当前图像进行放大" - }, "showInfo": { "title": "显示信息", "desc": "显示当前图像的元数据" @@ -422,8 +418,6 @@ "type": "种类", "strength": "强度", "upscaling": "放大", - "upscale": "放大 (Shift + U)", - "upscaleImage": "放大图像", "scale": "等级", "imageFit": "使生成图像长宽适配初始图像", "scaleBeforeProcessing": "处理前缩放", diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 9698f85219..a1ce52b407 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -1,5 +1,6 @@ import type { TypedStartListening } from '@reduxjs/toolkit'; import { createListenerMiddleware } from '@reduxjs/toolkit'; +import { addAdHocPostProcessingRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener'; import { addCommitStagingAreaImageListener } from 'app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener'; import { addAnyEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/anyEnqueued'; import { addAppConfigReceivedListener } from 'app/store/middleware/listenerMiddleware/listeners/appConfigReceived'; @@ -47,11 +48,11 @@ import { addModelLoadEventListener } from 'app/store/middleware/listenerMiddlewa import { addSocketQueueItemStatusChangedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged'; import { addStagingAreaImageSavedListener } from 'app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved'; import { addUpdateAllNodesRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/updateAllNodesRequested'; -import { addUpscaleRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested'; import { addWorkflowLoadRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested'; import type { AppDispatch, RootState } from 'app/store/store'; import { addArchivedOrDeletedBoardListener } from './listeners/addArchivedOrDeletedBoardListener'; +import { addEnqueueRequestedUpscale } from './listeners/enqueueRequestedUpscale'; export const listenerMiddleware = createListenerMiddleware(); @@ -85,6 +86,7 @@ addGalleryOffsetChangedListener(startAppListening); addEnqueueRequestedCanvasListener(startAppListening); addEnqueueRequestedNodes(startAppListening); addEnqueueRequestedLinear(startAppListening); +addEnqueueRequestedUpscale(startAppListening); addAnyEnqueuedListener(startAppListening); addBatchEnqueuedListener(startAppListening); @@ -140,7 +142,7 @@ addModelsLoadedListener(startAppListening); addAppConfigReceivedListener(startAppListening); // Ad-hoc upscale workflwo -addUpscaleRequestedListener(startAppListening); +addAdHocPostProcessingRequestedListener(startAppListening); // Prompts addDynamicPromptsListener(startAppListening); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/upscaleRequested.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener.ts similarity index 58% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/upscaleRequested.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener.ts index ce480a3573..65f3198b91 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/upscaleRequested.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener.ts @@ -2,46 +2,28 @@ import { createAction } from '@reduxjs/toolkit'; import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { parseify } from 'common/util/serialize'; -import { buildAdHocUpscaleGraph } from 'features/nodes/util/graph/buildAdHocUpscaleGraph'; -import { createIsAllowedToUpscaleSelector } from 'features/parameters/hooks/useIsAllowedToUpscale'; +import { buildAdHocPostProcessingGraph } from 'features/nodes/util/graph/buildAdHocPostProcessingGraph'; import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { queueApi } from 'services/api/endpoints/queue'; import type { BatchConfig, ImageDTO } from 'services/api/types'; -export const upscaleRequested = createAction<{ imageDTO: ImageDTO }>(`upscale/upscaleRequested`); +export const adHocPostProcessingRequested = createAction<{ imageDTO: ImageDTO }>(`upscaling/postProcessingRequested`); -export const addUpscaleRequestedListener = (startAppListening: AppStartListening) => { +export const addAdHocPostProcessingRequestedListener = (startAppListening: AppStartListening) => { startAppListening({ - actionCreator: upscaleRequested, + actionCreator: adHocPostProcessingRequested, effect: async (action, { dispatch, getState }) => { const log = logger('session'); const { imageDTO } = action.payload; - const { image_name } = imageDTO; const state = getState(); - const { isAllowedToUpscale, detailTKey } = createIsAllowedToUpscaleSelector(imageDTO)(state); - - // if we can't upscale, show a toast and return - if (!isAllowedToUpscale) { - log.error( - { imageDTO }, - t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge') // should never coalesce - ); - toast({ - id: 'NOT_ALLOWED_TO_UPSCALE', - title: t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge'), // should never coalesce - status: 'error', - }); - return; - } - const enqueueBatchArg: BatchConfig = { prepend: true, batch: { - graph: buildAdHocUpscaleGraph({ - image_name, + graph: await buildAdHocPostProcessingGraph({ + image: imageDTO, state, }), runs: 1, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts index 1581da9b37..23d3cbc9af 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts @@ -10,32 +10,32 @@ import { import { boardsApi } from 'services/api/endpoints/boards'; import { imagesApi } from 'services/api/endpoints/images'; +// Type inference doesn't work for this if you inline it in the listener for some reason +const matchAnyBoardDeleted = isAnyOf( + imagesApi.endpoints.deleteBoard.matchFulfilled, + imagesApi.endpoints.deleteBoardAndImages.matchFulfilled +); + export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartListening) => { /** * The auto-add board shouldn't be set to an archived board or deleted board. When we archive a board, delete * a board, or change a the archived board visibility flag, we may need to reset the auto-add board. */ startAppListening({ - matcher: isAnyOf( - // If a board is deleted, we'll need to reset the auto-add board - imagesApi.endpoints.deleteBoard.matchFulfilled, - imagesApi.endpoints.deleteBoardAndImages.matchFulfilled - ), + matcher: matchAnyBoardDeleted, effect: async (action, { dispatch, getState }) => { const state = getState(); - const queryArgs = selectListBoardsQueryArgs(state); - const queryResult = boardsApi.endpoints.listAllBoards.select(queryArgs)(state); + const deletedBoardId = action.meta.arg.originalArgs; const { autoAddBoardId, selectedBoardId } = state.gallery; - if (!queryResult.data) { - return; - } - - if (!queryResult.data.find((board) => board.board_id === selectedBoardId)) { + // If the deleted board was currently selected, we should reset the selected board to uncategorized + if (deletedBoardId === selectedBoardId) { dispatch(boardIdSelected({ boardId: 'none' })); dispatch(galleryViewChanged('images')); } - if (!queryResult.data.find((board) => board.board_id === autoAddBoardId)) { + + // If the deleted board was selected for auto-add, we should reset the auto-add board to uncategorized + if (deletedBoardId === autoAddBoardId) { dispatch(autoAddBoardIdChanged('none')); } }, @@ -46,14 +46,8 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis matcher: boardsApi.endpoints.updateBoard.matchFulfilled, effect: async (action, { dispatch, getState }) => { const state = getState(); - const queryArgs = selectListBoardsQueryArgs(state); - const queryResult = boardsApi.endpoints.listAllBoards.select(queryArgs)(state); const { shouldShowArchivedBoards } = state.gallery; - if (!queryResult.data) { - return; - } - const wasArchived = action.meta.arg.originalArgs.changes.archived === true; if (wasArchived && !shouldShowArchivedBoards) { @@ -71,7 +65,7 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis const shouldShowArchivedBoards = action.payload; // We only need to take action if we have just hidden archived boards. - if (!shouldShowArchivedBoards) { + if (shouldShowArchivedBoards) { return; } @@ -86,14 +80,16 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis // Handle the case where selected board is archived const selectedBoard = queryResult.data.find((b) => b.board_id === selectedBoardId); - if (selectedBoard && selectedBoard.archived) { + if (!selectedBoard || selectedBoard.archived) { + // If we can't find the selected board or it's archived, we should reset the selected board to uncategorized dispatch(boardIdSelected({ boardId: 'none' })); dispatch(galleryViewChanged('images')); } // Handle the case where auto-add board is archived const autoAddBoard = queryResult.data.find((b) => b.board_id === autoAddBoardId); - if (autoAddBoard && autoAddBoard.archived) { + if (!autoAddBoard || autoAddBoard.archived) { + // If we can't find the auto-add board or it's archived, we should reset the selected board to uncategorized dispatch(autoAddBoardIdChanged('none')); } }, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedUpscale.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedUpscale.ts new file mode 100644 index 0000000000..dc870a9f8b --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedUpscale.ts @@ -0,0 +1,36 @@ +import { enqueueRequested } from 'app/store/actions'; +import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; +import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice'; +import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig'; +import { buildMultidiffusionUpscaleGraph } from 'features/nodes/util/graph/buildMultidiffusionUpscaleGraph'; +import { queueApi } from 'services/api/endpoints/queue'; + +export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening) => { + startAppListening({ + predicate: (action): action is ReturnType => + enqueueRequested.match(action) && action.payload.tabName === 'upscaling', + effect: async (action, { getState, dispatch }) => { + const state = getState(); + const { shouldShowProgressInViewer } = state.ui; + const { prepend } = action.payload; + + const graph = await buildMultidiffusionUpscaleGraph(state); + + const batchConfig = prepareLinearUIBatch(state, graph, prepend); + + const req = dispatch( + queueApi.endpoints.enqueueBatch.initiate(batchConfig, { + fixedCacheKey: 'enqueueBatch', + }) + ); + try { + await req.unwrap(); + if (shouldShowProgressInViewer) { + dispatch(isImageViewerOpenChanged(true)); + } + } finally { + req.reset(); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts index 0cd77dc2e7..a65c31b7cd 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts @@ -23,6 +23,7 @@ import { } from 'features/gallery/store/gallerySlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; +import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice'; import { imagesApi } from 'services/api/endpoints/images'; export const dndDropped = createAction<{ @@ -243,6 +244,20 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) => return; } + /** + * Image dropped on upscale initial image + */ + if ( + overData.actionType === 'SET_UPSCALE_INITIAL_IMAGE' && + activeData.payloadType === 'IMAGE_DTO' && + activeData.payload.imageDTO + ) { + const { imageDTO } = activeData.payload; + + dispatch(upscaleInitialImageChanged(imageDTO)); + return; + } + /** * Multiple images dropped on user board */ diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index bd1ee47825..1aa47345e1 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -14,6 +14,7 @@ import { import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; +import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice'; import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { omit } from 'lodash-es'; @@ -89,6 +90,15 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis return; } + if (postUploadAction?.type === 'SET_UPSCALE_INITIAL_IMAGE') { + dispatch(upscaleInitialImageChanged(imageDTO)); + toast({ + ...DEFAULT_UPLOADED_TOAST, + description: 'set as upscale initial image', + }); + return; + } + if (postUploadAction?.type === 'SET_CONTROL_ADAPTER_IMAGE') { const { id } = postUploadAction; dispatch( diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index eb86f54c84..9d02fcbfa5 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -10,6 +10,7 @@ import { heightChanged, widthChanged } from 'features/controlLayers/store/contro import { loraRemoved } from 'features/lora/store/loraSlice'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice'; +import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice'; import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas'; import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice'; @@ -17,7 +18,12 @@ import { forEach } from 'lodash-es'; import type { Logger } from 'roarr'; import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; import type { AnyModelConfig } from 'services/api/types'; -import { isNonRefinerMainModelConfig, isRefinerMainModelModelConfig, isVAEModelConfig } from 'services/api/types'; +import { + isNonRefinerMainModelConfig, + isRefinerMainModelModelConfig, + isSpandrelImageToImageModelConfig, + isVAEModelConfig, +} from 'services/api/types'; export const addModelsLoadedListener = (startAppListening: AppStartListening) => { startAppListening({ @@ -36,6 +42,7 @@ export const addModelsLoadedListener = (startAppListening: AppStartListening) => handleVAEModels(models, state, dispatch, log); handleLoRAModels(models, state, dispatch, log); handleControlAdapterModels(models, state, dispatch, log); + handleSpandrelImageToImageModels(models, state, dispatch, log); }, }); }; @@ -177,3 +184,25 @@ const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) dispatch(controlAdapterModelCleared({ id: ca.id })); }); }; + +const handleSpandrelImageToImageModels: ModelHandler = (models, state, dispatch, _log) => { + const { upscaleModel: currentUpscaleModel, postProcessingModel: currentPostProcessingModel } = state.upscale; + const upscaleModels = models.filter(isSpandrelImageToImageModelConfig); + const firstModel = upscaleModels[0] || null; + + const isCurrentUpscaleModelAvailable = currentUpscaleModel + ? upscaleModels.some((m) => m.key === currentUpscaleModel.key) + : false; + + if (!isCurrentUpscaleModelAvailable) { + dispatch(upscaleModelChanged(firstModel)); + } + + const isCurrentPostProcessingModelAvailable = currentPostProcessingModel + ? upscaleModels.some((m) => m.key === currentPostProcessingModel.key) + : false; + + if (!isCurrentPostProcessingModelAvailable) { + dispatch(postProcessingModelChanged(firstModel)); + } +}; diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 062cdc1cbf..6ae2011355 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -25,7 +25,7 @@ import { nodesPersistConfig, nodesSlice, nodesUndoableConfig } from 'features/no import { workflowSettingsPersistConfig, workflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice'; import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workflowSlice'; import { generationPersistConfig, generationSlice } from 'features/parameters/store/generationSlice'; -import { postprocessingPersistConfig, postprocessingSlice } from 'features/parameters/store/postprocessingSlice'; +import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/upscaleSlice'; import { queueSlice } from 'features/queue/store/queueSlice'; import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice'; import { configSlice } from 'features/system/store/configSlice'; @@ -52,7 +52,6 @@ const allReducers = { [gallerySlice.name]: gallerySlice.reducer, [generationSlice.name]: generationSlice.reducer, [nodesSlice.name]: undoable(nodesSlice.reducer, nodesUndoableConfig), - [postprocessingSlice.name]: postprocessingSlice.reducer, [systemSlice.name]: systemSlice.reducer, [configSlice.name]: configSlice.reducer, [uiSlice.name]: uiSlice.reducer, @@ -69,6 +68,7 @@ const allReducers = { [controlLayersSlice.name]: undoable(controlLayersSlice.reducer, controlLayersUndoableConfig), [workflowSettingsSlice.name]: workflowSettingsSlice.reducer, [api.reducerPath]: api.reducer, + [upscaleSlice.name]: upscaleSlice.reducer, }; const rootReducer = combineReducers(allReducers); @@ -102,7 +102,6 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = { [galleryPersistConfig.name]: galleryPersistConfig, [generationPersistConfig.name]: generationPersistConfig, [nodesPersistConfig.name]: nodesPersistConfig, - [postprocessingPersistConfig.name]: postprocessingPersistConfig, [systemPersistConfig.name]: systemPersistConfig, [workflowPersistConfig.name]: workflowPersistConfig, [uiPersistConfig.name]: uiPersistConfig, @@ -114,6 +113,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = { [hrfPersistConfig.name]: hrfPersistConfig, [controlLayersPersistConfig.name]: controlLayersPersistConfig, [workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig, + [upscalePersistConfig.name]: upscalePersistConfig, }; const unserialize: UnserializeFunction = (data, key) => { diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 6d7416d95d..bb27cf58a8 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -72,7 +72,6 @@ export type AppConfig = { canRestoreDeletedImagesFromBin: boolean; nodesAllowlist: string[] | undefined; nodesDenylist: string[] | undefined; - maxUpscalePixels?: number; metadataFetchDebounce?: number; workflowFetchDebounce?: number; isLocal?: boolean; diff --git a/invokeai/frontend/web/src/common/components/InformationalPopover/InformationalPopover.tsx b/invokeai/frontend/web/src/common/components/InformationalPopover/InformationalPopover.tsx index 935212ee58..cb295bb0f4 100644 --- a/invokeai/frontend/web/src/common/components/InformationalPopover/InformationalPopover.tsx +++ b/invokeai/frontend/web/src/common/components/InformationalPopover/InformationalPopover.tsx @@ -10,9 +10,12 @@ import { PopoverContent, PopoverTrigger, Portal, + Spacer, Text, } from '@invoke-ai/ui-library'; -import { useAppSelector } from 'app/store/storeHooks'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { setShouldEnableInformationalPopovers } from 'features/system/store/systemSlice'; +import { toast } from 'features/toast/toast'; import { merge, omit } from 'lodash-es'; import type { ReactElement } from 'react'; import { memo, useCallback, useMemo } from 'react'; @@ -71,7 +74,7 @@ type ContentProps = { const Content = ({ data, feature }: ContentProps) => { const { t } = useTranslation(); - + const dispatch = useAppDispatch(); const heading = useMemo(() => t(`popovers.${feature}.heading`), [feature, t]); const paragraphs = useMemo( @@ -82,16 +85,25 @@ const Content = ({ data, feature }: ContentProps) => { [feature, t] ); - const handleClick = useCallback(() => { + const onClickLearnMore = useCallback(() => { if (!data?.href) { return; } window.open(data.href); }, [data?.href]); + const onClickDontShowMeThese = useCallback(() => { + dispatch(setShouldEnableInformationalPopovers(false)); + toast({ + title: t('settings.informationalPopoversDisabled'), + description: t('settings.informationalPopoversDisabledDesc'), + status: 'info', + }); + }, [dispatch, t]); + return ( - - + + {heading && ( @@ -116,20 +128,19 @@ const Content = ({ data, feature }: ContentProps) => { {paragraphs.map((p) => ( {p} ))} - {data?.href && ( - <> - - + + {data?.href && ( + - - )} + )} + diff --git a/invokeai/frontend/web/src/common/components/InformationalPopover/constants.ts b/invokeai/frontend/web/src/common/components/InformationalPopover/constants.ts index f973d98f1a..de28f72223 100644 --- a/invokeai/frontend/web/src/common/components/InformationalPopover/constants.ts +++ b/invokeai/frontend/web/src/common/components/InformationalPopover/constants.ts @@ -53,7 +53,11 @@ export type Feature = | 'refinerCfgScale' | 'scaleBeforeProcessing' | 'seamlessTilingXAxis' - | 'seamlessTilingYAxis'; + | 'seamlessTilingYAxis' + | 'upscaleModel' + | 'scale' + | 'creativity' + | 'structure'; export type PopoverData = PopoverProps & { image?: string; diff --git a/invokeai/frontend/web/src/common/hooks/useAssertSingleton.ts b/invokeai/frontend/web/src/common/hooks/useAssertSingleton.ts new file mode 100644 index 0000000000..0f7cc9db6f --- /dev/null +++ b/invokeai/frontend/web/src/common/hooks/useAssertSingleton.ts @@ -0,0 +1,18 @@ +import { useEffect } from 'react'; +import { assert } from 'tsafe'; + +const IDS = new Set(); + +/** + * Asserts that there is only one instance of a singleton entity. It can be a hook or a component. + * @param id The ID of the singleton entity. + */ +export function useAssertSingleton(id: string) { + useEffect(() => { + assert(!IDS.has(id), `There should be only one instance of ${id}`); + IDS.add(id); + return () => { + IDS.delete(id); + }; + }, [id]); +} diff --git a/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts b/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts index 5b1bf1f5b3..d8e7d70a8c 100644 --- a/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts +++ b/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts @@ -21,6 +21,10 @@ const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (ac postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' }; } + if (activeTabName === 'upscaling') { + postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' }; + } + return postUploadAction; }); diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index dbf3c41480..ba2117f207 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -15,6 +15,7 @@ import type { Templates } from 'features/nodes/store/types'; import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice'; import { isInvocationNode } from 'features/nodes/types/invocation'; import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; +import { selectUpscalelice } from 'features/parameters/store/upscaleSlice'; import { selectSystemSlice } from 'features/system/store/systemSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import i18n from 'i18next'; @@ -40,8 +41,19 @@ const createSelector = (templates: Templates) => selectDynamicPromptsSlice, selectControlLayersSlice, activeTabNameSelector, + selectUpscalelice, ], - (controlAdapters, generation, system, nodes, workflowSettings, dynamicPrompts, controlLayers, activeTabName) => { + ( + controlAdapters, + generation, + system, + nodes, + workflowSettings, + dynamicPrompts, + controlLayers, + activeTabName, + upscale + ) => { const { model } = generation; const { size } = controlLayers.present; const { positivePrompt } = controlLayers.present; @@ -194,6 +206,16 @@ const createSelector = (templates: Templates) => reasons.push({ prefix, content }); } }); + } else if (activeTabName === 'upscaling') { + if (!upscale.upscaleInitialImage) { + reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') }); + } + if (!upscale.upscaleModel) { + reasons.push({ content: i18n.t('upscaling.missingUpscaleModel') }); + } + if (!upscale.tileControlnetModel) { + reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') }); + } } else { // Handling for all other tabs selectControlAdapterAll(controlAdapters) diff --git a/invokeai/frontend/web/src/features/dnd/types/index.ts b/invokeai/frontend/web/src/features/dnd/types/index.ts index 6fcf18421e..93bde117a1 100644 --- a/invokeai/frontend/web/src/features/dnd/types/index.ts +++ b/invokeai/frontend/web/src/features/dnd/types/index.ts @@ -62,6 +62,10 @@ export type CanvasInitialImageDropData = BaseDropData & { actionType: 'SET_CANVAS_INITIAL_IMAGE'; }; +type UpscaleInitialImageDropData = BaseDropData & { + actionType: 'SET_UPSCALE_INITIAL_IMAGE'; +}; + type NodesImageDropData = BaseDropData & { actionType: 'SET_NODES_IMAGE'; context: { @@ -98,7 +102,8 @@ export type TypesafeDroppableData = | IPALayerImageDropData | RGLayerIPAdapterImageDropData | IILayerImageDropData - | SelectForCompareDropData; + | SelectForCompareDropData + | UpscaleInitialImageDropData; type BaseDragData = { id: string; diff --git a/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts b/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts index 6dec862345..3f8fe5ab73 100644 --- a/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts +++ b/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts @@ -27,6 +27,8 @@ export const isValidDrop = (overData?: TypesafeDroppableData | null, activeData? return payloadType === 'IMAGE_DTO'; case 'SET_CANVAS_INITIAL_IMAGE': return payloadType === 'IMAGE_DTO'; + case 'SET_UPSCALE_INITIAL_IMAGE': + return payloadType === 'IMAGE_DTO'; case 'SET_NODES_IMAGE': return payloadType === 'IMAGE_DTO'; case 'SELECT_FOR_COMPARE': diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx new file mode 100644 index 0000000000..63ba2991cf --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx @@ -0,0 +1,47 @@ +import { Flex, Image, Text } from '@invoke-ai/ui-library'; +import { skipToken } from '@reduxjs/toolkit/query'; +import { useTranslation } from 'react-i18next'; +import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards'; +import { useGetImageDTOQuery } from 'services/api/endpoints/images'; +import type { BoardDTO } from 'services/api/types'; + +type Props = { + board: BoardDTO | null; +}; + +export const BoardTooltip = ({ board }: Props) => { + const { t } = useTranslation(); + const { imagesTotal } = useGetBoardImagesTotalQuery(board?.board_id || 'none', { + selectFromResult: ({ data }) => { + return { imagesTotal: data?.total ?? 0 }; + }, + }); + const { assetsTotal } = useGetBoardAssetsTotalQuery(board?.board_id || 'none', { + selectFromResult: ({ data }) => { + return { assetsTotal: data?.total ?? 0 }; + }, + }); + const { currentData: coverImage } = useGetImageDTOQuery(board?.cover_image_name ?? skipToken); + + return ( + + {coverImage && ( + + )} + + + {t('boards.imagesWithCount', { count: imagesTotal })}, {t('boards.assetsWithCount', { count: assetsTotal })} + + {board?.archived && ({t('boards.archived')})} + + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTotalsTooltip.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTotalsTooltip.tsx deleted file mode 100644 index b4c89a002d..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTotalsTooltip.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards'; - -type Props = { - board_id: string; - isArchived: boolean; -}; - -export const BoardTotalsTooltip = ({ board_id, isArchived }: Props) => { - const { t } = useTranslation(); - const { imagesTotal } = useGetBoardImagesTotalQuery(board_id, { - selectFromResult: ({ data }) => { - return { imagesTotal: data?.total ?? 0 }; - }, - }); - const { assetsTotal } = useGetBoardAssetsTotalQuery(board_id, { - selectFromResult: ({ data }) => { - return { assetsTotal: data?.total ?? 0 }; - }, - }); - return `${t('boards.imagesWithCount', { count: imagesTotal })}, ${t('boards.assetsWithCount', { count: assetsTotal })}${isArchived ? ` (${t('boards.archived')})` : ''}`; -}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx index 4325281e0f..d5a96e5966 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx @@ -1,13 +1,10 @@ -import { Box, Flex, Text } from '@invoke-ai/ui-library'; +import { Button, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library'; import { EMPTY_ARRAY } from 'app/store/constants'; import { useAppSelector } from 'app/store/storeHooks'; -import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; -import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal'; import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; -import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; -import type { CSSProperties } from 'react'; -import { memo, useMemo, useState } from 'react'; +import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +import { PiCaretDownBold } from 'react-icons/pi'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; import type { BoardDTO } from 'services/api/types'; @@ -15,101 +12,111 @@ import AddBoardButton from './AddBoardButton'; import GalleryBoard from './GalleryBoard'; import NoBoardBoard from './NoBoardBoard'; -const overlayScrollbarsStyles: CSSProperties = { - height: '100%', - width: '100%', +type Props = { + isPrivate: boolean; + setBoardToDelete: (board?: BoardDTO) => void; }; -const BoardsList = () => { +export const BoardsList = ({ isPrivate, setBoardToDelete }: Props) => { + const { t } = useTranslation(); const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); - const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); const queryArgs = useAppSelector(selectListBoardsQueryArgs); const { data: boards } = useListAllBoardsQuery(queryArgs); - const [boardToDelete, setBoardToDelete] = useState(); - const { t } = useTranslation(); + const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); + const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); - const { filteredPrivateBoards, filteredSharedBoards } = useMemo(() => { - const filteredBoards = boardSearchText - ? boards?.filter((board) => board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) - : boards; - const filteredPrivateBoards = filteredBoards?.filter((board) => board.is_private) ?? EMPTY_ARRAY; - const filteredSharedBoards = filteredBoards?.filter((board) => !board.is_private) ?? EMPTY_ARRAY; - return { filteredPrivateBoards, filteredSharedBoards }; - }, [boardSearchText, boards]); + const filteredBoards = useMemo(() => { + if (!boards) { + return EMPTY_ARRAY; + } + + return boards.filter((board) => { + if (boardSearchText.length) { + return board.is_private === isPrivate && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase()); + } else { + return board.is_private === isPrivate; + } + }); + }, [boardSearchText, boards, isPrivate]); + + const boardElements = useMemo(() => { + const elements = []; + if (allowPrivateBoards && isPrivate && !boardSearchText.length) { + elements.push(); + } + + if (!allowPrivateBoards && !boardSearchText.length) { + elements.push(); + } + + filteredBoards.forEach((board) => { + elements.push( + + ); + }); + + return elements; + }, [allowPrivateBoards, isPrivate, boardSearchText.length, filteredBoards, selectedBoardId, setBoardToDelete]); + + const boardListTitle = useMemo(() => { + if (allowPrivateBoards) { + return isPrivate ? t('boards.private') : t('boards.shared'); + } else { + return t('boards.boards'); + } + }, [isPrivate, allowPrivateBoards, t]); return ( - <> - - - - {allowPrivateBoards && ( - - - - {t('boards.private')} - - - - - - {filteredPrivateBoards.map((board) => ( - - ))} - - - )} - - - - {allowPrivateBoards ? t('boards.shared') : t('boards.boards')} - - - - - {!allowPrivateBoards && } - {filteredSharedBoards.map((board) => ( - - ))} - + + + {allowPrivateBoards ? ( + + ) : ( + + {boardListTitle} + + )} + + + + + {boardElements.length ? ( + boardElements + ) : ( + + {t('boards.noBoards', { boardType: boardSearchText.length ? 'Matching' : '' })} + + )} + + + ); }; -export default memo(BoardsList); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx new file mode 100644 index 0000000000..637dbd304a --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx @@ -0,0 +1,35 @@ +import { Box } from '@invoke-ai/ui-library'; +import { useAppSelector } from 'app/store/storeHooks'; +import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; +import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import type { CSSProperties } from 'react'; +import { memo, useState } from 'react'; +import type { BoardDTO } from 'services/api/types'; + +import { BoardsList } from './BoardsList'; + +const overlayScrollbarsStyles: CSSProperties = { + height: '100%', + width: '100%', +}; + +const BoardsListWrapper = () => { + const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); + const [boardToDelete, setBoardToDelete] = useState(); + + return ( + <> + + + + {allowPrivateBoards && } + + + + + + + ); +}; +export default memo(BoardsListWrapper); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx index fb53a13795..931c1e6cbb 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx @@ -40,7 +40,7 @@ const BoardsSearch = () => { ); return ( - + {(ref) => ( - } - openDelay={1000} - placement="left" - closeOnScroll - > + } openDelay={1000} placement="left" closeOnScroll p={2}> @@ -168,7 +165,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps {board.archived && !editingDisclosure.isOpen && } {!editingDisclosure.isOpen && {board.image_count}} - {t('unifiedCanvas.move')}} /> + {t('unifiedCanvas.move')}} /> )} @@ -197,8 +194,8 @@ const CoverImage = ({ board }: { board: BoardDTO }) => { src={coverImage.thumbnail_url} draggable={false} objectFit="cover" - w={8} - h={8} + w={10} + h={10} borderRadius="base" borderBottomRadius="lg" /> @@ -206,8 +203,8 @@ const CoverImage = ({ board }: { board: BoardDTO }) => { } return ( - - + + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx index 14bf3d5742..c8b3ceb431 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx @@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; import type { RemoveFromBoardDropData } from 'features/dnd/types'; import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge'; -import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; +import { BoardTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTooltip'; import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu'; import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; import { memo, useCallback, useMemo } from 'react'; @@ -46,25 +46,16 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { [] ); - const filteredOut = useMemo(() => { - return boardSearchText ? !boardName.toLowerCase().includes(boardSearchText.toLowerCase()) : false; - }, [boardName, boardSearchText]); - const { t } = useTranslation(); - if (filteredOut) { + if (boardSearchText.length) { return null; } return ( {(ref) => ( - } - openDelay={1000} - placement="left" - closeOnScroll - > + } openDelay={1000} placement="left" closeOnScroll> { alignItems="center" borderRadius="base" cursor="pointer" - px={2} py={1} - gap={2} + ps={1} + pe={4} + gap={4} bg={isSelected ? 'base.850' : undefined} _hover={_hover} + h={12} > - + {/* iconified from public/assets/images/invoke-symbol-wht-lrg.svg */} - + { - + {boardName} {autoAddBoardId === 'none' && } {imagesTotal} - {t('unifiedCanvas.move')}} /> + {t('unifiedCanvas.move')}} /> )} diff --git a/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx b/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx new file mode 100644 index 0000000000..8776fa888b --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx @@ -0,0 +1,105 @@ +import type { ChakraProps } from '@invoke-ai/ui-library'; +import { + Box, + Collapse, + Flex, + IconButton, + Spacer, + Tab, + TabList, + Tabs, + Text, + useDisclosure, +} from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useGallerySearchTerm } from 'features/gallery/components/ImageGrid/useGallerySearchTerm'; +import { galleryViewChanged } from 'features/gallery/store/gallerySlice'; +import type { CSSProperties } from 'react'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiMagnifyingGlassBold } from 'react-icons/pi'; +import { useBoardName } from 'services/api/hooks/useBoardName'; + +import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; +import { GalleryPagination } from './ImageGrid/GalleryPagination'; +import { GallerySearch } from './ImageGrid/GallerySearch'; + +const BASE_STYLES: ChakraProps['sx'] = { + fontWeight: 'semibold', + fontSize: 'sm', + color: 'base.300', +}; + +const SELECTED_STYLES: ChakraProps['sx'] = { + borderColor: 'base.800', + borderBottomColor: 'base.900', + color: 'invokeBlue.300', +}; + +const COLLAPSE_STYLES: CSSProperties = { flexShrink: 0, minHeight: 0 }; + +export const Gallery = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const galleryView = useAppSelector((s) => s.gallery.galleryView); + const initialSearchTerm = useAppSelector((s) => s.gallery.searchTerm); + const searchDisclosure = useDisclosure({ defaultIsOpen: initialSearchTerm.length > 0 }); + const [searchTerm, onChangeSearchTerm, onResetSearchTerm] = useGallerySearchTerm(); + + const handleClickImages = useCallback(() => { + dispatch(galleryViewChanged('images')); + }, [dispatch]); + + const handleClickAssets = useCallback(() => { + dispatch(galleryViewChanged('assets')); + }, [dispatch]); + + const handleClickSearch = useCallback(() => { + searchDisclosure.onToggle(); + onResetSearchTerm(); + }, [onResetSearchTerm, searchDisclosure]); + + const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); + const boardName = useBoardName(selectedBoardId); + + return ( + + + + + {boardName} + + + + {t('parameters.images')} + + + {t('gallery.assets')} + + } + colorScheme={searchDisclosure.isOpen ? 'invokeBlue' : 'base'} + variant="link" + /> + + + + + + + + + + + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx deleted file mode 100644 index 8ede311f9e..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Flex, Text } from '@invoke-ai/ui-library'; -import { useAppSelector } from 'app/store/storeHooks'; -import { memo } from 'react'; -import { useBoardName } from 'services/api/hooks/useBoardName'; - -type Props = { - onClick: () => void; -}; - -const GalleryBoardName = (props: Props) => { - const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); - const boardName = useBoardName(selectedBoardId); - - return ( - - - {boardName} - - - ); -}; - -export default memo(GalleryBoardName); diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryHeader.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryHeader.tsx index 69c2c8fe35..ce9436811c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryHeader.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryHeader.tsx @@ -3,32 +3,21 @@ import { useStore } from '@nanostores/react'; import { $projectName, $projectUrl } from 'app/store/nanostores/projectId'; import { memo } from 'react'; -import GalleryBoardName from './GalleryBoardName'; - -type Props = { - onClickBoardName: () => void; -}; - -export const GalleryHeader = memo((props: Props) => { +export const GalleryHeader = memo(() => { const projectName = useStore($projectName); const projectUrl = useStore($projectUrl); if (projectName && projectUrl) { return ( - + {projectName} - ); } - return ( - - - - ); + return null; }); GalleryHeader.displayName = 'GalleryHeader'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx index 31df113115..ab12684c11 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -13,6 +13,7 @@ import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/ac import { imageToCompareChanged } from 'features/gallery/store/gallerySlice'; import { $templates } from 'features/nodes/store/nodesSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; +import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { toast } from 'features/toast/toast'; import { setActiveTab } from 'features/ui/store/uiSlice'; @@ -124,6 +125,11 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { dispatch(imageToCompareChanged(imageDTO)); }, [dispatch, imageDTO]); + const handleSendToUpscale = useCallback(() => { + dispatch(upscaleInitialImageChanged(imageDTO)); + dispatch(setActiveTab('upscaling')); + }, [dispatch, imageDTO]); + return ( <> }> @@ -185,6 +191,9 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { {t('parameters.sendToUnifiedCanvas')} )} + } onClickCapture={handleSendToUpscale} id="send-to-upscale"> + {t('parameters.sendToUpscale')} + } onClickCapture={handleChangeBoard}> {t('boards.changeBoard')} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 5a096f5cef..a42410b885 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -1,57 +1,28 @@ -import type { ChakraProps } from '@invoke-ai/ui-library'; -import { - Box, - Collapse, - Divider, - Flex, - IconButton, - Spacer, - Tab, - TabList, - Tabs, - useDisclosure, -} from '@invoke-ai/ui-library'; +import { Box, Button, Collapse, Divider, Flex, IconButton, useDisclosure } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { GalleryHeader } from 'features/gallery/components/GalleryHeader'; -import { galleryViewChanged } from 'features/gallery/store/gallerySlice'; +import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice'; import ResizeHandle from 'features/ui/components/tabs/ResizeHandle'; import { usePanel, type UsePanelOptions } from 'features/ui/hooks/usePanel'; import type { CSSProperties } from 'react'; import { memo, useCallback, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiMagnifyingGlassBold } from 'react-icons/pi'; +import { PiCaretDownBold, PiCaretUpBold, PiMagnifyingGlassBold } from 'react-icons/pi'; import type { ImperativePanelGroupHandle } from 'react-resizable-panels'; import { Panel, PanelGroup } from 'react-resizable-panels'; -import BoardsList from './Boards/BoardsList/BoardsList'; +import BoardsListWrapper from './Boards/BoardsList/BoardsListWrapper'; import BoardsSearch from './Boards/BoardsList/BoardsSearch'; +import { Gallery } from './Gallery'; import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover'; -import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; -import { GalleryPagination } from './ImageGrid/GalleryPagination'; -import { GallerySearch } from './ImageGrid/GallerySearch'; const COLLAPSE_STYLES: CSSProperties = { flexShrink: 0, minHeight: 0 }; -const BASE_STYLES: ChakraProps['sx'] = { - fontWeight: 'semibold', - fontSize: 'sm', - color: 'base.300', -}; - -const SELECTED_STYLES: ChakraProps['sx'] = { - borderColor: 'base.800', - borderBottomColor: 'base.900', - color: 'invokeBlue.300', -}; - const ImageGalleryContent = () => { const { t } = useTranslation(); - const galleryView = useAppSelector((s) => s.gallery.galleryView); - const searchTerm = useAppSelector((s) => s.gallery.searchTerm); const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); const dispatch = useAppDispatch(); - const searchDisclosure = useDisclosure({ defaultIsOpen: false }); - const boardSearchDisclosure = useDisclosure({ defaultIsOpen: false }); + const boardSearchDisclosure = useDisclosure({ defaultIsOpen: !!boardSearchText.length }); const panelGroupRef = useRef(null); const boardsListPanelOptions = useMemo( @@ -67,42 +38,58 @@ const ImageGalleryContent = () => { ); const boardsListPanel = usePanel(boardsListPanelOptions); - const handleClickImages = useCallback(() => { - dispatch(galleryViewChanged('images')); - }, [dispatch]); + const handleClickBoardSearch = useCallback(() => { + if (boardSearchText.length) { + dispatch(boardSearchTextChanged('')); + } + boardSearchDisclosure.onToggle(); + boardsListPanel.expand(); + }, [boardSearchText.length, boardSearchDisclosure, boardsListPanel, dispatch]); - const handleClickAssets = useCallback(() => { - dispatch(galleryViewChanged('assets')); - }, [dispatch]); + const handleToggleBoardPanel = useCallback(() => { + if (boardSearchText.length) { + dispatch(boardSearchTextChanged('')); + } + boardSearchDisclosure.onClose(); + boardsListPanel.toggle(); + }, [boardSearchText.length, boardSearchDisclosure, boardsListPanel, dispatch]); return ( - - - - - } - variant="link" - /> - {boardSearchText && ( - - )} - + + + + + + + + } + colorScheme={boardSearchDisclosure.isOpen ? 'invokeBlue' : 'base'} + variant="link" + /> + + + + { > - + + + - + { onDoubleClick={boardsListPanel.onDoubleClickHandle} /> - - - - - {t('parameters.images')} - - - {t('gallery.assets')} - - - - } - variant="link" - /> - {searchTerm && ( - - )} - - - - - - - - - - - - - + diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryPagination.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryPagination.tsx index ddc23ebe4b..27776ab77d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryPagination.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryPagination.tsx @@ -3,6 +3,8 @@ import { ELLIPSIS, useGalleryPagination } from 'features/gallery/hooks/useGaller import { useCallback } from 'react'; import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi'; +import { JumpTo } from './JumpTo'; + export const GalleryPagination = () => { const { goPrev, goNext, isPrevEnabled, isNextEnabled, pageButtons, goToPage, currentPage, total } = useGalleryPagination(); @@ -20,7 +22,7 @@ export const GalleryPagination = () => { } return ( - + { variant="ghost" /> - {pageButtons.map((page, i) => { - if (page === ELLIPSIS) { - return ( - - ); - } - return ( - - ); - })} + {pageButtons.map((page, i) => ( + + ))} { isDisabled={!isNextEnabled} variant="ghost" /> + ); }; + +type PageButtonProps = { + page: number | typeof ELLIPSIS; + currentPage: number; + goToPage: (page: number) => void; +}; + +const PageButton = ({ page, currentPage, goToPage }: PageButtonProps) => { + if (page === ELLIPSIS) { + return ( + + ); + } + return ( + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx index 9e3a1bf487..bb2fe8ff29 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx @@ -1,59 +1,60 @@ import { IconButton, Input, InputGroup, InputRightElement, Spinner } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useAppSelector } from 'app/store/storeHooks'; import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors'; -import { searchTermChanged } from 'features/gallery/store/gallerySlice'; -import { debounce } from 'lodash-es'; -import type { ChangeEvent } from 'react'; -import { useCallback, useMemo, useState } from 'react'; +import type { ChangeEvent, KeyboardEvent } from 'react'; +import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiXBold } from 'react-icons/pi'; import { useListImagesQuery } from 'services/api/endpoints/images'; -export const GallerySearch = () => { - const dispatch = useAppDispatch(); - const searchTerm = useAppSelector((s) => s.gallery.searchTerm); +type Props = { + searchTerm: string; + onChangeSearchTerm: (value: string) => void; + onResetSearchTerm: () => void; +}; + +export const GallerySearch = ({ searchTerm, onChangeSearchTerm, onResetSearchTerm }: Props) => { const { t } = useTranslation(); - const [searchTermInput, setSearchTermInput] = useState(searchTerm); const queryArgs = useAppSelector(selectListImagesQueryArgs); const { isPending } = useListImagesQuery(queryArgs, { selectFromResult: ({ isLoading, isFetching }) => ({ isPending: isLoading || isFetching }), }); - const debouncedSetSearchTerm = useMemo(() => { - return debounce((value: string) => { - dispatch(searchTermChanged(value)); - }, 1000); - }, [dispatch]); const handleChangeInput = useCallback( (e: ChangeEvent) => { - setSearchTermInput(e.target.value); - debouncedSetSearchTerm(e.target.value); + onChangeSearchTerm(e.target.value); }, - [debouncedSetSearchTerm] + [onChangeSearchTerm] ); - const handleClearInput = useCallback(() => { - setSearchTermInput(''); - dispatch(searchTermChanged('')); - }, [dispatch]); + const handleKeydown = useCallback( + (e: KeyboardEvent) => { + // exit search mode on escape + if (e.key === 'Escape') { + onResetSearchTerm(); + } + }, + [onResetSearchTerm] + ); return ( {isPending && ( )} - {!isPending && searchTermInput.length && ( + {!isPending && searchTerm.length && ( { + const { t } = useTranslation(); + const { goToPage, currentPage, pages } = useGalleryPagination(); + const [newPage, setNewPage] = useState(currentPage); + const { isOpen, onToggle, onClose } = useDisclosure(); + const ref = useRef(null); + + const onOpen = useCallback(() => { + setNewPage(currentPage); + setTimeout(() => { + const input = ref.current?.querySelector('input'); + input?.focus(); + input?.select(); + }, 0); + }, [currentPage]); + + const onChangeJumpTo = useCallback((v: number) => { + setNewPage(v - 1); + }, []); + + const onClickGo = useCallback(() => { + goToPage(newPage); + onClose(); + }, [newPage, goToPage, onClose]); + + useHotkeys( + 'enter', + () => { + onClickGo(); + }, + { enabled: isOpen, enableOnFormTags: ['input'] }, + [isOpen, onClickGo] + ); + + useHotkeys( + 'esc', + () => { + setNewPage(currentPage); + onClose(); + }, + { enabled: isOpen, enableOnFormTags: ['input'] }, + [isOpen, onClose] + ); + + useEffect(() => { + setNewPage(currentPage); + }, [currentPage]); + + return ( + + + + + + + + + + + + + + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/useGallerySearchTerm.ts b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/useGallerySearchTerm.ts new file mode 100644 index 0000000000..52e6f8ec08 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/useGallerySearchTerm.ts @@ -0,0 +1,37 @@ +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useAssertSingleton } from 'common/hooks/useAssertSingleton'; +import { searchTermChanged } from 'features/gallery/store/gallerySlice'; +import { debounce } from 'lodash-es'; +import { useCallback, useMemo, useState } from 'react'; + +export const useGallerySearchTerm = () => { + // Highlander! + useAssertSingleton('gallery-search-state'); + + const dispatch = useAppDispatch(); + const searchTerm = useAppSelector((s) => s.gallery.searchTerm); + + const [localSearchTerm, setLocalSearchTerm] = useState(searchTerm); + + const debouncedSetSearchTerm = useMemo(() => { + return debounce((val: string) => { + dispatch(searchTermChanged(val)); + }, 1000); + }, [dispatch]); + + const onChange = useCallback( + (val: string) => { + setLocalSearchTerm(val); + debouncedSetSearchTerm(val); + }, + [debouncedSetSearchTerm] + ); + + const onReset = useCallback(() => { + debouncedSetSearchTerm.cancel(); + setLocalSearchTerm(''); + dispatch(searchTermChanged('')); + }, [debouncedSetSearchTerm, dispatch]); + + return [localSearchTerm, onChange, onReset] as const; +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx index d500d692fe..d1f874271d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx @@ -2,7 +2,7 @@ import { ButtonGroup, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ import { useStore } from '@nanostores/react'; import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/query'; -import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested'; +import { adHocPostProcessingRequested } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice'; import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton'; @@ -14,7 +14,7 @@ import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors import { selectGallerySlice } from 'features/gallery/store/gallerySlice'; import { parseAndRecallImageDimensions } from 'features/metadata/util/handlers'; import { $templates } from 'features/nodes/store/nodesSlice'; -import ParamUpscalePopover from 'features/parameters/components/Upscale/ParamUpscaleSettings'; +import { PostProcessingPopover } from 'features/parameters/components/PostProcessing/PostProcessingPopover'; import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { selectSystemSlice } from 'features/system/store/systemSlice'; @@ -97,7 +97,7 @@ const CurrentImageButtons = () => { if (!imageDTO) { return; } - dispatch(upscaleRequested({ imageDTO })); + dispatch(adHocPostProcessingRequested({ imageDTO })); }, [dispatch, imageDTO]); const handleDelete = useCallback(() => { @@ -193,7 +193,7 @@ const CurrentImageButtons = () => { {isUpscalingEnabled && ( - {isUpscalingEnabled && } + {isUpscalingEnabled && } )} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx index e610ca0077..dfc131c87c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx @@ -9,7 +9,13 @@ import CurrentImageButtons from './CurrentImageButtons'; import { ViewerToggleMenu } from './ViewerToggleMenu'; export const ViewerToolbar = memo(() => { - const tab = useAppSelector(activeTabNameSelector); + const showToggle = useAppSelector((s) => { + const tab = activeTabNameSelector(s); + if (tab === 'upscaling' || tab === 'workflows') { + return false; + } + return true; + }); return ( @@ -23,7 +29,7 @@ export const ViewerToolbar = memo(() => { - {tab !== 'workflows' && } + {showToggle && } diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryPagination.ts b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryPagination.ts index 75bea2dcb1..2350a6ccf5 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryPagination.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryPagination.ts @@ -1,6 +1,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors'; import { offsetChanged } from 'features/gallery/store/gallerySlice'; +import { throttle } from 'lodash-es'; import { useCallback, useEffect, useMemo } from 'react'; import { useListImagesQuery } from 'services/api/endpoints/images'; @@ -80,32 +81,41 @@ export const useGalleryPagination = () => { return offset > 0; }, [count, offset]); + const onOffsetChanged = useCallback( + (arg: Parameters[0]) => { + dispatch(offsetChanged(arg)); + }, + [dispatch] + ); + + const throttledOnOffsetChanged = useMemo(() => throttle(onOffsetChanged, 500), [onOffsetChanged]); + const goNext = useCallback( (withHotkey?: 'arrow' | 'alt+arrow') => { - dispatch(offsetChanged({ offset: offset + (limit || 0), withHotkey })); + throttledOnOffsetChanged({ offset: offset + (limit || 0), withHotkey }); }, - [dispatch, offset, limit] + [throttledOnOffsetChanged, offset, limit] ); const goPrev = useCallback( (withHotkey?: 'arrow' | 'alt+arrow') => { - dispatch(offsetChanged({ offset: Math.max(offset - (limit || 0), 0), withHotkey })); + throttledOnOffsetChanged({ offset: Math.max(offset - (limit || 0), 0), withHotkey }); }, - [dispatch, offset, limit] + [throttledOnOffsetChanged, offset, limit] ); const goToPage = useCallback( (page: number) => { - dispatch(offsetChanged({ offset: page * (limit || 0) })); + throttledOnOffsetChanged({ offset: page * (limit || 0) }); }, - [dispatch, limit] + [throttledOnOffsetChanged, limit] ); const goToFirst = useCallback(() => { - dispatch(offsetChanged({ offset: 0 })); - }, [dispatch]); + throttledOnOffsetChanged({ offset: 0 }); + }, [throttledOnOffsetChanged]); const goToLast = useCallback(() => { - dispatch(offsetChanged({ offset: (pages - 1) * (limit || 0) })); - }, [dispatch, pages, limit]); + throttledOnOffsetChanged({ offset: (pages - 1) * (limit || 0) }); + }, [throttledOnOffsetChanged, pages, limit]); // handle when total/pages decrease and user is on high page number (ie bulk removing or deleting) useEffect(() => { diff --git a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useControlNetOrT2IAdapterDefaultSettings.ts b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useControlNetOrT2IAdapterDefaultSettings.ts index 826bec17b1..693cf3cf52 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useControlNetOrT2IAdapterDefaultSettings.ts +++ b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useControlNetOrT2IAdapterDefaultSettings.ts @@ -1,15 +1,10 @@ -import { skipToken } from '@reduxjs/toolkit/query'; import { isNil } from 'lodash-es'; import { useMemo } from 'react'; -import { useGetModelConfigWithTypeGuard } from 'services/api/hooks/useGetModelConfigWithTypeGuard'; -import { isControlNetOrT2IAdapterModelConfig } from 'services/api/types'; - -export const useControlNetOrT2IAdapterDefaultSettings = (modelKey?: string | null) => { - const { modelConfig, isLoading } = useGetModelConfigWithTypeGuard( - modelKey ?? skipToken, - isControlNetOrT2IAdapterModelConfig - ); +import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types'; +export const useControlNetOrT2IAdapterDefaultSettings = ( + modelConfig: ControlNetModelConfig | T2IAdapterModelConfig +) => { const defaultSettingsDefaults = useMemo(() => { return { preprocessor: { @@ -19,5 +14,5 @@ export const useControlNetOrT2IAdapterDefaultSettings = (modelKey?: string | nul }; }, [modelConfig?.default_settings]); - return { defaultSettingsDefaults, isLoading }; + return defaultSettingsDefaults; }; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useInstallModel.ts b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useInstallModel.ts index 7636b9f314..12141af2a5 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useInstallModel.ts +++ b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useInstallModel.ts @@ -1,11 +1,9 @@ import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useInstallModelMutation } from 'services/api/endpoints/models'; +import { type InstallModelArg, useInstallModelMutation } from 'services/api/endpoints/models'; -type InstallModelArg = { - source: string; - inplace?: boolean; +type InstallModelArgWithCallbacks = InstallModelArg & { onSuccess?: () => void; onError?: (error: unknown) => void; }; @@ -15,8 +13,9 @@ export const useInstallModel = () => { const [_installModel, request] = useInstallModelMutation(); const installModel = useCallback( - ({ source, inplace, onSuccess, onError }: InstallModelArg) => { - _installModel({ source, inplace }) + ({ source, inplace, config, onSuccess, onError }: InstallModelArgWithCallbacks) => { + config ||= {}; + _installModel({ source, inplace, config }) .unwrap() .then((_) => { if (onSuccess) { diff --git a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useMainModelDefaultSettings.ts b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useMainModelDefaultSettings.ts index 6de99673e4..55ee40ada5 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useMainModelDefaultSettings.ts +++ b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useMainModelDefaultSettings.ts @@ -1,12 +1,9 @@ -import { skipToken } from '@reduxjs/toolkit/query'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; -import { getOptimalDimension } from 'features/parameters/util/optimalDimension'; import { selectConfigSlice } from 'features/system/store/configSlice'; import { isNil } from 'lodash-es'; import { useMemo } from 'react'; -import { useGetModelConfigWithTypeGuard } from 'services/api/hooks/useGetModelConfigWithTypeGuard'; -import { isNonRefinerMainModelConfig } from 'services/api/types'; +import type { MainModelConfig } from 'services/api/types'; const initialStatesSelector = createMemoizedSelector(selectConfigSlice, (config) => { const { steps, guidance, scheduler, cfgRescaleMultiplier, vaePrecision, width, height } = config.sd; @@ -22,9 +19,7 @@ const initialStatesSelector = createMemoizedSelector(selectConfigSlice, (config) }; }); -export const useMainModelDefaultSettings = (modelKey?: string | null) => { - const { modelConfig, isLoading } = useGetModelConfigWithTypeGuard(modelKey ?? skipToken, isNonRefinerMainModelConfig); - +export const useMainModelDefaultSettings = (modelConfig: MainModelConfig) => { const { initialSteps, initialCfg, @@ -81,5 +76,5 @@ export const useMainModelDefaultSettings = (modelKey?: string | null) => { initialHeight, ]); - return { defaultSettingsDefaults, isLoading, optimalDimension: getOptimalDimension(modelConfig) }; + return defaultSettingsDefaults; }; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx index 6da320aa0b..101394f85a 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx @@ -1,5 +1,6 @@ import { Button, Text, useToast } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; +import { $installModelsTab } from 'features/modelManagerV2/subpanels/InstallModels'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { useCallback, useEffect, useState } from 'react'; @@ -44,6 +45,7 @@ const ToastDescription = () => { const onClick = useCallback(() => { dispatch(setActiveTab('models')); + $installModelsTab.set(3); toast.close(TOAST_ID); }, [dispatch, toast]); diff --git a/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts b/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts index c637d30fd8..8a7e3a7aa8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts +++ b/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts @@ -1,6 +1,6 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import type { PersistConfig } from 'app/store/store'; +import type { PersistConfig, RootState } from 'app/store/store'; import type { ModelType } from 'services/api/types'; export type FilterableModelType = Exclude | 'refiner'; @@ -50,6 +50,8 @@ export const modelManagerV2Slice = createSlice({ export const { setSelectedModelKey, setSearchTerm, setFilteredModelType, setSelectedModelMode, setScanPath } = modelManagerV2Slice.actions; +export const selectModelManagerV2Slice = (state: RootState) => state.modelmanagerV2; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ const migrateModelManagerState = (state: any): any => { if (!('_version' in state)) { diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx index ee5960f7d2..7257c30007 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx @@ -1,13 +1,13 @@ import { Button, Flex, FormControl, FormErrorMessage, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library'; import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel'; import type { ChangeEventHandler } from 'react'; -import { useCallback, useState } from 'react'; +import { memo, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLazyGetHuggingFaceModelsQuery } from 'services/api/endpoints/models'; import { HuggingFaceResults } from './HuggingFaceResults'; -export const HuggingFaceForm = () => { +export const HuggingFaceForm = memo(() => { const [huggingFaceRepo, setHuggingFaceRepo] = useState(''); const [displayResults, setDisplayResults] = useState(false); const [errorMessage, setErrorMessage] = useState(''); @@ -66,4 +66,6 @@ export const HuggingFaceForm = () => { {data && data.urls && displayResults && } ); -}; +}); + +HuggingFaceForm.displayName = 'HuggingFaceForm'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResultItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResultItem.tsx index 32970a3666..5edb67eefa 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResultItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResultItem.tsx @@ -1,13 +1,13 @@ import { Flex, IconButton, Text } from '@invoke-ai/ui-library'; import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel'; -import { useCallback } from 'react'; +import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; type Props = { result: string; }; -export const HuggingFaceResultItem = ({ result }: Props) => { +export const HuggingFaceResultItem = memo(({ result }: Props) => { const { t } = useTranslation(); const [installModel] = useInstallModel(); @@ -27,4 +27,6 @@ export const HuggingFaceResultItem = ({ result }: Props) => { } onClick={onClick} size="sm" /> ); -}; +}); + +HuggingFaceResultItem.displayName = 'HuggingFaceResultItem'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResults.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResults.tsx index 826fd177ea..25546c6822 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResults.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResults.tsx @@ -11,7 +11,7 @@ import { import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel'; import type { ChangeEventHandler } from 'react'; -import { useCallback, useMemo, useState } from 'react'; +import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PiXBold } from 'react-icons/pi'; @@ -21,7 +21,7 @@ type HuggingFaceResultsProps = { results: string[]; }; -export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => { +export const HuggingFaceResults = memo(({ results }: HuggingFaceResultsProps) => { const { t } = useTranslation(); const [searchTerm, setSearchTerm] = useState(''); @@ -93,4 +93,6 @@ export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => { ); -}; +}); + +HuggingFaceResults.displayName = 'HuggingFaceResults'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/InstallModelForm.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/InstallModelForm.tsx index cc052878bf..64eb725e5e 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/InstallModelForm.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/InstallModelForm.tsx @@ -1,7 +1,7 @@ import { Button, Checkbox, Flex, FormControl, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library'; import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel'; import { t } from 'i18next'; -import { useCallback } from 'react'; +import { memo, useCallback } from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; @@ -10,7 +10,7 @@ type SimpleImportModelConfig = { inplace: boolean; }; -export const InstallModelForm = () => { +export const InstallModelForm = memo(() => { const [installModel, { isLoading }] = useInstallModel(); const { register, handleSubmit, formState, reset } = useForm({ @@ -74,4 +74,6 @@ export const InstallModelForm = () => { ); -}; +}); + +InstallModelForm.displayName = 'InstallModelForm'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx index b3544af5b3..c2443dde6b 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx @@ -2,12 +2,12 @@ import { Box, Button, Flex, Heading } from '@invoke-ai/ui-library'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import { toast } from 'features/toast/toast'; import { t } from 'i18next'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useListModelInstallsQuery, usePruneCompletedModelInstallsMutation } from 'services/api/endpoints/models'; import { ModelInstallQueueItem } from './ModelInstallQueueItem'; -export const ModelInstallQueue = () => { +export const ModelInstallQueue = memo(() => { const { data } = useListModelInstallsQuery(); const [_pruneCompletedModelInstalls] = usePruneCompletedModelInstallsMutation(); @@ -61,4 +61,6 @@ export const ModelInstallQueue = () => { ); -}; +}); + +ModelInstallQueue.displayName = 'ModelInstallQueue'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueueItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueueItem.tsx index 82a28b2d75..b14b1fbc4c 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueueItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueueItem.tsx @@ -2,7 +2,7 @@ import { Flex, IconButton, Progress, Text, Tooltip } from '@invoke-ai/ui-library import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { isNil } from 'lodash-es'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { PiXBold } from 'react-icons/pi'; import { useCancelModelInstallMutation } from 'services/api/endpoints/models'; import type { ModelInstallJob } from 'services/api/types'; @@ -25,7 +25,7 @@ const formatBytes = (bytes: number) => { return `${bytes.toFixed(2)} ${units[i]}`; }; -export const ModelInstallQueueItem = (props: ModelListItemProps) => { +export const ModelInstallQueueItem = memo((props: ModelListItemProps) => { const { installJob } = props; const [deleteImportModel] = useCancelModelInstallMutation(); @@ -124,7 +124,9 @@ export const ModelInstallQueueItem = (props: ModelListItemProps) => { /> ); -}; +}); + +ModelInstallQueueItem.displayName = 'ModelInstallQueueItem'; type TooltipLabelProps = { installJob: ModelInstallJob; @@ -132,7 +134,7 @@ type TooltipLabelProps = { source: string; }; -const TooltipLabel = ({ name, source, installJob }: TooltipLabelProps) => { +const TooltipLabel = memo(({ name, source, installJob }: TooltipLabelProps) => { const progressString = useMemo(() => { if (installJob.status !== 'downloading' || installJob.bytes === undefined || installJob.total_bytes === undefined) { return ''; @@ -156,4 +158,6 @@ const TooltipLabel = ({ name, source, installJob }: TooltipLabelProps) => { )} ); -}; +}); + +TooltipLabel.displayName = 'TooltipLabel'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderForm.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderForm.tsx index 2a8aec3285..1cd036bf13 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderForm.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderForm.tsx @@ -2,13 +2,13 @@ import { Button, Flex, FormControl, FormErrorMessage, FormHelperText, FormLabel, import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setScanPath } from 'features/modelManagerV2/store/modelManagerV2Slice'; import type { ChangeEventHandler } from 'react'; -import { useCallback, useState } from 'react'; +import { memo, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLazyScanFolderQuery } from 'services/api/endpoints/models'; import { ScanModelsResults } from './ScanFolderResults'; -export const ScanModelsForm = () => { +export const ScanModelsForm = memo(() => { const scanPath = useAppSelector((state) => state.modelmanagerV2.scanPath); const dispatch = useAppDispatch(); const [errorMessage, setErrorMessage] = useState(''); @@ -56,4 +56,6 @@ export const ScanModelsForm = () => { {data && } ); -}; +}); + +ScanModelsForm.displayName = 'ScanModelsForm'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResultItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResultItem.tsx index 4f2f77470d..9f8c1bdc84 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResultItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResultItem.tsx @@ -1,5 +1,5 @@ import { Badge, Box, Flex, IconButton, Text } from '@invoke-ai/ui-library'; -import { useCallback } from 'react'; +import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; import type { ScanFolderResponse } from 'services/api/endpoints/models'; @@ -8,7 +8,7 @@ type Props = { result: ScanFolderResponse[number]; installModel: (source: string) => void; }; -export const ScanModelResultItem = ({ result, installModel }: Props) => { +export const ScanModelResultItem = memo(({ result, installModel }: Props) => { const { t } = useTranslation(); const handleInstall = useCallback(() => { @@ -30,4 +30,6 @@ export const ScanModelResultItem = ({ result, installModel }: Props) => { ); -}; +}); + +ScanModelResultItem.displayName = 'ScanModelResultItem'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResults.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResults.tsx index 749ef4c8e0..ee8b834a29 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResults.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResults.tsx @@ -14,7 +14,7 @@ import { import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel'; import type { ChangeEvent, ChangeEventHandler } from 'react'; -import { useCallback, useMemo, useState } from 'react'; +import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PiXBold } from 'react-icons/pi'; import type { ScanFolderResponse } from 'services/api/endpoints/models'; @@ -25,7 +25,7 @@ type ScanModelResultsProps = { results: ScanFolderResponse; }; -export const ScanModelsResults = ({ results }: ScanModelResultsProps) => { +export const ScanModelsResults = memo(({ results }: ScanModelResultsProps) => { const { t } = useTranslation(); const [searchTerm, setSearchTerm] = useState(''); const [inplace, setInplace] = useState(true); @@ -116,4 +116,6 @@ export const ScanModelsResults = ({ results }: ScanModelResultsProps) => { ); -}; +}); + +ScanModelsResults.displayName = 'ScanModelsResults'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StartModelsResultItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StartModelsResultItem.tsx index 98e1e39640..a3c9c82d0e 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StartModelsResultItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StartModelsResultItem.tsx @@ -1,7 +1,7 @@ import { Badge, Box, Flex, IconButton, Text } from '@invoke-ai/ui-library'; import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel'; import ModelBaseBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelBaseBadge'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; import type { GetStarterModelsResponse } from 'services/api/endpoints/models'; @@ -9,20 +9,22 @@ import type { GetStarterModelsResponse } from 'services/api/endpoints/models'; type Props = { result: GetStarterModelsResponse[number]; }; -export const StarterModelsResultItem = ({ result }: Props) => { +export const StarterModelsResultItem = memo(({ result }: Props) => { const { t } = useTranslation(); const allSources = useMemo(() => { - const _allSources = [result.source]; + const _allSources = [{ source: result.source, config: { name: result.name, description: result.description } }]; if (result.dependencies) { - _allSources.push(...result.dependencies.map((d) => d.source)); + for (const d of result.dependencies) { + _allSources.push({ source: d.source, config: { name: d.name, description: d.description } }); + } } return _allSources; }, [result]); const [installModel] = useInstallModel(); const onClick = useCallback(() => { - for (const source of allSources) { - installModel({ source }); + for (const { config, source } of allSources) { + installModel({ config, source }); } }, [allSources, installModel]); @@ -30,7 +32,7 @@ export const StarterModelsResultItem = ({ result }: Props) => { - {result.type.replace('_', ' ')} + {result.type.replaceAll('_', ' ')} {result.name} @@ -45,4 +47,6 @@ export const StarterModelsResultItem = ({ result }: Props) => { ); -}; +}); + +StarterModelsResultItem.displayName = 'StarterModelsResultItem'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsForm.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsForm.tsx index 3198f1df78..837ef5c63b 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsForm.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsForm.tsx @@ -1,10 +1,11 @@ import { Flex } from '@invoke-ai/ui-library'; import { FetchingModelsLoader } from 'features/modelManagerV2/subpanels/ModelManagerPanel/FetchingModelsLoader'; +import { memo } from 'react'; import { useGetStarterModelsQuery } from 'services/api/endpoints/models'; import { StarterModelsResults } from './StarterModelsResults'; -export const StarterModelsForm = () => { +export const StarterModelsForm = memo(() => { const { isLoading, data } = useGetStarterModelsQuery(); return ( @@ -13,4 +14,6 @@ export const StarterModelsForm = () => { {data && } ); -}; +}); + +StarterModelsForm.displayName = 'StarterModelsForm'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsResults.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsResults.tsx index 7aa05af300..e593ee5fc3 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsResults.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsResults.tsx @@ -1,7 +1,7 @@ import { Flex, IconButton, Input, InputGroup, InputRightElement } from '@invoke-ai/ui-library'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import type { ChangeEventHandler } from 'react'; -import { useCallback, useMemo, useState } from 'react'; +import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PiXBold } from 'react-icons/pi'; import type { GetStarterModelsResponse } from 'services/api/endpoints/models'; @@ -12,20 +12,30 @@ type StarterModelsResultsProps = { results: NonNullable; }; -export const StarterModelsResults = ({ results }: StarterModelsResultsProps) => { +export const StarterModelsResults = memo(({ results }: StarterModelsResultsProps) => { const { t } = useTranslation(); const [searchTerm, setSearchTerm] = useState(''); const filteredResults = useMemo(() => { return results.filter((result) => { - const name = result.name.toLowerCase(); - const type = result.type.toLowerCase(); - return name.includes(searchTerm.toLowerCase()) || type.includes(searchTerm.toLowerCase()); + const trimmedSearchTerm = searchTerm.trim().toLowerCase(); + const matchStrings = [ + result.name.toLowerCase(), + result.type.toLowerCase().replaceAll('_', ' '), + result.description.toLowerCase(), + ]; + if (result.type === 'spandrel_image_to_image') { + matchStrings.push('upscale'); + matchStrings.push('post-processing'); + matchStrings.push('postprocessing'); + matchStrings.push('post processing'); + } + return matchStrings.some((matchString) => matchString.includes(trimmedSearchTerm)); }); }, [results, searchTerm]); const handleSearch: ChangeEventHandler = useCallback((e) => { - setSearchTerm(e.target.value.trim()); + setSearchTerm(e.target.value); }, []); const clearSearch = useCallback(() => { @@ -69,4 +79,6 @@ export const StarterModelsResults = ({ results }: StarterModelsResultsProps) => ); -}; +}); + +StarterModelsResults.displayName = 'StarterModelsResults'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/InstallModels.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/InstallModels.tsx index d09ab67fa4..90f3a578da 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/InstallModels.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/InstallModels.tsx @@ -1,28 +1,28 @@ import { Box, Flex, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; import { StarterModelsForm } from 'features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsForm'; -import { useMemo } from 'react'; +import { atom } from 'nanostores'; +import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useMainModels } from 'services/api/hooks/modelsByType'; import { HuggingFaceForm } from './AddModelPanel/HuggingFaceFolder/HuggingFaceForm'; import { InstallModelForm } from './AddModelPanel/InstallModelForm'; import { ModelInstallQueue } from './AddModelPanel/ModelInstallQueue/ModelInstallQueue'; import { ScanModelsForm } from './AddModelPanel/ScanFolder/ScanFolderForm'; -export const InstallModels = () => { +export const $installModelsTab = atom(0); + +export const InstallModels = memo(() => { const { t } = useTranslation(); - const [mainModels, { data }] = useMainModels(); - const defaultIndex = useMemo(() => { - if (data && mainModels.length) { - return 0; - } - return 3; - }, [data, mainModels.length]); + const index = useStore($installModelsTab); + const onChange = useCallback((index: number) => { + $installModelsTab.set(index); + }, []); return ( {t('modelManager.addModel')} - + {t('modelManager.urlOrLocalPath')} {t('modelManager.huggingFace')} @@ -49,4 +49,6 @@ export const InstallModels = () => { ); -}; +}); + +InstallModels.displayName = 'InstallModels'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx index dbe02392db..a07cb8c10b 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx @@ -1,14 +1,14 @@ import { Button, Flex, Heading } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { setSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice'; -import { useCallback } from 'react'; +import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; import ModelList from './ModelManagerPanel/ModelList'; import { ModelListNavigation } from './ModelManagerPanel/ModelListNavigation'; -export const ModelManager = () => { +export const ModelManager = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const handleClickAddModel = useCallback(() => { @@ -29,4 +29,6 @@ export const ModelManager = () => { ); -}; +}); + +ModelManager.displayName = 'ModelManager'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx index b82917221e..755a6e21fb 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx @@ -21,7 +21,8 @@ import { FetchingModelsLoader } from './FetchingModelsLoader'; import { ModelListWrapper } from './ModelListWrapper'; const ModelList = () => { - const { searchTerm, filteredModelType } = useAppSelector((s) => s.modelmanagerV2); + const filteredModelType = useAppSelector((s) => s.modelmanagerV2.filteredModelType); + const searchTerm = useAppSelector((s) => s.modelmanagerV2.searchTerm); const { t } = useTranslation(); const [mainModels, { isLoading: isLoadingMainModels }] = useMainModels(); diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx index a4a6d5c833..8bfcbd7351 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx @@ -1,7 +1,8 @@ import type { SystemStyleObject } from '@invoke-ai/ui-library'; import { ConfirmationAlertDialog, Flex, IconButton, Spacer, Text, useDisclosure } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { setSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice'; +import { selectModelManagerV2Slice, setSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice'; import ModelBaseBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelBaseBadge'; import ModelFormatBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelFormatBadge'; import { toast } from 'features/toast/toast'; @@ -23,15 +24,21 @@ const sx: SystemStyleObject = { "&[aria-selected='true']": { bg: 'base.700' }, }; -const ModelListItem = (props: ModelListItemProps) => { +const ModelListItem = ({ model }: ModelListItemProps) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); + const selectIsSelected = useMemo( + () => + createSelector( + selectModelManagerV2Slice, + (modelManagerV2Slice) => modelManagerV2Slice.selectedModelKey === model.key + ), + [model.key] + ); + const isSelected = useAppSelector(selectIsSelected); const [deleteModel] = useDeleteModelsMutation(); const { isOpen, onOpen, onClose } = useDisclosure(); - const { model } = props; - const handleSelectModel = useCallback(() => { dispatch(setSelectedModelKey(model.key)); }, [model.key, dispatch]); @@ -43,11 +50,6 @@ const ModelListItem = (props: ModelListItemProps) => { }, [onOpen] ); - - const isSelected = useMemo(() => { - return selectedModelKey === model.key; - }, [selectedModelKey, model.key]); - const handleModelDelete = useCallback(() => { deleteModel({ key: model.key }) .unwrap() diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListNavigation.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListNavigation.tsx index b256e8a70f..766b4f27b8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListNavigation.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListNavigation.tsx @@ -3,12 +3,12 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setSearchTerm } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { t } from 'i18next'; import type { ChangeEventHandler } from 'react'; -import { useCallback } from 'react'; +import { memo, useCallback } from 'react'; import { PiXBold } from 'react-icons/pi'; import { ModelTypeFilter } from './ModelTypeFilter'; -export const ModelListNavigation = () => { +export const ModelListNavigation = memo(() => { const dispatch = useAppDispatch(); const searchTerm = useAppSelector((s) => s.modelmanagerV2.searchTerm); @@ -49,4 +49,6 @@ export const ModelListNavigation = () => { ); -}; +}); + +ModelListNavigation.displayName = 'ModelListNavigation'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListWrapper.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListWrapper.tsx index e52bd12f25..bde79e4554 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListWrapper.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListWrapper.tsx @@ -1,4 +1,5 @@ import { StickyScrollable } from 'features/system/components/StickyScrollable'; +import { memo } from 'react'; import type { AnyModelConfig } from 'services/api/types'; import ModelListItem from './ModelListItem'; @@ -8,7 +9,7 @@ type ModelListWrapperProps = { modelList: AnyModelConfig[]; }; -export const ModelListWrapper = (props: ModelListWrapperProps) => { +export const ModelListWrapper = memo((props: ModelListWrapperProps) => { const { title, modelList } = props; return ( @@ -17,4 +18,6 @@ export const ModelListWrapper = (props: ModelListWrapperProps) => { ))} ); -}; +}); + +ModelListWrapper.displayName = 'ModelListWrapper'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx index 1a2444870b..9db3334e89 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx @@ -2,12 +2,12 @@ import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-libr import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import type { FilterableModelType } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { setFilteredModelType } from 'features/modelManagerV2/store/modelManagerV2Slice'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiFunnelBold } from 'react-icons/pi'; import { objectKeys } from 'tsafe'; -export const ModelTypeFilter = () => { +export const ModelTypeFilter = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const MODEL_TYPE_LABELS: Record = useMemo( @@ -57,4 +57,6 @@ export const ModelTypeFilter = () => { ); -}; +}); + +ModelTypeFilter.displayName = 'ModelTypeFilter'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPane.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPane.tsx index 5f1a70e0fe..eb85434d36 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPane.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPane.tsx @@ -1,14 +1,17 @@ import { Box } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; +import { memo } from 'react'; import { InstallModels } from './InstallModels'; import { Model } from './ModelPanel/Model'; -export const ModelPane = () => { +export const ModelPane = memo(() => { const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); return ( {selectedModelKey ? : } ); -}; +}); + +ModelPane.displayName = 'ModelPane'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/ControlNetOrT2IAdapterDefaultSettings.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/ControlNetOrT2IAdapterDefaultSettings.tsx index 9a84fbc726..25005e76c9 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/ControlNetOrT2IAdapterDefaultSettings.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/ControlNetOrT2IAdapterDefaultSettings.tsx @@ -1,26 +1,28 @@ -import { Button, Flex, Heading, SimpleGrid, Text } from '@invoke-ai/ui-library'; -import { useAppSelector } from 'app/store/storeHooks'; +import { Button, Flex, Heading, SimpleGrid } from '@invoke-ai/ui-library'; import { useControlNetOrT2IAdapterDefaultSettings } from 'features/modelManagerV2/hooks/useControlNetOrT2IAdapterDefaultSettings'; import { DefaultPreprocessor } from 'features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/DefaultPreprocessor'; import type { FormField } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings'; import { toast } from 'features/toast/toast'; -import { useCallback } from 'react'; +import { memo, useCallback } from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { PiCheckBold } from 'react-icons/pi'; import { useUpdateModelMutation } from 'services/api/endpoints/models'; +import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types'; export type ControlNetOrT2IAdapterDefaultSettingsFormData = { preprocessor: FormField; }; -export const ControlNetOrT2IAdapterDefaultSettings = () => { - const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); +type Props = { + modelConfig: ControlNetModelConfig | T2IAdapterModelConfig; +}; + +export const ControlNetOrT2IAdapterDefaultSettings = memo(({ modelConfig }: Props) => { const { t } = useTranslation(); - const { defaultSettingsDefaults, isLoading: isLoadingDefaultSettings } = - useControlNetOrT2IAdapterDefaultSettings(selectedModelKey); + const defaultSettingsDefaults = useControlNetOrT2IAdapterDefaultSettings(modelConfig); const [updateModel, { isLoading: isLoadingUpdateModel }] = useUpdateModelMutation(); @@ -30,16 +32,12 @@ export const ControlNetOrT2IAdapterDefaultSettings = () => { const onSubmit = useCallback>( (data) => { - if (!selectedModelKey) { - return; - } - const body = { preprocessor: data.preprocessor.isEnabled ? data.preprocessor.value : null, }; updateModel({ - key: selectedModelKey, + key: modelConfig.key, body: { default_settings: body }, }) .unwrap() @@ -61,13 +59,9 @@ export const ControlNetOrT2IAdapterDefaultSettings = () => { } }); }, - [selectedModelKey, reset, updateModel, t] + [updateModel, modelConfig.key, t, reset] ); - if (isLoadingDefaultSettings) { - return {t('common.loading')}; - } - return ( <> @@ -89,4 +83,6 @@ export const ControlNetOrT2IAdapterDefaultSettings = () => { ); -}; +}); + +ControlNetOrT2IAdapterDefaultSettings.displayName = 'ControlNetOrT2IAdapterDefaultSettings'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/DefaultPreprocessor.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/DefaultPreprocessor.tsx index b2284336bf..e446992779 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/DefaultPreprocessor.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/DefaultPreprocessor.tsx @@ -4,7 +4,7 @@ import { InformationalPopover } from 'common/components/InformationalPopover/Inf import type { ControlNetOrT2IAdapterDefaultSettingsFormData } from 'features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/ControlNetOrT2IAdapterDefaultSettings'; import type { FormField } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { UseControllerProps } from 'react-hook-form'; import { useController } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -28,7 +28,7 @@ const OPTIONS = [ type DefaultSchedulerType = ControlNetOrT2IAdapterDefaultSettingsFormData['preprocessor']; -export function DefaultPreprocessor(props: UseControllerProps) { +export const DefaultPreprocessor = memo((props: UseControllerProps) => { const { t } = useTranslation(); const { field } = useController(props); @@ -63,4 +63,6 @@ export function DefaultPreprocessor(props: UseControllerProps ); -} +}); + +DefaultPreprocessor.displayName = 'DefaultPreprocessor'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgRescaleMultiplier.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgRescaleMultiplier.tsx index d16ce1460c..cde1b8d7f3 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgRescaleMultiplier.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgRescaleMultiplier.tsx @@ -2,7 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f import { useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { UseControllerProps } from 'react-hook-form'; import { useController } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -11,7 +11,7 @@ import type { MainModelDefaultSettingsFormData } from './MainModelDefaultSetting type DefaultCfgRescaleMultiplierType = MainModelDefaultSettingsFormData['cfgRescaleMultiplier']; -export function DefaultCfgRescaleMultiplier(props: UseControllerProps) { +export const DefaultCfgRescaleMultiplier = memo((props: UseControllerProps) => { const { field } = useController(props); const sliderMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMin); @@ -74,4 +74,6 @@ export function DefaultCfgRescaleMultiplier(props: UseControllerProps ); -} +}); + +DefaultCfgRescaleMultiplier.displayName = 'DefaultCfgRescaleMultiplier'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgScale.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgScale.tsx index 293261bc35..85d11daba8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgScale.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgScale.tsx @@ -2,7 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f import { useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { UseControllerProps } from 'react-hook-form'; import { useController } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -11,7 +11,7 @@ import type { MainModelDefaultSettingsFormData } from './MainModelDefaultSetting type DefaultCfgType = MainModelDefaultSettingsFormData['cfgScale']; -export function DefaultCfgScale(props: UseControllerProps) { +export const DefaultCfgScale = memo((props: UseControllerProps) => { const { field } = useController(props); const sliderMin = useAppSelector((s) => s.config.sd.guidance.sliderMin); @@ -74,4 +74,6 @@ export function DefaultCfgScale(props: UseControllerProps ); -} +}); + +DefaultCfgScale.displayName = 'DefaultCfgScale'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight.tsx index a19ae26ebe..8dc00e6239 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight.tsx @@ -2,7 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f import { useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { UseControllerProps } from 'react-hook-form'; import { useController } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -16,7 +16,7 @@ type Props = { optimalDimension: number; }; -export function DefaultHeight({ control, optimalDimension }: Props) { +export const DefaultHeight = memo(({ control, optimalDimension }: Props) => { const { field } = useController({ control, name: 'height' }); const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin); const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax); @@ -78,4 +78,6 @@ export function DefaultHeight({ control, optimalDimension }: Props) { ); -} +}); + +DefaultHeight.displayName = 'DefaultHeight'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultScheduler.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultScheduler.tsx index 4397e35a51..1ad8177cf6 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultScheduler.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultScheduler.tsx @@ -4,7 +4,7 @@ import { InformationalPopover } from 'common/components/InformationalPopover/Inf import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; import { SCHEDULER_OPTIONS } from 'features/parameters/types/constants'; import { isParameterScheduler } from 'features/parameters/types/parameterSchemas'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { UseControllerProps } from 'react-hook-form'; import { useController } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -13,7 +13,7 @@ import type { MainModelDefaultSettingsFormData } from './MainModelDefaultSetting type DefaultSchedulerType = MainModelDefaultSettingsFormData['scheduler']; -export function DefaultScheduler(props: UseControllerProps) { +export const DefaultScheduler = memo((props: UseControllerProps) => { const { t } = useTranslation(); const { field } = useController(props); @@ -51,4 +51,6 @@ export function DefaultScheduler(props: UseControllerProps ); -} +}); + +DefaultScheduler.displayName = 'DefaultScheduler'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultSteps.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultSteps.tsx index 9c1912a0f7..5bfb268d08 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultSteps.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultSteps.tsx @@ -2,7 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f import { useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { UseControllerProps } from 'react-hook-form'; import { useController } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -11,7 +11,7 @@ import type { MainModelDefaultSettingsFormData } from './MainModelDefaultSetting type DefaultSteps = MainModelDefaultSettingsFormData['steps']; -export function DefaultSteps(props: UseControllerProps) { +export const DefaultSteps = memo((props: UseControllerProps) => { const { field } = useController(props); const sliderMin = useAppSelector((s) => s.config.sd.steps.sliderMin); @@ -74,4 +74,6 @@ export function DefaultSteps(props: UseControllerProps ); -} +}); + +DefaultSteps.displayName = 'DefaultSteps'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultVae.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultVae.tsx index 5517d7b347..cf1578d9f4 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultVae.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultVae.tsx @@ -4,7 +4,7 @@ import { skipToken } from '@reduxjs/toolkit/query'; import { useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { UseControllerProps } from 'react-hook-form'; import { useController } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -15,7 +15,7 @@ import type { MainModelDefaultSettingsFormData } from './MainModelDefaultSetting type DefaultVaeType = MainModelDefaultSettingsFormData['vae']; -export function DefaultVae(props: UseControllerProps) { +export const DefaultVae = memo((props: UseControllerProps) => { const { t } = useTranslation(); const { field } = useController(props); const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); @@ -64,4 +64,6 @@ export function DefaultVae(props: UseControllerProps ); -} +}); + +DefaultVae.displayName = 'DefaultVae'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultVaePrecision.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultVaePrecision.tsx index d33cf4e08d..b725d60968 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultVaePrecision.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultVaePrecision.tsx @@ -3,7 +3,7 @@ import { Combobox, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; import { isParameterPrecision } from 'features/parameters/types/parameterSchemas'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { UseControllerProps } from 'react-hook-form'; import { useController } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -17,7 +17,7 @@ const options = [ type DefaultVaePrecisionType = MainModelDefaultSettingsFormData['vaePrecision']; -export function DefaultVaePrecision(props: UseControllerProps) { +export const DefaultVaePrecision = memo((props: UseControllerProps) => { const { t } = useTranslation(); const { field } = useController(props); @@ -52,4 +52,6 @@ export function DefaultVaePrecision(props: UseControllerProps ); -} +}); + +DefaultVaePrecision.displayName = 'DefaultVaePrecision'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth.tsx index 851dba1ede..8986b9820b 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth.tsx @@ -2,7 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f import { useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { UseControllerProps } from 'react-hook-form'; import { useController } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -16,7 +16,7 @@ type Props = { optimalDimension: number; }; -export function DefaultWidth({ control, optimalDimension }: Props) { +export const DefaultWidth = memo(({ control, optimalDimension }: Props) => { const { field } = useController({ control, name: 'width' }); const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin); const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax); @@ -78,4 +78,6 @@ export function DefaultWidth({ control, optimalDimension }: Props) { ); -} +}); + +DefaultWidth.displayName = 'DefaultWidth'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings.tsx index 233fc7bc6b..095b452dfc 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings.tsx @@ -1,16 +1,18 @@ -import { Button, Flex, Heading, SimpleGrid, Text } from '@invoke-ai/ui-library'; +import { Button, Flex, Heading, SimpleGrid } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import { useMainModelDefaultSettings } from 'features/modelManagerV2/hooks/useMainModelDefaultSettings'; import { DefaultHeight } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight'; import { DefaultWidth } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth'; import type { ParameterScheduler } from 'features/parameters/types/parameterSchemas'; +import { getOptimalDimension } from 'features/parameters/util/optimalDimension'; import { toast } from 'features/toast/toast'; -import { useCallback } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { PiCheckBold } from 'react-icons/pi'; import { useUpdateModelMutation } from 'services/api/endpoints/models'; +import type { MainModelConfig } from 'services/api/types'; import { DefaultCfgRescaleMultiplier } from './DefaultCfgRescaleMultiplier'; import { DefaultCfgScale } from './DefaultCfgScale'; @@ -35,16 +37,16 @@ export type MainModelDefaultSettingsFormData = { height: FormField; }; -export const MainModelDefaultSettings = () => { +type Props = { + modelConfig: MainModelConfig; +}; + +export const MainModelDefaultSettings = memo(({ modelConfig }: Props) => { const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const { t } = useTranslation(); - const { - defaultSettingsDefaults, - isLoading: isLoadingDefaultSettings, - optimalDimension, - } = useMainModelDefaultSettings(selectedModelKey); - + const defaultSettingsDefaults = useMainModelDefaultSettings(modelConfig); + const optimalDimension = useMemo(() => getOptimalDimension(modelConfig), [modelConfig]); const [updateModel, { isLoading: isLoadingUpdateModel }] = useUpdateModelMutation(); const { handleSubmit, control, formState, reset } = useForm({ @@ -94,10 +96,6 @@ export const MainModelDefaultSettings = () => { [selectedModelKey, reset, updateModel, t] ); - if (isLoadingDefaultSettings) { - return {t('common.loading')}; - } - return ( <> @@ -126,4 +124,6 @@ export const MainModelDefaultSettings = () => { ); -}; +}); + +MainModelDefaultSettings.displayName = 'MainModelDefaultSettings'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx index fa7ca4c394..2934ac916a 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx @@ -1,120 +1,47 @@ -import { Button, Flex, Heading, Spacer, Text } from '@invoke-ai/ui-library'; -import { skipToken } from '@reduxjs/toolkit/query'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice'; -import { ModelConvertButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton'; -import { ModelEditButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelEditButton'; -import { toast } from 'features/toast/toast'; -import { useCallback } from 'react'; -import type { SubmitHandler } from 'react-hook-form'; -import { useForm } from 'react-hook-form'; +import { useAppSelector } from 'app/store/storeHooks'; +import { IAINoContentFallback, IAINoContentFallbackWithSpinner } from 'common/components/IAIImageFallback'; +import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiCheckBold, PiXBold } from 'react-icons/pi'; -import type { UpdateModelArg } from 'services/api/endpoints/models'; -import { useGetModelConfigQuery, useUpdateModelMutation } from 'services/api/endpoints/models'; +import { PiExclamationMarkBold } from 'react-icons/pi'; +import { modelConfigsAdapterSelectors, useGetModelConfigsQuery } from 'services/api/endpoints/models'; -import ModelImageUpload from './Fields/ModelImageUpload'; import { ModelEdit } from './ModelEdit'; import { ModelView } from './ModelView'; -export const Model = () => { +export const Model = memo(() => { const { t } = useTranslation(); const selectedModelMode = useAppSelector((s) => s.modelmanagerV2.selectedModelMode); const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); - const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken); - const [updateModel, { isLoading: isSubmitting }] = useUpdateModelMutation(); - const dispatch = useAppDispatch(); + const { data: modelConfigs, isLoading } = useGetModelConfigsQuery(); + const modelConfig = useMemo(() => { + if (!modelConfigs) { + return null; + } + if (selectedModelKey === null) { + return null; + } + const modelConfig = modelConfigsAdapterSelectors.selectById(modelConfigs, selectedModelKey); - const form = useForm({ - defaultValues: data, - mode: 'onChange', - }); + if (!modelConfig) { + return null; + } - const onSubmit = useCallback>( - (values) => { - if (!data?.key) { - return; - } - - const responseBody: UpdateModelArg = { - key: data.key, - body: values, - }; - - updateModel(responseBody) - .unwrap() - .then((payload) => { - form.reset(payload, { keepDefaultValues: true }); - dispatch(setSelectedModelMode('view')); - toast({ - id: 'MODEL_UPDATED', - title: t('modelManager.modelUpdated'), - status: 'success', - }); - }) - .catch((_) => { - form.reset(); - toast({ - id: 'MODEL_UPDATE_FAILED', - title: t('modelManager.modelUpdateFailed'), - status: 'error', - }); - }); - }, - [dispatch, data?.key, form, t, updateModel] - ); - - const handleClickCancel = useCallback(() => { - dispatch(setSelectedModelMode('view')); - }, [dispatch]); + return modelConfig; + }, [modelConfigs, selectedModelKey]); if (isLoading) { - return {t('common.loading')}; + return ; } - if (!data) { - return {t('common.somethingWentWrong')}; + if (!modelConfig) { + return ; } - return ( - - - - - - - {data.name} - - - {selectedModelMode === 'view' && } - {selectedModelMode === 'view' && } - {selectedModelMode === 'edit' && ( - - )} - {selectedModelMode === 'edit' && ( - - )} - - {data.source && ( - - {t('modelManager.source')}: {data?.source} - - )} - {data.description} - - - {selectedModelMode === 'view' ? : } - - ); -}; + if (selectedModelMode === 'view') { + return ; + } + + return ; +}); + +Model.displayName = 'Model'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx index ebdedffebf..106e0a1df0 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx @@ -1,11 +1,12 @@ import { FormControl, FormLabel, Text } from '@invoke-ai/ui-library'; +import { memo } from 'react'; interface Props { label: string; value: string | null | undefined; } -export const ModelAttrView = ({ label, value }: Props) => { +export const ModelAttrView = memo(({ label, value }: Props) => { return ( {label} @@ -14,4 +15,6 @@ export const ModelAttrView = ({ label, value }: Props) => { ); -}; +}); + +ModelAttrView.displayName = 'ModelAttrView'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx index 40ffca76b4..70775842f8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx @@ -8,52 +8,46 @@ import { UnorderedList, useDisclosure, } from '@invoke-ai/ui-library'; -import { skipToken } from '@reduxjs/toolkit/query'; import { toast } from 'features/toast/toast'; -import { useCallback } from 'react'; +import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useConvertModelMutation, useGetModelConfigQuery } from 'services/api/endpoints/models'; +import { useConvertModelMutation } from 'services/api/endpoints/models'; +import type { CheckpointModelConfig } from 'services/api/types'; interface ModelConvertProps { - modelKey: string | null; + modelConfig: CheckpointModelConfig; } -export const ModelConvertButton = (props: ModelConvertProps) => { - const { modelKey } = props; +export const ModelConvertButton = memo(({ modelConfig }: ModelConvertProps) => { const { t } = useTranslation(); - const { data } = useGetModelConfigQuery(modelKey ?? skipToken); const [convertModel, { isLoading }] = useConvertModelMutation(); const { isOpen, onOpen, onClose } = useDisclosure(); const modelConvertHandler = useCallback(() => { - if (!data || isLoading) { + if (!modelConfig || isLoading) { return; } - const toastId = `CONVERTING_MODEL_${data.key}`; + const toastId = `CONVERTING_MODEL_${modelConfig.key}`; toast({ id: toastId, - title: `${t('modelManager.convertingModelBegin')}: ${data?.name}`, + title: `${t('modelManager.convertingModelBegin')}: ${modelConfig.name}`, status: 'info', }); - convertModel(data?.key) + convertModel(modelConfig.key) .unwrap() .then(() => { - toast({ id: toastId, title: `${t('modelManager.modelConverted')}: ${data?.name}`, status: 'success' }); + toast({ id: toastId, title: `${t('modelManager.modelConverted')}: ${modelConfig.name}`, status: 'success' }); }) .catch(() => { toast({ id: toastId, - title: `${t('modelManager.modelConversionFailed')}: ${data?.name}`, + title: `${t('modelManager.modelConversionFailed')}: ${modelConfig.name}`, status: 'error', }); }); - }, [data, isLoading, t, convertModel]); - - if (data?.format !== 'checkpoint') { - return; - } + }, [modelConfig, isLoading, t, convertModel]); return ( <> @@ -68,7 +62,7 @@ export const ModelConvertButton = (props: ModelConvertProps) => { 🧨 {t('modelManager.convert')} { ); -}; +}); + +ModelConvertButton.displayName = 'ModelConvertButton'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelEdit.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelEdit.tsx index 8bc775c872..da57c30238 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelEdit.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelEdit.tsx @@ -1,4 +1,5 @@ import { + Button, Checkbox, Flex, FormControl, @@ -7,96 +8,154 @@ import { Heading, Input, SimpleGrid, - Text, Textarea, } from '@invoke-ai/ui-library'; -import { skipToken } from '@reduxjs/toolkit/query'; -import { useAppSelector } from 'app/store/storeHooks'; -import type { SubmitHandler, UseFormReturn } from 'react-hook-form'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice'; +import { ModelHeader } from 'features/modelManagerV2/subpanels/ModelPanel/ModelHeader'; +import { toast } from 'features/toast/toast'; +import { memo, useCallback } from 'react'; +import { type SubmitHandler, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; -import type { UpdateModelArg } from 'services/api/endpoints/models'; -import { useGetModelConfigQuery } from 'services/api/endpoints/models'; +import { PiCheckBold, PiXBold } from 'react-icons/pi'; +import { type UpdateModelArg, useUpdateModelMutation } from 'services/api/endpoints/models'; +import type { AnyModelConfig } from 'services/api/types'; import BaseModelSelect from './Fields/BaseModelSelect'; import ModelVariantSelect from './Fields/ModelVariantSelect'; import PredictionTypeSelect from './Fields/PredictionTypeSelect'; type Props = { - form: UseFormReturn; - onSubmit: SubmitHandler; + modelConfig: AnyModelConfig; }; const stringFieldOptions = { validate: (value?: string | null) => (value && value.trim().length > 3) || 'Must be at least 3 characters', }; -export const ModelEdit = ({ form }: Props) => { - const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); - const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken); +export const ModelEdit = memo(({ modelConfig }: Props) => { const { t } = useTranslation(); + const [updateModel, { isLoading: isSubmitting }] = useUpdateModelMutation(); + const dispatch = useAppDispatch(); - if (isLoading) { - return {t('common.loading')}; - } + const form = useForm({ + defaultValues: modelConfig, + mode: 'onChange', + }); - if (!data) { - return {t('common.somethingWentWrong')}; - } + const onSubmit = useCallback>( + (values) => { + const responseBody: UpdateModelArg = { + key: modelConfig.key, + body: values, + }; + + updateModel(responseBody) + .unwrap() + .then((payload) => { + form.reset(payload, { keepDefaultValues: true }); + dispatch(setSelectedModelMode('view')); + toast({ + id: 'MODEL_UPDATED', + title: t('modelManager.modelUpdated'), + status: 'success', + }); + }) + .catch((_) => { + form.reset(); + toast({ + id: 'MODEL_UPDATE_FAILED', + title: t('modelManager.modelUpdateFailed'), + status: 'error', + }); + }); + }, + [dispatch, modelConfig.key, form, t, updateModel] + ); + + const handleClickCancel = useCallback(() => { + dispatch(setSelectedModelMode('view')); + }, [dispatch]); return ( - -
- - - {t('modelManager.modelName')} - + + + + + + + + + + {t('modelManager.modelName')} + - {form.formState.errors.name?.message && ( - {form.formState.errors.name?.message} - )} - - - - - - - {t('modelManager.description')} -