mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
merge
This commit is contained in:
commit
31f63028fd
@ -165,13 +165,13 @@ class ImageProcessorInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||
title="Canny Processor",
|
||||
tags=["controlnet", "canny"],
|
||||
category="controlnet",
|
||||
version="1.3.2",
|
||||
version="1.3.3",
|
||||
)
|
||||
class CannyImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Canny edge detection for ControlNet"""
|
||||
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
low_threshold: int = InputField(
|
||||
default=100, ge=0, le=255, description="The low threshold of the Canny pixel gradient (0-255)"
|
||||
)
|
||||
@ -199,13 +199,13 @@ class CannyImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="HED (softedge) Processor",
|
||||
tags=["controlnet", "hed", "softedge"],
|
||||
category="controlnet",
|
||||
version="1.2.2",
|
||||
version="1.2.3",
|
||||
)
|
||||
class HedImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies HED edge detection to image"""
|
||||
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
# safe not supported in controlnet_aux v0.0.3
|
||||
# safe: bool = InputField(default=False, description=FieldDescriptions.safe_mode)
|
||||
scribble: bool = InputField(default=False, description=FieldDescriptions.scribble_mode)
|
||||
@ -228,13 +228,13 @@ class HedImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Lineart Processor",
|
||||
tags=["controlnet", "lineart"],
|
||||
category="controlnet",
|
||||
version="1.2.2",
|
||||
version="1.2.3",
|
||||
)
|
||||
class LineartImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies line art processing to image"""
|
||||
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
coarse: bool = InputField(default=False, description="Whether to use coarse mode")
|
||||
|
||||
def run_processor(self, image: Image.Image) -> Image.Image:
|
||||
@ -250,13 +250,13 @@ class LineartImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Lineart Anime Processor",
|
||||
tags=["controlnet", "lineart", "anime"],
|
||||
category="controlnet",
|
||||
version="1.2.2",
|
||||
version="1.2.3",
|
||||
)
|
||||
class LineartAnimeImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies line art anime processing to image"""
|
||||
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
|
||||
def run_processor(self, image: Image.Image) -> Image.Image:
|
||||
processor = LineartAnimeProcessor()
|
||||
@ -273,15 +273,15 @@ class LineartAnimeImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Midas Depth Processor",
|
||||
tags=["controlnet", "midas"],
|
||||
category="controlnet",
|
||||
version="1.2.3",
|
||||
version="1.2.4",
|
||||
)
|
||||
class MidasDepthImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies Midas depth processing to image"""
|
||||
|
||||
a_mult: float = InputField(default=2.0, ge=0, description="Midas parameter `a_mult` (a = a_mult * PI)")
|
||||
bg_th: float = InputField(default=0.1, ge=0, description="Midas parameter `bg_th`")
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
# depth_and_normal not supported in controlnet_aux v0.0.3
|
||||
# depth_and_normal: bool = InputField(default=False, description="whether to use depth and normal mode")
|
||||
|
||||
@ -304,13 +304,13 @@ class MidasDepthImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Normal BAE Processor",
|
||||
tags=["controlnet"],
|
||||
category="controlnet",
|
||||
version="1.2.2",
|
||||
version="1.2.3",
|
||||
)
|
||||
class NormalbaeImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies NormalBae processing to image"""
|
||||
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
|
||||
def run_processor(self, image):
|
||||
normalbae_processor = NormalBaeDetector.from_pretrained("lllyasviel/Annotators")
|
||||
@ -321,13 +321,13 @@ class NormalbaeImageProcessorInvocation(ImageProcessorInvocation):
|
||||
|
||||
|
||||
@invocation(
|
||||
"mlsd_image_processor", title="MLSD Processor", tags=["controlnet", "mlsd"], category="controlnet", version="1.2.2"
|
||||
"mlsd_image_processor", title="MLSD Processor", tags=["controlnet", "mlsd"], category="controlnet", version="1.2.3"
|
||||
)
|
||||
class MlsdImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies MLSD processing to image"""
|
||||
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
thr_v: float = InputField(default=0.1, ge=0, description="MLSD parameter `thr_v`")
|
||||
thr_d: float = InputField(default=0.1, ge=0, description="MLSD parameter `thr_d`")
|
||||
|
||||
@ -344,13 +344,13 @@ class MlsdImageProcessorInvocation(ImageProcessorInvocation):
|
||||
|
||||
|
||||
@invocation(
|
||||
"pidi_image_processor", title="PIDI Processor", tags=["controlnet", "pidi"], category="controlnet", version="1.2.2"
|
||||
"pidi_image_processor", title="PIDI Processor", tags=["controlnet", "pidi"], category="controlnet", version="1.2.3"
|
||||
)
|
||||
class PidiImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies PIDI processing to image"""
|
||||
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
safe: bool = InputField(default=False, description=FieldDescriptions.safe_mode)
|
||||
scribble: bool = InputField(default=False, description=FieldDescriptions.scribble_mode)
|
||||
|
||||
@ -371,13 +371,13 @@ class PidiImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Content Shuffle Processor",
|
||||
tags=["controlnet", "contentshuffle"],
|
||||
category="controlnet",
|
||||
version="1.2.2",
|
||||
version="1.2.3",
|
||||
)
|
||||
class ContentShuffleImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies content shuffle processing to image"""
|
||||
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
h: int = InputField(default=512, ge=0, description="Content shuffle `h` parameter")
|
||||
w: int = InputField(default=512, ge=0, description="Content shuffle `w` parameter")
|
||||
f: int = InputField(default=256, ge=0, description="Content shuffle `f` parameter")
|
||||
@ -401,7 +401,7 @@ class ContentShuffleImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Zoe (Depth) Processor",
|
||||
tags=["controlnet", "zoe", "depth"],
|
||||
category="controlnet",
|
||||
version="1.2.2",
|
||||
version="1.2.3",
|
||||
)
|
||||
class ZoeDepthImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies Zoe depth processing to image"""
|
||||
@ -417,15 +417,15 @@ class ZoeDepthImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Mediapipe Face Processor",
|
||||
tags=["controlnet", "mediapipe", "face"],
|
||||
category="controlnet",
|
||||
version="1.2.3",
|
||||
version="1.2.4",
|
||||
)
|
||||
class MediapipeFaceProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies mediapipe face processing to image"""
|
||||
|
||||
max_faces: int = InputField(default=1, ge=1, description="Maximum number of faces to detect")
|
||||
min_confidence: float = InputField(default=0.5, ge=0, le=1, description="Minimum confidence for face detection")
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
|
||||
def run_processor(self, image):
|
||||
mediapipe_face_processor = MediapipeFaceDetector()
|
||||
@ -444,7 +444,7 @@ class MediapipeFaceProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Leres (Depth) Processor",
|
||||
tags=["controlnet", "leres", "depth"],
|
||||
category="controlnet",
|
||||
version="1.2.2",
|
||||
version="1.2.3",
|
||||
)
|
||||
class LeresImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies leres processing to image"""
|
||||
@ -452,8 +452,8 @@ class LeresImageProcessorInvocation(ImageProcessorInvocation):
|
||||
thr_a: float = InputField(default=0, description="Leres parameter `thr_a`")
|
||||
thr_b: float = InputField(default=0, description="Leres parameter `thr_b`")
|
||||
boost: bool = InputField(default=False, description="Whether to use boost mode")
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
|
||||
def run_processor(self, image):
|
||||
leres_processor = LeresDetector.from_pretrained("lllyasviel/Annotators")
|
||||
@ -473,7 +473,7 @@ class LeresImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Tile Resample Processor",
|
||||
tags=["controlnet", "tile"],
|
||||
category="controlnet",
|
||||
version="1.2.2",
|
||||
version="1.2.3",
|
||||
)
|
||||
class TileResamplerProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Tile resampler processor"""
|
||||
@ -513,13 +513,13 @@ class TileResamplerProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Segment Anything Processor",
|
||||
tags=["controlnet", "segmentanything"],
|
||||
category="controlnet",
|
||||
version="1.2.3",
|
||||
version="1.2.4",
|
||||
)
|
||||
class SegmentAnythingProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Applies segment anything processing to image"""
|
||||
|
||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
detect_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.detect_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
|
||||
def run_processor(self, image):
|
||||
# segment_anything_processor = SamDetector.from_pretrained("ybelkada/segment-anything", subfolder="checkpoints")
|
||||
@ -560,12 +560,12 @@ class SamDetectorReproducibleColors(SamDetector):
|
||||
title="Color Map Processor",
|
||||
tags=["controlnet"],
|
||||
category="controlnet",
|
||||
version="1.2.2",
|
||||
version="1.2.3",
|
||||
)
|
||||
class ColorMapImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Generates a color map from the provided image"""
|
||||
|
||||
color_map_tile_size: int = InputField(default=64, ge=0, description=FieldDescriptions.tile_size)
|
||||
color_map_tile_size: int = InputField(default=64, ge=1, description=FieldDescriptions.tile_size)
|
||||
|
||||
def run_processor(self, image: Image.Image):
|
||||
np_image = np.array(image, dtype=np.uint8)
|
||||
@ -592,7 +592,7 @@ DEPTH_ANYTHING_MODEL_SIZES = Literal["large", "base", "small"]
|
||||
title="Depth Anything Processor",
|
||||
tags=["controlnet", "depth", "depth anything"],
|
||||
category="controlnet",
|
||||
version="1.1.1",
|
||||
version="1.1.2",
|
||||
)
|
||||
class DepthAnythingImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Generates a depth map based on the Depth Anything algorithm"""
|
||||
@ -600,7 +600,7 @@ class DepthAnythingImageProcessorInvocation(ImageProcessorInvocation):
|
||||
model_size: DEPTH_ANYTHING_MODEL_SIZES = InputField(
|
||||
default="small", description="The size of the depth model to use"
|
||||
)
|
||||
resolution: int = InputField(default=512, ge=64, multiple_of=64, description=FieldDescriptions.image_res)
|
||||
resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
|
||||
def run_processor(self, image: Image.Image):
|
||||
depth_anything_detector = DepthAnythingDetector()
|
||||
@ -615,7 +615,7 @@ class DepthAnythingImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="DW Openpose Image Processor",
|
||||
tags=["controlnet", "dwpose", "openpose"],
|
||||
category="controlnet",
|
||||
version="1.1.0",
|
||||
version="1.1.1",
|
||||
)
|
||||
class DWOpenposeImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Generates an openpose pose from an image using DWPose"""
|
||||
@ -623,7 +623,7 @@ class DWOpenposeImageProcessorInvocation(ImageProcessorInvocation):
|
||||
draw_body: bool = InputField(default=True)
|
||||
draw_face: bool = InputField(default=False)
|
||||
draw_hands: bool = InputField(default=False)
|
||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||
image_resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
|
||||
def run_processor(self, image: Image.Image):
|
||||
dw_openpose = DWOpenposeDetector()
|
||||
@ -642,15 +642,15 @@ class DWOpenposeImageProcessorInvocation(ImageProcessorInvocation):
|
||||
title="Heuristic Resize",
|
||||
tags=["image, controlnet"],
|
||||
category="image",
|
||||
version="1.0.0",
|
||||
version="1.0.1",
|
||||
classification=Classification.Prototype,
|
||||
)
|
||||
class HeuristicResizeInvocation(BaseInvocation):
|
||||
"""Resize an image using a heuristic method. Preserves edge maps."""
|
||||
|
||||
image: ImageField = InputField(description="The image to resize")
|
||||
width: int = InputField(default=512, gt=0, description="The width to resize to (px)")
|
||||
height: int = InputField(default=512, gt=0, description="The height to resize to (px)")
|
||||
width: int = InputField(default=512, ge=1, description="The width to resize to (px)")
|
||||
height: int = InputField(default=512, ge=1, description="The height to resize to (px)")
|
||||
|
||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||
image = context.images.get_pil(self.image.image_name, "RGB")
|
||||
|
@ -3,7 +3,7 @@ import inspect
|
||||
import math
|
||||
from contextlib import ExitStack
|
||||
from functools import singledispatchmethod
|
||||
from typing import Any, Iterator, List, Literal, Optional, Tuple, Union
|
||||
from typing import Any, Dict, Iterator, List, Literal, Optional, Tuple, Union
|
||||
|
||||
import einops
|
||||
import numpy as np
|
||||
@ -11,7 +11,6 @@ import numpy.typing as npt
|
||||
import torch
|
||||
import torchvision
|
||||
import torchvision.transforms as T
|
||||
from diffusers import AutoencoderKL, AutoencoderTiny
|
||||
from diffusers.configuration_utils import ConfigMixin
|
||||
from diffusers.image_processor import VaeImageProcessor
|
||||
from diffusers.models.adapter import T2IAdapter
|
||||
@ -21,9 +20,12 @@ from diffusers.models.attention_processor import (
|
||||
LoRAXFormersAttnProcessor,
|
||||
XFormersAttnProcessor,
|
||||
)
|
||||
from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL
|
||||
from diffusers.models.autoencoders.autoencoder_tiny import AutoencoderTiny
|
||||
from diffusers.models.unets.unet_2d_condition import UNet2DConditionModel
|
||||
from diffusers.schedulers import DPMSolverSDEScheduler
|
||||
from diffusers.schedulers import SchedulerMixin as Scheduler
|
||||
from diffusers.schedulers.scheduling_dpmsolver_sde import DPMSolverSDEScheduler
|
||||
from diffusers.schedulers.scheduling_tcd import TCDScheduler
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin as Scheduler
|
||||
from PIL import Image, ImageFilter
|
||||
from pydantic import field_validator
|
||||
from torchvision.transforms.functional import resize as tv_resize
|
||||
@ -521,9 +523,10 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
||||
)
|
||||
|
||||
if is_sdxl:
|
||||
return SDXLConditioningInfo(
|
||||
embeds=text_embedding, pooled_embeds=pooled_embedding, add_time_ids=add_time_ids
|
||||
), regions
|
||||
return (
|
||||
SDXLConditioningInfo(embeds=text_embedding, pooled_embeds=pooled_embedding, add_time_ids=add_time_ids),
|
||||
regions,
|
||||
)
|
||||
return BasicConditioningInfo(embeds=text_embedding), regions
|
||||
|
||||
def get_conditioning_data(
|
||||
@ -825,7 +828,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
||||
denoising_start: float,
|
||||
denoising_end: float,
|
||||
seed: int,
|
||||
) -> Tuple[int, List[int], int]:
|
||||
) -> Tuple[int, List[int], int, Dict[str, Any]]:
|
||||
assert isinstance(scheduler, ConfigMixin)
|
||||
if scheduler.config.get("cpu_only", False):
|
||||
scheduler.set_timesteps(steps, device="cpu")
|
||||
@ -853,13 +856,15 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
||||
timesteps = timesteps[t_start_idx : t_start_idx + t_end_idx]
|
||||
num_inference_steps = len(timesteps) // scheduler.order
|
||||
|
||||
scheduler_step_kwargs = {}
|
||||
scheduler_step_kwargs: Dict[str, Any] = {}
|
||||
scheduler_step_signature = inspect.signature(scheduler.step)
|
||||
if "generator" in scheduler_step_signature.parameters:
|
||||
# At some point, someone decided that schedulers that accept a generator should use the original seed with
|
||||
# all bits flipped. I don't know the original rationale for this, but now we must keep it like this for
|
||||
# reproducibility.
|
||||
scheduler_step_kwargs = {"generator": torch.Generator(device=device).manual_seed(seed ^ 0xFFFFFFFF)}
|
||||
scheduler_step_kwargs.update({"generator": torch.Generator(device=device).manual_seed(seed ^ 0xFFFFFFFF)})
|
||||
if isinstance(scheduler, TCDScheduler):
|
||||
scheduler_step_kwargs.update({"eta": 1.0})
|
||||
|
||||
return num_inference_steps, timesteps, init_timestep, scheduler_step_kwargs
|
||||
|
||||
|
@ -13,6 +13,7 @@ from diffusers import (
|
||||
LCMScheduler,
|
||||
LMSDiscreteScheduler,
|
||||
PNDMScheduler,
|
||||
TCDScheduler,
|
||||
UniPCMultistepScheduler,
|
||||
)
|
||||
|
||||
@ -40,4 +41,5 @@ SCHEDULER_MAP = {
|
||||
"dpmpp_sde_k": (DPMSolverSDEScheduler, {"use_karras_sigmas": True, "noise_sampler_seed": 0}),
|
||||
"unipc": (UniPCMultistepScheduler, {"cpu_only": True}),
|
||||
"lcm": (LCMScheduler, {}),
|
||||
"tcd": (TCDScheduler, {}),
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
"typegen": "node scripts/typegen.js",
|
||||
"preview": "vite preview",
|
||||
"lint:knip": "knip",
|
||||
"lint:dpdm": "dpdm --no-warning --no-tree --transform --exit-code circular:0 src/main.tsx",
|
||||
"lint:dpdm": "dpdm --no-warning --no-tree --transform --exit-code circular:1 src/main.tsx",
|
||||
"lint:eslint": "eslint --max-warnings=0 .",
|
||||
"lint:prettier": "prettier --check .",
|
||||
"lint:tsc": "tsc --noEmit",
|
||||
@ -58,7 +58,7 @@
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fontsource-variable/inter": "^5.0.17",
|
||||
"@invoke-ai/ui-library": "^0.0.21",
|
||||
"@invoke-ai/ui-library": "^0.0.25",
|
||||
"@nanostores/react": "^0.7.2",
|
||||
"@reduxjs/toolkit": "2.2.2",
|
||||
"@roarr/browser-log-writer": "^1.3.0",
|
||||
|
@ -7,7 +7,7 @@ settings:
|
||||
dependencies:
|
||||
'@chakra-ui/react':
|
||||
specifier: ^2.8.2
|
||||
version: 2.8.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(@types/react@18.2.59)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0)
|
||||
version: 2.8.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.73)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/react-use-size':
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0(react@18.2.0)
|
||||
@ -30,8 +30,8 @@ dependencies:
|
||||
specifier: ^5.0.17
|
||||
version: 5.0.17
|
||||
'@invoke-ai/ui-library':
|
||||
specifier: ^0.0.21
|
||||
version: 0.0.21(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.17)(@internationalized/date@3.5.2)(@types/react@18.2.73)(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: ^0.0.25
|
||||
version: 0.0.25(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.17)(@internationalized/date@3.5.3)(@types/react@18.2.73)(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@nanostores/react':
|
||||
specifier: ^0.7.2
|
||||
version: 0.7.2(nanostores@0.10.0)(react@18.2.0)
|
||||
@ -306,7 +306,7 @@ packages:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
dev: true
|
||||
|
||||
/@ark-ui/anatomy@1.3.0(@internationalized/date@3.5.2):
|
||||
/@ark-ui/anatomy@1.3.0(@internationalized/date@3.5.3):
|
||||
resolution: {integrity: sha512-1yG2MrzUlix6KthjQMCNiHnkXrWwEdFAX6D+HqGJaNu0XvaGul2J+wDNtjsdX+gxiWu1nXXEEOAWlFVYMUf65w==}
|
||||
dependencies:
|
||||
'@zag-js/accordion': 0.32.1
|
||||
@ -318,7 +318,7 @@ packages:
|
||||
'@zag-js/color-utils': 0.32.1
|
||||
'@zag-js/combobox': 0.32.1
|
||||
'@zag-js/date-picker': 0.32.1
|
||||
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.2)
|
||||
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.3)
|
||||
'@zag-js/dialog': 0.32.1
|
||||
'@zag-js/editable': 0.32.1
|
||||
'@zag-js/file-upload': 0.32.1
|
||||
@ -345,13 +345,13 @@ packages:
|
||||
- '@internationalized/date'
|
||||
dev: false
|
||||
|
||||
/@ark-ui/react@1.3.0(@internationalized/date@3.5.2)(react-dom@18.2.0)(react@18.2.0):
|
||||
/@ark-ui/react@1.3.0(@internationalized/date@3.5.3)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-JHjNoIX50+mUCTaEGMjfGQWGGi31pKsV646jZJlR/1xohpYJigzg8BvO97cTsVk8fwtur+cm11gz3Nf7f5QUnA==}
|
||||
peerDependencies:
|
||||
react: '>=18.0.0'
|
||||
react-dom: '>=18.0.0'
|
||||
dependencies:
|
||||
'@ark-ui/anatomy': 1.3.0(@internationalized/date@3.5.2)
|
||||
'@ark-ui/anatomy': 1.3.0(@internationalized/date@3.5.3)
|
||||
'@zag-js/accordion': 0.32.1
|
||||
'@zag-js/avatar': 0.32.1
|
||||
'@zag-js/carousel': 0.32.1
|
||||
@ -361,7 +361,7 @@ packages:
|
||||
'@zag-js/combobox': 0.32.1
|
||||
'@zag-js/core': 0.32.1
|
||||
'@zag-js/date-picker': 0.32.1
|
||||
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.2)
|
||||
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.3)
|
||||
'@zag-js/dialog': 0.32.1
|
||||
'@zag-js/editable': 0.32.1
|
||||
'@zag-js/file-upload': 0.32.1
|
||||
@ -1681,7 +1681,7 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/accordion@2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0):
|
||||
/@chakra-ui/accordion@2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0):
|
||||
resolution: {integrity: sha512-FSXRm8iClFyU+gVaXisOSEw0/4Q+qZbFRiuhIAkVU6Boj0FxAMrlo9a8AV5TuF77rgaHytCdHk0Ng+cyUijrag==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/system': '>=2.0.0'
|
||||
@ -1694,9 +1694,9 @@ packages:
|
||||
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/shared-utils': 2.0.5
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.6)(react@18.2.0)
|
||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.22)(react@18.2.0)
|
||||
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
@ -1848,16 +1848,6 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/css-reset@2.3.0(@emotion/react@11.11.3)(react@18.2.0):
|
||||
resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==}
|
||||
peerDependencies:
|
||||
'@emotion/react': '>=10.0.35'
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@emotion/react': 11.11.3(@types/react@18.2.59)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/css-reset@2.3.0(@emotion/react@11.11.4)(react@18.2.0):
|
||||
resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==}
|
||||
peerDependencies:
|
||||
@ -1905,18 +1895,6 @@ packages:
|
||||
resolution: {integrity: sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==}
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/focus-lock@2.1.0(@types/react@18.2.59)(react@18.2.0):
|
||||
resolution: {integrity: sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/dom-utils': 2.1.0
|
||||
react: 18.2.0
|
||||
react-focus-lock: 2.11.1(@types/react@18.2.59)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/focus-lock@2.1.0(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==}
|
||||
peerDependencies:
|
||||
@ -1924,7 +1902,7 @@ packages:
|
||||
dependencies:
|
||||
'@chakra-ui/dom-utils': 2.1.0
|
||||
react: 18.2.0
|
||||
react-focus-lock: 2.11.2(@types/react@18.2.73)(react@18.2.0)
|
||||
react-focus-lock: 2.11.1(@types/react@18.2.73)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
@ -2100,59 +2078,6 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/menu@2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0):
|
||||
resolution: {integrity: sha512-lJS7XEObzJxsOwWQh7yfG4H8FzFPRP5hVPN/CL+JzytEINCSBvsCDHrYPQGp7jzpCi8vnTqQQGQe0f8dwnXd2g==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/system': '>=2.0.0'
|
||||
framer-motion: '>=4.0.0'
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/clickable': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/descendant': 3.1.0(react@18.2.0)
|
||||
'@chakra-ui/lazy-utils': 2.0.5
|
||||
'@chakra-ui/popper': 3.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-children-utils': 2.0.6(react@18.2.0)
|
||||
'@chakra-ui/react-context': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-use-animation-state': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-use-disclosure': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-use-focus-effect': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.2.0)
|
||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/shared-utils': 2.0.5
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.6)(react@18.2.0)
|
||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.59)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/system': '>=2.0.0'
|
||||
framer-motion: '>=4.0.0'
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.59)(react@18.2.0)
|
||||
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/react-context': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-types': 2.0.7(react@18.2.0)
|
||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/shared-utils': 2.0.5
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.6)(react@18.2.0)
|
||||
aria-hidden: 1.2.3
|
||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-remove-scroll: 2.5.7(@types/react@18.2.59)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.73)(framer-motion@10.18.0)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==}
|
||||
peerDependencies:
|
||||
@ -2170,11 +2095,37 @@ packages:
|
||||
'@chakra-ui/shared-utils': 2.0.5
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.2.0)
|
||||
aria-hidden: 1.2.4
|
||||
aria-hidden: 1.2.3
|
||||
framer-motion: 10.18.0(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-remove-scroll: 2.5.9(@types/react@18.2.73)(react@18.2.0)
|
||||
react-remove-scroll: 2.5.7(@types/react@18.2.73)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.73)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/system': '>=2.0.0'
|
||||
framer-motion: '>=4.0.0'
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.73)(react@18.2.0)
|
||||
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/react-context': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-types': 2.0.7(react@18.2.0)
|
||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/shared-utils': 2.0.5
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.22)(react@18.2.0)
|
||||
aria-hidden: 1.2.3
|
||||
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-remove-scroll: 2.5.7(@types/react@18.2.73)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
@ -2248,7 +2199,7 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/popover@2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0):
|
||||
/@chakra-ui/popover@2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0):
|
||||
resolution: {integrity: sha512-K+2ai2dD0ljvJnlrzesCDT9mNzLifE3noGKZ3QwLqd/K34Ym1W/0aL1ERSynrcG78NKoXS54SdEzkhCZ4Gn/Zg==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/system': '>=2.0.0'
|
||||
@ -2266,8 +2217,8 @@ packages:
|
||||
'@chakra-ui/react-use-focus-on-pointer-down': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/shared-utils': 2.0.5
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
@ -2305,25 +2256,6 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/provider@2.4.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.0.0
|
||||
'@emotion/styled': ^11.0.0
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.11.3)(react@18.2.0)
|
||||
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/react-env': 3.1.0(react@18.2.0)
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/utils': 2.0.15
|
||||
'@emotion/react': 11.11.3(@types/react@18.2.59)(react@18.2.0)
|
||||
'@emotion/styled': 11.11.0(@emotion/react@11.11.3)(@types/react@18.2.59)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/provider@2.4.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==}
|
||||
peerDependencies:
|
||||
@ -2554,77 +2486,6 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/react@2.8.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(@types/react@18.2.59)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.0.0
|
||||
'@emotion/styled': ^11.0.0
|
||||
framer-motion: '>=4.0.0'
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/accordion': 2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0)
|
||||
'@chakra-ui/alert': 2.2.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/avatar': 2.3.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/breadcrumb': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/button': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/card': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/counter': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.11.3)(react@18.2.0)
|
||||
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.59)(react@18.2.0)
|
||||
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/hooks': 2.2.1(react@18.2.0)
|
||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/image': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/input': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/live-region': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0)
|
||||
'@chakra-ui/modal': 2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.59)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/number-input': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/pin-input': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/popover': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0)
|
||||
'@chakra-ui/popper': 3.1.0(react@18.2.0)
|
||||
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/provider': 2.4.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/radio': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/react-env': 3.1.0(react@18.2.0)
|
||||
'@chakra-ui/select': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/skeleton': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/skip-nav': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/slider': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/stat': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/styled-system': 2.9.2
|
||||
'@chakra-ui/switch': 2.1.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0)
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/textarea': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
||||
'@chakra-ui/theme-utils': 2.0.21
|
||||
'@chakra-ui/toast': 7.0.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/tooltip': 2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.6)(react@18.2.0)
|
||||
'@chakra-ui/utils': 2.0.15
|
||||
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@emotion/react': 11.11.3(@types/react@18.2.59)(react@18.2.0)
|
||||
'@emotion/styled': 11.11.0(@emotion/react@11.11.3)(@types/react@18.2.59)(react@18.2.0)
|
||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/react@2.8.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.73)(framer-motion@10.18.0)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
||||
peerDependencies:
|
||||
@ -2696,6 +2557,77 @@ packages:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/react@2.8.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.73)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.0.0
|
||||
'@emotion/styled': ^11.0.0
|
||||
framer-motion: '>=4.0.0'
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/accordion': 2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0)
|
||||
'@chakra-ui/alert': 2.2.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/avatar': 2.3.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/breadcrumb': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/button': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/card': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/counter': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.11.4)(react@18.2.0)
|
||||
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.73)(react@18.2.0)
|
||||
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/hooks': 2.2.1(react@18.2.0)
|
||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/image': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/input': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/live-region': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0)
|
||||
'@chakra-ui/modal': 2.3.1(@chakra-ui/system@2.6.2)(@types/react@18.2.73)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/number-input': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/pin-input': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/popover': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0)
|
||||
'@chakra-ui/popper': 3.1.0(react@18.2.0)
|
||||
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/provider': 2.4.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/radio': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/react-env': 3.1.0(react@18.2.0)
|
||||
'@chakra-ui/select': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/skeleton': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/skip-nav': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/slider': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/stat': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/styled-system': 2.9.2
|
||||
'@chakra-ui/switch': 2.1.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0)
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/textarea': 2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
||||
'@chakra-ui/theme-utils': 2.0.21
|
||||
'@chakra-ui/toast': 7.0.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/tooltip': 2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.0.22)(react@18.2.0)
|
||||
'@chakra-ui/utils': 2.0.15
|
||||
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@emotion/react': 11.11.4(@types/react@18.2.73)(react@18.2.0)
|
||||
'@emotion/styled': 11.11.0(@emotion/react@11.11.4)(@types/react@18.2.73)(react@18.2.0)
|
||||
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/select@2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0):
|
||||
resolution: {integrity: sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==}
|
||||
peerDependencies:
|
||||
@ -2814,7 +2746,7 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/switch@2.1.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react@18.2.0):
|
||||
/@chakra-ui/switch@2.1.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react@18.2.0):
|
||||
resolution: {integrity: sha512-pgmi/CC+E1v31FcnQhsSGjJnOE2OcND4cKPyTE+0F+bmGm48Q/b5UmKD9Y+CmZsrt/7V3h8KNczowupfuBfIHA==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/system': '>=2.0.0'
|
||||
@ -2823,30 +2755,11 @@ packages:
|
||||
dependencies:
|
||||
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/shared-utils': 2.0.5
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/system@2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.0.0
|
||||
'@emotion/styled': ^11.0.0
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/color-mode': 2.2.0(react@18.2.0)
|
||||
'@chakra-ui/object-utils': 2.1.0
|
||||
'@chakra-ui/react-utils': 2.0.12(react@18.2.0)
|
||||
'@chakra-ui/styled-system': 2.9.2
|
||||
'@chakra-ui/theme-utils': 2.0.21
|
||||
'@chakra-ui/utils': 2.0.15
|
||||
'@emotion/react': 11.11.3(@types/react@18.2.59)(react@18.2.0)
|
||||
'@emotion/styled': 11.11.0(@emotion/react@11.11.3)(@types/react@18.2.59)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-fast-compare: 3.2.2
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/system@2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==}
|
||||
peerDependencies:
|
||||
@ -2975,7 +2888,7 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/toast@7.0.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0):
|
||||
/@chakra-ui/toast@7.0.2(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-yvRP8jFKRs/YnkuE41BVTq9nB2v/KDRmje9u6dgDmE5+1bFt3bwjdf9gVbif4u5Ve7F7BGk5E093ARRVtvLvXA==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/system': 2.6.2
|
||||
@ -2991,9 +2904,9 @@ packages:
|
||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/shared-utils': 2.0.5
|
||||
'@chakra-ui/styled-system': 2.9.2
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
||||
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
@ -3020,7 +2933,7 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/tooltip@2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.6)(react-dom@18.2.0)(react@18.2.0):
|
||||
/@chakra-ui/tooltip@2.3.1(@chakra-ui/system@2.6.2)(framer-motion@11.0.22)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Rh39GBn/bL4kZpuEMPPRwYNnccRCL+w9OqamWHIB3Qboxs6h8cOyXfIdGxjo72lvhu1QI/a4KFqkM3St+WfC0A==}
|
||||
peerDependencies:
|
||||
'@chakra-ui/system': '>=2.0.0'
|
||||
@ -3036,8 +2949,8 @@ packages:
|
||||
'@chakra-ui/react-use-event-listener': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.2.0)
|
||||
'@chakra-ui/shared-utils': 2.0.5
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.3)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0)
|
||||
framer-motion: 11.0.22(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
@ -3064,17 +2977,6 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/transition@2.1.0(framer-motion@11.0.6)(react@18.2.0):
|
||||
resolution: {integrity: sha512-orkT6T/Dt+/+kVwJNy7zwJ+U2xAZ3EU7M3XCs45RBvUnZDr/u9vdmaM/3D/rOpmQJWgQBwKPJleUXrYWUagEDQ==}
|
||||
peerDependencies:
|
||||
framer-motion: '>=4.0.0'
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/shared-utils': 2.0.5
|
||||
framer-motion: 11.0.6(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@chakra-ui/utils@2.0.15:
|
||||
resolution: {integrity: sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==}
|
||||
dependencies:
|
||||
@ -3198,12 +3100,6 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@emotion/is-prop-valid@1.2.1:
|
||||
resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==}
|
||||
dependencies:
|
||||
'@emotion/memoize': 0.8.1
|
||||
dev: false
|
||||
|
||||
/@emotion/is-prop-valid@1.2.2:
|
||||
resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==}
|
||||
dependencies:
|
||||
@ -3220,27 +3116,6 @@ packages:
|
||||
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
|
||||
dev: false
|
||||
|
||||
/@emotion/react@11.11.3(@types/react@18.2.59)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: '>=16.8.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.9
|
||||
'@emotion/babel-plugin': 11.11.0
|
||||
'@emotion/cache': 11.11.0
|
||||
'@emotion/serialize': 1.1.3
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
|
||||
'@emotion/utils': 1.2.1
|
||||
'@emotion/weak-memoize': 0.3.1
|
||||
'@types/react': 18.2.59
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@emotion/react@11.11.4(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==}
|
||||
peerDependencies:
|
||||
@ -3276,27 +3151,6 @@ packages:
|
||||
resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==}
|
||||
dev: false
|
||||
|
||||
/@emotion/styled@11.11.0(@emotion/react@11.11.3)(@types/react@18.2.59)(react@18.2.0):
|
||||
resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.0.0-rc.0
|
||||
'@types/react': '*'
|
||||
react: '>=16.8.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.9
|
||||
'@emotion/babel-plugin': 11.11.0
|
||||
'@emotion/is-prop-valid': 1.2.1
|
||||
'@emotion/react': 11.11.3(@types/react@18.2.59)(react@18.2.0)
|
||||
'@emotion/serialize': 1.1.3
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
|
||||
'@emotion/utils': 1.2.1
|
||||
'@types/react': 18.2.59
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@emotion/styled@11.11.0(@emotion/react@11.11.4)(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==}
|
||||
peerDependencies:
|
||||
@ -3663,16 +3517,16 @@ packages:
|
||||
resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
|
||||
dev: true
|
||||
|
||||
/@internationalized/date@3.5.2:
|
||||
resolution: {integrity: sha512-vo1yOMUt2hzp63IutEaTUxROdvQg1qlMRsbCvbay2AK2Gai7wIgCyK5weEX3nHkiLgo4qCXHijFNC/ILhlRpOQ==}
|
||||
/@internationalized/date@3.5.3:
|
||||
resolution: {integrity: sha512-X9bi8NAEHAjD8yzmPYT2pdJsbe+tYSEBAfowtlxJVJdZR3aK8Vg7ZUT1Fm5M47KLzp/M1p1VwAaeSma3RT7biw==}
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.7
|
||||
'@swc/helpers': 0.5.11
|
||||
dev: false
|
||||
|
||||
/@internationalized/number@3.5.1:
|
||||
resolution: {integrity: sha512-N0fPU/nz15SwR9IbfJ5xaS9Ss/O5h1sVXMZf43vc9mxEG48ovglvvzBjF53aHlq20uoR6c+88CrIXipU/LSzwg==}
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.7
|
||||
'@swc/helpers': 0.5.11
|
||||
dev: false
|
||||
|
||||
/@invoke-ai/eslint-config-react@0.0.14(eslint@8.57.0)(prettier@3.2.5)(typescript@5.4.3):
|
||||
@ -3709,14 +3563,14 @@ packages:
|
||||
prettier: 3.2.5
|
||||
dev: true
|
||||
|
||||
/@invoke-ai/ui-library@0.0.21(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.17)(@internationalized/date@3.5.2)(@types/react@18.2.73)(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-tCvgkBPDt0gNq+8IcR03e/Mw7R8Mb/SMXTqx3FEIxlTQEo93A/D38dKXeDCzTdx4sQ+sknfB+JLBbHs6sg5hhQ==}
|
||||
/@invoke-ai/ui-library@0.0.25(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.17)(@internationalized/date@3.5.3)(@types/react@18.2.73)(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Fmjdlu62NXHgairYXGjcuCrxPEAl1G6Q6ban8g3excF6pDDdBeS7CmSNCyEDMxnSIOZrQlI04OhaMB17Imi9Uw==}
|
||||
peerDependencies:
|
||||
'@fontsource-variable/inter': ^5.0.16
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
dependencies:
|
||||
'@ark-ui/react': 1.3.0(@internationalized/date@3.5.2)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@ark-ui/react': 1.3.0(@internationalized/date@3.5.3)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@chakra-ui/anatomy': 2.2.2
|
||||
'@chakra-ui/icons': 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.2.0)
|
||||
@ -5381,8 +5235,8 @@ packages:
|
||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||
dev: true
|
||||
|
||||
/@swc/helpers@0.5.7:
|
||||
resolution: {integrity: sha512-BVvNZhx362+l2tSwSuyEUV4h7+jk9raNdoTSdLfwTshXJSaGmYKluGRJznziCI3KX02Z19DdsQrdfrpXAU3Hfg==}
|
||||
/@swc/helpers@0.5.11:
|
||||
resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==}
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
@ -5844,10 +5698,6 @@ packages:
|
||||
resolution: {integrity: sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==}
|
||||
dev: true
|
||||
|
||||
/@types/prop-types@15.7.11:
|
||||
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
|
||||
dev: false
|
||||
|
||||
/@types/prop-types@15.7.12:
|
||||
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
|
||||
|
||||
@ -5877,14 +5727,6 @@ packages:
|
||||
'@types/react': 18.2.73
|
||||
dev: false
|
||||
|
||||
/@types/react@18.2.59:
|
||||
resolution: {integrity: sha512-DE+F6BYEC8VtajY85Qr7mmhTd/79rJKIHCg99MU9SWPB4xvLb6D1za2vYflgZfmPqQVEr6UqJTnLXEwzpVPuOg==}
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.11
|
||||
'@types/scheduler': 0.16.8
|
||||
csstype: 3.1.3
|
||||
dev: false
|
||||
|
||||
/@types/react@18.2.73:
|
||||
resolution: {integrity: sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA==}
|
||||
dependencies:
|
||||
@ -5895,10 +5737,6 @@ packages:
|
||||
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==}
|
||||
dev: true
|
||||
|
||||
/@types/scheduler@0.16.8:
|
||||
resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
|
||||
dev: false
|
||||
|
||||
/@types/semver@7.5.8:
|
||||
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
||||
dev: true
|
||||
@ -6405,10 +6243,10 @@ packages:
|
||||
/@zag-js/date-picker@0.32.1:
|
||||
resolution: {integrity: sha512-n/hYmF+/R4+NuyfPRzCgeuLT6LJihKSuKzK29STPWy3sC/tBBHiqhNv1/4UKbatHUJXdBW2XF+N8Rw08RffcFQ==}
|
||||
dependencies:
|
||||
'@internationalized/date': 3.5.2
|
||||
'@internationalized/date': 3.5.3
|
||||
'@zag-js/anatomy': 0.32.1
|
||||
'@zag-js/core': 0.32.1
|
||||
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.2)
|
||||
'@zag-js/date-utils': 0.32.1(@internationalized/date@3.5.3)
|
||||
'@zag-js/dismissable': 0.32.1
|
||||
'@zag-js/dom-event': 0.32.1
|
||||
'@zag-js/dom-query': 0.32.1
|
||||
@ -6420,12 +6258,12 @@ packages:
|
||||
'@zag-js/utils': 0.32.1
|
||||
dev: false
|
||||
|
||||
/@zag-js/date-utils@0.32.1(@internationalized/date@3.5.2):
|
||||
/@zag-js/date-utils@0.32.1(@internationalized/date@3.5.3):
|
||||
resolution: {integrity: sha512-dbBDRSVr5pRUw3rXndyGuSshZiWqQI5JQO4D2KIFGkXzorj6WzoOpcO910Z7AdM/9cCAMpCjUrka8d8o9BpJBg==}
|
||||
peerDependencies:
|
||||
'@internationalized/date': '>=3.0.0'
|
||||
dependencies:
|
||||
'@internationalized/date': 3.5.2
|
||||
'@internationalized/date': 3.5.3
|
||||
dev: false
|
||||
|
||||
/@zag-js/dialog@0.32.1:
|
||||
@ -6999,13 +6837,6 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/aria-hidden@1.2.4:
|
||||
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/aria-query@5.1.3:
|
||||
resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==}
|
||||
dependencies:
|
||||
@ -9026,13 +8857,6 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/focus-lock@1.3.4:
|
||||
resolution: {integrity: sha512-Gv0N3mvej3pD+HWkNryrF8sExzEHqhQ6OSFxD4DPxm9n5HGCaHme98ZMBZroNEAJcsdtHxk+skvThGKyUeoEGA==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/focus-trap@7.5.4:
|
||||
resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==}
|
||||
dependencies:
|
||||
@ -9095,24 +8919,6 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/framer-motion@11.0.6(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-BpO3mWF8UwxzO3Ca5AmSkrg14QYTeJa9vKgoLOoBdBdTPj0e81i1dMwnX6EQJXRieUx20uiDBXq8bA6y7N6b8Q==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
tslib: 2.6.2
|
||||
optionalDependencies:
|
||||
'@emotion/is-prop-valid': 0.8.8
|
||||
dev: false
|
||||
|
||||
/framesync@6.1.2:
|
||||
resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==}
|
||||
dependencies:
|
||||
@ -11485,7 +11291,7 @@ packages:
|
||||
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
|
||||
dev: false
|
||||
|
||||
/react-focus-lock@2.11.1(@types/react@18.2.59)(react@18.2.0):
|
||||
/react-focus-lock@2.11.1(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-IXLwnTBrLTlKTpASZXqqXJ8oymWrgAlOfuuDYN4XCuN1YJ72dwX198UCaF1QqGUk5C3QOnlMik//n3ufcfe8Ig==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
@ -11495,31 +11301,12 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.9
|
||||
'@types/react': 18.2.59
|
||||
'@types/react': 18.2.73
|
||||
focus-lock: 1.3.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-clientside-effect: 1.2.6(react@18.2.0)
|
||||
use-callback-ref: 1.3.1(@types/react@18.2.59)(react@18.2.0)
|
||||
use-sidecar: 1.1.2(@types/react@18.2.59)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-focus-lock@2.11.2(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-DDTbEiov0+RthESPVSTIdAWPPKic+op3sCcP+icbMRobvQNt7LuAlJ3KoarqQv5sCgKArru3kXmlmFTa27/CdQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.1
|
||||
'@types/react': 18.2.73
|
||||
focus-lock: 1.3.4
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-clientside-effect: 1.2.6(react@18.2.0)
|
||||
use-callback-ref: 1.3.2(@types/react@18.2.73)(react@18.2.0)
|
||||
use-callback-ref: 1.3.1(@types/react@18.2.73)(react@18.2.0)
|
||||
use-sidecar: 1.1.2(@types/react@18.2.73)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
@ -11634,25 +11421,9 @@ packages:
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-remove-scroll-bar@2.3.5(@types/react@18.2.59)(react@18.2.0):
|
||||
/react-remove-scroll-bar@2.3.5(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.59
|
||||
react: 18.2.0
|
||||
react-style-singleton: 2.2.1(@types/react@18.2.59)(react@18.2.0)
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/react-remove-scroll-bar@2.3.6(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
@ -11666,28 +11437,9 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/react-remove-scroll@2.5.7(@types/react@18.2.59)(react@18.2.0):
|
||||
/react-remove-scroll@2.5.7(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.59
|
||||
react: 18.2.0
|
||||
react-remove-scroll-bar: 2.3.5(@types/react@18.2.59)(react@18.2.0)
|
||||
react-style-singleton: 2.2.1(@types/react@18.2.59)(react@18.2.0)
|
||||
tslib: 2.6.2
|
||||
use-callback-ref: 1.3.1(@types/react@18.2.59)(react@18.2.0)
|
||||
use-sidecar: 1.1.2(@types/react@18.2.59)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-remove-scroll@2.5.9(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-bvHCLBrFfM2OgcrpPY2YW84sPdS2o2HKWJUf1xGyGLnSoEnOTOBpahIarjRuYtN0ryahCeP242yf+5TrBX/pZA==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
@ -11697,10 +11449,10 @@ packages:
|
||||
dependencies:
|
||||
'@types/react': 18.2.73
|
||||
react: 18.2.0
|
||||
react-remove-scroll-bar: 2.3.6(@types/react@18.2.73)(react@18.2.0)
|
||||
react-remove-scroll-bar: 2.3.5(@types/react@18.2.73)(react@18.2.0)
|
||||
react-style-singleton: 2.2.1(@types/react@18.2.73)(react@18.2.0)
|
||||
tslib: 2.6.2
|
||||
use-callback-ref: 1.3.2(@types/react@18.2.73)(react@18.2.0)
|
||||
use-callback-ref: 1.3.1(@types/react@18.2.73)(react@18.2.0)
|
||||
use-sidecar: 1.1.2(@types/react@18.2.73)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
@ -11756,23 +11508,6 @@ packages:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/react-style-singleton@2.2.1(@types/react@18.2.59)(react@18.2.0):
|
||||
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.59
|
||||
get-nonce: 1.0.1
|
||||
invariant: 2.2.4
|
||||
react: 18.2.0
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/react-style-singleton@2.2.1(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
||||
engines: {node: '>=10'}
|
||||
@ -13288,24 +13023,9 @@ packages:
|
||||
punycode: 2.3.1
|
||||
dev: true
|
||||
|
||||
/use-callback-ref@1.3.1(@types/react@18.2.59)(react@18.2.0):
|
||||
/use-callback-ref@1.3.1(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.59
|
||||
react: 18.2.0
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/use-callback-ref@1.3.2(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
@ -13358,22 +13078,6 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/use-sidecar@1.1.2(@types/react@18.2.59)(react@18.2.0):
|
||||
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.59
|
||||
detect-node-es: 1.1.0
|
||||
react: 18.2.0
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/use-sidecar@1.1.2(@types/react@18.2.73)(react@18.2.0):
|
||||
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -88,11 +88,13 @@
|
||||
"negativePrompt": "Negative Prompt",
|
||||
"discordLabel": "Discord",
|
||||
"dontAskMeAgain": "Don't ask me again",
|
||||
"editor": "Editor",
|
||||
"error": "Error",
|
||||
"file": "File",
|
||||
"folder": "Folder",
|
||||
"format": "format",
|
||||
"githubLabel": "Github",
|
||||
"goTo": "Go to",
|
||||
"hotkeysLabel": "Hotkeys",
|
||||
"imageFailedToLoad": "Unable to Load Image",
|
||||
"img2img": "Image To Image",
|
||||
@ -140,7 +142,8 @@
|
||||
"blue": "Blue",
|
||||
"alpha": "Alpha",
|
||||
"selected": "Selected",
|
||||
"viewer": "Viewer"
|
||||
"viewer": "Viewer",
|
||||
"tab": "Tab"
|
||||
},
|
||||
"controlnet": {
|
||||
"controlAdapter_one": "Control Adapter",
|
||||
@ -225,7 +228,7 @@
|
||||
"composition": "Composition Only",
|
||||
"safe": "Safe",
|
||||
"saveControlImage": "Save Control Image",
|
||||
"scribble": "scribble",
|
||||
"scribble": "Scribble",
|
||||
"selectModel": "Select a model",
|
||||
"selectCLIPVisionModel": "Select a CLIP Vision model",
|
||||
"setControlImageDimensions": "Copy size to W/H (optimize for model)",
|
||||
@ -361,7 +364,8 @@
|
||||
"bulkDownloadRequestFailed": "Problem Preparing Download",
|
||||
"bulkDownloadFailed": "Download Failed",
|
||||
"problemDeletingImages": "Problem Deleting Images",
|
||||
"problemDeletingImagesDesc": "One or more images could not be deleted"
|
||||
"problemDeletingImagesDesc": "One or more images could not be deleted",
|
||||
"switchTo": "Switch to {{ tab }} (Z)"
|
||||
},
|
||||
"hotkeys": {
|
||||
"searchHotkeys": "Search Hotkeys",
|
||||
@ -584,6 +588,14 @@
|
||||
"upscale": {
|
||||
"desc": "Upscale the current image",
|
||||
"title": "Upscale"
|
||||
},
|
||||
"backToEditor": {
|
||||
"desc": "Closes the Image Viewer and shows the Editor View (Text to Image tab only)",
|
||||
"title": "Back to Editor"
|
||||
},
|
||||
"openImageViewer": {
|
||||
"desc": "Opens the Image Viewer (Text to Image tab only)",
|
||||
"title": "Open Image Viewer"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
@ -917,6 +929,7 @@
|
||||
"missingInputForField": "{{nodeLabel}} -> {{fieldLabel}} missing input",
|
||||
"missingNodeTemplate": "Missing node template",
|
||||
"noControlImageForControlAdapter": "Control Adapter #{{number}} has no control image",
|
||||
"imageNotProcessedForControlAdapter": "Control Adapter #{{number}}'s image is not processed",
|
||||
"noInitialImageSelected": "No initial image selected",
|
||||
"noModelForControlAdapter": "Control Adapter #{{number}} has no model selected.",
|
||||
"incompatibleBaseModelForControlAdapter": "Control Adapter #{{number}} model is incompatible with main model.",
|
||||
@ -1542,6 +1555,25 @@
|
||||
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
||||
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
||||
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
||||
"opacityFilter": "Opacity Filter"
|
||||
"globalInitialImage": "Global Initial Image",
|
||||
"globalInitialImageLayer": "$t(controlLayers.globalInitialImage) $t(unifiedCanvas.layer)",
|
||||
"opacityFilter": "Opacity Filter",
|
||||
"clearProcessor": "Clear Processor",
|
||||
"resetProcessor": "Reset Processor to Defaults",
|
||||
"noLayersAdded": "No Layers Added"
|
||||
},
|
||||
"ui": {
|
||||
"tabs": {
|
||||
"generation": "Generation",
|
||||
"generationTab": "$t(ui.tabs.generation) $t(common.tab)",
|
||||
"canvas": "Canvas",
|
||||
"canvasTab": "$t(ui.tabs.canvas) $t(common.tab)",
|
||||
"workflows": "Workflows",
|
||||
"workflowsTab": "$t(ui.tabs.workflows) $t(common.tab)",
|
||||
"models": "Models",
|
||||
"modelsTab": "$t(ui.tabs.models) $t(common.tab)",
|
||||
"queue": "Queue",
|
||||
"queueTab": "$t(ui.tabs.queue) $t(common.tab)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,7 @@ export type LoggerNamespace =
|
||||
| 'models'
|
||||
| 'config'
|
||||
| 'canvas'
|
||||
| 'txt2img'
|
||||
| 'img2img'
|
||||
| 'generation'
|
||||
| 'nodes'
|
||||
| 'system'
|
||||
| 'socketio'
|
||||
|
@ -16,7 +16,7 @@ import { addCanvasMaskSavedToGalleryListener } from 'app/store/middleware/listen
|
||||
import { addCanvasMaskToControlNetListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet';
|
||||
import { addCanvasMergedListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasMerged';
|
||||
import { addCanvasSavedToGalleryListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery';
|
||||
import { addControlLayersToControlAdapterBridge } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge';
|
||||
import { addControlAdapterPreprocessor } from 'app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor';
|
||||
import { addControlNetAutoProcessListener } from 'app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess';
|
||||
import { addControlNetImageProcessedListener } from 'app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed';
|
||||
import { addEnqueueRequestedCanvasListener } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedCanvas';
|
||||
@ -32,7 +32,6 @@ import { addImagesStarredListener } from 'app/store/middleware/listenerMiddlewar
|
||||
import { addImagesUnstarredListener } from 'app/store/middleware/listenerMiddleware/listeners/imagesUnstarred';
|
||||
import { addImageToDeleteSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected';
|
||||
import { addImageUploadedFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/imageUploaded';
|
||||
import { addInitialImageSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/initialImageSelected';
|
||||
import { addModelSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelSelected';
|
||||
import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded';
|
||||
import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged';
|
||||
@ -73,9 +72,6 @@ const startAppListening = listenerMiddleware.startListening as AppStartListening
|
||||
// Image uploaded
|
||||
addImageUploadedFulfilledListener(startAppListening);
|
||||
|
||||
// Image selected
|
||||
addInitialImageSelectedListener(startAppListening);
|
||||
|
||||
// Image deleted
|
||||
addRequestedSingleImageDeletionListener(startAppListening);
|
||||
addDeleteBoardAndImagesFulfilledListener(startAppListening);
|
||||
@ -158,5 +154,4 @@ addUpscaleRequestedListener(startAppListening);
|
||||
addDynamicPromptsListener(startAppListening);
|
||||
|
||||
addSetDefaultSettingsListener(startAppListening);
|
||||
|
||||
addControlLayersToControlAdapterBridge(startAppListening);
|
||||
addControlAdapterPreprocessor(startAppListening);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||
import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { allLayersDeleted } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {
|
||||
@ -14,19 +14,14 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
|
||||
|
||||
// Remove all deleted images from the UI
|
||||
|
||||
let wasInitialImageReset = false;
|
||||
let wasCanvasReset = false;
|
||||
let wasNodeEditorReset = false;
|
||||
let wereControlAdaptersReset = false;
|
||||
let wereControlLayersReset = false;
|
||||
|
||||
const { generation, canvas, nodes, controlAdapters } = getState();
|
||||
const { canvas, nodes, controlAdapters, controlLayers } = getState();
|
||||
deleted_images.forEach((image_name) => {
|
||||
const imageUsage = getImageUsage(generation, canvas, nodes, controlAdapters, image_name);
|
||||
|
||||
if (imageUsage.isInitialImage && !wasInitialImageReset) {
|
||||
dispatch(clearInitialImage());
|
||||
wasInitialImageReset = true;
|
||||
}
|
||||
const imageUsage = getImageUsage(canvas, nodes, controlAdapters, controlLayers.present, image_name);
|
||||
|
||||
if (imageUsage.isCanvasImage && !wasCanvasReset) {
|
||||
dispatch(resetCanvas());
|
||||
@ -42,6 +37,11 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
|
||||
dispatch(controlAdaptersReset());
|
||||
wereControlAdaptersReset = true;
|
||||
}
|
||||
|
||||
if (imageUsage.isControlLayerImage && !wereControlLayersReset) {
|
||||
dispatch(allLayersDeleted());
|
||||
wereControlLayersReset = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -48,10 +48,12 @@ export const addCanvasImageToControlNetListener = (startAppListening: AppStartLi
|
||||
})
|
||||
).unwrap();
|
||||
|
||||
const { image_name } = imageDTO;
|
||||
|
||||
dispatch(
|
||||
controlAdapterImageChanged({
|
||||
id,
|
||||
controlImage: imageDTO,
|
||||
controlImage: image_name,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
@ -58,10 +58,12 @@ export const addCanvasMaskToControlNetListener = (startAppListening: AppStartLis
|
||||
})
|
||||
).unwrap();
|
||||
|
||||
const { image_name } = imageDTO;
|
||||
|
||||
dispatch(
|
||||
controlAdapterImageChanged({
|
||||
id,
|
||||
controlImage: imageDTO,
|
||||
controlImage: image_name,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
@ -0,0 +1,156 @@
|
||||
import { isAnyOf } from '@reduxjs/toolkit';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import {
|
||||
caLayerImageChanged,
|
||||
caLayerIsProcessingImageChanged,
|
||||
caLayerModelChanged,
|
||||
caLayerProcessedImageChanged,
|
||||
caLayerProcessorConfigChanged,
|
||||
isControlAdapterLayer,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
import { isImageOutput } from 'features/nodes/types/common';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { t } from 'i18next';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
import type { BatchConfig, ImageDTO } from 'services/api/types';
|
||||
import { socketInvocationComplete } from 'services/events/actions';
|
||||
|
||||
const matcher = isAnyOf(caLayerImageChanged, caLayerProcessorConfigChanged, caLayerModelChanged);
|
||||
|
||||
const DEBOUNCE_MS = 300;
|
||||
const log = logger('session');
|
||||
|
||||
export const addControlAdapterPreprocessor = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
matcher,
|
||||
effect: async (action, { dispatch, getState, getOriginalState, cancelActiveListeners, delay, take }) => {
|
||||
const { layerId } = action.payload;
|
||||
const precheckLayerOriginal = getOriginalState()
|
||||
.controlLayers.present.layers.filter(isControlAdapterLayer)
|
||||
.find((l) => l.id === layerId);
|
||||
const precheckLayer = getState()
|
||||
.controlLayers.present.layers.filter(isControlAdapterLayer)
|
||||
.find((l) => l.id === layerId);
|
||||
|
||||
// Conditions to bail
|
||||
const layerDoesNotExist = !precheckLayer;
|
||||
const layerHasNoImage = !precheckLayer?.controlAdapter.image;
|
||||
const layerHasNoProcessorConfig = !precheckLayer?.controlAdapter.processorConfig;
|
||||
const layerIsAlreadyProcessingImage = precheckLayer?.controlAdapter.isProcessingImage;
|
||||
const areImageAndProcessorUnchanged =
|
||||
isEqual(precheckLayer?.controlAdapter.image, precheckLayerOriginal?.controlAdapter.image) &&
|
||||
isEqual(precheckLayer?.controlAdapter.processorConfig, precheckLayerOriginal?.controlAdapter.processorConfig);
|
||||
|
||||
if (
|
||||
layerDoesNotExist ||
|
||||
layerHasNoImage ||
|
||||
layerHasNoProcessorConfig ||
|
||||
areImageAndProcessorUnchanged ||
|
||||
layerIsAlreadyProcessingImage
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any in-progress instances of this listener
|
||||
cancelActiveListeners();
|
||||
log.trace('Control Layer CA auto-process triggered');
|
||||
|
||||
// Delay before starting actual work
|
||||
await delay(DEBOUNCE_MS);
|
||||
dispatch(caLayerIsProcessingImageChanged({ layerId, isProcessingImage: true }));
|
||||
|
||||
// Double-check that we are still eligible for processing
|
||||
const state = getState();
|
||||
const layer = state.controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||
const image = layer?.controlAdapter.image;
|
||||
const config = layer?.controlAdapter.processorConfig;
|
||||
|
||||
// If we have no image or there is no processor config, bail
|
||||
if (!layer || !image || !config) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error: TS isn't able to narrow the typing of buildNode and `config` will error...
|
||||
const processorNode = CA_PROCESSOR_DATA[config.type].buildNode(image, config);
|
||||
const enqueueBatchArg: BatchConfig = {
|
||||
prepend: true,
|
||||
batch: {
|
||||
graph: {
|
||||
nodes: {
|
||||
[processorNode.id]: { ...processorNode, is_intermediate: true },
|
||||
},
|
||||
edges: [],
|
||||
},
|
||||
runs: 1,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const req = dispatch(
|
||||
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, {
|
||||
fixedCacheKey: 'enqueueBatch',
|
||||
})
|
||||
);
|
||||
const enqueueResult = await req.unwrap();
|
||||
req.reset();
|
||||
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
||||
|
||||
const [invocationCompleteAction] = await take(
|
||||
(action): action is ReturnType<typeof socketInvocationComplete> =>
|
||||
socketInvocationComplete.match(action) &&
|
||||
action.payload.data.queue_batch_id === enqueueResult.batch.batch_id &&
|
||||
action.payload.data.source_node_id === processorNode.id
|
||||
);
|
||||
|
||||
// We still have to check the output type
|
||||
if (isImageOutput(invocationCompleteAction.payload.data.result)) {
|
||||
const { image_name } = invocationCompleteAction.payload.data.result.image;
|
||||
|
||||
// Wait for the ImageDTO to be received
|
||||
const [{ payload }] = await take(
|
||||
(action) =>
|
||||
imagesApi.endpoints.getImageDTO.matchFulfilled(action) && action.payload.image_name === image_name
|
||||
);
|
||||
|
||||
const imageDTO = payload as ImageDTO;
|
||||
|
||||
log.debug({ layerId, imageDTO }, 'ControlNet image processed');
|
||||
|
||||
// Update the processed image in the store
|
||||
dispatch(
|
||||
caLayerProcessedImageChanged({
|
||||
layerId,
|
||||
imageDTO,
|
||||
})
|
||||
);
|
||||
dispatch(caLayerIsProcessingImageChanged({ layerId, isProcessingImage: false }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
||||
dispatch(caLayerIsProcessingImageChanged({ layerId, isProcessingImage: false }));
|
||||
|
||||
if (error instanceof Object) {
|
||||
if ('data' in error && 'status' in error) {
|
||||
if (error.status === 403) {
|
||||
dispatch(caLayerImageChanged({ layerId, imageDTO: null }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.graphFailedToQueue'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
@ -1,144 +0,0 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
|
||||
import { controlAdapterAdded, controlAdapterRemoved } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import type { ControlNetConfig, IPAdapterConfig } from 'features/controlAdapters/store/types';
|
||||
import { isControlAdapterProcessorType } from 'features/controlAdapters/store/types';
|
||||
import {
|
||||
controlAdapterLayerAdded,
|
||||
ipAdapterLayerAdded,
|
||||
layerDeleted,
|
||||
maskLayerIPAdapterAdded,
|
||||
maskLayerIPAdapterDeleted,
|
||||
regionalGuidanceLayerAdded,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
||||
import { isControlNetModelConfig, isIPAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export const guidanceLayerAdded = createAction<Layer['type']>('controlLayers/guidanceLayerAdded');
|
||||
export const guidanceLayerDeleted = createAction<string>('controlLayers/guidanceLayerDeleted');
|
||||
export const allLayersDeleted = createAction('controlLayers/allLayersDeleted');
|
||||
export const guidanceLayerIPAdapterAdded = createAction<string>('controlLayers/guidanceLayerIPAdapterAdded');
|
||||
export const guidanceLayerIPAdapterDeleted = createAction<{ layerId: string; ipAdapterId: string }>(
|
||||
'controlLayers/guidanceLayerIPAdapterDeleted'
|
||||
);
|
||||
|
||||
export const addControlLayersToControlAdapterBridge = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: guidanceLayerAdded,
|
||||
effect: (action, { dispatch, getState }) => {
|
||||
const type = action.payload;
|
||||
const layerId = uuidv4();
|
||||
if (type === 'regional_guidance_layer') {
|
||||
dispatch(regionalGuidanceLayerAdded({ layerId }));
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const baseModel = state.generation.model?.base;
|
||||
const modelConfigs = modelsApi.endpoints.getModelConfigs.select(undefined)(state).data;
|
||||
|
||||
if (type === 'ip_adapter_layer') {
|
||||
const ipAdapterId = uuidv4();
|
||||
const overrides: Partial<IPAdapterConfig> = {
|
||||
id: ipAdapterId,
|
||||
};
|
||||
|
||||
// Find and select the first matching model
|
||||
if (modelConfigs) {
|
||||
const models = modelConfigsAdapterSelectors.selectAll(modelConfigs).filter(isIPAdapterModelConfig);
|
||||
overrides.model = models.find((m) => m.base === baseModel) ?? null;
|
||||
}
|
||||
dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides }));
|
||||
dispatch(ipAdapterLayerAdded({ layerId, ipAdapterId }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'control_adapter_layer') {
|
||||
const controlNetId = uuidv4();
|
||||
const overrides: Partial<ControlNetConfig> = {
|
||||
id: controlNetId,
|
||||
};
|
||||
|
||||
// Find and select the first matching model
|
||||
if (modelConfigs) {
|
||||
const models = modelConfigsAdapterSelectors.selectAll(modelConfigs).filter(isControlNetModelConfig);
|
||||
const model = models.find((m) => m.base === baseModel) ?? null;
|
||||
overrides.model = model;
|
||||
const defaultPreprocessor = model?.default_settings?.preprocessor;
|
||||
overrides.processorType = isControlAdapterProcessorType(defaultPreprocessor) ? defaultPreprocessor : 'none';
|
||||
overrides.processorNode = CONTROLNET_PROCESSORS[overrides.processorType].buildDefaults(baseModel);
|
||||
}
|
||||
dispatch(controlAdapterAdded({ type: 'controlnet', overrides }));
|
||||
dispatch(controlAdapterLayerAdded({ layerId, controlNetId }));
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
startAppListening({
|
||||
actionCreator: guidanceLayerDeleted,
|
||||
effect: (action, { getState, dispatch }) => {
|
||||
const layerId = action.payload;
|
||||
const state = getState();
|
||||
const layer = state.controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
|
||||
if (layer.type === 'ip_adapter_layer') {
|
||||
dispatch(controlAdapterRemoved({ id: layer.ipAdapterId }));
|
||||
} else if (layer.type === 'control_adapter_layer') {
|
||||
dispatch(controlAdapterRemoved({ id: layer.controlNetId }));
|
||||
} else if (layer.type === 'regional_guidance_layer') {
|
||||
for (const ipAdapterId of layer.ipAdapterIds) {
|
||||
dispatch(controlAdapterRemoved({ id: ipAdapterId }));
|
||||
}
|
||||
}
|
||||
dispatch(layerDeleted(layerId));
|
||||
},
|
||||
});
|
||||
|
||||
startAppListening({
|
||||
actionCreator: allLayersDeleted,
|
||||
effect: (action, { dispatch, getOriginalState }) => {
|
||||
const state = getOriginalState();
|
||||
for (const layer of state.controlLayers.present.layers) {
|
||||
dispatch(guidanceLayerDeleted(layer.id));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
startAppListening({
|
||||
actionCreator: guidanceLayerIPAdapterAdded,
|
||||
effect: (action, { dispatch, getState }) => {
|
||||
const layerId = action.payload;
|
||||
const ipAdapterId = uuidv4();
|
||||
const overrides: Partial<IPAdapterConfig> = {
|
||||
id: ipAdapterId,
|
||||
};
|
||||
|
||||
// Find and select the first matching model
|
||||
const state = getState();
|
||||
const baseModel = state.generation.model?.base;
|
||||
const modelConfigs = modelsApi.endpoints.getModelConfigs.select(undefined)(state).data;
|
||||
if (modelConfigs) {
|
||||
const models = modelConfigsAdapterSelectors.selectAll(modelConfigs).filter(isIPAdapterModelConfig);
|
||||
overrides.model = models.find((m) => m.base === baseModel) ?? null;
|
||||
}
|
||||
|
||||
dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides }));
|
||||
dispatch(maskLayerIPAdapterAdded({ layerId, ipAdapterId }));
|
||||
},
|
||||
});
|
||||
|
||||
startAppListening({
|
||||
actionCreator: guidanceLayerIPAdapterDeleted,
|
||||
effect: (action, { dispatch }) => {
|
||||
const { layerId, ipAdapterId } = action.payload;
|
||||
dispatch(controlAdapterRemoved({ id: ipAdapterId }));
|
||||
dispatch(maskLayerIPAdapterDeleted({ layerId, ipAdapterId }));
|
||||
},
|
||||
});
|
||||
};
|
@ -12,7 +12,6 @@ import {
|
||||
selectControlAdapterById,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
type AnyControlAdapterParamChangeAction =
|
||||
| ReturnType<typeof controlAdapterProcessorParamsChanged>
|
||||
@ -53,11 +52,6 @@ const predicate: AnyListenerPredicate<RootState> = (action, state, prevState) =>
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prevCA.controlImage === ca.controlImage && isEqual(prevCA.processorNode, ca.processorNode)) {
|
||||
// Don't re-process if the processor hasn't changed
|
||||
return false;
|
||||
}
|
||||
|
||||
const isProcessorSelected = processorType !== 'none';
|
||||
|
||||
const hasControlImage = Boolean(controlImage);
|
||||
|
@ -91,7 +91,7 @@ export const addControlNetImageProcessedListener = (startAppListening: AppStartL
|
||||
dispatch(
|
||||
controlAdapterProcessedImageChanged({
|
||||
id,
|
||||
processedControlImage,
|
||||
processedControlImage: processedControlImage.image_name,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ import type { ImageDTO } from 'services/api/types';
|
||||
export const addEnqueueRequestedCanvasListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||
enqueueRequested.match(action) && action.payload.tabName === 'unifiedCanvas',
|
||||
enqueueRequested.match(action) && action.payload.tabName === 'canvas',
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
const log = logger('queue');
|
||||
const { prepend } = action.payload;
|
||||
|
@ -1,16 +1,14 @@
|
||||
import { enqueueRequested } from 'app/store/actions';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { buildGenerationTabGraph } from 'features/nodes/util/graph/buildGenerationTabGraph';
|
||||
import { buildGenerationTabSDXLGraph } from 'features/nodes/util/graph/buildGenerationTabSDXLGraph';
|
||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||
import { buildLinearImageToImageGraph } from 'features/nodes/util/graph/buildLinearImageToImageGraph';
|
||||
import { buildLinearSDXLImageToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLImageToImageGraph';
|
||||
import { buildLinearSDXLTextToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLTextToImageGraph';
|
||||
import { buildLinearTextToImageGraph } from 'features/nodes/util/graph/buildLinearTextToImageGraph';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
|
||||
export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||
enqueueRequested.match(action) && (action.payload.tabName === 'txt2img' || action.payload.tabName === 'img2img'),
|
||||
enqueueRequested.match(action) && action.payload.tabName === 'generation',
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
const state = getState();
|
||||
const model = state.generation.model;
|
||||
@ -19,17 +17,9 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
||||
let graph;
|
||||
|
||||
if (model && model.base === 'sdxl') {
|
||||
if (action.payload.tabName === 'txt2img') {
|
||||
graph = await buildLinearSDXLTextToImageGraph(state);
|
||||
} else {
|
||||
graph = await buildLinearSDXLImageToImageGraph(state);
|
||||
}
|
||||
graph = await buildGenerationTabSDXLGraph(state);
|
||||
} else {
|
||||
if (action.payload.tabName === 'txt2img') {
|
||||
graph = await buildLinearTextToImageGraph(state);
|
||||
} else {
|
||||
graph = await buildLinearImageToImageGraph(state);
|
||||
}
|
||||
graph = await buildGenerationTabGraph(state);
|
||||
}
|
||||
|
||||
const batchConfig = prepareLinearUIBatch(state, graph, prepend);
|
||||
|
@ -8,7 +8,7 @@ import type { BatchConfig } from 'services/api/types';
|
||||
export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||
enqueueRequested.match(action) && action.payload.tabName === 'nodes',
|
||||
enqueueRequested.match(action) && action.payload.tabName === 'workflows',
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
const state = getState();
|
||||
const { nodes, edges } = state.nodes;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import type { AppDispatch, RootState } from 'app/store/store';
|
||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||
import {
|
||||
controlAdapterImageChanged,
|
||||
@ -7,6 +8,13 @@ import {
|
||||
selectControlAdapterAll,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||
import {
|
||||
isControlAdapterLayer,
|
||||
isInitialImageLayer,
|
||||
isIPAdapterLayer,
|
||||
isRegionalGuidanceLayer,
|
||||
layerDeleted,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||
@ -14,12 +22,82 @@ import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { isImageFieldInputInstance } from 'features/nodes/types/field';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||
import { clamp, forEach } from 'lodash-es';
|
||||
import { api } from 'services/api';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import { imagesSelectors } from 'services/api/util';
|
||||
|
||||
const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||
state.nodes.nodes.forEach((node) => {
|
||||
if (!isInvocationNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
forEach(node.data.inputs, (input) => {
|
||||
if (isImageFieldInputInstance(input) && input.value?.image_name === imageDTO.image_name) {
|
||||
dispatch(
|
||||
fieldImageValueChanged({
|
||||
nodeId: node.data.id,
|
||||
fieldName: input.name,
|
||||
value: undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||
forEach(selectControlAdapterAll(state.controlAdapters), (ca) => {
|
||||
if (
|
||||
ca.controlImage === imageDTO.image_name ||
|
||||
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
||||
) {
|
||||
dispatch(
|
||||
controlAdapterImageChanged({
|
||||
id: ca.id,
|
||||
controlImage: null,
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
controlAdapterProcessedImageChanged({
|
||||
id: ca.id,
|
||||
processedControlImage: null,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const deleteControlLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||
state.controlLayers.present.layers.forEach((l) => {
|
||||
if (isRegionalGuidanceLayer(l)) {
|
||||
if (l.ipAdapters.some((ipa) => ipa.image?.imageName === imageDTO.image_name)) {
|
||||
dispatch(layerDeleted(l.id));
|
||||
}
|
||||
}
|
||||
if (isControlAdapterLayer(l)) {
|
||||
if (
|
||||
l.controlAdapter.image?.imageName === imageDTO.image_name ||
|
||||
l.controlAdapter.processedImage?.imageName === imageDTO.image_name
|
||||
) {
|
||||
dispatch(layerDeleted(l.id));
|
||||
}
|
||||
}
|
||||
if (isIPAdapterLayer(l)) {
|
||||
if (l.ipAdapter.image?.imageName === imageDTO.image_name) {
|
||||
dispatch(layerDeleted(l.id));
|
||||
}
|
||||
}
|
||||
if (isInitialImageLayer(l)) {
|
||||
if (l.image?.imageName === imageDTO.image_name) {
|
||||
dispatch(layerDeleted(l.id));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const addRequestedSingleImageDeletionListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: imageDeletionConfirmed,
|
||||
@ -73,50 +151,9 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
|
||||
}
|
||||
|
||||
imageDTOs.forEach((imageDTO) => {
|
||||
// reset init image if we deleted it
|
||||
if (getState().generation.initialImage?.imageName === imageDTO.image_name) {
|
||||
dispatch(clearInitialImage());
|
||||
}
|
||||
|
||||
// reset control adapters that use the deleted images
|
||||
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
|
||||
if (
|
||||
ca.controlImage === imageDTO.image_name ||
|
||||
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
||||
) {
|
||||
dispatch(
|
||||
controlAdapterImageChanged({
|
||||
id: ca.id,
|
||||
controlImage: null,
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
controlAdapterProcessedImageChanged({
|
||||
id: ca.id,
|
||||
processedControlImage: null,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// reset nodes that use the deleted images
|
||||
getState().nodes.nodes.forEach((node) => {
|
||||
if (!isInvocationNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
forEach(node.data.inputs, (input) => {
|
||||
if (isImageFieldInputInstance(input) && input.value?.image_name === imageDTO.image_name) {
|
||||
dispatch(
|
||||
fieldImageValueChanged({
|
||||
nodeId: node.data.id,
|
||||
fieldName: input.name,
|
||||
value: undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||
deleteNodesImages(state, dispatch, imageDTO);
|
||||
deleteControlLayerImages(state, dispatch, imageDTO);
|
||||
});
|
||||
|
||||
// Delete from server
|
||||
@ -168,50 +205,9 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
|
||||
}
|
||||
|
||||
imageDTOs.forEach((imageDTO) => {
|
||||
// reset init image if we deleted it
|
||||
if (getState().generation.initialImage?.imageName === imageDTO.image_name) {
|
||||
dispatch(clearInitialImage());
|
||||
}
|
||||
|
||||
// reset control adapters that use the deleted images
|
||||
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
|
||||
if (
|
||||
ca.controlImage === imageDTO.image_name ||
|
||||
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
||||
) {
|
||||
dispatch(
|
||||
controlAdapterImageChanged({
|
||||
id: ca.id,
|
||||
controlImage: null,
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
controlAdapterProcessedImageChanged({
|
||||
id: ca.id,
|
||||
processedControlImage: null,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// reset nodes that use the deleted images
|
||||
getState().nodes.nodes.forEach((node) => {
|
||||
if (!isInvocationNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
forEach(node.data.inputs, (input) => {
|
||||
if (isImageFieldInputInstance(input) && input.value?.image_name === imageDTO.image_name) {
|
||||
dispatch(
|
||||
fieldImageValueChanged({
|
||||
nodeId: node.data.id,
|
||||
fieldName: input.name,
|
||||
value: undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||
deleteNodesImages(state, dispatch, imageDTO);
|
||||
deleteControlLayerImages(state, dispatch, imageDTO);
|
||||
});
|
||||
} catch {
|
||||
// no-op
|
||||
|
@ -7,10 +7,16 @@ import {
|
||||
controlAdapterImageChanged,
|
||||
controlAdapterIsEnabledChanged,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import {
|
||||
caLayerImageChanged,
|
||||
iiLayerImageChanged,
|
||||
ipaLayerImageChanged,
|
||||
rgLayerIPAdapterImageChanged,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { initialImageChanged, selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
export const dndDropped = createAction<{
|
||||
@ -47,18 +53,6 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on initial image
|
||||
*/
|
||||
if (
|
||||
overData.actionType === 'SET_INITIAL_IMAGE' &&
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
dispatch(initialImageChanged(activeData.payload.imageDTO));
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on ControlNet
|
||||
*/
|
||||
@ -71,7 +65,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
dispatch(
|
||||
controlAdapterImageChanged({
|
||||
id,
|
||||
controlImage: activeData.payload.imageDTO,
|
||||
controlImage: activeData.payload.imageDTO.image_name,
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
@ -83,6 +77,79 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on Control Adapter Layer
|
||||
*/
|
||||
if (
|
||||
overData.actionType === 'SET_CA_LAYER_IMAGE' &&
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
const { layerId } = overData.context;
|
||||
dispatch(
|
||||
caLayerImageChanged({
|
||||
layerId,
|
||||
imageDTO: activeData.payload.imageDTO,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on IP Adapter Layer
|
||||
*/
|
||||
if (
|
||||
overData.actionType === 'SET_IPA_LAYER_IMAGE' &&
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
const { layerId } = overData.context;
|
||||
dispatch(
|
||||
ipaLayerImageChanged({
|
||||
layerId,
|
||||
imageDTO: activeData.payload.imageDTO,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on RG Layer IP Adapter
|
||||
*/
|
||||
if (
|
||||
overData.actionType === 'SET_RG_LAYER_IP_ADAPTER_IMAGE' &&
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
const { layerId, ipAdapterId } = overData.context;
|
||||
dispatch(
|
||||
rgLayerIPAdapterImageChanged({
|
||||
layerId,
|
||||
ipAdapterId,
|
||||
imageDTO: activeData.payload.imageDTO,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on II Layer Image
|
||||
*/
|
||||
if (
|
||||
overData.actionType === 'SET_II_LAYER_IMAGE' &&
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
const { layerId } = overData.context;
|
||||
dispatch(
|
||||
iiLayerImageChanged({
|
||||
layerId,
|
||||
imageDTO: activeData.payload.imageDTO,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on Canvas
|
||||
*/
|
||||
|
@ -14,7 +14,6 @@ export const addImageToDeleteSelectedListener = (startAppListening: AppStartList
|
||||
|
||||
const isImageInUse =
|
||||
imagesUsage.some((i) => i.isCanvasImage) ||
|
||||
imagesUsage.some((i) => i.isInitialImage) ||
|
||||
imagesUsage.some((i) => i.isControlImage) ||
|
||||
imagesUsage.some((i) => i.isNodesImage);
|
||||
|
||||
|
@ -6,8 +6,14 @@ import {
|
||||
controlAdapterImageChanged,
|
||||
controlAdapterIsEnabledChanged,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import {
|
||||
caLayerImageChanged,
|
||||
iiLayerImageChanged,
|
||||
ipaLayerImageChanged,
|
||||
rgLayerIPAdapterImageChanged,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { initialImageChanged, selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { t } from 'i18next';
|
||||
import { omit } from 'lodash-es';
|
||||
@ -96,7 +102,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
dispatch(
|
||||
controlAdapterImageChanged({
|
||||
id,
|
||||
controlImage: imageDTO,
|
||||
controlImage: imageDTO.image_name,
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
@ -108,15 +114,48 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
return;
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_INITIAL_IMAGE') {
|
||||
dispatch(initialImageChanged(imageDTO));
|
||||
if (postUploadAction?.type === 'SET_CA_LAYER_IMAGE') {
|
||||
const { layerId } = postUploadAction;
|
||||
dispatch(caLayerImageChanged({ layerId, imageDTO }));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setInitialImage'),
|
||||
description: t('toast.setControlImage'),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_IPA_LAYER_IMAGE') {
|
||||
const { layerId } = postUploadAction;
|
||||
dispatch(ipaLayerImageChanged({ layerId, imageDTO }));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_RG_LAYER_IP_ADAPTER_IMAGE') {
|
||||
const { layerId, ipAdapterId } = postUploadAction;
|
||||
dispatch(rgLayerIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO }));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_II_LAYER_IMAGE') {
|
||||
const { layerId } = postUploadAction;
|
||||
dispatch(iiLayerImageChanged({ layerId, imageDTO }));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
||||
|
@ -1,21 +0,0 @@
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { t } from 'i18next';
|
||||
|
||||
export const addInitialImageSelectedListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: initialImageSelected,
|
||||
effect: (action, { dispatch }) => {
|
||||
if (!action.payload) {
|
||||
dispatch(addToast(makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' })));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(initialImageChanged(action.payload));
|
||||
dispatch(addToast(makeToast(t('toast.sentToImageToImage'))));
|
||||
},
|
||||
});
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import {
|
||||
controlAdapterModelChanged,
|
||||
controlAdapterIsEnabledChanged,
|
||||
selectControlAdapterAll,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||
@ -54,7 +54,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
||||
// handle incompatible controlnets
|
||||
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
|
||||
if (ca.model?.base !== newBaseModel) {
|
||||
dispatch(controlAdapterModelChanged({ id: ca.id, modelConfig: null }));
|
||||
dispatch(controlAdapterIsEnabledChanged({ id: ca.id, isEnabled: false }));
|
||||
modelsCleared += 1;
|
||||
}
|
||||
});
|
||||
|
@ -96,16 +96,16 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
|
||||
dispatch(setScheduler(scheduler));
|
||||
}
|
||||
}
|
||||
|
||||
const setSizeOptions = { updateAspectRatio: true, clamp: true };
|
||||
if (width) {
|
||||
if (isParameterWidth(width)) {
|
||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
||||
dispatch(widthChanged({ width, ...setSizeOptions }));
|
||||
}
|
||||
}
|
||||
|
||||
if (height) {
|
||||
if (isParameterHeight(height)) {
|
||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
||||
dispatch(heightChanged({ height, ...setSizeOptions }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,14 +17,10 @@ const accept: Accept = {
|
||||
const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (activeTabName) => {
|
||||
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
||||
|
||||
if (activeTabName === 'unifiedCanvas') {
|
||||
if (activeTabName === 'canvas') {
|
||||
postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' };
|
||||
}
|
||||
|
||||
if (activeTabName === 'img2img') {
|
||||
postUploadAction = { type: 'SET_INITIAL_IMAGE' };
|
||||
}
|
||||
|
||||
return postUploadAction;
|
||||
});
|
||||
|
||||
|
@ -67,7 +67,7 @@ export const useGlobalHotkeys = () => {
|
||||
useHotkeys(
|
||||
'1',
|
||||
() => {
|
||||
dispatch(setActiveTab('txt2img'));
|
||||
dispatch(setActiveTab('generation'));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
@ -75,7 +75,7 @@ export const useGlobalHotkeys = () => {
|
||||
useHotkeys(
|
||||
'2',
|
||||
() => {
|
||||
dispatch(setActiveTab('img2img'));
|
||||
dispatch(setActiveTab('canvas'));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
@ -83,31 +83,23 @@ export const useGlobalHotkeys = () => {
|
||||
useHotkeys(
|
||||
'3',
|
||||
() => {
|
||||
dispatch(setActiveTab('unifiedCanvas'));
|
||||
dispatch(setActiveTab('workflows'));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'4',
|
||||
() => {
|
||||
dispatch(setActiveTab('nodes'));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'5',
|
||||
() => {
|
||||
if (isModelManagerEnabled) {
|
||||
dispatch(setActiveTab('modelManager'));
|
||||
dispatch(setActiveTab('models'));
|
||||
}
|
||||
},
|
||||
[dispatch, isModelManagerEnabled]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
isModelManagerEnabled ? '6' : '5',
|
||||
isModelManagerEnabled ? '5' : '4',
|
||||
() => {
|
||||
dispatch(setActiveTab('queue'));
|
||||
},
|
||||
|
@ -28,7 +28,7 @@ const selector = createMemoizedSelector(
|
||||
activeTabNameSelector,
|
||||
],
|
||||
(controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => {
|
||||
const { initialImage, model } = generation;
|
||||
const { model } = generation;
|
||||
const { positivePrompt } = controlLayers.present;
|
||||
|
||||
const { isConnected } = system;
|
||||
@ -40,11 +40,7 @@ const selector = createMemoizedSelector(
|
||||
reasons.push(i18n.t('parameters.invoke.systemDisconnected'));
|
||||
}
|
||||
|
||||
if (activeTabName === 'img2img' && !initialImage) {
|
||||
reasons.push(i18n.t('parameters.invoke.noInitialImageSelected'));
|
||||
}
|
||||
|
||||
if (activeTabName === 'nodes') {
|
||||
if (activeTabName === 'workflows') {
|
||||
if (nodes.shouldValidateGraph) {
|
||||
if (!nodes.nodes.length) {
|
||||
reasons.push(i18n.t('parameters.invoke.noNodesInGraph'));
|
||||
@ -97,71 +93,93 @@ const selector = createMemoizedSelector(
|
||||
reasons.push(i18n.t('parameters.invoke.noModelSelected'));
|
||||
}
|
||||
|
||||
let enabledControlAdapters = selectControlAdapterAll(controlAdapters).filter((ca) => ca.isEnabled);
|
||||
|
||||
if (activeTabName === 'txt2img') {
|
||||
// Special handling for control layers on txt2img
|
||||
const enabledControlLayersAdapterIds = controlLayers.present.layers
|
||||
if (activeTabName === 'generation') {
|
||||
// Handling for generation tab
|
||||
controlLayers.present.layers
|
||||
.filter((l) => l.isEnabled)
|
||||
.flatMap((layer) => {
|
||||
if (layer.type === 'regional_guidance_layer') {
|
||||
return layer.ipAdapterIds;
|
||||
.flatMap((l) => {
|
||||
if (l.type === 'control_adapter_layer') {
|
||||
return l.controlAdapter;
|
||||
} else if (l.type === 'ip_adapter_layer') {
|
||||
return l.ipAdapter;
|
||||
} else if (l.type === 'regional_guidance_layer') {
|
||||
return l.ipAdapters;
|
||||
}
|
||||
if (layer.type === 'control_adapter_layer') {
|
||||
return [layer.controlNetId];
|
||||
return [];
|
||||
})
|
||||
.forEach((ca, i) => {
|
||||
const hasNoModel = !ca.model;
|
||||
const mismatchedModelBase = ca.model?.base !== model?.base;
|
||||
const hasNoImage = !ca.image;
|
||||
const imageNotProcessed =
|
||||
(ca.type === 'controlnet' || ca.type === 't2i_adapter') && !ca.processedImage && ca.processorConfig;
|
||||
|
||||
if (hasNoModel) {
|
||||
reasons.push(
|
||||
i18n.t('parameters.invoke.noModelForControlAdapter', {
|
||||
number: i + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (layer.type === 'ip_adapter_layer') {
|
||||
return [layer.ipAdapterId];
|
||||
if (mismatchedModelBase) {
|
||||
// This should never happen, just a sanity check
|
||||
reasons.push(
|
||||
i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', {
|
||||
number: i + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (hasNoImage) {
|
||||
reasons.push(
|
||||
i18n.t('parameters.invoke.noControlImageForControlAdapter', {
|
||||
number: i + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (imageNotProcessed) {
|
||||
reasons.push(
|
||||
i18n.t('parameters.invoke.imageNotProcessedForControlAdapter', {
|
||||
number: i + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
enabledControlAdapters = enabledControlAdapters.filter((ca) => enabledControlLayersAdapterIds.includes(ca.id));
|
||||
} else {
|
||||
const allControlLayerAdapterIds = controlLayers.present.layers.flatMap((layer) => {
|
||||
if (layer.type === 'regional_guidance_layer') {
|
||||
return layer.ipAdapterIds;
|
||||
}
|
||||
if (layer.type === 'control_adapter_layer') {
|
||||
return [layer.controlNetId];
|
||||
}
|
||||
if (layer.type === 'ip_adapter_layer') {
|
||||
return [layer.ipAdapterId];
|
||||
}
|
||||
});
|
||||
enabledControlAdapters = enabledControlAdapters.filter((ca) => !allControlLayerAdapterIds.includes(ca.id));
|
||||
// Handling for all other tabs
|
||||
selectControlAdapterAll(controlAdapters)
|
||||
.filter((ca) => ca.isEnabled)
|
||||
.forEach((ca, i) => {
|
||||
if (!ca.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ca.model) {
|
||||
reasons.push(
|
||||
i18n.t('parameters.invoke.noModelForControlAdapter', {
|
||||
number: i + 1,
|
||||
})
|
||||
);
|
||||
} else if (ca.model.base !== model?.base) {
|
||||
// This should never happen, just a sanity check
|
||||
reasons.push(
|
||||
i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', {
|
||||
number: i + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!ca.controlImage ||
|
||||
(isControlNetOrT2IAdapter(ca) && !ca.processedControlImage && ca.processorType !== 'none')
|
||||
) {
|
||||
reasons.push(
|
||||
i18n.t('parameters.invoke.noControlImageForControlAdapter', {
|
||||
number: i + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
enabledControlAdapters.forEach((ca, i) => {
|
||||
if (!ca.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ca.model) {
|
||||
reasons.push(
|
||||
i18n.t('parameters.invoke.noModelForControlAdapter', {
|
||||
number: i + 1,
|
||||
})
|
||||
);
|
||||
} else if (ca.model.base !== model?.base) {
|
||||
// This should never happen, just a sanity check
|
||||
reasons.push(
|
||||
i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', {
|
||||
number: i + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!ca.controlImage ||
|
||||
(isControlNetOrT2IAdapter(ca) && !ca.processedControlImage && ca.processorType !== 'none')
|
||||
) {
|
||||
reasons.push(
|
||||
i18n.t('parameters.invoke.noControlImageForControlAdapter', {
|
||||
number: i + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { isReady: !reasons.length, reasons };
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
|
||||
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
|
||||
import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -219,97 +220,107 @@ const IAICanvasToolbar = () => {
|
||||
const value = useMemo(() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0], [layer]);
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" gap={2} flexWrap="wrap">
|
||||
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
|
||||
<FormControl isDisabled={isStaging} w="5rem">
|
||||
<Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} />
|
||||
</FormControl>
|
||||
</Tooltip>
|
||||
<Flex w="full" gap={2} alignItems="center">
|
||||
<Flex flex={1} justifyContent="center">
|
||||
<Flex gap={2} marginInlineEnd="auto" />
|
||||
</Flex>
|
||||
<Flex flex={1} gap={2} justifyContent="center">
|
||||
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
|
||||
<FormControl isDisabled={isStaging} w="5rem">
|
||||
<Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} />
|
||||
</FormControl>
|
||||
</Tooltip>
|
||||
|
||||
<IAICanvasMaskOptions />
|
||||
<IAICanvasToolChooserOptions />
|
||||
<IAICanvasMaskOptions />
|
||||
<IAICanvasToolChooserOptions />
|
||||
|
||||
<ButtonGroup>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.move')} (V)`}
|
||||
tooltip={`${t('unifiedCanvas.move')} (V)`}
|
||||
icon={<PiHandGrabbingBold />}
|
||||
isChecked={tool === 'move' || isStaging}
|
||||
onClick={handleSelectMoveTool}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={`${shouldShowBoundingBox ? t('unifiedCanvas.hideBoundingBox') : t('unifiedCanvas.showBoundingBox')} (Shift + H)`}
|
||||
tooltip={`${shouldShowBoundingBox ? t('unifiedCanvas.hideBoundingBox') : t('unifiedCanvas.showBoundingBox')} (Shift + H)`}
|
||||
icon={shouldShowBoundingBox ? <PiEyeBold /> : <PiEyeSlashBold />}
|
||||
onClick={handleSetShouldShowBoundingBox}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.resetView')} (R)`}
|
||||
tooltip={`${t('unifiedCanvas.resetView')} (R)`}
|
||||
icon={<PiCrosshairSimpleBold />}
|
||||
onClick={handleClickResetCanvasView}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.mergeVisible')} (Shift+M)`}
|
||||
tooltip={`${t('unifiedCanvas.mergeVisible')} (Shift+M)`}
|
||||
icon={<PiStackBold />}
|
||||
onClick={handleMergeVisible}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
|
||||
tooltip={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
|
||||
icon={<PiFloppyDiskBold />}
|
||||
onClick={handleSaveToGallery}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
{isClipboardAPIAvailable && (
|
||||
<ButtonGroup>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.copyToClipboard')} (Cmd/Ctrl+C)`}
|
||||
tooltip={`${t('unifiedCanvas.copyToClipboard')} (Cmd/Ctrl+C)`}
|
||||
icon={<PiCopyBold />}
|
||||
onClick={handleCopyImageToClipboard}
|
||||
aria-label={`${t('unifiedCanvas.move')} (V)`}
|
||||
tooltip={`${t('unifiedCanvas.move')} (V)`}
|
||||
icon={<PiHandGrabbingBold />}
|
||||
isChecked={tool === 'move' || isStaging}
|
||||
onClick={handleSelectMoveTool}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={`${shouldShowBoundingBox ? t('unifiedCanvas.hideBoundingBox') : t('unifiedCanvas.showBoundingBox')} (Shift + H)`}
|
||||
tooltip={`${shouldShowBoundingBox ? t('unifiedCanvas.hideBoundingBox') : t('unifiedCanvas.showBoundingBox')} (Shift + H)`}
|
||||
icon={shouldShowBoundingBox ? <PiEyeBold /> : <PiEyeSlashBold />}
|
||||
onClick={handleSetShouldShowBoundingBox}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.downloadAsImage')} (Shift+D)`}
|
||||
tooltip={`${t('unifiedCanvas.downloadAsImage')} (Shift+D)`}
|
||||
icon={<PiDownloadSimpleBold />}
|
||||
onClick={handleDownloadAsImage}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<IAICanvasUndoButton />
|
||||
<IAICanvasRedoButton />
|
||||
</ButtonGroup>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.resetView')} (R)`}
|
||||
tooltip={`${t('unifiedCanvas.resetView')} (R)`}
|
||||
icon={<PiCrosshairSimpleBold />}
|
||||
onClick={handleClickResetCanvasView}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup>
|
||||
<IconButton
|
||||
aria-label={`${t('common.upload')}`}
|
||||
tooltip={`${t('common.upload')}`}
|
||||
icon={<PiUploadSimpleBold />}
|
||||
isDisabled={isStaging}
|
||||
{...getUploadButtonProps()}
|
||||
/>
|
||||
<input {...getUploadInputProps()} />
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.clearCanvas')}`}
|
||||
tooltip={`${t('unifiedCanvas.clearCanvas')}`}
|
||||
icon={<PiTrashSimpleBold />}
|
||||
onClick={handleResetCanvas}
|
||||
colorScheme="error"
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<IAICanvasSettingsButtonPopover />
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.mergeVisible')} (Shift+M)`}
|
||||
tooltip={`${t('unifiedCanvas.mergeVisible')} (Shift+M)`}
|
||||
icon={<PiStackBold />}
|
||||
onClick={handleMergeVisible}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
|
||||
tooltip={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
|
||||
icon={<PiFloppyDiskBold />}
|
||||
onClick={handleSaveToGallery}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
{isClipboardAPIAvailable && (
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.copyToClipboard')} (Cmd/Ctrl+C)`}
|
||||
tooltip={`${t('unifiedCanvas.copyToClipboard')} (Cmd/Ctrl+C)`}
|
||||
icon={<PiCopyBold />}
|
||||
onClick={handleCopyImageToClipboard}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.downloadAsImage')} (Shift+D)`}
|
||||
tooltip={`${t('unifiedCanvas.downloadAsImage')} (Shift+D)`}
|
||||
icon={<PiDownloadSimpleBold />}
|
||||
onClick={handleDownloadAsImage}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<IAICanvasUndoButton />
|
||||
<IAICanvasRedoButton />
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup>
|
||||
<IconButton
|
||||
aria-label={`${t('common.upload')}`}
|
||||
tooltip={`${t('common.upload')}`}
|
||||
icon={<PiUploadSimpleBold />}
|
||||
isDisabled={isStaging}
|
||||
{...getUploadButtonProps()}
|
||||
/>
|
||||
<input {...getUploadInputProps()} />
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.clearCanvas')}`}
|
||||
tooltip={`${t('unifiedCanvas.clearCanvas')}`}
|
||||
icon={<PiTrashSimpleBold />}
|
||||
onClick={handleResetCanvas}
|
||||
colorScheme="error"
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<IAICanvasSettingsButtonPopover />
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
<Flex flex={1} justifyContent="center">
|
||||
<Flex gap={2} marginInlineStart="auto">
|
||||
<ViewerButton />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -75,7 +75,7 @@ const useInpaintingCanvasHotkeys = () => {
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (e.repeat || e.key !== ' ' || isInteractiveTarget(e.target) || activeTabName !== 'unifiedCanvas') {
|
||||
if (e.repeat || e.key !== ' ' || isInteractiveTarget(e.target) || activeTabName !== 'canvas') {
|
||||
return;
|
||||
}
|
||||
if ($toolStash.get() || $tool.get() === 'move') {
|
||||
@ -90,7 +90,7 @@ const useInpaintingCanvasHotkeys = () => {
|
||||
);
|
||||
const onKeyUp = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (e.repeat || e.key !== ' ' || isInteractiveTarget(e.target) || activeTabName !== 'unifiedCanvas') {
|
||||
if (e.repeat || e.key !== ' ' || isInteractiveTarget(e.target) || activeTabName !== 'canvas') {
|
||||
return;
|
||||
}
|
||||
if (!$toolStash.get() || $tool.get() !== 'move') {
|
||||
|
@ -8,6 +8,7 @@ import calculateScale from 'features/canvas/util/calculateScale';
|
||||
import { STAGE_PADDING_PERCENTAGE } from 'features/canvas/util/constants';
|
||||
import floorCoordinates from 'features/canvas/util/floorCoordinates';
|
||||
import getScaledBoundingBoxDimensions from 'features/canvas/util/getScaledBoundingBoxDimensions';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import { modelChanged } from 'features/parameters/store/generationSlice';
|
||||
@ -588,8 +589,9 @@ export const canvasSlice = createSlice({
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(modelChanged, (state, action) => {
|
||||
if (action.meta.previousModel?.base === action.payload?.base) {
|
||||
// The base model hasn't changed, we don't need to optimize the size
|
||||
const newModel = action.payload;
|
||||
if (!newModel || action.meta.previousModel?.base === newModel.base) {
|
||||
// Model was cleared or the base didn't change
|
||||
return;
|
||||
}
|
||||
const optimalDimension = getOptimalDimension(action.payload);
|
||||
@ -597,14 +599,8 @@ export const canvasSlice = createSlice({
|
||||
if (getIsSizeOptimal(width, height, optimalDimension)) {
|
||||
return;
|
||||
}
|
||||
setBoundingBoxDimensionsReducer(
|
||||
state,
|
||||
{
|
||||
width,
|
||||
height,
|
||||
},
|
||||
optimalDimension
|
||||
);
|
||||
const newSize = calculateNewSize(state.aspectRatio.value, optimalDimension * optimalDimension);
|
||||
setBoundingBoxDimensionsReducer(state, newSize, optimalDimension);
|
||||
});
|
||||
|
||||
builder.addCase(socketQueueItemStatusChanged, (state, action) => {
|
||||
|
@ -76,7 +76,7 @@ const ControlAdapterConfig = (props: { id: string; number: number }) => {
|
||||
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
|
||||
<ParamControlAdapterModel id={id} />
|
||||
</Box>
|
||||
{activeTabName === 'unifiedCanvas' && <ControlNetCanvasImageImports id={id} />}
|
||||
{activeTabName === 'canvas' && <ControlNetCanvasImageImports id={id} />}
|
||||
<IconButton
|
||||
size="sm"
|
||||
tooltip={t('controlnet.duplicate')}
|
||||
@ -113,7 +113,7 @@ const ControlAdapterConfig = (props: { id: string; number: number }) => {
|
||||
<Flex w="full" flexDir="column" gap={4}>
|
||||
<Flex gap={8} w="full" alignItems="center">
|
||||
<Flex flexDir="column" gap={4} h={controlAdapterType === 'ip_adapter' ? 40 : 32} w="full">
|
||||
{controlAdapterType === 'ip_adapter' && <ParamControlAdapterIPMethod id={id} />}
|
||||
<ParamControlAdapterIPMethod id={id} />
|
||||
<ParamControlAdapterWeight id={id} />
|
||||
<ParamControlAdapterBeginEnd id={id} />
|
||||
</Flex>
|
||||
|
@ -93,15 +93,16 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeTabName === 'unifiedCanvas') {
|
||||
if (activeTabName === 'canvas') {
|
||||
dispatch(setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension));
|
||||
} else {
|
||||
const options = { updateAspectRatio: true, clamp: true };
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
}
|
||||
}, [controlImage, activeTabName, dispatch, optimalDimension]);
|
||||
|
||||
|
@ -46,9 +46,13 @@ const ParamControlAdapterIPMethod = ({ id }: Props) => {
|
||||
|
||||
const value = useMemo(() => options.find((o) => o.value === method), [options, method]);
|
||||
|
||||
if (!method) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="ipAdapterMethod">
|
||||
<InformationalPopover feature="controlNetResizeMode">
|
||||
<FormLabel>{t('controlnet.ipAdapterMethod')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Combobox value={value} options={options} isDisabled={!isEnabled} onChange={handleIPMethodChanged} />
|
||||
|
@ -102,9 +102,13 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
<Flex sx={{ gap: 2 }}>
|
||||
<Tooltip label={selectedModel?.description}>
|
||||
<FormControl isDisabled={!isEnabled} isInvalid={!value || mainModel?.base !== modelConfig?.base} w="full">
|
||||
<FormControl
|
||||
isDisabled={!isEnabled}
|
||||
isInvalid={!value || mainModel?.base !== modelConfig?.base}
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
<Combobox
|
||||
options={options}
|
||||
placeholder={t('controlnet.selectModel')}
|
||||
@ -118,8 +122,7 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => {
|
||||
<FormControl
|
||||
isDisabled={!isEnabled}
|
||||
isInvalid={!value || mainModel?.base !== modelConfig?.base}
|
||||
width="max-content"
|
||||
minWidth={28}
|
||||
sx={{ width: 'max-content', minWidth: 28 }}
|
||||
>
|
||||
<Combobox
|
||||
options={clipVisionOptions}
|
||||
|
@ -5,15 +5,15 @@ import {
|
||||
selectControlAdaptersSlice,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { useMemo } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export const useControlAdapterIPMethod = (id: string) => {
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
|
||||
const ca = selectControlAdapterById(controlAdapters, id);
|
||||
assert(ca?.type === 'ip_adapter');
|
||||
return ca.method;
|
||||
const cn = selectControlAdapterById(controlAdapters, id);
|
||||
if (cn && cn?.type === 'ip_adapter') {
|
||||
return cn.method;
|
||||
}
|
||||
}),
|
||||
[id]
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { PayloadAction, Update } from '@reduxjs/toolkit';
|
||||
import { createEntityAdapter, createSlice, isAnyOf } from '@reduxjs/toolkit';
|
||||
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
|
||||
import { getSelectorsOptions } from 'app/store/createMemoizedSelector';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
@ -7,7 +7,7 @@ import { buildControlAdapter } from 'features/controlAdapters/util/buildControlA
|
||||
import { buildControlAdapterProcessor } from 'features/controlAdapters/util/buildControlAdapterProcessor';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import { merge, uniq } from 'lodash-es';
|
||||
import type { ControlNetModelConfig, ImageDTO, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||
import type { ControlNetModelConfig, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||
import { socketInvocationError } from 'services/events/actions';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
@ -134,46 +134,23 @@ export const controlAdaptersSlice = createSlice({
|
||||
const { id, isEnabled } = action.payload;
|
||||
caAdapter.updateOne(state, { id, changes: { isEnabled } });
|
||||
},
|
||||
controlAdapterImageChanged: (state, action: PayloadAction<{ id: string; controlImage: ImageDTO | null }>) => {
|
||||
controlAdapterImageChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
id: string;
|
||||
controlImage: string | null;
|
||||
}>
|
||||
) => {
|
||||
const { id, controlImage } = action.payload;
|
||||
const ca = selectControlAdapterById(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isControlNetOrT2IAdapter(ca)) {
|
||||
if (controlImage) {
|
||||
const { image_name, width, height } = controlImage;
|
||||
const processorNode = deepClone(ca.processorNode);
|
||||
const minDim = Math.min(controlImage.width, controlImage.height);
|
||||
if ('detect_resolution' in processorNode) {
|
||||
processorNode.detect_resolution = minDim;
|
||||
}
|
||||
if ('image_resolution' in processorNode) {
|
||||
processorNode.image_resolution = minDim;
|
||||
}
|
||||
if ('resolution' in processorNode) {
|
||||
processorNode.resolution = minDim;
|
||||
}
|
||||
caAdapter.updateOne(state, {
|
||||
id,
|
||||
changes: {
|
||||
processorNode,
|
||||
controlImage: image_name,
|
||||
controlImageDimensions: { width, height },
|
||||
processedControlImage: null,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
caAdapter.updateOne(state, {
|
||||
id,
|
||||
changes: { controlImage: null, controlImageDimensions: null, processedControlImage: null },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// ip adapter
|
||||
caAdapter.updateOne(state, { id, changes: { controlImage: controlImage?.image_name ?? null } });
|
||||
}
|
||||
caAdapter.updateOne(state, {
|
||||
id,
|
||||
changes: { controlImage, processedControlImage: null },
|
||||
});
|
||||
|
||||
if (controlImage !== null && isControlNetOrT2IAdapter(ca) && ca.processorType !== 'none') {
|
||||
state.pendingControlImages.push(id);
|
||||
@ -183,7 +160,7 @@ export const controlAdaptersSlice = createSlice({
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
id: string;
|
||||
processedControlImage: ImageDTO | null;
|
||||
processedControlImage: string | null;
|
||||
}>
|
||||
) => {
|
||||
const { id, processedControlImage } = action.payload;
|
||||
@ -196,24 +173,12 @@ export const controlAdaptersSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
if (processedControlImage) {
|
||||
const { image_name, width, height } = processedControlImage;
|
||||
caAdapter.updateOne(state, {
|
||||
id,
|
||||
changes: {
|
||||
processedControlImage: image_name,
|
||||
processedControlImageDimensions: { width, height },
|
||||
},
|
||||
});
|
||||
} else {
|
||||
caAdapter.updateOne(state, {
|
||||
id,
|
||||
changes: {
|
||||
processedControlImage: null,
|
||||
processedControlImageDimensions: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
caAdapter.updateOne(state, {
|
||||
id,
|
||||
changes: {
|
||||
processedControlImage,
|
||||
},
|
||||
});
|
||||
|
||||
state.pendingControlImages = state.pendingControlImages.filter((pendingId) => pendingId !== id);
|
||||
},
|
||||
@ -227,7 +192,7 @@ export const controlAdaptersSlice = createSlice({
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
id: string;
|
||||
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | IPAdapterModelConfig | null;
|
||||
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | IPAdapterModelConfig;
|
||||
}>
|
||||
) => {
|
||||
const { id, modelConfig } = action.payload;
|
||||
@ -236,11 +201,6 @@ export const controlAdaptersSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
if (modelConfig === null) {
|
||||
caAdapter.updateOne(state, { id, changes: { model: null } });
|
||||
return;
|
||||
}
|
||||
|
||||
const model = zModelIdentifierField.parse(modelConfig);
|
||||
|
||||
if (!isControlNetOrT2IAdapter(cn)) {
|
||||
@ -248,36 +208,22 @@ export const controlAdaptersSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
const update: Update<ControlNetConfig | T2IAdapterConfig, string> = {
|
||||
id,
|
||||
changes: { model, shouldAutoConfig: true },
|
||||
};
|
||||
|
||||
update.changes.processedControlImage = null;
|
||||
|
||||
if (modelConfig.type === 'ip_adapter') {
|
||||
// should never happen...
|
||||
return;
|
||||
}
|
||||
|
||||
// We always update the model
|
||||
const update: Update<ControlNetConfig | T2IAdapterConfig, string> = { id, changes: { model } };
|
||||
|
||||
// Build the default processor for this model
|
||||
const processor = buildControlAdapterProcessor(modelConfig);
|
||||
if (processor.processorType !== cn.processorNode.type) {
|
||||
// If the processor type has changed, update the processor node
|
||||
update.changes.shouldAutoConfig = true;
|
||||
update.changes.processedControlImage = null;
|
||||
update.changes.processorType = processor.processorType;
|
||||
update.changes.processorNode = processor.processorNode;
|
||||
update.changes.processorType = processor.processorType;
|
||||
update.changes.processorNode = processor.processorNode;
|
||||
|
||||
if (cn.controlImageDimensions) {
|
||||
const minDim = Math.min(cn.controlImageDimensions.width, cn.controlImageDimensions.height);
|
||||
if ('detect_resolution' in update.changes.processorNode) {
|
||||
update.changes.processorNode.detect_resolution = minDim;
|
||||
}
|
||||
if ('image_resolution' in update.changes.processorNode) {
|
||||
update.changes.processorNode.image_resolution = minDim;
|
||||
}
|
||||
if ('resolution' in update.changes.processorNode) {
|
||||
update.changes.processorNode.resolution = minDim;
|
||||
}
|
||||
}
|
||||
}
|
||||
caAdapter.updateOne(state, update);
|
||||
},
|
||||
controlAdapterWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
||||
@ -394,23 +340,8 @@ export const controlAdaptersSlice = createSlice({
|
||||
|
||||
if (update.changes.shouldAutoConfig && modelConfig) {
|
||||
const processor = buildControlAdapterProcessor(modelConfig);
|
||||
if (processor.processorType !== cn.processorNode.type) {
|
||||
update.changes.processorType = processor.processorType;
|
||||
update.changes.processorNode = processor.processorNode;
|
||||
// Copy image resolution settings, urgh
|
||||
if (cn.controlImageDimensions) {
|
||||
const minDim = Math.min(cn.controlImageDimensions.width, cn.controlImageDimensions.height);
|
||||
if ('detect_resolution' in update.changes.processorNode) {
|
||||
update.changes.processorNode.detect_resolution = minDim;
|
||||
}
|
||||
if ('image_resolution' in update.changes.processorNode) {
|
||||
update.changes.processorNode.image_resolution = minDim;
|
||||
}
|
||||
if ('resolution' in update.changes.processorNode) {
|
||||
update.changes.processorNode.resolution = minDim;
|
||||
}
|
||||
}
|
||||
}
|
||||
update.changes.processorType = processor.processorType;
|
||||
update.changes.processorNode = processor.processorNode;
|
||||
}
|
||||
|
||||
caAdapter.updateOne(state, update);
|
||||
@ -481,8 +412,6 @@ export const {
|
||||
t2iAdaptersReset,
|
||||
} = controlAdaptersSlice.actions;
|
||||
|
||||
export const isAnyControlAdapterAdded = isAnyOf(controlAdapterAdded, controlAdapterRecalled);
|
||||
|
||||
export const selectControlAdaptersSlice = (state: RootState) => state.controlAdapters;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
|
@ -225,9 +225,7 @@ export type ControlNetConfig = {
|
||||
controlMode: ControlMode;
|
||||
resizeMode: ResizeMode;
|
||||
controlImage: string | null;
|
||||
controlImageDimensions: { width: number; height: number } | null;
|
||||
processedControlImage: string | null;
|
||||
processedControlImageDimensions: { width: number; height: number } | null;
|
||||
processorType: ControlAdapterProcessorType;
|
||||
processorNode: RequiredControlAdapterProcessorNode;
|
||||
shouldAutoConfig: boolean;
|
||||
@ -243,9 +241,7 @@ export type T2IAdapterConfig = {
|
||||
endStepPct: number;
|
||||
resizeMode: ResizeMode;
|
||||
controlImage: string | null;
|
||||
controlImageDimensions: { width: number; height: number } | null;
|
||||
processedControlImage: string | null;
|
||||
processedControlImageDimensions: { width: number; height: number } | null;
|
||||
processorType: ControlAdapterProcessorType;
|
||||
processorNode: RequiredControlAdapterProcessorNode;
|
||||
shouldAutoConfig: boolean;
|
||||
|
@ -20,9 +20,7 @@ export const initialControlNet: Omit<ControlNetConfig, 'id'> = {
|
||||
controlMode: 'balanced',
|
||||
resizeMode: 'just_resize',
|
||||
controlImage: null,
|
||||
controlImageDimensions: null,
|
||||
processedControlImage: null,
|
||||
processedControlImageDimensions: null,
|
||||
processorType: 'canny_image_processor',
|
||||
processorNode: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults() as RequiredCannyImageProcessorInvocation,
|
||||
shouldAutoConfig: true,
|
||||
@ -37,9 +35,7 @@ export const initialT2IAdapter: Omit<T2IAdapterConfig, 'id'> = {
|
||||
endStepPct: 1,
|
||||
resizeMode: 'just_resize',
|
||||
controlImage: null,
|
||||
controlImageDimensions: null,
|
||||
processedControlImage: null,
|
||||
processedControlImageDimensions: null,
|
||||
processorType: 'canny_image_processor',
|
||||
processorNode: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults() as RequiredCannyImageProcessorInvocation,
|
||||
shouldAutoConfig: true,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||
import { guidanceLayerAdded } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useAddCALayer, useAddIILayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import { rgLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
@ -8,14 +9,11 @@ import { PiPlusBold } from 'react-icons/pi';
|
||||
export const AddLayerButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const addRegionalGuidanceLayer = useCallback(() => {
|
||||
dispatch(guidanceLayerAdded('regional_guidance_layer'));
|
||||
}, [dispatch]);
|
||||
const addControlAdapterLayer = useCallback(() => {
|
||||
dispatch(guidanceLayerAdded('control_adapter_layer'));
|
||||
}, [dispatch]);
|
||||
const addIPAdapterLayer = useCallback(() => {
|
||||
dispatch(guidanceLayerAdded('ip_adapter_layer'));
|
||||
const [addCALayer, isAddCALayerDisabled] = useAddCALayer();
|
||||
const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer();
|
||||
const [addIILayer, isAddIILayerDisabled] = useAddIILayer();
|
||||
const addRGLayer = useCallback(() => {
|
||||
dispatch(rgLayerAdded());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
@ -24,15 +22,18 @@ export const AddLayerButton = memo(() => {
|
||||
{t('controlLayers.addLayer')}
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addRegionalGuidanceLayer}>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addRGLayer}>
|
||||
{t('controlLayers.regionalGuidanceLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addControlAdapterLayer}>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addCALayer} isDisabled={isAddCALayerDisabled}>
|
||||
{t('controlLayers.globalControlAdapterLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addIPAdapterLayer}>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addIPALayer} isDisabled={isAddIPALayerDisabled}>
|
||||
{t('controlLayers.globalIPAdapterLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addIILayer} isDisabled={isAddIILayerDisabled}>
|
||||
{t('controlLayers.globalInitialImageLayer')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Button, Flex } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAddIPAdapterToIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import {
|
||||
isRegionalGuidanceLayer,
|
||||
maskLayerNegativePromptChanged,
|
||||
maskLayerPositivePromptChanged,
|
||||
rgLayerNegativePromptChanged,
|
||||
rgLayerPositivePromptChanged,
|
||||
selectControlLayersSlice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
@ -19,6 +19,7 @@ type AddPromptButtonProps = {
|
||||
export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToIPALayer(layerId);
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
@ -33,13 +34,10 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
|
||||
);
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
const addPositivePrompt = useCallback(() => {
|
||||
dispatch(maskLayerPositivePromptChanged({ layerId, prompt: '' }));
|
||||
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
const addNegativePrompt = useCallback(() => {
|
||||
dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
const addIPAdapter = useCallback(() => {
|
||||
dispatch(guidanceLayerIPAdapterAdded(layerId));
|
||||
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
|
||||
return (
|
||||
@ -62,7 +60,13 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
|
||||
>
|
||||
{t('common.negativePrompt')}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" leftIcon={<PiPlusBold />} onClick={addIPAdapter}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
leftIcon={<PiPlusBold />}
|
||||
onClick={addIPAdapter}
|
||||
isDisabled={isAddIPAdapterDisabled}
|
||||
>
|
||||
{t('common.ipAdapter')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
@ -0,0 +1,46 @@
|
||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { CALayerControlAdapterWrapper } from 'features/controlLayers/components/CALayer/CALayerControlAdapterWrapper';
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import { layerSelected, selectCALayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import CALayerOpacity from './CALayerOpacity';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const CALayer = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isSelected = useAppSelector((s) => selectCALayerOrThrow(s.controlLayers.present, layerId).isSelected);
|
||||
const onClick = useCallback(() => {
|
||||
// Must be capture so that the layer is selected before deleting/resetting/etc
|
||||
dispatch(layerSelected(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerTitle type="control_adapter_layer" />
|
||||
<Spacer />
|
||||
<CALayerOpacity layerId={layerId} />
|
||||
<LayerMenu layerId={layerId} />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||
<CALayerControlAdapterWrapper layerId={layerId} />
|
||||
</Flex>
|
||||
)}
|
||||
</LayerWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
CALayer.displayName = 'CALayer';
|
@ -0,0 +1,121 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { ControlAdapter } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapter';
|
||||
import {
|
||||
caLayerControlModeChanged,
|
||||
caLayerImageChanged,
|
||||
caLayerModelChanged,
|
||||
caLayerProcessorConfigChanged,
|
||||
caOrIPALayerBeginEndStepPctChanged,
|
||||
caOrIPALayerWeightChanged,
|
||||
selectCALayerOrThrow,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { ControlModeV2, ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { CALayerImageDropData } from 'features/dnd/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import type {
|
||||
CALayerImagePostUploadAction,
|
||||
ControlNetModelConfig,
|
||||
ImageDTO,
|
||||
T2IAdapterModelConfig,
|
||||
} from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const CALayerControlAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const controlAdapter = useAppSelector((s) => selectCALayerOrThrow(s.controlLayers.present, layerId).controlAdapter);
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
dispatch(
|
||||
caOrIPALayerBeginEndStepPctChanged({
|
||||
layerId,
|
||||
beginEndStepPct,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeControlMode = useCallback(
|
||||
(controlMode: ControlModeV2) => {
|
||||
dispatch(
|
||||
caLayerControlModeChanged({
|
||||
layerId,
|
||||
controlMode,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeWeight = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(caOrIPALayerWeightChanged({ layerId, weight }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeProcessorConfig = useCallback(
|
||||
(processorConfig: ProcessorConfig | null) => {
|
||||
dispatch(caLayerProcessorConfigChanged({ layerId, processorConfig }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => {
|
||||
dispatch(
|
||||
caLayerModelChanged({
|
||||
layerId,
|
||||
modelConfig,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(caLayerImageChanged({ layerId, imageDTO }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const droppableData = useMemo<CALayerImageDropData>(
|
||||
() => ({
|
||||
actionType: 'SET_CA_LAYER_IMAGE',
|
||||
context: {
|
||||
layerId,
|
||||
},
|
||||
id: layerId,
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
|
||||
const postUploadAction = useMemo<CALayerImagePostUploadAction>(
|
||||
() => ({
|
||||
layerId,
|
||||
type: 'SET_CA_LAYER_IMAGE',
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
|
||||
return (
|
||||
<ControlAdapter
|
||||
controlAdapter={controlAdapter}
|
||||
onChangeBeginEndStepPct={onChangeBeginEndStepPct}
|
||||
onChangeControlMode={onChangeControlMode}
|
||||
onChangeWeight={onChangeWeight}
|
||||
onChangeProcessorConfig={onChangeProcessorConfig}
|
||||
onChangeModel={onChangeModel}
|
||||
onChangeImage={onChangeImage}
|
||||
droppableData={droppableData}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
CALayerControlAdapterWrapper.displayName = 'CALayerControlAdapterWrapper';
|
@ -15,7 +15,7 @@ import {
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { useLayerOpacity } from 'features/controlLayers/hooks/layerStateHooks';
|
||||
import { isFilterEnabledChanged, layerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { caLayerIsFilterEnabledChanged, caLayerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -34,13 +34,13 @@ const CALayerOpacity = ({ layerId }: Props) => {
|
||||
const { opacity, isFilterEnabled } = useLayerOpacity(layerId);
|
||||
const onChangeOpacity = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(layerOpacityChanged({ layerId, opacity: v / 100 }));
|
||||
dispatch(caLayerOpacityChanged({ layerId, opacity: v / 100 }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
const onChangeFilter = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(isFilterEnabledChanged({ layerId, isFilterEnabled: e.target.checked }));
|
||||
dispatch(caLayerIsFilterEnabledChanged({ layerId, isFilterEnabled: e.target.checked }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
@ -55,7 +55,7 @@ const CALayerOpacity = ({ layerId }: Props) => {
|
||||
onDoubleClick={stopPropagation}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverContent onDoubleClick={stopPropagation}>
|
||||
<PopoverArrow />
|
||||
<PopoverBody>
|
||||
<Flex direction="column" gap={2}>
|
@ -1,71 +0,0 @@
|
||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import CALayerOpacity from 'features/controlLayers/components/CALayerOpacity';
|
||||
import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig';
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerDeleteButton';
|
||||
import { LayerMenu } from 'features/controlLayers/components/LayerMenu';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerVisibilityToggle';
|
||||
import {
|
||||
isControlAdapterLayer,
|
||||
layerSelected,
|
||||
selectControlLayersSlice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const CALayerListItem = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isControlAdapterLayer(layer), `Layer ${layerId} not found or not a ControlNet layer`);
|
||||
return {
|
||||
controlNetId: layer.controlNetId,
|
||||
isSelected: layerId === controlLayers.present.selectedLayerId,
|
||||
};
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
const { controlNetId, isSelected } = useAppSelector(selector);
|
||||
const onClickCapture = useCallback(() => {
|
||||
// Must be capture so that the layer is selected before deleting/resetting/etc
|
||||
dispatch(layerSelected(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
|
||||
return (
|
||||
<Flex
|
||||
gap={2}
|
||||
onClickCapture={onClickCapture}
|
||||
bg={isSelected ? 'base.400' : 'base.800'}
|
||||
px={2}
|
||||
borderRadius="base"
|
||||
py="1px"
|
||||
>
|
||||
<Flex flexDir="column" w="full" bg="base.850" borderRadius="base">
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerTitle type="control_adapter_layer" />
|
||||
<Spacer />
|
||||
<CALayerOpacity layerId={layerId} />
|
||||
<LayerMenu layerId={layerId} />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||
<ControlAdapterLayerConfig id={controlNetId} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
CALayerListItem.displayName = 'CALayerListItem';
|
@ -0,0 +1,117 @@
|
||||
import { Box, Divider, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
||||
import { ControlAdapterModelCombobox } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterModelCombobox';
|
||||
import type {
|
||||
ControlModeV2,
|
||||
ControlNetConfigV2,
|
||||
ProcessorConfig,
|
||||
T2IAdapterConfigV2,
|
||||
} from 'features/controlLayers/util/controlAdapters';
|
||||
import type { TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretUpBold } from 'react-icons/pi';
|
||||
import { useToggle } from 'react-use';
|
||||
import type { ControlNetModelConfig, ImageDTO, PostUploadAction, T2IAdapterModelConfig } from 'services/api/types';
|
||||
|
||||
import { ControlAdapterBeginEndStepPct } from './ControlAdapterBeginEndStepPct';
|
||||
import { ControlAdapterControlModeSelect } from './ControlAdapterControlModeSelect';
|
||||
import { ControlAdapterImagePreview } from './ControlAdapterImagePreview';
|
||||
import { ControlAdapterProcessorConfig } from './ControlAdapterProcessorConfig';
|
||||
import { ControlAdapterProcessorTypeSelect } from './ControlAdapterProcessorTypeSelect';
|
||||
import { ControlAdapterWeight } from './ControlAdapterWeight';
|
||||
|
||||
type Props = {
|
||||
controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2;
|
||||
onChangeBeginEndStepPct: (beginEndStepPct: [number, number]) => void;
|
||||
onChangeControlMode: (controlMode: ControlModeV2) => void;
|
||||
onChangeWeight: (weight: number) => void;
|
||||
onChangeProcessorConfig: (processorConfig: ProcessorConfig | null) => void;
|
||||
onChangeModel: (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => void;
|
||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||
droppableData: TypesafeDroppableData;
|
||||
postUploadAction: PostUploadAction;
|
||||
};
|
||||
|
||||
export const ControlAdapter = memo(
|
||||
({
|
||||
controlAdapter,
|
||||
onChangeBeginEndStepPct,
|
||||
onChangeControlMode,
|
||||
onChangeWeight,
|
||||
onChangeProcessorConfig,
|
||||
onChangeModel,
|
||||
onChangeImage,
|
||||
droppableData,
|
||||
postUploadAction,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={3} position="relative" w="full">
|
||||
<Flex gap={3} alignItems="center" w="full">
|
||||
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
|
||||
<ControlAdapterModelCombobox modelKey={controlAdapter.model?.key ?? null} onChange={onChangeModel} />
|
||||
</Box>
|
||||
|
||||
<IconButton
|
||||
size="sm"
|
||||
tooltip={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
|
||||
aria-label={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
|
||||
onClick={toggleIsExpanded}
|
||||
variant="ghost"
|
||||
icon={
|
||||
<Icon
|
||||
boxSize={4}
|
||||
as={PiCaretUpBold}
|
||||
transform={isExpanded ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex gap={3} w="full">
|
||||
<Flex flexDir="column" gap={3} w="full" h="full">
|
||||
{controlAdapter.type === 'controlnet' && (
|
||||
<ControlAdapterControlModeSelect
|
||||
controlMode={controlAdapter.controlMode}
|
||||
onChange={onChangeControlMode}
|
||||
/>
|
||||
)}
|
||||
<ControlAdapterWeight weight={controlAdapter.weight} onChange={onChangeWeight} />
|
||||
<ControlAdapterBeginEndStepPct
|
||||
beginEndStepPct={controlAdapter.beginEndStepPct}
|
||||
onChange={onChangeBeginEndStepPct}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||
<ControlAdapterImagePreview
|
||||
controlAdapter={controlAdapter}
|
||||
onChangeImage={onChangeImage}
|
||||
droppableData={droppableData}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{isExpanded && (
|
||||
<>
|
||||
<Divider />
|
||||
<Flex flexDir="column" gap={3} w="full">
|
||||
<ControlAdapterProcessorTypeSelect
|
||||
config={controlAdapter.processorConfig}
|
||||
onChange={onChangeProcessorConfig}
|
||||
/>
|
||||
<ControlAdapterProcessorConfig
|
||||
config={controlAdapter.processorConfig}
|
||||
onChange={onChangeProcessorConfig}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ControlAdapter.displayName = 'ControlAdapter';
|
@ -0,0 +1,43 @@
|
||||
import { CompositeRangeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
beginEndStepPct: [number, number];
|
||||
onChange: (beginEndStepPct: [number, number]) => void;
|
||||
};
|
||||
|
||||
const formatPct = (v: number) => `${Math.round(v * 100)}%`;
|
||||
const ariaLabel = ['Begin Step %', 'End Step %'];
|
||||
|
||||
export const ControlAdapterBeginEndStepPct = memo(({ beginEndStepPct, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const onReset = useCallback(() => {
|
||||
onChange([0, 1]);
|
||||
}, [onChange]);
|
||||
|
||||
return (
|
||||
<FormControl orientation="horizontal">
|
||||
<InformationalPopover feature="controlNetBeginEnd">
|
||||
<FormLabel m={0}>{t('controlnet.beginEndStepPercentShort')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<CompositeRangeSlider
|
||||
aria-label={ariaLabel}
|
||||
value={beginEndStepPct}
|
||||
onChange={onChange}
|
||||
onReset={onReset}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
fineStep={0.01}
|
||||
minStepsBetweenThumbs={1}
|
||||
formatValue={formatPct}
|
||||
marks
|
||||
withThumbTooltip
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
ControlAdapterBeginEndStepPct.displayName = 'ControlAdapterBeginEndStepPct';
|
@ -1,24 +1,19 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useControlAdapterControlMode } from 'features/controlAdapters/hooks/useControlAdapterControlMode';
|
||||
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
|
||||
import { controlAdapterControlModeChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import type { ControlMode } from 'features/controlAdapters/store/types';
|
||||
import type { ControlModeV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { isControlModeV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
controlMode: ControlModeV2;
|
||||
onChange: (controlMode: ControlModeV2) => void;
|
||||
};
|
||||
|
||||
const ParamControlAdapterControlMode = ({ id }: Props) => {
|
||||
const isEnabled = useControlAdapterIsEnabled(id);
|
||||
const controlMode = useControlAdapterControlMode(id);
|
||||
const dispatch = useAppDispatch();
|
||||
export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const CONTROL_MODE_DATA = useMemo(
|
||||
() => [
|
||||
{ label: t('controlnet.balanced'), value: 'balanced' },
|
||||
@ -31,17 +26,10 @@ const ParamControlAdapterControlMode = ({ id }: Props) => {
|
||||
|
||||
const handleControlModeChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
dispatch(
|
||||
controlAdapterControlModeChanged({
|
||||
id,
|
||||
controlMode: v.value as ControlMode,
|
||||
})
|
||||
);
|
||||
assert(isControlModeV2(v?.value));
|
||||
onChange(v.value);
|
||||
},
|
||||
[id, dispatch]
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
@ -54,13 +42,19 @@ const ParamControlAdapterControlMode = ({ id }: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl isDisabled={!isEnabled}>
|
||||
<FormControl>
|
||||
<InformationalPopover feature="controlNetControlMode">
|
||||
<FormLabel m={0}>{t('controlnet.control')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Combobox value={value} options={CONTROL_MODE_DATA} onChange={handleControlModeChange} />
|
||||
<Combobox
|
||||
value={value}
|
||||
options={CONTROL_MODE_DATA}
|
||||
onChange={handleControlModeChange}
|
||||
isClearable={false}
|
||||
isSearchable={false}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default memo(ParamControlAdapterControlMode);
|
||||
ControlAdapterControlModeSelect.displayName = 'ControlAdapterControlModeSelect';
|
@ -0,0 +1,217 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Box, Flex, Spinner, useShiftModifier } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { ControlNetConfigV2, T2IAdapterConfigV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi';
|
||||
import {
|
||||
useAddImageToBoardMutation,
|
||||
useChangeImageIsIntermediateMutation,
|
||||
useGetImageDTOQuery,
|
||||
useRemoveImageFromBoardMutation,
|
||||
} from 'services/api/endpoints/images';
|
||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2;
|
||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||
droppableData: TypesafeDroppableData;
|
||||
postUploadAction: PostUploadAction;
|
||||
};
|
||||
|
||||
export const ControlAdapterImagePreview = memo(
|
||||
({ controlAdapter, onChangeImage, droppableData, postUploadAction }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const shift = useShiftModifier();
|
||||
|
||||
const [isMouseOverImage, setIsMouseOverImage] = useState(false);
|
||||
|
||||
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(
|
||||
controlAdapter.image?.imageName ?? skipToken
|
||||
);
|
||||
const { currentData: processedControlImage, isError: isErrorProcessedControlImage } = useGetImageDTOQuery(
|
||||
controlAdapter.processedImage?.imageName ?? skipToken
|
||||
);
|
||||
|
||||
const [changeIsIntermediate] = useChangeImageIsIntermediateMutation();
|
||||
const [addToBoard] = useAddImageToBoardMutation();
|
||||
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
||||
const handleResetControlImage = useCallback(() => {
|
||||
onChangeImage(null);
|
||||
}, [onChangeImage]);
|
||||
|
||||
const handleSaveControlImage = useCallback(async () => {
|
||||
if (!processedControlImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
await changeIsIntermediate({
|
||||
imageDTO: processedControlImage,
|
||||
is_intermediate: false,
|
||||
}).unwrap();
|
||||
|
||||
if (autoAddBoardId !== 'none') {
|
||||
addToBoard({
|
||||
imageDTO: processedControlImage,
|
||||
board_id: autoAddBoardId,
|
||||
});
|
||||
} else {
|
||||
removeFromBoard({ imageDTO: processedControlImage });
|
||||
}
|
||||
}, [processedControlImage, changeIsIntermediate, autoAddBoardId, addToBoard, removeFromBoard]);
|
||||
|
||||
const handleSetControlImageToDimensions = useCallback(() => {
|
||||
if (!controlImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeTabName === 'canvas') {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension)
|
||||
);
|
||||
} else {
|
||||
const options = { updateAspectRatio: true, clamp: true };
|
||||
|
||||
if (shift) {
|
||||
const { width, height } = controlImage;
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
}
|
||||
}
|
||||
}, [controlImage, activeTabName, dispatch, optimalDimension, shift]);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
setIsMouseOverImage(true);
|
||||
}, []);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsMouseOverImage(false);
|
||||
}, []);
|
||||
|
||||
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
|
||||
if (controlImage) {
|
||||
return {
|
||||
id: controlAdapter.id,
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO: controlImage },
|
||||
};
|
||||
}
|
||||
}, [controlImage, controlAdapter.id]);
|
||||
|
||||
const shouldShowProcessedImage =
|
||||
controlImage &&
|
||||
processedControlImage &&
|
||||
!isMouseOverImage &&
|
||||
!controlAdapter.isProcessingImage &&
|
||||
controlAdapter.processorConfig !== null;
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && (isErrorControlImage || isErrorProcessedControlImage)) {
|
||||
handleResetControlImage();
|
||||
}
|
||||
}, [handleResetControlImage, isConnected, isErrorControlImage, isErrorProcessedControlImage]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
position="relative"
|
||||
w="full"
|
||||
h={36}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={controlImage}
|
||||
isDropDisabled={shouldShowProcessedImage}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
insetInlineStart={0}
|
||||
w="full"
|
||||
h="full"
|
||||
opacity={shouldShowProcessedImage ? 1 : 0}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
pointerEvents="none"
|
||||
>
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={processedControlImage}
|
||||
isUploadDisabled={true}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleResetControlImage}
|
||||
icon={controlImage ? <PiArrowCounterClockwiseBold size={16} /> : undefined}
|
||||
tooltip={t('controlnet.resetControlImage')}
|
||||
/>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleSaveControlImage}
|
||||
icon={controlImage ? <PiFloppyDiskBold size={16} /> : undefined}
|
||||
tooltip={t('controlnet.saveControlImage')}
|
||||
styleOverrides={saveControlImageStyleOverrides}
|
||||
/>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleSetControlImageToDimensions}
|
||||
icon={controlImage ? <PiRulerBold size={16} /> : undefined}
|
||||
tooltip={shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')}
|
||||
styleOverrides={setControlImageDimensionsStyleOverrides}
|
||||
/>
|
||||
</>
|
||||
|
||||
{controlAdapter.isProcessingImage && (
|
||||
<Flex
|
||||
position="absolute"
|
||||
top={0}
|
||||
insetInlineStart={0}
|
||||
w="full"
|
||||
h="full"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
opacity={0.8}
|
||||
borderRadius="base"
|
||||
bg="base.900"
|
||||
>
|
||||
<Spinner size="xl" color="base.400" />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ControlAdapterImagePreview.displayName = 'ControlAdapterImagePreview';
|
||||
|
||||
const saveControlImageStyleOverrides: SystemStyleObject = { mt: 6 };
|
||||
const setControlImageDimensionsStyleOverrides: SystemStyleObject = { mt: 12 };
|
@ -0,0 +1,62 @@
|
||||
import { Combobox, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useControlNetAndT2IAdapterModels } from 'services/api/hooks/modelsByType';
|
||||
import type { AnyModelConfig, ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
modelKey: string | null;
|
||||
onChange: (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => void;
|
||||
};
|
||||
|
||||
export const ControlAdapterModelCombobox = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
||||
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
||||
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
||||
|
||||
const _onChange = useCallback(
|
||||
(modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | null) => {
|
||||
if (!modelConfig) {
|
||||
return;
|
||||
}
|
||||
onChangeModel(modelConfig);
|
||||
},
|
||||
[onChangeModel]
|
||||
);
|
||||
|
||||
const getIsDisabled = useCallback(
|
||||
(model: AnyModelConfig): boolean => {
|
||||
const isCompatible = currentBaseModel === model.base;
|
||||
const hasMainModel = Boolean(currentBaseModel);
|
||||
return !hasMainModel || !isCompatible;
|
||||
},
|
||||
[currentBaseModel]
|
||||
);
|
||||
|
||||
const { options, value, onChange, noOptionsMessage } = useGroupedModelCombobox({
|
||||
modelConfigs,
|
||||
onChange: _onChange,
|
||||
selectedModel,
|
||||
getIsDisabled,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
return (
|
||||
<Tooltip label={selectedModel?.description}>
|
||||
<FormControl isInvalid={!value || currentBaseModel !== selectedModel?.base} w="full">
|
||||
<Combobox
|
||||
options={options}
|
||||
placeholder={t('controlnet.selectModel')}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
/>
|
||||
</FormControl>
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
|
||||
ControlAdapterModelCombobox.displayName = 'ControlAdapterModelCombobox';
|
@ -0,0 +1,85 @@
|
||||
import type { ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { CannyProcessor } from './processors/CannyProcessor';
|
||||
import { ColorMapProcessor } from './processors/ColorMapProcessor';
|
||||
import { ContentShuffleProcessor } from './processors/ContentShuffleProcessor';
|
||||
import { DepthAnythingProcessor } from './processors/DepthAnythingProcessor';
|
||||
import { DWOpenposeProcessor } from './processors/DWOpenposeProcessor';
|
||||
import { HedProcessor } from './processors/HedProcessor';
|
||||
import { LineartProcessor } from './processors/LineartProcessor';
|
||||
import { MediapipeFaceProcessor } from './processors/MediapipeFaceProcessor';
|
||||
import { MidasDepthProcessor } from './processors/MidasDepthProcessor';
|
||||
import { MlsdImageProcessor } from './processors/MlsdImageProcessor';
|
||||
import { PidiProcessor } from './processors/PidiProcessor';
|
||||
|
||||
type Props = {
|
||||
config: ProcessorConfig | null;
|
||||
onChange: (config: ProcessorConfig | null) => void;
|
||||
};
|
||||
|
||||
export const ControlAdapterProcessorConfig = memo(({ config, onChange }: Props) => {
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.type === 'canny_image_processor') {
|
||||
return <CannyProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'color_map_image_processor') {
|
||||
return <ColorMapProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'depth_anything_image_processor') {
|
||||
return <DepthAnythingProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'hed_image_processor') {
|
||||
return <HedProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'lineart_image_processor') {
|
||||
return <LineartProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'content_shuffle_image_processor') {
|
||||
return <ContentShuffleProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'lineart_anime_image_processor') {
|
||||
// No configurable options for this processor
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.type === 'mediapipe_face_processor') {
|
||||
return <MediapipeFaceProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'midas_depth_image_processor') {
|
||||
return <MidasDepthProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'mlsd_image_processor') {
|
||||
return <MlsdImageProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'normalbae_image_processor') {
|
||||
// No configurable options for this processor
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.type === 'dw_openpose_image_processor') {
|
||||
return <DWOpenposeProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'pidi_image_processor') {
|
||||
return <PidiProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'zoe_depth_image_processor') {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
ControlAdapterProcessorConfig.displayName = 'ControlAdapterProcessorConfig';
|
@ -0,0 +1,70 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, Flex, FormControl, FormLabel, IconButton } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import type { ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA, isProcessorTypeV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { includes, map } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiXBold } from 'react-icons/pi';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
config: ProcessorConfig | null;
|
||||
onChange: (config: ProcessorConfig | null) => void;
|
||||
};
|
||||
|
||||
const selectDisabledProcessors = createMemoizedSelector(
|
||||
configSelector,
|
||||
(config) => config.sd.disabledControlNetProcessors
|
||||
);
|
||||
|
||||
export const ControlAdapterProcessorTypeSelect = memo(({ config, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const disabledProcessors = useAppSelector(selectDisabledProcessors);
|
||||
const options = useMemo(() => {
|
||||
return map(CA_PROCESSOR_DATA, ({ labelTKey }, type) => ({ value: type, label: t(labelTKey) })).filter(
|
||||
(o) => !includes(disabledProcessors, o.value)
|
||||
);
|
||||
}, [disabledProcessors, t]);
|
||||
|
||||
const _onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
if (!v) {
|
||||
onChange(null);
|
||||
} else {
|
||||
assert(isProcessorTypeV2(v.value));
|
||||
onChange(CA_PROCESSOR_DATA[v.value].buildDefaults());
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
const clearProcessor = useCallback(() => {
|
||||
onChange(null);
|
||||
}, [onChange]);
|
||||
const value = useMemo(() => options.find((o) => o.value === config?.type) ?? null, [options, config?.type]);
|
||||
|
||||
return (
|
||||
<Flex gap={2}>
|
||||
<FormControl>
|
||||
<InformationalPopover feature="controlNetProcessor">
|
||||
<FormLabel m={0}>{t('controlnet.processor')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Combobox value={value} options={options} onChange={_onChange} isSearchable={false} isClearable={false} />
|
||||
</FormControl>
|
||||
<IconButton
|
||||
aria-label={t('controlLayers.clearProcessor')}
|
||||
onClick={clearProcessor}
|
||||
isDisabled={!config}
|
||||
icon={<PiXBold />}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
ControlAdapterProcessorTypeSelect.displayName = 'ControlAdapterProcessorTypeSelect';
|
@ -1,24 +1,19 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
|
||||
import { useControlAdapterWeight } from 'features/controlAdapters/hooks/useControlAdapterWeight';
|
||||
import { controlAdapterWeightChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type ParamControlAdapterWeightProps = {
|
||||
id: string;
|
||||
type Props = {
|
||||
weight: number;
|
||||
onChange: (weight: number) => void;
|
||||
};
|
||||
|
||||
const formatValue = (v: number) => v.toFixed(2);
|
||||
const marks = [0, 1, 2];
|
||||
|
||||
const ParamControlAdapterWeight = ({ id }: ParamControlAdapterWeightProps) => {
|
||||
export const ControlAdapterWeight = memo(({ weight, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isEnabled = useControlAdapterIsEnabled(id);
|
||||
const weight = useControlAdapterWeight(id);
|
||||
const initial = useAppSelector((s) => s.config.sd.ca.weight.initial);
|
||||
const sliderMin = useAppSelector((s) => s.config.sd.ca.weight.sliderMin);
|
||||
const sliderMax = useAppSelector((s) => s.config.sd.ca.weight.sliderMax);
|
||||
@ -27,20 +22,8 @@ const ParamControlAdapterWeight = ({ id }: ParamControlAdapterWeightProps) => {
|
||||
const coarseStep = useAppSelector((s) => s.config.sd.ca.weight.coarseStep);
|
||||
const fineStep = useAppSelector((s) => s.config.sd.ca.weight.fineStep);
|
||||
|
||||
const onChange = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(controlAdapterWeightChanged({ id, weight }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
if (isNil(weight)) {
|
||||
// should never happen
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl isDisabled={!isEnabled} orientation="horizontal">
|
||||
<FormControl orientation="horizontal">
|
||||
<InformationalPopover feature="controlNetWeight">
|
||||
<FormLabel m={0}>{t('controlnet.weight')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
@ -67,8 +50,6 @@ const ParamControlAdapterWeight = ({ id }: ParamControlAdapterWeightProps) => {
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default memo(ParamControlAdapterWeight);
|
||||
|
||||
const marks = [0, 1, 2];
|
||||
ControlAdapterWeight.displayName = 'ControlAdapterWeight';
|
@ -0,0 +1,72 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { ControlAdapterBeginEndStepPct } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterBeginEndStepPct';
|
||||
import { ControlAdapterWeight } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterWeight';
|
||||
import { IPAdapterImagePreview } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterImagePreview';
|
||||
import { IPAdapterMethod } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterMethod';
|
||||
import { IPAdapterModelSelect } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterModelSelect';
|
||||
import type { CLIPVisionModelV2, IPAdapterConfigV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { memo } from 'react';
|
||||
import type { ImageDTO, IPAdapterModelConfig, PostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
ipAdapter: IPAdapterConfigV2;
|
||||
onChangeBeginEndStepPct: (beginEndStepPct: [number, number]) => void;
|
||||
onChangeWeight: (weight: number) => void;
|
||||
onChangeIPMethod: (method: IPMethodV2) => void;
|
||||
onChangeModel: (modelConfig: IPAdapterModelConfig) => void;
|
||||
onChangeCLIPVisionModel: (clipVisionModel: CLIPVisionModelV2) => void;
|
||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||
droppableData: TypesafeDroppableData;
|
||||
postUploadAction: PostUploadAction;
|
||||
};
|
||||
|
||||
export const IPAdapter = memo(
|
||||
({
|
||||
ipAdapter,
|
||||
onChangeBeginEndStepPct,
|
||||
onChangeWeight,
|
||||
onChangeIPMethod,
|
||||
onChangeModel,
|
||||
onChangeCLIPVisionModel,
|
||||
onChangeImage,
|
||||
droppableData,
|
||||
postUploadAction,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Flex flexDir="column" gap={4} position="relative" w="full">
|
||||
<Flex gap={3} alignItems="center" w="full">
|
||||
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
|
||||
<IPAdapterModelSelect
|
||||
modelKey={ipAdapter.model?.key ?? null}
|
||||
onChangeModel={onChangeModel}
|
||||
clipVisionModel={ipAdapter.clipVisionModel}
|
||||
onChangeCLIPVisionModel={onChangeCLIPVisionModel}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex gap={4} w="full" alignItems="center">
|
||||
<Flex flexDir="column" gap={3} w="full">
|
||||
<IPAdapterMethod method={ipAdapter.method} onChange={onChangeIPMethod} />
|
||||
<ControlAdapterWeight weight={ipAdapter.weight} onChange={onChangeWeight} />
|
||||
<ControlAdapterBeginEndStepPct
|
||||
beginEndStepPct={ipAdapter.beginEndStepPct}
|
||||
onChange={onChangeBeginEndStepPct}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||
<IPAdapterImagePreview
|
||||
image={ipAdapter.image}
|
||||
onChangeImage={onChangeImage}
|
||||
ipAdapterId={ipAdapter.id}
|
||||
droppableData={droppableData}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
IPAdapter.displayName = 'IPAdapter';
|
@ -0,0 +1,115 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Flex, useShiftModifier } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { ImageWithDims } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
image: ImageWithDims | null;
|
||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||
ipAdapterId: string; // required for the dnd/upload interactions
|
||||
droppableData: TypesafeDroppableData;
|
||||
postUploadAction: PostUploadAction;
|
||||
};
|
||||
|
||||
export const IPAdapterImagePreview = memo(
|
||||
({ image, onChangeImage, ipAdapterId, droppableData, postUploadAction }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const shift = useShiftModifier();
|
||||
|
||||
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(
|
||||
image?.imageName ?? skipToken
|
||||
);
|
||||
const handleResetControlImage = useCallback(() => {
|
||||
onChangeImage(null);
|
||||
}, [onChangeImage]);
|
||||
|
||||
const handleSetControlImageToDimensions = useCallback(() => {
|
||||
if (!controlImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeTabName === 'canvas') {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension)
|
||||
);
|
||||
} else {
|
||||
const options = { updateAspectRatio: true, clamp: true };
|
||||
if (shift) {
|
||||
const { width, height } = controlImage;
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
}
|
||||
}
|
||||
}, [controlImage, activeTabName, dispatch, optimalDimension, shift]);
|
||||
|
||||
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
|
||||
if (controlImage) {
|
||||
return {
|
||||
id: ipAdapterId,
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO: controlImage },
|
||||
};
|
||||
}
|
||||
}, [controlImage, ipAdapterId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && isErrorControlImage) {
|
||||
handleResetControlImage();
|
||||
}
|
||||
}, [handleResetControlImage, isConnected, isErrorControlImage]);
|
||||
|
||||
return (
|
||||
<Flex position="relative" w="full" h={36} alignItems="center" justifyContent="center">
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={controlImage}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
|
||||
<>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleResetControlImage}
|
||||
icon={controlImage ? <PiArrowCounterClockwiseBold size={16} /> : undefined}
|
||||
tooltip={t('controlnet.resetControlImage')}
|
||||
/>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleSetControlImageToDimensions}
|
||||
icon={controlImage ? <PiRulerBold size={16} /> : undefined}
|
||||
tooltip={shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')}
|
||||
styleOverrides={setControlImageDimensionsStyleOverrides}
|
||||
/>
|
||||
</>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
IPAdapterImagePreview.displayName = 'IPAdapterImagePreview';
|
||||
|
||||
const setControlImageDimensionsStyleOverrides: SystemStyleObject = { mt: 6 };
|
@ -0,0 +1,44 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import type { IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { isIPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
method: IPMethodV2;
|
||||
onChange: (method: IPMethodV2) => void;
|
||||
};
|
||||
|
||||
export const IPAdapterMethod = memo(({ method, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const options: { label: string; value: IPMethodV2 }[] = useMemo(
|
||||
() => [
|
||||
{ label: t('controlnet.full'), value: 'full' },
|
||||
{ label: `${t('controlnet.style')} (${t('common.beta')})`, value: 'style' },
|
||||
{ label: `${t('controlnet.composition')} (${t('common.beta')})`, value: 'composition' },
|
||||
],
|
||||
[t]
|
||||
);
|
||||
const _onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
assert(isIPMethodV2(v?.value));
|
||||
onChange(v.value);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
const value = useMemo(() => options.find((o) => o.value === method), [options, method]);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="ipAdapterMethod">
|
||||
<FormLabel>{t('controlnet.ipAdapterMethod')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Combobox value={value} options={options} onChange={_onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
IPAdapterMethod.displayName = 'IPAdapterMethod';
|
@ -0,0 +1,100 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||
import type { CLIPVisionModelV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { isCLIPVisionModelV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIPAdapterModels } from 'services/api/hooks/modelsByType';
|
||||
import type { AnyModelConfig, IPAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
const CLIP_VISION_OPTIONS = [
|
||||
{ label: 'ViT-H', value: 'ViT-H' },
|
||||
{ label: 'ViT-G', value: 'ViT-G' },
|
||||
];
|
||||
|
||||
type Props = {
|
||||
modelKey: string | null;
|
||||
onChangeModel: (modelConfig: IPAdapterModelConfig) => void;
|
||||
clipVisionModel: CLIPVisionModelV2;
|
||||
onChangeCLIPVisionModel: (clipVisionModel: CLIPVisionModelV2) => void;
|
||||
};
|
||||
|
||||
export const IPAdapterModelSelect = memo(
|
||||
({ modelKey, onChangeModel, clipVisionModel, onChangeCLIPVisionModel }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
||||
const [modelConfigs, { isLoading }] = useIPAdapterModels();
|
||||
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
||||
|
||||
const _onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig | null) => {
|
||||
if (!modelConfig) {
|
||||
return;
|
||||
}
|
||||
onChangeModel(modelConfig);
|
||||
},
|
||||
[onChangeModel]
|
||||
);
|
||||
|
||||
const _onChangeCLIPVisionModel = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
assert(isCLIPVisionModelV2(v?.value));
|
||||
onChangeCLIPVisionModel(v.value);
|
||||
},
|
||||
[onChangeCLIPVisionModel]
|
||||
);
|
||||
|
||||
const getIsDisabled = useCallback(
|
||||
(model: AnyModelConfig): boolean => {
|
||||
const isCompatible = currentBaseModel === model.base;
|
||||
const hasMainModel = Boolean(currentBaseModel);
|
||||
return !hasMainModel || !isCompatible;
|
||||
},
|
||||
[currentBaseModel]
|
||||
);
|
||||
|
||||
const { options, value, onChange, noOptionsMessage } = useGroupedModelCombobox({
|
||||
modelConfigs,
|
||||
onChange: _onChangeModel,
|
||||
selectedModel,
|
||||
getIsDisabled,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
const clipVisionModelValue = useMemo(
|
||||
() => CLIP_VISION_OPTIONS.find((o) => o.value === clipVisionModel),
|
||||
[clipVisionModel]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
<Tooltip label={selectedModel?.description}>
|
||||
<FormControl isInvalid={!value || currentBaseModel !== selectedModel?.base} w="full">
|
||||
<Combobox
|
||||
options={options}
|
||||
placeholder={t('controlnet.selectModel')}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
/>
|
||||
</FormControl>
|
||||
</Tooltip>
|
||||
{selectedModel?.format === 'checkpoint' && (
|
||||
<FormControl isInvalid={!value || currentBaseModel !== selectedModel?.base} width="max-content" minWidth={28}>
|
||||
<Combobox
|
||||
options={CLIP_VISION_OPTIONS}
|
||||
placeholder={t('controlnet.selectCLIPVisionModel')}
|
||||
value={clipVisionModelValue}
|
||||
onChange={_onChangeCLIPVisionModel}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
IPAdapterModelSelect.displayName = 'IPAdapterModelSelect';
|
@ -0,0 +1,67 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import { CA_PROCESSOR_DATA, type CannyProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<CannyProcessorConfig>;
|
||||
const DEFAULTS = CA_PROCESSOR_DATA['canny_image_processor'].buildDefaults();
|
||||
|
||||
export const CannyProcessor = ({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const handleLowThresholdChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, low_threshold: v });
|
||||
},
|
||||
[onChange, config]
|
||||
);
|
||||
const handleHighThresholdChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, high_threshold: v });
|
||||
},
|
||||
[onChange, config]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.lowThreshold')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.low_threshold}
|
||||
onChange={handleLowThresholdChanged}
|
||||
defaultValue={DEFAULTS.low_threshold}
|
||||
min={0}
|
||||
max={255}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.low_threshold}
|
||||
onChange={handleLowThresholdChanged}
|
||||
defaultValue={DEFAULTS.low_threshold}
|
||||
min={0}
|
||||
max={255}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.highThreshold')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.high_threshold}
|
||||
onChange={handleHighThresholdChanged}
|
||||
defaultValue={DEFAULTS.high_threshold}
|
||||
min={0}
|
||||
max={255}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.high_threshold}
|
||||
onChange={handleHighThresholdChanged}
|
||||
defaultValue={DEFAULTS.high_threshold}
|
||||
min={0}
|
||||
max={255}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
CannyProcessor.displayName = 'CannyProcessor';
|
@ -0,0 +1,47 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import { CA_PROCESSOR_DATA, type ColorMapProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<ColorMapProcessorConfig>;
|
||||
const DEFAULTS = CA_PROCESSOR_DATA['color_map_image_processor'].buildDefaults();
|
||||
|
||||
export const ColorMapProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const handleColorMapTileSizeChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, color_map_tile_size: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.colorMapTileSize')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.color_map_tile_size}
|
||||
defaultValue={DEFAULTS.color_map_tile_size}
|
||||
onChange={handleColorMapTileSizeChanged}
|
||||
min={1}
|
||||
max={256}
|
||||
step={1}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.color_map_tile_size}
|
||||
defaultValue={DEFAULTS.color_map_tile_size}
|
||||
onChange={handleColorMapTileSizeChanged}
|
||||
min={1}
|
||||
max={4096}
|
||||
step={1}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
ColorMapProcessor.displayName = 'ColorMapProcessor';
|
@ -0,0 +1,79 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { ContentShuffleProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<ContentShuffleProcessorConfig>;
|
||||
const DEFAULTS = CA_PROCESSOR_DATA['content_shuffle_image_processor'].buildDefaults();
|
||||
|
||||
export const ContentShuffleProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleWChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, w: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleHChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, h: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleFChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, f: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.w')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.w}
|
||||
defaultValue={DEFAULTS.w}
|
||||
onChange={handleWChanged}
|
||||
min={0}
|
||||
max={4096}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput value={config.w} defaultValue={DEFAULTS.w} onChange={handleWChanged} min={0} max={4096} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.h')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.h}
|
||||
defaultValue={DEFAULTS.h}
|
||||
onChange={handleHChanged}
|
||||
min={0}
|
||||
max={4096}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput value={config.h} defaultValue={DEFAULTS.h} onChange={handleHChanged} min={0} max={4096} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.f')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.f}
|
||||
defaultValue={DEFAULTS.f}
|
||||
onChange={handleFChanged}
|
||||
min={0}
|
||||
max={4096}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput value={config.f} defaultValue={DEFAULTS.f} onChange={handleFChanged} min={0} max={4096} />
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
ContentShuffleProcessor.displayName = 'ContentShuffleProcessor';
|
@ -0,0 +1,62 @@
|
||||
import { Flex, FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { DWOpenposeProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<DWOpenposeProcessorConfig>;
|
||||
const DEFAULTS = CA_PROCESSOR_DATA['dw_openpose_image_processor'].buildDefaults();
|
||||
|
||||
export const DWOpenposeProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleDrawBodyChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, draw_body: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleDrawFaceChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, draw_face: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleDrawHandsChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, draw_hands: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<Flex sx={{ flexDir: 'row', gap: 6 }}>
|
||||
<FormControl w="max-content">
|
||||
<FormLabel m={0}>{t('controlnet.body')}</FormLabel>
|
||||
<Switch defaultChecked={DEFAULTS.draw_body} isChecked={config.draw_body} onChange={handleDrawBodyChanged} />
|
||||
</FormControl>
|
||||
<FormControl w="max-content">
|
||||
<FormLabel m={0}>{t('controlnet.face')}</FormLabel>
|
||||
<Switch defaultChecked={DEFAULTS.draw_face} isChecked={config.draw_face} onChange={handleDrawFaceChanged} />
|
||||
</FormControl>
|
||||
<FormControl w="max-content">
|
||||
<FormLabel m={0}>{t('controlnet.hands')}</FormLabel>
|
||||
<Switch
|
||||
defaultChecked={DEFAULTS.draw_hands}
|
||||
isChecked={config.draw_hands}
|
||||
onChange={handleDrawHandsChanged}
|
||||
/>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
DWOpenposeProcessor.displayName = 'DWOpenposeProcessor';
|
@ -0,0 +1,52 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { DepthAnythingModelSize, DepthAnythingProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA, isDepthAnythingModelSize } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<DepthAnythingProcessorConfig>;
|
||||
const DEFAULTS = CA_PROCESSOR_DATA['depth_anything_image_processor'].buildDefaults();
|
||||
|
||||
export const DepthAnythingProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const handleModelSizeChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
if (!isDepthAnythingModelSize(v?.value)) {
|
||||
return;
|
||||
}
|
||||
onChange({ ...config, model_size: v.value });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const options: { label: string; value: DepthAnythingModelSize }[] = useMemo(
|
||||
() => [
|
||||
{ label: t('controlnet.small'), value: 'small' },
|
||||
{ label: t('controlnet.base'), value: 'base' },
|
||||
{ label: t('controlnet.large'), value: 'large' },
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
const value = useMemo(() => options.filter((o) => o.value === config.model_size)[0], [options, config.model_size]);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.modelSize')}</FormLabel>
|
||||
<Combobox
|
||||
value={value}
|
||||
defaultInputValue={DEFAULTS.model_size}
|
||||
options={options}
|
||||
onChange={handleModelSizeChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
DepthAnythingProcessor.displayName = 'DepthAnythingProcessor';
|
@ -0,0 +1,32 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { HedProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<HedProcessorConfig>;
|
||||
|
||||
export const HedProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleScribbleChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, scribble: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.scribble')}</FormLabel>
|
||||
<Switch isChecked={config.scribble} onChange={handleScribbleChanged} />
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
HedProcessor.displayName = 'HedProcessor';
|
@ -0,0 +1,32 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { LineartProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<LineartProcessorConfig>;
|
||||
|
||||
export const LineartProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCoarseChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, coarse: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.coarse')}</FormLabel>
|
||||
<Switch isChecked={config.coarse} onChange={handleCoarseChanged} />
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
LineartProcessor.displayName = 'LineartProcessor';
|
@ -0,0 +1,73 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import { CA_PROCESSOR_DATA, type MediapipeFaceProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<MediapipeFaceProcessorConfig>;
|
||||
const DEFAULTS = CA_PROCESSOR_DATA['mediapipe_face_processor'].buildDefaults();
|
||||
|
||||
export const MediapipeFaceProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleMaxFacesChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, max_faces: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleMinConfidenceChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, min_confidence: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.maxFaces')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.max_faces}
|
||||
onChange={handleMaxFacesChanged}
|
||||
defaultValue={DEFAULTS.max_faces}
|
||||
min={1}
|
||||
max={20}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.max_faces}
|
||||
onChange={handleMaxFacesChanged}
|
||||
defaultValue={DEFAULTS.max_faces}
|
||||
min={1}
|
||||
max={20}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.minConfidence')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.min_confidence}
|
||||
onChange={handleMinConfidenceChanged}
|
||||
defaultValue={DEFAULTS.min_confidence}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.min_confidence}
|
||||
onChange={handleMinConfidenceChanged}
|
||||
defaultValue={DEFAULTS.min_confidence}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
MediapipeFaceProcessor.displayName = 'MediapipeFaceProcessor';
|
@ -0,0 +1,76 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { MidasDepthProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<MidasDepthProcessorConfig>;
|
||||
const DEFAULTS = CA_PROCESSOR_DATA['midas_depth_image_processor'].buildDefaults();
|
||||
|
||||
export const MidasDepthProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleAMultChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, a_mult: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleBgThChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, bg_th: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.amult')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.a_mult}
|
||||
onChange={handleAMultChanged}
|
||||
defaultValue={DEFAULTS.a_mult}
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.01}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.a_mult}
|
||||
onChange={handleAMultChanged}
|
||||
defaultValue={DEFAULTS.a_mult}
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.01}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.bgth')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.bg_th}
|
||||
onChange={handleBgThChanged}
|
||||
defaultValue={DEFAULTS.bg_th}
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.01}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.bg_th}
|
||||
onChange={handleBgThChanged}
|
||||
defaultValue={DEFAULTS.bg_th}
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.01}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
MidasDepthProcessor.displayName = 'MidasDepthProcessor';
|
@ -0,0 +1,76 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { MlsdProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<MlsdProcessorConfig>;
|
||||
const DEFAULTS = CA_PROCESSOR_DATA['mlsd_image_processor'].buildDefaults();
|
||||
|
||||
export const MlsdImageProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleThrDChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, thr_d: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleThrVChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, thr_v: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.w')} </FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.thr_d}
|
||||
onChange={handleThrDChanged}
|
||||
defaultValue={DEFAULTS.thr_d}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.thr_d}
|
||||
onChange={handleThrDChanged}
|
||||
defaultValue={DEFAULTS.thr_d}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.h')} </FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.thr_v}
|
||||
onChange={handleThrVChanged}
|
||||
defaultValue={DEFAULTS.thr_v}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.thr_v}
|
||||
onChange={handleThrVChanged}
|
||||
defaultValue={DEFAULTS.thr_v}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
MlsdImageProcessor.displayName = 'MlsdImageProcessor';
|
@ -0,0 +1,43 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { PidiProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<PidiProcessorConfig>;
|
||||
|
||||
export const PidiProcessor = ({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleScribbleChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, scribble: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleSafeChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, safe: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.scribble')}</FormLabel>
|
||||
<Switch isChecked={config.scribble} onChange={handleScribbleChanged} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.safe')}</FormLabel>
|
||||
<Switch isChecked={config.safe} onChange={handleSafeChanged} />
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
PidiProcessor.displayName = 'PidiProcessor';
|
@ -0,0 +1,15 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = PropsWithChildren;
|
||||
|
||||
const ProcessorWrapper = (props: Props) => {
|
||||
return (
|
||||
<Flex flexDir="column" gap={3}>
|
||||
{props.children}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ProcessorWrapper);
|
@ -0,0 +1,6 @@
|
||||
import type { ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
|
||||
export type ProcessorComponentProps<T extends ProcessorConfig> = {
|
||||
onChange: (config: T) => void;
|
||||
config: T;
|
||||
};
|
@ -2,16 +2,19 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
|
||||
import { CALayerListItem } from 'features/controlLayers/components/CALayerListItem';
|
||||
import { CALayer } from 'features/controlLayers/components/CALayer/CALayer';
|
||||
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
|
||||
import { IPLayerListItem } from 'features/controlLayers/components/IPLayerListItem';
|
||||
import { RGLayerListItem } from 'features/controlLayers/components/RGLayerListItem';
|
||||
import { IILayer } from 'features/controlLayers/components/IILayer/IILayer';
|
||||
import { IPALayer } from 'features/controlLayers/components/IPALayer/IPALayer';
|
||||
import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer';
|
||||
import { isRenderableLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import { partition } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectLayerIdTypePairs = createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const [renderableLayers, ipAdapterLayers] = partition(controlLayers.present.layers, isRenderableLayer);
|
||||
@ -19,20 +22,24 @@ const selectLayerIdTypePairs = createMemoizedSelector(selectControlLayersSlice,
|
||||
});
|
||||
|
||||
export const ControlLayersPanelContent = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const layerIdTypePairs = useAppSelector(selectLayerIdTypePairs);
|
||||
return (
|
||||
<Flex flexDir="column" gap={4} w="full" h="full">
|
||||
<Flex flexDir="column" gap={2} w="full" h="full">
|
||||
<Flex justifyContent="space-around">
|
||||
<AddLayerButton />
|
||||
<DeleteAllLayersButton />
|
||||
</Flex>
|
||||
<ScrollableContent>
|
||||
<Flex flexDir="column" gap={4}>
|
||||
{layerIdTypePairs.map(({ id, type }) => (
|
||||
<LayerWrapper key={id} id={id} type={type} />
|
||||
))}
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
{layerIdTypePairs.length > 0 && (
|
||||
<ScrollableContent>
|
||||
<Flex flexDir="column" gap={2}>
|
||||
{layerIdTypePairs.map(({ id, type }) => (
|
||||
<LayerWrapper key={id} id={id} type={type} />
|
||||
))}
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
)}
|
||||
{layerIdTypePairs.length === 0 && <IAINoContentFallback icon={null} label={t('controlLayers.noLayersAdded')} />}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
@ -46,13 +53,16 @@ type LayerWrapperProps = {
|
||||
|
||||
const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => {
|
||||
if (type === 'regional_guidance_layer') {
|
||||
return <RGLayerListItem key={id} layerId={id} />;
|
||||
return <RGLayer key={id} layerId={id} />;
|
||||
}
|
||||
if (type === 'control_adapter_layer') {
|
||||
return <CALayerListItem key={id} layerId={id} />;
|
||||
return <CALayer key={id} layerId={id} />;
|
||||
}
|
||||
if (type === 'ip_adapter_layer') {
|
||||
return <IPLayerListItem key={id} layerId={id} />;
|
||||
return <IPALayer key={id} layerId={id} />;
|
||||
}
|
||||
if (type === 'initial_image_layer') {
|
||||
return <IILayer key={id} layerId={id} />;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -4,15 +4,26 @@ import { BrushSize } from 'features/controlLayers/components/BrushSize';
|
||||
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
|
||||
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
||||
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
||||
import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const ControlLayersToolbar = memo(() => {
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
<BrushSize />
|
||||
<ToolChooser />
|
||||
<UndoRedoButtonGroup />
|
||||
<ControlLayersSettingsPopover />
|
||||
<Flex w="full" gap={2}>
|
||||
<Flex flex={1} justifyContent="center">
|
||||
<Flex gap={2} marginInlineEnd="auto" />
|
||||
</Flex>
|
||||
<Flex flex={1} gap={2} justifyContent="center">
|
||||
<BrushSize />
|
||||
<ToolChooser />
|
||||
<UndoRedoButtonGroup />
|
||||
<ControlLayersSettingsPopover />
|
||||
</Flex>
|
||||
<Flex flex={1} justifyContent="center">
|
||||
<Flex gap={2} marginInlineStart="auto">
|
||||
<ViewerButton />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Button } from '@invoke-ai/ui-library';
|
||||
import { allLayersDeleted } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { allLayersDeleted } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
@ -8,12 +8,19 @@ import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
export const DeleteAllLayersButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isDisabled = useAppSelector((s) => s.controlLayers.present.layers.length === 0);
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(allLayersDeleted());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Button onClick={onClick} leftIcon={<PiTrashSimpleBold />} variant="ghost" colorScheme="error">
|
||||
<Button
|
||||
onClick={onClick}
|
||||
leftIcon={<PiTrashSimpleBold />}
|
||||
variant="ghost"
|
||||
colorScheme="error"
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
{t('controlLayers.deleteAll')}
|
||||
</Button>
|
||||
);
|
||||
|
@ -0,0 +1,83 @@
|
||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IILayerOpacity from 'features/controlLayers/components/IILayer/IILayerOpacity';
|
||||
import { InitialImagePreview } from 'features/controlLayers/components/IILayer/InitialImagePreview';
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import {
|
||||
iiLayerImageChanged,
|
||||
layerSelected,
|
||||
selectIILayerOrThrow,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { IILayerImageDropData } from 'features/dnd/types';
|
||||
import ImageToImageStrength from 'features/parameters/components/ImageToImage/ImageToImageStrength';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import type { IILayerImagePostUploadAction, ImageDTO } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const IILayer = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const layer = useAppSelector((s) => selectIILayerOrThrow(s.controlLayers.present, layerId));
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(layerSelected(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(iiLayerImageChanged({ layerId, imageDTO }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const droppableData = useMemo<IILayerImageDropData>(
|
||||
() => ({
|
||||
actionType: 'SET_II_LAYER_IMAGE',
|
||||
context: {
|
||||
layerId,
|
||||
},
|
||||
id: layerId,
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
|
||||
const postUploadAction = useMemo<IILayerImagePostUploadAction>(
|
||||
() => ({
|
||||
layerId,
|
||||
type: 'SET_II_LAYER_IMAGE',
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={layer.isSelected ? 'base.400' : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerTitle type="initial_image_layer" />
|
||||
<Spacer />
|
||||
<IILayerOpacity layerId={layerId} />
|
||||
<LayerMenu layerId={layerId} />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||
<ImageToImageStrength />
|
||||
<InitialImagePreview
|
||||
image={layer.image}
|
||||
onChangeImage={onChangeImage}
|
||||
droppableData={droppableData}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</LayerWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
IILayer.displayName = 'IILayer';
|
@ -0,0 +1,98 @@
|
||||
import {
|
||||
CompositeNumberInput,
|
||||
CompositeSlider,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import {
|
||||
iiLayerOpacityChanged,
|
||||
isInitialImageLayer,
|
||||
selectControlLayersSlice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDropHalfFill } from 'react-icons/pi';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
const marks = [0, 25, 50, 75, 100];
|
||||
const formatPct = (v: number | string) => `${v} %`;
|
||||
|
||||
const IILayerOpacity = ({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const selectOpacity = useMemo(
|
||||
() =>
|
||||
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.filter(isInitialImageLayer).find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return Math.round(layer.opacity * 100);
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
const opacity = useAppSelector(selectOpacity);
|
||||
const onChangeOpacity = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(iiLayerOpacityChanged({ layerId, opacity: v / 100 }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
return (
|
||||
<Popover isLazy>
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
aria-label={t('controlLayers.opacity')}
|
||||
size="sm"
|
||||
icon={<PiDropHalfFill size={16} />}
|
||||
variant="ghost"
|
||||
onDoubleClick={stopPropagation}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent onDoubleClick={stopPropagation}>
|
||||
<PopoverArrow />
|
||||
<PopoverBody>
|
||||
<Flex direction="column" gap={2}>
|
||||
<FormControl orientation="horizontal">
|
||||
<FormLabel m={0}>{t('controlLayers.opacity')}</FormLabel>
|
||||
<CompositeSlider
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
value={opacity}
|
||||
defaultValue={100}
|
||||
onChange={onChangeOpacity}
|
||||
marks={marks}
|
||||
w={48}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
value={opacity}
|
||||
defaultValue={100}
|
||||
onChange={onChangeOpacity}
|
||||
w={24}
|
||||
format={formatPct}
|
||||
/>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(IILayerOpacity);
|
@ -0,0 +1,109 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Flex, useShiftModifier } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { ImageWithDims } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
image: ImageWithDims | null;
|
||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||
droppableData: TypesafeDroppableData;
|
||||
postUploadAction: PostUploadAction;
|
||||
};
|
||||
|
||||
export const InitialImagePreview = memo(({ image, onChangeImage, droppableData, postUploadAction }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const shift = useShiftModifier();
|
||||
|
||||
const { currentData: imageDTO, isError: isErrorControlImage } = useGetImageDTOQuery(image?.imageName ?? skipToken);
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
onChangeImage(null);
|
||||
}, [onChangeImage]);
|
||||
|
||||
const onUseSize = useCallback(() => {
|
||||
if (!imageDTO) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeTabName === 'canvas') {
|
||||
dispatch(setBoundingBoxDimensions({ width: imageDTO.width, height: imageDTO.height }, optimalDimension));
|
||||
} else {
|
||||
const options = { updateAspectRatio: true, clamp: true };
|
||||
if (shift) {
|
||||
const { width, height } = imageDTO;
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
imageDTO.width / imageDTO.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
}
|
||||
}
|
||||
}, [imageDTO, activeTabName, dispatch, optimalDimension, shift]);
|
||||
|
||||
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
|
||||
if (imageDTO) {
|
||||
return {
|
||||
id: 'initial_image_layer',
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO: imageDTO },
|
||||
};
|
||||
}
|
||||
}, [imageDTO]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && isErrorControlImage) {
|
||||
onReset();
|
||||
}
|
||||
}, [onReset, isConnected, isErrorControlImage]);
|
||||
|
||||
return (
|
||||
<Flex position="relative" w="full" h={36} alignItems="center" justifyContent="center">
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={imageDTO}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
|
||||
<>
|
||||
<IAIDndImageIcon
|
||||
onClick={onReset}
|
||||
icon={imageDTO ? <PiArrowCounterClockwiseBold size={16} /> : undefined}
|
||||
tooltip={t('controlnet.resetControlImage')}
|
||||
/>
|
||||
<IAIDndImageIcon
|
||||
onClick={onUseSize}
|
||||
icon={imageDTO ? <PiRulerBold size={16} /> : undefined}
|
||||
tooltip={shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')}
|
||||
styleOverrides={useSizeStyleOverrides}
|
||||
/>
|
||||
</>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
InitialImagePreview.displayName = 'InitialImagePreview';
|
||||
|
||||
const useSizeStyleOverrides: SystemStyleObject = { mt: 6 };
|
@ -0,0 +1,32 @@
|
||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { IPALayerIPAdapterWrapper } from 'features/controlLayers/components/IPALayer/IPALayerIPAdapterWrapper';
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const IPALayer = memo(({ layerId }: Props) => {
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
return (
|
||||
<LayerWrapper borderColor="base.800">
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerTitle type="ip_adapter_layer" />
|
||||
<Spacer />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||
<IPALayerIPAdapterWrapper layerId={layerId} />
|
||||
</Flex>
|
||||
)}
|
||||
</LayerWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
IPALayer.displayName = 'IPALayer';
|
@ -0,0 +1,106 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { IPAdapter } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapter';
|
||||
import {
|
||||
caOrIPALayerBeginEndStepPctChanged,
|
||||
caOrIPALayerWeightChanged,
|
||||
ipaLayerCLIPVisionModelChanged,
|
||||
ipaLayerImageChanged,
|
||||
ipaLayerMethodChanged,
|
||||
ipaLayerModelChanged,
|
||||
selectIPALayerOrThrow,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { IPALayerImageDropData } from 'features/dnd/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import type { ImageDTO, IPAdapterModelConfig, IPALayerImagePostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const IPALayerIPAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const ipAdapter = useAppSelector((s) => selectIPALayerOrThrow(s.controlLayers.present, layerId).ipAdapter);
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
dispatch(
|
||||
caOrIPALayerBeginEndStepPctChanged({
|
||||
layerId,
|
||||
beginEndStepPct,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeWeight = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(caOrIPALayerWeightChanged({ layerId, weight }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeIPMethod = useCallback(
|
||||
(method: IPMethodV2) => {
|
||||
dispatch(ipaLayerMethodChanged({ layerId, method }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig) => {
|
||||
dispatch(ipaLayerModelChanged({ layerId, modelConfig }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeCLIPVisionModel = useCallback(
|
||||
(clipVisionModel: CLIPVisionModelV2) => {
|
||||
dispatch(ipaLayerCLIPVisionModelChanged({ layerId, clipVisionModel }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(ipaLayerImageChanged({ layerId, imageDTO }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const droppableData = useMemo<IPALayerImageDropData>(
|
||||
() => ({
|
||||
actionType: 'SET_IPA_LAYER_IMAGE',
|
||||
context: {
|
||||
layerId,
|
||||
},
|
||||
id: layerId,
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
|
||||
const postUploadAction = useMemo<IPALayerImagePostUploadAction>(
|
||||
() => ({
|
||||
type: 'SET_IPA_LAYER_IMAGE',
|
||||
layerId,
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
|
||||
return (
|
||||
<IPAdapter
|
||||
ipAdapter={ipAdapter}
|
||||
onChangeBeginEndStepPct={onChangeBeginEndStepPct}
|
||||
onChangeWeight={onChangeWeight}
|
||||
onChangeIPMethod={onChangeIPMethod}
|
||||
onChangeModel={onChangeModel}
|
||||
onChangeCLIPVisionModel={onChangeCLIPVisionModel}
|
||||
onChangeImage={onChangeImage}
|
||||
droppableData={droppableData}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
IPALayerIPAdapterWrapper.displayName = 'IPALayerIPAdapterWrapper';
|
@ -1,47 +0,0 @@
|
||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig';
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerDeleteButton';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerVisibilityToggle';
|
||||
import { isIPAdapterLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const IPLayerListItem = memo(({ layerId }: Props) => {
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isIPAdapterLayer(layer), `Layer ${layerId} not found or not an IP Adapter layer`);
|
||||
return layer.ipAdapterId;
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
const ipAdapterId = useAppSelector(selector);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
return (
|
||||
<Flex gap={2} bg="base.800" borderRadius="base" p="1px" px={2}>
|
||||
<Flex flexDir="column" w="full" bg="base.850" borderRadius="base">
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerTitle type="ip_adapter_layer" />
|
||||
<Spacer />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||
<ControlAdapterLayerConfig id={ipAdapterId} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
IPLayerListItem.displayName = 'IPLayerListItem';
|
@ -1,7 +1,7 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { guidanceLayerDeleted } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { layerDeleted } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
@ -12,7 +12,7 @@ export const LayerDeleteButton = memo(({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const deleteLayer = useCallback(() => {
|
||||
dispatch(guidanceLayerDeleted(layerId));
|
||||
dispatch(layerDeleted(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
return (
|
||||
<IconButton
|
@ -1,8 +1,8 @@
|
||||
import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { LayerMenuArrangeActions } from 'features/controlLayers/components/LayerMenuArrangeActions';
|
||||
import { LayerMenuRGActions } from 'features/controlLayers/components/LayerMenuRGActions';
|
||||
import { LayerMenuArrangeActions } from 'features/controlLayers/components/LayerCommon/LayerMenuArrangeActions';
|
||||
import { LayerMenuRGActions } from 'features/controlLayers/components/LayerCommon/LayerMenuRGActions';
|
||||
import { useLayerType } from 'features/controlLayers/hooks/layerStateHooks';
|
||||
import { layerDeleted, layerReset } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
@ -37,7 +37,9 @@ export const LayerMenu = memo(({ layerId }: Props) => {
|
||||
<MenuDivider />
|
||||
</>
|
||||
)}
|
||||
{(layerType === 'regional_guidance_layer' || layerType === 'control_adapter_layer') && (
|
||||
{(layerType === 'regional_guidance_layer' ||
|
||||
layerType === 'control_adapter_layer' ||
|
||||
layerType === 'initial_image_layer') && (
|
||||
<>
|
||||
<LayerMenuArrangeActions layerId={layerId} />
|
||||
<MenuDivider />
|
@ -1,11 +1,11 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAddIPAdapterToIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import {
|
||||
isRegionalGuidanceLayer,
|
||||
maskLayerNegativePromptChanged,
|
||||
maskLayerPositivePromptChanged,
|
||||
rgLayerNegativePromptChanged,
|
||||
rgLayerPositivePromptChanged,
|
||||
selectControlLayersSlice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@ -18,6 +18,7 @@ type Props = { layerId: string };
|
||||
export const LayerMenuRGActions = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToIPALayer(layerId);
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
@ -32,13 +33,10 @@ export const LayerMenuRGActions = memo(({ layerId }: Props) => {
|
||||
);
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
const addPositivePrompt = useCallback(() => {
|
||||
dispatch(maskLayerPositivePromptChanged({ layerId, prompt: '' }));
|
||||
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
const addNegativePrompt = useCallback(() => {
|
||||
dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
const addIPAdapter = useCallback(() => {
|
||||
dispatch(guidanceLayerIPAdapterAdded(layerId));
|
||||
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
return (
|
||||
<>
|
||||
@ -48,7 +46,7 @@ export const LayerMenuRGActions = memo(({ layerId }: Props) => {
|
||||
<MenuItem onClick={addNegativePrompt} isDisabled={!validActions.canAddNegativePrompt} icon={<PiPlusBold />}>
|
||||
{t('controlLayers.addNegativePrompt')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={addIPAdapter} icon={<PiPlusBold />}>
|
||||
<MenuItem onClick={addIPAdapter} icon={<PiPlusBold />} isDisabled={isAddIPAdapterDisabled}>
|
||||
{t('controlLayers.addIPAdapter')}
|
||||
</MenuItem>
|
||||
</>
|
@ -16,6 +16,8 @@ export const LayerTitle = memo(({ type }: Props) => {
|
||||
return t('controlLayers.globalControlAdapter');
|
||||
} else if (type === 'ip_adapter_layer') {
|
||||
return t('controlLayers.globalIPAdapter');
|
||||
} else if (type === 'initial_image_layer') {
|
||||
return t('controlLayers.globalInitialImage');
|
||||
}
|
||||
}, [t, type]);
|
||||
|
@ -0,0 +1,21 @@
|
||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = PropsWithChildren<{
|
||||
onClick?: () => void;
|
||||
borderColor: ChakraProps['bg'];
|
||||
}>;
|
||||
|
||||
export const LayerWrapper = memo(({ onClick, borderColor, children }: Props) => {
|
||||
return (
|
||||
<Flex gap={2} onClick={onClick} bg={borderColor} px={2} borderRadius="base" py="1px">
|
||||
<Flex flexDir="column" w="full" bg="base.850" borderRadius="base">
|
||||
{children}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
LayerWrapper.displayName = 'LayerWrapper';
|
@ -0,0 +1,83 @@
|
||||
import { Badge, Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
||||
import { AddPromptButtons } from 'features/controlLayers/components/AddPromptButtons';
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import {
|
||||
isRegionalGuidanceLayer,
|
||||
layerSelected,
|
||||
selectControlLayersSlice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import { RGLayerColorPicker } from './RGLayerColorPicker';
|
||||
import { RGLayerIPAdapterList } from './RGLayerIPAdapterList';
|
||||
import { RGLayerNegativePrompt } from './RGLayerNegativePrompt';
|
||||
import { RGLayerPositivePrompt } from './RGLayerPositivePrompt';
|
||||
import RGLayerSettingsPopover from './RGLayerSettingsPopover';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const RGLayer = memo(({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
return {
|
||||
color: rgbColorToString(layer.previewColor),
|
||||
hasPositivePrompt: layer.positivePrompt !== null,
|
||||
hasNegativePrompt: layer.negativePrompt !== null,
|
||||
hasIPAdapters: layer.ipAdapters.length > 0,
|
||||
isSelected: layerId === controlLayers.present.selectedLayerId,
|
||||
autoNegative: layer.autoNegative,
|
||||
};
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
const { autoNegative, color, hasPositivePrompt, hasNegativePrompt, hasIPAdapters, isSelected } =
|
||||
useAppSelector(selector);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(layerSelected(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? color : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerTitle type="regional_guidance_layer" />
|
||||
<Spacer />
|
||||
{autoNegative === 'invert' && (
|
||||
<Badge color="base.300" bg="transparent" borderWidth={1} userSelect="none">
|
||||
{t('controlLayers.autoNegative')}
|
||||
</Badge>
|
||||
)}
|
||||
<RGLayerColorPicker layerId={layerId} />
|
||||
<RGLayerSettingsPopover layerId={layerId} />
|
||||
<LayerMenu layerId={layerId} />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <AddPromptButtons layerId={layerId} />}
|
||||
{hasPositivePrompt && <RGLayerPositivePrompt layerId={layerId} />}
|
||||
{hasNegativePrompt && <RGLayerNegativePrompt layerId={layerId} />}
|
||||
{hasIPAdapters && <RGLayerIPAdapterList layerId={layerId} />}
|
||||
</Flex>
|
||||
)}
|
||||
</LayerWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
RGLayer.displayName = 'RGLayer';
|
@ -3,7 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
isRegionalGuidanceLayer,
|
||||
maskLayerAutoNegativeChanged,
|
||||
rgLayerAutoNegativeChanged,
|
||||
selectControlLayersSlice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { ChangeEvent } from 'react';
|
||||
@ -35,7 +35,7 @@ export const RGLayerAutoNegativeCheckbox = memo(({ layerId }: Props) => {
|
||||
const autoNegative = useAutoNegative(layerId);
|
||||
const onChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(maskLayerAutoNegativeChanged({ layerId, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
||||
dispatch(rgLayerAutoNegativeChanged({ layerId, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
@ -6,7 +6,7 @@ import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
||||
import {
|
||||
isRegionalGuidanceLayer,
|
||||
maskLayerPreviewColorChanged,
|
||||
rgLayerPreviewColorChanged,
|
||||
selectControlLayersSlice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@ -33,7 +33,7 @@ export const RGLayerColorPicker = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onColorChange = useCallback(
|
||||
(color: RgbColor) => {
|
||||
dispatch(maskLayerPreviewColorChanged({ layerId, color }));
|
||||
dispatch(rgLayerPreviewColorChanged({ layerId, color }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
@ -0,0 +1,45 @@
|
||||
import { Divider, Flex } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { RGLayerIPAdapterWrapper } from 'features/controlLayers/components/RGLayer/RGLayerIPAdapterWrapper';
|
||||
import { isRegionalGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const RGLayerIPAdapterList = memo(({ layerId }: Props) => {
|
||||
const selectIPAdapterIds = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.filter(isRegionalGuidanceLayer).find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return layer.ipAdapters;
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
const ipAdapters = useAppSelector(selectIPAdapterIds);
|
||||
|
||||
if (ipAdapters.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ipAdapters.map(({ id }, index) => (
|
||||
<Flex flexDir="column" key={id}>
|
||||
{index > 0 && (
|
||||
<Flex pb={3}>
|
||||
<Divider />
|
||||
</Flex>
|
||||
)}
|
||||
<RGLayerIPAdapterWrapper layerId={layerId} ipAdapterId={id} ipAdapterNumber={index + 1} />
|
||||
</Flex>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
RGLayerIPAdapterList.displayName = 'RGLayerIPAdapterList';
|
@ -0,0 +1,131 @@
|
||||
import { Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { IPAdapter } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapter';
|
||||
import {
|
||||
rgLayerIPAdapterBeginEndStepPctChanged,
|
||||
rgLayerIPAdapterCLIPVisionModelChanged,
|
||||
rgLayerIPAdapterDeleted,
|
||||
rgLayerIPAdapterImageChanged,
|
||||
rgLayerIPAdapterMethodChanged,
|
||||
rgLayerIPAdapterModelChanged,
|
||||
rgLayerIPAdapterWeightChanged,
|
||||
selectRGLayerIPAdapterOrThrow,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { RGLayerIPAdapterImageDropData } from 'features/dnd/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
import type { ImageDTO, IPAdapterModelConfig, RGLayerIPAdapterImagePostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
ipAdapterId: string;
|
||||
ipAdapterNumber: number;
|
||||
};
|
||||
|
||||
export const RGLayerIPAdapterWrapper = memo(({ layerId, ipAdapterId, ipAdapterNumber }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onDeleteIPAdapter = useCallback(() => {
|
||||
dispatch(rgLayerIPAdapterDeleted({ layerId, ipAdapterId }));
|
||||
}, [dispatch, ipAdapterId, layerId]);
|
||||
const ipAdapter = useAppSelector((s) => selectRGLayerIPAdapterOrThrow(s.controlLayers.present, layerId, ipAdapterId));
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
dispatch(
|
||||
rgLayerIPAdapterBeginEndStepPctChanged({
|
||||
layerId,
|
||||
ipAdapterId,
|
||||
beginEndStepPct,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const onChangeWeight = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(rgLayerIPAdapterWeightChanged({ layerId, ipAdapterId, weight }));
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const onChangeIPMethod = useCallback(
|
||||
(method: IPMethodV2) => {
|
||||
dispatch(rgLayerIPAdapterMethodChanged({ layerId, ipAdapterId, method }));
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig) => {
|
||||
dispatch(rgLayerIPAdapterModelChanged({ layerId, ipAdapterId, modelConfig }));
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const onChangeCLIPVisionModel = useCallback(
|
||||
(clipVisionModel: CLIPVisionModelV2) => {
|
||||
dispatch(rgLayerIPAdapterCLIPVisionModelChanged({ layerId, ipAdapterId, clipVisionModel }));
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(rgLayerIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO }));
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const droppableData = useMemo<RGLayerIPAdapterImageDropData>(
|
||||
() => ({
|
||||
actionType: 'SET_RG_LAYER_IP_ADAPTER_IMAGE',
|
||||
context: {
|
||||
layerId,
|
||||
ipAdapterId,
|
||||
},
|
||||
id: layerId,
|
||||
}),
|
||||
[ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const postUploadAction = useMemo<RGLayerIPAdapterImagePostUploadAction>(
|
||||
() => ({
|
||||
type: 'SET_RG_LAYER_IP_ADAPTER_IMAGE',
|
||||
layerId,
|
||||
ipAdapterId,
|
||||
}),
|
||||
[ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={3}>
|
||||
<Flex alignItems="center" gap={3}>
|
||||
<Text fontWeight="semibold" color="base.400">{`IP Adapter ${ipAdapterNumber}`}</Text>
|
||||
<Spacer />
|
||||
<IconButton
|
||||
size="sm"
|
||||
icon={<PiTrashSimpleBold />}
|
||||
aria-label="Delete IP Adapter"
|
||||
onClick={onDeleteIPAdapter}
|
||||
variant="ghost"
|
||||
colorScheme="error"
|
||||
/>
|
||||
</Flex>
|
||||
<IPAdapter
|
||||
ipAdapter={ipAdapter}
|
||||
onChangeBeginEndStepPct={onChangeBeginEndStepPct}
|
||||
onChangeWeight={onChangeWeight}
|
||||
onChangeIPMethod={onChangeIPMethod}
|
||||
onChangeModel={onChangeModel}
|
||||
onChangeCLIPVisionModel={onChangeCLIPVisionModel}
|
||||
onChangeImage={onChangeImage}
|
||||
droppableData={droppableData}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
RGLayerIPAdapterWrapper.displayName = 'RGLayerIPAdapterWrapper';
|
@ -1,8 +1,8 @@
|
||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayerPromptDeleteButton';
|
||||
import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayer/RGLayerPromptDeleteButton';
|
||||
import { useLayerNegativePrompt } from 'features/controlLayers/hooks/layerStateHooks';
|
||||
import { maskLayerNegativePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { rgLayerNegativePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||
@ -21,7 +21,7 @@ export const RGLayerNegativePrompt = memo(({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const _onChange = useCallback(
|
||||
(v: string) => {
|
||||
dispatch(maskLayerNegativePromptChanged({ layerId, prompt: v }));
|
||||
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: v }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
@ -1,8 +1,8 @@
|
||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayerPromptDeleteButton';
|
||||
import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayer/RGLayerPromptDeleteButton';
|
||||
import { useLayerPositivePrompt } from 'features/controlLayers/hooks/layerStateHooks';
|
||||
import { maskLayerPositivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { rgLayerPositivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||
@ -21,7 +21,7 @@ export const RGLayerPositivePrompt = memo(({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const _onChange = useCallback(
|
||||
(v: string) => {
|
||||
dispatch(maskLayerPositivePromptChanged({ layerId, prompt: v }));
|
||||
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: v }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
@ -1,8 +1,8 @@
|
||||
import { IconButton, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import {
|
||||
maskLayerNegativePromptChanged,
|
||||
maskLayerPositivePromptChanged,
|
||||
rgLayerNegativePromptChanged,
|
||||
rgLayerPositivePromptChanged,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -18,9 +18,9 @@ export const RGLayerPromptDeleteButton = memo(({ layerId, polarity }: Props) =>
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
if (polarity === 'positive') {
|
||||
dispatch(maskLayerPositivePromptChanged({ layerId, prompt: null }));
|
||||
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: null }));
|
||||
} else {
|
||||
dispatch(maskLayerNegativePromptChanged({ layerId, prompt: null }));
|
||||
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: null }));
|
||||
}
|
||||
}, [dispatch, layerId, polarity]);
|
||||
return (
|
@ -10,7 +10,7 @@ import {
|
||||
PopoverTrigger,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { RGLayerAutoNegativeCheckbox } from 'features/controlLayers/components/RGLayerAutoNegativeCheckbox';
|
||||
import { RGLayerAutoNegativeCheckbox } from 'features/controlLayers/components/RGLayer/RGLayerAutoNegativeCheckbox';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiGearSixBold } from 'react-icons/pi';
|
@ -1,80 +0,0 @@
|
||||
import { Divider, Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { guidanceLayerIPAdapterDeleted } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig';
|
||||
import { isRegionalGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const RGLayerIPAdapterList = memo(({ layerId }: Props) => {
|
||||
const selectIPAdapterIds = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.filter(isRegionalGuidanceLayer).find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return layer.ipAdapterIds;
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
const ipAdapterIds = useAppSelector(selectIPAdapterIds);
|
||||
|
||||
if (ipAdapterIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ipAdapterIds.map((id, index) => (
|
||||
<Flex flexDir="column" key={id}>
|
||||
{index > 0 && (
|
||||
<Flex pb={3}>
|
||||
<Divider />
|
||||
</Flex>
|
||||
)}
|
||||
<RGLayerIPAdapterListItem layerId={layerId} ipAdapterId={id} ipAdapterNumber={index + 1} />
|
||||
</Flex>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
RGLayerIPAdapterList.displayName = 'RGLayerIPAdapterList';
|
||||
|
||||
type IPAdapterListItemProps = {
|
||||
layerId: string;
|
||||
ipAdapterId: string;
|
||||
ipAdapterNumber: number;
|
||||
};
|
||||
|
||||
const RGLayerIPAdapterListItem = memo(({ layerId, ipAdapterId, ipAdapterNumber }: IPAdapterListItemProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onDeleteIPAdapter = useCallback(() => {
|
||||
dispatch(guidanceLayerIPAdapterDeleted({ layerId, ipAdapterId }));
|
||||
}, [dispatch, ipAdapterId, layerId]);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={3}>
|
||||
<Flex alignItems="center" gap={3}>
|
||||
<Text fontWeight="semibold" color="base.400">{`IP Adapter ${ipAdapterNumber}`}</Text>
|
||||
<Spacer />
|
||||
<IconButton
|
||||
size="sm"
|
||||
icon={<PiTrashSimpleBold />}
|
||||
aria-label="Delete IP Adapter"
|
||||
onClick={onDeleteIPAdapter}
|
||||
variant="ghost"
|
||||
colorScheme="error"
|
||||
/>
|
||||
</Flex>
|
||||
<ControlAdapterLayerConfig id={ipAdapterId} />
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
RGLayerIPAdapterListItem.displayName = 'RGLayerIPAdapterListItem';
|
@ -1,84 +0,0 @@
|
||||
import { Badge, Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerDeleteButton';
|
||||
import { LayerMenu } from 'features/controlLayers/components/LayerMenu';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerVisibilityToggle';
|
||||
import { RGLayerColorPicker } from 'features/controlLayers/components/RGLayerColorPicker';
|
||||
import { RGLayerIPAdapterList } from 'features/controlLayers/components/RGLayerIPAdapterList';
|
||||
import { RGLayerNegativePrompt } from 'features/controlLayers/components/RGLayerNegativePrompt';
|
||||
import { RGLayerPositivePrompt } from 'features/controlLayers/components/RGLayerPositivePrompt';
|
||||
import RGLayerSettingsPopover from 'features/controlLayers/components/RGLayerSettingsPopover';
|
||||
import {
|
||||
isRegionalGuidanceLayer,
|
||||
layerSelected,
|
||||
selectControlLayersSlice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import { AddPromptButtons } from './AddPromptButtons';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const RGLayerListItem = memo(({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
return {
|
||||
color: rgbColorToString(layer.previewColor),
|
||||
hasPositivePrompt: layer.positivePrompt !== null,
|
||||
hasNegativePrompt: layer.negativePrompt !== null,
|
||||
hasIPAdapters: layer.ipAdapterIds.length > 0,
|
||||
isSelected: layerId === controlLayers.present.selectedLayerId,
|
||||
autoNegative: layer.autoNegative,
|
||||
};
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
const { autoNegative, color, hasPositivePrompt, hasNegativePrompt, hasIPAdapters, isSelected } =
|
||||
useAppSelector(selector);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(layerSelected(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
return (
|
||||
<Flex gap={2} onClick={onClick} bg={isSelected ? color : 'base.800'} px={2} borderRadius="base" py="1px">
|
||||
<Flex flexDir="column" w="full" bg="base.850" borderRadius="base">
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerTitle type="regional_guidance_layer" />
|
||||
<Spacer />
|
||||
{autoNegative === 'invert' && (
|
||||
<Badge color="base.300" bg="transparent" borderWidth={1} userSelect="none">
|
||||
{t('controlLayers.autoNegative')}
|
||||
</Badge>
|
||||
)}
|
||||
<RGLayerColorPicker layerId={layerId} />
|
||||
<RGLayerSettingsPopover layerId={layerId} />
|
||||
<LayerMenu layerId={layerId} />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <AddPromptButtons layerId={layerId} />}
|
||||
{hasPositivePrompt && <RGLayerPositivePrompt layerId={layerId} />}
|
||||
{hasNegativePrompt && <RGLayerNegativePrompt layerId={layerId} />}
|
||||
{hasIPAdapters && <RGLayerIPAdapterList layerId={layerId} />}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
RGLayerListItem.displayName = 'RGLayerListItem';
|
@ -7,7 +7,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useMouseEvents } from 'features/controlLayers/hooks/mouseEventHooks';
|
||||
import {
|
||||
$cursorPosition,
|
||||
$isMouseOver,
|
||||
$lastMouseDownPos,
|
||||
$tool,
|
||||
isRegionalGuidanceLayer,
|
||||
@ -48,10 +47,9 @@ const useStageRenderer = (
|
||||
const dispatch = useAppDispatch();
|
||||
const state = useAppSelector((s) => s.controlLayers.present);
|
||||
const tool = useStore($tool);
|
||||
const { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel } = useMouseEvents();
|
||||
const mouseEventHandlers = useMouseEvents();
|
||||
const cursorPosition = useStore($cursorPosition);
|
||||
const lastMouseDownPos = useStore($lastMouseDownPos);
|
||||
const isMouseOver = useStore($isMouseOver);
|
||||
const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor);
|
||||
const selectedLayerType = useAppSelector(selectSelectedLayerType);
|
||||
const layerIds = useMemo(() => state.layers.map((l) => l.id), [state.layers]);
|
||||
@ -90,23 +88,21 @@ const useStageRenderer = (
|
||||
if (asPreview) {
|
||||
return;
|
||||
}
|
||||
stage.on('mousedown', onMouseDown);
|
||||
stage.on('mouseup', onMouseUp);
|
||||
stage.on('mousemove', onMouseMove);
|
||||
stage.on('mouseenter', onMouseEnter);
|
||||
stage.on('mouseleave', onMouseLeave);
|
||||
stage.on('wheel', onMouseWheel);
|
||||
stage.on('mousedown', mouseEventHandlers.onMouseDown);
|
||||
stage.on('mouseup', mouseEventHandlers.onMouseUp);
|
||||
stage.on('mousemove', mouseEventHandlers.onMouseMove);
|
||||
stage.on('mouseleave', mouseEventHandlers.onMouseLeave);
|
||||
stage.on('wheel', mouseEventHandlers.onMouseWheel);
|
||||
|
||||
return () => {
|
||||
log.trace('Cleaning up stage listeners');
|
||||
stage.off('mousedown', onMouseDown);
|
||||
stage.off('mouseup', onMouseUp);
|
||||
stage.off('mousemove', onMouseMove);
|
||||
stage.off('mouseenter', onMouseEnter);
|
||||
stage.off('mouseleave', onMouseLeave);
|
||||
stage.off('wheel', onMouseWheel);
|
||||
stage.off('mousedown', mouseEventHandlers.onMouseDown);
|
||||
stage.off('mouseup', mouseEventHandlers.onMouseUp);
|
||||
stage.off('mousemove', mouseEventHandlers.onMouseMove);
|
||||
stage.off('mouseleave', mouseEventHandlers.onMouseLeave);
|
||||
stage.off('wheel', mouseEventHandlers.onMouseWheel);
|
||||
};
|
||||
}, [stage, asPreview, onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel]);
|
||||
}, [stage, asPreview, mouseEventHandlers]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
log.trace('Updating stage dimensions');
|
||||
@ -147,7 +143,6 @@ const useStageRenderer = (
|
||||
state.globalMaskLayerOpacity,
|
||||
cursorPosition,
|
||||
lastMouseDownPos,
|
||||
isMouseOver,
|
||||
state.brushSize
|
||||
);
|
||||
}, [
|
||||
@ -159,7 +154,6 @@ const useStageRenderer = (
|
||||
state.globalMaskLayerOpacity,
|
||||
cursorPosition,
|
||||
lastMouseDownPos,
|
||||
isMouseOver,
|
||||
state.brushSize,
|
||||
renderers,
|
||||
]);
|
||||
|
@ -1,237 +0,0 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Box, Flex, Spinner, useShiftModifier } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
import { useControlAdapterControlImage } from 'features/controlAdapters/hooks/useControlAdapterControlImage';
|
||||
import { useControlAdapterProcessedControlImage } from 'features/controlAdapters/hooks/useControlAdapterProcessedControlImage';
|
||||
import { useControlAdapterProcessorType } from 'features/controlAdapters/hooks/useControlAdapterProcessorType';
|
||||
import {
|
||||
controlAdapterImageChanged,
|
||||
selectControlAdaptersSlice,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi';
|
||||
import {
|
||||
useAddImageToBoardMutation,
|
||||
useChangeImageIsIntermediateMutation,
|
||||
useGetImageDTOQuery,
|
||||
useRemoveImageFromBoardMutation,
|
||||
} from 'services/api/endpoints/images';
|
||||
import type { PostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
isSmall?: boolean;
|
||||
};
|
||||
|
||||
const selectPendingControlImages = createMemoizedSelector(
|
||||
selectControlAdaptersSlice,
|
||||
(controlAdapters) => controlAdapters.pendingControlImages
|
||||
);
|
||||
|
||||
const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const controlImageName = useControlAdapterControlImage(id);
|
||||
const processedControlImageName = useControlAdapterProcessedControlImage(id);
|
||||
const processorType = useControlAdapterProcessorType(id);
|
||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const pendingControlImages = useAppSelector(selectPendingControlImages);
|
||||
const shift = useShiftModifier();
|
||||
|
||||
const [isMouseOverImage, setIsMouseOverImage] = useState(false);
|
||||
|
||||
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(
|
||||
controlImageName ?? skipToken
|
||||
);
|
||||
|
||||
const { currentData: processedControlImage, isError: isErrorProcessedControlImage } = useGetImageDTOQuery(
|
||||
processedControlImageName ?? skipToken
|
||||
);
|
||||
|
||||
const [changeIsIntermediate] = useChangeImageIsIntermediateMutation();
|
||||
const [addToBoard] = useAddImageToBoardMutation();
|
||||
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
||||
const handleResetControlImage = useCallback(() => {
|
||||
dispatch(controlAdapterImageChanged({ id, controlImage: null }));
|
||||
}, [id, dispatch]);
|
||||
|
||||
const handleSaveControlImage = useCallback(async () => {
|
||||
if (!processedControlImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
await changeIsIntermediate({
|
||||
imageDTO: processedControlImage,
|
||||
is_intermediate: false,
|
||||
}).unwrap();
|
||||
|
||||
if (autoAddBoardId !== 'none') {
|
||||
addToBoard({
|
||||
imageDTO: processedControlImage,
|
||||
board_id: autoAddBoardId,
|
||||
});
|
||||
} else {
|
||||
removeFromBoard({ imageDTO: processedControlImage });
|
||||
}
|
||||
}, [processedControlImage, changeIsIntermediate, autoAddBoardId, addToBoard, removeFromBoard]);
|
||||
|
||||
const handleSetControlImageToDimensions = useCallback(() => {
|
||||
if (!controlImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeTabName === 'unifiedCanvas') {
|
||||
dispatch(setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension));
|
||||
} else {
|
||||
if (shift) {
|
||||
const { width, height } = controlImage;
|
||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
||||
}
|
||||
}
|
||||
}, [controlImage, activeTabName, dispatch, optimalDimension, shift]);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
setIsMouseOverImage(true);
|
||||
}, []);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsMouseOverImage(false);
|
||||
}, []);
|
||||
|
||||
const draggableData = useMemo<TypesafeDraggableData | undefined>(() => {
|
||||
if (controlImage) {
|
||||
return {
|
||||
id,
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO: controlImage },
|
||||
};
|
||||
}
|
||||
}, [controlImage, id]);
|
||||
|
||||
const droppableData = useMemo<TypesafeDroppableData | undefined>(
|
||||
() => ({
|
||||
id,
|
||||
actionType: 'SET_CONTROL_ADAPTER_IMAGE',
|
||||
context: { id },
|
||||
}),
|
||||
[id]
|
||||
);
|
||||
|
||||
const postUploadAction = useMemo<PostUploadAction>(() => ({ type: 'SET_CONTROL_ADAPTER_IMAGE', id }), [id]);
|
||||
|
||||
const shouldShowProcessedImage =
|
||||
controlImage &&
|
||||
processedControlImage &&
|
||||
!isMouseOverImage &&
|
||||
!pendingControlImages.includes(id) &&
|
||||
processorType !== 'none';
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && (isErrorControlImage || isErrorProcessedControlImage)) {
|
||||
handleResetControlImage();
|
||||
}
|
||||
}, [handleResetControlImage, isConnected, isErrorControlImage, isErrorProcessedControlImage]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
position="relative"
|
||||
w="full"
|
||||
h={isSmall ? 36 : 366} // magic no touch
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={controlImage}
|
||||
isDropDisabled={shouldShowProcessedImage}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
insetInlineStart={0}
|
||||
w="full"
|
||||
h="full"
|
||||
opacity={shouldShowProcessedImage ? 1 : 0}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
pointerEvents="none"
|
||||
>
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={processedControlImage}
|
||||
isUploadDisabled={true}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleResetControlImage}
|
||||
icon={controlImage ? <PiArrowCounterClockwiseBold size={16} /> : undefined}
|
||||
tooltip={t('controlnet.resetControlImage')}
|
||||
/>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleSaveControlImage}
|
||||
icon={controlImage ? <PiFloppyDiskBold size={16} /> : undefined}
|
||||
tooltip={t('controlnet.saveControlImage')}
|
||||
styleOverrides={saveControlImageStyleOverrides}
|
||||
/>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleSetControlImageToDimensions}
|
||||
icon={controlImage ? <PiRulerBold size={16} /> : undefined}
|
||||
tooltip={shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')}
|
||||
styleOverrides={setControlImageDimensionsStyleOverrides}
|
||||
/>
|
||||
</>
|
||||
|
||||
{pendingControlImages.includes(id) && (
|
||||
<Flex
|
||||
position="absolute"
|
||||
top={0}
|
||||
insetInlineStart={0}
|
||||
w="full"
|
||||
h="full"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
opacity={0.8}
|
||||
borderRadius="base"
|
||||
bg="base.900"
|
||||
>
|
||||
<Spinner size="xl" color="base.400" />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ControlAdapterImagePreview);
|
||||
|
||||
const saveControlImageStyleOverrides: SystemStyleObject = { mt: 6 };
|
||||
const setControlImageDimensionsStyleOverrides: SystemStyleObject = { mt: 12 };
|
@ -1,72 +0,0 @@
|
||||
import { Box, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
||||
import ControlAdapterProcessorComponent from 'features/controlAdapters/components/ControlAdapterProcessorComponent';
|
||||
import ControlAdapterShouldAutoConfig from 'features/controlAdapters/components/ControlAdapterShouldAutoConfig';
|
||||
import ParamControlAdapterIPMethod from 'features/controlAdapters/components/parameters/ParamControlAdapterIPMethod';
|
||||
import ParamControlAdapterProcessorSelect from 'features/controlAdapters/components/parameters/ParamControlAdapterProcessorSelect';
|
||||
import { useControlAdapterType } from 'features/controlAdapters/hooks/useControlAdapterType';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretUpBold } from 'react-icons/pi';
|
||||
import { useToggle } from 'react-use';
|
||||
|
||||
import ControlAdapterImagePreview from './ControlAdapterImagePreview';
|
||||
import { ParamControlAdapterBeginEnd } from './ParamControlAdapterBeginEnd';
|
||||
import ParamControlAdapterControlMode from './ParamControlAdapterControlMode';
|
||||
import ParamControlAdapterModel from './ParamControlAdapterModel';
|
||||
import ParamControlAdapterWeight from './ParamControlAdapterWeight';
|
||||
|
||||
const ControlAdapterLayerConfig = (props: { id: string }) => {
|
||||
const { id } = props;
|
||||
const controlAdapterType = useControlAdapterType(id);
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={4} position="relative" w="full">
|
||||
<Flex gap={3} alignItems="center" w="full">
|
||||
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
|
||||
<ParamControlAdapterModel id={id} />{' '}
|
||||
</Box>
|
||||
|
||||
{controlAdapterType !== 'ip_adapter' && (
|
||||
<IconButton
|
||||
size="sm"
|
||||
tooltip={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
|
||||
aria-label={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
|
||||
onClick={toggleIsExpanded}
|
||||
variant="ghost"
|
||||
icon={
|
||||
<Icon
|
||||
boxSize={4}
|
||||
as={PiCaretUpBold}
|
||||
transform={isExpanded ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex gap={4} w="full" alignItems="center">
|
||||
<Flex flexDir="column" gap={3} w="full">
|
||||
{controlAdapterType === 'ip_adapter' && <ParamControlAdapterIPMethod id={id} />}
|
||||
{controlAdapterType === 'controlnet' && <ParamControlAdapterControlMode id={id} />}
|
||||
<ParamControlAdapterWeight id={id} />
|
||||
<ParamControlAdapterBeginEnd id={id} />
|
||||
</Flex>
|
||||
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||
<ControlAdapterImagePreview id={id} isSmall />
|
||||
</Flex>
|
||||
</Flex>
|
||||
{isExpanded && (
|
||||
<>
|
||||
<ControlAdapterShouldAutoConfig id={id} />
|
||||
<ParamControlAdapterProcessorSelect id={id} />
|
||||
<ControlAdapterProcessorComponent id={id} />
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ControlAdapterLayerConfig);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user