mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Compare commits
29 Commits
v4.2.9.dev
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
87261bdbc9 | ||
|
4e4b6c6dbc | ||
|
5e8cf9fb6a | ||
|
c738fe051f | ||
|
29fe1533f2 | ||
|
77090070bd | ||
|
6ba9b1b6b0 | ||
|
c578b8df1e | ||
|
cad9a41433 | ||
|
5fefb3b0f4 | ||
|
5284a870b0 | ||
|
e064377c05 | ||
|
3e569c8312 | ||
|
16825ee6e9 | ||
|
3f5340fa53 | ||
|
f2a1a39b33 | ||
|
326de55d3e | ||
|
b2df909570 | ||
|
026ac36b06 | ||
|
92125e5fd2 | ||
|
c0c139da88 | ||
|
404ad6a7fd | ||
|
fc39086fb4 | ||
|
cd215700fe | ||
|
e97fd85904 | ||
|
0a263fa5b1 | ||
|
fae3836a8d | ||
|
b3d2eb4178 | ||
|
576f1cbb75 |
@ -11,7 +11,6 @@ from invokeai.app.services.session_queue.session_queue_common import (
|
|||||||
Batch,
|
Batch,
|
||||||
BatchStatus,
|
BatchStatus,
|
||||||
CancelByBatchIDsResult,
|
CancelByBatchIDsResult,
|
||||||
CancelByOriginResult,
|
|
||||||
ClearResult,
|
ClearResult,
|
||||||
EnqueueBatchResult,
|
EnqueueBatchResult,
|
||||||
PruneResult,
|
PruneResult,
|
||||||
@ -106,19 +105,6 @@ async def cancel_by_batch_ids(
|
|||||||
return ApiDependencies.invoker.services.session_queue.cancel_by_batch_ids(queue_id=queue_id, batch_ids=batch_ids)
|
return ApiDependencies.invoker.services.session_queue.cancel_by_batch_ids(queue_id=queue_id, batch_ids=batch_ids)
|
||||||
|
|
||||||
|
|
||||||
@session_queue_router.put(
|
|
||||||
"/{queue_id}/cancel_by_origin",
|
|
||||||
operation_id="cancel_by_origin",
|
|
||||||
responses={200: {"model": CancelByBatchIDsResult}},
|
|
||||||
)
|
|
||||||
async def cancel_by_origin(
|
|
||||||
queue_id: str = Path(description="The queue id to perform this operation on"),
|
|
||||||
origin: str = Query(description="The origin to cancel all queue items for"),
|
|
||||||
) -> CancelByOriginResult:
|
|
||||||
"""Immediately cancels all queue items with the given origin"""
|
|
||||||
return ApiDependencies.invoker.services.session_queue.cancel_by_origin(queue_id=queue_id, origin=origin)
|
|
||||||
|
|
||||||
|
|
||||||
@session_queue_router.put(
|
@session_queue_router.put(
|
||||||
"/{queue_id}/clear",
|
"/{queue_id}/clear",
|
||||||
operation_id="clear",
|
operation_id="clear",
|
||||||
|
@ -45,11 +45,13 @@ class UIType(str, Enum, metaclass=MetaEnum):
|
|||||||
SDXLRefinerModel = "SDXLRefinerModelField"
|
SDXLRefinerModel = "SDXLRefinerModelField"
|
||||||
ONNXModel = "ONNXModelField"
|
ONNXModel = "ONNXModelField"
|
||||||
VAEModel = "VAEModelField"
|
VAEModel = "VAEModelField"
|
||||||
|
FluxVAEModel = "FluxVAEModelField"
|
||||||
LoRAModel = "LoRAModelField"
|
LoRAModel = "LoRAModelField"
|
||||||
ControlNetModel = "ControlNetModelField"
|
ControlNetModel = "ControlNetModelField"
|
||||||
IPAdapterModel = "IPAdapterModelField"
|
IPAdapterModel = "IPAdapterModelField"
|
||||||
T2IAdapterModel = "T2IAdapterModelField"
|
T2IAdapterModel = "T2IAdapterModelField"
|
||||||
T5EncoderModel = "T5EncoderModelField"
|
T5EncoderModel = "T5EncoderModelField"
|
||||||
|
CLIPEmbedModel = "CLIPEmbedModelField"
|
||||||
SpandrelImageToImageModel = "SpandrelImageToImageModelField"
|
SpandrelImageToImageModel = "SpandrelImageToImageModelField"
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
@ -128,6 +130,7 @@ class FieldDescriptions:
|
|||||||
noise = "Noise tensor"
|
noise = "Noise tensor"
|
||||||
clip = "CLIP (tokenizer, text encoder, LoRAs) and skipped layer count"
|
clip = "CLIP (tokenizer, text encoder, LoRAs) and skipped layer count"
|
||||||
t5_encoder = "T5 tokenizer and text encoder"
|
t5_encoder = "T5 tokenizer and text encoder"
|
||||||
|
clip_embed_model = "CLIP Embed loader"
|
||||||
unet = "UNet (scheduler, LoRAs)"
|
unet = "UNet (scheduler, LoRAs)"
|
||||||
transformer = "Transformer"
|
transformer = "Transformer"
|
||||||
vae = "VAE"
|
vae = "VAE"
|
||||||
|
@ -40,7 +40,10 @@ class FluxTextEncoderInvocation(BaseInvocation):
|
|||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def invoke(self, context: InvocationContext) -> FluxConditioningOutput:
|
def invoke(self, context: InvocationContext) -> FluxConditioningOutput:
|
||||||
t5_embeddings, clip_embeddings = self._encode_prompt(context)
|
# Note: The T5 and CLIP encoding are done in separate functions to ensure that all model references are locally
|
||||||
|
# scoped. This ensures that the T5 model can be freed and gc'd before loading the CLIP model (if necessary).
|
||||||
|
t5_embeddings = self._t5_encode(context)
|
||||||
|
clip_embeddings = self._clip_encode(context)
|
||||||
conditioning_data = ConditioningFieldData(
|
conditioning_data = ConditioningFieldData(
|
||||||
conditionings=[FLUXConditioningInfo(clip_embeds=clip_embeddings, t5_embeds=t5_embeddings)]
|
conditionings=[FLUXConditioningInfo(clip_embeds=clip_embeddings, t5_embeds=t5_embeddings)]
|
||||||
)
|
)
|
||||||
@ -48,12 +51,7 @@ class FluxTextEncoderInvocation(BaseInvocation):
|
|||||||
conditioning_name = context.conditioning.save(conditioning_data)
|
conditioning_name = context.conditioning.save(conditioning_data)
|
||||||
return FluxConditioningOutput.build(conditioning_name)
|
return FluxConditioningOutput.build(conditioning_name)
|
||||||
|
|
||||||
def _encode_prompt(self, context: InvocationContext) -> tuple[torch.Tensor, torch.Tensor]:
|
def _t5_encode(self, context: InvocationContext) -> torch.Tensor:
|
||||||
# Load CLIP.
|
|
||||||
clip_tokenizer_info = context.models.load(self.clip.tokenizer)
|
|
||||||
clip_text_encoder_info = context.models.load(self.clip.text_encoder)
|
|
||||||
|
|
||||||
# Load T5.
|
|
||||||
t5_tokenizer_info = context.models.load(self.t5_encoder.tokenizer)
|
t5_tokenizer_info = context.models.load(self.t5_encoder.tokenizer)
|
||||||
t5_text_encoder_info = context.models.load(self.t5_encoder.text_encoder)
|
t5_text_encoder_info = context.models.load(self.t5_encoder.text_encoder)
|
||||||
|
|
||||||
@ -70,6 +68,15 @@ class FluxTextEncoderInvocation(BaseInvocation):
|
|||||||
|
|
||||||
prompt_embeds = t5_encoder(prompt)
|
prompt_embeds = t5_encoder(prompt)
|
||||||
|
|
||||||
|
assert isinstance(prompt_embeds, torch.Tensor)
|
||||||
|
return prompt_embeds
|
||||||
|
|
||||||
|
def _clip_encode(self, context: InvocationContext) -> torch.Tensor:
|
||||||
|
clip_tokenizer_info = context.models.load(self.clip.tokenizer)
|
||||||
|
clip_text_encoder_info = context.models.load(self.clip.text_encoder)
|
||||||
|
|
||||||
|
prompt = [self.prompt]
|
||||||
|
|
||||||
with (
|
with (
|
||||||
clip_text_encoder_info as clip_text_encoder,
|
clip_text_encoder_info as clip_text_encoder,
|
||||||
clip_tokenizer_info as clip_tokenizer,
|
clip_tokenizer_info as clip_tokenizer,
|
||||||
@ -81,6 +88,5 @@ class FluxTextEncoderInvocation(BaseInvocation):
|
|||||||
|
|
||||||
pooled_prompt_embeds = clip_encoder(prompt)
|
pooled_prompt_embeds = clip_encoder(prompt)
|
||||||
|
|
||||||
assert isinstance(prompt_embeds, torch.Tensor)
|
|
||||||
assert isinstance(pooled_prompt_embeds, torch.Tensor)
|
assert isinstance(pooled_prompt_embeds, torch.Tensor)
|
||||||
return prompt_embeds, pooled_prompt_embeds
|
return pooled_prompt_embeds
|
||||||
|
@ -58,13 +58,7 @@ class FluxTextToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
# Load the conditioning data.
|
latents = self._run_diffusion(context)
|
||||||
cond_data = context.conditioning.load(self.positive_text_conditioning.conditioning_name)
|
|
||||||
assert len(cond_data.conditionings) == 1
|
|
||||||
flux_conditioning = cond_data.conditionings[0]
|
|
||||||
assert isinstance(flux_conditioning, FLUXConditioningInfo)
|
|
||||||
|
|
||||||
latents = self._run_diffusion(context, flux_conditioning.clip_embeds, flux_conditioning.t5_embeds)
|
|
||||||
image = self._run_vae_decoding(context, latents)
|
image = self._run_vae_decoding(context, latents)
|
||||||
image_dto = context.images.save(image=image)
|
image_dto = context.images.save(image=image)
|
||||||
return ImageOutput.build(image_dto)
|
return ImageOutput.build(image_dto)
|
||||||
@ -72,12 +66,20 @@ class FluxTextToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|||||||
def _run_diffusion(
|
def _run_diffusion(
|
||||||
self,
|
self,
|
||||||
context: InvocationContext,
|
context: InvocationContext,
|
||||||
clip_embeddings: torch.Tensor,
|
|
||||||
t5_embeddings: torch.Tensor,
|
|
||||||
):
|
):
|
||||||
transformer_info = context.models.load(self.transformer.transformer)
|
|
||||||
inference_dtype = torch.bfloat16
|
inference_dtype = torch.bfloat16
|
||||||
|
|
||||||
|
# Load the conditioning data.
|
||||||
|
cond_data = context.conditioning.load(self.positive_text_conditioning.conditioning_name)
|
||||||
|
assert len(cond_data.conditionings) == 1
|
||||||
|
flux_conditioning = cond_data.conditionings[0]
|
||||||
|
assert isinstance(flux_conditioning, FLUXConditioningInfo)
|
||||||
|
flux_conditioning = flux_conditioning.to(dtype=inference_dtype)
|
||||||
|
t5_embeddings = flux_conditioning.t5_embeds
|
||||||
|
clip_embeddings = flux_conditioning.clip_embeds
|
||||||
|
|
||||||
|
transformer_info = context.models.load(self.transformer.transformer)
|
||||||
|
|
||||||
# Prepare input noise.
|
# Prepare input noise.
|
||||||
x = get_noise(
|
x = get_noise(
|
||||||
num_samples=1,
|
num_samples=1,
|
||||||
@ -88,24 +90,19 @@ class FluxTextToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|||||||
seed=self.seed,
|
seed=self.seed,
|
||||||
)
|
)
|
||||||
|
|
||||||
img, img_ids = prepare_latent_img_patches(x)
|
x, img_ids = prepare_latent_img_patches(x)
|
||||||
|
|
||||||
is_schnell = "schnell" in transformer_info.config.config_path
|
is_schnell = "schnell" in transformer_info.config.config_path
|
||||||
|
|
||||||
timesteps = get_schedule(
|
timesteps = get_schedule(
|
||||||
num_steps=self.num_steps,
|
num_steps=self.num_steps,
|
||||||
image_seq_len=img.shape[1],
|
image_seq_len=x.shape[1],
|
||||||
shift=not is_schnell,
|
shift=not is_schnell,
|
||||||
)
|
)
|
||||||
|
|
||||||
bs, t5_seq_len, _ = t5_embeddings.shape
|
bs, t5_seq_len, _ = t5_embeddings.shape
|
||||||
txt_ids = torch.zeros(bs, t5_seq_len, 3, dtype=inference_dtype, device=TorchDevice.choose_torch_device())
|
txt_ids = torch.zeros(bs, t5_seq_len, 3, dtype=inference_dtype, device=TorchDevice.choose_torch_device())
|
||||||
|
|
||||||
# HACK(ryand): Manually empty the cache. Currently we don't check the size of the model before loading it from
|
|
||||||
# disk. Since the transformer model is large (24GB), there's a good chance that it will OOM on 32GB RAM systems
|
|
||||||
# if the cache is not empty.
|
|
||||||
context.models._services.model_manager.load.ram_cache.make_room(24 * 2**30)
|
|
||||||
|
|
||||||
with transformer_info as transformer:
|
with transformer_info as transformer:
|
||||||
assert isinstance(transformer, Flux)
|
assert isinstance(transformer, Flux)
|
||||||
|
|
||||||
@ -140,7 +137,7 @@ class FluxTextToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|||||||
|
|
||||||
x = denoise(
|
x = denoise(
|
||||||
model=transformer,
|
model=transformer,
|
||||||
img=img,
|
img=x,
|
||||||
img_ids=img_ids,
|
img_ids=img_ids,
|
||||||
txt=t5_embeddings,
|
txt=t5_embeddings,
|
||||||
txt_ids=txt_ids,
|
txt_ids=txt_ids,
|
||||||
|
@ -6,19 +6,13 @@ import cv2
|
|||||||
import numpy
|
import numpy
|
||||||
from PIL import Image, ImageChops, ImageFilter, ImageOps
|
from PIL import Image, ImageChops, ImageFilter, ImageOps
|
||||||
|
|
||||||
from invokeai.app.invocations.baseinvocation import (
|
from invokeai.app.invocations.baseinvocation import BaseInvocation, Classification, invocation
|
||||||
BaseInvocation,
|
|
||||||
Classification,
|
|
||||||
invocation,
|
|
||||||
invocation_output,
|
|
||||||
)
|
|
||||||
from invokeai.app.invocations.constants import IMAGE_MODES
|
from invokeai.app.invocations.constants import IMAGE_MODES
|
||||||
from invokeai.app.invocations.fields import (
|
from invokeai.app.invocations.fields import (
|
||||||
ColorField,
|
ColorField,
|
||||||
FieldDescriptions,
|
FieldDescriptions,
|
||||||
ImageField,
|
ImageField,
|
||||||
InputField,
|
InputField,
|
||||||
OutputField,
|
|
||||||
WithBoard,
|
WithBoard,
|
||||||
WithMetadata,
|
WithMetadata,
|
||||||
)
|
)
|
||||||
@ -1013,62 +1007,3 @@ class MaskFromIDInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|||||||
image_dto = context.images.save(image=mask, image_category=ImageCategory.MASK)
|
image_dto = context.images.save(image=mask, image_category=ImageCategory.MASK)
|
||||||
|
|
||||||
return ImageOutput.build(image_dto)
|
return ImageOutput.build(image_dto)
|
||||||
|
|
||||||
|
|
||||||
@invocation_output("canvas_v2_mask_and_crop_output")
|
|
||||||
class CanvasV2MaskAndCropOutput(ImageOutput):
|
|
||||||
offset_x: int = OutputField(description="The x offset of the image, after cropping")
|
|
||||||
offset_y: int = OutputField(description="The y offset of the image, after cropping")
|
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
|
||||||
"canvas_v2_mask_and_crop",
|
|
||||||
title="Canvas V2 Mask and Crop",
|
|
||||||
tags=["image", "mask", "id"],
|
|
||||||
category="image",
|
|
||||||
version="1.0.0",
|
|
||||||
classification=Classification.Prototype,
|
|
||||||
)
|
|
||||||
class CanvasV2MaskAndCropInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|
||||||
"""Handles Canvas V2 image output masking and cropping"""
|
|
||||||
|
|
||||||
source_image: ImageField | None = InputField(
|
|
||||||
default=None,
|
|
||||||
description="The source image onto which the masked generated image is pasted. If omitted, the masked generated image is returned with transparency.",
|
|
||||||
)
|
|
||||||
generated_image: ImageField = InputField(description="The image to apply the mask to")
|
|
||||||
mask: ImageField = InputField(description="The mask to apply")
|
|
||||||
mask_blur: int = InputField(default=0, ge=0, description="The amount to blur the mask by")
|
|
||||||
|
|
||||||
def _prepare_mask(self, mask: Image.Image) -> Image.Image:
|
|
||||||
mask_array = numpy.array(mask)
|
|
||||||
kernel = numpy.ones((self.mask_blur, self.mask_blur), numpy.uint8)
|
|
||||||
dilated_mask_array = cv2.erode(mask_array, kernel, iterations=3)
|
|
||||||
dilated_mask = Image.fromarray(dilated_mask_array)
|
|
||||||
if self.mask_blur > 0:
|
|
||||||
mask = dilated_mask.filter(ImageFilter.GaussianBlur(self.mask_blur))
|
|
||||||
return ImageOps.invert(mask.convert("L"))
|
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> CanvasV2MaskAndCropOutput:
|
|
||||||
mask = self._prepare_mask(context.images.get_pil(self.mask.image_name))
|
|
||||||
|
|
||||||
if self.source_image:
|
|
||||||
generated_image = context.images.get_pil(self.generated_image.image_name)
|
|
||||||
source_image = context.images.get_pil(self.source_image.image_name)
|
|
||||||
source_image.paste(generated_image, (0, 0), mask)
|
|
||||||
image_dto = context.images.save(image=source_image)
|
|
||||||
else:
|
|
||||||
generated_image = context.images.get_pil(self.generated_image.image_name)
|
|
||||||
generated_image.putalpha(mask)
|
|
||||||
image_dto = context.images.save(image=generated_image)
|
|
||||||
|
|
||||||
# bbox = image.getbbox()
|
|
||||||
# image = image.crop(bbox)
|
|
||||||
|
|
||||||
return CanvasV2MaskAndCropOutput(
|
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
|
||||||
offset_x=0,
|
|
||||||
offset_y=0,
|
|
||||||
width=image_dto.width,
|
|
||||||
height=image_dto.height,
|
|
||||||
)
|
|
||||||
|
@ -157,7 +157,7 @@ class FluxModelLoaderOutput(BaseInvocationOutput):
|
|||||||
title="Flux Main Model",
|
title="Flux Main Model",
|
||||||
tags=["model", "flux"],
|
tags=["model", "flux"],
|
||||||
category="model",
|
category="model",
|
||||||
version="1.0.3",
|
version="1.0.4",
|
||||||
classification=Classification.Prototype,
|
classification=Classification.Prototype,
|
||||||
)
|
)
|
||||||
class FluxModelLoaderInvocation(BaseInvocation):
|
class FluxModelLoaderInvocation(BaseInvocation):
|
||||||
@ -169,23 +169,35 @@ class FluxModelLoaderInvocation(BaseInvocation):
|
|||||||
input=Input.Direct,
|
input=Input.Direct,
|
||||||
)
|
)
|
||||||
|
|
||||||
t5_encoder: ModelIdentifierField = InputField(
|
t5_encoder_model: ModelIdentifierField = InputField(
|
||||||
description=FieldDescriptions.t5_encoder,
|
description=FieldDescriptions.t5_encoder, ui_type=UIType.T5EncoderModel, input=Input.Direct, title="T5 Encoder"
|
||||||
ui_type=UIType.T5EncoderModel,
|
)
|
||||||
|
|
||||||
|
clip_embed_model: ModelIdentifierField = InputField(
|
||||||
|
description=FieldDescriptions.clip_embed_model,
|
||||||
|
ui_type=UIType.CLIPEmbedModel,
|
||||||
input=Input.Direct,
|
input=Input.Direct,
|
||||||
|
title="CLIP Embed",
|
||||||
|
)
|
||||||
|
|
||||||
|
vae_model: ModelIdentifierField = InputField(
|
||||||
|
description=FieldDescriptions.vae_model, ui_type=UIType.FluxVAEModel, title="VAE"
|
||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> FluxModelLoaderOutput:
|
def invoke(self, context: InvocationContext) -> FluxModelLoaderOutput:
|
||||||
model_key = self.model.key
|
for key in [self.model.key, self.t5_encoder_model.key, self.clip_embed_model.key, self.vae_model.key]:
|
||||||
|
if not context.models.exists(key):
|
||||||
|
raise ValueError(f"Unknown model: {key}")
|
||||||
|
|
||||||
|
transformer = self.model.model_copy(update={"submodel_type": SubModelType.Transformer})
|
||||||
|
vae = self.vae_model.model_copy(update={"submodel_type": SubModelType.VAE})
|
||||||
|
|
||||||
|
tokenizer = self.clip_embed_model.model_copy(update={"submodel_type": SubModelType.Tokenizer})
|
||||||
|
clip_encoder = self.clip_embed_model.model_copy(update={"submodel_type": SubModelType.TextEncoder})
|
||||||
|
|
||||||
|
tokenizer2 = self.t5_encoder_model.model_copy(update={"submodel_type": SubModelType.Tokenizer2})
|
||||||
|
t5_encoder = self.t5_encoder_model.model_copy(update={"submodel_type": SubModelType.TextEncoder2})
|
||||||
|
|
||||||
if not context.models.exists(model_key):
|
|
||||||
raise ValueError(f"Unknown model: {model_key}")
|
|
||||||
transformer = self._get_model(context, SubModelType.Transformer)
|
|
||||||
tokenizer = self._get_model(context, SubModelType.Tokenizer)
|
|
||||||
tokenizer2 = self._get_model(context, SubModelType.Tokenizer2)
|
|
||||||
clip_encoder = self._get_model(context, SubModelType.TextEncoder)
|
|
||||||
t5_encoder = self._get_model(context, SubModelType.TextEncoder2)
|
|
||||||
vae = self._get_model(context, SubModelType.VAE)
|
|
||||||
transformer_config = context.models.get_config(transformer)
|
transformer_config = context.models.get_config(transformer)
|
||||||
assert isinstance(transformer_config, CheckpointConfigBase)
|
assert isinstance(transformer_config, CheckpointConfigBase)
|
||||||
|
|
||||||
@ -197,52 +209,6 @@ class FluxModelLoaderInvocation(BaseInvocation):
|
|||||||
max_seq_len=max_seq_lengths[transformer_config.config_path],
|
max_seq_len=max_seq_lengths[transformer_config.config_path],
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_model(self, context: InvocationContext, submodel: SubModelType) -> ModelIdentifierField:
|
|
||||||
match submodel:
|
|
||||||
case SubModelType.Transformer:
|
|
||||||
return self.model.model_copy(update={"submodel_type": SubModelType.Transformer})
|
|
||||||
case SubModelType.VAE:
|
|
||||||
return self._pull_model_from_mm(
|
|
||||||
context,
|
|
||||||
SubModelType.VAE,
|
|
||||||
"FLUX.1-schnell_ae",
|
|
||||||
ModelType.VAE,
|
|
||||||
BaseModelType.Flux,
|
|
||||||
)
|
|
||||||
case submodel if submodel in [SubModelType.Tokenizer, SubModelType.TextEncoder]:
|
|
||||||
return self._pull_model_from_mm(
|
|
||||||
context,
|
|
||||||
submodel,
|
|
||||||
"clip-vit-large-patch14",
|
|
||||||
ModelType.CLIPEmbed,
|
|
||||||
BaseModelType.Any,
|
|
||||||
)
|
|
||||||
case submodel if submodel in [SubModelType.Tokenizer2, SubModelType.TextEncoder2]:
|
|
||||||
return self._pull_model_from_mm(
|
|
||||||
context,
|
|
||||||
submodel,
|
|
||||||
self.t5_encoder.name,
|
|
||||||
ModelType.T5Encoder,
|
|
||||||
BaseModelType.Any,
|
|
||||||
)
|
|
||||||
case _:
|
|
||||||
raise Exception(f"{submodel.value} is not a supported submodule for a flux model")
|
|
||||||
|
|
||||||
def _pull_model_from_mm(
|
|
||||||
self,
|
|
||||||
context: InvocationContext,
|
|
||||||
submodel: SubModelType,
|
|
||||||
name: str,
|
|
||||||
type: ModelType,
|
|
||||||
base: BaseModelType,
|
|
||||||
):
|
|
||||||
if models := context.models.search_by_attrs(name=name, base=base, type=type):
|
|
||||||
if len(models) != 1:
|
|
||||||
raise Exception(f"Multiple models detected for selected model with name {name}")
|
|
||||||
return ModelIdentifierField.from_config(models[0]).model_copy(update={"submodel_type": submodel})
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Please install the {base}:{type} model named {name} via starter models")
|
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
"main_model_loader",
|
"main_model_loader",
|
||||||
|
@ -88,7 +88,6 @@ class QueueItemEventBase(QueueEventBase):
|
|||||||
|
|
||||||
item_id: int = Field(description="The ID of the queue item")
|
item_id: int = Field(description="The ID of the queue item")
|
||||||
batch_id: str = Field(description="The ID of the queue batch")
|
batch_id: str = Field(description="The ID of the queue batch")
|
||||||
origin: str | None = Field(default=None, description="The origin of the batch")
|
|
||||||
|
|
||||||
|
|
||||||
class InvocationEventBase(QueueItemEventBase):
|
class InvocationEventBase(QueueItemEventBase):
|
||||||
@ -96,6 +95,8 @@ class InvocationEventBase(QueueItemEventBase):
|
|||||||
|
|
||||||
session_id: str = Field(description="The ID of the session (aka graph execution state)")
|
session_id: str = Field(description="The ID of the session (aka graph execution state)")
|
||||||
queue_id: str = Field(description="The ID of the queue")
|
queue_id: str = Field(description="The ID of the queue")
|
||||||
|
item_id: int = Field(description="The ID of the queue item")
|
||||||
|
batch_id: str = Field(description="The ID of the queue batch")
|
||||||
session_id: str = Field(description="The ID of the session (aka graph execution state)")
|
session_id: str = Field(description="The ID of the session (aka graph execution state)")
|
||||||
invocation: AnyInvocation = Field(description="The ID of the invocation")
|
invocation: AnyInvocation = Field(description="The ID of the invocation")
|
||||||
invocation_source_id: str = Field(description="The ID of the prepared invocation's source node")
|
invocation_source_id: str = Field(description="The ID of the prepared invocation's source node")
|
||||||
@ -113,7 +114,6 @@ class InvocationStartedEvent(InvocationEventBase):
|
|||||||
queue_id=queue_item.queue_id,
|
queue_id=queue_item.queue_id,
|
||||||
item_id=queue_item.item_id,
|
item_id=queue_item.item_id,
|
||||||
batch_id=queue_item.batch_id,
|
batch_id=queue_item.batch_id,
|
||||||
origin=queue_item.origin,
|
|
||||||
session_id=queue_item.session_id,
|
session_id=queue_item.session_id,
|
||||||
invocation=invocation,
|
invocation=invocation,
|
||||||
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||||
@ -147,7 +147,6 @@ class InvocationDenoiseProgressEvent(InvocationEventBase):
|
|||||||
queue_id=queue_item.queue_id,
|
queue_id=queue_item.queue_id,
|
||||||
item_id=queue_item.item_id,
|
item_id=queue_item.item_id,
|
||||||
batch_id=queue_item.batch_id,
|
batch_id=queue_item.batch_id,
|
||||||
origin=queue_item.origin,
|
|
||||||
session_id=queue_item.session_id,
|
session_id=queue_item.session_id,
|
||||||
invocation=invocation,
|
invocation=invocation,
|
||||||
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||||
@ -185,7 +184,6 @@ class InvocationCompleteEvent(InvocationEventBase):
|
|||||||
queue_id=queue_item.queue_id,
|
queue_id=queue_item.queue_id,
|
||||||
item_id=queue_item.item_id,
|
item_id=queue_item.item_id,
|
||||||
batch_id=queue_item.batch_id,
|
batch_id=queue_item.batch_id,
|
||||||
origin=queue_item.origin,
|
|
||||||
session_id=queue_item.session_id,
|
session_id=queue_item.session_id,
|
||||||
invocation=invocation,
|
invocation=invocation,
|
||||||
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||||
@ -218,7 +216,6 @@ class InvocationErrorEvent(InvocationEventBase):
|
|||||||
queue_id=queue_item.queue_id,
|
queue_id=queue_item.queue_id,
|
||||||
item_id=queue_item.item_id,
|
item_id=queue_item.item_id,
|
||||||
batch_id=queue_item.batch_id,
|
batch_id=queue_item.batch_id,
|
||||||
origin=queue_item.origin,
|
|
||||||
session_id=queue_item.session_id,
|
session_id=queue_item.session_id,
|
||||||
invocation=invocation,
|
invocation=invocation,
|
||||||
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||||
@ -256,7 +253,6 @@ class QueueItemStatusChangedEvent(QueueItemEventBase):
|
|||||||
queue_id=queue_item.queue_id,
|
queue_id=queue_item.queue_id,
|
||||||
item_id=queue_item.item_id,
|
item_id=queue_item.item_id,
|
||||||
batch_id=queue_item.batch_id,
|
batch_id=queue_item.batch_id,
|
||||||
origin=queue_item.origin,
|
|
||||||
session_id=queue_item.session_id,
|
session_id=queue_item.session_id,
|
||||||
status=queue_item.status,
|
status=queue_item.status,
|
||||||
error_type=queue_item.error_type,
|
error_type=queue_item.error_type,
|
||||||
@ -283,14 +279,12 @@ class BatchEnqueuedEvent(QueueEventBase):
|
|||||||
description="The number of invocations initially requested to be enqueued (may be less than enqueued if queue was full)"
|
description="The number of invocations initially requested to be enqueued (may be less than enqueued if queue was full)"
|
||||||
)
|
)
|
||||||
priority: int = Field(description="The priority of the batch")
|
priority: int = Field(description="The priority of the batch")
|
||||||
origin: str | None = Field(default=None, description="The origin of the batch")
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, enqueue_result: EnqueueBatchResult) -> "BatchEnqueuedEvent":
|
def build(cls, enqueue_result: EnqueueBatchResult) -> "BatchEnqueuedEvent":
|
||||||
return cls(
|
return cls(
|
||||||
queue_id=enqueue_result.queue_id,
|
queue_id=enqueue_result.queue_id,
|
||||||
batch_id=enqueue_result.batch.batch_id,
|
batch_id=enqueue_result.batch.batch_id,
|
||||||
origin=enqueue_result.batch.origin,
|
|
||||||
enqueued=enqueue_result.enqueued,
|
enqueued=enqueue_result.enqueued,
|
||||||
requested=enqueue_result.requested,
|
requested=enqueue_result.requested,
|
||||||
priority=enqueue_result.priority,
|
priority=enqueue_result.priority,
|
||||||
|
@ -6,7 +6,6 @@ from invokeai.app.services.session_queue.session_queue_common import (
|
|||||||
Batch,
|
Batch,
|
||||||
BatchStatus,
|
BatchStatus,
|
||||||
CancelByBatchIDsResult,
|
CancelByBatchIDsResult,
|
||||||
CancelByOriginResult,
|
|
||||||
CancelByQueueIDResult,
|
CancelByQueueIDResult,
|
||||||
ClearResult,
|
ClearResult,
|
||||||
EnqueueBatchResult,
|
EnqueueBatchResult,
|
||||||
@ -96,11 +95,6 @@ class SessionQueueBase(ABC):
|
|||||||
"""Cancels all queue items with matching batch IDs"""
|
"""Cancels all queue items with matching batch IDs"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def cancel_by_origin(self, queue_id: str, origin: str) -> CancelByOriginResult:
|
|
||||||
"""Cancels all queue items with the given batch origin"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
|
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
|
||||||
"""Cancels all queue items with matching queue ID"""
|
"""Cancels all queue items with matching queue ID"""
|
||||||
|
@ -77,7 +77,6 @@ BatchDataCollection: TypeAlias = list[list[BatchDatum]]
|
|||||||
|
|
||||||
class Batch(BaseModel):
|
class Batch(BaseModel):
|
||||||
batch_id: str = Field(default_factory=uuid_string, description="The ID of the batch")
|
batch_id: str = Field(default_factory=uuid_string, description="The ID of the batch")
|
||||||
origin: str | None = Field(default=None, description="The origin of this batch.")
|
|
||||||
data: Optional[BatchDataCollection] = Field(default=None, description="The batch data collection.")
|
data: Optional[BatchDataCollection] = Field(default=None, description="The batch data collection.")
|
||||||
graph: Graph = Field(description="The graph to initialize the session with")
|
graph: Graph = Field(description="The graph to initialize the session with")
|
||||||
workflow: Optional[WorkflowWithoutID] = Field(
|
workflow: Optional[WorkflowWithoutID] = Field(
|
||||||
@ -196,7 +195,6 @@ class SessionQueueItemWithoutGraph(BaseModel):
|
|||||||
status: QUEUE_ITEM_STATUS = Field(default="pending", description="The status of this queue item")
|
status: QUEUE_ITEM_STATUS = Field(default="pending", description="The status of this queue item")
|
||||||
priority: int = Field(default=0, description="The priority of this queue item")
|
priority: int = Field(default=0, description="The priority of this queue item")
|
||||||
batch_id: str = Field(description="The ID of the batch associated with this queue item")
|
batch_id: str = Field(description="The ID of the batch associated with this queue item")
|
||||||
origin: str | None = Field(default=None, description="The origin of this queue item. ")
|
|
||||||
session_id: str = Field(
|
session_id: str = Field(
|
||||||
description="The ID of the session associated with this queue item. The session doesn't exist in graph_executions until the queue item is executed."
|
description="The ID of the session associated with this queue item. The session doesn't exist in graph_executions until the queue item is executed."
|
||||||
)
|
)
|
||||||
@ -296,7 +294,6 @@ class SessionQueueStatus(BaseModel):
|
|||||||
class BatchStatus(BaseModel):
|
class BatchStatus(BaseModel):
|
||||||
queue_id: str = Field(..., description="The ID of the queue")
|
queue_id: str = Field(..., description="The ID of the queue")
|
||||||
batch_id: str = Field(..., description="The ID of the batch")
|
batch_id: str = Field(..., description="The ID of the batch")
|
||||||
origin: str | None = Field(..., description="The origin of the batch")
|
|
||||||
pending: int = Field(..., description="Number of queue items with status 'pending'")
|
pending: int = Field(..., description="Number of queue items with status 'pending'")
|
||||||
in_progress: int = Field(..., description="Number of queue items with status 'in_progress'")
|
in_progress: int = Field(..., description="Number of queue items with status 'in_progress'")
|
||||||
completed: int = Field(..., description="Number of queue items with status 'complete'")
|
completed: int = Field(..., description="Number of queue items with status 'complete'")
|
||||||
@ -331,12 +328,6 @@ class CancelByBatchIDsResult(BaseModel):
|
|||||||
canceled: int = Field(..., description="Number of queue items canceled")
|
canceled: int = Field(..., description="Number of queue items canceled")
|
||||||
|
|
||||||
|
|
||||||
class CancelByOriginResult(BaseModel):
|
|
||||||
"""Result of canceling by list of batch ids"""
|
|
||||||
|
|
||||||
canceled: int = Field(..., description="Number of queue items canceled")
|
|
||||||
|
|
||||||
|
|
||||||
class CancelByQueueIDResult(CancelByBatchIDsResult):
|
class CancelByQueueIDResult(CancelByBatchIDsResult):
|
||||||
"""Result of canceling by queue id"""
|
"""Result of canceling by queue id"""
|
||||||
|
|
||||||
@ -442,7 +433,6 @@ class SessionQueueValueToInsert(NamedTuple):
|
|||||||
field_values: Optional[str] # field_values json
|
field_values: Optional[str] # field_values json
|
||||||
priority: int # priority
|
priority: int # priority
|
||||||
workflow: Optional[str] # workflow json
|
workflow: Optional[str] # workflow json
|
||||||
origin: str | None
|
|
||||||
|
|
||||||
|
|
||||||
ValuesToInsert: TypeAlias = list[SessionQueueValueToInsert]
|
ValuesToInsert: TypeAlias = list[SessionQueueValueToInsert]
|
||||||
@ -463,7 +453,6 @@ def prepare_values_to_insert(queue_id: str, batch: Batch, priority: int, max_new
|
|||||||
json.dumps(field_values, default=to_jsonable_python) if field_values else None, # field_values (json)
|
json.dumps(field_values, default=to_jsonable_python) if field_values else None, # field_values (json)
|
||||||
priority, # priority
|
priority, # priority
|
||||||
json.dumps(workflow, default=to_jsonable_python) if workflow else None, # workflow (json)
|
json.dumps(workflow, default=to_jsonable_python) if workflow else None, # workflow (json)
|
||||||
batch.origin, # origin
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return values_to_insert
|
return values_to_insert
|
||||||
|
@ -10,7 +10,6 @@ from invokeai.app.services.session_queue.session_queue_common import (
|
|||||||
Batch,
|
Batch,
|
||||||
BatchStatus,
|
BatchStatus,
|
||||||
CancelByBatchIDsResult,
|
CancelByBatchIDsResult,
|
||||||
CancelByOriginResult,
|
|
||||||
CancelByQueueIDResult,
|
CancelByQueueIDResult,
|
||||||
ClearResult,
|
ClearResult,
|
||||||
EnqueueBatchResult,
|
EnqueueBatchResult,
|
||||||
@ -128,8 +127,8 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
|
|
||||||
self.__cursor.executemany(
|
self.__cursor.executemany(
|
||||||
"""--sql
|
"""--sql
|
||||||
INSERT INTO session_queue (queue_id, session, session_id, batch_id, field_values, priority, workflow, origin)
|
INSERT INTO session_queue (queue_id, session, session_id, batch_id, field_values, priority, workflow)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
values_to_insert,
|
values_to_insert,
|
||||||
)
|
)
|
||||||
@ -418,7 +417,11 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
)
|
)
|
||||||
self.__conn.commit()
|
self.__conn.commit()
|
||||||
if current_queue_item is not None and current_queue_item.batch_id in batch_ids:
|
if current_queue_item is not None and current_queue_item.batch_id in batch_ids:
|
||||||
self._set_queue_item_status(current_queue_item.item_id, "canceled")
|
batch_status = self.get_batch_status(queue_id=queue_id, batch_id=current_queue_item.batch_id)
|
||||||
|
queue_status = self.get_queue_status(queue_id=queue_id)
|
||||||
|
self.__invoker.services.events.emit_queue_item_status_changed(
|
||||||
|
current_queue_item, batch_status, queue_status
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.__conn.rollback()
|
self.__conn.rollback()
|
||||||
raise
|
raise
|
||||||
@ -426,46 +429,6 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
return CancelByBatchIDsResult(canceled=count)
|
return CancelByBatchIDsResult(canceled=count)
|
||||||
|
|
||||||
def cancel_by_origin(self, queue_id: str, origin: str) -> CancelByOriginResult:
|
|
||||||
try:
|
|
||||||
current_queue_item = self.get_current(queue_id)
|
|
||||||
self.__lock.acquire()
|
|
||||||
where = """--sql
|
|
||||||
WHERE
|
|
||||||
queue_id == ?
|
|
||||||
AND origin == ?
|
|
||||||
AND status != 'canceled'
|
|
||||||
AND status != 'completed'
|
|
||||||
AND status != 'failed'
|
|
||||||
"""
|
|
||||||
params = (queue_id, origin)
|
|
||||||
self.__cursor.execute(
|
|
||||||
f"""--sql
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM session_queue
|
|
||||||
{where};
|
|
||||||
""",
|
|
||||||
params,
|
|
||||||
)
|
|
||||||
count = self.__cursor.fetchone()[0]
|
|
||||||
self.__cursor.execute(
|
|
||||||
f"""--sql
|
|
||||||
UPDATE session_queue
|
|
||||||
SET status = 'canceled'
|
|
||||||
{where};
|
|
||||||
""",
|
|
||||||
params,
|
|
||||||
)
|
|
||||||
self.__conn.commit()
|
|
||||||
if current_queue_item is not None and current_queue_item.origin == origin:
|
|
||||||
self._set_queue_item_status(current_queue_item.item_id, "canceled")
|
|
||||||
except Exception:
|
|
||||||
self.__conn.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
return CancelByOriginResult(canceled=count)
|
|
||||||
|
|
||||||
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
|
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
|
||||||
try:
|
try:
|
||||||
current_queue_item = self.get_current(queue_id)
|
current_queue_item = self.get_current(queue_id)
|
||||||
@ -578,8 +541,7 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
started_at,
|
started_at,
|
||||||
session_id,
|
session_id,
|
||||||
batch_id,
|
batch_id,
|
||||||
queue_id,
|
queue_id
|
||||||
origin
|
|
||||||
FROM session_queue
|
FROM session_queue
|
||||||
WHERE queue_id = ?
|
WHERE queue_id = ?
|
||||||
"""
|
"""
|
||||||
@ -659,7 +621,7 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
self.__cursor.execute(
|
self.__cursor.execute(
|
||||||
"""--sql
|
"""--sql
|
||||||
SELECT status, count(*), origin
|
SELECT status, count(*)
|
||||||
FROM session_queue
|
FROM session_queue
|
||||||
WHERE
|
WHERE
|
||||||
queue_id = ?
|
queue_id = ?
|
||||||
@ -671,7 +633,6 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
result = cast(list[sqlite3.Row], self.__cursor.fetchall())
|
result = cast(list[sqlite3.Row], self.__cursor.fetchall())
|
||||||
total = sum(row[1] for row in result)
|
total = sum(row[1] for row in result)
|
||||||
counts: dict[str, int] = {row[0]: row[1] for row in result}
|
counts: dict[str, int] = {row[0]: row[1] for row in result}
|
||||||
origin = result[0]["origin"] if result else None
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.__conn.rollback()
|
self.__conn.rollback()
|
||||||
raise
|
raise
|
||||||
@ -680,7 +641,6 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
|
|
||||||
return BatchStatus(
|
return BatchStatus(
|
||||||
batch_id=batch_id,
|
batch_id=batch_id,
|
||||||
origin=origin,
|
|
||||||
queue_id=queue_id,
|
queue_id=queue_id,
|
||||||
pending=counts.get("pending", 0),
|
pending=counts.get("pending", 0),
|
||||||
in_progress=counts.get("in_progress", 0),
|
in_progress=counts.get("in_progress", 0),
|
||||||
|
@ -17,7 +17,6 @@ from invokeai.app.services.shared.sqlite_migrator.migrations.migration_11 import
|
|||||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_12 import build_migration_12
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_12 import build_migration_12
|
||||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_13 import build_migration_13
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_13 import build_migration_13
|
||||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_14 import build_migration_14
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_14 import build_migration_14
|
||||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_15 import build_migration_15
|
|
||||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_impl import SqliteMigrator
|
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_impl import SqliteMigrator
|
||||||
|
|
||||||
|
|
||||||
@ -52,7 +51,6 @@ def init_db(config: InvokeAIAppConfig, logger: Logger, image_files: ImageFileSto
|
|||||||
migrator.register_migration(build_migration_12(app_config=config))
|
migrator.register_migration(build_migration_12(app_config=config))
|
||||||
migrator.register_migration(build_migration_13())
|
migrator.register_migration(build_migration_13())
|
||||||
migrator.register_migration(build_migration_14())
|
migrator.register_migration(build_migration_14())
|
||||||
migrator.register_migration(build_migration_15())
|
|
||||||
migrator.run_migrations()
|
migrator.run_migrations()
|
||||||
|
|
||||||
return db
|
return db
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
|
|
||||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
|
||||||
|
|
||||||
|
|
||||||
class Migration15Callback:
|
|
||||||
def __call__(self, cursor: sqlite3.Cursor) -> None:
|
|
||||||
self._add_origin_col(cursor)
|
|
||||||
|
|
||||||
def _add_origin_col(self, cursor: sqlite3.Cursor) -> None:
|
|
||||||
"""
|
|
||||||
- Adds `origin` column to the session queue table.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cursor.execute("ALTER TABLE session_queue ADD COLUMN origin TEXT;")
|
|
||||||
|
|
||||||
|
|
||||||
def build_migration_15() -> Migration:
|
|
||||||
"""
|
|
||||||
Build the migration from database version 14 to 15.
|
|
||||||
|
|
||||||
This migration does the following:
|
|
||||||
- Adds `origin` column to the session queue table.
|
|
||||||
"""
|
|
||||||
migration_15 = Migration(
|
|
||||||
from_version=14,
|
|
||||||
to_version=15,
|
|
||||||
callback=Migration15Callback(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return migration_15
|
|
@ -2,13 +2,13 @@
|
|||||||
"name": "FLUX Text to Image",
|
"name": "FLUX Text to Image",
|
||||||
"author": "InvokeAI",
|
"author": "InvokeAI",
|
||||||
"description": "A simple text-to-image workflow using FLUX dev or schnell models. Prerequisite model downloads: T5 Encoder, CLIP-L Encoder, and FLUX VAE. Quantized and un-quantized versions can be found in the starter models tab within your Model Manager. We recommend 4 steps for FLUX schnell models and 30 steps for FLUX dev models.",
|
"description": "A simple text-to-image workflow using FLUX dev or schnell models. Prerequisite model downloads: T5 Encoder, CLIP-L Encoder, and FLUX VAE. Quantized and un-quantized versions can be found in the starter models tab within your Model Manager. We recommend 4 steps for FLUX schnell models and 30 steps for FLUX dev models.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.4",
|
||||||
"contact": "",
|
"contact": "",
|
||||||
"tags": "text2image, flux",
|
"tags": "text2image, flux",
|
||||||
"notes": "Prerequisite model downloads: T5 Encoder, CLIP-L Encoder, and FLUX VAE. Quantized and un-quantized versions can be found in the starter models tab within your Model Manager. We recommend 4 steps for FLUX schnell models and 30 steps for FLUX dev models.",
|
"notes": "Prerequisite model downloads: T5 Encoder, CLIP-L Encoder, and FLUX VAE. Quantized and un-quantized versions can be found in the starter models tab within your Model Manager. We recommend 4 steps for FLUX schnell models and 30 steps for FLUX dev models.",
|
||||||
"exposedFields": [
|
"exposedFields": [
|
||||||
{
|
{
|
||||||
"nodeId": "4f0207c2-ff40-41fd-b047-ad33fbb1c33a",
|
"nodeId": "f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90",
|
||||||
"fieldName": "model"
|
"fieldName": "model"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -20,8 +20,8 @@
|
|||||||
"fieldName": "num_steps"
|
"fieldName": "num_steps"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nodeId": "4f0207c2-ff40-41fd-b047-ad33fbb1c33a",
|
"nodeId": "f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90",
|
||||||
"fieldName": "t5_encoder"
|
"fieldName": "t5_encoder_model"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {
|
"meta": {
|
||||||
@ -30,12 +30,12 @@
|
|||||||
},
|
},
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": "4f0207c2-ff40-41fd-b047-ad33fbb1c33a",
|
"id": "f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90",
|
||||||
"type": "invocation",
|
"type": "invocation",
|
||||||
"data": {
|
"data": {
|
||||||
"id": "4f0207c2-ff40-41fd-b047-ad33fbb1c33a",
|
"id": "f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90",
|
||||||
"type": "flux_model_loader",
|
"type": "flux_model_loader",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"label": "",
|
"label": "",
|
||||||
"notes": "",
|
"notes": "",
|
||||||
"isOpen": true,
|
"isOpen": true,
|
||||||
@ -44,31 +44,25 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"model": {
|
"model": {
|
||||||
"name": "model",
|
"name": "model",
|
||||||
"label": "Model (Starter Models can be found in Model Manager)",
|
"label": ""
|
||||||
"value": {
|
|
||||||
"key": "f04a7a2f-c74d-4538-8d5e-879a53501662",
|
|
||||||
"hash": "random:4875da7a9508444ffa706f61961c260d0c6729f6181a86b31fad06df1277b850",
|
|
||||||
"name": "FLUX Dev (Quantized)",
|
|
||||||
"base": "flux",
|
|
||||||
"type": "main"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"t5_encoder": {
|
"t5_encoder_model": {
|
||||||
"name": "t5_encoder",
|
"name": "t5_encoder_model",
|
||||||
"label": "T 5 Encoder (Starter Models can be found in Model Manager)",
|
"label": ""
|
||||||
"value": {
|
},
|
||||||
"key": "20dcd9ec-5fbb-4012-8401-049e707da5e5",
|
"clip_embed_model": {
|
||||||
"hash": "random:f986be43ff3502169e4adbdcee158afb0e0a65a1edc4cab16ae59963630cfd8f",
|
"name": "clip_embed_model",
|
||||||
"name": "t5_bnb_int8_quantized_encoder",
|
"label": ""
|
||||||
"base": "any",
|
},
|
||||||
"type": "t5_encoder"
|
"vae_model": {
|
||||||
}
|
"name": "vae_model",
|
||||||
|
"label": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": 337.09365228062825,
|
"x": 381.1882713063478,
|
||||||
"y": 40.63469521079861
|
"y": -95.89663532854017
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -207,45 +201,45 @@
|
|||||||
],
|
],
|
||||||
"edges": [
|
"edges": [
|
||||||
{
|
{
|
||||||
"id": "reactflow__edge-4f0207c2-ff40-41fd-b047-ad33fbb1c33amax_seq_len-01f674f8-b3d1-4df1-acac-6cb8e0bfb63ct5_max_seq_len",
|
"id": "reactflow__edge-f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90max_seq_len-01f674f8-b3d1-4df1-acac-6cb8e0bfb63ct5_max_seq_len",
|
||||||
"type": "default",
|
"type": "default",
|
||||||
"source": "4f0207c2-ff40-41fd-b047-ad33fbb1c33a",
|
"source": "f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90",
|
||||||
"target": "01f674f8-b3d1-4df1-acac-6cb8e0bfb63c",
|
"target": "01f674f8-b3d1-4df1-acac-6cb8e0bfb63c",
|
||||||
"sourceHandle": "max_seq_len",
|
"sourceHandle": "max_seq_len",
|
||||||
"targetHandle": "t5_max_seq_len"
|
"targetHandle": "t5_max_seq_len"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "reactflow__edge-4f0207c2-ff40-41fd-b047-ad33fbb1c33avae-159bdf1b-79e7-4174-b86e-d40e646964c8vae",
|
"id": "reactflow__edge-f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90vae-159bdf1b-79e7-4174-b86e-d40e646964c8vae",
|
||||||
"type": "default",
|
"type": "default",
|
||||||
"source": "4f0207c2-ff40-41fd-b047-ad33fbb1c33a",
|
"source": "f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90",
|
||||||
"target": "159bdf1b-79e7-4174-b86e-d40e646964c8",
|
"target": "159bdf1b-79e7-4174-b86e-d40e646964c8",
|
||||||
"sourceHandle": "vae",
|
"sourceHandle": "vae",
|
||||||
"targetHandle": "vae"
|
"targetHandle": "vae"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "reactflow__edge-4f0207c2-ff40-41fd-b047-ad33fbb1c33atransformer-159bdf1b-79e7-4174-b86e-d40e646964c8transformer",
|
"id": "reactflow__edge-f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90t5_encoder-01f674f8-b3d1-4df1-acac-6cb8e0bfb63ct5_encoder",
|
||||||
"type": "default",
|
"type": "default",
|
||||||
"source": "4f0207c2-ff40-41fd-b047-ad33fbb1c33a",
|
"source": "f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90",
|
||||||
"target": "159bdf1b-79e7-4174-b86e-d40e646964c8",
|
|
||||||
"sourceHandle": "transformer",
|
|
||||||
"targetHandle": "transformer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "reactflow__edge-4f0207c2-ff40-41fd-b047-ad33fbb1c33at5_encoder-01f674f8-b3d1-4df1-acac-6cb8e0bfb63ct5_encoder",
|
|
||||||
"type": "default",
|
|
||||||
"source": "4f0207c2-ff40-41fd-b047-ad33fbb1c33a",
|
|
||||||
"target": "01f674f8-b3d1-4df1-acac-6cb8e0bfb63c",
|
"target": "01f674f8-b3d1-4df1-acac-6cb8e0bfb63c",
|
||||||
"sourceHandle": "t5_encoder",
|
"sourceHandle": "t5_encoder",
|
||||||
"targetHandle": "t5_encoder"
|
"targetHandle": "t5_encoder"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "reactflow__edge-4f0207c2-ff40-41fd-b047-ad33fbb1c33aclip-01f674f8-b3d1-4df1-acac-6cb8e0bfb63cclip",
|
"id": "reactflow__edge-f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90clip-01f674f8-b3d1-4df1-acac-6cb8e0bfb63cclip",
|
||||||
"type": "default",
|
"type": "default",
|
||||||
"source": "4f0207c2-ff40-41fd-b047-ad33fbb1c33a",
|
"source": "f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90",
|
||||||
"target": "01f674f8-b3d1-4df1-acac-6cb8e0bfb63c",
|
"target": "01f674f8-b3d1-4df1-acac-6cb8e0bfb63c",
|
||||||
"sourceHandle": "clip",
|
"sourceHandle": "clip",
|
||||||
"targetHandle": "clip"
|
"targetHandle": "clip"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90transformer-159bdf1b-79e7-4174-b86e-d40e646964c8transformer",
|
||||||
|
"type": "default",
|
||||||
|
"source": "f8d9d7c8-9ed7-4bd7-9e42-ab0e89bfac90",
|
||||||
|
"target": "159bdf1b-79e7-4174-b86e-d40e646964c8",
|
||||||
|
"sourceHandle": "transformer",
|
||||||
|
"targetHandle": "transformer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "reactflow__edge-01f674f8-b3d1-4df1-acac-6cb8e0bfb63cconditioning-159bdf1b-79e7-4174-b86e-d40e646964c8positive_text_conditioning",
|
"id": "reactflow__edge-01f674f8-b3d1-4df1-acac-6cb8e0bfb63cconditioning-159bdf1b-79e7-4174-b86e-d40e646964c8positive_text_conditioning",
|
||||||
"type": "default",
|
"type": "default",
|
||||||
|
@ -111,16 +111,7 @@ def denoise(
|
|||||||
step_callback: Callable[[], None],
|
step_callback: Callable[[], None],
|
||||||
guidance: float = 4.0,
|
guidance: float = 4.0,
|
||||||
):
|
):
|
||||||
dtype = model.txt_in.bias.dtype
|
# guidance_vec is ignored for schnell.
|
||||||
|
|
||||||
# TODO(ryand): This shouldn't be necessary if we manage the dtypes properly in the caller.
|
|
||||||
img = img.to(dtype=dtype)
|
|
||||||
img_ids = img_ids.to(dtype=dtype)
|
|
||||||
txt = txt.to(dtype=dtype)
|
|
||||||
txt_ids = txt_ids.to(dtype=dtype)
|
|
||||||
vec = vec.to(dtype=dtype)
|
|
||||||
|
|
||||||
# this is ignored for schnell
|
|
||||||
guidance_vec = torch.full((img.shape[0],), guidance, device=img.device, dtype=img.dtype)
|
guidance_vec = torch.full((img.shape[0],), guidance, device=img.device, dtype=img.dtype)
|
||||||
for t_curr, t_prev in tqdm(list(zip(timesteps[:-1], timesteps[1:], strict=True))):
|
for t_curr, t_prev in tqdm(list(zip(timesteps[:-1], timesteps[1:], strict=True))):
|
||||||
t_vec = torch.full((img.shape[0],), t_curr, dtype=img.dtype, device=img.device)
|
t_vec = torch.full((img.shape[0],), t_curr, dtype=img.dtype, device=img.device)
|
||||||
@ -168,9 +159,9 @@ def prepare_latent_img_patches(latent_img: torch.Tensor) -> tuple[torch.Tensor,
|
|||||||
img = repeat(img, "1 ... -> bs ...", bs=bs)
|
img = repeat(img, "1 ... -> bs ...", bs=bs)
|
||||||
|
|
||||||
# Generate patch position ids.
|
# Generate patch position ids.
|
||||||
img_ids = torch.zeros(h // 2, w // 2, 3, device=img.device)
|
img_ids = torch.zeros(h // 2, w // 2, 3, device=img.device, dtype=img.dtype)
|
||||||
img_ids[..., 1] = img_ids[..., 1] + torch.arange(h // 2, device=img.device)[:, None]
|
img_ids[..., 1] = img_ids[..., 1] + torch.arange(h // 2, device=img.device, dtype=img.dtype)[:, None]
|
||||||
img_ids[..., 2] = img_ids[..., 2] + torch.arange(w // 2, device=img.device)[None, :]
|
img_ids[..., 2] = img_ids[..., 2] + torch.arange(w // 2, device=img.device, dtype=img.dtype)[None, :]
|
||||||
img_ids = repeat(img_ids, "h w c -> b (h w) c", b=bs)
|
img_ids = repeat(img_ids, "h w c -> b (h w) c", b=bs)
|
||||||
|
|
||||||
return img, img_ids
|
return img, img_ids
|
||||||
|
@ -72,6 +72,7 @@ class ModelLoader(ModelLoaderBase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
config.path = str(self._get_model_path(config))
|
config.path = str(self._get_model_path(config))
|
||||||
|
self._ram_cache.make_room(self.get_size_fs(config, Path(config.path), submodel_type))
|
||||||
loaded_model = self._load_model(config, submodel_type)
|
loaded_model = self._load_model(config, submodel_type)
|
||||||
|
|
||||||
self._ram_cache.put(
|
self._ram_cache.put(
|
||||||
|
@ -193,15 +193,6 @@ class ModelCacheBase(ABC, Generic[T]):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def exists(
|
|
||||||
self,
|
|
||||||
key: str,
|
|
||||||
submodel_type: Optional[SubModelType] = None,
|
|
||||||
) -> bool:
|
|
||||||
"""Return true if the model identified by key and submodel_type is in the cache."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def cache_size(self) -> int:
|
def cache_size(self) -> int:
|
||||||
"""Get the total size of the models currently cached."""
|
"""Get the total size of the models currently cached."""
|
||||||
|
@ -1,22 +1,6 @@
|
|||||||
# Copyright (c) 2024 Lincoln D. Stein and the InvokeAI Development team
|
# Copyright (c) 2024 Lincoln D. Stein and the InvokeAI Development team
|
||||||
# TODO: Add Stalker's proper name to copyright
|
# TODO: Add Stalker's proper name to copyright
|
||||||
"""
|
""" """
|
||||||
Manage a RAM cache of diffusion/transformer models for fast switching.
|
|
||||||
They are moved between GPU VRAM and CPU RAM as necessary. If the cache
|
|
||||||
grows larger than a preset maximum, then the least recently used
|
|
||||||
model will be cleared and (re)loaded from disk when next needed.
|
|
||||||
|
|
||||||
The cache returns context manager generators designed to load the
|
|
||||||
model into the GPU within the context, and unload outside the
|
|
||||||
context. Use like this:
|
|
||||||
|
|
||||||
cache = ModelCache(max_cache_size=7.5)
|
|
||||||
with cache.get_model('runwayml/stable-diffusion-1-5') as SD1,
|
|
||||||
cache.get_model('stabilityai/stable-diffusion-2') as SD2:
|
|
||||||
do_something_in_GPU(SD1,SD2)
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
import math
|
import math
|
||||||
@ -40,45 +24,64 @@ from invokeai.backend.model_manager.load.model_util import calc_model_size_by_da
|
|||||||
from invokeai.backend.util.devices import TorchDevice
|
from invokeai.backend.util.devices import TorchDevice
|
||||||
from invokeai.backend.util.logging import InvokeAILogger
|
from invokeai.backend.util.logging import InvokeAILogger
|
||||||
|
|
||||||
# Maximum size of the cache, in gigs
|
# Size of a GB in bytes.
|
||||||
# Default is roughly enough to hold three fp16 diffusers models in RAM simultaneously
|
GB = 2**30
|
||||||
DEFAULT_MAX_CACHE_SIZE = 6.0
|
|
||||||
|
|
||||||
# amount of GPU memory to hold in reserve for use by generations (GB)
|
|
||||||
DEFAULT_MAX_VRAM_CACHE_SIZE = 2.75
|
|
||||||
|
|
||||||
# actual size of a gig
|
|
||||||
GIG = 1073741824
|
|
||||||
|
|
||||||
# Size of a MB in bytes.
|
# Size of a MB in bytes.
|
||||||
MB = 2**20
|
MB = 2**20
|
||||||
|
|
||||||
|
|
||||||
class ModelCache(ModelCacheBase[AnyModel]):
|
class ModelCache(ModelCacheBase[AnyModel]):
|
||||||
"""Implementation of ModelCacheBase."""
|
"""A cache for managing models in memory.
|
||||||
|
|
||||||
|
The cache is based on two levels of model storage:
|
||||||
|
- execution_device: The device where most models are executed (typically "cuda", "mps", or "cpu").
|
||||||
|
- storage_device: The device where models are offloaded when not in active use (typically "cpu").
|
||||||
|
|
||||||
|
The model cache is based on the following assumptions:
|
||||||
|
- storage_device_mem_size > execution_device_mem_size
|
||||||
|
- disk_to_storage_device_transfer_time >> storage_device_to_execution_device_transfer_time
|
||||||
|
|
||||||
|
A copy of all models in the cache is always kept on the storage_device. A subset of the models also have a copy on
|
||||||
|
the execution_device.
|
||||||
|
|
||||||
|
Models are moved between the storage_device and the execution_device as necessary. Cache size limits are enforced
|
||||||
|
on both the storage_device and the execution_device. The execution_device cache uses a smallest-first offload
|
||||||
|
policy. The storage_device cache uses a least-recently-used (LRU) offload policy.
|
||||||
|
|
||||||
|
Note: Neither of these offload policies has really been compared against alternatives. It's likely that different
|
||||||
|
policies would be better, although the optimal policies are likely heavily dependent on usage patterns and HW
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
The cache returns context manager generators designed to load the model into the execution device (often GPU) within
|
||||||
|
the context, and unload outside the context.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
```
|
||||||
|
cache = ModelCache(max_cache_size=7.5, max_vram_cache_size=6.0)
|
||||||
|
with cache.get_model('runwayml/stable-diffusion-1-5') as SD1:
|
||||||
|
do_something_on_gpu(SD1)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
max_cache_size: float = DEFAULT_MAX_CACHE_SIZE,
|
max_cache_size: float,
|
||||||
max_vram_cache_size: float = DEFAULT_MAX_VRAM_CACHE_SIZE,
|
max_vram_cache_size: float,
|
||||||
execution_device: torch.device = torch.device("cuda"),
|
execution_device: torch.device = torch.device("cuda"),
|
||||||
storage_device: torch.device = torch.device("cpu"),
|
storage_device: torch.device = torch.device("cpu"),
|
||||||
precision: torch.dtype = torch.float16,
|
|
||||||
sequential_offload: bool = False,
|
|
||||||
lazy_offloading: bool = True,
|
lazy_offloading: bool = True,
|
||||||
sha_chunksize: int = 16777216,
|
|
||||||
log_memory_usage: bool = False,
|
log_memory_usage: bool = False,
|
||||||
logger: Optional[Logger] = None,
|
logger: Optional[Logger] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize the model RAM cache.
|
Initialize the model RAM cache.
|
||||||
|
|
||||||
:param max_cache_size: Maximum size of the RAM cache [6.0 GB]
|
:param max_cache_size: Maximum size of the storage_device cache in GBs.
|
||||||
|
:param max_vram_cache_size: Maximum size of the execution_device cache in GBs.
|
||||||
:param execution_device: Torch device to load active model into [torch.device('cuda')]
|
:param execution_device: Torch device to load active model into [torch.device('cuda')]
|
||||||
:param storage_device: Torch device to save inactive model in [torch.device('cpu')]
|
:param storage_device: Torch device to save inactive model in [torch.device('cpu')]
|
||||||
:param precision: Precision for loaded models [torch.float16]
|
:param lazy_offloading: Keep model in VRAM until another model needs to be loaded.
|
||||||
:param lazy_offloading: Keep model in VRAM until another model needs to be loaded
|
|
||||||
:param sequential_offload: Conserve VRAM by loading and unloading each stage of the pipeline sequentially
|
|
||||||
:param log_memory_usage: If True, a memory snapshot will be captured before and after every model cache
|
:param log_memory_usage: If True, a memory snapshot will be captured before and after every model cache
|
||||||
operation, and the result will be logged (at debug level). There is a time cost to capturing the memory
|
operation, and the result will be logged (at debug level). There is a time cost to capturing the memory
|
||||||
snapshots, so it is recommended to disable this feature unless you are actively inspecting the model cache's
|
snapshots, so it is recommended to disable this feature unless you are actively inspecting the model cache's
|
||||||
@ -86,7 +89,6 @@ class ModelCache(ModelCacheBase[AnyModel]):
|
|||||||
"""
|
"""
|
||||||
# allow lazy offloading only when vram cache enabled
|
# allow lazy offloading only when vram cache enabled
|
||||||
self._lazy_offloading = lazy_offloading and max_vram_cache_size > 0
|
self._lazy_offloading = lazy_offloading and max_vram_cache_size > 0
|
||||||
self._precision: torch.dtype = precision
|
|
||||||
self._max_cache_size: float = max_cache_size
|
self._max_cache_size: float = max_cache_size
|
||||||
self._max_vram_cache_size: float = max_vram_cache_size
|
self._max_vram_cache_size: float = max_vram_cache_size
|
||||||
self._execution_device: torch.device = execution_device
|
self._execution_device: torch.device = execution_device
|
||||||
@ -145,15 +147,6 @@ class ModelCache(ModelCacheBase[AnyModel]):
|
|||||||
total += cache_record.size
|
total += cache_record.size
|
||||||
return total
|
return total
|
||||||
|
|
||||||
def exists(
|
|
||||||
self,
|
|
||||||
key: str,
|
|
||||||
submodel_type: Optional[SubModelType] = None,
|
|
||||||
) -> bool:
|
|
||||||
"""Return true if the model identified by key and submodel_type is in the cache."""
|
|
||||||
key = self._make_cache_key(key, submodel_type)
|
|
||||||
return key in self._cached_models
|
|
||||||
|
|
||||||
def put(
|
def put(
|
||||||
self,
|
self,
|
||||||
key: str,
|
key: str,
|
||||||
@ -203,7 +196,7 @@ class ModelCache(ModelCacheBase[AnyModel]):
|
|||||||
# more stats
|
# more stats
|
||||||
if self.stats:
|
if self.stats:
|
||||||
stats_name = stats_name or key
|
stats_name = stats_name or key
|
||||||
self.stats.cache_size = int(self._max_cache_size * GIG)
|
self.stats.cache_size = int(self._max_cache_size * GB)
|
||||||
self.stats.high_watermark = max(self.stats.high_watermark, self.cache_size())
|
self.stats.high_watermark = max(self.stats.high_watermark, self.cache_size())
|
||||||
self.stats.in_cache = len(self._cached_models)
|
self.stats.in_cache = len(self._cached_models)
|
||||||
self.stats.loaded_model_sizes[stats_name] = max(
|
self.stats.loaded_model_sizes[stats_name] = max(
|
||||||
@ -231,10 +224,13 @@ class ModelCache(ModelCacheBase[AnyModel]):
|
|||||||
return model_key
|
return model_key
|
||||||
|
|
||||||
def offload_unlocked_models(self, size_required: int) -> None:
|
def offload_unlocked_models(self, size_required: int) -> None:
|
||||||
"""Move any unused models from VRAM."""
|
"""Offload models from the execution_device to make room for size_required.
|
||||||
reserved = self._max_vram_cache_size * GIG
|
|
||||||
|
:param size_required: The amount of space to clear in the execution_device cache, in bytes.
|
||||||
|
"""
|
||||||
|
reserved = self._max_vram_cache_size * GB
|
||||||
vram_in_use = torch.cuda.memory_allocated() + size_required
|
vram_in_use = torch.cuda.memory_allocated() + size_required
|
||||||
self.logger.debug(f"{(vram_in_use/GIG):.2f}GB VRAM needed for models; max allowed={(reserved/GIG):.2f}GB")
|
self.logger.debug(f"{(vram_in_use/GB):.2f}GB VRAM needed for models; max allowed={(reserved/GB):.2f}GB")
|
||||||
for _, cache_entry in sorted(self._cached_models.items(), key=lambda x: x[1].size):
|
for _, cache_entry in sorted(self._cached_models.items(), key=lambda x: x[1].size):
|
||||||
if vram_in_use <= reserved:
|
if vram_in_use <= reserved:
|
||||||
break
|
break
|
||||||
@ -245,7 +241,7 @@ class ModelCache(ModelCacheBase[AnyModel]):
|
|||||||
cache_entry.loaded = False
|
cache_entry.loaded = False
|
||||||
vram_in_use = torch.cuda.memory_allocated() + size_required
|
vram_in_use = torch.cuda.memory_allocated() + size_required
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
f"Removing {cache_entry.key} from VRAM to free {(cache_entry.size/GIG):.2f}GB; vram free = {(torch.cuda.memory_allocated()/GIG):.2f}GB"
|
f"Removing {cache_entry.key} from VRAM to free {(cache_entry.size/GB):.2f}GB; vram free = {(torch.cuda.memory_allocated()/GB):.2f}GB"
|
||||||
)
|
)
|
||||||
|
|
||||||
TorchDevice.empty_cache()
|
TorchDevice.empty_cache()
|
||||||
@ -303,7 +299,7 @@ class ModelCache(ModelCacheBase[AnyModel]):
|
|||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
f"Moved model '{cache_entry.key}' from {source_device} to"
|
f"Moved model '{cache_entry.key}' from {source_device} to"
|
||||||
f" {target_device} in {(end_model_to_time-start_model_to_time):.2f}s."
|
f" {target_device} in {(end_model_to_time-start_model_to_time):.2f}s."
|
||||||
f"Estimated model size: {(cache_entry.size/GIG):.3f} GB."
|
f"Estimated model size: {(cache_entry.size/GB):.3f} GB."
|
||||||
f"{get_pretty_snapshot_diff(snapshot_before, snapshot_after)}"
|
f"{get_pretty_snapshot_diff(snapshot_before, snapshot_after)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -326,14 +322,14 @@ class ModelCache(ModelCacheBase[AnyModel]):
|
|||||||
f"Moving model '{cache_entry.key}' from {source_device} to"
|
f"Moving model '{cache_entry.key}' from {source_device} to"
|
||||||
f" {target_device} caused an unexpected change in VRAM usage. The model's"
|
f" {target_device} caused an unexpected change in VRAM usage. The model's"
|
||||||
" estimated size may be incorrect. Estimated model size:"
|
" estimated size may be incorrect. Estimated model size:"
|
||||||
f" {(cache_entry.size/GIG):.3f} GB.\n"
|
f" {(cache_entry.size/GB):.3f} GB.\n"
|
||||||
f"{get_pretty_snapshot_diff(snapshot_before, snapshot_after)}"
|
f"{get_pretty_snapshot_diff(snapshot_before, snapshot_after)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def print_cuda_stats(self) -> None:
|
def print_cuda_stats(self) -> None:
|
||||||
"""Log CUDA diagnostics."""
|
"""Log CUDA diagnostics."""
|
||||||
vram = "%4.2fG" % (torch.cuda.memory_allocated() / GIG)
|
vram = "%4.2fG" % (torch.cuda.memory_allocated() / GB)
|
||||||
ram = "%4.2fG" % (self.cache_size() / GIG)
|
ram = "%4.2fG" % (self.cache_size() / GB)
|
||||||
|
|
||||||
in_ram_models = 0
|
in_ram_models = 0
|
||||||
in_vram_models = 0
|
in_vram_models = 0
|
||||||
@ -353,17 +349,20 @@ class ModelCache(ModelCacheBase[AnyModel]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def make_room(self, size: int) -> None:
|
def make_room(self, size: int) -> None:
|
||||||
"""Make enough room in the cache to accommodate a new model of indicated size."""
|
"""Make enough room in the cache to accommodate a new model of indicated size.
|
||||||
# calculate how much memory this model will require
|
|
||||||
# multiplier = 2 if self.precision==torch.float32 else 1
|
Note: This function deletes all of the cache's internal references to a model in order to free it. If there are
|
||||||
|
external references to the model, there's nothing that the cache can do about it, and those models will not be
|
||||||
|
garbage-collected.
|
||||||
|
"""
|
||||||
bytes_needed = size
|
bytes_needed = size
|
||||||
maximum_size = self.max_cache_size * GIG # stored in GB, convert to bytes
|
maximum_size = self.max_cache_size * GB # stored in GB, convert to bytes
|
||||||
current_size = self.cache_size()
|
current_size = self.cache_size()
|
||||||
|
|
||||||
if current_size + bytes_needed > maximum_size:
|
if current_size + bytes_needed > maximum_size:
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
f"Max cache size exceeded: {(current_size/GIG):.2f}/{self.max_cache_size:.2f} GB, need an additional"
|
f"Max cache size exceeded: {(current_size/GB):.2f}/{self.max_cache_size:.2f} GB, need an additional"
|
||||||
f" {(bytes_needed/GIG):.2f} GB"
|
f" {(bytes_needed/GB):.2f} GB"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.logger.debug(f"Before making_room: cached_models={len(self._cached_models)}")
|
self.logger.debug(f"Before making_room: cached_models={len(self._cached_models)}")
|
||||||
@ -380,7 +379,7 @@ class ModelCache(ModelCacheBase[AnyModel]):
|
|||||||
|
|
||||||
if not cache_entry.locked:
|
if not cache_entry.locked:
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
f"Removing {model_key} from RAM cache to free at least {(size/GIG):.2f} GB (-{(cache_entry.size/GIG):.2f} GB)"
|
f"Removing {model_key} from RAM cache to free at least {(size/GB):.2f} GB (-{(cache_entry.size/GB):.2f} GB)"
|
||||||
)
|
)
|
||||||
current_size -= cache_entry.size
|
current_size -= cache_entry.size
|
||||||
models_cleared += 1
|
models_cleared += 1
|
||||||
|
@ -54,8 +54,10 @@ class InvokeLinear8bitLt(bnb.nn.Linear8bitLt):
|
|||||||
|
|
||||||
# See `bnb.nn.Linear8bitLt._save_to_state_dict()` for the serialization logic of SCB and weight_format.
|
# See `bnb.nn.Linear8bitLt._save_to_state_dict()` for the serialization logic of SCB and weight_format.
|
||||||
scb = state_dict.pop(prefix + "SCB", None)
|
scb = state_dict.pop(prefix + "SCB", None)
|
||||||
# weight_format is unused, but we pop it so we can validate that there are no unexpected keys.
|
|
||||||
_weight_format = state_dict.pop(prefix + "weight_format", None)
|
# Currently, we only support weight_format=0.
|
||||||
|
weight_format = state_dict.pop(prefix + "weight_format", None)
|
||||||
|
assert weight_format == 0
|
||||||
|
|
||||||
# TODO(ryand): Technically, we should be using `strict`, `missing_keys`, `unexpected_keys`, and `error_msgs`
|
# TODO(ryand): Technically, we should be using `strict`, `missing_keys`, `unexpected_keys`, and `error_msgs`
|
||||||
# rather than raising an exception to correctly implement this API.
|
# rather than raising an exception to correctly implement this API.
|
||||||
@ -89,6 +91,14 @@ class InvokeLinear8bitLt(bnb.nn.Linear8bitLt):
|
|||||||
)
|
)
|
||||||
self.bias = bias if bias is None else torch.nn.Parameter(bias)
|
self.bias = bias if bias is None else torch.nn.Parameter(bias)
|
||||||
|
|
||||||
|
# Reset the state. The persisted fields are based on the initialization behaviour in
|
||||||
|
# `bnb.nn.Linear8bitLt.__init__()`.
|
||||||
|
new_state = bnb.MatmulLtState()
|
||||||
|
new_state.threshold = self.state.threshold
|
||||||
|
new_state.has_fp16_weights = False
|
||||||
|
new_state.use_pool = self.state.use_pool
|
||||||
|
self.state = new_state
|
||||||
|
|
||||||
|
|
||||||
def _convert_linear_layers_to_llm_8bit(
|
def _convert_linear_layers_to_llm_8bit(
|
||||||
module: torch.nn.Module, ignore_modules: set[str], outlier_threshold: float, prefix: str = ""
|
module: torch.nn.Module, ignore_modules: set[str], outlier_threshold: float, prefix: str = ""
|
||||||
|
@ -43,6 +43,11 @@ class FLUXConditioningInfo:
|
|||||||
clip_embeds: torch.Tensor
|
clip_embeds: torch.Tensor
|
||||||
t5_embeds: torch.Tensor
|
t5_embeds: torch.Tensor
|
||||||
|
|
||||||
|
def to(self, device: torch.device | None = None, dtype: torch.dtype | None = None):
|
||||||
|
self.clip_embeds = self.clip_embeds.to(device=device, dtype=dtype)
|
||||||
|
self.t5_embeds = self.t5_embeds.to(device=device, dtype=dtype)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ConditioningFieldData:
|
class ConditioningFieldData:
|
||||||
|
@ -3,10 +3,9 @@ Initialization file for invokeai.backend.util
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from invokeai.backend.util.logging import InvokeAILogger
|
from invokeai.backend.util.logging import InvokeAILogger
|
||||||
from invokeai.backend.util.util import GIG, Chdir, directory_size
|
from invokeai.backend.util.util import Chdir, directory_size
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"GIG",
|
|
||||||
"directory_size",
|
"directory_size",
|
||||||
"Chdir",
|
"Chdir",
|
||||||
"InvokeAILogger",
|
"InvokeAILogger",
|
||||||
|
@ -7,9 +7,6 @@ from pathlib import Path
|
|||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
# actual size of a gig
|
|
||||||
GIG = 1073741824
|
|
||||||
|
|
||||||
|
|
||||||
def slugify(value: str, allow_unicode: bool = False) -> str:
|
def slugify(value: str, allow_unicode: bool = False) -> str:
|
||||||
"""
|
"""
|
||||||
|
@ -12,10 +12,6 @@ module.exports = {
|
|||||||
'i18next/no-literal-string': 'error',
|
'i18next/no-literal-string': 'error',
|
||||||
// https://eslint.org/docs/latest/rules/no-console
|
// https://eslint.org/docs/latest/rules/no-console
|
||||||
'no-console': 'error',
|
'no-console': 'error',
|
||||||
// https://eslint.org/docs/latest/rules/no-promise-executor-return
|
|
||||||
'no-promise-executor-return': 'error',
|
|
||||||
// https://eslint.org/docs/latest/rules/require-await
|
|
||||||
'require-await': 'error',
|
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { PropsWithChildren, memo, useEffect } from 'react';
|
import { PropsWithChildren, memo, useEffect } from 'react';
|
||||||
import { modelChanged } from '../src/features/controlLayers/store/paramsSlice';
|
import { modelChanged } from '../src/features/parameters/store/generationSlice';
|
||||||
import { useAppDispatch } from '../src/app/store/storeHooks';
|
import { useAppDispatch } from '../src/app/store/storeHooks';
|
||||||
import { useGlobalModifiersInit } from '@invoke-ai/ui-library';
|
import { useGlobalModifiersInit } from '@invoke-ai/ui-library';
|
||||||
/**
|
/**
|
||||||
@ -10,9 +10,7 @@ export const ReduxInit = memo((props: PropsWithChildren) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
useGlobalModifiersInit();
|
useGlobalModifiersInit();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(
|
dispatch(modelChanged({ key: 'test_model', hash: 'some_hash', name: 'some name', base: 'sd-1', type: 'main' }));
|
||||||
modelChanged({ model: { key: 'test_model', hash: 'some_hash', name: 'some name', base: 'sd-1', type: 'main' } })
|
|
||||||
);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return props.children;
|
return props.children;
|
||||||
|
@ -9,8 +9,6 @@ const config: KnipConfig = {
|
|||||||
'src/services/api/schema.ts',
|
'src/services/api/schema.ts',
|
||||||
'src/features/nodes/types/v1/**',
|
'src/features/nodes/types/v1/**',
|
||||||
'src/features/nodes/types/v2/**',
|
'src/features/nodes/types/v2/**',
|
||||||
// TODO(psyche): maybe we can clean up these utils after canvas v2 release
|
|
||||||
'src/features/controlLayers/konva/util.ts',
|
|
||||||
],
|
],
|
||||||
ignoreBinaries: ['only-allow'],
|
ignoreBinaries: ['only-allow'],
|
||||||
paths: {
|
paths: {
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"build": "pnpm run lint && vite build",
|
"build": "pnpm run lint && vite build",
|
||||||
"typegen": "node scripts/typegen.js",
|
"typegen": "node scripts/typegen.js",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint:knip": "knip --tags=-knipignore",
|
"lint:knip": "knip",
|
||||||
"lint:dpdm": "dpdm --no-warning --no-tree --transform --exit-code circular:1 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:eslint": "eslint --max-warnings=0 .",
|
||||||
"lint:prettier": "prettier --check .",
|
"lint:prettier": "prettier --check .",
|
||||||
@ -52,17 +52,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@chakra-ui/react-use-size": "^2.1.0",
|
||||||
"@dagrejs/dagre": "^1.1.3",
|
"@dagrejs/dagre": "^1.1.3",
|
||||||
"@dagrejs/graphlib": "^2.2.3",
|
"@dagrejs/graphlib": "^2.2.3",
|
||||||
"@dnd-kit/core": "^6.1.0",
|
"@dnd-kit/core": "^6.1.0",
|
||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@fontsource-variable/inter": "^5.0.20",
|
"@fontsource-variable/inter": "^5.0.20",
|
||||||
"@invoke-ai/ui-library": "^0.0.32",
|
"@invoke-ai/ui-library": "^0.0.29",
|
||||||
"@nanostores/react": "^0.7.3",
|
"@nanostores/react": "^0.7.3",
|
||||||
"@reduxjs/toolkit": "2.2.3",
|
"@reduxjs/toolkit": "2.2.3",
|
||||||
"@roarr/browser-log-writer": "^1.3.0",
|
"@roarr/browser-log-writer": "^1.3.0",
|
||||||
"async-mutex": "^0.5.0",
|
|
||||||
"chakra-react-select": "^4.9.1",
|
"chakra-react-select": "^4.9.1",
|
||||||
"compare-versions": "^6.1.1",
|
"compare-versions": "^6.1.1",
|
||||||
"dateformat": "^5.0.3",
|
"dateformat": "^5.0.3",
|
||||||
@ -74,8 +74,6 @@
|
|||||||
"jsondiffpatch": "^0.6.0",
|
"jsondiffpatch": "^0.6.0",
|
||||||
"konva": "^9.3.14",
|
"konva": "^9.3.14",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lru-cache": "^11.0.0",
|
|
||||||
"nanoid": "^5.0.7",
|
|
||||||
"nanostores": "^0.11.2",
|
"nanostores": "^0.11.2",
|
||||||
"new-github-issue-url": "^1.0.0",
|
"new-github-issue-url": "^1.0.0",
|
||||||
"overlayscrollbars": "^2.10.0",
|
"overlayscrollbars": "^2.10.0",
|
||||||
@ -90,6 +88,7 @@
|
|||||||
"react-hotkeys-hook": "4.5.0",
|
"react-hotkeys-hook": "4.5.0",
|
||||||
"react-i18next": "^14.1.3",
|
"react-i18next": "^14.1.3",
|
||||||
"react-icons": "^5.2.1",
|
"react-icons": "^5.2.1",
|
||||||
|
"react-konva": "^18.2.10",
|
||||||
"react-redux": "9.1.2",
|
"react-redux": "9.1.2",
|
||||||
"react-resizable-panels": "^2.0.23",
|
"react-resizable-panels": "^2.0.23",
|
||||||
"react-select": "5.8.0",
|
"react-select": "5.8.0",
|
||||||
@ -103,9 +102,9 @@
|
|||||||
"roarr": "^7.21.1",
|
"roarr": "^7.21.1",
|
||||||
"serialize-error": "^11.0.3",
|
"serialize-error": "^11.0.3",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.7.5",
|
||||||
"stable-hash": "^0.0.4",
|
|
||||||
"use-debounce": "^10.0.2",
|
"use-debounce": "^10.0.2",
|
||||||
"use-device-pixel-ratio": "^1.1.2",
|
"use-device-pixel-ratio": "^1.1.2",
|
||||||
|
"use-image": "^1.1.1",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"zod": "^3.23.8",
|
"zod": "^3.23.8",
|
||||||
"zod-validation-error": "^3.3.1"
|
"zod-validation-error": "^3.3.1"
|
||||||
|
297
invokeai/frontend/web/pnpm-lock.yaml
generated
297
invokeai/frontend/web/pnpm-lock.yaml
generated
@ -5,6 +5,9 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@chakra-ui/react-use-size':
|
||||||
|
specifier: ^2.1.0
|
||||||
|
version: 2.1.0(react@18.3.1)
|
||||||
'@dagrejs/dagre':
|
'@dagrejs/dagre':
|
||||||
specifier: ^1.1.3
|
specifier: ^1.1.3
|
||||||
version: 1.1.3
|
version: 1.1.3
|
||||||
@ -24,8 +27,8 @@ dependencies:
|
|||||||
specifier: ^5.0.20
|
specifier: ^5.0.20
|
||||||
version: 5.0.20
|
version: 5.0.20
|
||||||
'@invoke-ai/ui-library':
|
'@invoke-ai/ui-library':
|
||||||
specifier: ^0.0.32
|
specifier: ^0.0.29
|
||||||
version: 0.0.32(@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.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1)
|
version: 0.0.29(@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.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@nanostores/react':
|
'@nanostores/react':
|
||||||
specifier: ^0.7.3
|
specifier: ^0.7.3
|
||||||
version: 0.7.3(nanostores@0.11.2)(react@18.3.1)
|
version: 0.7.3(nanostores@0.11.2)(react@18.3.1)
|
||||||
@ -35,12 +38,9 @@ dependencies:
|
|||||||
'@roarr/browser-log-writer':
|
'@roarr/browser-log-writer':
|
||||||
specifier: ^1.3.0
|
specifier: ^1.3.0
|
||||||
version: 1.3.0
|
version: 1.3.0
|
||||||
async-mutex:
|
|
||||||
specifier: ^0.5.0
|
|
||||||
version: 0.5.0
|
|
||||||
chakra-react-select:
|
chakra-react-select:
|
||||||
specifier: ^4.9.1
|
specifier: ^4.9.1
|
||||||
version: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@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)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
version: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@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)(@emotion/react@11.13.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
compare-versions:
|
compare-versions:
|
||||||
specifier: ^6.1.1
|
specifier: ^6.1.1
|
||||||
version: 6.1.1
|
version: 6.1.1
|
||||||
@ -71,12 +71,6 @@ dependencies:
|
|||||||
lodash-es:
|
lodash-es:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
lru-cache:
|
|
||||||
specifier: ^11.0.0
|
|
||||||
version: 11.0.0
|
|
||||||
nanoid:
|
|
||||||
specifier: ^5.0.7
|
|
||||||
version: 5.0.7
|
|
||||||
nanostores:
|
nanostores:
|
||||||
specifier: ^0.11.2
|
specifier: ^0.11.2
|
||||||
version: 0.11.2
|
version: 0.11.2
|
||||||
@ -119,6 +113,9 @@ dependencies:
|
|||||||
react-icons:
|
react-icons:
|
||||||
specifier: ^5.2.1
|
specifier: ^5.2.1
|
||||||
version: 5.2.1(react@18.3.1)
|
version: 5.2.1(react@18.3.1)
|
||||||
|
react-konva:
|
||||||
|
specifier: ^18.2.10
|
||||||
|
version: 18.2.10(konva@9.3.14)(react-dom@18.3.1)(react@18.3.1)
|
||||||
react-redux:
|
react-redux:
|
||||||
specifier: 9.1.2
|
specifier: 9.1.2
|
||||||
version: 9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1)
|
version: 9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1)
|
||||||
@ -158,15 +155,15 @@ dependencies:
|
|||||||
socket.io-client:
|
socket.io-client:
|
||||||
specifier: ^4.7.5
|
specifier: ^4.7.5
|
||||||
version: 4.7.5
|
version: 4.7.5
|
||||||
stable-hash:
|
|
||||||
specifier: ^0.0.4
|
|
||||||
version: 0.0.4
|
|
||||||
use-debounce:
|
use-debounce:
|
||||||
specifier: ^10.0.2
|
specifier: ^10.0.2
|
||||||
version: 10.0.2(react@18.3.1)
|
version: 10.0.2(react@18.3.1)
|
||||||
use-device-pixel-ratio:
|
use-device-pixel-ratio:
|
||||||
specifier: ^1.1.2
|
specifier: ^1.1.2
|
||||||
version: 1.1.2(react@18.3.1)
|
version: 1.1.2(react@18.3.1)
|
||||||
|
use-image:
|
||||||
|
specifier: ^1.1.1
|
||||||
|
version: 1.1.1(react-dom@18.3.1)(react@18.3.1)
|
||||||
uuid:
|
uuid:
|
||||||
specifier: ^10.0.0
|
specifier: ^10.0.0
|
||||||
version: 10.0.0
|
version: 10.0.0
|
||||||
@ -1752,13 +1749,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.14.1
|
regenerator-runtime: 0.14.1
|
||||||
|
|
||||||
/@babel/runtime@7.25.4:
|
|
||||||
resolution: {integrity: sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
dependencies:
|
|
||||||
regenerator-runtime: 0.14.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@babel/template@7.24.0:
|
/@babel/template@7.24.0:
|
||||||
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
|
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@ -1845,7 +1835,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -1861,7 +1851,7 @@ packages:
|
|||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1879,7 +1869,7 @@ packages:
|
|||||||
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1892,7 +1882,7 @@ packages:
|
|||||||
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1912,7 +1902,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1923,7 +1913,7 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1942,7 +1932,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@zag-js/focus-visible': 0.16.0
|
'@zag-js/focus-visible': 0.16.0
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -1965,7 +1955,7 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1984,7 +1974,7 @@ packages:
|
|||||||
'@chakra-ui/system': '>=2.0.0'
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1999,13 +1989,13 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/css-reset@2.3.0(@emotion/react@11.13.3)(react@18.3.1):
|
/@chakra-ui/css-reset@2.3.0(@emotion/react@11.13.0)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==}
|
resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': '>=10.0.35'
|
'@emotion/react': '>=10.0.35'
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2038,7 +2028,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2069,7 +2059,7 @@ packages:
|
|||||||
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2092,7 +2082,7 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2103,7 +2093,7 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2115,7 +2105,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2130,7 +2120,7 @@ packages:
|
|||||||
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2146,7 +2136,7 @@ packages:
|
|||||||
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2171,7 +2161,7 @@ packages:
|
|||||||
'@chakra-ui/breakpoint-utils': 2.0.8
|
'@chakra-ui/breakpoint-utils': 2.0.8
|
||||||
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2196,7 +2186,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1)
|
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -2223,7 +2213,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1)
|
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.3.24)(react@18.3.1)
|
'@chakra-ui/transition': 2.1.0(framer-motion@11.3.24)(react@18.3.1)
|
||||||
framer-motion: 11.3.24(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 11.3.24(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -2244,7 +2234,7 @@ packages:
|
|||||||
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
||||||
aria-hidden: 1.2.4
|
aria-hidden: 1.2.4
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
@ -2273,7 +2263,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2297,7 +2287,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2319,7 +2309,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-focus-on-pointer-down': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-focus-on-pointer-down': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
@ -2354,11 +2344,11 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/provider@2.4.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1):
|
/@chakra-ui/provider@2.4.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==}
|
resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.0.0
|
'@emotion/react': ^11.0.0
|
||||||
@ -2366,13 +2356,13 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
react-dom: '>=18'
|
react-dom: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.3)(react@18.3.1)
|
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/utils': 2.0.15
|
'@chakra-ui/utils': 2.0.15
|
||||||
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
dev: false
|
dev: false
|
||||||
@ -2388,7 +2378,7 @@ packages:
|
|||||||
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@zag-js/focus-visible': 0.16.0
|
'@zag-js/focus-visible': 0.16.0
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
@ -2588,7 +2578,7 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/react@2.8.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1):
|
/@chakra-ui/react@2.8.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.0.0
|
'@emotion/react': ^11.0.0
|
||||||
@ -2607,7 +2597,7 @@ packages:
|
|||||||
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/counter': 2.1.0(react@18.3.1)
|
'@chakra-ui/counter': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.3)(react@18.3.1)
|
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.3.3)(react@18.3.1)
|
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
@ -2626,7 +2616,7 @@ packages:
|
|||||||
'@chakra-ui/popper': 3.1.0(react@18.3.1)
|
'@chakra-ui/popper': 3.1.0(react@18.3.1)
|
||||||
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/provider': 2.4.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1)
|
'@chakra-ui/provider': 2.4.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@chakra-ui/radio': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/radio': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
||||||
'@chakra-ui/select': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/select': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
@ -2638,7 +2628,7 @@ packages:
|
|||||||
'@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/styled-system': 2.9.2
|
'@chakra-ui/styled-system': 2.9.2
|
||||||
'@chakra-ui/switch': 2.1.2(@chakra-ui/system@2.6.2)(framer-motion@10.18.0)(react@18.3.1)
|
'@chakra-ui/switch': 2.1.2(@chakra-ui/system@2.6.2)(framer-motion@10.18.0)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
@ -2650,8 +2640,8 @@ packages:
|
|||||||
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
||||||
'@chakra-ui/utils': 2.0.15
|
'@chakra-ui/utils': 2.0.15
|
||||||
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
@ -2667,7 +2657,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2684,7 +2674,7 @@ packages:
|
|||||||
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/react-use-previous': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-previous': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2694,7 +2684,7 @@ packages:
|
|||||||
'@chakra-ui/system': '>=2.0.0'
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2714,7 +2704,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-pan-event': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-pan-event': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-size': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-size': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2725,7 +2715,7 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2738,7 +2728,7 @@ packages:
|
|||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2751,7 +2741,7 @@ packages:
|
|||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2772,12 +2762,12 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/system@2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1):
|
/@chakra-ui/system@2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==}
|
resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.0.0
|
'@emotion/react': ^11.0.0
|
||||||
@ -2790,8 +2780,8 @@ packages:
|
|||||||
'@chakra-ui/styled-system': 2.9.2
|
'@chakra-ui/styled-system': 2.9.2
|
||||||
'@chakra-ui/theme-utils': 2.0.21
|
'@chakra-ui/theme-utils': 2.0.21
|
||||||
'@chakra-ui/utils': 2.0.15
|
'@chakra-ui/utils': 2.0.15
|
||||||
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-fast-compare: 3.2.2
|
react-fast-compare: 3.2.2
|
||||||
dev: false
|
dev: false
|
||||||
@ -2804,7 +2794,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2823,7 +2813,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2835,7 +2825,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2847,7 +2837,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2898,7 +2888,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/styled-system': 2.9.2
|
'@chakra-ui/styled-system': 2.9.2
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -2921,7 +2911,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-event-listener': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-event-listener': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
@ -2964,7 +2954,7 @@ packages:
|
|||||||
'@chakra-ui/system': '>=2.0.0'
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -3047,10 +3037,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==}
|
resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-module-imports': 7.24.7
|
'@babel/helper-module-imports': 7.24.7
|
||||||
'@babel/runtime': 7.25.4
|
'@babel/runtime': 7.25.0
|
||||||
'@emotion/hash': 0.9.2
|
'@emotion/hash': 0.9.2
|
||||||
'@emotion/memoize': 0.9.0
|
'@emotion/memoize': 0.9.0
|
||||||
'@emotion/serialize': 1.3.1
|
'@emotion/serialize': 1.3.0
|
||||||
babel-plugin-macros: 3.1.0
|
babel-plugin-macros: 3.1.0
|
||||||
convert-source-map: 1.9.0
|
convert-source-map: 1.9.0
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
@ -3138,8 +3128,8 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1):
|
/@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==}
|
resolution: {integrity: sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': '*'
|
||||||
react: '>=16.8.0'
|
react: '>=16.8.0'
|
||||||
@ -3147,10 +3137,10 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.4
|
'@babel/runtime': 7.25.0
|
||||||
'@emotion/babel-plugin': 11.12.0
|
'@emotion/babel-plugin': 11.12.0
|
||||||
'@emotion/cache': 11.13.1
|
'@emotion/cache': 11.13.1
|
||||||
'@emotion/serialize': 1.3.1
|
'@emotion/serialize': 1.3.0
|
||||||
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
|
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
|
||||||
'@emotion/utils': 1.4.0
|
'@emotion/utils': 1.4.0
|
||||||
'@emotion/weak-memoize': 0.4.0
|
'@emotion/weak-memoize': 0.4.0
|
||||||
@ -3171,12 +3161,12 @@ packages:
|
|||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/serialize@1.3.1:
|
/@emotion/serialize@1.3.0:
|
||||||
resolution: {integrity: sha512-dEPNKzBPU+vFPGa+z3axPRn8XVDetYORmDC0wAiej+TNcOZE70ZMJa0X7JdeoM6q/nWTMZeLpN/fTnD9o8MQBA==}
|
resolution: {integrity: sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emotion/hash': 0.9.2
|
'@emotion/hash': 0.9.2
|
||||||
'@emotion/memoize': 0.9.0
|
'@emotion/memoize': 0.9.0
|
||||||
'@emotion/unitless': 0.10.0
|
'@emotion/unitless': 0.9.0
|
||||||
'@emotion/utils': 1.4.0
|
'@emotion/utils': 1.4.0
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
dev: false
|
dev: false
|
||||||
@ -3189,7 +3179,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
|
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/styled@11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1):
|
/@emotion/styled@11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==}
|
resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.0.0-rc.0
|
'@emotion/react': ^11.0.0-rc.0
|
||||||
@ -3199,11 +3189,11 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.4
|
'@babel/runtime': 7.25.0
|
||||||
'@emotion/babel-plugin': 11.12.0
|
'@emotion/babel-plugin': 11.12.0
|
||||||
'@emotion/is-prop-valid': 1.3.0
|
'@emotion/is-prop-valid': 1.3.0
|
||||||
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@emotion/serialize': 1.3.1
|
'@emotion/serialize': 1.3.0
|
||||||
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
|
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
|
||||||
'@emotion/utils': 1.4.0
|
'@emotion/utils': 1.4.0
|
||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
@ -3212,14 +3202,14 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/unitless@0.10.0:
|
|
||||||
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@emotion/unitless@0.8.1:
|
/@emotion/unitless@0.8.1:
|
||||||
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
|
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@emotion/unitless@0.9.0:
|
||||||
|
resolution: {integrity: sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1):
|
/@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1):
|
||||||
resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==}
|
resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3571,8 +3561,8 @@ packages:
|
|||||||
prettier: 3.3.3
|
prettier: 3.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@invoke-ai/ui-library@0.0.32(@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.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1):
|
/@invoke-ai/ui-library@0.0.29(@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.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-JxAoblrDu/cZ4ha9KO4ry5OWvyLUE1Dj28i+ciMaDNUpC/cN+IyiTbUBoFoPaoN5JP8Zpd/MYCcmF2qsziHDzg==}
|
resolution: {integrity: sha512-7SYOaiEEKk9iHk0hg2R2yVxiuV3I1x6bDEv0R3Y2tCH/Aq5XDG2tR+d7SQAPqf5+za3S+qfNFjbjl7GvEMwqmA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@fontsource-variable/inter': ^5.0.16
|
'@fontsource-variable/inter': ^5.0.16
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
@ -3582,14 +3572,14 @@ packages:
|
|||||||
'@chakra-ui/icons': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icons': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@chakra-ui/react': 2.8.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1)
|
'@chakra-ui/react': 2.8.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@chakra-ui/styled-system': 2.9.2
|
'@chakra-ui/styled-system': 2.9.2
|
||||||
'@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2)
|
'@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2)
|
||||||
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@fontsource-variable/inter': 5.0.20
|
'@fontsource-variable/inter': 5.0.20
|
||||||
'@nanostores/react': 0.7.3(nanostores@0.11.2)(react@18.3.1)
|
'@nanostores/react': 0.7.3(nanostores@0.11.2)(react@18.3.1)
|
||||||
chakra-react-select: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@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)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
chakra-react-select: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@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)(@emotion/react@11.13.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
nanostores: 0.11.2
|
nanostores: 0.11.2
|
||||||
@ -5212,6 +5202,12 @@ packages:
|
|||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/react-reconciler@0.28.8:
|
||||||
|
resolution: {integrity: sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==}
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/react-transition-group@4.4.10:
|
/@types/react-transition-group@4.4.10:
|
||||||
resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
|
resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5781,7 +5777,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
|
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.7.0
|
tslib: 2.6.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/aria-query@5.3.0:
|
/aria-query@5.3.0:
|
||||||
@ -5907,12 +5903,6 @@ packages:
|
|||||||
tslib: 2.6.3
|
tslib: 2.6.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/async-mutex@0.5.0:
|
|
||||||
resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==}
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.6.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/attr-accept@2.2.2:
|
/attr-accept@2.2.2:
|
||||||
resolution: {integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==}
|
resolution: {integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -6133,7 +6123,7 @@ packages:
|
|||||||
type-detect: 4.0.8
|
type-detect: 4.0.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/chakra-react-select@4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@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)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
/chakra-react-select@4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@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)(@emotion/react@11.13.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-jmgfN+S/wnTaCp3pW30GYDIZ5J8jWcT1gIbhpw6RdKV+atm/U4/sT+gaHOHHhRL8xeaYip+iI/m8MPGREkve0w==}
|
resolution: {integrity: sha512-jmgfN+S/wnTaCp3pW30GYDIZ5J8jWcT1gIbhpw6RdKV+atm/U4/sT+gaHOHHhRL8xeaYip+iI/m8MPGREkve0w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@chakra-ui/form-control': ^2.0.0
|
'@chakra-ui/form-control': ^2.0.0
|
||||||
@ -6153,8 +6143,8 @@ packages:
|
|||||||
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.3.24)(react@18.3.1)
|
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.3.24)(react@18.3.1)
|
||||||
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
react-select: 5.8.0(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
react-select: 5.8.0(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
@ -7579,7 +7569,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==}
|
resolution: {integrity: sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.7.0
|
tslib: 2.6.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/for-each@0.3.3:
|
/for-each@0.3.3:
|
||||||
@ -7618,7 +7608,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
tslib: 2.7.0
|
tslib: 2.6.3
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/is-prop-valid': 0.8.8
|
'@emotion/is-prop-valid': 0.8.8
|
||||||
dev: false
|
dev: false
|
||||||
@ -8394,6 +8384,15 @@ packages:
|
|||||||
set-function-name: 2.0.2
|
set-function-name: 2.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/its-fine@1.2.5(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18.0'
|
||||||
|
dependencies:
|
||||||
|
'@types/react-reconciler': 0.28.8
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/jackspeak@2.3.6:
|
/jackspeak@2.3.6:
|
||||||
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
|
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@ -8711,11 +8710,6 @@ packages:
|
|||||||
engines: {node: 14 || >=16.14}
|
engines: {node: 14 || >=16.14}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/lru-cache@11.0.0:
|
|
||||||
resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==}
|
|
||||||
engines: {node: 20 || >=22}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/lru-cache@5.1.1:
|
/lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -9003,12 +8997,6 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/nanoid@5.0.7:
|
|
||||||
resolution: {integrity: sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==}
|
|
||||||
engines: {node: ^18 || >=20}
|
|
||||||
hasBin: true
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/nanostores@0.11.2:
|
/nanostores@0.11.2:
|
||||||
resolution: {integrity: sha512-6bucNxMJA5rNV554WQl+MWGng0QVMzlRgpKTHHfIbVLrhQ+yRXBychV9ECGVuuUfCMQPjfIG9bj8oJFZ9hYP/Q==}
|
resolution: {integrity: sha512-6bucNxMJA5rNV554WQl+MWGng0QVMzlRgpKTHHfIbVLrhQ+yRXBychV9ECGVuuUfCMQPjfIG9bj8oJFZ9hYP/Q==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
@ -9626,7 +9614,7 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.4
|
'@babel/runtime': 7.25.0
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -9721,7 +9709,7 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.4
|
'@babel/runtime': 7.25.0
|
||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
focus-lock: 1.3.5
|
focus-lock: 1.3.5
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
@ -9783,7 +9771,7 @@ packages:
|
|||||||
react-native:
|
react-native:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.4
|
'@babel/runtime': 7.25.0
|
||||||
html-parse-stringify: 3.0.1
|
html-parse-stringify: 3.0.1
|
||||||
i18next: 23.12.2
|
i18next: 23.12.2
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -9813,6 +9801,33 @@ packages:
|
|||||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/react-konva@18.2.10(konva@9.3.14)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-ohcX1BJINL43m4ynjZ24MxFI1syjBdrXhqVxYVDw2rKgr3yuS0x/6m1Y2Z4sl4T/gKhfreBx8KHisd0XC6OT1g==}
|
||||||
|
peerDependencies:
|
||||||
|
konva: ^8.0.1 || ^7.2.5 || ^9.0.0
|
||||||
|
react: '>=18.0.0'
|
||||||
|
react-dom: '>=18.0.0'
|
||||||
|
dependencies:
|
||||||
|
'@types/react-reconciler': 0.28.8
|
||||||
|
its-fine: 1.2.5(react@18.3.1)
|
||||||
|
konva: 9.3.14
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
react-reconciler: 0.29.2(react@18.3.1)
|
||||||
|
scheduler: 0.23.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/react-reconciler@0.29.2(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.3.1
|
||||||
|
dependencies:
|
||||||
|
loose-envify: 1.4.0
|
||||||
|
react: 18.3.1
|
||||||
|
scheduler: 0.23.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-redux@9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1):
|
/react-redux@9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1):
|
||||||
resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==}
|
resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -9845,7 +9860,7 @@ packages:
|
|||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
|
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
tslib: 2.7.0
|
tslib: 2.6.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-remove-scroll@2.5.10(@types/react@18.3.3)(react@18.3.1):
|
/react-remove-scroll@2.5.10(@types/react@18.3.3)(react@18.3.1):
|
||||||
@ -9862,7 +9877,7 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.3.1)
|
react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.3.1)
|
||||||
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
|
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
tslib: 2.7.0
|
tslib: 2.6.3
|
||||||
use-callback-ref: 1.3.2(@types/react@18.3.3)(react@18.3.1)
|
use-callback-ref: 1.3.2(@types/react@18.3.3)(react@18.3.1)
|
||||||
use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
|
use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
|
||||||
dev: false
|
dev: false
|
||||||
@ -9912,7 +9927,7 @@ packages:
|
|||||||
get-nonce: 1.0.1
|
get-nonce: 1.0.1
|
||||||
invariant: 2.2.4
|
invariant: 2.2.4
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
tslib: 2.7.0
|
tslib: 2.6.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1):
|
/react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1):
|
||||||
@ -10610,10 +10625,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/stable-hash@0.0.4:
|
|
||||||
resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/stack-generator@2.0.10:
|
/stack-generator@2.0.10:
|
||||||
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
|
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -11052,10 +11063,6 @@ packages:
|
|||||||
/tslib@2.6.3:
|
/tslib@2.6.3:
|
||||||
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
|
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
|
||||||
|
|
||||||
/tslib@2.7.0:
|
|
||||||
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/tsutils@3.21.0(typescript@5.5.4):
|
/tsutils@3.21.0(typescript@5.5.4):
|
||||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@ -11303,7 +11310,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
tslib: 2.7.0
|
tslib: 2.6.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/use-debounce@10.0.2(react@18.3.1):
|
/use-debounce@10.0.2(react@18.3.1):
|
||||||
@ -11323,6 +11330,16 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/use-image@1.1.1(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-n4YO2k8AJG/BcDtxmBx8Aa+47kxY5m335dJiCQA5tTeVU4XdhrhqR6wT0WISRXwdMEOv5CSjqekDZkEMiiWaYQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/use-isomorphic-layout-effect@1.1.2(@types/react@18.3.3)(react@18.3.1):
|
/use-isomorphic-layout-effect@1.1.2(@types/react@18.3.3)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -11349,7 +11366,7 @@ packages:
|
|||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
detect-node-es: 1.1.0
|
detect-node-es: 1.1.0
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
tslib: 2.7.0
|
tslib: 2.6.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/use-sync-external-store@1.2.0(react@18.3.1):
|
/use-sync-external-store@1.2.0(react@18.3.1):
|
||||||
|
@ -80,7 +80,6 @@
|
|||||||
"aboutDesc": "Using Invoke for work? Check out:",
|
"aboutDesc": "Using Invoke for work? Check out:",
|
||||||
"aboutHeading": "Own Your Creative Power",
|
"aboutHeading": "Own Your Creative Power",
|
||||||
"accept": "Accept",
|
"accept": "Accept",
|
||||||
"apply": "Apply",
|
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"advanced": "Advanced",
|
"advanced": "Advanced",
|
||||||
"ai": "ai",
|
"ai": "ai",
|
||||||
@ -116,7 +115,6 @@
|
|||||||
"githubLabel": "Github",
|
"githubLabel": "Github",
|
||||||
"goTo": "Go to",
|
"goTo": "Go to",
|
||||||
"hotkeysLabel": "Hotkeys",
|
"hotkeysLabel": "Hotkeys",
|
||||||
"loadingImage": "Loading Image",
|
|
||||||
"imageFailedToLoad": "Unable to Load Image",
|
"imageFailedToLoad": "Unable to Load Image",
|
||||||
"img2img": "Image To Image",
|
"img2img": "Image To Image",
|
||||||
"inpaint": "inpaint",
|
"inpaint": "inpaint",
|
||||||
@ -327,10 +325,6 @@
|
|||||||
"canceled": "Canceled",
|
"canceled": "Canceled",
|
||||||
"completedIn": "Completed in",
|
"completedIn": "Completed in",
|
||||||
"batch": "Batch",
|
"batch": "Batch",
|
||||||
"origin": "Origin",
|
|
||||||
"originCanvas": "Canvas",
|
|
||||||
"originWorkflows": "Workflows",
|
|
||||||
"originOther": "Other",
|
|
||||||
"batchFieldValues": "Batch Field Values",
|
"batchFieldValues": "Batch Field Values",
|
||||||
"item": "Item",
|
"item": "Item",
|
||||||
"session": "Session",
|
"session": "Session",
|
||||||
@ -702,6 +696,8 @@
|
|||||||
"availableModels": "Available Models",
|
"availableModels": "Available Models",
|
||||||
"baseModel": "Base Model",
|
"baseModel": "Base Model",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"clipEmbed": "CLIP Embed",
|
||||||
|
"clipVision": "CLIP Vision",
|
||||||
"config": "Config",
|
"config": "Config",
|
||||||
"convert": "Convert",
|
"convert": "Convert",
|
||||||
"convertingModelBegin": "Converting Model. Please wait.",
|
"convertingModelBegin": "Converting Model. Please wait.",
|
||||||
@ -789,6 +785,7 @@
|
|||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"simpleModelPlaceholder": "URL or path to a local file or diffusers folder",
|
"simpleModelPlaceholder": "URL or path to a local file or diffusers folder",
|
||||||
"source": "Source",
|
"source": "Source",
|
||||||
|
"spandrelImageToImage": "Image to Image (Spandrel)",
|
||||||
"starterModels": "Starter Models",
|
"starterModels": "Starter Models",
|
||||||
"starterModelsInModelManager": "Starter Models can be found in Model Manager",
|
"starterModelsInModelManager": "Starter Models can be found in Model Manager",
|
||||||
"syncModels": "Sync Models",
|
"syncModels": "Sync Models",
|
||||||
@ -797,6 +794,7 @@
|
|||||||
"loraTriggerPhrases": "LoRA Trigger Phrases",
|
"loraTriggerPhrases": "LoRA Trigger Phrases",
|
||||||
"mainModelTriggerPhrases": "Main Model Trigger Phrases",
|
"mainModelTriggerPhrases": "Main Model Trigger Phrases",
|
||||||
"typePhraseHere": "Type phrase here",
|
"typePhraseHere": "Type phrase here",
|
||||||
|
"t5Encoder": "T5 Encoder",
|
||||||
"upcastAttention": "Upcast Attention",
|
"upcastAttention": "Upcast Attention",
|
||||||
"uploadImage": "Upload Image",
|
"uploadImage": "Upload Image",
|
||||||
"urlOrLocalPath": "URL or Local Path",
|
"urlOrLocalPath": "URL or Local Path",
|
||||||
@ -1102,6 +1100,7 @@
|
|||||||
"confirmOnDelete": "Confirm On Delete",
|
"confirmOnDelete": "Confirm On Delete",
|
||||||
"developer": "Developer",
|
"developer": "Developer",
|
||||||
"displayInProgress": "Display Progress Images",
|
"displayInProgress": "Display Progress Images",
|
||||||
|
"enableImageDebugging": "Enable Image Debugging",
|
||||||
"enableInformationalPopovers": "Enable Informational Popovers",
|
"enableInformationalPopovers": "Enable Informational Popovers",
|
||||||
"informationalPopoversDisabled": "Informational Popovers Disabled",
|
"informationalPopoversDisabled": "Informational Popovers Disabled",
|
||||||
"informationalPopoversDisabledDesc": "Informational popovers have been disabled. Enable them in Settings.",
|
"informationalPopoversDisabledDesc": "Informational popovers have been disabled. Enable them in Settings.",
|
||||||
@ -1568,7 +1567,7 @@
|
|||||||
"copyToClipboard": "Copy to Clipboard",
|
"copyToClipboard": "Copy to Clipboard",
|
||||||
"cursorPosition": "Cursor Position",
|
"cursorPosition": "Cursor Position",
|
||||||
"darkenOutsideSelection": "Darken Outside Selection",
|
"darkenOutsideSelection": "Darken Outside Selection",
|
||||||
"discardAll": "Discard All & Cancel Pending Generations",
|
"discardAll": "Discard All",
|
||||||
"discardCurrent": "Discard Current",
|
"discardCurrent": "Discard Current",
|
||||||
"downloadAsImage": "Download As Image",
|
"downloadAsImage": "Download As Image",
|
||||||
"enableMask": "Enable Mask",
|
"enableMask": "Enable Mask",
|
||||||
@ -1646,121 +1645,39 @@
|
|||||||
"storeNotInitialized": "Store is not initialized"
|
"storeNotInitialized": "Store is not initialized"
|
||||||
},
|
},
|
||||||
"controlLayers": {
|
"controlLayers": {
|
||||||
"clearHistory": "Clear History",
|
"deleteAll": "Delete All",
|
||||||
"generateMode": "Generate",
|
|
||||||
"generateModeDesc": "Create individual images. Generated images are added directly to the gallery.",
|
|
||||||
"composeMode": "Compose",
|
|
||||||
"composeModeDesc": "Compose your work iterative. Generated images are added back to the canvas.",
|
|
||||||
"autoSave": "Auto-save to Gallery",
|
|
||||||
"resetCanvas": "Reset Canvas",
|
|
||||||
"resetAll": "Reset All",
|
|
||||||
"clearCaches": "Clear Caches",
|
|
||||||
"recalculateRects": "Recalculate Rects",
|
|
||||||
"clipToBbox": "Clip Strokes to Bbox",
|
|
||||||
"addLayer": "Add Layer",
|
"addLayer": "Add Layer",
|
||||||
"duplicate": "Duplicate",
|
|
||||||
"moveToFront": "Move to Front",
|
"moveToFront": "Move to Front",
|
||||||
"moveToBack": "Move to Back",
|
"moveToBack": "Move to Back",
|
||||||
"moveForward": "Move Forward",
|
"moveForward": "Move Forward",
|
||||||
"moveBackward": "Move Backward",
|
"moveBackward": "Move Backward",
|
||||||
"brushSize": "Brush Size",
|
"brushSize": "Brush Size",
|
||||||
"width": "Width",
|
|
||||||
"zoom": "Zoom",
|
|
||||||
"resetView": "Reset View",
|
|
||||||
"controlLayers": "Control Layers",
|
"controlLayers": "Control Layers",
|
||||||
"globalMaskOpacity": "Global Mask Opacity",
|
"globalMaskOpacity": "Global Mask Opacity",
|
||||||
"autoNegative": "Auto Negative",
|
"autoNegative": "Auto Negative",
|
||||||
"enableAutoNegative": "Enable Auto Negative",
|
|
||||||
"disableAutoNegative": "Disable Auto Negative",
|
|
||||||
"deletePrompt": "Delete Prompt",
|
"deletePrompt": "Delete Prompt",
|
||||||
"resetRegion": "Reset Region",
|
"resetRegion": "Reset Region",
|
||||||
"debugLayers": "Debug Layers",
|
"debugLayers": "Debug Layers",
|
||||||
"rectangle": "Rectangle",
|
"rectangle": "Rectangle",
|
||||||
"maskFill": "Mask Fill",
|
"maskPreviewColor": "Mask Preview Color",
|
||||||
"addPositivePrompt": "Add $t(common.positivePrompt)",
|
"addPositivePrompt": "Add $t(common.positivePrompt)",
|
||||||
"addNegativePrompt": "Add $t(common.negativePrompt)",
|
"addNegativePrompt": "Add $t(common.negativePrompt)",
|
||||||
"addIPAdapter": "Add $t(common.ipAdapter)",
|
"addIPAdapter": "Add $t(common.ipAdapter)",
|
||||||
|
"regionalGuidance": "Regional Guidance",
|
||||||
"regionalGuidanceLayer": "$t(controlLayers.regionalGuidance) $t(unifiedCanvas.layer)",
|
"regionalGuidanceLayer": "$t(controlLayers.regionalGuidance) $t(unifiedCanvas.layer)",
|
||||||
"raster": "Raster",
|
|
||||||
"rasterLayer_one": "Raster Layer",
|
|
||||||
"controlLayer_one": "Control Layer",
|
|
||||||
"inpaintMask_one": "Inpaint Mask",
|
|
||||||
"regionalGuidance_one": "Regional Guidance",
|
|
||||||
"ipAdapter_one": "IP Adapter",
|
|
||||||
"rasterLayer_other": "Raster Layers",
|
|
||||||
"controlLayer_other": "Control Layers",
|
|
||||||
"inpaintMask_other": "Inpaint Masks",
|
|
||||||
"regionalGuidance_other": "Regional Guidance",
|
|
||||||
"ipAdapter_other": "IP Adapters",
|
|
||||||
"opacity": "Opacity",
|
"opacity": "Opacity",
|
||||||
"regionalGuidance_withCount_hidden": "Regional Guidance ({{count}} hidden)",
|
|
||||||
"controlAdapters_withCount_hidden": "Control Adapters ({{count}} hidden)",
|
|
||||||
"controlLayers_withCount_hidden": "Control Layers ({{count}} hidden)",
|
|
||||||
"rasterLayers_withCount_hidden": "Raster Layers ({{count}} hidden)",
|
|
||||||
"ipAdapters_withCount_hidden": "IP Adapters ({{count}} hidden)",
|
|
||||||
"inpaintMasks_withCount_hidden": "Inpaint Masks ({{count}} hidden)",
|
|
||||||
"regionalGuidance_withCount_visible": "Regional Guidance ({{count}})",
|
|
||||||
"controlAdapters_withCount_visible": "Control Adapters ({{count}})",
|
|
||||||
"controlLayers_withCount_visible": "Control Layers ({{count}})",
|
|
||||||
"rasterLayers_withCount_visible": "Raster Layers ({{count}})",
|
|
||||||
"ipAdapters_withCount_visible": "IP Adapters ({{count}})",
|
|
||||||
"inpaintMasks_withCount_visible": "Inpaint Masks ({{count}})",
|
|
||||||
"globalControlAdapter": "Global $t(controlnet.controlAdapter_one)",
|
"globalControlAdapter": "Global $t(controlnet.controlAdapter_one)",
|
||||||
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
||||||
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
||||||
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
||||||
"globalInitialImage": "Global Initial Image",
|
"globalInitialImage": "Global Initial Image",
|
||||||
"globalInitialImageLayer": "$t(controlLayers.globalInitialImage) $t(unifiedCanvas.layer)",
|
"globalInitialImageLayer": "$t(controlLayers.globalInitialImage) $t(unifiedCanvas.layer)",
|
||||||
"layer": "Layer",
|
|
||||||
"opacityFilter": "Opacity Filter",
|
"opacityFilter": "Opacity Filter",
|
||||||
"clearProcessor": "Clear Processor",
|
"clearProcessor": "Clear Processor",
|
||||||
"resetProcessor": "Reset Processor to Defaults",
|
"resetProcessor": "Reset Processor to Defaults",
|
||||||
"noLayersAdded": "No Layers Added",
|
"noLayersAdded": "No Layers Added",
|
||||||
"layers_one": "Layer",
|
"layers_one": "Layer",
|
||||||
"layers_other": "Layers",
|
"layers_other": "Layers"
|
||||||
"objects_zero": "empty",
|
|
||||||
"objects_one": "{{count}} object",
|
|
||||||
"objects_other": "{{count}} objects",
|
|
||||||
"convertToControlLayer": "Convert to Control Layer",
|
|
||||||
"convertToRasterLayer": "Convert to Raster Layer",
|
|
||||||
"transparency": "Transparency",
|
|
||||||
"enableTransparencyEffect": "Enable Transparency Effect",
|
|
||||||
"disableTransparencyEffect": "Disable Transparency Effect",
|
|
||||||
"hidingType": "Hiding {{type}}",
|
|
||||||
"showingType": "Showing {{type}}",
|
|
||||||
"dynamicGrid": "Dynamic Grid",
|
|
||||||
"logDebugInfo": "Log Debug Info",
|
|
||||||
"locked": "Locked",
|
|
||||||
"unlocked": "Unlocked",
|
|
||||||
"deleteSelected": "Delete Selected",
|
|
||||||
"deleteAll": "Delete All",
|
|
||||||
"fill": {
|
|
||||||
"fillStyle": "Fill Style",
|
|
||||||
"solid": "Solid",
|
|
||||||
"grid": "Grid",
|
|
||||||
"crosshatch": "Crosshatch",
|
|
||||||
"vertical": "Vertical",
|
|
||||||
"horizontal": "Horizontal",
|
|
||||||
"diagonal": "Diagonal"
|
|
||||||
},
|
|
||||||
"tool": {
|
|
||||||
"brush": "Brush",
|
|
||||||
"eraser": "Eraser",
|
|
||||||
"rectangle": "Rectangle",
|
|
||||||
"bbox": "Bbox",
|
|
||||||
"move": "Move",
|
|
||||||
"view": "View",
|
|
||||||
"transform": "Transform",
|
|
||||||
"colorPicker": "Color Picker"
|
|
||||||
},
|
|
||||||
"filter": {
|
|
||||||
"filter": "Filter",
|
|
||||||
"filters": "Filters",
|
|
||||||
"filterType": "Filter Type",
|
|
||||||
"preview": "Preview",
|
|
||||||
"apply": "Apply",
|
|
||||||
"cancel": "Cancel"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"upscaling": {
|
"upscaling": {
|
||||||
"upscale": "Upscale",
|
"upscale": "Upscale",
|
||||||
@ -1848,30 +1765,5 @@
|
|||||||
"upscaling": "Upscaling",
|
"upscaling": "Upscaling",
|
||||||
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)"
|
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"system": {
|
|
||||||
"enableLogging": "Enable Logging",
|
|
||||||
"logLevel": {
|
|
||||||
"logLevel": "Log Level",
|
|
||||||
"trace": "Trace",
|
|
||||||
"debug": "Debug",
|
|
||||||
"info": "Info",
|
|
||||||
"warn": "Warn",
|
|
||||||
"error": "Error",
|
|
||||||
"fatal": "Fatal"
|
|
||||||
},
|
|
||||||
"logNamespaces": {
|
|
||||||
"logNamespaces": "Log Namespaces",
|
|
||||||
"gallery": "Gallery",
|
|
||||||
"models": "Models",
|
|
||||||
"config": "Config",
|
|
||||||
"canvas": "Canvas",
|
|
||||||
"generation": "Generation",
|
|
||||||
"workflows": "Workflows",
|
|
||||||
"system": "System",
|
|
||||||
"events": "Events",
|
|
||||||
"queue": "Queue",
|
|
||||||
"metadata": "Metadata"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ async function generateTypes(schema) {
|
|||||||
process.stdout.write(`\nOK!\r\n`);
|
process.stdout.write(`\nOK!\r\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
async function main() {
|
||||||
const encoding = 'utf-8';
|
const encoding = 'utf-8';
|
||||||
|
|
||||||
if (process.stdin.isTTY) {
|
if (process.stdin.isTTY) {
|
||||||
|
@ -6,7 +6,6 @@ import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/ap
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import type { PartialAppConfig } from 'app/types/invokeai';
|
import type { PartialAppConfig } from 'app/types/invokeai';
|
||||||
import ImageUploadOverlay from 'common/components/ImageUploadOverlay';
|
import ImageUploadOverlay from 'common/components/ImageUploadOverlay';
|
||||||
import { useScopeFocusWatcher } from 'common/hooks/interactionScopes';
|
|
||||||
import { useClearStorage } from 'common/hooks/useClearStorage';
|
import { useClearStorage } from 'common/hooks/useClearStorage';
|
||||||
import { useFullscreenDropzone } from 'common/hooks/useFullscreenDropzone';
|
import { useFullscreenDropzone } from 'common/hooks/useFullscreenDropzone';
|
||||||
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
||||||
@ -14,13 +13,13 @@ import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardMo
|
|||||||
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
|
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
|
||||||
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
||||||
import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast';
|
import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast';
|
||||||
import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
|
|
||||||
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
|
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
|
||||||
|
import { activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||||
import { configChanged } from 'features/system/store/configSlice';
|
import { configChanged } from 'features/system/store/configSlice';
|
||||||
import { selectLanguage } from 'features/system/store/systemSelectors';
|
import { languageSelector } from 'features/system/store/systemSelectors';
|
||||||
import { AppContent } from 'features/ui/components/AppContent';
|
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
||||||
|
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
import type { TabName } from 'features/ui/store/uiTypes';
|
|
||||||
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
|
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
import i18n from 'i18n';
|
import i18n from 'i18n';
|
||||||
@ -41,11 +40,18 @@ interface Props {
|
|||||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||||
};
|
};
|
||||||
selectedWorkflowId?: string;
|
selectedWorkflowId?: string;
|
||||||
destination?: TabName | undefined;
|
selectedStylePresetId?: string;
|
||||||
|
destination?: InvokeTabName | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, destination }: Props) => {
|
const App = ({
|
||||||
const language = useAppSelector(selectLanguage);
|
config = DEFAULT_CONFIG,
|
||||||
|
selectedImage,
|
||||||
|
selectedWorkflowId,
|
||||||
|
selectedStylePresetId,
|
||||||
|
destination,
|
||||||
|
}: Props) => {
|
||||||
|
const language = useAppSelector(languageSelector);
|
||||||
const logger = useLogger('system');
|
const logger = useLogger('system');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const clearStorage = useClearStorage();
|
const clearStorage = useClearStorage();
|
||||||
@ -83,6 +89,12 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, desti
|
|||||||
}
|
}
|
||||||
}, [selectedWorkflowId, getAndLoadWorkflow]);
|
}, [selectedWorkflowId, getAndLoadWorkflow]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedStylePresetId) {
|
||||||
|
dispatch(activeStylePresetIdChanged(selectedStylePresetId));
|
||||||
|
}
|
||||||
|
}, [dispatch, selectedStylePresetId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (destination) {
|
if (destination) {
|
||||||
dispatch(setActiveTab(destination));
|
dispatch(setActiveTab(destination));
|
||||||
@ -95,7 +107,6 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, desti
|
|||||||
|
|
||||||
useStarterModelsToast();
|
useStarterModelsToast();
|
||||||
useSyncQueueStatus();
|
useSyncQueueStatus();
|
||||||
useScopeFocusWatcher();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary onReset={handleReset} FallbackComponent={AppErrorBoundaryFallback}>
|
<ErrorBoundary onReset={handleReset} FallbackComponent={AppErrorBoundaryFallback}>
|
||||||
@ -108,7 +119,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, desti
|
|||||||
{...dropzone.getRootProps()}
|
{...dropzone.getRootProps()}
|
||||||
>
|
>
|
||||||
<input {...dropzone.getInputProps()} />
|
<input {...dropzone.getInputProps()} />
|
||||||
<AppContent />
|
<InvokeTabs />
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{dropzone.isDragActive && isHandlingUpload && (
|
{dropzone.isDragActive && isHandlingUpload && (
|
||||||
<ImageUploadOverlay dropzone={dropzone} setIsHandlingUpload={setIsHandlingUpload} />
|
<ImageUploadOverlay dropzone={dropzone} setIsHandlingUpload={setIsHandlingUpload} />
|
||||||
@ -119,7 +130,6 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, desti
|
|||||||
<ChangeBoardModal />
|
<ChangeBoardModal />
|
||||||
<DynamicPromptsModal />
|
<DynamicPromptsModal />
|
||||||
<StylePresetModal />
|
<StylePresetModal />
|
||||||
<ClearQueueConfirmationsAlertDialog />
|
|
||||||
<PreselectedImage selectedImage={selectedImage} />
|
<PreselectedImage selectedImage={selectedImage} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { Button, Flex, Heading, Image, Link, Text } from '@invoke-ai/ui-library';
|
import { Button, Flex, Heading, Image, Link, Text } from '@invoke-ai/ui-library';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectConfigSlice } from 'features/system/store/configSlice';
|
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import newGithubIssueUrl from 'new-github-issue-url';
|
import newGithubIssueUrl from 'new-github-issue-url';
|
||||||
import InvokeLogoYellow from 'public/assets/images/invoke-symbol-ylw-lrg.svg';
|
import InvokeLogoYellow from 'public/assets/images/invoke-symbol-ylw-lrg.svg';
|
||||||
@ -15,11 +13,9 @@ type Props = {
|
|||||||
resetErrorBoundary: () => void;
|
resetErrorBoundary: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectIsLocal = createSelector(selectConfigSlice, (config) => config.isLocal);
|
|
||||||
|
|
||||||
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isLocal = useAppSelector(selectIsLocal);
|
const isLocal = useAppSelector((s) => s.config.isLocal);
|
||||||
|
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useCallback(() => {
|
||||||
const text = JSON.stringify(serializeError(error), null, 2);
|
const text = JSON.stringify(serializeError(error), null, 2);
|
||||||
|
@ -19,7 +19,7 @@ import type { PartialAppConfig } from 'app/types/invokeai';
|
|||||||
import Loading from 'common/components/Loading/Loading';
|
import Loading from 'common/components/Loading/Loading';
|
||||||
import AppDndContext from 'features/dnd/components/AppDndContext';
|
import AppDndContext from 'features/dnd/components/AppDndContext';
|
||||||
import type { WorkflowCategory } from 'features/nodes/types/workflow';
|
import type { WorkflowCategory } from 'features/nodes/types/workflow';
|
||||||
import type { TabName } from 'features/ui/store/uiTypes';
|
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
import type { PropsWithChildren, ReactNode } from 'react';
|
import type { PropsWithChildren, ReactNode } from 'react';
|
||||||
import React, { lazy, memo, useEffect, useMemo } from 'react';
|
import React, { lazy, memo, useEffect, useMemo } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
@ -45,7 +45,8 @@ interface Props extends PropsWithChildren {
|
|||||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||||
};
|
};
|
||||||
selectedWorkflowId?: string;
|
selectedWorkflowId?: string;
|
||||||
destination?: TabName;
|
selectedStylePresetId?: string;
|
||||||
|
destination?: InvokeTabName;
|
||||||
customStarUi?: CustomStarUi;
|
customStarUi?: CustomStarUi;
|
||||||
socketOptions?: Partial<ManagerOptions & SocketOptions>;
|
socketOptions?: Partial<ManagerOptions & SocketOptions>;
|
||||||
isDebugging?: boolean;
|
isDebugging?: boolean;
|
||||||
@ -66,6 +67,7 @@ const InvokeAIUI = ({
|
|||||||
queueId,
|
queueId,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
selectedWorkflowId,
|
selectedWorkflowId,
|
||||||
|
selectedStylePresetId,
|
||||||
destination,
|
destination,
|
||||||
customStarUi,
|
customStarUi,
|
||||||
socketOptions,
|
socketOptions,
|
||||||
@ -227,6 +229,7 @@ const InvokeAIUI = ({
|
|||||||
config={config}
|
config={config}
|
||||||
selectedImage={selectedImage}
|
selectedImage={selectedImage}
|
||||||
selectedWorkflowId={selectedWorkflowId}
|
selectedWorkflowId={selectedWorkflowId}
|
||||||
|
selectedStylePresetId={selectedStylePresetId}
|
||||||
destination={destination}
|
destination={destination}
|
||||||
/>
|
/>
|
||||||
</AppDndContext>
|
</AppDndContext>
|
||||||
|
@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react';
|
|||||||
import { $authToken } from 'app/store/nanostores/authToken';
|
import { $authToken } from 'app/store/nanostores/authToken';
|
||||||
import { $baseUrl } from 'app/store/nanostores/baseUrl';
|
import { $baseUrl } from 'app/store/nanostores/baseUrl';
|
||||||
import { $isDebugging } from 'app/store/nanostores/isDebugging';
|
import { $isDebugging } from 'app/store/nanostores/isDebugging';
|
||||||
import { useAppStore } from 'app/store/nanostores/store';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import type { MapStore } from 'nanostores';
|
import type { MapStore } from 'nanostores';
|
||||||
import { atom, map } from 'nanostores';
|
import { atom, map } from 'nanostores';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
@ -18,19 +18,14 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
|
|
||||||
|
|
||||||
export const $socket = atom<AppSocket | null>(null);
|
|
||||||
export const $socketOptions = map<Partial<ManagerOptions & SocketOptions>>({});
|
export const $socketOptions = map<Partial<ManagerOptions & SocketOptions>>({});
|
||||||
|
|
||||||
const $isSocketInitialized = atom<boolean>(false);
|
const $isSocketInitialized = atom<boolean>(false);
|
||||||
export const $isConnected = atom<boolean>(false);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the socket.io connection and sets up event listeners.
|
* Initializes the socket.io connection and sets up event listeners.
|
||||||
*/
|
*/
|
||||||
export const useSocketIO = () => {
|
export const useSocketIO = () => {
|
||||||
const { dispatch, getState } = useAppStore();
|
const dispatch = useAppDispatch();
|
||||||
const baseUrl = useStore($baseUrl);
|
const baseUrl = useStore($baseUrl);
|
||||||
const authToken = useStore($authToken);
|
const authToken = useStore($authToken);
|
||||||
const addlSocketOptions = useStore($socketOptions);
|
const addlSocketOptions = useStore($socketOptions);
|
||||||
@ -66,9 +61,8 @@ export const useSocketIO = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const socket: AppSocket = io(socketUrl, socketOptions);
|
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(socketUrl, socketOptions);
|
||||||
$socket.set(socket);
|
setEventListeners({ dispatch, socket });
|
||||||
setEventListeners({ socket, dispatch, getState, setIsConnected: $isConnected.set });
|
|
||||||
socket.connect();
|
socket.connect();
|
||||||
|
|
||||||
if ($isDebugging.get() || import.meta.env.MODE === 'development') {
|
if ($isDebugging.get() || import.meta.env.MODE === 'development') {
|
||||||
@ -90,5 +84,5 @@ export const useSocketIO = () => {
|
|||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
$isSocketInitialized.set(false);
|
$isSocketInitialized.set(false);
|
||||||
};
|
};
|
||||||
}, [dispatch, getState, socketOptions, socketUrl]);
|
}, [dispatch, socketOptions, socketUrl]);
|
||||||
};
|
};
|
||||||
|
@ -15,21 +15,21 @@ export const BASE_CONTEXT = {};
|
|||||||
|
|
||||||
export const $logger = atom<Logger>(Roarr.child(BASE_CONTEXT));
|
export const $logger = atom<Logger>(Roarr.child(BASE_CONTEXT));
|
||||||
|
|
||||||
export const zLogNamespace = z.enum([
|
export type LoggerNamespace =
|
||||||
'canvas',
|
| 'images'
|
||||||
'config',
|
| 'models'
|
||||||
'events',
|
| 'config'
|
||||||
'gallery',
|
| 'canvas'
|
||||||
'generation',
|
| 'generation'
|
||||||
'metadata',
|
| 'nodes'
|
||||||
'models',
|
| 'system'
|
||||||
'system',
|
| 'socketio'
|
||||||
'queue',
|
| 'session'
|
||||||
'workflows',
|
| 'queue'
|
||||||
]);
|
| 'dnd'
|
||||||
export type LogNamespace = z.infer<typeof zLogNamespace>;
|
| 'controlLayers';
|
||||||
|
|
||||||
export const logger = (namespace: LogNamespace) => $logger.get().child({ namespace });
|
export const logger = (namespace: LoggerNamespace) => $logger.get().child({ namespace });
|
||||||
|
|
||||||
export const zLogLevel = z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']);
|
export const zLogLevel = z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']);
|
||||||
export type LogLevel = z.infer<typeof zLogLevel>;
|
export type LogLevel = z.infer<typeof zLogLevel>;
|
||||||
|
@ -1,41 +1,29 @@
|
|||||||
import { createLogWriter } from '@roarr/browser-log-writer';
|
import { createLogWriter } from '@roarr/browser-log-writer';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
|
||||||
selectSystemLogIsEnabled,
|
|
||||||
selectSystemLogLevel,
|
|
||||||
selectSystemLogNamespaces,
|
|
||||||
} from 'features/system/store/systemSlice';
|
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { ROARR, Roarr } from 'roarr';
|
import { ROARR, Roarr } from 'roarr';
|
||||||
|
|
||||||
import type { LogNamespace } from './logger';
|
import type { LoggerNamespace } from './logger';
|
||||||
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
|
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
|
||||||
|
|
||||||
export const useLogger = (namespace: LogNamespace) => {
|
export const useLogger = (namespace: LoggerNamespace) => {
|
||||||
const logLevel = useAppSelector(selectSystemLogLevel);
|
const consoleLogLevel = useAppSelector((s) => s.system.consoleLogLevel);
|
||||||
const logNamespaces = useAppSelector(selectSystemLogNamespaces);
|
const shouldLogToConsole = useAppSelector((s) => s.system.shouldLogToConsole);
|
||||||
const logIsEnabled = useAppSelector(selectSystemLogIsEnabled);
|
|
||||||
|
|
||||||
// The provided Roarr browser log writer uses localStorage to config logging to console
|
// The provided Roarr browser log writer uses localStorage to config logging to console
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (logIsEnabled) {
|
if (shouldLogToConsole) {
|
||||||
// Enable console log output
|
// Enable console log output
|
||||||
localStorage.setItem('ROARR_LOG', 'true');
|
localStorage.setItem('ROARR_LOG', 'true');
|
||||||
|
|
||||||
// Use a filter to show only logs of the given level
|
// Use a filter to show only logs of the given level
|
||||||
let filter = `context.logLevel:>=${LOG_LEVEL_MAP[logLevel]}`;
|
localStorage.setItem('ROARR_FILTER', `context.logLevel:>=${LOG_LEVEL_MAP[consoleLogLevel]}`);
|
||||||
if (logNamespaces.length > 0) {
|
|
||||||
filter += ` AND (${logNamespaces.map((ns) => `context.namespace:${ns}`).join(' OR ')})`;
|
|
||||||
} else {
|
|
||||||
filter += ' AND context.namespace:undefined';
|
|
||||||
}
|
|
||||||
localStorage.setItem('ROARR_FILTER', filter);
|
|
||||||
} else {
|
} else {
|
||||||
// Disable console log output
|
// Disable console log output
|
||||||
localStorage.setItem('ROARR_LOG', 'false');
|
localStorage.setItem('ROARR_LOG', 'false');
|
||||||
}
|
}
|
||||||
ROARR.write = createLogWriter();
|
ROARR.write = createLogWriter();
|
||||||
}, [logLevel, logIsEnabled, logNamespaces]);
|
}, [consoleLogLevel, shouldLogToConsole]);
|
||||||
|
|
||||||
// Update the module-scoped logger context as needed
|
// Update the module-scoped logger context as needed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import type { TabName } from 'features/ui/store/uiTypes';
|
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
|
|
||||||
export const enqueueRequested = createAction<{
|
export const enqueueRequested = createAction<{
|
||||||
tabName: TabName;
|
tabName: InvokeTabName;
|
||||||
prepend: boolean;
|
prepend: boolean;
|
||||||
}>('app/enqueueRequested');
|
}>('app/enqueueRequested');
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
export const STORAGE_PREFIX = '@@invokeai-';
|
export const STORAGE_PREFIX = '@@invokeai-';
|
||||||
export const EMPTY_ARRAY = [];
|
export const EMPTY_ARRAY = [];
|
||||||
export const EMPTY_OBJECT = {};
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { createDraftSafeSelectorCreator, createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
|
import { createDraftSafeSelectorCreator, createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
|
||||||
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
|
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
|
||||||
import type { RootState } from 'app/store/store';
|
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,5 +19,3 @@ export const getSelectorsOptions: GetSelectorsOptions = {
|
|||||||
argsMemoize: lruMemoize,
|
argsMemoize: lruMemoize,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createMemoizedAppSelector = createMemoizedSelector.withTypes<RootState>();
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
import { PersistError, RehydrateError } from 'redux-remember';
|
import { PersistError, RehydrateError } from 'redux-remember';
|
||||||
import { serializeError } from 'serialize-error';
|
import { serializeError } from 'serialize-error';
|
||||||
|
|
||||||
@ -40,6 +41,6 @@ export const errorHandler = (err: PersistError | RehydrateError) => {
|
|||||||
} else if (err instanceof RehydrateError) {
|
} else if (err instanceof RehydrateError) {
|
||||||
log.error({ error: serializeError(err) }, 'Problem rehydrating state');
|
log.error({ error: serializeError(err) }, 'Problem rehydrating state');
|
||||||
} else {
|
} else {
|
||||||
log.error({ error: serializeError(err) }, 'Problem in persistence layer');
|
log.error({ error: parseify(err) }, 'Problem in persistence layer');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import type { UnknownAction } from '@reduxjs/toolkit';
|
import type { UnknownAction } from '@reduxjs/toolkit';
|
||||||
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import { isAnyGraphBuilt } from 'features/nodes/store/actions';
|
import { isAnyGraphBuilt } from 'features/nodes/store/actions';
|
||||||
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
||||||
import type { Graph } from 'services/api/types';
|
import type { Graph } from 'services/api/types';
|
||||||
|
import { socketGeneratorProgress } from 'services/events/actions';
|
||||||
|
|
||||||
export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
|
export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
|
||||||
if (isAnyGraphBuilt(action)) {
|
if (isAnyGraphBuilt(action)) {
|
||||||
@ -22,5 +24,13 @@ export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (socketGeneratorProgress.match(action)) {
|
||||||
|
const sanitized = deepClone(action);
|
||||||
|
if (sanitized.payload.data.progress_image) {
|
||||||
|
sanitized.payload.data.progress_image.dataURL = '<Progress image omitted>';
|
||||||
|
}
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { TypedStartListening } from '@reduxjs/toolkit';
|
import type { TypedStartListening } from '@reduxjs/toolkit';
|
||||||
import { createListenerMiddleware } from '@reduxjs/toolkit';
|
import { createListenerMiddleware } from '@reduxjs/toolkit';
|
||||||
import { addAdHocPostProcessingRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener';
|
import { addAdHocPostProcessingRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener';
|
||||||
import { addStagingListeners } from 'app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener';
|
import { addCommitStagingAreaImageListener } from 'app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener';
|
||||||
import { addAnyEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/anyEnqueued';
|
import { addAnyEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/anyEnqueued';
|
||||||
import { addAppConfigReceivedListener } from 'app/store/middleware/listenerMiddleware/listeners/appConfigReceived';
|
import { addAppConfigReceivedListener } from 'app/store/middleware/listenerMiddleware/listeners/appConfigReceived';
|
||||||
import { addAppStartedListener } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
|
import { addAppStartedListener } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
|
||||||
@ -9,6 +9,17 @@ import { addBatchEnqueuedListener } from 'app/store/middleware/listenerMiddlewar
|
|||||||
import { addDeleteBoardAndImagesFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted';
|
import { addDeleteBoardAndImagesFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted';
|
||||||
import { addBoardIdSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/boardIdSelected';
|
import { addBoardIdSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/boardIdSelected';
|
||||||
import { addBulkDownloadListeners } from 'app/store/middleware/listenerMiddleware/listeners/bulkDownload';
|
import { addBulkDownloadListeners } from 'app/store/middleware/listenerMiddleware/listeners/bulkDownload';
|
||||||
|
import { addCanvasCopiedToClipboardListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasCopiedToClipboard';
|
||||||
|
import { addCanvasDownloadedAsImageListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasDownloadedAsImage';
|
||||||
|
import { addCanvasImageToControlNetListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasImageToControlNet';
|
||||||
|
import { addCanvasMaskSavedToGalleryListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasMaskSavedToGallery';
|
||||||
|
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 { 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';
|
||||||
import { addEnqueueRequestedLinear } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear';
|
import { addEnqueueRequestedLinear } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear';
|
||||||
import { addEnqueueRequestedNodes } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes';
|
import { addEnqueueRequestedNodes } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes';
|
||||||
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
||||||
@ -26,7 +37,16 @@ import { addModelSelectedListener } from 'app/store/middleware/listenerMiddlewar
|
|||||||
import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded';
|
import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded';
|
||||||
import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged';
|
import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged';
|
||||||
import { addSetDefaultSettingsListener } from 'app/store/middleware/listenerMiddleware/listeners/setDefaultSettings';
|
import { addSetDefaultSettingsListener } from 'app/store/middleware/listenerMiddleware/listeners/setDefaultSettings';
|
||||||
import { addSocketConnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketConnected';
|
import { addSocketConnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected';
|
||||||
|
import { addSocketDisconnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketDisconnected';
|
||||||
|
import { addGeneratorProgressEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketGeneratorProgress';
|
||||||
|
import { addInvocationCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete';
|
||||||
|
import { addInvocationErrorEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError';
|
||||||
|
import { addInvocationStartedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationStarted';
|
||||||
|
import { addModelInstallEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelInstall';
|
||||||
|
import { addModelLoadEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelLoad';
|
||||||
|
import { addSocketQueueItemStatusChangedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged';
|
||||||
|
import { addStagingAreaImageSavedListener } from 'app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved';
|
||||||
import { addUpdateAllNodesRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/updateAllNodesRequested';
|
import { addUpdateAllNodesRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/updateAllNodesRequested';
|
||||||
import { addWorkflowLoadRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested';
|
import { addWorkflowLoadRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested';
|
||||||
import type { AppDispatch, RootState } from 'app/store/store';
|
import type { AppDispatch, RootState } from 'app/store/store';
|
||||||
@ -63,6 +83,7 @@ addGalleryImageClickedListener(startAppListening);
|
|||||||
addGalleryOffsetChangedListener(startAppListening);
|
addGalleryOffsetChangedListener(startAppListening);
|
||||||
|
|
||||||
// User Invoked
|
// User Invoked
|
||||||
|
addEnqueueRequestedCanvasListener(startAppListening);
|
||||||
addEnqueueRequestedNodes(startAppListening);
|
addEnqueueRequestedNodes(startAppListening);
|
||||||
addEnqueueRequestedLinear(startAppListening);
|
addEnqueueRequestedLinear(startAppListening);
|
||||||
addEnqueueRequestedUpscale(startAppListening);
|
addEnqueueRequestedUpscale(startAppListening);
|
||||||
@ -70,23 +91,32 @@ addAnyEnqueuedListener(startAppListening);
|
|||||||
addBatchEnqueuedListener(startAppListening);
|
addBatchEnqueuedListener(startAppListening);
|
||||||
|
|
||||||
// Canvas actions
|
// Canvas actions
|
||||||
// addCanvasSavedToGalleryListener(startAppListening);
|
addCanvasSavedToGalleryListener(startAppListening);
|
||||||
// addCanvasMaskSavedToGalleryListener(startAppListening);
|
addCanvasMaskSavedToGalleryListener(startAppListening);
|
||||||
// addCanvasImageToControlNetListener(startAppListening);
|
addCanvasImageToControlNetListener(startAppListening);
|
||||||
// addCanvasMaskToControlNetListener(startAppListening);
|
addCanvasMaskToControlNetListener(startAppListening);
|
||||||
// addCanvasDownloadedAsImageListener(startAppListening);
|
addCanvasDownloadedAsImageListener(startAppListening);
|
||||||
// addCanvasCopiedToClipboardListener(startAppListening);
|
addCanvasCopiedToClipboardListener(startAppListening);
|
||||||
// addCanvasMergedListener(startAppListening);
|
addCanvasMergedListener(startAppListening);
|
||||||
// addStagingAreaImageSavedListener(startAppListening);
|
addStagingAreaImageSavedListener(startAppListening);
|
||||||
// addCommitStagingAreaImageListener(startAppListening);
|
addCommitStagingAreaImageListener(startAppListening);
|
||||||
addStagingListeners(startAppListening);
|
|
||||||
|
|
||||||
// Socket.IO
|
// Socket.IO
|
||||||
|
addGeneratorProgressEventListener(startAppListening);
|
||||||
|
addInvocationCompleteEventListener(startAppListening);
|
||||||
|
addInvocationErrorEventListener(startAppListening);
|
||||||
|
addInvocationStartedEventListener(startAppListening);
|
||||||
addSocketConnectedEventListener(startAppListening);
|
addSocketConnectedEventListener(startAppListening);
|
||||||
|
addSocketDisconnectedEventListener(startAppListening);
|
||||||
// Gallery bulk download
|
addModelLoadEventListener(startAppListening);
|
||||||
|
addModelInstallEventListener(startAppListening);
|
||||||
|
addSocketQueueItemStatusChangedEventListener(startAppListening);
|
||||||
addBulkDownloadListeners(startAppListening);
|
addBulkDownloadListeners(startAppListening);
|
||||||
|
|
||||||
|
// ControlNet
|
||||||
|
addControlNetImageProcessedListener(startAppListening);
|
||||||
|
addControlNetAutoProcessListener(startAppListening);
|
||||||
|
|
||||||
// Boards
|
// Boards
|
||||||
addImageAddedToBoardFulfilledListener(startAppListening);
|
addImageAddedToBoardFulfilledListener(startAppListening);
|
||||||
addImageRemovedFromBoardFulfilledListener(startAppListening);
|
addImageRemovedFromBoardFulfilledListener(startAppListening);
|
||||||
@ -118,4 +148,4 @@ addAdHocPostProcessingRequestedListener(startAppListening);
|
|||||||
addDynamicPromptsListener(startAppListening);
|
addDynamicPromptsListener(startAppListening);
|
||||||
|
|
||||||
addSetDefaultSettingsListener(startAppListening);
|
addSetDefaultSettingsListener(startAppListening);
|
||||||
// addControlAdapterPreprocessor(startAppListening);
|
addControlAdapterPreprocessor(startAppListening);
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import type { SerializableObject } from 'common/types';
|
import { parseify } from 'common/util/serialize';
|
||||||
import { buildAdHocPostProcessingGraph } from 'features/nodes/util/graph/buildAdHocPostProcessingGraph';
|
import { buildAdHocPostProcessingGraph } from 'features/nodes/util/graph/buildAdHocPostProcessingGraph';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
import type { BatchConfig, ImageDTO } from 'services/api/types';
|
import type { BatchConfig, ImageDTO } from 'services/api/types';
|
||||||
|
|
||||||
const log = logger('queue');
|
|
||||||
|
|
||||||
export const adHocPostProcessingRequested = createAction<{ imageDTO: ImageDTO }>(`upscaling/postProcessingRequested`);
|
export const adHocPostProcessingRequested = createAction<{ imageDTO: ImageDTO }>(`upscaling/postProcessingRequested`);
|
||||||
|
|
||||||
export const addAdHocPostProcessingRequestedListener = (startAppListening: AppStartListening) => {
|
export const addAdHocPostProcessingRequestedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: adHocPostProcessingRequested,
|
actionCreator: adHocPostProcessingRequested,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const log = logger('session');
|
||||||
|
|
||||||
const { imageDTO } = action.payload;
|
const { imageDTO } = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
@ -39,9 +39,9 @@ export const addAdHocPostProcessingRequestedListener = (startAppListening: AppSt
|
|||||||
|
|
||||||
const enqueueResult = await req.unwrap();
|
const enqueueResult = await req.unwrap();
|
||||||
req.reset();
|
req.reset();
|
||||||
log.debug({ enqueueResult } as SerializableObject, t('queue.graphQueued'));
|
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error({ enqueueBatchArg } as SerializableObject, t('queue.graphFailedToQueue'));
|
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
||||||
|
|
||||||
if (error instanceof Object && 'status' in error && error.status === 403) {
|
if (error instanceof Object && 'status' in error && error.status === 403) {
|
||||||
return;
|
return;
|
||||||
|
@ -23,7 +23,7 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis
|
|||||||
*/
|
*/
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: matchAnyBoardDeleted,
|
matcher: matchAnyBoardDeleted,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const deletedBoardId = action.meta.arg.originalArgs;
|
const deletedBoardId = action.meta.arg.originalArgs;
|
||||||
const { autoAddBoardId, selectedBoardId } = state.gallery;
|
const { autoAddBoardId, selectedBoardId } = state.gallery;
|
||||||
@ -44,7 +44,7 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis
|
|||||||
// If we archived a board, it may end up hidden. If it's selected or the auto-add board, we should reset those.
|
// If we archived a board, it may end up hidden. If it's selected or the auto-add board, we should reset those.
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: boardsApi.endpoints.updateBoard.matchFulfilled,
|
matcher: boardsApi.endpoints.updateBoard.matchFulfilled,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { shouldShowArchivedBoards } = state.gallery;
|
const { shouldShowArchivedBoards } = state.gallery;
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis
|
|||||||
// When we hide archived boards, if the selected or the auto-add board is archived, we should reset those.
|
// When we hide archived boards, if the selected or the auto-add board is archived, we should reset those.
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: shouldShowArchivedBoardsChanged,
|
actionCreator: shouldShowArchivedBoardsChanged,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const shouldShowArchivedBoards = action.payload;
|
const shouldShowArchivedBoards = action.payload;
|
||||||
|
|
||||||
// We only need to take action if we have just hidden archived boards.
|
// We only need to take action if we have just hidden archived boards.
|
||||||
@ -100,7 +100,7 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis
|
|||||||
*/
|
*/
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: boardsApi.endpoints.listAllBoards.matchFulfilled,
|
matcher: boardsApi.endpoints.listAllBoards.matchFulfilled,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const boards = action.payload;
|
const boards = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { selectedBoardId, autoAddBoardId } = state.gallery;
|
const { selectedBoardId, autoAddBoardId } = state.gallery;
|
||||||
|
@ -1,37 +1,33 @@
|
|||||||
|
import { isAnyOf } from '@reduxjs/toolkit';
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import {
|
import {
|
||||||
sessionStagingAreaImageAccepted,
|
canvasBatchIdsReset,
|
||||||
sessionStagingAreaReset,
|
commitStagingAreaImage,
|
||||||
} from 'features/controlLayers/store/canvasSessionSlice';
|
discardStagedImages,
|
||||||
import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice';
|
resetCanvas,
|
||||||
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
setInitialCanvasImage,
|
||||||
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
import { $lastCanvasProgressEvent } from 'services/events/setEventListeners';
|
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
const log = logger('canvas');
|
const matcher = isAnyOf(commitStagingAreaImage, discardStagedImages, resetCanvas, setInitialCanvasImage);
|
||||||
|
|
||||||
export const addStagingListeners = (startAppListening: AppStartListening) => {
|
export const addCommitStagingAreaImageListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: sessionStagingAreaReset,
|
matcher,
|
||||||
effect: async (_, { dispatch }) => {
|
effect: async (_, { dispatch, getState }) => {
|
||||||
|
const log = logger('canvas');
|
||||||
|
const state = getState();
|
||||||
|
const { batchIds } = state.canvas;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const req = dispatch(
|
const req = dispatch(
|
||||||
queueApi.endpoints.cancelByBatchOrigin.initiate(
|
queueApi.endpoints.cancelByBatchIds.initiate({ batch_ids: batchIds }, { fixedCacheKey: 'cancelByBatchIds' })
|
||||||
{ origin: 'canvas' },
|
|
||||||
{ fixedCacheKey: 'cancelByBatchOrigin' }
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
const { canceled } = await req.unwrap();
|
const { canceled } = await req.unwrap();
|
||||||
req.reset();
|
req.reset();
|
||||||
|
|
||||||
$lastCanvasProgressEvent.set(null);
|
|
||||||
|
|
||||||
if (canceled > 0) {
|
if (canceled > 0) {
|
||||||
log.debug(`Canceled ${canceled} canvas batches`);
|
log.debug(`Canceled ${canceled} canvas batches`);
|
||||||
toast({
|
toast({
|
||||||
@ -40,6 +36,7 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
|
|||||||
status: 'success',
|
status: 'success',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
dispatch(canvasBatchIdsReset());
|
||||||
} catch {
|
} catch {
|
||||||
log.error('Failed to cancel canvas batches');
|
log.error('Failed to cancel canvas batches');
|
||||||
toast({
|
toast({
|
||||||
@ -50,26 +47,4 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: sessionStagingAreaImageAccepted,
|
|
||||||
effect: (action, api) => {
|
|
||||||
const { index } = action.payload;
|
|
||||||
const state = api.getState();
|
|
||||||
const stagingAreaImage = state.canvasSession.stagedImages[index];
|
|
||||||
|
|
||||||
assert(stagingAreaImage, 'No staged image found to accept');
|
|
||||||
const { x, y } = selectCanvasSlice(state).bbox.rect;
|
|
||||||
|
|
||||||
const { imageDTO, offsetX, offsetY } = stagingAreaImage;
|
|
||||||
const imageObject = imageDTOToImageObject(imageDTO);
|
|
||||||
const overrides: Partial<CanvasRasterLayerState> = {
|
|
||||||
position: { x: x + offsetX, y: y + offsetY },
|
|
||||||
objects: [imageObject],
|
|
||||||
};
|
|
||||||
|
|
||||||
api.dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
|
||||||
api.dispatch(sessionStagingAreaReset());
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ import { queueApi, selectQueueStatus } from 'services/api/endpoints/queue';
|
|||||||
export const addAnyEnqueuedListener = (startAppListening: AppStartListening) => {
|
export const addAnyEnqueuedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: queueApi.endpoints.enqueueBatch.matchFulfilled,
|
matcher: queueApi.endpoints.enqueueBatch.matchFulfilled,
|
||||||
effect: (_, { dispatch, getState }) => {
|
effect: async (_, { dispatch, getState }) => {
|
||||||
const { data } = selectQueueStatus(getState());
|
const { data } = selectQueueStatus(getState());
|
||||||
|
|
||||||
if (!data || data.processor.is_started) {
|
if (!data || data.processor.is_started) {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { setInfillMethod } from 'features/controlLayers/store/paramsSlice';
|
import { setInfillMethod } from 'features/parameters/store/generationSlice';
|
||||||
import { shouldUseNSFWCheckerChanged, shouldUseWatermarkerChanged } from 'features/system/store/systemSlice';
|
import { shouldUseNSFWCheckerChanged, shouldUseWatermarkerChanged } from 'features/system/store/systemSlice';
|
||||||
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
||||||
|
|
||||||
export const addAppConfigReceivedListener = (startAppListening: AppStartListening) => {
|
export const addAppConfigReceivedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: appInfoApi.endpoints.getAppConfig.matchFulfilled,
|
matcher: appInfoApi.endpoints.getAppConfig.matchFulfilled,
|
||||||
effect: (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const { infill_methods = [], nsfw_methods = [], watermarking_methods = [] } = action.payload;
|
const { infill_methods = [], nsfw_methods = [], watermarking_methods = [] } = action.payload;
|
||||||
const infillMethod = getState().params.infillMethod;
|
const infillMethod = getState().generation.infillMethod;
|
||||||
|
|
||||||
if (!infill_methods.includes(infillMethod)) {
|
if (!infill_methods.includes(infillMethod)) {
|
||||||
// if there is no infill method, set it to the first one
|
// if there is no infill method, set it to the first one
|
||||||
|
@ -6,7 +6,7 @@ export const appStarted = createAction('app/appStarted');
|
|||||||
export const addAppStartedListener = (startAppListening: AppStartListening) => {
|
export const addAppStartedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: appStarted,
|
actionCreator: appStarted,
|
||||||
effect: (action, { unsubscribe, cancelActiveListeners }) => {
|
effect: async (action, { unsubscribe, cancelActiveListeners }) => {
|
||||||
// this should only run once
|
// this should only run once
|
||||||
cancelActiveListeners();
|
cancelActiveListeners();
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
|
@ -1,30 +1,27 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import type { SerializableObject } from 'common/types';
|
import { parseify } from 'common/util/serialize';
|
||||||
import { zPydanticValidationError } from 'features/system/store/zodSchemas';
|
import { zPydanticValidationError } from 'features/system/store/zodSchemas';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { truncate, upperFirst } from 'lodash-es';
|
import { truncate, upperFirst } from 'lodash-es';
|
||||||
import { serializeError } from 'serialize-error';
|
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
|
|
||||||
const log = logger('queue');
|
|
||||||
|
|
||||||
export const addBatchEnqueuedListener = (startAppListening: AppStartListening) => {
|
export const addBatchEnqueuedListener = (startAppListening: AppStartListening) => {
|
||||||
// success
|
// success
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: queueApi.endpoints.enqueueBatch.matchFulfilled,
|
matcher: queueApi.endpoints.enqueueBatch.matchFulfilled,
|
||||||
effect: (action) => {
|
effect: async (action) => {
|
||||||
const enqueueResult = action.payload;
|
const response = action.payload;
|
||||||
const arg = action.meta.arg.originalArgs;
|
const arg = action.meta.arg.originalArgs;
|
||||||
log.debug({ enqueueResult } as SerializableObject, 'Batch enqueued');
|
logger('queue').debug({ enqueueResult: parseify(response) }, 'Batch enqueued');
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
id: 'QUEUE_BATCH_SUCCEEDED',
|
id: 'QUEUE_BATCH_SUCCEEDED',
|
||||||
title: t('queue.batchQueued'),
|
title: t('queue.batchQueued'),
|
||||||
status: 'success',
|
status: 'success',
|
||||||
description: t('queue.batchQueuedDesc', {
|
description: t('queue.batchQueuedDesc', {
|
||||||
count: enqueueResult.enqueued,
|
count: response.enqueued,
|
||||||
direction: arg.prepend ? t('queue.front') : t('queue.back'),
|
direction: arg.prepend ? t('queue.front') : t('queue.back'),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@ -34,9 +31,9 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
|
|||||||
// error
|
// error
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: queueApi.endpoints.enqueueBatch.matchRejected,
|
matcher: queueApi.endpoints.enqueueBatch.matchRejected,
|
||||||
effect: (action) => {
|
effect: async (action) => {
|
||||||
const response = action.payload;
|
const response = action.payload;
|
||||||
const batchConfig = action.meta.arg.originalArgs;
|
const arg = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
toast({
|
toast({
|
||||||
@ -45,7 +42,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
|
|||||||
status: 'error',
|
status: 'error',
|
||||||
description: t('common.unknownError'),
|
description: t('common.unknownError'),
|
||||||
});
|
});
|
||||||
log.error({ batchConfig } as SerializableObject, t('queue.batchFailedToQueue'));
|
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +68,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
|
|||||||
description: t('common.unknownError'),
|
description: t('common.unknownError'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
log.error({ batchConfig, error: serializeError(response) } as SerializableObject, t('queue.batchFailedToQueue'));
|
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,31 +1,47 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
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 { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||||
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {
|
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.deleteBoardAndImages.matchFulfilled,
|
matcher: imagesApi.endpoints.deleteBoardAndImages.matchFulfilled,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const { deleted_images } = action.payload;
|
const { deleted_images } = action.payload;
|
||||||
|
|
||||||
// Remove all deleted images from the UI
|
// Remove all deleted images from the UI
|
||||||
|
|
||||||
|
let wasCanvasReset = false;
|
||||||
let wasNodeEditorReset = false;
|
let wasNodeEditorReset = false;
|
||||||
|
let wereControlAdaptersReset = false;
|
||||||
|
let wereControlLayersReset = false;
|
||||||
|
|
||||||
const state = getState();
|
const { canvas, nodes, controlAdapters, controlLayers } = getState();
|
||||||
const nodes = selectNodesSlice(state);
|
|
||||||
const canvas = selectCanvasSlice(state);
|
|
||||||
|
|
||||||
deleted_images.forEach((image_name) => {
|
deleted_images.forEach((image_name) => {
|
||||||
const imageUsage = getImageUsage(nodes, canvas, image_name);
|
const imageUsage = getImageUsage(canvas, nodes.present, controlAdapters, controlLayers.present, image_name);
|
||||||
|
|
||||||
|
if (imageUsage.isCanvasImage && !wasCanvasReset) {
|
||||||
|
dispatch(resetCanvas());
|
||||||
|
wasCanvasReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (imageUsage.isNodesImage && !wasNodeEditorReset) {
|
if (imageUsage.isNodesImage && !wasNodeEditorReset) {
|
||||||
dispatch(nodeEditorReset());
|
dispatch(nodeEditorReset());
|
||||||
wasNodeEditorReset = true;
|
wasNodeEditorReset = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (imageUsage.isControlImage && !wereControlAdaptersReset) {
|
||||||
|
dispatch(controlAdaptersReset());
|
||||||
|
wereControlAdaptersReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageUsage.isControlLayerImage && !wereControlLayersReset) {
|
||||||
|
dispatch(allLayersDeleted());
|
||||||
|
wereControlLayersReset = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
|
import { ExternalLink } from '@invoke-ai/ui-library';
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import {
|
||||||
|
socketBulkDownloadComplete,
|
||||||
|
socketBulkDownloadError,
|
||||||
|
socketBulkDownloadStarted,
|
||||||
|
} from 'services/events/actions';
|
||||||
|
|
||||||
const log = logger('gallery');
|
const log = logger('images');
|
||||||
|
|
||||||
export const addBulkDownloadListeners = (startAppListening: AppStartListening) => {
|
export const addBulkDownloadListeners = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.bulkDownloadImages.matchFulfilled,
|
matcher: imagesApi.endpoints.bulkDownloadImages.matchFulfilled,
|
||||||
effect: (action) => {
|
effect: async (action) => {
|
||||||
log.debug(action.payload, 'Bulk download requested');
|
log.debug(action.payload, 'Bulk download requested');
|
||||||
|
|
||||||
// If we have an item name, we are processing the bulk download locally and should use it as the toast id to
|
// If we have an item name, we are processing the bulk download locally and should use it as the toast id to
|
||||||
@ -27,7 +33,7 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
|
|||||||
|
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.bulkDownloadImages.matchRejected,
|
matcher: imagesApi.endpoints.bulkDownloadImages.matchRejected,
|
||||||
effect: () => {
|
effect: async () => {
|
||||||
log.debug('Bulk download request failed');
|
log.debug('Bulk download request failed');
|
||||||
|
|
||||||
// There isn't any toast to update if we get this event.
|
// There isn't any toast to update if we get this event.
|
||||||
@ -38,4 +44,55 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketBulkDownloadStarted,
|
||||||
|
effect: async (action) => {
|
||||||
|
// This should always happen immediately after the bulk download request, so we don't need to show a toast here.
|
||||||
|
log.debug(action.payload.data, 'Bulk download preparation started');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketBulkDownloadComplete,
|
||||||
|
effect: async (action) => {
|
||||||
|
log.debug(action.payload.data, 'Bulk download preparation completed');
|
||||||
|
|
||||||
|
const { bulk_download_item_name } = action.payload.data;
|
||||||
|
|
||||||
|
// TODO(psyche): This URL may break in in some environments (e.g. Nvidia workbench) but we need to test it first
|
||||||
|
const url = `/api/v1/images/download/${bulk_download_item_name}`;
|
||||||
|
|
||||||
|
toast({
|
||||||
|
id: bulk_download_item_name,
|
||||||
|
title: t('gallery.bulkDownloadReady', 'Download ready'),
|
||||||
|
status: 'success',
|
||||||
|
description: (
|
||||||
|
<ExternalLink
|
||||||
|
label={t('gallery.clickToDownload', 'Click here to download')}
|
||||||
|
href={url}
|
||||||
|
download={bulk_download_item_name}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
duration: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketBulkDownloadError,
|
||||||
|
effect: async (action) => {
|
||||||
|
log.debug(action.payload.data, 'Bulk download preparation failed');
|
||||||
|
|
||||||
|
const { bulk_download_item_name } = action.payload.data;
|
||||||
|
|
||||||
|
toast({
|
||||||
|
id: bulk_download_item_name,
|
||||||
|
title: t('gallery.bulkDownloadFailed'),
|
||||||
|
status: 'error',
|
||||||
|
description: action.payload.data.error,
|
||||||
|
duration: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import { $logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { canvasCopiedToClipboard } from 'features/canvas/store/actions';
|
||||||
|
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||||
|
import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
|
||||||
|
export const addCanvasCopiedToClipboardListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: canvasCopiedToClipboard,
|
||||||
|
effect: async (action, { getState }) => {
|
||||||
|
const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' });
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blob = getBaseLayerBlob(state);
|
||||||
|
|
||||||
|
copyBlobToClipboard(blob);
|
||||||
|
} catch (err) {
|
||||||
|
moduleLog.error(String(err));
|
||||||
|
toast({
|
||||||
|
id: 'CANVAS_COPY_FAILED',
|
||||||
|
title: t('toast.problemCopyingCanvas'),
|
||||||
|
description: t('toast.problemCopyingCanvasDesc'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
id: 'CANVAS_COPY_SUCCEEDED',
|
||||||
|
title: t('toast.canvasCopiedClipboard'),
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
import { $logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { canvasDownloadedAsImage } from 'features/canvas/store/actions';
|
||||||
|
import { downloadBlob } from 'features/canvas/util/downloadBlob';
|
||||||
|
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
|
||||||
|
export const addCanvasDownloadedAsImageListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: canvasDownloadedAsImage,
|
||||||
|
effect: async (action, { getState }) => {
|
||||||
|
const moduleLog = $logger.get().child({ namespace: 'canvasSavedToGalleryListener' });
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
let blob;
|
||||||
|
try {
|
||||||
|
blob = await getBaseLayerBlob(state);
|
||||||
|
} catch (err) {
|
||||||
|
moduleLog.error(String(err));
|
||||||
|
toast({
|
||||||
|
id: 'CANVAS_DOWNLOAD_FAILED',
|
||||||
|
title: t('toast.problemDownloadingCanvas'),
|
||||||
|
description: t('toast.problemDownloadingCanvasDesc'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadBlob(blob, 'canvas.png');
|
||||||
|
toast({ id: 'CANVAS_DOWNLOAD_SUCCEEDED', title: t('toast.canvasDownloaded'), status: 'success' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { canvasImageToControlAdapter } from 'features/canvas/store/actions';
|
||||||
|
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||||
|
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
export const addCanvasImageToControlNetListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: canvasImageToControlAdapter,
|
||||||
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const log = logger('canvas');
|
||||||
|
const state = getState();
|
||||||
|
const { id } = action.payload;
|
||||||
|
|
||||||
|
let blob: Blob;
|
||||||
|
try {
|
||||||
|
blob = await getBaseLayerBlob(state, true);
|
||||||
|
} catch (err) {
|
||||||
|
log.error(String(err));
|
||||||
|
toast({
|
||||||
|
id: 'PROBLEM_SAVING_CANVAS',
|
||||||
|
title: t('toast.problemSavingCanvas'),
|
||||||
|
description: t('toast.problemSavingCanvasDesc'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { autoAddBoardId } = state.gallery;
|
||||||
|
|
||||||
|
const imageDTO = await dispatch(
|
||||||
|
imagesApi.endpoints.uploadImage.initiate({
|
||||||
|
file: new File([blob], 'savedCanvas.png', {
|
||||||
|
type: 'image/png',
|
||||||
|
}),
|
||||||
|
image_category: 'control',
|
||||||
|
is_intermediate: true,
|
||||||
|
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
||||||
|
crop_visible: false,
|
||||||
|
postUploadAction: {
|
||||||
|
type: 'TOAST',
|
||||||
|
title: t('toast.canvasSentControlnetAssets'),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
const { image_name } = imageDTO;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
controlAdapterImageChanged({
|
||||||
|
id,
|
||||||
|
controlImage: image_name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { canvasMaskSavedToGallery } from 'features/canvas/store/actions';
|
||||||
|
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
export const addCanvasMaskSavedToGalleryListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: canvasMaskSavedToGallery,
|
||||||
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const log = logger('canvas');
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
const canvasBlobsAndImageData = await getCanvasData(
|
||||||
|
state.canvas.layerState,
|
||||||
|
state.canvas.boundingBoxCoordinates,
|
||||||
|
state.canvas.boundingBoxDimensions,
|
||||||
|
state.canvas.isMaskEnabled,
|
||||||
|
state.canvas.shouldPreserveMaskedArea
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!canvasBlobsAndImageData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { maskBlob } = canvasBlobsAndImageData;
|
||||||
|
|
||||||
|
if (!maskBlob) {
|
||||||
|
log.error('Problem getting mask layer blob');
|
||||||
|
toast({
|
||||||
|
id: 'PROBLEM_SAVING_MASK',
|
||||||
|
title: t('toast.problemSavingMask'),
|
||||||
|
description: t('toast.problemSavingMaskDesc'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { autoAddBoardId } = state.gallery;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
imagesApi.endpoints.uploadImage.initiate({
|
||||||
|
file: new File([maskBlob], 'canvasMaskImage.png', {
|
||||||
|
type: 'image/png',
|
||||||
|
}),
|
||||||
|
image_category: 'mask',
|
||||||
|
is_intermediate: false,
|
||||||
|
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
||||||
|
crop_visible: true,
|
||||||
|
postUploadAction: {
|
||||||
|
type: 'TOAST',
|
||||||
|
title: t('toast.maskSavedAssets'),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,70 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { canvasMaskToControlAdapter } from 'features/canvas/store/actions';
|
||||||
|
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
||||||
|
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
export const addCanvasMaskToControlNetListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: canvasMaskToControlAdapter,
|
||||||
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const log = logger('canvas');
|
||||||
|
const state = getState();
|
||||||
|
const { id } = action.payload;
|
||||||
|
const canvasBlobsAndImageData = await getCanvasData(
|
||||||
|
state.canvas.layerState,
|
||||||
|
state.canvas.boundingBoxCoordinates,
|
||||||
|
state.canvas.boundingBoxDimensions,
|
||||||
|
state.canvas.isMaskEnabled,
|
||||||
|
state.canvas.shouldPreserveMaskedArea
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!canvasBlobsAndImageData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { maskBlob } = canvasBlobsAndImageData;
|
||||||
|
|
||||||
|
if (!maskBlob) {
|
||||||
|
log.error('Problem getting mask layer blob');
|
||||||
|
toast({
|
||||||
|
id: 'PROBLEM_IMPORTING_MASK',
|
||||||
|
title: t('toast.problemImportingMask'),
|
||||||
|
description: t('toast.problemImportingMaskDesc'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { autoAddBoardId } = state.gallery;
|
||||||
|
|
||||||
|
const imageDTO = await dispatch(
|
||||||
|
imagesApi.endpoints.uploadImage.initiate({
|
||||||
|
file: new File([maskBlob], 'canvasMaskImage.png', {
|
||||||
|
type: 'image/png',
|
||||||
|
}),
|
||||||
|
image_category: 'mask',
|
||||||
|
is_intermediate: true,
|
||||||
|
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
||||||
|
crop_visible: false,
|
||||||
|
postUploadAction: {
|
||||||
|
type: 'TOAST',
|
||||||
|
title: t('toast.maskSentControlnetAssets'),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
const { image_name } = imageDTO;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
controlAdapterImageChanged({
|
||||||
|
id,
|
||||||
|
controlImage: image_name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,73 @@
|
|||||||
|
import { $logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { canvasMerged } from 'features/canvas/store/actions';
|
||||||
|
import { $canvasBaseLayer } from 'features/canvas/store/canvasNanostore';
|
||||||
|
import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
|
import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
export const addCanvasMergedListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: canvasMerged,
|
||||||
|
effect: async (action, { dispatch }) => {
|
||||||
|
const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' });
|
||||||
|
const blob = await getFullBaseLayerBlob();
|
||||||
|
|
||||||
|
if (!blob) {
|
||||||
|
moduleLog.error('Problem getting base layer blob');
|
||||||
|
toast({
|
||||||
|
id: 'PROBLEM_MERGING_CANVAS',
|
||||||
|
title: t('toast.problemMergingCanvas'),
|
||||||
|
description: t('toast.problemMergingCanvasDesc'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvasBaseLayer = $canvasBaseLayer.get();
|
||||||
|
|
||||||
|
if (!canvasBaseLayer) {
|
||||||
|
moduleLog.error('Problem getting canvas base layer');
|
||||||
|
toast({
|
||||||
|
id: 'PROBLEM_MERGING_CANVAS',
|
||||||
|
title: t('toast.problemMergingCanvas'),
|
||||||
|
description: t('toast.problemMergingCanvasDesc'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseLayerRect = canvasBaseLayer.getClientRect({
|
||||||
|
relativeTo: canvasBaseLayer.getParent() ?? undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageDTO = await dispatch(
|
||||||
|
imagesApi.endpoints.uploadImage.initiate({
|
||||||
|
file: new File([blob], 'mergedCanvas.png', {
|
||||||
|
type: 'image/png',
|
||||||
|
}),
|
||||||
|
image_category: 'general',
|
||||||
|
is_intermediate: true,
|
||||||
|
postUploadAction: {
|
||||||
|
type: 'TOAST',
|
||||||
|
title: t('toast.canvasMerged'),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// TODO: I can't figure out how to do the type narrowing in the `take()` so just brute forcing it here
|
||||||
|
const { image_name } = imageDTO;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setMergedCanvas({
|
||||||
|
kind: 'image',
|
||||||
|
layer: 'base',
|
||||||
|
imageName: image_name,
|
||||||
|
...baseLayerRect,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,53 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
|
import { canvasSavedToGallery } from 'features/canvas/store/actions';
|
||||||
|
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
export const addCanvasSavedToGalleryListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: canvasSavedToGallery,
|
||||||
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const log = logger('canvas');
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
let blob;
|
||||||
|
try {
|
||||||
|
blob = await getBaseLayerBlob(state);
|
||||||
|
} catch (err) {
|
||||||
|
log.error(String(err));
|
||||||
|
toast({
|
||||||
|
id: 'CANVAS_SAVE_FAILED',
|
||||||
|
title: t('toast.problemSavingCanvas'),
|
||||||
|
description: t('toast.problemSavingCanvasDesc'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { autoAddBoardId } = state.gallery;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
imagesApi.endpoints.uploadImage.initiate({
|
||||||
|
file: new File([blob], 'savedCanvas.png', {
|
||||||
|
type: 'image/png',
|
||||||
|
}),
|
||||||
|
image_category: 'general',
|
||||||
|
is_intermediate: false,
|
||||||
|
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
||||||
|
crop_visible: true,
|
||||||
|
postUploadAction: {
|
||||||
|
type: 'TOAST',
|
||||||
|
title: t('toast.canvasSavedGallery'),
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
_canvas_objects: parseify(state.canvas.layerState.objects),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,194 @@
|
|||||||
|
import { isAnyOf } from '@reduxjs/toolkit';
|
||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import type { AppDispatch } from 'app/store/store';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
|
import {
|
||||||
|
caLayerImageChanged,
|
||||||
|
caLayerModelChanged,
|
||||||
|
caLayerProcessedImageChanged,
|
||||||
|
caLayerProcessorConfigChanged,
|
||||||
|
caLayerProcessorPendingBatchIdChanged,
|
||||||
|
caLayerRecalled,
|
||||||
|
isControlAdapterLayer,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { isEqual } from 'lodash-es';
|
||||||
|
import { getImageDTO } from 'services/api/endpoints/images';
|
||||||
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
|
import type { BatchConfig } from 'services/api/types';
|
||||||
|
import { socketInvocationComplete } from 'services/events/actions';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
const matcher = isAnyOf(
|
||||||
|
caLayerImageChanged,
|
||||||
|
caLayerProcessedImageChanged,
|
||||||
|
caLayerProcessorConfigChanged,
|
||||||
|
caLayerModelChanged,
|
||||||
|
caLayerRecalled
|
||||||
|
);
|
||||||
|
|
||||||
|
const DEBOUNCE_MS = 300;
|
||||||
|
const log = logger('session');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple helper to cancel a batch and reset the pending batch ID
|
||||||
|
*/
|
||||||
|
const cancelProcessorBatch = async (dispatch: AppDispatch, layerId: string, batchId: string) => {
|
||||||
|
const req = dispatch(queueApi.endpoints.cancelByBatchIds.initiate({ batch_ids: [batchId] }));
|
||||||
|
log.trace({ batchId }, 'Cancelling existing preprocessor batch');
|
||||||
|
try {
|
||||||
|
await req.unwrap();
|
||||||
|
} catch {
|
||||||
|
// no-op
|
||||||
|
} finally {
|
||||||
|
req.reset();
|
||||||
|
// Always reset the pending batch ID - the cancel req could fail if the batch doesn't exist
|
||||||
|
dispatch(caLayerProcessorPendingBatchIdChanged({ layerId, batchId: null }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addControlAdapterPreprocessor = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
matcher,
|
||||||
|
effect: async (action, { dispatch, getState, getOriginalState, cancelActiveListeners, delay, take, signal }) => {
|
||||||
|
const layerId = caLayerRecalled.match(action) ? action.payload.id : action.payload.layerId;
|
||||||
|
const state = getState();
|
||||||
|
const originalState = getOriginalState();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
const layer = state.controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||||
|
|
||||||
|
if (!layer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should only process if the processor settings or image have changed
|
||||||
|
const originalLayer = originalState.controlLayers.present.layers
|
||||||
|
.filter(isControlAdapterLayer)
|
||||||
|
.find((l) => l.id === layerId);
|
||||||
|
const originalImage = originalLayer?.controlAdapter.image;
|
||||||
|
const originalConfig = originalLayer?.controlAdapter.processorConfig;
|
||||||
|
|
||||||
|
const image = layer.controlAdapter.image;
|
||||||
|
const processedImage = layer.controlAdapter.processedImage;
|
||||||
|
const config = layer.controlAdapter.processorConfig;
|
||||||
|
|
||||||
|
if (isEqual(config, originalConfig) && isEqual(image, originalImage) && processedImage) {
|
||||||
|
// Neither config nor image have changed, we can bail
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!image || !config) {
|
||||||
|
// - If we have no image, we have nothing to process
|
||||||
|
// - If we have no processor config, we have nothing to process
|
||||||
|
// Clear the processed image and bail
|
||||||
|
dispatch(caLayerProcessedImageChanged({ layerId, imageDTO: null }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, the user has stopped fiddling with the processor settings and there is a processor selected.
|
||||||
|
|
||||||
|
// If there is a pending processor batch, cancel it.
|
||||||
|
if (layer.controlAdapter.processorPendingBatchId) {
|
||||||
|
cancelProcessorBatch(dispatch, layerId, layer.controlAdapter.processorPendingBatchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(psyche): I can't get TS to be happy, it thinkgs `config` is `never` but it should be inferred from the generic... I'll just cast it for now
|
||||||
|
const processorNode = CA_PROCESSOR_DATA[config.type].buildNode(image, config as never);
|
||||||
|
const enqueueBatchArg: BatchConfig = {
|
||||||
|
prepend: true,
|
||||||
|
batch: {
|
||||||
|
graph: {
|
||||||
|
nodes: {
|
||||||
|
[processorNode.id]: {
|
||||||
|
...processorNode,
|
||||||
|
// Control images are always intermediate - do not save to gallery
|
||||||
|
is_intermediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edges: [],
|
||||||
|
},
|
||||||
|
runs: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Kick off the processor batch
|
||||||
|
const req = dispatch(
|
||||||
|
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, {
|
||||||
|
fixedCacheKey: 'enqueueBatch',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const enqueueResult = await req.unwrap();
|
||||||
|
// TODO(psyche): Update the pydantic models, pretty sure we will _always_ have a batch_id here, but the model says it's optional
|
||||||
|
assert(enqueueResult.batch.batch_id, 'Batch ID not returned from queue');
|
||||||
|
dispatch(caLayerProcessorPendingBatchIdChanged({ layerId, batchId: enqueueResult.batch.batch_id }));
|
||||||
|
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
||||||
|
|
||||||
|
// Wait for the processor node to complete
|
||||||
|
const [invocationCompleteAction] = await take(
|
||||||
|
(action): action is ReturnType<typeof socketInvocationComplete> =>
|
||||||
|
socketInvocationComplete.match(action) &&
|
||||||
|
action.payload.data.batch_id === enqueueResult.batch.batch_id &&
|
||||||
|
action.payload.data.invocation_source_id === processorNode.id
|
||||||
|
);
|
||||||
|
|
||||||
|
// We still have to check the output type
|
||||||
|
assert(
|
||||||
|
invocationCompleteAction.payload.data.result.type === 'image_output',
|
||||||
|
`Processor did not return an image output, got: ${invocationCompleteAction.payload.data.result}`
|
||||||
|
);
|
||||||
|
const { image_name } = invocationCompleteAction.payload.data.result.image;
|
||||||
|
|
||||||
|
const imageDTO = await getImageDTO(image_name);
|
||||||
|
assert(imageDTO, "Failed to fetch processor output's image DTO");
|
||||||
|
|
||||||
|
// Whew! We made it. Update the layer with the processed image
|
||||||
|
log.debug({ layerId, imageDTO }, 'ControlNet image processed');
|
||||||
|
dispatch(caLayerProcessedImageChanged({ layerId, imageDTO }));
|
||||||
|
dispatch(caLayerProcessorPendingBatchIdChanged({ layerId, batchId: null }));
|
||||||
|
} catch (error) {
|
||||||
|
if (signal.aborted) {
|
||||||
|
// The listener was canceled - we need to cancel the pending processor batch, if there is one (could have changed by now).
|
||||||
|
const pendingBatchId = getState()
|
||||||
|
.controlLayers.present.layers.filter(isControlAdapterLayer)
|
||||||
|
.find((l) => l.id === layerId)?.controlAdapter.processorPendingBatchId;
|
||||||
|
if (pendingBatchId) {
|
||||||
|
cancelProcessorBatch(dispatch, layerId, pendingBatchId);
|
||||||
|
}
|
||||||
|
log.trace('Control Adapter preprocessor cancelled');
|
||||||
|
} else {
|
||||||
|
// Some other error condition...
|
||||||
|
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
||||||
|
|
||||||
|
if (error instanceof Object) {
|
||||||
|
if ('data' in error && 'status' in error) {
|
||||||
|
if (error.status === 403) {
|
||||||
|
dispatch(caLayerImageChanged({ layerId, imageDTO: null }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
id: 'GRAPH_QUEUE_FAILED',
|
||||||
|
title: t('queue.graphFailedToQueue'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
req.reset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,85 @@
|
|||||||
|
import type { AnyListenerPredicate } from '@reduxjs/toolkit';
|
||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import type { RootState } from 'app/store/store';
|
||||||
|
import { controlAdapterImageProcessed } from 'features/controlAdapters/store/actions';
|
||||||
|
import {
|
||||||
|
controlAdapterAutoConfigToggled,
|
||||||
|
controlAdapterImageChanged,
|
||||||
|
controlAdapterModelChanged,
|
||||||
|
controlAdapterProcessorParamsChanged,
|
||||||
|
controlAdapterProcessortTypeChanged,
|
||||||
|
selectControlAdapterById,
|
||||||
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
|
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||||
|
|
||||||
|
type AnyControlAdapterParamChangeAction =
|
||||||
|
| ReturnType<typeof controlAdapterProcessorParamsChanged>
|
||||||
|
| ReturnType<typeof controlAdapterModelChanged>
|
||||||
|
| ReturnType<typeof controlAdapterImageChanged>
|
||||||
|
| ReturnType<typeof controlAdapterProcessortTypeChanged>
|
||||||
|
| ReturnType<typeof controlAdapterAutoConfigToggled>;
|
||||||
|
|
||||||
|
const predicate: AnyListenerPredicate<RootState> = (action, state, prevState) => {
|
||||||
|
const isActionMatched =
|
||||||
|
controlAdapterProcessorParamsChanged.match(action) ||
|
||||||
|
controlAdapterModelChanged.match(action) ||
|
||||||
|
controlAdapterImageChanged.match(action) ||
|
||||||
|
controlAdapterProcessortTypeChanged.match(action) ||
|
||||||
|
controlAdapterAutoConfigToggled.match(action);
|
||||||
|
|
||||||
|
if (!isActionMatched) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = action.payload;
|
||||||
|
const prevCA = selectControlAdapterById(prevState.controlAdapters, id);
|
||||||
|
const ca = selectControlAdapterById(state.controlAdapters, id);
|
||||||
|
if (!prevCA || !isControlNetOrT2IAdapter(prevCA) || !ca || !isControlNetOrT2IAdapter(ca)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlAdapterAutoConfigToggled.match(action)) {
|
||||||
|
// do not process if the user just disabled auto-config
|
||||||
|
if (prevCA.shouldAutoConfig === true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { controlImage, processorType, shouldAutoConfig } = ca;
|
||||||
|
if (controlAdapterModelChanged.match(action) && !shouldAutoConfig) {
|
||||||
|
// do not process if the action is a model change but the processor settings are dirty
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isProcessorSelected = processorType !== 'none';
|
||||||
|
|
||||||
|
const hasControlImage = Boolean(controlImage);
|
||||||
|
|
||||||
|
return isProcessorSelected && hasControlImage;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEBOUNCE_MS = 300;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener that automatically processes a ControlNet image when its processor parameters are changed.
|
||||||
|
*
|
||||||
|
* The network request is debounced.
|
||||||
|
*/
|
||||||
|
export const addControlNetAutoProcessListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
predicate,
|
||||||
|
effect: async (action, { dispatch, cancelActiveListeners, delay }) => {
|
||||||
|
const log = logger('session');
|
||||||
|
const { id } = (action as AnyControlAdapterParamChangeAction).payload;
|
||||||
|
|
||||||
|
// Cancel any in-progress instances of this listener
|
||||||
|
cancelActiveListeners();
|
||||||
|
log.trace('ControlNet auto-process triggered');
|
||||||
|
// Delay before starting actual work
|
||||||
|
await delay(DEBOUNCE_MS);
|
||||||
|
|
||||||
|
dispatch(controlAdapterImageProcessed({ id }));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,118 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
|
import { controlAdapterImageProcessed } from 'features/controlAdapters/store/actions';
|
||||||
|
import {
|
||||||
|
controlAdapterImageChanged,
|
||||||
|
controlAdapterProcessedImageChanged,
|
||||||
|
pendingControlImagesCleared,
|
||||||
|
selectControlAdapterById,
|
||||||
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
|
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
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';
|
||||||
|
|
||||||
|
export const addControlNetImageProcessedListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: controlAdapterImageProcessed,
|
||||||
|
effect: async (action, { dispatch, getState, take }) => {
|
||||||
|
const log = logger('session');
|
||||||
|
const { id } = action.payload;
|
||||||
|
const ca = selectControlAdapterById(getState().controlAdapters, id);
|
||||||
|
|
||||||
|
if (!ca?.controlImage || !isControlNetOrT2IAdapter(ca)) {
|
||||||
|
log.error('Unable to process ControlNet image');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ca.processorType === 'none' || ca.processorNode.type === 'none') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControlNet one-off procressing graph is just the processor node, no edges.
|
||||||
|
// Also we need to grab the image.
|
||||||
|
|
||||||
|
const nodeId = ca.processorNode.id;
|
||||||
|
const enqueueBatchArg: BatchConfig = {
|
||||||
|
prepend: true,
|
||||||
|
batch: {
|
||||||
|
graph: {
|
||||||
|
nodes: {
|
||||||
|
[ca.processorNode.id]: {
|
||||||
|
...ca.processorNode,
|
||||||
|
is_intermediate: true,
|
||||||
|
use_cache: false,
|
||||||
|
image: { image_name: ca.controlImage },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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.batch_id === enqueueResult.batch.batch_id &&
|
||||||
|
action.payload.data.invocation_source_id === nodeId
|
||||||
|
);
|
||||||
|
|
||||||
|
// We still have to check the output type
|
||||||
|
if (invocationCompleteAction.payload.data.result.type === 'image_output') {
|
||||||
|
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 processedControlImage = payload as ImageDTO;
|
||||||
|
|
||||||
|
log.debug({ controlNetId: action.payload, processedControlImage }, 'ControlNet image processed');
|
||||||
|
|
||||||
|
// Update the processed image in the store
|
||||||
|
dispatch(
|
||||||
|
controlAdapterProcessedImageChanged({
|
||||||
|
id,
|
||||||
|
processedControlImage: processedControlImage.image_name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
||||||
|
|
||||||
|
if (error instanceof Object) {
|
||||||
|
if ('data' in error && 'status' in error) {
|
||||||
|
if (error.status === 403) {
|
||||||
|
dispatch(pendingControlImagesCleared());
|
||||||
|
dispatch(controlAdapterImageChanged({ id, controlImage: null }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
id: 'GRAPH_QUEUE_FAILED',
|
||||||
|
title: t('queue.graphFailedToQueue'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,144 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import { enqueueRequested } from 'app/store/actions';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
|
import { canvasBatchIdAdded, stagingAreaInitialized } from 'features/canvas/store/canvasSlice';
|
||||||
|
import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
|
||||||
|
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
||||||
|
import { getCanvasGenerationMode } from 'features/canvas/util/getCanvasGenerationMode';
|
||||||
|
import { canvasGraphBuilt } from 'features/nodes/store/actions';
|
||||||
|
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||||
|
import { buildCanvasGraph } from 'features/nodes/util/graph/canvas/buildCanvasGraph';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
|
import type { ImageDTO } from 'services/api/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This listener is responsible invoking the canvas. This involves a number of steps:
|
||||||
|
*
|
||||||
|
* 1. Generate image blobs from the canvas layers
|
||||||
|
* 2. Determine the generation mode from the layers (txt2img, img2img, inpaint)
|
||||||
|
* 3. Build the canvas graph
|
||||||
|
* 4. Create the session with the graph
|
||||||
|
* 5. Upload the init image if necessary
|
||||||
|
* 6. Upload the mask image if necessary
|
||||||
|
* 7. Update the init and mask images with the session ID
|
||||||
|
* 8. Initialize the staging area if not yet initialized
|
||||||
|
* 9. Dispatch the sessionReadyToInvoke action to invoke the session
|
||||||
|
*/
|
||||||
|
export const addEnqueueRequestedCanvasListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||||
|
enqueueRequested.match(action) && action.payload.tabName === 'canvas',
|
||||||
|
effect: async (action, { getState, dispatch }) => {
|
||||||
|
const log = logger('queue');
|
||||||
|
const { prepend } = action.payload;
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
const { layerState, boundingBoxCoordinates, boundingBoxDimensions, isMaskEnabled, shouldPreserveMaskedArea } =
|
||||||
|
state.canvas;
|
||||||
|
|
||||||
|
// Build canvas blobs
|
||||||
|
const canvasBlobsAndImageData = await getCanvasData(
|
||||||
|
layerState,
|
||||||
|
boundingBoxCoordinates,
|
||||||
|
boundingBoxDimensions,
|
||||||
|
isMaskEnabled,
|
||||||
|
shouldPreserveMaskedArea
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!canvasBlobsAndImageData) {
|
||||||
|
log.error('Unable to create canvas data');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { baseBlob, baseImageData, maskBlob, maskImageData } = canvasBlobsAndImageData;
|
||||||
|
|
||||||
|
// Determine the generation mode
|
||||||
|
const generationMode = getCanvasGenerationMode(baseImageData, maskImageData);
|
||||||
|
|
||||||
|
if (state.system.enableImageDebugging) {
|
||||||
|
const baseDataURL = await blobToDataURL(baseBlob);
|
||||||
|
const maskDataURL = await blobToDataURL(maskBlob);
|
||||||
|
openBase64ImageInTab([
|
||||||
|
{ base64: maskDataURL, caption: 'mask b64' },
|
||||||
|
{ base64: baseDataURL, caption: 'image b64' },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(`Generation mode: ${generationMode}`);
|
||||||
|
|
||||||
|
// Temp placeholders for the init and mask images
|
||||||
|
let canvasInitImage: ImageDTO | undefined;
|
||||||
|
let canvasMaskImage: ImageDTO | undefined;
|
||||||
|
|
||||||
|
// For img2img and inpaint/outpaint, we need to upload the init images
|
||||||
|
if (['img2img', 'inpaint', 'outpaint'].includes(generationMode)) {
|
||||||
|
// upload the image, saving the request id
|
||||||
|
canvasInitImage = await dispatch(
|
||||||
|
imagesApi.endpoints.uploadImage.initiate({
|
||||||
|
file: new File([baseBlob], 'canvasInitImage.png', {
|
||||||
|
type: 'image/png',
|
||||||
|
}),
|
||||||
|
image_category: 'general',
|
||||||
|
is_intermediate: true,
|
||||||
|
})
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// For inpaint/outpaint, we also need to upload the mask layer
|
||||||
|
if (['inpaint', 'outpaint'].includes(generationMode)) {
|
||||||
|
// upload the image, saving the request id
|
||||||
|
canvasMaskImage = await dispatch(
|
||||||
|
imagesApi.endpoints.uploadImage.initiate({
|
||||||
|
file: new File([maskBlob], 'canvasMaskImage.png', {
|
||||||
|
type: 'image/png',
|
||||||
|
}),
|
||||||
|
image_category: 'mask',
|
||||||
|
is_intermediate: true,
|
||||||
|
})
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
const graph = await buildCanvasGraph(state, generationMode, canvasInitImage, canvasMaskImage);
|
||||||
|
|
||||||
|
log.debug({ graph: parseify(graph) }, `Canvas graph built`);
|
||||||
|
|
||||||
|
// currently this action is just listened to for logging
|
||||||
|
dispatch(canvasGraphBuilt(graph));
|
||||||
|
|
||||||
|
const batchConfig = prepareLinearUIBatch(state, graph, prepend);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const req = dispatch(
|
||||||
|
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
||||||
|
fixedCacheKey: 'enqueueBatch',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const enqueueResult = await req.unwrap();
|
||||||
|
req.reset();
|
||||||
|
|
||||||
|
const batchId = enqueueResult.batch.batch_id as string; // we know the is a string, backend provides it
|
||||||
|
|
||||||
|
// Prep the canvas staging area if it is not yet initialized
|
||||||
|
if (!state.canvas.layerState.stagingArea.boundingBox) {
|
||||||
|
dispatch(
|
||||||
|
stagingAreaInitialized({
|
||||||
|
boundingBox: {
|
||||||
|
...state.canvas.boundingBoxCoordinates,
|
||||||
|
...state.canvas.boundingBoxDimensions,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate the session with the canvas session ID
|
||||||
|
dispatch(canvasBatchIdAdded(batchId));
|
||||||
|
} catch {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -1,21 +1,10 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import { enqueueRequested } from 'app/store/actions';
|
import { enqueueRequested } from 'app/store/actions';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import type { SerializableObject } from 'common/types';
|
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import type { Result } from 'common/util/result';
|
|
||||||
import { isErr, withResult, withResultAsync } from 'common/util/result';
|
|
||||||
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
|
|
||||||
import { sessionStagingAreaReset, sessionStartedStaging } from 'features/controlLayers/store/canvasSessionSlice';
|
|
||||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||||
import { buildSD1Graph } from 'features/nodes/util/graph/generation/buildSD1Graph';
|
import { buildGenerationTabGraph } from 'features/nodes/util/graph/generation/buildGenerationTabGraph';
|
||||||
import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGraph';
|
import { buildGenerationTabSDXLGraph } from 'features/nodes/util/graph/generation/buildGenerationTabSDXLGraph';
|
||||||
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
|
||||||
import { serializeError } from 'serialize-error';
|
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
import type { Invocation } from 'services/api/types';
|
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
const log = logger('generation');
|
|
||||||
|
|
||||||
export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) => {
|
export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
@ -23,77 +12,33 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
|||||||
enqueueRequested.match(action) && action.payload.tabName === 'generation',
|
enqueueRequested.match(action) && action.payload.tabName === 'generation',
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const model = state.params.model;
|
const { shouldShowProgressInViewer } = state.ui;
|
||||||
|
const model = state.generation.model;
|
||||||
const { prepend } = action.payload;
|
const { prepend } = action.payload;
|
||||||
|
|
||||||
const manager = $canvasManager.get();
|
let graph;
|
||||||
assert(manager, 'No model found in state');
|
|
||||||
|
|
||||||
let didStartStaging = false;
|
if (model?.base === 'sdxl') {
|
||||||
|
graph = await buildGenerationTabSDXLGraph(state);
|
||||||
if (!state.canvasSession.isStaging && state.canvasSession.mode === 'compose') {
|
} else {
|
||||||
dispatch(sessionStartedStaging());
|
graph = await buildGenerationTabGraph(state);
|
||||||
didStartStaging = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const abortStaging = () => {
|
const batchConfig = prepareLinearUIBatch(state, graph, prepend);
|
||||||
if (didStartStaging && getState().canvasSession.isStaging) {
|
|
||||||
dispatch(sessionStagingAreaReset());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let buildGraphResult: Result<
|
|
||||||
{ g: Graph; noise: Invocation<'noise'>; posCond: Invocation<'compel' | 'sdxl_compel_prompt'> },
|
|
||||||
Error
|
|
||||||
>;
|
|
||||||
|
|
||||||
assert(model, 'No model found in state');
|
|
||||||
const base = model.base;
|
|
||||||
|
|
||||||
switch (base) {
|
|
||||||
case 'sdxl':
|
|
||||||
buildGraphResult = await withResultAsync(() => buildSDXLGraph(state, manager));
|
|
||||||
break;
|
|
||||||
case 'sd-1':
|
|
||||||
case `sd-2`:
|
|
||||||
buildGraphResult = await withResultAsync(() => buildSD1Graph(state, manager));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(false, `No graph builders for base ${base}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isErr(buildGraphResult)) {
|
|
||||||
log.error({ error: serializeError(buildGraphResult.error) }, 'Failed to build graph');
|
|
||||||
abortStaging();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { g, noise, posCond } = buildGraphResult.value;
|
|
||||||
|
|
||||||
const prepareBatchResult = withResult(() => prepareLinearUIBatch(state, g, prepend, noise, posCond));
|
|
||||||
|
|
||||||
if (isErr(prepareBatchResult)) {
|
|
||||||
log.error({ error: serializeError(prepareBatchResult.error) }, 'Failed to prepare batch');
|
|
||||||
abortStaging();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const req = dispatch(
|
const req = dispatch(
|
||||||
queueApi.endpoints.enqueueBatch.initiate(prepareBatchResult.value, {
|
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
||||||
fixedCacheKey: 'enqueueBatch',
|
fixedCacheKey: 'enqueueBatch',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
req.reset();
|
try {
|
||||||
|
await req.unwrap();
|
||||||
const enqueueResult = await withResultAsync(() => req.unwrap());
|
if (shouldShowProgressInViewer) {
|
||||||
|
dispatch(isImageViewerOpenChanged(true));
|
||||||
if (isErr(enqueueResult)) {
|
}
|
||||||
log.error({ error: serializeError(enqueueResult.error) }, 'Failed to enqueue batch');
|
} finally {
|
||||||
abortStaging();
|
req.reset();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug({ batchConfig: prepareBatchResult.value } as SerializableObject, 'Enqueued batch');
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { enqueueRequested } from 'app/store/actions';
|
import { enqueueRequested } from 'app/store/actions';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
|
||||||
import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
|
import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
|
||||||
import { buildWorkflowWithValidation } from 'features/nodes/util/workflow/buildWorkflow';
|
import { buildWorkflowWithValidation } from 'features/nodes/util/workflow/buildWorkflow';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
@ -12,12 +11,12 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) =
|
|||||||
enqueueRequested.match(action) && action.payload.tabName === 'workflows',
|
enqueueRequested.match(action) && action.payload.tabName === 'workflows',
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const nodes = selectNodesSlice(state);
|
const { nodes, edges } = state.nodes.present;
|
||||||
const workflow = state.workflow;
|
const workflow = state.workflow;
|
||||||
const graph = buildNodesGraph(nodes);
|
const graph = buildNodesGraph(state.nodes.present);
|
||||||
const builtWorkflow = buildWorkflowWithValidation({
|
const builtWorkflow = buildWorkflowWithValidation({
|
||||||
nodes: nodes.nodes,
|
nodes,
|
||||||
edges: nodes.edges,
|
edges,
|
||||||
workflow,
|
workflow,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -30,8 +29,7 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) =
|
|||||||
batch: {
|
batch: {
|
||||||
graph,
|
graph,
|
||||||
workflow: builtWorkflow,
|
workflow: builtWorkflow,
|
||||||
runs: state.params.iterations,
|
runs: state.generation.iterations,
|
||||||
origin: 'workflows',
|
|
||||||
},
|
},
|
||||||
prepend: action.payload.prepend,
|
prepend: action.payload.prepend,
|
||||||
};
|
};
|
||||||
|
@ -14,9 +14,9 @@ export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening)
|
|||||||
const { shouldShowProgressInViewer } = state.ui;
|
const { shouldShowProgressInViewer } = state.ui;
|
||||||
const { prepend } = action.payload;
|
const { prepend } = action.payload;
|
||||||
|
|
||||||
const { g, noise, posCond } = await buildMultidiffusionUpscaleGraph(state);
|
const graph = await buildMultidiffusionUpscaleGraph(state);
|
||||||
|
|
||||||
const batchConfig = prepareLinearUIBatch(state, g, prepend, noise, posCond);
|
const batchConfig = prepareLinearUIBatch(state, graph, prepend);
|
||||||
|
|
||||||
const req = dispatch(
|
const req = dispatch(
|
||||||
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
||||||
|
@ -27,7 +27,7 @@ export const galleryImageClicked = createAction<{
|
|||||||
export const addGalleryImageClickedListener = (startAppListening: AppStartListening) => {
|
export const addGalleryImageClickedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: galleryImageClicked,
|
actionCreator: galleryImageClicked,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const { imageDTO, shiftKey, ctrlKey, metaKey, altKey } = action.payload;
|
const { imageDTO, shiftKey, ctrlKey, metaKey, altKey } = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const queryArgs = selectListImagesQueryArgs(state);
|
const queryArgs = selectListImagesQueryArgs(state);
|
||||||
|
@ -1,27 +1,24 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import type { SerializableObject } from 'common/types';
|
|
||||||
import { parseify } from 'common/util/serialize';
|
import { parseify } from 'common/util/serialize';
|
||||||
import { $templates } from 'features/nodes/store/nodesSlice';
|
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||||
import { parseSchema } from 'features/nodes/util/schema/parseSchema';
|
import { parseSchema } from 'features/nodes/util/schema/parseSchema';
|
||||||
import { size } from 'lodash-es';
|
import { size } from 'lodash-es';
|
||||||
import { serializeError } from 'serialize-error';
|
|
||||||
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
||||||
|
|
||||||
const log = logger('system');
|
|
||||||
|
|
||||||
export const addGetOpenAPISchemaListener = (startAppListening: AppStartListening) => {
|
export const addGetOpenAPISchemaListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: appInfoApi.endpoints.getOpenAPISchema.matchFulfilled,
|
matcher: appInfoApi.endpoints.getOpenAPISchema.matchFulfilled,
|
||||||
effect: (action, { getState }) => {
|
effect: (action, { getState }) => {
|
||||||
|
const log = logger('system');
|
||||||
const schemaJSON = action.payload;
|
const schemaJSON = action.payload;
|
||||||
|
|
||||||
log.debug({ schemaJSON: parseify(schemaJSON) } as SerializableObject, 'Received OpenAPI schema');
|
log.debug({ schemaJSON: parseify(schemaJSON) }, 'Received OpenAPI schema');
|
||||||
const { nodesAllowlist, nodesDenylist } = getState().config;
|
const { nodesAllowlist, nodesDenylist } = getState().config;
|
||||||
|
|
||||||
const nodeTemplates = parseSchema(schemaJSON, nodesAllowlist, nodesDenylist);
|
const nodeTemplates = parseSchema(schemaJSON, nodesAllowlist, nodesDenylist);
|
||||||
|
|
||||||
log.debug({ nodeTemplates } as SerializableObject, `Built ${size(nodeTemplates)} node templates`);
|
log.debug({ nodeTemplates: parseify(nodeTemplates) }, `Built ${size(nodeTemplates)} node templates`);
|
||||||
|
|
||||||
$templates.set(nodeTemplates);
|
$templates.set(nodeTemplates);
|
||||||
},
|
},
|
||||||
@ -33,7 +30,8 @@ export const addGetOpenAPISchemaListener = (startAppListening: AppStartListening
|
|||||||
// If action.meta.condition === true, the request was canceled/skipped because another request was in flight or
|
// If action.meta.condition === true, the request was canceled/skipped because another request was in flight or
|
||||||
// the value was already in the cache. We don't want to log these errors.
|
// the value was already in the cache. We don't want to log these errors.
|
||||||
if (!action.meta.condition) {
|
if (!action.meta.condition) {
|
||||||
log.error({ error: serializeError(action.error) }, 'Problem retrieving OpenAPI Schema');
|
const log = logger('system');
|
||||||
|
log.error({ error: parseify(action.error) }, 'Problem retrieving OpenAPI Schema');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -2,13 +2,15 @@ import { logger } from 'app/logging/logger';
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
const log = logger('gallery');
|
|
||||||
|
|
||||||
export const addImageAddedToBoardFulfilledListener = (startAppListening: AppStartListening) => {
|
export const addImageAddedToBoardFulfilledListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.addImageToBoard.matchFulfilled,
|
matcher: imagesApi.endpoints.addImageToBoard.matchFulfilled,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
|
const log = logger('images');
|
||||||
const { board_id, imageDTO } = action.meta.arg.originalArgs;
|
const { board_id, imageDTO } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
// TODO: update listImages cache for this board
|
||||||
|
|
||||||
log.debug({ board_id, imageDTO }, 'Image added to board');
|
log.debug({ board_id, imageDTO }, 'Image added to board');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -16,7 +18,9 @@ export const addImageAddedToBoardFulfilledListener = (startAppListening: AppStar
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.addImageToBoard.matchRejected,
|
matcher: imagesApi.endpoints.addImageToBoard.matchRejected,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
|
const log = logger('images');
|
||||||
const { board_id, imageDTO } = action.meta.arg.originalArgs;
|
const { board_id, imageDTO } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
log.debug({ board_id, imageDTO }, 'Problem adding image to board');
|
log.debug({ board_id, imageDTO }, 'Problem adding image to board');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,20 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import type { AppDispatch, RootState } from 'app/store/store';
|
import type { AppDispatch, RootState } from 'app/store/store';
|
||||||
import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasSlice';
|
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
import {
|
||||||
import { getEntityIdentifier } from 'features/controlLayers/store/types';
|
controlAdapterImageChanged,
|
||||||
|
controlAdapterProcessedImageChanged,
|
||||||
|
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 { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||||
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
||||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
@ -15,10 +26,6 @@ import { forEach, intersectionBy } from 'lodash-es';
|
|||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
|
|
||||||
const log = logger('gallery');
|
|
||||||
|
|
||||||
//TODO(psyche): handle image deletion (canvas sessions?)
|
|
||||||
|
|
||||||
// Some utils to delete images from different parts of the app
|
// Some utils to delete images from different parts of the app
|
||||||
const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
state.nodes.present.nodes.forEach((node) => {
|
state.nodes.present.nodes.forEach((node) => {
|
||||||
@ -40,37 +47,52 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
// state.canvas.present.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => {
|
forEach(selectControlAdapterAll(state.controlAdapters), (ca) => {
|
||||||
// if (
|
if (
|
||||||
// imageObject?.image.image_name === imageDTO.image_name ||
|
ca.controlImage === imageDTO.image_name ||
|
||||||
// processedImageObject?.image.image_name === imageDTO.image_name
|
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
||||||
// ) {
|
) {
|
||||||
// dispatch(caImageChanged({ id, imageDTO: null }));
|
dispatch(
|
||||||
// dispatch(caProcessedImageChanged({ id, imageDTO: null }));
|
controlAdapterImageChanged({
|
||||||
// }
|
id: ca.id,
|
||||||
// });
|
controlImage: null,
|
||||||
// };
|
})
|
||||||
|
);
|
||||||
const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
dispatch(
|
||||||
selectCanvasSlice(state).ipAdapters.entities.forEach((entity) => {
|
controlAdapterProcessedImageChanged({
|
||||||
if (entity.ipAdapter.image?.image_name === imageDTO.image_name) {
|
id: ca.id,
|
||||||
dispatch(ipaImageChanged({ entityIdentifier: getEntityIdentifier(entity), imageDTO: null }));
|
processedControlImage: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
const deleteControlLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
selectCanvasSlice(state).rasterLayers.entities.forEach(({ id, objects }) => {
|
state.controlLayers.present.layers.forEach((l) => {
|
||||||
let shouldDelete = false;
|
if (isRegionalGuidanceLayer(l)) {
|
||||||
for (const obj of objects) {
|
if (l.ipAdapters.some((ipa) => ipa.image?.name === imageDTO.image_name)) {
|
||||||
if (obj.type === 'image' && obj.image.image_name === imageDTO.image_name) {
|
dispatch(layerDeleted(l.id));
|
||||||
shouldDelete = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shouldDelete) {
|
if (isControlAdapterLayer(l)) {
|
||||||
dispatch(entityDeleted({ entityIdentifier: { id, type: 'raster_layer' } }));
|
if (
|
||||||
|
l.controlAdapter.image?.name === imageDTO.image_name ||
|
||||||
|
l.controlAdapter.processedImage?.name === imageDTO.image_name
|
||||||
|
) {
|
||||||
|
dispatch(layerDeleted(l.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isIPAdapterLayer(l)) {
|
||||||
|
if (l.ipAdapter.image?.name === imageDTO.image_name) {
|
||||||
|
dispatch(layerDeleted(l.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isInitialImageLayer(l)) {
|
||||||
|
if (l.image?.name === imageDTO.image_name) {
|
||||||
|
dispatch(layerDeleted(l.id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -123,10 +145,14 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to reset the features where the image is in use - none of these work if their image(s) don't exist
|
||||||
|
if (imageUsage.isCanvasImage) {
|
||||||
|
dispatch(resetCanvas());
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||||
deleteNodesImages(state, dispatch, imageDTO);
|
deleteNodesImages(state, dispatch, imageDTO);
|
||||||
// deleteControlAdapterImages(state, dispatch, imageDTO);
|
deleteControlLayerImages(state, dispatch, imageDTO);
|
||||||
deleteIPAdapterImages(state, dispatch, imageDTO);
|
|
||||||
deleteLayerImages(state, dispatch, imageDTO);
|
|
||||||
} catch {
|
} catch {
|
||||||
// no-op
|
// no-op
|
||||||
} finally {
|
} finally {
|
||||||
@ -163,11 +189,14 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
|||||||
|
|
||||||
// We need to reset the features where the image is in use - none of these work if their image(s) don't exist
|
// We need to reset the features where the image is in use - none of these work if their image(s) don't exist
|
||||||
|
|
||||||
|
if (imagesUsage.some((i) => i.isCanvasImage)) {
|
||||||
|
dispatch(resetCanvas());
|
||||||
|
}
|
||||||
|
|
||||||
imageDTOs.forEach((imageDTO) => {
|
imageDTOs.forEach((imageDTO) => {
|
||||||
|
deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||||
deleteNodesImages(state, dispatch, imageDTO);
|
deleteNodesImages(state, dispatch, imageDTO);
|
||||||
// deleteControlAdapterImages(state, dispatch, imageDTO);
|
deleteControlLayerImages(state, dispatch, imageDTO);
|
||||||
deleteIPAdapterImages(state, dispatch, imageDTO);
|
|
||||||
deleteLayerImages(state, dispatch, imageDTO);
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// no-op
|
// no-op
|
||||||
@ -191,6 +220,7 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.deleteImage.matchFulfilled,
|
matcher: imagesApi.endpoints.deleteImage.matchFulfilled,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
|
const log = logger('images');
|
||||||
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Image deleted');
|
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Image deleted');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -198,6 +228,7 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.deleteImage.matchRejected,
|
matcher: imagesApi.endpoints.deleteImage.matchRejected,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
|
const log = logger('images');
|
||||||
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Unable to delete image');
|
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Unable to delete image');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,19 +1,28 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import {
|
import {
|
||||||
controlLayerAdded,
|
controlAdapterImageChanged,
|
||||||
ipaImageChanged,
|
controlAdapterIsEnabledChanged,
|
||||||
rasterLayerAdded,
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
rgIPAdapterImageChanged,
|
import {
|
||||||
} from 'features/controlLayers/store/canvasSlice';
|
caLayerImageChanged,
|
||||||
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
iiLayerImageChanged,
|
||||||
import type { CanvasControlLayerState, CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
ipaLayerImageChanged,
|
||||||
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
rgLayerIPAdapterImageChanged,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
||||||
import { imageToCompareChanged, isImageViewerOpenChanged, selectionChanged } from 'features/gallery/store/gallerySlice';
|
import {
|
||||||
|
imageSelected,
|
||||||
|
imageToCompareChanged,
|
||||||
|
isImageViewerOpenChanged,
|
||||||
|
selectionChanged,
|
||||||
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
@ -22,12 +31,11 @@ export const dndDropped = createAction<{
|
|||||||
activeData: TypesafeDraggableData;
|
activeData: TypesafeDraggableData;
|
||||||
}>('dnd/dndDropped');
|
}>('dnd/dndDropped');
|
||||||
|
|
||||||
const log = logger('system');
|
|
||||||
|
|
||||||
export const addImageDroppedListener = (startAppListening: AppStartListening) => {
|
export const addImageDroppedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: dndDropped,
|
actionCreator: dndDropped,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const log = logger('dnd');
|
||||||
const { activeData, overData } = action.payload;
|
const { activeData, overData } = action.payload;
|
||||||
if (!isValidDrop(overData, activeData)) {
|
if (!isValidDrop(overData, activeData)) {
|
||||||
return;
|
return;
|
||||||
@ -38,22 +46,80 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
} else if (activeData.payloadType === 'GALLERY_SELECTION') {
|
} else if (activeData.payloadType === 'GALLERY_SELECTION') {
|
||||||
log.debug({ activeData, overData }, `Images (${getState().gallery.selection.length}) dropped`);
|
log.debug({ activeData, overData }, `Images (${getState().gallery.selection.length}) dropped`);
|
||||||
} else if (activeData.payloadType === 'NODE_FIELD') {
|
} else if (activeData.payloadType === 'NODE_FIELD') {
|
||||||
log.debug({ activeData, overData }, 'Node field dropped');
|
log.debug({ activeData: parseify(activeData), overData: parseify(overData) }, 'Node field dropped');
|
||||||
} else {
|
} else {
|
||||||
log.debug({ activeData, overData }, `Unknown payload dropped`);
|
log.debug({ activeData, overData }, `Unknown payload dropped`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image dropped on current image
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
overData.actionType === 'SET_CURRENT_IMAGE' &&
|
||||||
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
|
activeData.payload.imageDTO
|
||||||
|
) {
|
||||||
|
dispatch(imageSelected(activeData.payload.imageDTO));
|
||||||
|
dispatch(isImageViewerOpenChanged(true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image dropped on ControlNet
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
overData.actionType === 'SET_CONTROL_ADAPTER_IMAGE' &&
|
||||||
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
|
activeData.payload.imageDTO
|
||||||
|
) {
|
||||||
|
const { id } = overData.context;
|
||||||
|
dispatch(
|
||||||
|
controlAdapterImageChanged({
|
||||||
|
id,
|
||||||
|
controlImage: activeData.payload.imageDTO.image_name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
controlAdapterIsEnabledChanged({
|
||||||
|
id,
|
||||||
|
isEnabled: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
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
|
* Image dropped on IP Adapter Layer
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
overData.actionType === 'SET_IPA_IMAGE' &&
|
overData.actionType === 'SET_IPA_LAYER_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
const { id } = overData.context;
|
const { layerId } = overData.context;
|
||||||
dispatch(
|
dispatch(
|
||||||
ipaImageChanged({ entityIdentifier: { id, type: 'ip_adapter' }, imageDTO: activeData.payload.imageDTO })
|
ipaLayerImageChanged({
|
||||||
|
layerId,
|
||||||
|
imageDTO: activeData.payload.imageDTO,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -62,14 +128,14 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
* Image dropped on RG Layer IP Adapter
|
* Image dropped on RG Layer IP Adapter
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
overData.actionType === 'SET_RG_IP_ADAPTER_IMAGE' &&
|
overData.actionType === 'SET_RG_LAYER_IP_ADAPTER_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
const { id, ipAdapterId } = overData.context;
|
const { layerId, ipAdapterId } = overData.context;
|
||||||
dispatch(
|
dispatch(
|
||||||
rgIPAdapterImageChanged({
|
rgLayerIPAdapterImageChanged({
|
||||||
entityIdentifier: { id, type: 'regional_guidance' },
|
layerId,
|
||||||
ipAdapterId,
|
ipAdapterId,
|
||||||
imageDTO: activeData.payload.imageDTO,
|
imageDTO: activeData.payload.imageDTO,
|
||||||
})
|
})
|
||||||
@ -78,38 +144,32 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image dropped on Raster layer
|
* Image dropped on II Layer Image
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
overData.actionType === 'ADD_RASTER_LAYER_FROM_IMAGE' &&
|
overData.actionType === 'SET_II_LAYER_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
|
const { layerId } = overData.context;
|
||||||
const { x, y } = selectCanvasSlice(getState()).bbox.rect;
|
dispatch(
|
||||||
const overrides: Partial<CanvasRasterLayerState> = {
|
iiLayerImageChanged({
|
||||||
objects: [imageObject],
|
layerId,
|
||||||
position: { x, y },
|
imageDTO: activeData.payload.imageDTO,
|
||||||
};
|
})
|
||||||
dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image dropped on Raster layer
|
* Image dropped on Canvas
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
overData.actionType === 'ADD_CONTROL_LAYER_FROM_IMAGE' &&
|
overData.actionType === 'SET_CANVAS_INITIAL_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
|
dispatch(setInitialCanvasImage(activeData.payload.imageDTO, selectOptimalDimension(getState())));
|
||||||
const { x, y } = selectCanvasSlice(getState()).bbox.rect;
|
|
||||||
const overrides: Partial<CanvasControlLayerState> = {
|
|
||||||
objects: [imageObject],
|
|
||||||
position: { x, y },
|
|
||||||
};
|
|
||||||
dispatch(controlLayerAdded({ overrides, isSelected: true }));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ import { logger } from 'app/logging/logger';
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
const log = logger('gallery');
|
|
||||||
|
|
||||||
export const addImageRemovedFromBoardFulfilledListener = (startAppListening: AppStartListening) => {
|
export const addImageRemovedFromBoardFulfilledListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.removeImageFromBoard.matchFulfilled,
|
matcher: imagesApi.endpoints.removeImageFromBoard.matchFulfilled,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
|
const log = logger('images');
|
||||||
const imageDTO = action.meta.arg.originalArgs;
|
const imageDTO = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
log.debug({ imageDTO }, 'Image removed from board');
|
log.debug({ imageDTO }, 'Image removed from board');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -16,7 +16,9 @@ export const addImageRemovedFromBoardFulfilledListener = (startAppListening: App
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.removeImageFromBoard.matchRejected,
|
matcher: imagesApi.endpoints.removeImageFromBoard.matchRejected,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
|
const log = logger('images');
|
||||||
const imageDTO = action.meta.arg.originalArgs;
|
const imageDTO = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
log.debug({ imageDTO }, 'Problem removing image from board');
|
log.debug({ imageDTO }, 'Problem removing image from board');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -6,17 +6,16 @@ import { imagesToDeleteSelected, isModalOpenChanged } from 'features/deleteImage
|
|||||||
export const addImageToDeleteSelectedListener = (startAppListening: AppStartListening) => {
|
export const addImageToDeleteSelectedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: imagesToDeleteSelected,
|
actionCreator: imagesToDeleteSelected,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const imageDTOs = action.payload;
|
const imageDTOs = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { shouldConfirmOnDelete } = state.system;
|
const { shouldConfirmOnDelete } = state.system;
|
||||||
const imagesUsage = selectImageUsage(getState());
|
const imagesUsage = selectImageUsage(getState());
|
||||||
|
|
||||||
const isImageInUse =
|
const isImageInUse =
|
||||||
imagesUsage.some((i) => i.isLayerImage) ||
|
imagesUsage.some((i) => i.isCanvasImage) ||
|
||||||
imagesUsage.some((i) => i.isControlAdapterImage) ||
|
imagesUsage.some((i) => i.isControlImage) ||
|
||||||
imagesUsage.some((i) => i.isIPAdapterImage) ||
|
imagesUsage.some((i) => i.isNodesImage);
|
||||||
imagesUsage.some((i) => i.isLayerImage);
|
|
||||||
|
|
||||||
if (shouldConfirmOnDelete || isImageInUse) {
|
if (shouldConfirmOnDelete || isImageInUse) {
|
||||||
dispatch(isModalOpenChanged(true));
|
dispatch(isModalOpenChanged(true));
|
||||||
|
@ -1,8 +1,19 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
|
import {
|
||||||
|
controlAdapterImageChanged,
|
||||||
|
controlAdapterIsEnabledChanged,
|
||||||
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
|
import {
|
||||||
|
caLayerImageChanged,
|
||||||
|
iiLayerImageChanged,
|
||||||
|
ipaLayerImageChanged,
|
||||||
|
rgLayerIPAdapterImageChanged,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
@ -10,12 +21,11 @@ import { omit } from 'lodash-es';
|
|||||||
import { boardsApi } from 'services/api/endpoints/boards';
|
import { boardsApi } from 'services/api/endpoints/boards';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
const log = logger('gallery');
|
|
||||||
|
|
||||||
export const addImageUploadedFulfilledListener = (startAppListening: AppStartListening) => {
|
export const addImageUploadedFulfilledListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.uploadImage.matchFulfilled,
|
matcher: imagesApi.endpoints.uploadImage.matchFulfilled,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
|
const log = logger('images');
|
||||||
const imageDTO = action.payload;
|
const imageDTO = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { autoAddBoardId } = state.gallery;
|
const { autoAddBoardId } = state.gallery;
|
||||||
@ -71,6 +81,15 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') {
|
||||||
|
dispatch(setInitialCanvasImage(imageDTO, selectOptimalDimension(state)));
|
||||||
|
toast({
|
||||||
|
...DEFAULT_UPLOADED_TOAST,
|
||||||
|
description: t('toast.setAsCanvasInitialImage'),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_UPSCALE_INITIAL_IMAGE') {
|
if (postUploadAction?.type === 'SET_UPSCALE_INITIAL_IMAGE') {
|
||||||
dispatch(upscaleInitialImageChanged(imageDTO));
|
dispatch(upscaleInitialImageChanged(imageDTO));
|
||||||
toast({
|
toast({
|
||||||
@ -80,33 +99,70 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (postUploadAction?.type === 'SET_CA_IMAGE') {
|
if (postUploadAction?.type === 'SET_CONTROL_ADAPTER_IMAGE') {
|
||||||
// const { id } = postUploadAction;
|
|
||||||
// dispatch(caImageChanged({ id, imageDTO }));
|
|
||||||
// toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_IPA_IMAGE') {
|
|
||||||
const { id } = postUploadAction;
|
const { id } = postUploadAction;
|
||||||
dispatch(ipaImageChanged({ entityIdentifier: { id, type: 'ip_adapter' }, imageDTO }));
|
dispatch(
|
||||||
toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
|
controlAdapterIsEnabledChanged({
|
||||||
|
id,
|
||||||
|
isEnabled: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
controlAdapterImageChanged({
|
||||||
|
id,
|
||||||
|
controlImage: imageDTO.image_name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
toast({
|
||||||
|
...DEFAULT_UPLOADED_TOAST,
|
||||||
|
description: t('toast.setControlImage'),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_RG_IP_ADAPTER_IMAGE') {
|
if (postUploadAction?.type === 'SET_CA_LAYER_IMAGE') {
|
||||||
const { id, ipAdapterId } = postUploadAction;
|
const { layerId } = postUploadAction;
|
||||||
dispatch(
|
dispatch(caLayerImageChanged({ layerId, imageDTO }));
|
||||||
rgIPAdapterImageChanged({ entityIdentifier: { id, type: 'regional_guidance' }, ipAdapterId, imageDTO })
|
toast({
|
||||||
);
|
...DEFAULT_UPLOADED_TOAST,
|
||||||
toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
|
description: t('toast.setControlImage'),
|
||||||
return;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postUploadAction?.type === 'SET_IPA_LAYER_IMAGE') {
|
||||||
|
const { layerId } = postUploadAction;
|
||||||
|
dispatch(ipaLayerImageChanged({ layerId, imageDTO }));
|
||||||
|
toast({
|
||||||
|
...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 }));
|
||||||
|
toast({
|
||||||
|
...DEFAULT_UPLOADED_TOAST,
|
||||||
|
description: t('toast.setControlImage'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postUploadAction?.type === 'SET_II_LAYER_IMAGE') {
|
||||||
|
const { layerId } = postUploadAction;
|
||||||
|
dispatch(iiLayerImageChanged({ layerId, imageDTO }));
|
||||||
|
toast({
|
||||||
|
...DEFAULT_UPLOADED_TOAST,
|
||||||
|
description: t('toast.setControlImage'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
||||||
const { nodeId, fieldName } = postUploadAction;
|
const { nodeId, fieldName } = postUploadAction;
|
||||||
dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO }));
|
dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO }));
|
||||||
toast({ ...DEFAULT_UPLOADED_TOAST, description: `${t('toast.setNodeField')} ${fieldName}` });
|
toast({
|
||||||
|
...DEFAULT_UPLOADED_TOAST,
|
||||||
|
description: `${t('toast.setNodeField')} ${fieldName}`,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -115,6 +171,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.uploadImage.matchRejected,
|
matcher: imagesApi.endpoints.uploadImage.matchRejected,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
|
const log = logger('images');
|
||||||
const sanitizedData = {
|
const sanitizedData = {
|
||||||
arg: {
|
arg: {
|
||||||
...omit(action.meta.arg.originalArgs, ['file', 'postUploadAction']),
|
...omit(action.meta.arg.originalArgs, ['file', 'postUploadAction']),
|
||||||
|
@ -6,7 +6,7 @@ import type { ImageDTO } from 'services/api/types';
|
|||||||
export const addImagesStarredListener = (startAppListening: AppStartListening) => {
|
export const addImagesStarredListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.starImages.matchFulfilled,
|
matcher: imagesApi.endpoints.starImages.matchFulfilled,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const { updated_image_names: starredImages } = action.payload;
|
const { updated_image_names: starredImages } = action.payload;
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
@ -6,7 +6,7 @@ import type { ImageDTO } from 'services/api/types';
|
|||||||
export const addImagesUnstarredListener = (startAppListening: AppStartListening) => {
|
export const addImagesUnstarredListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.unstarImages.matchFulfilled,
|
matcher: imagesApi.endpoints.unstarImages.matchFulfilled,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const { updated_image_names: unstarredImages } = action.payload;
|
const { updated_image_names: unstarredImages } = action.payload;
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
|
import {
|
||||||
import { modelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice';
|
controlAdapterIsEnabledChanged,
|
||||||
|
selectControlAdapterAll,
|
||||||
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
|
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||||
import { modelSelected } from 'features/parameters/store/actions';
|
import { modelSelected } from 'features/parameters/store/actions';
|
||||||
|
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
||||||
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
import { forEach } from 'lodash-es';
|
||||||
const log = logger('models');
|
|
||||||
|
|
||||||
export const addModelSelectedListener = (startAppListening: AppStartListening) => {
|
export const addModelSelectedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: modelSelected,
|
actionCreator: modelSelected,
|
||||||
effect: (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const log = logger('models');
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const result = zParameterModel.safeParse(action.payload);
|
const result = zParameterModel.safeParse(action.payload);
|
||||||
|
|
||||||
@ -24,36 +29,34 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
|||||||
const newModel = result.data;
|
const newModel = result.data;
|
||||||
|
|
||||||
const newBaseModel = newModel.base;
|
const newBaseModel = newModel.base;
|
||||||
const didBaseModelChange = state.params.model?.base !== newBaseModel;
|
const didBaseModelChange = state.generation.model?.base !== newBaseModel;
|
||||||
|
|
||||||
if (didBaseModelChange) {
|
if (didBaseModelChange) {
|
||||||
// we may need to reset some incompatible submodels
|
// we may need to reset some incompatible submodels
|
||||||
let modelsCleared = 0;
|
let modelsCleared = 0;
|
||||||
|
|
||||||
// handle incompatible loras
|
// handle incompatible loras
|
||||||
state.loras.loras.forEach((lora) => {
|
forEach(state.lora.loras, (lora, id) => {
|
||||||
if (lora.model.base !== newBaseModel) {
|
if (lora.model.base !== newBaseModel) {
|
||||||
dispatch(loraDeleted({ id: lora.id }));
|
dispatch(loraRemoved(id));
|
||||||
modelsCleared += 1;
|
modelsCleared += 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle incompatible vae
|
// handle incompatible vae
|
||||||
const { vae } = state.params;
|
const { vae } = state.generation;
|
||||||
if (vae && vae.base !== newBaseModel) {
|
if (vae && vae.base !== newBaseModel) {
|
||||||
dispatch(vaeSelected(null));
|
dispatch(vaeSelected(null));
|
||||||
modelsCleared += 1;
|
modelsCleared += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle incompatible controlnets
|
// handle incompatible controlnets
|
||||||
// state.canvas.present.controlAdapters.entities.forEach((ca) => {
|
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
|
||||||
// if (ca.model?.base !== newBaseModel) {
|
if (ca.model?.base !== newBaseModel) {
|
||||||
// modelsCleared += 1;
|
dispatch(controlAdapterIsEnabledChanged({ id: ca.id, isEnabled: false }));
|
||||||
// if (ca.isEnabled) {
|
modelsCleared += 1;
|
||||||
// dispatch(entityIsEnabledToggled({ entityIdentifier: { id: ca.id, type: 'control_adapter' } }));
|
}
|
||||||
// }
|
});
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
if (modelsCleared > 0) {
|
if (modelsCleared > 0) {
|
||||||
toast({
|
toast({
|
||||||
@ -67,7 +70,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(modelChanged({ model: newModel, previousModel: state.params.model }));
|
dispatch(modelChanged(newModel, state.generation.model));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,42 +1,36 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import type { AppDispatch, RootState } from 'app/store/store';
|
import type { AppDispatch, RootState } from 'app/store/store';
|
||||||
import type { SerializableObject } from 'common/types';
|
import type { JSONObject } from 'common/types';
|
||||||
import {
|
import {
|
||||||
bboxHeightChanged,
|
controlAdapterModelCleared,
|
||||||
bboxWidthChanged,
|
selectControlAdapterAll,
|
||||||
controlLayerModelChanged,
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
ipaModelChanged,
|
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
rgIPAdapterModelChanged,
|
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||||
} from 'features/controlLayers/store/canvasSlice';
|
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||||
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
|
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
||||||
import { modelChanged, refinerModelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice';
|
|
||||||
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
|
||||||
import { getEntityIdentifier } from 'features/controlLayers/store/types';
|
|
||||||
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
|
||||||
import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
|
import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
|
||||||
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
|
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
|
||||||
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||||
|
import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice';
|
||||||
|
import { forEach } from 'lodash-es';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
||||||
import type { AnyModelConfig } from 'services/api/types';
|
import type { AnyModelConfig } from 'services/api/types';
|
||||||
import {
|
import {
|
||||||
isControlNetOrT2IAdapterModelConfig,
|
|
||||||
isIPAdapterModelConfig,
|
|
||||||
isLoRAModelConfig,
|
|
||||||
isNonRefinerMainModelConfig,
|
isNonRefinerMainModelConfig,
|
||||||
isRefinerMainModelModelConfig,
|
isRefinerMainModelModelConfig,
|
||||||
isSpandrelImageToImageModelConfig,
|
isSpandrelImageToImageModelConfig,
|
||||||
isVAEModelConfig,
|
isVAEModelConfig,
|
||||||
} from 'services/api/types';
|
} from 'services/api/types';
|
||||||
|
|
||||||
const log = logger('models');
|
|
||||||
|
|
||||||
export const addModelsLoadedListener = (startAppListening: AppStartListening) => {
|
export const addModelsLoadedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
predicate: modelsApi.endpoints.getModelConfigs.matchFulfilled,
|
predicate: modelsApi.endpoints.getModelConfigs.matchFulfilled,
|
||||||
effect: (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
||||||
|
const log = logger('models');
|
||||||
log.info({ models: action.payload.entities }, `Models loaded (${action.payload.ids.length})`);
|
log.info({ models: action.payload.entities }, `Models loaded (${action.payload.ids.length})`);
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
@ -49,7 +43,6 @@ export const addModelsLoadedListener = (startAppListening: AppStartListening) =>
|
|||||||
handleLoRAModels(models, state, dispatch, log);
|
handleLoRAModels(models, state, dispatch, log);
|
||||||
handleControlAdapterModels(models, state, dispatch, log);
|
handleControlAdapterModels(models, state, dispatch, log);
|
||||||
handleSpandrelImageToImageModels(models, state, dispatch, log);
|
handleSpandrelImageToImageModels(models, state, dispatch, log);
|
||||||
handleIPAdapterModels(models, state, dispatch, log);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -58,15 +51,15 @@ type ModelHandler = (
|
|||||||
models: AnyModelConfig[],
|
models: AnyModelConfig[],
|
||||||
state: RootState,
|
state: RootState,
|
||||||
dispatch: AppDispatch,
|
dispatch: AppDispatch,
|
||||||
log: Logger<SerializableObject>
|
log: Logger<JSONObject>
|
||||||
) => undefined;
|
) => undefined;
|
||||||
|
|
||||||
const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
||||||
const currentModel = state.params.model;
|
const currentModel = state.generation.model;
|
||||||
const mainModels = models.filter(isNonRefinerMainModelConfig);
|
const mainModels = models.filter(isNonRefinerMainModelConfig);
|
||||||
if (mainModels.length === 0) {
|
if (mainModels.length === 0) {
|
||||||
// No models loaded at all
|
// No models loaded at all
|
||||||
dispatch(modelChanged({ model: null }));
|
dispatch(modelChanged(null));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,16 +74,25 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
|||||||
if (defaultModelInList) {
|
if (defaultModelInList) {
|
||||||
const result = zParameterModel.safeParse(defaultModelInList);
|
const result = zParameterModel.safeParse(defaultModelInList);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel }));
|
dispatch(modelChanged(defaultModelInList, currentModel));
|
||||||
const { bbox } = selectCanvasSlice(state);
|
|
||||||
const optimalDimension = getOptimalDimension(defaultModelInList);
|
const optimalDimension = getOptimalDimension(defaultModelInList);
|
||||||
if (getIsSizeOptimal(bbox.rect.width, bbox.rect.height, optimalDimension)) {
|
if (
|
||||||
|
getIsSizeOptimal(
|
||||||
|
state.controlLayers.present.size.width,
|
||||||
|
state.controlLayers.present.size.height,
|
||||||
|
optimalDimension
|
||||||
|
)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { width, height } = calculateNewSize(bbox.aspectRatio.value, optimalDimension * optimalDimension);
|
const { width, height } = calculateNewSize(
|
||||||
|
state.controlLayers.present.size.aspectRatio.value,
|
||||||
|
optimalDimension * optimalDimension
|
||||||
|
);
|
||||||
|
|
||||||
dispatch(bboxWidthChanged({ width }));
|
dispatch(widthChanged({ width }));
|
||||||
dispatch(bboxHeightChanged({ height }));
|
dispatch(heightChanged({ height }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,11 +104,11 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(modelChanged({ model: result.data, previousModel: currentModel }));
|
dispatch(modelChanged(result.data, currentModel));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefinerModels: ModelHandler = (models, state, dispatch, _log) => {
|
const handleRefinerModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
const currentRefinerModel = state.params.refinerModel;
|
const currentRefinerModel = state.sdxl.refinerModel;
|
||||||
const refinerModels = models.filter(isRefinerMainModelModelConfig);
|
const refinerModels = models.filter(isRefinerMainModelModelConfig);
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
// No models loaded at all
|
// No models loaded at all
|
||||||
@ -125,7 +127,7 @@ const handleRefinerModels: ModelHandler = (models, state, dispatch, _log) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleVAEModels: ModelHandler = (models, state, dispatch, log) => {
|
const handleVAEModels: ModelHandler = (models, state, dispatch, log) => {
|
||||||
const currentVae = state.params.vae;
|
const currentVae = state.generation.vae;
|
||||||
|
|
||||||
if (currentVae === null) {
|
if (currentVae === null) {
|
||||||
// null is a valid VAE! it means "use the default with the main model"
|
// null is a valid VAE! it means "use the default with the main model"
|
||||||
@ -158,47 +160,28 @@ const handleVAEModels: ModelHandler = (models, state, dispatch, log) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {
|
const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
const loraModels = models.filter(isLoRAModelConfig);
|
const loras = state.lora.loras;
|
||||||
state.loras.loras.forEach((lora) => {
|
|
||||||
const isLoRAAvailable = loraModels.some((m) => m.key === lora.model.key);
|
forEach(loras, (lora, id) => {
|
||||||
|
const isLoRAAvailable = models.some((m) => m.key === lora.model.key);
|
||||||
|
|
||||||
if (isLoRAAvailable) {
|
if (isLoRAAvailable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(loraDeleted({ id: lora.id }));
|
|
||||||
|
dispatch(loraRemoved(id));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
const caModels = models.filter(isControlNetOrT2IAdapterModelConfig);
|
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
|
||||||
selectCanvasSlice(state).controlLayers.entities.forEach((entity) => {
|
const isModelAvailable = models.some((m) => m.key === ca.model?.key);
|
||||||
const isModelAvailable = caModels.some((m) => m.key === entity.controlAdapter.model?.key);
|
|
||||||
if (isModelAvailable) {
|
if (isModelAvailable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(controlLayerModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null }));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
dispatch(controlAdapterModelCleared({ id: ca.id }));
|
||||||
const ipaModels = models.filter(isIPAdapterModelConfig);
|
|
||||||
selectCanvasSlice(state).ipAdapters.entities.forEach((entity) => {
|
|
||||||
const isModelAvailable = ipaModels.some((m) => m.key === entity.ipAdapter.model?.key);
|
|
||||||
if (isModelAvailable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(ipaModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null }));
|
|
||||||
});
|
|
||||||
|
|
||||||
selectCanvasSlice(state).regions.entities.forEach((entity) => {
|
|
||||||
entity.ipAdapters.forEach(({ id: ipAdapterId, model }) => {
|
|
||||||
const isModelAvailable = ipaModels.some((m) => m.key === model?.key);
|
|
||||||
if (isModelAvailable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(
|
|
||||||
rgIPAdapterModelChanged({ entityIdentifier: getEntityIdentifier(entity), ipAdapterId, modelConfig: null })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { isAnyOf } from '@reduxjs/toolkit';
|
import { isAnyOf } from '@reduxjs/toolkit';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { positivePromptChanged } from 'features/controlLayers/store/paramsSlice';
|
import { positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import {
|
import {
|
||||||
combinatorialToggled,
|
combinatorialToggled,
|
||||||
isErrorChanged,
|
isErrorChanged,
|
||||||
@ -15,7 +15,7 @@ import { getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilder
|
|||||||
import { activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
|
import { activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||||
import { stylePresetsApi } from 'services/api/endpoints/stylePresets';
|
import { stylePresetsApi } from 'services/api/endpoints/stylePresets';
|
||||||
import { utilitiesApi } from 'services/api/endpoints/utilities';
|
import { utilitiesApi } from 'services/api/endpoints/utilities';
|
||||||
import { socketConnected } from 'services/events/setEventListeners';
|
import { socketConnected } from 'services/events/actions';
|
||||||
|
|
||||||
const matcher = isAnyOf(
|
const matcher = isAnyOf(
|
||||||
positivePromptChanged,
|
positivePromptChanged,
|
||||||
@ -24,6 +24,8 @@ const matcher = isAnyOf(
|
|||||||
maxPromptsReset,
|
maxPromptsReset,
|
||||||
socketConnected,
|
socketConnected,
|
||||||
activeStylePresetIdChanged,
|
activeStylePresetIdChanged,
|
||||||
|
stylePresetsApi.endpoints.deleteStylePreset.matchFulfilled,
|
||||||
|
stylePresetsApi.endpoints.updateStylePreset.matchFulfilled,
|
||||||
stylePresetsApi.endpoints.listStylePresets.matchFulfilled
|
stylePresetsApi.endpoints.listStylePresets.matchFulfilled
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
|
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { setDefaultSettings } from 'features/parameters/store/actions';
|
||||||
import {
|
import {
|
||||||
setCfgRescaleMultiplier,
|
setCfgRescaleMultiplier,
|
||||||
setCfgScale,
|
setCfgScale,
|
||||||
@ -7,8 +8,7 @@ import {
|
|||||||
setSteps,
|
setSteps,
|
||||||
vaePrecisionChanged,
|
vaePrecisionChanged,
|
||||||
vaeSelected,
|
vaeSelected,
|
||||||
} from 'features/controlLayers/store/paramsSlice';
|
} from 'features/parameters/store/generationSlice';
|
||||||
import { setDefaultSettings } from 'features/parameters/store/actions';
|
|
||||||
import {
|
import {
|
||||||
isParameterCFGRescaleMultiplier,
|
isParameterCFGRescaleMultiplier,
|
||||||
isParameterCFGScale,
|
isParameterCFGScale,
|
||||||
@ -30,7 +30,7 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
|
|||||||
effect: async (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
const currentModel = state.params.model;
|
const currentModel = state.generation.model;
|
||||||
|
|
||||||
if (!currentModel) {
|
if (!currentModel) {
|
||||||
return;
|
return;
|
||||||
@ -98,13 +98,13 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
|
|||||||
const setSizeOptions = { updateAspectRatio: true, clamp: true };
|
const setSizeOptions = { updateAspectRatio: true, clamp: true };
|
||||||
if (width) {
|
if (width) {
|
||||||
if (isParameterWidth(width)) {
|
if (isParameterWidth(width)) {
|
||||||
dispatch(bboxWidthChanged({ width, ...setSizeOptions }));
|
dispatch(widthChanged({ width, ...setSizeOptions }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (height) {
|
if (height) {
|
||||||
if (isParameterHeight(height)) {
|
if (isParameterHeight(height)) {
|
||||||
dispatch(bboxHeightChanged({ height, ...setSizeOptions }));
|
dispatch(heightChanged({ height, ...setSizeOptions }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ import { atom } from 'nanostores';
|
|||||||
import { api } from 'services/api';
|
import { api } from 'services/api';
|
||||||
import { modelsApi } from 'services/api/endpoints/models';
|
import { modelsApi } from 'services/api/endpoints/models';
|
||||||
import { queueApi, selectQueueStatus } from 'services/api/endpoints/queue';
|
import { queueApi, selectQueueStatus } from 'services/api/endpoints/queue';
|
||||||
import { socketConnected } from 'services/events/setEventListeners';
|
import { socketConnected } from 'services/events/actions';
|
||||||
|
|
||||||
const log = logger('events');
|
const log = logger('socketio');
|
||||||
|
|
||||||
const $isFirstConnection = atom(true);
|
const $isFirstConnection = atom(true);
|
||||||
|
|
@ -0,0 +1,14 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { socketDisconnected } from 'services/events/actions';
|
||||||
|
|
||||||
|
const log = logger('socketio');
|
||||||
|
|
||||||
|
export const addSocketDisconnectedEventListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketDisconnected,
|
||||||
|
effect: () => {
|
||||||
|
log.debug('Disconnected');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { deepClone } from 'common/util/deepClone';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
|
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
||||||
|
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||||
|
import { socketGeneratorProgress } from 'services/events/actions';
|
||||||
|
|
||||||
|
const log = logger('socketio');
|
||||||
|
|
||||||
|
export const addGeneratorProgressEventListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketGeneratorProgress,
|
||||||
|
effect: (action) => {
|
||||||
|
log.trace(parseify(action.payload), `Generator progress`);
|
||||||
|
const { invocation_source_id, step, total_steps, progress_image } = action.payload.data;
|
||||||
|
const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]);
|
||||||
|
if (nes) {
|
||||||
|
nes.status = zNodeStatus.enum.IN_PROGRESS;
|
||||||
|
nes.progress = (step + 1) / total_steps;
|
||||||
|
nes.progressImage = progress_image ?? null;
|
||||||
|
upsertExecutionState(nes.nodeId, nes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,122 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { deepClone } from 'common/util/deepClone';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
|
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
|
||||||
|
import {
|
||||||
|
boardIdSelected,
|
||||||
|
galleryViewChanged,
|
||||||
|
imageSelected,
|
||||||
|
isImageViewerOpenChanged,
|
||||||
|
offsetChanged,
|
||||||
|
} from 'features/gallery/store/gallerySlice';
|
||||||
|
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
||||||
|
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||||
|
import { CANVAS_OUTPUT } from 'features/nodes/util/graph/constants';
|
||||||
|
import { boardsApi } from 'services/api/endpoints/boards';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import { getCategories, getListImagesUrl } from 'services/api/util';
|
||||||
|
import { socketInvocationComplete } from 'services/events/actions';
|
||||||
|
|
||||||
|
// These nodes output an image, but do not actually *save* an image, so we don't want to handle the gallery logic on them
|
||||||
|
const nodeTypeDenylist = ['load_image', 'image'];
|
||||||
|
|
||||||
|
const log = logger('socketio');
|
||||||
|
|
||||||
|
export const addInvocationCompleteEventListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketInvocationComplete,
|
||||||
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const { data } = action.payload;
|
||||||
|
log.debug({ data: parseify(data) }, `Invocation complete (${data.invocation.type})`);
|
||||||
|
|
||||||
|
const { result, invocation_source_id } = data;
|
||||||
|
// This complete event has an associated image output
|
||||||
|
if (data.result.type === 'image_output' && !nodeTypeDenylist.includes(data.invocation.type)) {
|
||||||
|
const { image_name } = data.result.image;
|
||||||
|
const { canvas, gallery } = getState();
|
||||||
|
|
||||||
|
// This populates the `getImageDTO` cache
|
||||||
|
const imageDTORequest = dispatch(
|
||||||
|
imagesApi.endpoints.getImageDTO.initiate(image_name, {
|
||||||
|
forceRefetch: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const imageDTO = await imageDTORequest.unwrap();
|
||||||
|
imageDTORequest.unsubscribe();
|
||||||
|
|
||||||
|
// Add canvas images to the staging area
|
||||||
|
if (canvas.batchIds.includes(data.batch_id) && data.invocation_source_id === CANVAS_OUTPUT) {
|
||||||
|
dispatch(addImageToStagingArea(imageDTO));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imageDTO.is_intermediate) {
|
||||||
|
// update the total images for the board
|
||||||
|
dispatch(
|
||||||
|
boardsApi.util.updateQueryData('getBoardImagesTotal', imageDTO.board_id ?? 'none', (draft) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
draft.total += 1;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
imagesApi.util.invalidateTags([
|
||||||
|
{ type: 'Board', id: imageDTO.board_id ?? 'none' },
|
||||||
|
{
|
||||||
|
type: 'ImageList',
|
||||||
|
id: getListImagesUrl({
|
||||||
|
board_id: imageDTO.board_id ?? 'none',
|
||||||
|
categories: getCategories(imageDTO),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
const { shouldAutoSwitch } = gallery;
|
||||||
|
|
||||||
|
// If auto-switch is enabled, select the new image
|
||||||
|
if (shouldAutoSwitch) {
|
||||||
|
// if auto-add is enabled, switch the gallery view and board if needed as the image comes in
|
||||||
|
if (gallery.galleryView !== 'images') {
|
||||||
|
dispatch(galleryViewChanged('images'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageDTO.board_id && imageDTO.board_id !== gallery.selectedBoardId) {
|
||||||
|
dispatch(
|
||||||
|
boardIdSelected({
|
||||||
|
boardId: imageDTO.board_id,
|
||||||
|
selectedImageName: imageDTO.image_name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(offsetChanged({ offset: 0 }));
|
||||||
|
|
||||||
|
if (!imageDTO.board_id && gallery.selectedBoardId !== 'none') {
|
||||||
|
dispatch(
|
||||||
|
boardIdSelected({
|
||||||
|
boardId: 'none',
|
||||||
|
selectedImageName: imageDTO.image_name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(imageSelected(imageDTO));
|
||||||
|
dispatch(isImageViewerOpenChanged(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]);
|
||||||
|
if (nes) {
|
||||||
|
nes.status = zNodeStatus.enum.COMPLETED;
|
||||||
|
if (nes.progress !== null) {
|
||||||
|
nes.progress = 1;
|
||||||
|
}
|
||||||
|
nes.outputs.push(result);
|
||||||
|
upsertExecutionState(nes.nodeId, nes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,31 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { deepClone } from 'common/util/deepClone';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
|
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
||||||
|
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||||
|
import { socketInvocationError } from 'services/events/actions';
|
||||||
|
|
||||||
|
const log = logger('socketio');
|
||||||
|
|
||||||
|
export const addInvocationErrorEventListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketInvocationError,
|
||||||
|
effect: (action) => {
|
||||||
|
const { invocation_source_id, invocation, error_type, error_message, error_traceback } = action.payload.data;
|
||||||
|
log.error(parseify(action.payload), `Invocation error (${invocation.type})`);
|
||||||
|
const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]);
|
||||||
|
if (nes) {
|
||||||
|
nes.status = zNodeStatus.enum.FAILED;
|
||||||
|
nes.progress = null;
|
||||||
|
nes.progressImage = null;
|
||||||
|
nes.error = {
|
||||||
|
error_type,
|
||||||
|
error_message,
|
||||||
|
error_traceback,
|
||||||
|
};
|
||||||
|
upsertExecutionState(nes.nodeId, nes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { deepClone } from 'common/util/deepClone';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
|
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
||||||
|
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||||
|
import { socketInvocationStarted } from 'services/events/actions';
|
||||||
|
|
||||||
|
const log = logger('socketio');
|
||||||
|
|
||||||
|
export const addInvocationStartedEventListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketInvocationStarted,
|
||||||
|
effect: (action) => {
|
||||||
|
log.debug(parseify(action.payload), `Invocation started (${action.payload.data.invocation.type})`);
|
||||||
|
const { invocation_source_id } = action.payload.data;
|
||||||
|
const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]);
|
||||||
|
if (nes) {
|
||||||
|
nes.status = zNodeStatus.enum.IN_PROGRESS;
|
||||||
|
upsertExecutionState(nes.nodeId, nes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,196 @@
|
|||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { api, LIST_TAG } from 'services/api';
|
||||||
|
import { modelsApi } from 'services/api/endpoints/models';
|
||||||
|
import {
|
||||||
|
socketModelInstallCancelled,
|
||||||
|
socketModelInstallComplete,
|
||||||
|
socketModelInstallDownloadProgress,
|
||||||
|
socketModelInstallDownloadsComplete,
|
||||||
|
socketModelInstallDownloadStarted,
|
||||||
|
socketModelInstallError,
|
||||||
|
socketModelInstallStarted,
|
||||||
|
} from 'services/events/actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A model install has two main stages - downloading and installing. All these events are namespaced under `model_install_`
|
||||||
|
* which is a bit misleading. For example, a `model_install_started` event is actually fired _after_ the model has fully
|
||||||
|
* downloaded and is being "physically" installed.
|
||||||
|
*
|
||||||
|
* Note: the download events are only fired for remote model installs, not local.
|
||||||
|
*
|
||||||
|
* Here's the expected flow:
|
||||||
|
* - API receives install request, model manager preps the install
|
||||||
|
* - `model_install_download_started` fired when the download starts
|
||||||
|
* - `model_install_download_progress` fired continually until the download is complete
|
||||||
|
* - `model_install_download_complete` fired when the download is complete
|
||||||
|
* - `model_install_started` fired when the "physical" installation starts
|
||||||
|
* - `model_install_complete` fired when the installation is complete
|
||||||
|
* - `model_install_cancelled` fired if the installation is cancelled
|
||||||
|
* - `model_install_error` fired if the installation has an error
|
||||||
|
*/
|
||||||
|
|
||||||
|
const selectModelInstalls = modelsApi.endpoints.listModelInstalls.select();
|
||||||
|
|
||||||
|
export const addModelInstallEventListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketModelInstallDownloadStarted,
|
||||||
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const { id } = action.payload.data;
|
||||||
|
const { data } = selectModelInstalls(getState());
|
||||||
|
|
||||||
|
if (!data || !data.find((m) => m.id === id)) {
|
||||||
|
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
||||||
|
const modelImport = draft.find((m) => m.id === id);
|
||||||
|
if (modelImport) {
|
||||||
|
modelImport.status = 'downloading';
|
||||||
|
}
|
||||||
|
return draft;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketModelInstallStarted,
|
||||||
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const { id } = action.payload.data;
|
||||||
|
const { data } = selectModelInstalls(getState());
|
||||||
|
|
||||||
|
if (!data || !data.find((m) => m.id === id)) {
|
||||||
|
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
||||||
|
const modelImport = draft.find((m) => m.id === id);
|
||||||
|
if (modelImport) {
|
||||||
|
modelImport.status = 'running';
|
||||||
|
}
|
||||||
|
return draft;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketModelInstallDownloadProgress,
|
||||||
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const { bytes, total_bytes, id } = action.payload.data;
|
||||||
|
const { data } = selectModelInstalls(getState());
|
||||||
|
|
||||||
|
if (!data || !data.find((m) => m.id === id)) {
|
||||||
|
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
||||||
|
const modelImport = draft.find((m) => m.id === id);
|
||||||
|
if (modelImport) {
|
||||||
|
modelImport.bytes = bytes;
|
||||||
|
modelImport.total_bytes = total_bytes;
|
||||||
|
modelImport.status = 'downloading';
|
||||||
|
}
|
||||||
|
return draft;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketModelInstallComplete,
|
||||||
|
effect: (action, { dispatch, getState }) => {
|
||||||
|
const { id } = action.payload.data;
|
||||||
|
|
||||||
|
const { data } = selectModelInstalls(getState());
|
||||||
|
|
||||||
|
if (!data || !data.find((m) => m.id === id)) {
|
||||||
|
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
||||||
|
const modelImport = draft.find((m) => m.id === id);
|
||||||
|
if (modelImport) {
|
||||||
|
modelImport.status = 'completed';
|
||||||
|
}
|
||||||
|
return draft;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(api.util.invalidateTags([{ type: 'ModelConfig', id: LIST_TAG }]));
|
||||||
|
dispatch(api.util.invalidateTags([{ type: 'ModelScanFolderResults', id: LIST_TAG }]));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketModelInstallError,
|
||||||
|
effect: (action, { dispatch, getState }) => {
|
||||||
|
const { id, error, error_type } = action.payload.data;
|
||||||
|
const { data } = selectModelInstalls(getState());
|
||||||
|
|
||||||
|
if (!data || !data.find((m) => m.id === id)) {
|
||||||
|
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
||||||
|
const modelImport = draft.find((m) => m.id === id);
|
||||||
|
if (modelImport) {
|
||||||
|
modelImport.status = 'error';
|
||||||
|
modelImport.error_reason = error_type;
|
||||||
|
modelImport.error = error;
|
||||||
|
}
|
||||||
|
return draft;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketModelInstallCancelled,
|
||||||
|
effect: (action, { dispatch, getState }) => {
|
||||||
|
const { id } = action.payload.data;
|
||||||
|
const { data } = selectModelInstalls(getState());
|
||||||
|
|
||||||
|
if (!data || !data.find((m) => m.id === id)) {
|
||||||
|
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
||||||
|
const modelImport = draft.find((m) => m.id === id);
|
||||||
|
if (modelImport) {
|
||||||
|
modelImport.status = 'cancelled';
|
||||||
|
}
|
||||||
|
return draft;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketModelInstallDownloadsComplete,
|
||||||
|
effect: (action, { dispatch, getState }) => {
|
||||||
|
const { id } = action.payload.data;
|
||||||
|
const { data } = selectModelInstalls(getState());
|
||||||
|
|
||||||
|
if (!data || !data.find((m) => m.id === id)) {
|
||||||
|
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
||||||
|
const modelImport = draft.find((m) => m.id === id);
|
||||||
|
if (modelImport) {
|
||||||
|
modelImport.status = 'downloads_done';
|
||||||
|
}
|
||||||
|
return draft;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,42 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { socketModelLoadComplete, socketModelLoadStarted } from 'services/events/actions';
|
||||||
|
|
||||||
|
const log = logger('socketio');
|
||||||
|
|
||||||
|
export const addModelLoadEventListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketModelLoadStarted,
|
||||||
|
effect: (action) => {
|
||||||
|
const { config, submodel_type } = action.payload.data;
|
||||||
|
const { name, base, type } = config;
|
||||||
|
|
||||||
|
const extras: string[] = [base, type];
|
||||||
|
|
||||||
|
if (submodel_type) {
|
||||||
|
extras.push(submodel_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = `Model load started: ${name} (${extras.join(', ')})`;
|
||||||
|
|
||||||
|
log.debug(action.payload, message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketModelLoadComplete,
|
||||||
|
effect: (action) => {
|
||||||
|
const { config, submodel_type } = action.payload.data;
|
||||||
|
const { name, base, type } = config;
|
||||||
|
|
||||||
|
const extras: string[] = [base, type];
|
||||||
|
if (submodel_type) {
|
||||||
|
extras.push(submodel_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = `Model load complete: ${name} (${extras.join(', ')})`;
|
||||||
|
|
||||||
|
log.debug(action.payload, message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,114 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { deepClone } from 'common/util/deepClone';
|
||||||
|
import { $nodeExecutionStates } from 'features/nodes/hooks/useExecutionState';
|
||||||
|
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||||
|
import ErrorToastDescription, { getTitleFromErrorType } from 'features/toast/ErrorToastDescription';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { forEach } from 'lodash-es';
|
||||||
|
import { queueApi, queueItemsAdapter } from 'services/api/endpoints/queue';
|
||||||
|
import { socketQueueItemStatusChanged } from 'services/events/actions';
|
||||||
|
|
||||||
|
const log = logger('socketio');
|
||||||
|
|
||||||
|
export const addSocketQueueItemStatusChangedEventListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: socketQueueItemStatusChanged,
|
||||||
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
// we've got new status for the queue item, batch and queue
|
||||||
|
const {
|
||||||
|
item_id,
|
||||||
|
session_id,
|
||||||
|
status,
|
||||||
|
started_at,
|
||||||
|
updated_at,
|
||||||
|
completed_at,
|
||||||
|
batch_status,
|
||||||
|
queue_status,
|
||||||
|
error_type,
|
||||||
|
error_message,
|
||||||
|
error_traceback,
|
||||||
|
} = action.payload.data;
|
||||||
|
|
||||||
|
log.debug(action.payload, `Queue item ${item_id} status updated: ${status}`);
|
||||||
|
|
||||||
|
// Update this specific queue item in the list of queue items (this is the queue item DTO, without the session)
|
||||||
|
dispatch(
|
||||||
|
queueApi.util.updateQueryData('listQueueItems', undefined, (draft) => {
|
||||||
|
queueItemsAdapter.updateOne(draft, {
|
||||||
|
id: String(item_id),
|
||||||
|
changes: {
|
||||||
|
status,
|
||||||
|
started_at,
|
||||||
|
updated_at: updated_at ?? undefined,
|
||||||
|
completed_at: completed_at ?? undefined,
|
||||||
|
error_type,
|
||||||
|
error_message,
|
||||||
|
error_traceback,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the queue status (we do not get the processor status here)
|
||||||
|
dispatch(
|
||||||
|
queueApi.util.updateQueryData('getQueueStatus', undefined, (draft) => {
|
||||||
|
if (!draft) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.assign(draft.queue, queue_status);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the batch status
|
||||||
|
dispatch(
|
||||||
|
queueApi.util.updateQueryData('getBatchStatus', { batch_id: batch_status.batch_id }, () => batch_status)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Invalidate caches for things we cannot update
|
||||||
|
// TODO: technically, we could possibly update the current session queue item, but feels safer to just request it again
|
||||||
|
dispatch(
|
||||||
|
queueApi.util.invalidateTags([
|
||||||
|
'CurrentSessionQueueItem',
|
||||||
|
'NextSessionQueueItem',
|
||||||
|
'InvocationCacheStatus',
|
||||||
|
{ type: 'SessionQueueItem', id: item_id },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status === 'in_progress') {
|
||||||
|
forEach($nodeExecutionStates.get(), (nes) => {
|
||||||
|
if (!nes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const clone = deepClone(nes);
|
||||||
|
clone.status = zNodeStatus.enum.PENDING;
|
||||||
|
clone.error = null;
|
||||||
|
clone.progress = null;
|
||||||
|
clone.progressImage = null;
|
||||||
|
clone.outputs = [];
|
||||||
|
$nodeExecutionStates.setKey(clone.nodeId, clone);
|
||||||
|
});
|
||||||
|
} else if (status === 'failed' && error_type) {
|
||||||
|
const isLocal = getState().config.isLocal ?? true;
|
||||||
|
const sessionId = session_id;
|
||||||
|
|
||||||
|
toast({
|
||||||
|
id: `INVOCATION_ERROR_${error_type}`,
|
||||||
|
title: getTitleFromErrorType(error_type),
|
||||||
|
status: 'error',
|
||||||
|
duration: null,
|
||||||
|
updateDescription: isLocal,
|
||||||
|
description: (
|
||||||
|
<ErrorToastDescription
|
||||||
|
errorType={error_type}
|
||||||
|
errorMessage={error_message}
|
||||||
|
sessionId={sessionId}
|
||||||
|
isLocal={isLocal}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,43 @@
|
|||||||
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { stagingAreaImageSaved } from 'features/canvas/store/actions';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
export const addStagingAreaImageSavedListener = (startAppListening: AppStartListening) => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: stagingAreaImageSaved,
|
||||||
|
effect: async (action, { dispatch, getState }) => {
|
||||||
|
const { imageDTO } = action.payload;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newImageDTO = await dispatch(
|
||||||
|
imagesApi.endpoints.changeImageIsIntermediate.initiate({
|
||||||
|
imageDTO,
|
||||||
|
is_intermediate: false,
|
||||||
|
})
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// we may need to add it to the autoadd board
|
||||||
|
const { autoAddBoardId } = getState().gallery;
|
||||||
|
|
||||||
|
if (autoAddBoardId && autoAddBoardId !== 'none') {
|
||||||
|
await dispatch(
|
||||||
|
imagesApi.endpoints.addImageToBoard.initiate({
|
||||||
|
imageDTO: newImageDTO,
|
||||||
|
board_id: autoAddBoardId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toast({ id: 'IMAGE_SAVED', title: t('toast.imageSaved'), status: 'success' });
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
id: 'IMAGE_SAVE_FAILED',
|
||||||
|
title: t('toast.imageSavingFailed'),
|
||||||
|
description: (error as Error)?.message,
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -2,20 +2,18 @@ import { logger } from 'app/logging/logger';
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { updateAllNodesRequested } from 'features/nodes/store/actions';
|
import { updateAllNodesRequested } from 'features/nodes/store/actions';
|
||||||
import { $templates, nodesChanged } from 'features/nodes/store/nodesSlice';
|
import { $templates, nodesChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { selectNodes } from 'features/nodes/store/selectors';
|
|
||||||
import { NodeUpdateError } from 'features/nodes/types/error';
|
import { NodeUpdateError } from 'features/nodes/types/error';
|
||||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||||
import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate';
|
import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
|
||||||
const log = logger('workflows');
|
|
||||||
|
|
||||||
export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartListening) => {
|
export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: updateAllNodesRequested,
|
actionCreator: updateAllNodesRequested,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const nodes = selectNodes(getState());
|
const log = logger('nodes');
|
||||||
|
const { nodes } = getState().nodes.present;
|
||||||
const templates = $templates.get();
|
const templates = $templates.get();
|
||||||
|
|
||||||
let unableToUpdateCount = 0;
|
let unableToUpdateCount = 0;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { $nodeExecutionStates } from 'features/nodes/hooks/useExecutionState';
|
import { parseify } from 'common/util/serialize';
|
||||||
import { workflowLoaded, workflowLoadRequested } from 'features/nodes/store/actions';
|
import { workflowLoaded, workflowLoadRequested } from 'features/nodes/store/actions';
|
||||||
import { $templates } from 'features/nodes/store/nodesSlice';
|
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||||
import { $needsFit } from 'features/nodes/store/reactFlowInstance';
|
import { $needsFit } from 'features/nodes/store/reactFlowInstance';
|
||||||
@ -10,14 +10,11 @@ import { graphToWorkflow } from 'features/nodes/util/workflow/graphToWorkflow';
|
|||||||
import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow';
|
import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { serializeError } from 'serialize-error';
|
|
||||||
import { checkBoardAccess, checkImageAccess, checkModelAccess } from 'services/api/hooks/accessChecks';
|
import { checkBoardAccess, checkImageAccess, checkModelAccess } from 'services/api/hooks/accessChecks';
|
||||||
import type { GraphAndWorkflowResponse, NonNullableGraph } from 'services/api/types';
|
import type { GraphAndWorkflowResponse, NonNullableGraph } from 'services/api/types';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { fromZodError } from 'zod-validation-error';
|
import { fromZodError } from 'zod-validation-error';
|
||||||
|
|
||||||
const log = logger('workflows');
|
|
||||||
|
|
||||||
const getWorkflow = async (data: GraphAndWorkflowResponse, templates: Templates) => {
|
const getWorkflow = async (data: GraphAndWorkflowResponse, templates: Templates) => {
|
||||||
if (data.workflow) {
|
if (data.workflow) {
|
||||||
// Prefer to load the workflow if it's available - it has more information
|
// Prefer to load the workflow if it's available - it has more information
|
||||||
@ -37,6 +34,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: workflowLoadRequested,
|
actionCreator: workflowLoadRequested,
|
||||||
effect: async (action, { dispatch }) => {
|
effect: async (action, { dispatch }) => {
|
||||||
|
const log = logger('nodes');
|
||||||
const { data, asCopy } = action.payload;
|
const { data, asCopy } = action.payload;
|
||||||
const nodeTemplates = $templates.get();
|
const nodeTemplates = $templates.get();
|
||||||
|
|
||||||
@ -48,7 +46,6 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
delete workflow.id;
|
delete workflow.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
$nodeExecutionStates.set({});
|
|
||||||
dispatch(workflowLoaded(workflow));
|
dispatch(workflowLoaded(workflow));
|
||||||
if (!warnings.length) {
|
if (!warnings.length) {
|
||||||
toast({
|
toast({
|
||||||
@ -72,7 +69,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof WorkflowVersionError) {
|
if (e instanceof WorkflowVersionError) {
|
||||||
// The workflow version was not recognized in the valid list of versions
|
// The workflow version was not recognized in the valid list of versions
|
||||||
log.error({ error: serializeError(e) }, e.message);
|
log.error({ error: parseify(e) }, e.message);
|
||||||
toast({
|
toast({
|
||||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||||
title: t('nodes.unableToValidateWorkflow'),
|
title: t('nodes.unableToValidateWorkflow'),
|
||||||
@ -81,7 +78,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
});
|
});
|
||||||
} else if (e instanceof WorkflowMigrationError) {
|
} else if (e instanceof WorkflowMigrationError) {
|
||||||
// There was a problem migrating the workflow to the latest version
|
// There was a problem migrating the workflow to the latest version
|
||||||
log.error({ error: serializeError(e) }, e.message);
|
log.error({ error: parseify(e) }, e.message);
|
||||||
toast({
|
toast({
|
||||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||||
title: t('nodes.unableToValidateWorkflow'),
|
title: t('nodes.unableToValidateWorkflow'),
|
||||||
@ -93,7 +90,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
const { message } = fromZodError(e, {
|
const { message } = fromZodError(e, {
|
||||||
prefix: t('nodes.workflowValidation'),
|
prefix: t('nodes.workflowValidation'),
|
||||||
});
|
});
|
||||||
log.error({ error: serializeError(e) }, message);
|
log.error({ error: parseify(e) }, message);
|
||||||
toast({
|
toast({
|
||||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||||
title: t('nodes.unableToValidateWorkflow'),
|
title: t('nodes.unableToValidateWorkflow'),
|
||||||
@ -102,7 +99,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Some other error occurred
|
// Some other error occurred
|
||||||
log.error({ error: serializeError(e) }, t('nodes.unknownErrorValidatingWorkflow'));
|
log.error({ error: parseify(e) }, t('nodes.unknownErrorValidatingWorkflow'));
|
||||||
toast({
|
toast({
|
||||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||||
title: t('nodes.unableToValidateWorkflow'),
|
title: t('nodes.unableToValidateWorkflow'),
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
import type { createStore } from 'app/store/store';
|
||||||
import type { AppStore } from 'app/store/store';
|
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
|
|
||||||
// Inject socket options and url into window for debugging
|
// Inject socket options and url into window for debugging
|
||||||
@ -23,7 +22,7 @@ class ReduxStoreNotInitialized extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const $store = atom<Readonly<AppStore | undefined>>();
|
export const $store = atom<Readonly<ReturnType<typeof createStore>> | undefined>();
|
||||||
|
|
||||||
export const getStore = () => {
|
export const getStore = () => {
|
||||||
const store = $store.get();
|
const store = $store.get();
|
||||||
@ -32,11 +31,3 @@ export const getStore = () => {
|
|||||||
}
|
}
|
||||||
return store;
|
return store;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAppStore = () => {
|
|
||||||
const store = useStore($store);
|
|
||||||
if (!store) {
|
|
||||||
throw new ReduxStoreNotInitialized();
|
|
||||||
}
|
|
||||||
return store;
|
|
||||||
};
|
|
||||||
|
@ -3,31 +3,37 @@ import { autoBatchEnhancer, combineReducers, configureStore } from '@reduxjs/too
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver';
|
import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver';
|
||||||
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
||||||
import type { SerializableObject } from 'common/types';
|
import type { JSONObject } from 'common/types';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { canvasPersistConfig, canvasSlice } from 'features/canvas/store/canvasSlice';
|
||||||
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
||||||
import { canvasSessionPersistConfig, canvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice';
|
import {
|
||||||
import { canvasSettingsPersistConfig, canvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
|
controlAdaptersPersistConfig,
|
||||||
import { canvasPersistConfig, canvasSlice, canvasUndoableConfig } from 'features/controlLayers/store/canvasSlice';
|
controlAdaptersSlice,
|
||||||
import { lorasPersistConfig, lorasSlice } from 'features/controlLayers/store/lorasSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice';
|
import {
|
||||||
import { toolPersistConfig, toolSlice } from 'features/controlLayers/store/toolSlice';
|
controlLayersPersistConfig,
|
||||||
|
controlLayersSlice,
|
||||||
|
controlLayersUndoableConfig,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice';
|
import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice';
|
||||||
import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice';
|
import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
import { hrfPersistConfig, hrfSlice } from 'features/hrf/store/hrfSlice';
|
import { hrfPersistConfig, hrfSlice } from 'features/hrf/store/hrfSlice';
|
||||||
|
import { loraPersistConfig, loraSlice } from 'features/lora/store/loraSlice';
|
||||||
import { modelManagerV2PersistConfig, modelManagerV2Slice } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
import { modelManagerV2PersistConfig, modelManagerV2Slice } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
||||||
import { nodesPersistConfig, nodesSlice, nodesUndoableConfig } from 'features/nodes/store/nodesSlice';
|
import { nodesPersistConfig, nodesSlice, nodesUndoableConfig } from 'features/nodes/store/nodesSlice';
|
||||||
import { workflowSettingsPersistConfig, workflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
|
import { workflowSettingsPersistConfig, workflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
|
||||||
import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workflowSlice';
|
import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workflowSlice';
|
||||||
|
import { generationPersistConfig, generationSlice } from 'features/parameters/store/generationSlice';
|
||||||
import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/upscaleSlice';
|
import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/upscaleSlice';
|
||||||
import { queueSlice } from 'features/queue/store/queueSlice';
|
import { queueSlice } from 'features/queue/store/queueSlice';
|
||||||
|
import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice';
|
||||||
import { stylePresetPersistConfig, stylePresetSlice } from 'features/stylePresets/store/stylePresetSlice';
|
import { stylePresetPersistConfig, stylePresetSlice } from 'features/stylePresets/store/stylePresetSlice';
|
||||||
import { configSlice } from 'features/system/store/configSlice';
|
import { configSlice } from 'features/system/store/configSlice';
|
||||||
import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice';
|
import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice';
|
||||||
import { uiPersistConfig, uiSlice } from 'features/ui/store/uiSlice';
|
import { uiPersistConfig, uiSlice } from 'features/ui/store/uiSlice';
|
||||||
import { diff } from 'jsondiffpatch';
|
import { diff } from 'jsondiffpatch';
|
||||||
import { keys, mergeWith, omit, pick } from 'lodash-es';
|
import { defaultsDeep, keys, omit, pick } from 'lodash-es';
|
||||||
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
||||||
import type { SerializeFunction, UnserializeFunction } from 'redux-remember';
|
import type { SerializeFunction, UnserializeFunction } from 'redux-remember';
|
||||||
import { rememberEnhancer, rememberReducer } from 'redux-remember';
|
import { rememberEnhancer, rememberReducer } from 'redux-remember';
|
||||||
@ -42,31 +48,29 @@ import { actionsDenylist } from './middleware/devtools/actionsDenylist';
|
|||||||
import { stateSanitizer } from './middleware/devtools/stateSanitizer';
|
import { stateSanitizer } from './middleware/devtools/stateSanitizer';
|
||||||
import { listenerMiddleware } from './middleware/listenerMiddleware';
|
import { listenerMiddleware } from './middleware/listenerMiddleware';
|
||||||
|
|
||||||
const log = logger('system');
|
|
||||||
|
|
||||||
const allReducers = {
|
const allReducers = {
|
||||||
[api.reducerPath]: api.reducer,
|
[canvasSlice.name]: canvasSlice.reducer,
|
||||||
[gallerySlice.name]: gallerySlice.reducer,
|
[gallerySlice.name]: gallerySlice.reducer,
|
||||||
|
[generationSlice.name]: generationSlice.reducer,
|
||||||
[nodesSlice.name]: undoable(nodesSlice.reducer, nodesUndoableConfig),
|
[nodesSlice.name]: undoable(nodesSlice.reducer, nodesUndoableConfig),
|
||||||
[systemSlice.name]: systemSlice.reducer,
|
[systemSlice.name]: systemSlice.reducer,
|
||||||
[configSlice.name]: configSlice.reducer,
|
[configSlice.name]: configSlice.reducer,
|
||||||
[uiSlice.name]: uiSlice.reducer,
|
[uiSlice.name]: uiSlice.reducer,
|
||||||
|
[controlAdaptersSlice.name]: controlAdaptersSlice.reducer,
|
||||||
[dynamicPromptsSlice.name]: dynamicPromptsSlice.reducer,
|
[dynamicPromptsSlice.name]: dynamicPromptsSlice.reducer,
|
||||||
[deleteImageModalSlice.name]: deleteImageModalSlice.reducer,
|
[deleteImageModalSlice.name]: deleteImageModalSlice.reducer,
|
||||||
[changeBoardModalSlice.name]: changeBoardModalSlice.reducer,
|
[changeBoardModalSlice.name]: changeBoardModalSlice.reducer,
|
||||||
|
[loraSlice.name]: loraSlice.reducer,
|
||||||
[modelManagerV2Slice.name]: modelManagerV2Slice.reducer,
|
[modelManagerV2Slice.name]: modelManagerV2Slice.reducer,
|
||||||
|
[sdxlSlice.name]: sdxlSlice.reducer,
|
||||||
[queueSlice.name]: queueSlice.reducer,
|
[queueSlice.name]: queueSlice.reducer,
|
||||||
[workflowSlice.name]: workflowSlice.reducer,
|
[workflowSlice.name]: workflowSlice.reducer,
|
||||||
[hrfSlice.name]: hrfSlice.reducer,
|
[hrfSlice.name]: hrfSlice.reducer,
|
||||||
[canvasSlice.name]: undoable(canvasSlice.reducer, canvasUndoableConfig),
|
[controlLayersSlice.name]: undoable(controlLayersSlice.reducer, controlLayersUndoableConfig),
|
||||||
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
|
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
|
||||||
|
[api.reducerPath]: api.reducer,
|
||||||
[upscaleSlice.name]: upscaleSlice.reducer,
|
[upscaleSlice.name]: upscaleSlice.reducer,
|
||||||
[stylePresetSlice.name]: stylePresetSlice.reducer,
|
[stylePresetSlice.name]: stylePresetSlice.reducer,
|
||||||
[paramsSlice.name]: paramsSlice.reducer,
|
|
||||||
[toolSlice.name]: toolSlice.reducer,
|
|
||||||
[canvasSettingsSlice.name]: canvasSettingsSlice.reducer,
|
|
||||||
[canvasSessionSlice.name]: canvasSessionSlice.reducer,
|
|
||||||
[lorasSlice.name]: lorasSlice.reducer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const rootReducer = combineReducers(allReducers);
|
const rootReducer = combineReducers(allReducers);
|
||||||
@ -96,26 +100,27 @@ export type PersistConfig<T = any> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
|
const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
|
||||||
|
[canvasPersistConfig.name]: canvasPersistConfig,
|
||||||
[galleryPersistConfig.name]: galleryPersistConfig,
|
[galleryPersistConfig.name]: galleryPersistConfig,
|
||||||
|
[generationPersistConfig.name]: generationPersistConfig,
|
||||||
[nodesPersistConfig.name]: nodesPersistConfig,
|
[nodesPersistConfig.name]: nodesPersistConfig,
|
||||||
[systemPersistConfig.name]: systemPersistConfig,
|
[systemPersistConfig.name]: systemPersistConfig,
|
||||||
[workflowPersistConfig.name]: workflowPersistConfig,
|
[workflowPersistConfig.name]: workflowPersistConfig,
|
||||||
[uiPersistConfig.name]: uiPersistConfig,
|
[uiPersistConfig.name]: uiPersistConfig,
|
||||||
|
[controlAdaptersPersistConfig.name]: controlAdaptersPersistConfig,
|
||||||
[dynamicPromptsPersistConfig.name]: dynamicPromptsPersistConfig,
|
[dynamicPromptsPersistConfig.name]: dynamicPromptsPersistConfig,
|
||||||
|
[sdxlPersistConfig.name]: sdxlPersistConfig,
|
||||||
|
[loraPersistConfig.name]: loraPersistConfig,
|
||||||
[modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig,
|
[modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig,
|
||||||
[hrfPersistConfig.name]: hrfPersistConfig,
|
[hrfPersistConfig.name]: hrfPersistConfig,
|
||||||
[canvasPersistConfig.name]: canvasPersistConfig,
|
[controlLayersPersistConfig.name]: controlLayersPersistConfig,
|
||||||
[workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig,
|
[workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig,
|
||||||
[upscalePersistConfig.name]: upscalePersistConfig,
|
[upscalePersistConfig.name]: upscalePersistConfig,
|
||||||
[stylePresetPersistConfig.name]: stylePresetPersistConfig,
|
[stylePresetPersistConfig.name]: stylePresetPersistConfig,
|
||||||
[paramsPersistConfig.name]: paramsPersistConfig,
|
|
||||||
[toolPersistConfig.name]: toolPersistConfig,
|
|
||||||
[canvasSettingsPersistConfig.name]: canvasSettingsPersistConfig,
|
|
||||||
[canvasSessionPersistConfig.name]: canvasSessionPersistConfig,
|
|
||||||
[lorasPersistConfig.name]: lorasPersistConfig,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const unserialize: UnserializeFunction = (data, key) => {
|
const unserialize: UnserializeFunction = (data, key) => {
|
||||||
|
const log = logger('system');
|
||||||
const persistConfig = persistConfigs[key as keyof typeof persistConfigs];
|
const persistConfig = persistConfigs[key as keyof typeof persistConfigs];
|
||||||
if (!persistConfig) {
|
if (!persistConfig) {
|
||||||
throw new Error(`No persist config for slice "${key}"`);
|
throw new Error(`No persist config for slice "${key}"`);
|
||||||
@ -125,21 +130,17 @@ const unserialize: UnserializeFunction = (data, key) => {
|
|||||||
const parsed = JSON.parse(data);
|
const parsed = JSON.parse(data);
|
||||||
|
|
||||||
// strip out old keys
|
// strip out old keys
|
||||||
const stripped = pick(deepClone(parsed), keys(initialState));
|
const stripped = pick(parsed, keys(initialState));
|
||||||
// run (additive) migrations
|
// run (additive) migrations
|
||||||
const migrated = migrate(stripped);
|
const migrated = migrate(stripped);
|
||||||
/*
|
// merge in initial state as default values, covering any missing keys
|
||||||
* Merge in initial state as default values, covering any missing keys. You might be tempted to use _.defaultsDeep,
|
const transformed = defaultsDeep(migrated, initialState);
|
||||||
* but that merges arrays by index and partial objects by key. Using an identity function as the customizer results
|
|
||||||
* in behaviour like defaultsDeep, but doesn't overwrite any values that are not undefined in the migrated state.
|
|
||||||
*/
|
|
||||||
const transformed = mergeWith(migrated, initialState, (objVal) => objVal);
|
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
{
|
{
|
||||||
persistedData: parsed,
|
persistedData: parsed,
|
||||||
rehydratedData: transformed,
|
rehydratedData: transformed,
|
||||||
diff: diff(parsed, transformed) as SerializableObject, // this is always serializable
|
diff: diff(parsed, transformed) as JSONObject, // this is always serializable
|
||||||
},
|
},
|
||||||
`Rehydrated slice "${key}"`
|
`Rehydrated slice "${key}"`
|
||||||
);
|
);
|
||||||
@ -201,8 +202,7 @@ export const createStore = (uniqueStoreKey?: string, persist = true) =>
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppStore = ReturnType<typeof createStore>;
|
export type RootState = ReturnType<ReturnType<typeof createStore>['getState']>;
|
||||||
export type RootState = ReturnType<AppStore['getState']>;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>;
|
export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>;
|
||||||
export type AppDispatch = ReturnType<typeof createStore>['dispatch'];
|
export type AppDispatch = ReturnType<typeof createStore>['dispatch'];
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { FilterType } from 'features/controlLayers/store/types';
|
import type { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
|
||||||
import type { ParameterPrecision, ParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
import type { ParameterPrecision, ParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
||||||
import type { TabName } from 'features/ui/store/uiTypes';
|
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
import type { O } from 'ts-toolbelt';
|
import type { O } from 'ts-toolbelt';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,7 +72,7 @@ export type AppConfig = {
|
|||||||
maxUpscaleDimension?: number;
|
maxUpscaleDimension?: number;
|
||||||
allowPrivateBoards: boolean;
|
allowPrivateBoards: boolean;
|
||||||
allowPrivateStylePresets: boolean;
|
allowPrivateStylePresets: boolean;
|
||||||
disabledTabs: TabName[];
|
disabledTabs: InvokeTabName[];
|
||||||
disabledFeatures: AppFeature[];
|
disabledFeatures: AppFeature[];
|
||||||
disabledSDFeatures: SDFeature[];
|
disabledSDFeatures: SDFeature[];
|
||||||
nodesAllowlist: string[] | undefined;
|
nodesAllowlist: string[] | undefined;
|
||||||
@ -83,7 +83,7 @@ export type AppConfig = {
|
|||||||
sd: {
|
sd: {
|
||||||
defaultModel?: string;
|
defaultModel?: string;
|
||||||
disabledControlNetModels: string[];
|
disabledControlNetModels: string[];
|
||||||
disabledControlNetProcessors: FilterType[];
|
disabledControlNetProcessors: (keyof typeof CONTROLNET_PROCESSORS)[];
|
||||||
// Core parameters
|
// Core parameters
|
||||||
iterations: NumericalParameterConfig;
|
iterations: NumericalParameterConfig;
|
||||||
width: NumericalParameterConfig; // initial value comes from model
|
width: NumericalParameterConfig; // initial value comes from model
|
||||||
|
@ -33,23 +33,28 @@ type IAINoImageFallbackProps = FlexProps & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const IAINoContentFallback = memo((props: IAINoImageFallbackProps) => {
|
export const IAINoContentFallback = memo((props: IAINoImageFallbackProps) => {
|
||||||
const { icon = PiImageBold, boxSize = 16, ...rest } = props;
|
const { icon = PiImageBold, boxSize = 16, sx, ...rest } = props;
|
||||||
|
|
||||||
|
const styles = useMemo(
|
||||||
|
() => ({
|
||||||
|
w: 'full',
|
||||||
|
h: 'full',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
borderRadius: 'base',
|
||||||
|
flexDir: 'column',
|
||||||
|
gap: 2,
|
||||||
|
userSelect: 'none',
|
||||||
|
opacity: 0.7,
|
||||||
|
color: 'base.500',
|
||||||
|
fontSize: 'md',
|
||||||
|
...sx,
|
||||||
|
}),
|
||||||
|
[sx]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex sx={styles} {...rest}>
|
||||||
w="full"
|
|
||||||
h="full"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
borderRadius="base"
|
|
||||||
flexDir="column"
|
|
||||||
gap={2}
|
|
||||||
userSelect="none"
|
|
||||||
opacity={0.7}
|
|
||||||
color="base.500"
|
|
||||||
fontSize="md"
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{icon && <Icon as={icon} boxSize={boxSize} opacity={0.7} />}
|
{icon && <Icon as={icon} boxSize={boxSize} opacity={0.7} />}
|
||||||
{props.label && <Text textAlign="center">{props.label}</Text>}
|
{props.label && <Text textAlign="center">{props.label}</Text>}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -13,9 +13,8 @@ import {
|
|||||||
Spacer,
|
Spacer,
|
||||||
Text,
|
Text,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectSystemSlice, setShouldEnableInformationalPopovers } from 'features/system/store/systemSlice';
|
import { setShouldEnableInformationalPopovers } from 'features/system/store/systemSlice';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { merge, omit } from 'lodash-es';
|
import { merge, omit } from 'lodash-es';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
@ -32,13 +31,8 @@ type Props = {
|
|||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectShouldEnableInformationalPopovers = createSelector(
|
|
||||||
selectSystemSlice,
|
|
||||||
(system) => system.shouldEnableInformationalPopovers
|
|
||||||
);
|
|
||||||
|
|
||||||
export const InformationalPopover = memo(({ feature, children, inPortal = true, ...rest }: Props) => {
|
export const InformationalPopover = memo(({ feature, children, inPortal = true, ...rest }: Props) => {
|
||||||
const shouldEnableInformationalPopovers = useAppSelector(selectShouldEnableInformationalPopovers);
|
const shouldEnableInformationalPopovers = useAppSelector((s) => s.system.shouldEnableInformationalPopovers);
|
||||||
|
|
||||||
const data = useMemo(() => POPOVER_DATA[feature], [feature]);
|
const data = useMemo(() => POPOVER_DATA[feature], [feature]);
|
||||||
|
|
||||||
|
@ -1,158 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
|
||||||
import { objectKeys } from 'common/util/objectKeys';
|
|
||||||
import { isEqual } from 'lodash-es';
|
|
||||||
import type { Atom } from 'nanostores';
|
|
||||||
import { atom, computed } from 'nanostores';
|
|
||||||
import type { RefObject } from 'react';
|
|
||||||
import { useEffect, useMemo } from 'react';
|
|
||||||
|
|
||||||
const log = logger('system');
|
|
||||||
|
|
||||||
const _INTERACTION_SCOPES = ['gallery', 'canvas', 'stagingArea', 'workflows', 'imageViewer'] as const;
|
|
||||||
|
|
||||||
type InteractionScope = (typeof _INTERACTION_SCOPES)[number];
|
|
||||||
|
|
||||||
export const $activeScopes = atom<Set<InteractionScope>>(new Set());
|
|
||||||
|
|
||||||
type InteractionScopeData = {
|
|
||||||
targets: Set<HTMLElement>;
|
|
||||||
$isActive: Atom<boolean>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const INTERACTION_SCOPES: Record<InteractionScope, InteractionScopeData> = _INTERACTION_SCOPES.reduce(
|
|
||||||
(acc, region) => {
|
|
||||||
acc[region] = {
|
|
||||||
targets: new Set(),
|
|
||||||
$isActive: computed($activeScopes, (activeScopes) => activeScopes.has(region)),
|
|
||||||
};
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as Record<InteractionScope, InteractionScopeData>
|
|
||||||
);
|
|
||||||
|
|
||||||
const formatScopes = (interactionScopes: Set<InteractionScope>) => {
|
|
||||||
if (interactionScopes.size === 0) {
|
|
||||||
return 'none';
|
|
||||||
}
|
|
||||||
return Array.from(interactionScopes).join(', ');
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addScope = (scope: InteractionScope) => {
|
|
||||||
const currentScopes = $activeScopes.get();
|
|
||||||
if (currentScopes.has(scope)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newScopes = new Set(currentScopes);
|
|
||||||
newScopes.add(scope);
|
|
||||||
$activeScopes.set(newScopes);
|
|
||||||
log.trace(`Added scope ${scope}: ${formatScopes($activeScopes.get())}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeScope = (scope: InteractionScope) => {
|
|
||||||
const currentScopes = $activeScopes.get();
|
|
||||||
if (!currentScopes.has(scope)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newScopes = new Set(currentScopes);
|
|
||||||
newScopes.delete(scope);
|
|
||||||
$activeScopes.set(newScopes);
|
|
||||||
log.trace(`Removed scope ${scope}: ${formatScopes($activeScopes.get())}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setScopes = (scopes: InteractionScope[]) => {
|
|
||||||
const newScopes = new Set(scopes);
|
|
||||||
$activeScopes.set(newScopes);
|
|
||||||
log.trace(`Set scopes: ${formatScopes($activeScopes.get())}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useScopeOnFocus = (scope: InteractionScope, ref: RefObject<HTMLElement>) => {
|
|
||||||
useEffect(() => {
|
|
||||||
const element = ref.current;
|
|
||||||
|
|
||||||
if (!element) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
INTERACTION_SCOPES[scope].targets.add(element);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
INTERACTION_SCOPES[scope].targets.delete(element);
|
|
||||||
};
|
|
||||||
}, [ref, scope]);
|
|
||||||
};
|
|
||||||
|
|
||||||
type UseScopeOnMountOptions = {
|
|
||||||
mount?: boolean;
|
|
||||||
unmount?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultUseScopeOnMountOptions: UseScopeOnMountOptions = {
|
|
||||||
mount: true,
|
|
||||||
unmount: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useScopeOnMount = (scope: InteractionScope, options?: UseScopeOnMountOptions) => {
|
|
||||||
useEffect(() => {
|
|
||||||
const { mount, unmount } = { ...defaultUseScopeOnMountOptions, ...options };
|
|
||||||
|
|
||||||
if (mount) {
|
|
||||||
addScope(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (unmount) {
|
|
||||||
removeScope(scope);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [options, scope]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useScopeImperativeApi = (scope: InteractionScope) => {
|
|
||||||
const api = useMemo(() => {
|
|
||||||
return {
|
|
||||||
add: () => {
|
|
||||||
addScope(scope);
|
|
||||||
},
|
|
||||||
remove: () => {
|
|
||||||
removeScope(scope);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}, [scope]);
|
|
||||||
|
|
||||||
return api;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFocusEvent = (_event: FocusEvent) => {
|
|
||||||
const activeElement = document.activeElement;
|
|
||||||
if (!(activeElement instanceof HTMLElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newActiveScopes = new Set<InteractionScope>();
|
|
||||||
|
|
||||||
for (const scope of objectKeys(INTERACTION_SCOPES)) {
|
|
||||||
for (const element of INTERACTION_SCOPES[scope].targets) {
|
|
||||||
if (element.contains(activeElement)) {
|
|
||||||
newActiveScopes.add(scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldActiveScopes = $activeScopes.get();
|
|
||||||
if (!isEqual(oldActiveScopes, newActiveScopes)) {
|
|
||||||
$activeScopes.set(newActiveScopes);
|
|
||||||
log.trace(`Scopes changed: ${formatScopes($activeScopes.get())}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useScopeFocusWatcher = () => {
|
|
||||||
useAssertSingleton('useScopeFocusWatcher');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('focus', handleFocusEvent, true);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('focus', handleFocusEvent, true);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
};
|
|
@ -1,4 +1,3 @@
|
|||||||
import type { WritableAtom } from 'nanostores';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
export const useBoolean = (initialValue: boolean) => {
|
export const useBoolean = (initialValue: boolean) => {
|
||||||
@ -20,33 +19,3 @@ export const useBoolean = (initialValue: boolean) => {
|
|||||||
|
|
||||||
return api;
|
return api;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildUseBoolean = ($boolean: WritableAtom<boolean>) => {
|
|
||||||
return () => {
|
|
||||||
const setTrue = useCallback(() => {
|
|
||||||
$boolean.set(true);
|
|
||||||
}, []);
|
|
||||||
const setFalse = useCallback(() => {
|
|
||||||
$boolean.set(false);
|
|
||||||
}, []);
|
|
||||||
const set = useCallback((value: boolean) => {
|
|
||||||
$boolean.set(value);
|
|
||||||
}, []);
|
|
||||||
const toggle = useCallback(() => {
|
|
||||||
$boolean.set(!$boolean.get());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const api = useMemo(
|
|
||||||
() => ({
|
|
||||||
setTrue,
|
|
||||||
setFalse,
|
|
||||||
set,
|
|
||||||
toggle,
|
|
||||||
$boolean,
|
|
||||||
}),
|
|
||||||
[set, setFalse, setTrue, toggle]
|
|
||||||
);
|
|
||||||
|
|
||||||
return api;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import type { Accept, FileRejection } from 'react-dropzone';
|
import type { Accept, FileRejection } from 'react-dropzone';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
@ -15,9 +14,13 @@ const accept: Accept = {
|
|||||||
'image/jpeg': ['.jpg', '.jpeg', '.png'],
|
'image/jpeg': ['.jpg', '.jpeg', '.png'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectPostUploadAction = createMemoizedSelector(selectActiveTab, (activeTabName) => {
|
const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (activeTabName) => {
|
||||||
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
||||||
|
|
||||||
|
if (activeTabName === 'canvas') {
|
||||||
|
postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' };
|
||||||
|
}
|
||||||
|
|
||||||
if (activeTabName === 'upscaling') {
|
if (activeTabName === 'upscaling') {
|
||||||
postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' };
|
postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' };
|
||||||
}
|
}
|
||||||
@ -27,9 +30,10 @@ const selectPostUploadAction = createMemoizedSelector(selectActiveTab, (activeTa
|
|||||||
|
|
||||||
export const useFullscreenDropzone = () => {
|
export const useFullscreenDropzone = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
|
||||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
|
||||||
const postUploadAction = useAppSelector(selectPostUploadAction);
|
const postUploadAction = useAppSelector(selectPostUploadAction);
|
||||||
|
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||||
|
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||||
|
|
||||||
const [uploadImage] = useUploadImageMutation();
|
const [uploadImage] = useUploadImageMutation();
|
||||||
|
|
||||||
const fileRejectionCallback = useCallback(
|
const fileRejectionCallback = useCallback(
|
||||||
@ -47,7 +51,7 @@ export const useFullscreenDropzone = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const fileAcceptedCallback = useCallback(
|
const fileAcceptedCallback = useCallback(
|
||||||
(file: File) => {
|
async (file: File) => {
|
||||||
uploadImage({
|
uploadImage({
|
||||||
file,
|
file,
|
||||||
image_category: 'user',
|
image_category: 'user',
|
||||||
@ -97,7 +101,7 @@ export const useFullscreenDropzone = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This is a hack to allow pasting images into the uploader
|
// This is a hack to allow pasting images into the uploader
|
||||||
const handlePaste = (e: ClipboardEvent) => {
|
const handlePaste = async (e: ClipboardEvent) => {
|
||||||
if (!dropzone.inputRef.current) {
|
if (!dropzone.inputRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { addScope, removeScope, setScopes } from 'common/hooks/interactionScopes';
|
|
||||||
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
||||||
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
||||||
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
|
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
|
||||||
@ -17,7 +16,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
['ctrl+enter', 'meta+enter'],
|
['ctrl+enter', 'meta+enter'],
|
||||||
queueBack,
|
queueBack,
|
||||||
{
|
{
|
||||||
enabled: !isDisabledQueueBack && !isLoadingQueueBack,
|
enabled: () => !isDisabledQueueBack && !isLoadingQueueBack,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
enableOnFormTags: ['input', 'textarea', 'select'],
|
enableOnFormTags: ['input', 'textarea', 'select'],
|
||||||
},
|
},
|
||||||
@ -30,7 +29,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
['ctrl+shift+enter', 'meta+shift+enter'],
|
['ctrl+shift+enter', 'meta+shift+enter'],
|
||||||
queueFront,
|
queueFront,
|
||||||
{
|
{
|
||||||
enabled: !isDisabledQueueFront && !isLoadingQueueFront,
|
enabled: () => !isDisabledQueueFront && !isLoadingQueueFront,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
enableOnFormTags: ['input', 'textarea', 'select'],
|
enableOnFormTags: ['input', 'textarea', 'select'],
|
||||||
},
|
},
|
||||||
@ -47,7 +46,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
['shift+x'],
|
['shift+x'],
|
||||||
cancelQueueItem,
|
cancelQueueItem,
|
||||||
{
|
{
|
||||||
enabled: !isDisabledCancelQueueItem && !isLoadingCancelQueueItem,
|
enabled: () => !isDisabledCancelQueueItem && !isLoadingCancelQueueItem,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
[cancelQueueItem, isDisabledCancelQueueItem, isLoadingCancelQueueItem]
|
[cancelQueueItem, isDisabledCancelQueueItem, isLoadingCancelQueueItem]
|
||||||
@ -59,7 +58,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
['ctrl+shift+x', 'meta+shift+x'],
|
['ctrl+shift+x', 'meta+shift+x'],
|
||||||
clearQueue,
|
clearQueue,
|
||||||
{
|
{
|
||||||
enabled: !isDisabledClearQueue && !isLoadingClearQueue,
|
enabled: () => !isDisabledClearQueue && !isLoadingClearQueue,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
[clearQueue, isDisabledClearQueue, isLoadingClearQueue]
|
[clearQueue, isDisabledClearQueue, isLoadingClearQueue]
|
||||||
@ -69,8 +68,6 @@ export const useGlobalHotkeys = () => {
|
|||||||
'1',
|
'1',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('generation'));
|
dispatch(setActiveTab('generation'));
|
||||||
addScope('canvas');
|
|
||||||
removeScope('workflows');
|
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
@ -78,9 +75,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'2',
|
'2',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('upscaling'));
|
dispatch(setActiveTab('canvas'));
|
||||||
removeScope('canvas');
|
|
||||||
removeScope('workflows');
|
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
@ -89,8 +84,6 @@ export const useGlobalHotkeys = () => {
|
|||||||
'3',
|
'3',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('workflows'));
|
dispatch(setActiveTab('workflows'));
|
||||||
removeScope('canvas');
|
|
||||||
addScope('workflows');
|
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
@ -100,7 +93,6 @@ export const useGlobalHotkeys = () => {
|
|||||||
() => {
|
() => {
|
||||||
if (isModelManagerEnabled) {
|
if (isModelManagerEnabled) {
|
||||||
dispatch(setActiveTab('models'));
|
dispatch(setActiveTab('models'));
|
||||||
setScopes([]);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, isModelManagerEnabled]
|
[dispatch, isModelManagerEnabled]
|
||||||
@ -110,7 +102,6 @@ export const useGlobalHotkeys = () => {
|
|||||||
isModelManagerEnabled ? '5' : '4',
|
isModelManagerEnabled ? '5' : '4',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('queue'));
|
dispatch(setActiveTab('queue'));
|
||||||
setScopes([]);
|
|
||||||
},
|
},
|
||||||
[dispatch, isModelManagerEnabled]
|
[dispatch, isModelManagerEnabled]
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import type { GroupBase } from 'chakra-react-select';
|
import type { GroupBase } from 'chakra-react-select';
|
||||||
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
|
||||||
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import { groupBy, reduce } from 'lodash-es';
|
import { groupBy, reduce } from 'lodash-es';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
@ -30,13 +28,11 @@ const groupByBaseFunc = <T extends AnyModelConfig>(model: T) => model.base.toUpp
|
|||||||
const groupByBaseAndTypeFunc = <T extends AnyModelConfig>(model: T) =>
|
const groupByBaseAndTypeFunc = <T extends AnyModelConfig>(model: T) =>
|
||||||
`${model.base.toUpperCase()} / ${model.type.replaceAll('_', ' ').toUpperCase()}`;
|
`${model.base.toUpperCase()} / ${model.type.replaceAll('_', ' ').toUpperCase()}`;
|
||||||
|
|
||||||
const selectBaseWithSDXLFallback = createSelector(selectParamsSlice, (params) => params.model?.base ?? 'sdxl');
|
|
||||||
|
|
||||||
export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
||||||
arg: UseGroupedModelComboboxArg<T>
|
arg: UseGroupedModelComboboxArg<T>
|
||||||
): UseGroupedModelComboboxReturn => {
|
): UseGroupedModelComboboxReturn => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const base = useAppSelector(selectBaseWithSDXLFallback);
|
const base_model = useAppSelector((s) => s.generation.model?.base ?? 'sdxl');
|
||||||
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, groupByType = false } = arg;
|
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, groupByType = false } = arg;
|
||||||
const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
|
const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
|
||||||
if (!modelConfigs) {
|
if (!modelConfigs) {
|
||||||
@ -58,9 +54,9 @@ export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
|||||||
},
|
},
|
||||||
[] as GroupBase<ComboboxOption>[]
|
[] as GroupBase<ComboboxOption>[]
|
||||||
);
|
);
|
||||||
_options.sort((a) => (a.label?.split('/')[0]?.toLowerCase().includes(base) ? -1 : 1));
|
_options.sort((a) => (a.label?.split('/')[0]?.toLowerCase().includes(base_model) ? -1 : 1));
|
||||||
return _options;
|
return _options;
|
||||||
}, [modelConfigs, groupByType, getIsDisabled, base]);
|
}, [modelConfigs, groupByType, getIsDisabled, base_model]);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import { useUploadImageMutation } from 'services/api/endpoints/images';
|
import { useUploadImageMutation } from 'services/api/endpoints/images';
|
||||||
@ -30,7 +29,7 @@ type UseImageUploadButtonArgs = {
|
|||||||
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
|
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
|
||||||
*/
|
*/
|
||||||
export const useImageUploadButton = ({ postUploadAction, isDisabled }: UseImageUploadButtonArgs) => {
|
export const useImageUploadButton = ({ postUploadAction, isDisabled }: UseImageUploadButtonArgs) => {
|
||||||
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||||
const [uploadImage] = useUploadImageMutation();
|
const [uploadImage] = useUploadImageMutation();
|
||||||
const onDropAccepted = useCallback(
|
const onDropAccepted = useCallback(
|
||||||
(files: File[]) => {
|
(files: File[]) => {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user