mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
ui: node editor misc 2 (#4306)
## What type of PR is this? (check all applicable) - [ ] Refactor - [x] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update - [ ] Community Node Submission ## Description Next batch of Node Editor changes.
This commit is contained in:
commit
8087b428cc
37
.gitignore
vendored
37
.gitignore
vendored
@ -1,23 +1,8 @@
|
|||||||
# ignore default image save location and model symbolic link
|
|
||||||
.idea/
|
.idea/
|
||||||
embeddings/
|
|
||||||
outputs/
|
|
||||||
models/ldm/stable-diffusion-v1/model.ckpt
|
|
||||||
**/restoration/codeformer/weights
|
|
||||||
|
|
||||||
# ignore user models config
|
|
||||||
configs/models.user.yaml
|
|
||||||
config/models.user.yml
|
|
||||||
invokeai.init
|
|
||||||
.version
|
|
||||||
.last_model
|
|
||||||
|
|
||||||
# ignore the Anaconda/Miniconda installer used while building Docker image
|
# ignore the Anaconda/Miniconda installer used while building Docker image
|
||||||
anaconda.sh
|
anaconda.sh
|
||||||
|
|
||||||
# ignore a directory which serves as a place for initial images
|
|
||||||
inputs/
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
@ -189,39 +174,17 @@ cython_debug/
|
|||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
src
|
|
||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
outputs
|
|
||||||
|
|
||||||
# Logs and associated folders
|
|
||||||
# created from generated embeddings.
|
|
||||||
logs
|
|
||||||
testtube
|
|
||||||
checkpoints
|
|
||||||
# If it's a Mac
|
# If it's a Mac
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
invokeai/frontend/yarn.lock
|
|
||||||
invokeai/frontend/node_modules
|
|
||||||
|
|
||||||
# Let the frontend manage its own gitignore
|
# Let the frontend manage its own gitignore
|
||||||
!invokeai/frontend/web/*
|
!invokeai/frontend/web/*
|
||||||
|
|
||||||
# Scratch folder
|
# Scratch folder
|
||||||
.scratch/
|
.scratch/
|
||||||
.vscode/
|
.vscode/
|
||||||
gfpgan/
|
|
||||||
models/ldm/stable-diffusion-v1/*.sha256
|
|
||||||
|
|
||||||
|
|
||||||
# GFPGAN model files
|
|
||||||
gfpgan/
|
|
||||||
|
|
||||||
# config file (will be created by installer)
|
|
||||||
configs/models.yaml
|
|
||||||
|
|
||||||
# ignore initfile
|
|
||||||
.invokeai
|
|
||||||
|
|
||||||
# ignore environment.yml and requirements.txt
|
# ignore environment.yml and requirements.txt
|
||||||
# these are links to the real files in environments-and-requirements
|
# these are links to the real files in environments-and-requirements
|
||||||
|
@ -122,6 +122,7 @@ def custom_openapi():
|
|||||||
|
|
||||||
output_schemas = schema(output_types, ref_prefix="#/components/schemas/")
|
output_schemas = schema(output_types, ref_prefix="#/components/schemas/")
|
||||||
for schema_key, output_schema in output_schemas["definitions"].items():
|
for schema_key, output_schema in output_schemas["definitions"].items():
|
||||||
|
output_schema["class"] = "output"
|
||||||
openapi_schema["components"]["schemas"][schema_key] = output_schema
|
openapi_schema["components"]["schemas"][schema_key] = output_schema
|
||||||
|
|
||||||
# TODO: note that we assume the schema_key here is the TYPE.__name__
|
# TODO: note that we assume the schema_key here is the TYPE.__name__
|
||||||
@ -130,8 +131,8 @@ def custom_openapi():
|
|||||||
|
|
||||||
# Add Node Editor UI helper schemas
|
# Add Node Editor UI helper schemas
|
||||||
ui_config_schemas = schema([UIConfigBase, _InputField, _OutputField], ref_prefix="#/components/schemas/")
|
ui_config_schemas = schema([UIConfigBase, _InputField, _OutputField], ref_prefix="#/components/schemas/")
|
||||||
for schema_key, output_schema in ui_config_schemas["definitions"].items():
|
for schema_key, ui_config_schema in ui_config_schemas["definitions"].items():
|
||||||
openapi_schema["components"]["schemas"][schema_key] = output_schema
|
openapi_schema["components"]["schemas"][schema_key] = ui_config_schema
|
||||||
|
|
||||||
# Add a reference to the output type to additionalProperties of the invoker schema
|
# Add a reference to the output type to additionalProperties of the invoker schema
|
||||||
for invoker in all_invocations:
|
for invoker in all_invocations:
|
||||||
@ -140,8 +141,8 @@ def custom_openapi():
|
|||||||
output_type_title = output_type_titles[output_type.__name__]
|
output_type_title = output_type_titles[output_type.__name__]
|
||||||
invoker_schema = openapi_schema["components"]["schemas"][invoker_name]
|
invoker_schema = openapi_schema["components"]["schemas"][invoker_name]
|
||||||
outputs_ref = {"$ref": f"#/components/schemas/{output_type_title}"}
|
outputs_ref = {"$ref": f"#/components/schemas/{output_type_title}"}
|
||||||
|
|
||||||
invoker_schema["output"] = outputs_ref
|
invoker_schema["output"] = outputs_ref
|
||||||
|
invoker_schema["class"] = "invocation"
|
||||||
|
|
||||||
from invokeai.backend.model_management.models import get_model_config_enums
|
from invokeai.backend.model_management.models import get_model_config_enums
|
||||||
|
|
||||||
|
@ -143,6 +143,7 @@ class UIType(str, Enum):
|
|||||||
# region Misc
|
# region Misc
|
||||||
FilePath = "FilePath"
|
FilePath = "FilePath"
|
||||||
Enum = "enum"
|
Enum = "enum"
|
||||||
|
Scheduler = "Scheduler"
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
@ -392,6 +393,13 @@ class BaseInvocationOutput(BaseModel):
|
|||||||
toprocess.extend(next_subclasses)
|
toprocess.extend(next_subclasses)
|
||||||
return tuple(subclasses)
|
return tuple(subclasses)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
@staticmethod
|
||||||
|
def schema_extra(schema: dict[str, Any], model_class: Type[BaseModel]) -> None:
|
||||||
|
if "required" not in schema or not isinstance(schema["required"], list):
|
||||||
|
schema["required"] = list()
|
||||||
|
schema["required"].extend(["type"])
|
||||||
|
|
||||||
|
|
||||||
class RequiredConnectionException(Exception):
|
class RequiredConnectionException(Exception):
|
||||||
"""Raised when an field which requires a connection did not receive a value."""
|
"""Raised when an field which requires a connection did not receive a value."""
|
||||||
@ -452,6 +460,9 @@ class BaseInvocation(ABC, BaseModel):
|
|||||||
schema["title"] = uiconfig.title
|
schema["title"] = uiconfig.title
|
||||||
if uiconfig and hasattr(uiconfig, "tags"):
|
if uiconfig and hasattr(uiconfig, "tags"):
|
||||||
schema["tags"] = uiconfig.tags
|
schema["tags"] = uiconfig.tags
|
||||||
|
if "required" not in schema or not isinstance(schema["required"], list):
|
||||||
|
schema["required"] = list()
|
||||||
|
schema["required"].extend(["type", "id"])
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def invoke(self, context: InvocationContext) -> BaseInvocationOutput:
|
def invoke(self, context: InvocationContext) -> BaseInvocationOutput:
|
||||||
|
@ -115,12 +115,14 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
noise: Optional[LatentsField] = InputField(description=FieldDescriptions.noise, input=Input.Connection)
|
noise: Optional[LatentsField] = InputField(description=FieldDescriptions.noise, input=Input.Connection)
|
||||||
steps: int = InputField(default=10, gt=0, description=FieldDescriptions.steps)
|
steps: int = InputField(default=10, gt=0, description=FieldDescriptions.steps)
|
||||||
cfg_scale: Union[float, List[float]] = InputField(
|
cfg_scale: Union[float, List[float]] = InputField(
|
||||||
default=7.5, ge=1, description=FieldDescriptions.cfg_scale, ui_type=UIType.Float
|
default=7.5, ge=1, description=FieldDescriptions.cfg_scale, ui_type=UIType.Float, title="CFG Scale"
|
||||||
)
|
)
|
||||||
denoising_start: float = InputField(default=0.0, ge=0, le=1, description=FieldDescriptions.denoising_start)
|
denoising_start: float = InputField(default=0.0, ge=0, le=1, description=FieldDescriptions.denoising_start)
|
||||||
denoising_end: float = InputField(default=1.0, ge=0, le=1, description=FieldDescriptions.denoising_end)
|
denoising_end: float = InputField(default=1.0, ge=0, le=1, description=FieldDescriptions.denoising_end)
|
||||||
scheduler: SAMPLER_NAME_VALUES = InputField(default="euler", description=FieldDescriptions.scheduler)
|
scheduler: SAMPLER_NAME_VALUES = InputField(
|
||||||
unet: UNetField = InputField(description=FieldDescriptions.unet, input=Input.Connection)
|
default="euler", description=FieldDescriptions.scheduler, ui_type=UIType.Scheduler
|
||||||
|
)
|
||||||
|
unet: UNetField = InputField(description=FieldDescriptions.unet, input=Input.Connection, title="UNet")
|
||||||
control: Union[ControlField, list[ControlField]] = InputField(
|
control: Union[ControlField, list[ControlField]] = InputField(
|
||||||
default=None, description=FieldDescriptions.control, input=Input.Connection
|
default=None, description=FieldDescriptions.control, input=Input.Connection
|
||||||
)
|
)
|
||||||
@ -454,7 +456,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@title("Latents to Image")
|
@title("Latents to Image")
|
||||||
@tags("latents", "image", "vae")
|
@tags("latents", "image", "vae", "l2i")
|
||||||
class LatentsToImageInvocation(BaseInvocation):
|
class LatentsToImageInvocation(BaseInvocation):
|
||||||
"""Generates an image from latents."""
|
"""Generates an image from latents."""
|
||||||
|
|
||||||
@ -642,7 +644,7 @@ class ScaleLatentsInvocation(BaseInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@title("Image to Latents")
|
@title("Image to Latents")
|
||||||
@tags("latents", "image", "vae")
|
@tags("latents", "image", "vae", "i2l")
|
||||||
class ImageToLatentsInvocation(BaseInvocation):
|
class ImageToLatentsInvocation(BaseInvocation):
|
||||||
"""Encodes an image into latents."""
|
"""Encodes an image into latents."""
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class AddInvocation(BaseInvocation):
|
|||||||
b: int = InputField(default=0, description=FieldDescriptions.num_2)
|
b: int = InputField(default=0, description=FieldDescriptions.num_2)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
||||||
return IntegerOutput(a=self.a + self.b)
|
return IntegerOutput(value=self.a + self.b)
|
||||||
|
|
||||||
|
|
||||||
@title("Subtract Integers")
|
@title("Subtract Integers")
|
||||||
@ -36,7 +36,7 @@ class SubtractInvocation(BaseInvocation):
|
|||||||
b: int = InputField(default=0, description=FieldDescriptions.num_2)
|
b: int = InputField(default=0, description=FieldDescriptions.num_2)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
||||||
return IntegerOutput(a=self.a - self.b)
|
return IntegerOutput(value=self.a - self.b)
|
||||||
|
|
||||||
|
|
||||||
@title("Multiply Integers")
|
@title("Multiply Integers")
|
||||||
@ -51,7 +51,7 @@ class MultiplyInvocation(BaseInvocation):
|
|||||||
b: int = InputField(default=0, description=FieldDescriptions.num_2)
|
b: int = InputField(default=0, description=FieldDescriptions.num_2)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
||||||
return IntegerOutput(a=self.a * self.b)
|
return IntegerOutput(value=self.a * self.b)
|
||||||
|
|
||||||
|
|
||||||
@title("Divide Integers")
|
@title("Divide Integers")
|
||||||
@ -66,7 +66,7 @@ class DivideInvocation(BaseInvocation):
|
|||||||
b: int = InputField(default=0, description=FieldDescriptions.num_2)
|
b: int = InputField(default=0, description=FieldDescriptions.num_2)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
||||||
return IntegerOutput(a=int(self.a / self.b))
|
return IntegerOutput(value=int(self.a / self.b))
|
||||||
|
|
||||||
|
|
||||||
@title("Random Integer")
|
@title("Random Integer")
|
||||||
@ -81,4 +81,4 @@ class RandomIntInvocation(BaseInvocation):
|
|||||||
high: int = InputField(default=np.iinfo(np.int32).max, description="The exclusive high value")
|
high: int = InputField(default=np.iinfo(np.int32).max, description="The exclusive high value")
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
||||||
return IntegerOutput(a=np.random.randint(self.low, self.high))
|
return IntegerOutput(value=np.random.randint(self.low, self.high))
|
||||||
|
@ -72,7 +72,7 @@ class LoRAModelField(BaseModel):
|
|||||||
base_model: BaseModelType = Field(description="Base model")
|
base_model: BaseModelType = Field(description="Base model")
|
||||||
|
|
||||||
|
|
||||||
@title("Main Model Loader")
|
@title("Main Model")
|
||||||
@tags("model")
|
@tags("model")
|
||||||
class MainModelLoaderInvocation(BaseInvocation):
|
class MainModelLoaderInvocation(BaseInvocation):
|
||||||
"""Loads a main model, outputting its submodels."""
|
"""Loads a main model, outputting its submodels."""
|
||||||
@ -179,7 +179,7 @@ class LoraLoaderOutput(BaseInvocationOutput):
|
|||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
@title("LoRA Loader")
|
@title("LoRA")
|
||||||
@tags("lora", "model")
|
@tags("lora", "model")
|
||||||
class LoraLoaderInvocation(BaseInvocation):
|
class LoraLoaderInvocation(BaseInvocation):
|
||||||
"""Apply selected lora to unet and text_encoder."""
|
"""Apply selected lora to unet and text_encoder."""
|
||||||
@ -257,7 +257,7 @@ class SDXLLoraLoaderOutput(BaseInvocationOutput):
|
|||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
@title("SDXL LoRA Loader")
|
@title("SDXL LoRA")
|
||||||
@tags("sdxl", "lora", "model")
|
@tags("sdxl", "lora", "model")
|
||||||
class SDXLLoraLoaderInvocation(BaseInvocation):
|
class SDXLLoraLoaderInvocation(BaseInvocation):
|
||||||
"""Apply selected lora to unet and text_encoder."""
|
"""Apply selected lora to unet and text_encoder."""
|
||||||
@ -356,7 +356,7 @@ class VaeLoaderOutput(BaseInvocationOutput):
|
|||||||
vae: VaeField = OutputField(description=FieldDescriptions.vae, title="VAE")
|
vae: VaeField = OutputField(description=FieldDescriptions.vae, title="VAE")
|
||||||
|
|
||||||
|
|
||||||
@title("VAE Loader")
|
@title("VAE")
|
||||||
@tags("vae", "model")
|
@tags("vae", "model")
|
||||||
class VaeLoaderInvocation(BaseInvocation):
|
class VaeLoaderInvocation(BaseInvocation):
|
||||||
"""Loads a VAE model, outputting a VaeLoaderOutput"""
|
"""Loads a VAE model, outputting a VaeLoaderOutput"""
|
||||||
|
@ -169,7 +169,7 @@ class ONNXTextToLatentsInvocation(BaseInvocation):
|
|||||||
ui_type=UIType.Float,
|
ui_type=UIType.Float,
|
||||||
)
|
)
|
||||||
scheduler: SAMPLER_NAME_VALUES = InputField(
|
scheduler: SAMPLER_NAME_VALUES = InputField(
|
||||||
default="euler", description=FieldDescriptions.scheduler, input=Input.Direct
|
default="euler", description=FieldDescriptions.scheduler, input=Input.Direct, ui_type=UIType.Scheduler
|
||||||
)
|
)
|
||||||
precision: PRECISION_VALUES = InputField(default="tensor(float16)", description=FieldDescriptions.precision)
|
precision: PRECISION_VALUES = InputField(default="tensor(float16)", description=FieldDescriptions.precision)
|
||||||
unet: UNetField = InputField(
|
unet: UNetField = InputField(
|
||||||
@ -406,7 +406,7 @@ class OnnxModelField(BaseModel):
|
|||||||
model_type: ModelType = Field(description="Model Type")
|
model_type: ModelType = Field(description="Model Type")
|
||||||
|
|
||||||
|
|
||||||
@title("ONNX Model Loader")
|
@title("ONNX Main Model")
|
||||||
@tags("onnx", "model")
|
@tags("onnx", "model")
|
||||||
class OnnxModelLoaderInvocation(BaseInvocation):
|
class OnnxModelLoaderInvocation(BaseInvocation):
|
||||||
"""Loads a main model, outputting its submodels."""
|
"""Loads a main model, outputting its submodels."""
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
from typing import Literal, Optional, Tuple
|
from typing import Literal, Optional, Tuple
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
import torch
|
import torch
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from .baseinvocation import (
|
from .baseinvocation import (
|
||||||
BaseInvocation,
|
BaseInvocation,
|
||||||
@ -33,7 +33,7 @@ class BooleanOutput(BaseInvocationOutput):
|
|||||||
"""Base class for nodes that output a single boolean"""
|
"""Base class for nodes that output a single boolean"""
|
||||||
|
|
||||||
type: Literal["boolean_output"] = "boolean_output"
|
type: Literal["boolean_output"] = "boolean_output"
|
||||||
a: bool = OutputField(description="The output boolean")
|
value: bool = OutputField(description="The output boolean")
|
||||||
|
|
||||||
|
|
||||||
class BooleanCollectionOutput(BaseInvocationOutput):
|
class BooleanCollectionOutput(BaseInvocationOutput):
|
||||||
@ -42,9 +42,7 @@ class BooleanCollectionOutput(BaseInvocationOutput):
|
|||||||
type: Literal["boolean_collection_output"] = "boolean_collection_output"
|
type: Literal["boolean_collection_output"] = "boolean_collection_output"
|
||||||
|
|
||||||
# Outputs
|
# Outputs
|
||||||
collection: list[bool] = OutputField(
|
collection: list[bool] = OutputField(description="The output boolean collection", ui_type=UIType.BooleanCollection)
|
||||||
default_factory=list, description="The output boolean collection", ui_type=UIType.BooleanCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@title("Boolean Primitive")
|
@title("Boolean Primitive")
|
||||||
@ -55,10 +53,10 @@ class BooleanInvocation(BaseInvocation):
|
|||||||
type: Literal["boolean"] = "boolean"
|
type: Literal["boolean"] = "boolean"
|
||||||
|
|
||||||
# Inputs
|
# Inputs
|
||||||
a: bool = InputField(default=False, description="The boolean value")
|
value: bool = InputField(default=False, description="The boolean value")
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> BooleanOutput:
|
def invoke(self, context: InvocationContext) -> BooleanOutput:
|
||||||
return BooleanOutput(a=self.a)
|
return BooleanOutput(value=self.value)
|
||||||
|
|
||||||
|
|
||||||
@title("Boolean Primitive Collection")
|
@title("Boolean Primitive Collection")
|
||||||
@ -70,7 +68,7 @@ class BooleanCollectionInvocation(BaseInvocation):
|
|||||||
|
|
||||||
# Inputs
|
# Inputs
|
||||||
collection: list[bool] = InputField(
|
collection: list[bool] = InputField(
|
||||||
default=False, description="The collection of boolean values", ui_type=UIType.BooleanCollection
|
default_factory=list, description="The collection of boolean values", ui_type=UIType.BooleanCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> BooleanCollectionOutput:
|
def invoke(self, context: InvocationContext) -> BooleanCollectionOutput:
|
||||||
@ -86,7 +84,7 @@ class IntegerOutput(BaseInvocationOutput):
|
|||||||
"""Base class for nodes that output a single integer"""
|
"""Base class for nodes that output a single integer"""
|
||||||
|
|
||||||
type: Literal["integer_output"] = "integer_output"
|
type: Literal["integer_output"] = "integer_output"
|
||||||
a: int = OutputField(description="The output integer")
|
value: int = OutputField(description="The output integer")
|
||||||
|
|
||||||
|
|
||||||
class IntegerCollectionOutput(BaseInvocationOutput):
|
class IntegerCollectionOutput(BaseInvocationOutput):
|
||||||
@ -95,9 +93,7 @@ class IntegerCollectionOutput(BaseInvocationOutput):
|
|||||||
type: Literal["integer_collection_output"] = "integer_collection_output"
|
type: Literal["integer_collection_output"] = "integer_collection_output"
|
||||||
|
|
||||||
# Outputs
|
# Outputs
|
||||||
collection: list[int] = OutputField(
|
collection: list[int] = OutputField(description="The int collection", ui_type=UIType.IntegerCollection)
|
||||||
default_factory=list, description="The int collection", ui_type=UIType.IntegerCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@title("Integer Primitive")
|
@title("Integer Primitive")
|
||||||
@ -108,10 +104,10 @@ class IntegerInvocation(BaseInvocation):
|
|||||||
type: Literal["integer"] = "integer"
|
type: Literal["integer"] = "integer"
|
||||||
|
|
||||||
# Inputs
|
# Inputs
|
||||||
a: int = InputField(default=0, description="The integer value")
|
value: int = InputField(default=0, description="The integer value")
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
def invoke(self, context: InvocationContext) -> IntegerOutput:
|
||||||
return IntegerOutput(a=self.a)
|
return IntegerOutput(value=self.value)
|
||||||
|
|
||||||
|
|
||||||
@title("Integer Primitive Collection")
|
@title("Integer Primitive Collection")
|
||||||
@ -139,7 +135,7 @@ class FloatOutput(BaseInvocationOutput):
|
|||||||
"""Base class for nodes that output a single float"""
|
"""Base class for nodes that output a single float"""
|
||||||
|
|
||||||
type: Literal["float_output"] = "float_output"
|
type: Literal["float_output"] = "float_output"
|
||||||
a: float = OutputField(description="The output float")
|
value: float = OutputField(description="The output float")
|
||||||
|
|
||||||
|
|
||||||
class FloatCollectionOutput(BaseInvocationOutput):
|
class FloatCollectionOutput(BaseInvocationOutput):
|
||||||
@ -148,9 +144,7 @@ class FloatCollectionOutput(BaseInvocationOutput):
|
|||||||
type: Literal["float_collection_output"] = "float_collection_output"
|
type: Literal["float_collection_output"] = "float_collection_output"
|
||||||
|
|
||||||
# Outputs
|
# Outputs
|
||||||
collection: list[float] = OutputField(
|
collection: list[float] = OutputField(description="The float collection", ui_type=UIType.FloatCollection)
|
||||||
default_factory=list, description="The float collection", ui_type=UIType.FloatCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@title("Float Primitive")
|
@title("Float Primitive")
|
||||||
@ -161,10 +155,10 @@ class FloatInvocation(BaseInvocation):
|
|||||||
type: Literal["float"] = "float"
|
type: Literal["float"] = "float"
|
||||||
|
|
||||||
# Inputs
|
# Inputs
|
||||||
param: float = InputField(default=0.0, description="The float value")
|
value: float = InputField(default=0.0, description="The float value")
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> FloatOutput:
|
def invoke(self, context: InvocationContext) -> FloatOutput:
|
||||||
return FloatOutput(a=self.param)
|
return FloatOutput(value=self.value)
|
||||||
|
|
||||||
|
|
||||||
@title("Float Primitive Collection")
|
@title("Float Primitive Collection")
|
||||||
@ -176,7 +170,7 @@ class FloatCollectionInvocation(BaseInvocation):
|
|||||||
|
|
||||||
# Inputs
|
# Inputs
|
||||||
collection: list[float] = InputField(
|
collection: list[float] = InputField(
|
||||||
default=0, description="The collection of float values", ui_type=UIType.FloatCollection
|
default_factory=list, description="The collection of float values", ui_type=UIType.FloatCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> FloatCollectionOutput:
|
def invoke(self, context: InvocationContext) -> FloatCollectionOutput:
|
||||||
@ -192,7 +186,7 @@ class StringOutput(BaseInvocationOutput):
|
|||||||
"""Base class for nodes that output a single string"""
|
"""Base class for nodes that output a single string"""
|
||||||
|
|
||||||
type: Literal["string_output"] = "string_output"
|
type: Literal["string_output"] = "string_output"
|
||||||
text: str = OutputField(description="The output string")
|
value: str = OutputField(description="The output string")
|
||||||
|
|
||||||
|
|
||||||
class StringCollectionOutput(BaseInvocationOutput):
|
class StringCollectionOutput(BaseInvocationOutput):
|
||||||
@ -201,9 +195,7 @@ class StringCollectionOutput(BaseInvocationOutput):
|
|||||||
type: Literal["string_collection_output"] = "string_collection_output"
|
type: Literal["string_collection_output"] = "string_collection_output"
|
||||||
|
|
||||||
# Outputs
|
# Outputs
|
||||||
collection: list[str] = OutputField(
|
collection: list[str] = OutputField(description="The output strings", ui_type=UIType.StringCollection)
|
||||||
default_factory=list, description="The output strings", ui_type=UIType.StringCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@title("String Primitive")
|
@title("String Primitive")
|
||||||
@ -214,10 +206,10 @@ class StringInvocation(BaseInvocation):
|
|||||||
type: Literal["string"] = "string"
|
type: Literal["string"] = "string"
|
||||||
|
|
||||||
# Inputs
|
# Inputs
|
||||||
text: str = InputField(default="", description="The string value", ui_component=UIComponent.Textarea)
|
value: str = InputField(default="", description="The string value", ui_component=UIComponent.Textarea)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> StringOutput:
|
def invoke(self, context: InvocationContext) -> StringOutput:
|
||||||
return StringOutput(text=self.text)
|
return StringOutput(value=self.value)
|
||||||
|
|
||||||
|
|
||||||
@title("String Primitive Collection")
|
@title("String Primitive Collection")
|
||||||
@ -229,7 +221,7 @@ class StringCollectionInvocation(BaseInvocation):
|
|||||||
|
|
||||||
# Inputs
|
# Inputs
|
||||||
collection: list[str] = InputField(
|
collection: list[str] = InputField(
|
||||||
default=0, description="The collection of string values", ui_type=UIType.StringCollection
|
default_factory=list, description="The collection of string values", ui_type=UIType.StringCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> StringCollectionOutput:
|
def invoke(self, context: InvocationContext) -> StringCollectionOutput:
|
||||||
@ -262,9 +254,7 @@ class ImageCollectionOutput(BaseInvocationOutput):
|
|||||||
type: Literal["image_collection_output"] = "image_collection_output"
|
type: Literal["image_collection_output"] = "image_collection_output"
|
||||||
|
|
||||||
# Outputs
|
# Outputs
|
||||||
collection: list[ImageField] = OutputField(
|
collection: list[ImageField] = OutputField(description="The output images", ui_type=UIType.ImageCollection)
|
||||||
default_factory=list, description="The output images", ui_type=UIType.ImageCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@title("Image Primitive")
|
@title("Image Primitive")
|
||||||
@ -334,7 +324,6 @@ class LatentsCollectionOutput(BaseInvocationOutput):
|
|||||||
type: Literal["latents_collection_output"] = "latents_collection_output"
|
type: Literal["latents_collection_output"] = "latents_collection_output"
|
||||||
|
|
||||||
collection: list[LatentsField] = OutputField(
|
collection: list[LatentsField] = OutputField(
|
||||||
default_factory=list,
|
|
||||||
description=FieldDescriptions.latents,
|
description=FieldDescriptions.latents,
|
||||||
ui_type=UIType.LatentsCollection,
|
ui_type=UIType.LatentsCollection,
|
||||||
)
|
)
|
||||||
@ -365,7 +354,7 @@ class LatentsCollectionInvocation(BaseInvocation):
|
|||||||
|
|
||||||
# Inputs
|
# Inputs
|
||||||
collection: list[LatentsField] = InputField(
|
collection: list[LatentsField] = InputField(
|
||||||
default=0, description="The collection of latents tensors", ui_type=UIType.LatentsCollection
|
description="The collection of latents tensors", ui_type=UIType.LatentsCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> LatentsCollectionOutput:
|
def invoke(self, context: InvocationContext) -> LatentsCollectionOutput:
|
||||||
@ -410,9 +399,7 @@ class ColorCollectionOutput(BaseInvocationOutput):
|
|||||||
type: Literal["color_collection_output"] = "color_collection_output"
|
type: Literal["color_collection_output"] = "color_collection_output"
|
||||||
|
|
||||||
# Outputs
|
# Outputs
|
||||||
collection: list[ColorField] = OutputField(
|
collection: list[ColorField] = OutputField(description="The output colors", ui_type=UIType.ColorCollection)
|
||||||
default_factory=list, description="The output colors", ui_type=UIType.ColorCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@title("Color Primitive")
|
@title("Color Primitive")
|
||||||
@ -455,7 +442,6 @@ class ConditioningCollectionOutput(BaseInvocationOutput):
|
|||||||
|
|
||||||
# Outputs
|
# Outputs
|
||||||
collection: list[ConditioningField] = OutputField(
|
collection: list[ConditioningField] = OutputField(
|
||||||
default_factory=list,
|
|
||||||
description="The output conditioning tensors",
|
description="The output conditioning tensors",
|
||||||
ui_type=UIType.ConditioningCollection,
|
ui_type=UIType.ConditioningCollection,
|
||||||
)
|
)
|
||||||
|
@ -37,7 +37,7 @@ class SDXLRefinerModelLoaderOutput(BaseInvocationOutput):
|
|||||||
vae: VaeField = OutputField(description=FieldDescriptions.vae, title="VAE")
|
vae: VaeField = OutputField(description=FieldDescriptions.vae, title="VAE")
|
||||||
|
|
||||||
|
|
||||||
@title("SDXL Main Model Loader")
|
@title("SDXL Main Model")
|
||||||
@tags("model", "sdxl")
|
@tags("model", "sdxl")
|
||||||
class SDXLModelLoaderInvocation(BaseInvocation):
|
class SDXLModelLoaderInvocation(BaseInvocation):
|
||||||
"""Loads an sdxl base model, outputting its submodels."""
|
"""Loads an sdxl base model, outputting its submodels."""
|
||||||
@ -122,7 +122,7 @@ class SDXLModelLoaderInvocation(BaseInvocation):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@title("SDXL Refiner Model Loader")
|
@title("SDXL Refiner Model")
|
||||||
@tags("model", "sdxl", "refiner")
|
@tags("model", "sdxl", "refiner")
|
||||||
class SDXLRefinerModelLoaderInvocation(BaseInvocation):
|
class SDXLRefinerModelLoaderInvocation(BaseInvocation):
|
||||||
"""Loads an sdxl refiner model, outputting its submodels."""
|
"""Loads an sdxl refiner model, outputting its submodels."""
|
||||||
|
@ -17,9 +17,9 @@ def create_text_to_image() -> LibraryGraph:
|
|||||||
description="Converts text to an image",
|
description="Converts text to an image",
|
||||||
graph=Graph(
|
graph=Graph(
|
||||||
nodes={
|
nodes={
|
||||||
"width": IntegerInvocation(id="width", a=512),
|
"width": IntegerInvocation(id="width", value=512),
|
||||||
"height": IntegerInvocation(id="height", a=512),
|
"height": IntegerInvocation(id="height", value=512),
|
||||||
"seed": IntegerInvocation(id="seed", a=-1),
|
"seed": IntegerInvocation(id="seed", value=-1),
|
||||||
"3": NoiseInvocation(id="3"),
|
"3": NoiseInvocation(id="3"),
|
||||||
"4": CompelInvocation(id="4"),
|
"4": CompelInvocation(id="4"),
|
||||||
"5": CompelInvocation(id="5"),
|
"5": CompelInvocation(id="5"),
|
||||||
@ -29,15 +29,15 @@ def create_text_to_image() -> LibraryGraph:
|
|||||||
},
|
},
|
||||||
edges=[
|
edges=[
|
||||||
Edge(
|
Edge(
|
||||||
source=EdgeConnection(node_id="width", field="a"),
|
source=EdgeConnection(node_id="width", field="value"),
|
||||||
destination=EdgeConnection(node_id="3", field="width"),
|
destination=EdgeConnection(node_id="3", field="width"),
|
||||||
),
|
),
|
||||||
Edge(
|
Edge(
|
||||||
source=EdgeConnection(node_id="height", field="a"),
|
source=EdgeConnection(node_id="height", field="value"),
|
||||||
destination=EdgeConnection(node_id="3", field="height"),
|
destination=EdgeConnection(node_id="3", field="height"),
|
||||||
),
|
),
|
||||||
Edge(
|
Edge(
|
||||||
source=EdgeConnection(node_id="seed", field="a"),
|
source=EdgeConnection(node_id="seed", field="value"),
|
||||||
destination=EdgeConnection(node_id="3", field="seed"),
|
destination=EdgeConnection(node_id="3", field="seed"),
|
||||||
),
|
),
|
||||||
Edge(
|
Edge(
|
||||||
@ -65,9 +65,9 @@ def create_text_to_image() -> LibraryGraph:
|
|||||||
exposed_inputs=[
|
exposed_inputs=[
|
||||||
ExposedNodeInput(node_path="4", field="prompt", alias="positive_prompt"),
|
ExposedNodeInput(node_path="4", field="prompt", alias="positive_prompt"),
|
||||||
ExposedNodeInput(node_path="5", field="prompt", alias="negative_prompt"),
|
ExposedNodeInput(node_path="5", field="prompt", alias="negative_prompt"),
|
||||||
ExposedNodeInput(node_path="width", field="a", alias="width"),
|
ExposedNodeInput(node_path="width", field="value", alias="width"),
|
||||||
ExposedNodeInput(node_path="height", field="a", alias="height"),
|
ExposedNodeInput(node_path="height", field="value", alias="height"),
|
||||||
ExposedNodeInput(node_path="seed", field="a", alias="seed"),
|
ExposedNodeInput(node_path="seed", field="value", alias="seed"),
|
||||||
],
|
],
|
||||||
exposed_outputs=[ExposedNodeOutput(node_path="8", field="image", alias="image")],
|
exposed_outputs=[ExposedNodeOutput(node_path="8", field="image", alias="image")],
|
||||||
)
|
)
|
||||||
|
@ -23,6 +23,11 @@ module.exports = {
|
|||||||
plugins: ['react', '@typescript-eslint', 'eslint-plugin-react-hooks'],
|
plugins: ['react', '@typescript-eslint', 'eslint-plugin-react-hooks'],
|
||||||
root: true,
|
root: true,
|
||||||
rules: {
|
rules: {
|
||||||
|
curly: 'error',
|
||||||
|
'react/jsx-curly-brace-presence': [
|
||||||
|
'error',
|
||||||
|
{ props: 'never', children: 'never' },
|
||||||
|
],
|
||||||
'react-hooks/exhaustive-deps': 'error',
|
'react-hooks/exhaustive-deps': 'error',
|
||||||
'no-var': 'error',
|
'no-var': 'error',
|
||||||
'brace-style': 'error',
|
'brace-style': 'error',
|
||||||
|
@ -73,7 +73,6 @@
|
|||||||
"@nanostores/react": "^0.7.1",
|
"@nanostores/react": "^0.7.1",
|
||||||
"@reduxjs/toolkit": "^1.9.5",
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
"@roarr/browser-log-writer": "^1.1.5",
|
"@roarr/browser-log-writer": "^1.1.5",
|
||||||
"chakra-ui-contextmenu": "^1.0.5",
|
|
||||||
"dateformat": "^5.0.3",
|
"dateformat": "^5.0.3",
|
||||||
"downshift": "^7.6.0",
|
"downshift": "^7.6.0",
|
||||||
"formik": "^2.4.2",
|
"formik": "^2.4.2",
|
||||||
@ -85,6 +84,7 @@
|
|||||||
"konva": "^9.2.0",
|
"konva": "^9.2.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"nanostores": "^0.9.2",
|
"nanostores": "^0.9.2",
|
||||||
|
"new-github-issue-url": "^1.0.0",
|
||||||
"openapi-fetch": "^0.6.1",
|
"openapi-fetch": "^0.6.1",
|
||||||
"overlayscrollbars": "^2.2.0",
|
"overlayscrollbars": "^2.2.0",
|
||||||
"overlayscrollbars-react": "^0.5.0",
|
"overlayscrollbars-react": "^0.5.0",
|
||||||
@ -95,6 +95,7 @@
|
|||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
|
"react-error-boundary": "^4.0.11",
|
||||||
"react-hotkeys-hook": "4.4.0",
|
"react-hotkeys-hook": "4.4.0",
|
||||||
"react-i18next": "^13.0.1",
|
"react-i18next": "^13.0.1",
|
||||||
"react-icons": "^4.10.1",
|
"react-icons": "^4.10.1",
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
"img2img": "Image To Image",
|
"img2img": "Image To Image",
|
||||||
"unifiedCanvas": "Unified Canvas",
|
"unifiedCanvas": "Unified Canvas",
|
||||||
"linear": "Linear",
|
"linear": "Linear",
|
||||||
"nodes": "Node Editor",
|
"nodes": "Workflow Editor",
|
||||||
"batch": "Batch Manager",
|
"batch": "Batch Manager",
|
||||||
"modelManager": "Model Manager",
|
"modelManager": "Model Manager",
|
||||||
"postprocessing": "Post Processing",
|
"postprocessing": "Post Processing",
|
||||||
@ -133,6 +133,7 @@
|
|||||||
"generalHotkeys": "General Hotkeys",
|
"generalHotkeys": "General Hotkeys",
|
||||||
"galleryHotkeys": "Gallery Hotkeys",
|
"galleryHotkeys": "Gallery Hotkeys",
|
||||||
"unifiedCanvasHotkeys": "Unified Canvas Hotkeys",
|
"unifiedCanvasHotkeys": "Unified Canvas Hotkeys",
|
||||||
|
"nodesHotkeys": "Nodes Hotkeys",
|
||||||
"invoke": {
|
"invoke": {
|
||||||
"title": "Invoke",
|
"title": "Invoke",
|
||||||
"desc": "Generate an image"
|
"desc": "Generate an image"
|
||||||
@ -332,6 +333,10 @@
|
|||||||
"acceptStagingImage": {
|
"acceptStagingImage": {
|
||||||
"title": "Accept Staging Image",
|
"title": "Accept Staging Image",
|
||||||
"desc": "Accept Current Staging Area Image"
|
"desc": "Accept Current Staging Area Image"
|
||||||
|
},
|
||||||
|
"addNodes": {
|
||||||
|
"title": "Add Nodes",
|
||||||
|
"desc": "Opens the add node menu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"modelManager": {
|
"modelManager": {
|
||||||
|
@ -42,7 +42,6 @@ async function main() {
|
|||||||
// We only want to make fields optional if they are required
|
// We only want to make fields optional if they are required
|
||||||
if (!Array.isArray(schemaObject?.required)) {
|
if (!Array.isArray(schemaObject?.required)) {
|
||||||
schemaObject.required = ['id', 'type'];
|
schemaObject.required = ['id', 'type'];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
schemaObject.required.forEach((prop) => {
|
schemaObject.required.forEach((prop) => {
|
||||||
@ -68,12 +67,26 @@ async function main() {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// if (
|
|
||||||
// 'input' in schemaObject &&
|
// Check if we are generating types for an invocation output
|
||||||
// (schemaObject.input === 'any' || schemaObject.input === 'connection')
|
const isInvocationOutputPath = metadata.path.match(
|
||||||
// ) {
|
/^#\/components\/schemas\/\w*Output$/
|
||||||
// schemaObject.required = false;
|
);
|
||||||
// }
|
|
||||||
|
const hasOutputProperties =
|
||||||
|
schemaObject.properties && 'type' in schemaObject.properties;
|
||||||
|
|
||||||
|
if (isInvocationOutputPath && hasOutputProperties) {
|
||||||
|
if (!Array.isArray(schemaObject?.required)) {
|
||||||
|
schemaObject.required = ['type'];
|
||||||
|
}
|
||||||
|
schemaObject.required = [
|
||||||
|
...new Set(schemaObject.required.concat(['type'])),
|
||||||
|
];
|
||||||
|
console.log(
|
||||||
|
`Making output's "type" required: ${COLORS.fg.yellow}${schemaObject.title}${COLORS.reset}`
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
fs.writeFileSync(OUTPUT_FILE, types);
|
fs.writeFileSync(OUTPUT_FILE, types);
|
||||||
|
@ -16,9 +16,11 @@ import InvokeTabs from 'features/ui/components/InvokeTabs';
|
|||||||
import ParametersDrawer from 'features/ui/components/ParametersDrawer';
|
import ParametersDrawer from 'features/ui/components/ParametersDrawer';
|
||||||
import i18n from 'i18n';
|
import i18n from 'i18n';
|
||||||
import { size } from 'lodash-es';
|
import { size } from 'lodash-es';
|
||||||
import { ReactNode, memo, useEffect } from 'react';
|
import { ReactNode, memo, useCallback, useEffect } from 'react';
|
||||||
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import GlobalHotkeys from './GlobalHotkeys';
|
import GlobalHotkeys from './GlobalHotkeys';
|
||||||
import Toaster from './Toaster';
|
import Toaster from './Toaster';
|
||||||
|
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
@ -32,6 +34,11 @@ const App = ({ config = DEFAULT_CONFIG, headerComponent }: Props) => {
|
|||||||
|
|
||||||
const logger = useLogger();
|
const logger = useLogger();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
location.reload();
|
||||||
|
return false;
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
i18n.changeLanguage(language);
|
i18n.changeLanguage(language);
|
||||||
@ -49,7 +56,10 @@ const App = ({ config = DEFAULT_CONFIG, headerComponent }: Props) => {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ErrorBoundary
|
||||||
|
onReset={handleReset}
|
||||||
|
FallbackComponent={AppErrorBoundaryFallback}
|
||||||
|
>
|
||||||
<Grid w="100vw" h="100vh" position="relative" overflow="hidden">
|
<Grid w="100vw" h="100vh" position="relative" overflow="hidden">
|
||||||
<ImageUploader>
|
<ImageUploader>
|
||||||
<Grid
|
<Grid
|
||||||
@ -87,7 +97,7 @@ const App = ({ config = DEFAULT_CONFIG, headerComponent }: Props) => {
|
|||||||
<ChangeBoardModal />
|
<ChangeBoardModal />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<GlobalHotkeys />
|
<GlobalHotkeys />
|
||||||
</>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
import { Flex, Heading, Link, Text, useToast } from '@chakra-ui/react';
|
||||||
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
import newGithubIssueUrl from 'new-github-issue-url';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { FaCopy, FaExternalLinkAlt } from 'react-icons/fa';
|
||||||
|
import { FaArrowRotateLeft } from 'react-icons/fa6';
|
||||||
|
import { serializeError } from 'serialize-error';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
error: Error;
|
||||||
|
resetErrorBoundary: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const handleCopy = useCallback(() => {
|
||||||
|
const text = JSON.stringify(serializeError(error), null, 2);
|
||||||
|
navigator.clipboard.writeText(`\`\`\`\n${text}\n\`\`\``);
|
||||||
|
toast({
|
||||||
|
title: 'Error Copied',
|
||||||
|
});
|
||||||
|
}, [error, toast]);
|
||||||
|
|
||||||
|
const url = useMemo(
|
||||||
|
() =>
|
||||||
|
newGithubIssueUrl({
|
||||||
|
user: 'invoke-ai',
|
||||||
|
repo: 'InvokeAI',
|
||||||
|
template: 'BUG_REPORT.yml',
|
||||||
|
title: `[bug]: ${error.name}: ${error.message}`,
|
||||||
|
}),
|
||||||
|
[error.message, error.name]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
layerStyle="body"
|
||||||
|
sx={{
|
||||||
|
w: '100vw',
|
||||||
|
h: '100vh',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
p: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
layerStyle="first"
|
||||||
|
sx={{
|
||||||
|
flexDir: 'column',
|
||||||
|
borderRadius: 'base',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
p: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Heading>Something went wrong</Heading>
|
||||||
|
<Flex
|
||||||
|
layerStyle="second"
|
||||||
|
sx={{
|
||||||
|
px: 8,
|
||||||
|
py: 4,
|
||||||
|
borderRadius: 'base',
|
||||||
|
gap: 4,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'error.500',
|
||||||
|
_dark: { color: 'error.400' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{error.name}: {error.message}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex sx={{ gap: 4 }}>
|
||||||
|
<IAIButton
|
||||||
|
leftIcon={<FaArrowRotateLeft />}
|
||||||
|
onClick={resetErrorBoundary}
|
||||||
|
>
|
||||||
|
Reset UI
|
||||||
|
</IAIButton>
|
||||||
|
<IAIButton leftIcon={<FaCopy />} onClick={handleCopy}>
|
||||||
|
Copy Error
|
||||||
|
</IAIButton>
|
||||||
|
<Link href={url} isExternal>
|
||||||
|
<IAIButton leftIcon={<FaExternalLinkAlt />}>Create Issue</IAIButton>
|
||||||
|
</Link>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(AppErrorBoundaryFallback);
|
@ -3,7 +3,7 @@ import {
|
|||||||
createLocalStorageManager,
|
createLocalStorageManager,
|
||||||
extendTheme,
|
extendTheme,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ReactNode, useEffect, useMemo } from 'react';
|
import { ReactNode, memo, useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { theme as invokeAITheme } from 'theme/theme';
|
import { theme as invokeAITheme } from 'theme/theme';
|
||||||
|
|
||||||
@ -46,4 +46,4 @@ function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ThemeLocaleProvider;
|
export default memo(ThemeLocaleProvider);
|
||||||
|
@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { toastQueueSelector } from 'features/system/store/systemSelectors';
|
import { toastQueueSelector } from 'features/system/store/systemSelectors';
|
||||||
import { addToast, clearToastQueue } from 'features/system/store/systemSlice';
|
import { addToast, clearToastQueue } from 'features/system/store/systemSlice';
|
||||||
import { MakeToastArg, makeToast } from 'features/system/util/makeToast';
|
import { MakeToastArg, makeToast } from 'features/system/util/makeToast';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { memo, useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical component. Watches the toast queue and makes toasts when the queue is not empty.
|
* Logical component. Watches the toast queue and makes toasts when the queue is not empty.
|
||||||
@ -44,4 +44,4 @@ export const useAppToaster = () => {
|
|||||||
return toaster;
|
return toaster;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Toaster;
|
export default memo(Toaster);
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
import {
|
||||||
|
controlNetImageChanged,
|
||||||
|
controlNetProcessedImageChanged,
|
||||||
|
} from 'features/controlNet/store/controlNetSlice';
|
||||||
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 { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { isInvocationNode } from 'features/nodes/types/types';
|
||||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp, forEach } from 'lodash-es';
|
||||||
import { api } from 'services/api';
|
import { api } from 'services/api';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import { imagesAdapter } from 'services/api/util';
|
import { imagesAdapter } from 'services/api/util';
|
||||||
@ -73,22 +77,61 @@ export const addRequestedSingleImageDeletionListener = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 (imageUsage.isCanvasImage) {
|
if (imageUsage.isCanvasImage) {
|
||||||
dispatch(resetCanvas());
|
dispatch(resetCanvas());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageUsage.isControlNetImage) {
|
imageDTOs.forEach((imageDTO) => {
|
||||||
dispatch(controlNetReset());
|
// reset init image if we deleted it
|
||||||
}
|
if (
|
||||||
|
getState().generation.initialImage?.imageName === imageDTO.image_name
|
||||||
|
) {
|
||||||
|
dispatch(clearInitialImage());
|
||||||
|
}
|
||||||
|
|
||||||
if (imageUsage.isInitialImage) {
|
// reset controlNets that use the deleted images
|
||||||
dispatch(clearInitialImage());
|
forEach(getState().controlNet.controlNets, (controlNet) => {
|
||||||
}
|
if (
|
||||||
|
controlNet.controlImage === imageDTO.image_name ||
|
||||||
|
controlNet.processedControlImage === imageDTO.image_name
|
||||||
|
) {
|
||||||
|
dispatch(
|
||||||
|
controlNetImageChanged({
|
||||||
|
controlNetId: controlNet.controlNetId,
|
||||||
|
controlImage: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
controlNetProcessedImageChanged({
|
||||||
|
controlNetId: controlNet.controlNetId,
|
||||||
|
processedControlImage: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (imageUsage.isNodesImage) {
|
// reset nodes that use the deleted images
|
||||||
dispatch(nodeEditorReset());
|
getState().nodes.nodes.forEach((node) => {
|
||||||
}
|
if (!isInvocationNode(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(node.data.inputs, (input) => {
|
||||||
|
if (
|
||||||
|
input.type === 'ImageField' &&
|
||||||
|
input.value?.image_name === imageDTO.image_name
|
||||||
|
) {
|
||||||
|
dispatch(
|
||||||
|
fieldImageValueChanged({
|
||||||
|
nodeId: node.data.id,
|
||||||
|
fieldName: input.name,
|
||||||
|
value: undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Delete from server
|
// Delete from server
|
||||||
const { requestId } = dispatch(
|
const { requestId } = dispatch(
|
||||||
@ -154,17 +197,58 @@ export const addRequestedMultipleImageDeletionListener = () => {
|
|||||||
dispatch(resetCanvas());
|
dispatch(resetCanvas());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imagesUsage.some((i) => i.isControlNetImage)) {
|
imageDTOs.forEach((imageDTO) => {
|
||||||
dispatch(controlNetReset());
|
// reset init image if we deleted it
|
||||||
}
|
if (
|
||||||
|
getState().generation.initialImage?.imageName ===
|
||||||
|
imageDTO.image_name
|
||||||
|
) {
|
||||||
|
dispatch(clearInitialImage());
|
||||||
|
}
|
||||||
|
|
||||||
if (imagesUsage.some((i) => i.isInitialImage)) {
|
// reset controlNets that use the deleted images
|
||||||
dispatch(clearInitialImage());
|
forEach(getState().controlNet.controlNets, (controlNet) => {
|
||||||
}
|
if (
|
||||||
|
controlNet.controlImage === imageDTO.image_name ||
|
||||||
|
controlNet.processedControlImage === imageDTO.image_name
|
||||||
|
) {
|
||||||
|
dispatch(
|
||||||
|
controlNetImageChanged({
|
||||||
|
controlNetId: controlNet.controlNetId,
|
||||||
|
controlImage: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
controlNetProcessedImageChanged({
|
||||||
|
controlNetId: controlNet.controlNetId,
|
||||||
|
processedControlImage: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (imagesUsage.some((i) => i.isNodesImage)) {
|
// reset nodes that use the deleted images
|
||||||
dispatch(nodeEditorReset());
|
getState().nodes.nodes.forEach((node) => {
|
||||||
}
|
if (!isInvocationNode(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(node.data.inputs, (input) => {
|
||||||
|
if (
|
||||||
|
input.type === 'ImageField' &&
|
||||||
|
input.value?.image_name === imageDTO.image_name
|
||||||
|
) {
|
||||||
|
dispatch(
|
||||||
|
fieldImageValueChanged({
|
||||||
|
nodeId: node.data.id,
|
||||||
|
fieldName: input.name,
|
||||||
|
value: undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ import {
|
|||||||
import { memo, ReactNode } from 'react';
|
import { memo, ReactNode } from 'react';
|
||||||
|
|
||||||
export interface IAIButtonProps extends ButtonProps {
|
export interface IAIButtonProps extends ButtonProps {
|
||||||
tooltip?: string;
|
tooltip?: TooltipProps['label'];
|
||||||
tooltipProps?: Omit<TooltipProps, 'children'>;
|
tooltipProps?: Omit<TooltipProps, 'children' | 'label'>;
|
||||||
isChecked?: boolean;
|
isChecked?: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
@ -100,14 +100,18 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const handleMouseOver = useCallback(
|
const handleMouseOver = useCallback(
|
||||||
(e: MouseEvent<HTMLDivElement>) => {
|
(e: MouseEvent<HTMLDivElement>) => {
|
||||||
if (onMouseOver) onMouseOver(e);
|
if (onMouseOver) {
|
||||||
|
onMouseOver(e);
|
||||||
|
}
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
},
|
},
|
||||||
[onMouseOver]
|
[onMouseOver]
|
||||||
);
|
);
|
||||||
const handleMouseOut = useCallback(
|
const handleMouseOut = useCallback(
|
||||||
(e: MouseEvent<HTMLDivElement>) => {
|
(e: MouseEvent<HTMLDivElement>) => {
|
||||||
if (onMouseOut) onMouseOut(e);
|
if (onMouseOut) {
|
||||||
|
onMouseOut(e);
|
||||||
|
}
|
||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
},
|
},
|
||||||
[onMouseOut]
|
[onMouseOut]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Box, Flex, Icon } from '@chakra-ui/react';
|
import { Box, Flex, Icon } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
import { FaExclamation } from 'react-icons/fa';
|
import { FaExclamation } from 'react-icons/fa';
|
||||||
|
|
||||||
const IAIErrorLoadingImageFallback = () => {
|
const IAIErrorLoadingImageFallback = () => {
|
||||||
@ -39,4 +40,4 @@ const IAIErrorLoadingImageFallback = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAIErrorLoadingImageFallback;
|
export default memo(IAIErrorLoadingImageFallback);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Box, Skeleton } from '@chakra-ui/react';
|
import { Box, Skeleton } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const IAIFillSkeleton = () => {
|
const IAIFillSkeleton = () => {
|
||||||
return (
|
return (
|
||||||
@ -27,4 +28,4 @@ const IAIFillSkeleton = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAIFillSkeleton;
|
export default memo(IAIFillSkeleton);
|
||||||
|
@ -9,8 +9,8 @@ import { memo } from 'react';
|
|||||||
|
|
||||||
export type IAIIconButtonProps = IconButtonProps & {
|
export type IAIIconButtonProps = IconButtonProps & {
|
||||||
role?: string;
|
role?: string;
|
||||||
tooltip?: string;
|
tooltip?: TooltipProps['label'];
|
||||||
tooltipProps?: Omit<TooltipProps, 'children'>;
|
tooltipProps?: Omit<TooltipProps, 'children' | 'label'>;
|
||||||
isChecked?: boolean;
|
isChecked?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Badge, Flex } from '@chakra-ui/react';
|
import { Badge, Flex } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO } from 'services/api/types';
|
||||||
|
|
||||||
type ImageMetadataOverlayProps = {
|
type ImageMetadataOverlayProps = {
|
||||||
@ -26,4 +27,4 @@ const ImageMetadataOverlay = ({ imageDTO }: ImageMetadataOverlayProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ImageMetadataOverlay;
|
export default memo(ImageMetadataOverlay);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Box, Flex, Heading } from '@chakra-ui/react';
|
import { Box, Flex, Heading } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
type ImageUploadOverlayProps = {
|
type ImageUploadOverlayProps = {
|
||||||
@ -87,4 +88,4 @@ const ImageUploadOverlay = (props: ImageUploadOverlayProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ImageUploadOverlay;
|
export default memo(ImageUploadOverlay);
|
||||||
|
@ -150,7 +150,9 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
{...getRootProps({ style: {} })}
|
{...getRootProps({ style: {} })}
|
||||||
onKeyDown={(e: KeyboardEvent) => {
|
onKeyDown={(e: KeyboardEvent) => {
|
||||||
// Bail out if user hits spacebar - do not open the uploader
|
// Bail out if user hits spacebar - do not open the uploader
|
||||||
if (e.key === ' ') return;
|
if (e.key === ' ') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Flex, Icon } from '@chakra-ui/react';
|
import { Flex, Icon } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
import { FaImage } from 'react-icons/fa';
|
import { FaImage } from 'react-icons/fa';
|
||||||
|
|
||||||
const SelectImagePlaceholder = () => {
|
const SelectImagePlaceholder = () => {
|
||||||
@ -19,4 +20,4 @@ const SelectImagePlaceholder = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SelectImagePlaceholder;
|
export default memo(SelectImagePlaceholder);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
@ -18,6 +19,7 @@ const SelectionOverlay = ({ isSelected, isHovered }: Props) => {
|
|||||||
opacity: isSelected ? 1 : 0.7,
|
opacity: isSelected ? 1 : 0.7,
|
||||||
transitionProperty: 'common',
|
transitionProperty: 'common',
|
||||||
transitionDuration: '0.1s',
|
transitionDuration: '0.1s',
|
||||||
|
pointerEvents: 'none',
|
||||||
shadow: isSelected
|
shadow: isSelected
|
||||||
? isHovered
|
? isHovered
|
||||||
? 'hoverSelected.light'
|
? 'hoverSelected.light'
|
||||||
@ -39,4 +41,4 @@ const SelectionOverlay = ({ isSelected, isHovered }: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SelectionOverlay;
|
export default memo(SelectionOverlay);
|
||||||
|
@ -2,71 +2,104 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
// import { validateSeedWeights } from 'common/util/seedWeightPairs';
|
import { isInvocationNode } from 'features/nodes/types/types';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { forEach } from 'lodash-es';
|
import { forEach, map } from 'lodash-es';
|
||||||
import { NON_REFINER_BASE_MODELS } from 'services/api/constants';
|
import { getConnectedEdges } from 'reactflow';
|
||||||
import { modelsApi } from '../../services/api/endpoints/models';
|
|
||||||
|
|
||||||
const readinessSelector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector, activeTabNameSelector],
|
[stateSelector, activeTabNameSelector],
|
||||||
(state, activeTabName) => {
|
(state, activeTabName) => {
|
||||||
const { generation, system } = state;
|
const { generation, system, nodes } = state;
|
||||||
const { initialImage } = generation;
|
const { initialImage, model } = generation;
|
||||||
|
|
||||||
const { isProcessing, isConnected } = system;
|
const { isProcessing, isConnected } = system;
|
||||||
|
|
||||||
let isReady = true;
|
const reasons: string[] = [];
|
||||||
const reasonsWhyNotReady: string[] = [];
|
|
||||||
|
|
||||||
if (activeTabName === 'img2img' && !initialImage) {
|
|
||||||
isReady = false;
|
|
||||||
reasonsWhyNotReady.push('No initial image selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { isSuccess: mainModelsSuccessfullyLoaded } =
|
|
||||||
modelsApi.endpoints.getMainModels.select(NON_REFINER_BASE_MODELS)(state);
|
|
||||||
if (!mainModelsSuccessfullyLoaded) {
|
|
||||||
isReady = false;
|
|
||||||
reasonsWhyNotReady.push('Models are not loaded');
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: job queue
|
|
||||||
// Cannot generate if already processing an image
|
// Cannot generate if already processing an image
|
||||||
if (isProcessing) {
|
if (isProcessing) {
|
||||||
isReady = false;
|
reasons.push('System busy');
|
||||||
reasonsWhyNotReady.push('System Busy');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot generate if not connected
|
// Cannot generate if not connected
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
isReady = false;
|
reasons.push('System disconnected');
|
||||||
reasonsWhyNotReady.push('System Disconnected');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Cannot generate variations without valid seed weights
|
if (activeTabName === 'img2img' && !initialImage) {
|
||||||
// if (
|
reasons.push('No initial image selected');
|
||||||
// shouldGenerateVariations &&
|
}
|
||||||
// (!(validateSeedWeights(seedWeights) || seedWeights === '') || seed === -1)
|
|
||||||
// ) {
|
|
||||||
// isReady = false;
|
|
||||||
// reasonsWhyNotReady.push('Seed-Weights badly formatted.');
|
|
||||||
// }
|
|
||||||
|
|
||||||
forEach(state.controlNet.controlNets, (controlNet, id) => {
|
if (activeTabName === 'nodes' && nodes.shouldValidateGraph) {
|
||||||
if (!controlNet.model) {
|
nodes.nodes.forEach((node) => {
|
||||||
isReady = false;
|
if (!isInvocationNode(node)) {
|
||||||
reasonsWhyNotReady.push(`ControlNet ${id} has no model selected.`);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeTemplate = nodes.nodeTemplates[node.data.type];
|
||||||
|
|
||||||
|
if (!nodeTemplate) {
|
||||||
|
// Node type not found
|
||||||
|
reasons.push('Missing node template');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectedEdges = getConnectedEdges([node], nodes.edges);
|
||||||
|
|
||||||
|
forEach(node.data.inputs, (field) => {
|
||||||
|
const fieldTemplate = nodeTemplate.inputs[field.name];
|
||||||
|
const hasConnection = connectedEdges.some(
|
||||||
|
(edge) =>
|
||||||
|
edge.target === node.id && edge.targetHandle === field.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fieldTemplate) {
|
||||||
|
reasons.push('Missing field template');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldTemplate.required && !field.value && !hasConnection) {
|
||||||
|
reasons.push(
|
||||||
|
`${node.data.label || nodeTemplate.title} -> ${
|
||||||
|
field.label || fieldTemplate.title
|
||||||
|
} missing input`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!model) {
|
||||||
|
reasons.push('No model selected');
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// All good
|
if (state.controlNet.isEnabled) {
|
||||||
return { isReady, reasonsWhyNotReady };
|
map(state.controlNet.controlNets).forEach((controlNet, i) => {
|
||||||
|
if (!controlNet.isEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!controlNet.model) {
|
||||||
|
reasons.push(`ControlNet ${i + 1} has no model selected.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!controlNet.controlImage ||
|
||||||
|
(!controlNet.processedControlImage &&
|
||||||
|
controlNet.processorType !== 'none')
|
||||||
|
) {
|
||||||
|
reasons.push(`ControlNet ${i + 1} has no control image`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isReady: !reasons.length, isProcessing, reasons };
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useIsReadyToInvoke = () => {
|
export const useIsReadyToInvoke = () => {
|
||||||
const { isReady } = useAppSelector(readinessSelector);
|
const { isReady, isProcessing, reasons } = useAppSelector(selector);
|
||||||
return isReady;
|
return { isReady, isProcessing, reasons };
|
||||||
};
|
};
|
||||||
|
@ -11,8 +11,14 @@ export default function useResolution():
|
|||||||
const tabletResolutions = ['md', 'lg'];
|
const tabletResolutions = ['md', 'lg'];
|
||||||
const desktopResolutions = ['xl', '2xl'];
|
const desktopResolutions = ['xl', '2xl'];
|
||||||
|
|
||||||
if (mobileResolutions.includes(breakpointValue)) return 'mobile';
|
if (mobileResolutions.includes(breakpointValue)) {
|
||||||
if (tabletResolutions.includes(breakpointValue)) return 'tablet';
|
return 'mobile';
|
||||||
if (desktopResolutions.includes(breakpointValue)) return 'desktop';
|
}
|
||||||
|
if (tabletResolutions.includes(breakpointValue)) {
|
||||||
|
return 'tablet';
|
||||||
|
}
|
||||||
|
if (desktopResolutions.includes(breakpointValue)) {
|
||||||
|
return 'desktop';
|
||||||
|
}
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,11 @@ export const dateComparator = (a: string, b: string) => {
|
|||||||
const dateB = new Date(b);
|
const dateB = new Date(b);
|
||||||
|
|
||||||
// sort in ascending order
|
// sort in ascending order
|
||||||
if (dateA > dateB) return 1;
|
if (dateA > dateB) {
|
||||||
if (dateA < dateB) return -1;
|
return 1;
|
||||||
|
}
|
||||||
|
if (dateA < dateB) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,9 @@ type Base64AndCaption = {
|
|||||||
|
|
||||||
const openBase64ImageInTab = (images: Base64AndCaption[]) => {
|
const openBase64ImageInTab = (images: Base64AndCaption[]) => {
|
||||||
const w = window.open('');
|
const w = window.open('');
|
||||||
if (!w) return;
|
if (!w) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
images.forEach((i) => {
|
images.forEach((i) => {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
@ -5,6 +5,7 @@ import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaTrash } from 'react-icons/fa';
|
import { FaTrash } from 'react-icons/fa';
|
||||||
import { isStagingSelector } from '../store/canvasSelectors';
|
import { isStagingSelector } from '../store/canvasSelectors';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const ClearCanvasHistoryButtonModal = () => {
|
const ClearCanvasHistoryButtonModal = () => {
|
||||||
const isStaging = useAppSelector(isStagingSelector);
|
const isStaging = useAppSelector(isStagingSelector);
|
||||||
@ -28,4 +29,4 @@ const ClearCanvasHistoryButtonModal = () => {
|
|||||||
</IAIAlertDialog>
|
</IAIAlertDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ClearCanvasHistoryButtonModal;
|
export default memo(ClearCanvasHistoryButtonModal);
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { KonvaEventObject } from 'konva/lib/Node';
|
import { KonvaEventObject } from 'konva/lib/Node';
|
||||||
import { Vector2d } from 'konva/lib/types';
|
import { Vector2d } from 'konva/lib/types';
|
||||||
import { useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
import { Layer, Stage } from 'react-konva';
|
import { Layer, Stage } from 'react-konva';
|
||||||
import useCanvasDragMove from '../hooks/useCanvasDragMove';
|
import useCanvasDragMove from '../hooks/useCanvasDragMove';
|
||||||
import useCanvasHotkeys from '../hooks/useCanvasHotkeys';
|
import useCanvasHotkeys from '../hooks/useCanvasHotkeys';
|
||||||
@ -220,4 +220,4 @@ const IAICanvas = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvas;
|
export default memo(IAICanvas);
|
||||||
|
@ -4,6 +4,7 @@ import { isEqual } from 'lodash-es';
|
|||||||
|
|
||||||
import { Group, Rect } from 'react-konva';
|
import { Group, Rect } from 'react-konva';
|
||||||
import { canvasSelector } from '../store/canvasSelectors';
|
import { canvasSelector } from '../store/canvasSelectors';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
canvasSelector,
|
canvasSelector,
|
||||||
@ -67,4 +68,4 @@ const IAICanvasBoundingBoxOverlay = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasBoundingBoxOverlay;
|
export default memo(IAICanvasBoundingBoxOverlay);
|
||||||
|
@ -6,7 +6,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { isEqual, range } from 'lodash-es';
|
import { isEqual, range } from 'lodash-es';
|
||||||
|
|
||||||
import { ReactNode, useCallback, useLayoutEffect, useState } from 'react';
|
import { ReactNode, memo, useCallback, useLayoutEffect, useState } from 'react';
|
||||||
import { Group, Line as KonvaLine } from 'react-konva';
|
import { Group, Line as KonvaLine } from 'react-konva';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
@ -117,4 +117,4 @@ const IAICanvasGrid = () => {
|
|||||||
return <Group>{gridLines}</Group>;
|
return <Group>{gridLines}</Group>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasGrid;
|
export default memo(IAICanvasGrid);
|
||||||
|
@ -4,6 +4,7 @@ import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
|||||||
import useImage from 'use-image';
|
import useImage from 'use-image';
|
||||||
import { CanvasImage } from '../store/canvasTypes';
|
import { CanvasImage } from '../store/canvasTypes';
|
||||||
import { $authToken } from 'services/api/client';
|
import { $authToken } from 'services/api/client';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
type IAICanvasImageProps = {
|
type IAICanvasImageProps = {
|
||||||
canvasImage: CanvasImage;
|
canvasImage: CanvasImage;
|
||||||
@ -25,4 +26,4 @@ const IAICanvasImage = (props: IAICanvasImageProps) => {
|
|||||||
return <Image x={x} y={y} image={image} listening={false} />;
|
return <Image x={x} y={y} image={image} listening={false} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasImage;
|
export default memo(IAICanvasImage);
|
||||||
|
@ -4,7 +4,7 @@ import { systemSelector } from 'features/system/store/systemSelectors';
|
|||||||
import { ImageConfig } from 'konva/lib/shapes/Image';
|
import { ImageConfig } from 'konva/lib/shapes/Image';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { Image as KonvaImage } from 'react-konva';
|
import { Image as KonvaImage } from 'react-konva';
|
||||||
import { canvasSelector } from '../store/canvasSelectors';
|
import { canvasSelector } from '../store/canvasSelectors';
|
||||||
|
|
||||||
@ -66,4 +66,4 @@ const IAICanvasIntermediateImage = (props: Props) => {
|
|||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasIntermediateImage;
|
export default memo(IAICanvasIntermediateImage);
|
||||||
|
@ -7,7 +7,7 @@ import { Rect } from 'react-konva';
|
|||||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { isNumber } from 'lodash-es';
|
import { isNumber } from 'lodash-es';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
export const canvasMaskCompositerSelector = createSelector(
|
export const canvasMaskCompositerSelector = createSelector(
|
||||||
canvasSelector,
|
canvasSelector,
|
||||||
@ -125,7 +125,9 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
|
|||||||
}, [offset]);
|
}, [offset]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fillPatternImage) return;
|
if (fillPatternImage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
@ -135,7 +137,9 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
|
|||||||
}, [fillPatternImage, maskColorString]);
|
}, [fillPatternImage, maskColorString]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!fillPatternImage) return;
|
if (!fillPatternImage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
fillPatternImage.src = getColoredSVG(maskColorString);
|
fillPatternImage.src = getColoredSVG(maskColorString);
|
||||||
}, [fillPatternImage, maskColorString]);
|
}, [fillPatternImage, maskColorString]);
|
||||||
|
|
||||||
@ -151,8 +155,9 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
|
|||||||
!isNumber(stageScale) ||
|
!isNumber(stageScale) ||
|
||||||
!isNumber(stageDimensions.width) ||
|
!isNumber(stageDimensions.width) ||
|
||||||
!isNumber(stageDimensions.height)
|
!isNumber(stageDimensions.height)
|
||||||
)
|
) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Rect
|
<Rect
|
||||||
@ -172,4 +177,4 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasMaskCompositer;
|
export default memo(IAICanvasMaskCompositer);
|
||||||
|
@ -6,6 +6,7 @@ import { isEqual } from 'lodash-es';
|
|||||||
|
|
||||||
import { Group, Line } from 'react-konva';
|
import { Group, Line } from 'react-konva';
|
||||||
import { isCanvasMaskLine } from '../store/canvasTypes';
|
import { isCanvasMaskLine } from '../store/canvasTypes';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
export const canvasLinesSelector = createSelector(
|
export const canvasLinesSelector = createSelector(
|
||||||
[canvasSelector],
|
[canvasSelector],
|
||||||
@ -52,4 +53,4 @@ const IAICanvasLines = (props: InpaintingCanvasLinesProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasLines;
|
export default memo(IAICanvasLines);
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
isCanvasFillRect,
|
isCanvasFillRect,
|
||||||
} from '../store/canvasTypes';
|
} from '../store/canvasTypes';
|
||||||
import IAICanvasImage from './IAICanvasImage';
|
import IAICanvasImage from './IAICanvasImage';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[canvasSelector],
|
[canvasSelector],
|
||||||
@ -33,7 +34,9 @@ const selector = createSelector(
|
|||||||
const IAICanvasObjectRenderer = () => {
|
const IAICanvasObjectRenderer = () => {
|
||||||
const { objects } = useAppSelector(selector);
|
const { objects } = useAppSelector(selector);
|
||||||
|
|
||||||
if (!objects) return null;
|
if (!objects) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group name="outpainting-objects" listening={false}>
|
<Group name="outpainting-objects" listening={false}>
|
||||||
@ -101,4 +104,4 @@ const IAICanvasObjectRenderer = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasObjectRenderer;
|
export default memo(IAICanvasObjectRenderer);
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
setDoesCanvasNeedScaling,
|
setDoesCanvasNeedScaling,
|
||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { useLayoutEffect, useRef } from 'react';
|
import { memo, useLayoutEffect, useRef } from 'react';
|
||||||
|
|
||||||
const canvasResizerSelector = createSelector(
|
const canvasResizerSelector = createSelector(
|
||||||
canvasSelector,
|
canvasSelector,
|
||||||
@ -42,7 +42,9 @@ const IAICanvasResizer = () => {
|
|||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { clientWidth, clientHeight } = ref.current;
|
const { clientWidth, clientHeight } = ref.current;
|
||||||
|
|
||||||
@ -86,4 +88,4 @@ const IAICanvasResizer = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasResizer;
|
export default memo(IAICanvasResizer);
|
||||||
|
@ -6,6 +6,7 @@ import { isEqual } from 'lodash-es';
|
|||||||
|
|
||||||
import { Group, Rect } from 'react-konva';
|
import { Group, Rect } from 'react-konva';
|
||||||
import IAICanvasImage from './IAICanvasImage';
|
import IAICanvasImage from './IAICanvasImage';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[canvasSelector],
|
[canvasSelector],
|
||||||
@ -88,4 +89,4 @@ const IAICanvasStagingArea = (props: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasStagingArea;
|
export default memo(IAICanvasStagingArea);
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -129,7 +129,9 @@ const IAICanvasStagingAreaToolbar = () => {
|
|||||||
currentStagingAreaImage?.imageName ?? skipToken
|
currentStagingAreaImage?.imageName ?? skipToken
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!currentStagingAreaImage) return null;
|
if (!currentStagingAreaImage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -207,4 +209,4 @@ const IAICanvasStagingAreaToolbar = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasStagingAreaToolbar;
|
export default memo(IAICanvasStagingAreaToolbar);
|
||||||
|
@ -7,6 +7,7 @@ import { isEqual } from 'lodash-es';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import roundToHundreth from '../util/roundToHundreth';
|
import roundToHundreth from '../util/roundToHundreth';
|
||||||
import IAICanvasStatusTextCursorPos from './IAICanvasStatusText/IAICanvasStatusTextCursorPos';
|
import IAICanvasStatusTextCursorPos from './IAICanvasStatusText/IAICanvasStatusTextCursorPos';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const warningColor = 'var(--invokeai-colors-warning-500)';
|
const warningColor = 'var(--invokeai-colors-warning-500)';
|
||||||
|
|
||||||
@ -162,4 +163,4 @@ const IAICanvasStatusText = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasStatusText;
|
export default memo(IAICanvasStatusText);
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
COLOR_PICKER_SIZE,
|
COLOR_PICKER_SIZE,
|
||||||
COLOR_PICKER_STROKE_RADIUS,
|
COLOR_PICKER_STROKE_RADIUS,
|
||||||
} from '../util/constants';
|
} from '../util/constants';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const canvasBrushPreviewSelector = createSelector(
|
const canvasBrushPreviewSelector = createSelector(
|
||||||
canvasSelector,
|
canvasSelector,
|
||||||
@ -134,7 +135,9 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
|||||||
clip,
|
clip,
|
||||||
} = useAppSelector(canvasBrushPreviewSelector);
|
} = useAppSelector(canvasBrushPreviewSelector);
|
||||||
|
|
||||||
if (!shouldDrawBrushPreview) return null;
|
if (!shouldDrawBrushPreview) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group listening={false} {...clip} {...rest}>
|
<Group listening={false} {...clip} {...rest}>
|
||||||
@ -206,4 +209,4 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasToolPreview;
|
export default memo(IAICanvasToolPreview);
|
||||||
|
@ -19,7 +19,7 @@ import { KonvaEventObject } from 'konva/lib/Node';
|
|||||||
import { Vector2d } from 'konva/lib/types';
|
import { Vector2d } from 'konva/lib/types';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { Group, Rect, Transformer } from 'react-konva';
|
import { Group, Rect, Transformer } from 'react-konva';
|
||||||
|
|
||||||
@ -85,7 +85,9 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
|||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!transformerRef.current || !shapeRef.current) return;
|
if (!transformerRef.current || !shapeRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
transformerRef.current.nodes([shapeRef.current]);
|
transformerRef.current.nodes([shapeRef.current]);
|
||||||
transformerRef.current.getLayer()?.batchDraw();
|
transformerRef.current.getLayer()?.batchDraw();
|
||||||
}, []);
|
}, []);
|
||||||
@ -133,7 +135,9 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
|||||||
* not its width and height. We need to un-scale the width and height before
|
* not its width and height. We need to un-scale the width and height before
|
||||||
* setting the values.
|
* setting the values.
|
||||||
*/
|
*/
|
||||||
if (!shapeRef.current) return;
|
if (!shapeRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const rect = shapeRef.current;
|
const rect = shapeRef.current;
|
||||||
|
|
||||||
@ -313,4 +317,4 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasBoundingBox;
|
export default memo(IAICanvasBoundingBox);
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -150,4 +151,4 @@ const IAICanvasMaskOptions = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasMaskOptions;
|
export default memo(IAICanvasMaskOptions);
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent, memo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaWrench } from 'react-icons/fa';
|
import { FaWrench } from 'react-icons/fa';
|
||||||
@ -163,4 +163,4 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasSettingsButtonPopover;
|
export default memo(IAICanvasSettingsButtonPopover);
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
import { clamp, isEqual } from 'lodash-es';
|
import { clamp, isEqual } from 'lodash-es';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -252,4 +253,4 @@ const IAICanvasToolChooserOptions = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasToolChooserOptions;
|
export default memo(IAICanvasToolChooserOptions);
|
||||||
|
@ -48,6 +48,7 @@ import IAICanvasRedoButton from './IAICanvasRedoButton';
|
|||||||
import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
|
import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
|
||||||
import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions';
|
import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions';
|
||||||
import IAICanvasUndoButton from './IAICanvasUndoButton';
|
import IAICanvasUndoButton from './IAICanvasUndoButton';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
export const selector = createSelector(
|
export const selector = createSelector(
|
||||||
[systemSelector, canvasSelector, isStagingSelector],
|
[systemSelector, canvasSelector, isStagingSelector],
|
||||||
@ -166,7 +167,9 @@ const IAICanvasToolbar = () => {
|
|||||||
|
|
||||||
const handleResetCanvasView = (shouldScaleTo1 = false) => {
|
const handleResetCanvasView = (shouldScaleTo1 = false) => {
|
||||||
const canvasBaseLayer = getCanvasBaseLayer();
|
const canvasBaseLayer = getCanvasBaseLayer();
|
||||||
if (!canvasBaseLayer) return;
|
if (!canvasBaseLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const clientRect = canvasBaseLayer.getClientRect({
|
const clientRect = canvasBaseLayer.getClientRect({
|
||||||
skipTransform: true,
|
skipTransform: true,
|
||||||
});
|
});
|
||||||
@ -309,4 +312,4 @@ const IAICanvasToolbar = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IAICanvasToolbar;
|
export default memo(IAICanvasToolbar);
|
||||||
|
@ -32,13 +32,17 @@ const useCanvasDrag = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
handleDragStart: useCallback(() => {
|
handleDragStart: useCallback(() => {
|
||||||
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) return;
|
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
dispatch(setIsMovingStage(true));
|
dispatch(setIsMovingStage(true));
|
||||||
}, [dispatch, isMovingBoundingBox, isStaging, tool]),
|
}, [dispatch, isMovingBoundingBox, isStaging, tool]),
|
||||||
|
|
||||||
handleDragMove: useCallback(
|
handleDragMove: useCallback(
|
||||||
(e: KonvaEventObject<MouseEvent>) => {
|
(e: KonvaEventObject<MouseEvent>) => {
|
||||||
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) return;
|
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newCoordinates = { x: e.target.x(), y: e.target.y() };
|
const newCoordinates = { x: e.target.x(), y: e.target.y() };
|
||||||
|
|
||||||
@ -48,7 +52,9 @@ const useCanvasDrag = () => {
|
|||||||
),
|
),
|
||||||
|
|
||||||
handleDragEnd: useCallback(() => {
|
handleDragEnd: useCallback(() => {
|
||||||
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) return;
|
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
dispatch(setIsMovingStage(false));
|
dispatch(setIsMovingStage(false));
|
||||||
}, [dispatch, isMovingBoundingBox, isStaging, tool]),
|
}, [dispatch, isMovingBoundingBox, isStaging, tool]),
|
||||||
};
|
};
|
||||||
|
@ -134,7 +134,9 @@ const useInpaintingCanvasHotkeys = () => {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
['space'],
|
['space'],
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
if (e.repeat) return;
|
if (e.repeat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
canvasStage?.container().focus();
|
canvasStage?.container().focus();
|
||||||
|
|
||||||
|
@ -38,7 +38,9 @@ const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
|||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
(e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
||||||
if (!stageRef.current) return;
|
if (!stageRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
stageRef.current.container().focus();
|
stageRef.current.container().focus();
|
||||||
|
|
||||||
@ -54,7 +56,9 @@ const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
|||||||
|
|
||||||
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
||||||
|
|
||||||
if (!scaledCursorPosition) return;
|
if (!scaledCursorPosition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.evt.preventDefault();
|
e.evt.preventDefault();
|
||||||
|
|
||||||
|
@ -41,11 +41,15 @@ const useCanvasMouseMove = (
|
|||||||
const { updateColorUnderCursor } = useColorPicker();
|
const { updateColorUnderCursor } = useColorPicker();
|
||||||
|
|
||||||
return useCallback(() => {
|
return useCallback(() => {
|
||||||
if (!stageRef.current) return;
|
if (!stageRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
||||||
|
|
||||||
if (!scaledCursorPosition) return;
|
if (!scaledCursorPosition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(setCursorPosition(scaledCursorPosition));
|
dispatch(setCursorPosition(scaledCursorPosition));
|
||||||
|
|
||||||
@ -56,7 +60,9 @@ const useCanvasMouseMove = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDrawing || tool === 'move' || isStaging) return;
|
if (!isDrawing || tool === 'move' || isStaging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
didMouseMoveRef.current = true;
|
didMouseMoveRef.current = true;
|
||||||
dispatch(
|
dispatch(
|
||||||
|
@ -47,7 +47,9 @@ const useCanvasMouseUp = (
|
|||||||
if (!didMouseMoveRef.current && isDrawing && stageRef.current) {
|
if (!didMouseMoveRef.current && isDrawing && stageRef.current) {
|
||||||
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
||||||
|
|
||||||
if (!scaledCursorPosition) return;
|
if (!scaledCursorPosition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend the current line.
|
* Extend the current line.
|
||||||
|
@ -35,13 +35,17 @@ const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
|||||||
return useCallback(
|
return useCallback(
|
||||||
(e: KonvaEventObject<WheelEvent>) => {
|
(e: KonvaEventObject<WheelEvent>) => {
|
||||||
// stop default scrolling
|
// stop default scrolling
|
||||||
if (!stageRef.current || isMoveStageKeyHeld) return;
|
if (!stageRef.current || isMoveStageKeyHeld) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.evt.preventDefault();
|
e.evt.preventDefault();
|
||||||
|
|
||||||
const cursorPos = stageRef.current.getPointerPosition();
|
const cursorPos = stageRef.current.getPointerPosition();
|
||||||
|
|
||||||
if (!cursorPos) return;
|
if (!cursorPos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const mousePointTo = {
|
const mousePointTo = {
|
||||||
x: (cursorPos.x - stageRef.current.x()) / stageScale,
|
x: (cursorPos.x - stageRef.current.x()) / stageScale,
|
||||||
|
@ -16,11 +16,15 @@ const useColorPicker = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
updateColorUnderCursor: () => {
|
updateColorUnderCursor: () => {
|
||||||
if (!stage || !canvasBaseLayer) return;
|
if (!stage || !canvasBaseLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const position = stage.getPointerPosition();
|
const position = stage.getPointerPosition();
|
||||||
|
|
||||||
if (!position) return;
|
if (!position) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const pixelRatio = Konva.pixelRatio;
|
const pixelRatio = Konva.pixelRatio;
|
||||||
|
|
||||||
|
@ -397,7 +397,9 @@ export const canvasSlice = createSlice({
|
|||||||
const { tool, layer, brushColor, brushSize, shouldRestrictStrokesToBox } =
|
const { tool, layer, brushColor, brushSize, shouldRestrictStrokesToBox } =
|
||||||
state;
|
state;
|
||||||
|
|
||||||
if (tool === 'move' || tool === 'colorPicker') return;
|
if (tool === 'move' || tool === 'colorPicker') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newStrokeWidth = brushSize / 2;
|
const newStrokeWidth = brushSize / 2;
|
||||||
|
|
||||||
@ -434,14 +436,18 @@ export const canvasSlice = createSlice({
|
|||||||
addPointToCurrentLine: (state, action: PayloadAction<number[]>) => {
|
addPointToCurrentLine: (state, action: PayloadAction<number[]>) => {
|
||||||
const lastLine = state.layerState.objects.findLast(isCanvasAnyLine);
|
const lastLine = state.layerState.objects.findLast(isCanvasAnyLine);
|
||||||
|
|
||||||
if (!lastLine) return;
|
if (!lastLine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lastLine.points.push(...action.payload);
|
lastLine.points.push(...action.payload);
|
||||||
},
|
},
|
||||||
undo: (state) => {
|
undo: (state) => {
|
||||||
const targetState = state.pastLayerStates.pop();
|
const targetState = state.pastLayerStates.pop();
|
||||||
|
|
||||||
if (!targetState) return;
|
if (!targetState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.futureLayerStates.unshift(cloneDeep(state.layerState));
|
state.futureLayerStates.unshift(cloneDeep(state.layerState));
|
||||||
|
|
||||||
@ -454,7 +460,9 @@ export const canvasSlice = createSlice({
|
|||||||
redo: (state) => {
|
redo: (state) => {
|
||||||
const targetState = state.futureLayerStates.shift();
|
const targetState = state.futureLayerStates.shift();
|
||||||
|
|
||||||
if (!targetState) return;
|
if (!targetState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.pastLayerStates.push(cloneDeep(state.layerState));
|
state.pastLayerStates.push(cloneDeep(state.layerState));
|
||||||
|
|
||||||
|
@ -5,7 +5,9 @@ const getScaledCursorPosition = (stage: Stage) => {
|
|||||||
|
|
||||||
const stageTransform = stage.getAbsoluteTransform().copy();
|
const stageTransform = stage.getAbsoluteTransform().copy();
|
||||||
|
|
||||||
if (!pointerPosition || !stageTransform) return;
|
if (!pointerPosition || !stageTransform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const scaledCursorPosition = stageTransform.invert().point(pointerPosition);
|
const scaledCursorPosition = stageTransform.invert().point(pointerPosition);
|
||||||
|
|
||||||
|
@ -91,8 +91,8 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
>
|
>
|
||||||
<Flex sx={{ gap: 2, alignItems: 'center' }}>
|
<Flex sx={{ gap: 2, alignItems: 'center' }}>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
tooltip={'Toggle this ControlNet'}
|
tooltip="Toggle this ControlNet"
|
||||||
aria-label={'Toggle this ControlNet'}
|
aria-label="Toggle this ControlNet"
|
||||||
isChecked={isEnabled}
|
isChecked={isEnabled}
|
||||||
onChange={handleToggleIsEnabled}
|
onChange={handleToggleIsEnabled}
|
||||||
/>
|
/>
|
||||||
|
@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
import { isControlNetEnabledToggled } from 'features/controlNet/store/controlNetSlice';
|
import { isControlNetEnabledToggled } from 'features/controlNet/store/controlNetSlice';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -36,4 +36,4 @@ const ParamControlNetFeatureToggle = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ParamControlNetFeatureToggle;
|
export default memo(ParamControlNetFeatureToggle);
|
||||||
|
@ -23,7 +23,7 @@ const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
|
|||||||
return (
|
return (
|
||||||
<IAISlider
|
<IAISlider
|
||||||
isDisabled={!isEnabled}
|
isDisabled={!isEnabled}
|
||||||
label={'Weight'}
|
label="Weight"
|
||||||
value={weight}
|
value={weight}
|
||||||
onChange={handleWeightChanged}
|
onChange={handleWeightChanged}
|
||||||
min={0}
|
min={0}
|
||||||
|
@ -8,6 +8,7 @@ import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial
|
|||||||
import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled';
|
import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled';
|
||||||
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
|
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
|
||||||
import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -40,4 +41,4 @@ const ParamDynamicPromptsCollapse = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ParamDynamicPromptsCollapse;
|
export default memo(ParamDynamicPromptsCollapse);
|
||||||
|
@ -3,7 +3,7 @@ import { stateSelector } from 'app/store/store';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { combinatorialToggled } from '../store/dynamicPromptsSlice';
|
import { combinatorialToggled } from '../store/dynamicPromptsSlice';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
@ -34,4 +34,4 @@ const ParamDynamicPromptsCombinatorial = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ParamDynamicPromptsCombinatorial;
|
export default memo(ParamDynamicPromptsCombinatorial);
|
||||||
|
@ -3,7 +3,7 @@ import { stateSelector } from 'app/store/store';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { isEnabledToggled } from '../store/dynamicPromptsSlice';
|
import { isEnabledToggled } from '../store/dynamicPromptsSlice';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
@ -33,4 +33,4 @@ const ParamDynamicPromptsToggle = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ParamDynamicPromptsToggle;
|
export default memo(ParamDynamicPromptsToggle);
|
||||||
|
@ -3,7 +3,7 @@ import { stateSelector } from 'app/store/store';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
maxPromptsChanged,
|
maxPromptsChanged,
|
||||||
maxPromptsReset,
|
maxPromptsReset,
|
||||||
@ -60,4 +60,4 @@ const ParamDynamicPromptsMaxPrompts = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ParamDynamicPromptsMaxPrompts;
|
export default memo(ParamDynamicPromptsMaxPrompts);
|
||||||
|
@ -13,7 +13,7 @@ import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSe
|
|||||||
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
|
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
|
||||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||||
import { forEach } from 'lodash-es';
|
import { forEach } from 'lodash-es';
|
||||||
import { PropsWithChildren, useCallback, useMemo, useRef } from 'react';
|
import { PropsWithChildren, memo, useCallback, useMemo, useRef } from 'react';
|
||||||
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
||||||
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ const ParamEmbeddingPopover = (props: Props) => {
|
|||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder={'Add Embedding'}
|
placeholder="Add Embedding"
|
||||||
value={null}
|
value={null}
|
||||||
data={data}
|
data={data}
|
||||||
nothingFound="No matching Embeddings"
|
nothingFound="No matching Embeddings"
|
||||||
@ -140,4 +140,4 @@ const ParamEmbeddingPopover = (props: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ParamEmbeddingPopover;
|
export default memo(ParamEmbeddingPopover);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Badge, Flex } from '@chakra-ui/react';
|
import { Badge, Flex } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const AutoAddIcon = () => {
|
const AutoAddIcon = () => {
|
||||||
return (
|
return (
|
||||||
@ -20,4 +21,4 @@ const AutoAddIcon = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AutoAddIcon;
|
export default memo(AutoAddIcon);
|
||||||
|
@ -6,7 +6,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|||||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||||
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
|
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
|
||||||
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
@ -66,7 +66,7 @@ const BoardAutoAddSelect = () => {
|
|||||||
label="Auto-Add Board"
|
label="Auto-Add Board"
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder={'Select a Board'}
|
placeholder="Select a Board"
|
||||||
value={autoAddBoardId}
|
value={autoAddBoardId}
|
||||||
data={boards}
|
data={boards}
|
||||||
nothingFound="No matching Boards"
|
nothingFound="No matching Boards"
|
||||||
@ -81,4 +81,4 @@ const BoardAutoAddSelect = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BoardAutoAddSelect;
|
export default memo(BoardAutoAddSelect);
|
||||||
|
@ -2,8 +2,12 @@ import { MenuGroup, MenuItem, MenuList } from '@chakra-ui/react';
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
|
import {
|
||||||
|
IAIContextMenu,
|
||||||
|
IAIContextMenuProps,
|
||||||
|
} from 'common/components/IAIContextMenu';
|
||||||
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { BoardId } from 'features/gallery/store/types';
|
||||||
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
||||||
import { FaPlus } from 'react-icons/fa';
|
import { FaPlus } from 'react-icons/fa';
|
||||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||||
@ -11,80 +15,80 @@ import { BoardDTO } from 'services/api/types';
|
|||||||
import { menuListMotionProps } from 'theme/components/menu';
|
import { menuListMotionProps } from 'theme/components/menu';
|
||||||
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
|
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
|
||||||
import NoBoardContextMenuItems from './NoBoardContextMenuItems';
|
import NoBoardContextMenuItems from './NoBoardContextMenuItems';
|
||||||
import { BoardId } from 'features/gallery/store/types';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
board?: BoardDTO;
|
board?: BoardDTO;
|
||||||
board_id: BoardId;
|
board_id: BoardId;
|
||||||
children: ContextMenuProps<HTMLDivElement>['children'];
|
children: IAIContextMenuProps<HTMLDivElement>['children'];
|
||||||
setBoardToDelete?: (board?: BoardDTO) => void;
|
setBoardToDelete?: (board?: BoardDTO) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BoardContextMenu = memo(
|
const BoardContextMenu = ({
|
||||||
({ board, board_id, setBoardToDelete, children }: Props) => {
|
board,
|
||||||
const dispatch = useAppDispatch();
|
board_id,
|
||||||
|
setBoardToDelete,
|
||||||
|
children,
|
||||||
|
}: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const selector = useMemo(
|
const selector = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createSelector(stateSelector, ({ gallery, system }) => {
|
createSelector(stateSelector, ({ gallery, system }) => {
|
||||||
const isAutoAdd = gallery.autoAddBoardId === board_id;
|
const isAutoAdd = gallery.autoAddBoardId === board_id;
|
||||||
const isProcessing = system.isProcessing;
|
const isProcessing = system.isProcessing;
|
||||||
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
|
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
|
||||||
return { isAutoAdd, isProcessing, autoAssignBoardOnClick };
|
return { isAutoAdd, isProcessing, autoAssignBoardOnClick };
|
||||||
}),
|
}),
|
||||||
[board_id]
|
[board_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isAutoAdd, isProcessing, autoAssignBoardOnClick } =
|
const { isAutoAdd, isProcessing, autoAssignBoardOnClick } =
|
||||||
useAppSelector(selector);
|
useAppSelector(selector);
|
||||||
const boardName = useBoardName(board_id);
|
const boardName = useBoardName(board_id);
|
||||||
|
|
||||||
const handleSetAutoAdd = useCallback(() => {
|
const handleSetAutoAdd = useCallback(() => {
|
||||||
dispatch(autoAddBoardIdChanged(board_id));
|
dispatch(autoAddBoardIdChanged(board_id));
|
||||||
}, [board_id, dispatch]);
|
}, [board_id, dispatch]);
|
||||||
|
|
||||||
const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
|
const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu<HTMLDivElement>
|
<IAIContextMenu<HTMLDivElement>
|
||||||
menuProps={{ size: 'sm', isLazy: true }}
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
menuButtonProps={{
|
menuButtonProps={{
|
||||||
bg: 'transparent',
|
bg: 'transparent',
|
||||||
_hover: { bg: 'transparent' },
|
_hover: { bg: 'transparent' },
|
||||||
}}
|
}}
|
||||||
renderMenu={() => (
|
renderMenu={() => (
|
||||||
<MenuList
|
<MenuList
|
||||||
sx={{ visibility: 'visible !important' }}
|
sx={{ visibility: 'visible !important' }}
|
||||||
motionProps={menuListMotionProps}
|
motionProps={menuListMotionProps}
|
||||||
onContextMenu={skipEvent}
|
onContextMenu={skipEvent}
|
||||||
>
|
>
|
||||||
<MenuGroup title={boardName}>
|
<MenuGroup title={boardName}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<FaPlus />}
|
icon={<FaPlus />}
|
||||||
isDisabled={isAutoAdd || isProcessing || autoAssignBoardOnClick}
|
isDisabled={isAutoAdd || isProcessing || autoAssignBoardOnClick}
|
||||||
onClick={handleSetAutoAdd}
|
onClick={handleSetAutoAdd}
|
||||||
>
|
>
|
||||||
Auto-add to this Board
|
Auto-add to this Board
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{!board && <NoBoardContextMenuItems />}
|
{!board && <NoBoardContextMenuItems />}
|
||||||
{board && (
|
{board && (
|
||||||
<GalleryBoardContextMenuItems
|
<GalleryBoardContextMenuItems
|
||||||
board={board}
|
board={board}
|
||||||
setBoardToDelete={setBoardToDelete}
|
setBoardToDelete={setBoardToDelete}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</MenuGroup>
|
</MenuGroup>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ContextMenu>
|
</IAIContextMenu>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
);
|
|
||||||
|
|
||||||
BoardContextMenu.displayName = 'HoverableBoard';
|
export default memo(BoardContextMenu);
|
||||||
|
|
||||||
export default BoardContextMenu;
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { FaPlus } from 'react-icons/fa';
|
import { FaPlus } from 'react-icons/fa';
|
||||||
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
||||||
|
|
||||||
@ -24,4 +24,4 @@ const AddBoardButton = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddBoardButton;
|
export default memo(AddBoardButton);
|
||||||
|
@ -41,7 +41,7 @@ const BoardsList = (props: Props) => {
|
|||||||
<>
|
<>
|
||||||
<Collapse in={isOpen} animateOpacity>
|
<Collapse in={isOpen} animateOpacity>
|
||||||
<Flex
|
<Flex
|
||||||
layerStyle={'first'}
|
layerStyle="first"
|
||||||
sx={{
|
sx={{
|
||||||
flexDir: 'column',
|
flexDir: 'column',
|
||||||
gap: 2,
|
gap: 2,
|
||||||
|
@ -39,187 +39,188 @@ interface GalleryBoardProps {
|
|||||||
setBoardToDelete: (board?: BoardDTO) => void;
|
setBoardToDelete: (board?: BoardDTO) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GalleryBoard = memo(
|
const GalleryBoard = ({
|
||||||
({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => {
|
board,
|
||||||
const dispatch = useAppDispatch();
|
isSelected,
|
||||||
const selector = useMemo(
|
setBoardToDelete,
|
||||||
() =>
|
}: GalleryBoardProps) => {
|
||||||
createSelector(
|
const dispatch = useAppDispatch();
|
||||||
stateSelector,
|
const selector = useMemo(
|
||||||
({ gallery, system }) => {
|
() =>
|
||||||
const isSelectedForAutoAdd =
|
createSelector(
|
||||||
board.board_id === gallery.autoAddBoardId;
|
stateSelector,
|
||||||
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
|
({ gallery, system }) => {
|
||||||
const isProcessing = system.isProcessing;
|
const isSelectedForAutoAdd =
|
||||||
|
board.board_id === gallery.autoAddBoardId;
|
||||||
|
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
|
||||||
|
const isProcessing = system.isProcessing;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSelectedForAutoAdd,
|
isSelectedForAutoAdd,
|
||||||
autoAssignBoardOnClick,
|
autoAssignBoardOnClick,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
),
|
),
|
||||||
[board.board_id]
|
[board.board_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isSelectedForAutoAdd, autoAssignBoardOnClick, isProcessing } =
|
const { isSelectedForAutoAdd, autoAssignBoardOnClick, isProcessing } =
|
||||||
useAppSelector(selector);
|
useAppSelector(selector);
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const handleMouseOver = useCallback(() => {
|
const handleMouseOver = useCallback(() => {
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
}, []);
|
}, []);
|
||||||
const handleMouseOut = useCallback(() => {
|
const handleMouseOut = useCallback(() => {
|
||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { data: imagesTotal } = useGetBoardImagesTotalQuery(board.board_id);
|
const { data: imagesTotal } = useGetBoardImagesTotalQuery(board.board_id);
|
||||||
const { data: assetsTotal } = useGetBoardAssetsTotalQuery(board.board_id);
|
const { data: assetsTotal } = useGetBoardAssetsTotalQuery(board.board_id);
|
||||||
const tooltip = useMemo(() => {
|
const tooltip = useMemo(() => {
|
||||||
if (!imagesTotal || !assetsTotal) {
|
if (!imagesTotal || !assetsTotal) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
}
|
||||||
|
return `${imagesTotal} image${
|
||||||
|
imagesTotal > 1 ? 's' : ''
|
||||||
|
}, ${assetsTotal} asset${assetsTotal > 1 ? 's' : ''}`;
|
||||||
|
}, [assetsTotal, imagesTotal]);
|
||||||
|
|
||||||
|
const { currentData: coverImage } = useGetImageDTOQuery(
|
||||||
|
board.cover_image_name ?? skipToken
|
||||||
|
);
|
||||||
|
|
||||||
|
const { board_name, board_id } = board;
|
||||||
|
const [localBoardName, setLocalBoardName] = useState(board_name);
|
||||||
|
|
||||||
|
const handleSelectBoard = useCallback(() => {
|
||||||
|
dispatch(boardIdSelected(board_id));
|
||||||
|
if (autoAssignBoardOnClick && !isProcessing) {
|
||||||
|
dispatch(autoAddBoardIdChanged(board_id));
|
||||||
|
}
|
||||||
|
}, [board_id, autoAssignBoardOnClick, isProcessing, dispatch]);
|
||||||
|
|
||||||
|
const [updateBoard, { isLoading: isUpdateBoardLoading }] =
|
||||||
|
useUpdateBoardMutation();
|
||||||
|
|
||||||
|
const droppableData: AddToBoardDropData = useMemo(
|
||||||
|
() => ({
|
||||||
|
id: board_id,
|
||||||
|
actionType: 'ADD_TO_BOARD',
|
||||||
|
context: { boardId: board_id },
|
||||||
|
}),
|
||||||
|
[board_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(
|
||||||
|
async (newBoardName: string) => {
|
||||||
|
// empty strings are not allowed
|
||||||
|
if (!newBoardName.trim()) {
|
||||||
|
setLocalBoardName(board_name);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return `${imagesTotal} image${
|
|
||||||
imagesTotal > 1 ? 's' : ''
|
|
||||||
}, ${assetsTotal} asset${assetsTotal > 1 ? 's' : ''}`;
|
|
||||||
}, [assetsTotal, imagesTotal]);
|
|
||||||
|
|
||||||
const { currentData: coverImage } = useGetImageDTOQuery(
|
// don't updated the board name if it hasn't changed
|
||||||
board.cover_image_name ?? skipToken
|
if (newBoardName === board_name) {
|
||||||
);
|
return;
|
||||||
|
|
||||||
const { board_name, board_id } = board;
|
|
||||||
const [localBoardName, setLocalBoardName] = useState(board_name);
|
|
||||||
|
|
||||||
const handleSelectBoard = useCallback(() => {
|
|
||||||
dispatch(boardIdSelected(board_id));
|
|
||||||
if (autoAssignBoardOnClick && !isProcessing) {
|
|
||||||
dispatch(autoAddBoardIdChanged(board_id));
|
|
||||||
}
|
}
|
||||||
}, [board_id, autoAssignBoardOnClick, isProcessing, dispatch]);
|
|
||||||
|
|
||||||
const [updateBoard, { isLoading: isUpdateBoardLoading }] =
|
try {
|
||||||
useUpdateBoardMutation();
|
const { board_name } = await updateBoard({
|
||||||
|
board_id,
|
||||||
|
changes: { board_name: newBoardName },
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
const droppableData: AddToBoardDropData = useMemo(
|
// update local state
|
||||||
() => ({
|
setLocalBoardName(board_name);
|
||||||
id: board_id,
|
} catch {
|
||||||
actionType: 'ADD_TO_BOARD',
|
// revert on error
|
||||||
context: { boardId: board_id },
|
setLocalBoardName(board_name);
|
||||||
}),
|
}
|
||||||
[board_id]
|
},
|
||||||
);
|
[board_id, board_name, updateBoard]
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleChange = useCallback((newBoardName: string) => {
|
||||||
async (newBoardName: string) => {
|
setLocalBoardName(newBoardName);
|
||||||
// empty strings are not allowed
|
}, []);
|
||||||
if (!newBoardName.trim()) {
|
|
||||||
setLocalBoardName(board_name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't updated the board name if it hasn't changed
|
return (
|
||||||
if (newBoardName === board_name) {
|
<Box sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}>
|
||||||
return;
|
<Flex
|
||||||
}
|
onMouseOver={handleMouseOver}
|
||||||
|
onMouseOut={handleMouseOut}
|
||||||
try {
|
sx={{
|
||||||
const { board_name } = await updateBoard({
|
position: 'relative',
|
||||||
board_id,
|
justifyContent: 'center',
|
||||||
changes: { board_name: newBoardName },
|
alignItems: 'center',
|
||||||
}).unwrap();
|
aspectRatio: '1/1',
|
||||||
|
w: 'full',
|
||||||
// update local state
|
h: 'full',
|
||||||
setLocalBoardName(board_name);
|
}}
|
||||||
} catch {
|
|
||||||
// revert on error
|
|
||||||
setLocalBoardName(board_name);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[board_id, board_name, updateBoard]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleChange = useCallback((newBoardName: string) => {
|
|
||||||
setLocalBoardName(newBoardName);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}
|
|
||||||
>
|
>
|
||||||
<Flex
|
<BoardContextMenu
|
||||||
onMouseOver={handleMouseOver}
|
board={board}
|
||||||
onMouseOut={handleMouseOut}
|
board_id={board_id}
|
||||||
sx={{
|
setBoardToDelete={setBoardToDelete}
|
||||||
position: 'relative',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
aspectRatio: '1/1',
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<BoardContextMenu
|
{(ref) => (
|
||||||
board={board}
|
<Tooltip label={tooltip} openDelay={1000} hasArrow>
|
||||||
board_id={board_id}
|
<Flex
|
||||||
setBoardToDelete={setBoardToDelete}
|
ref={ref}
|
||||||
>
|
onClick={handleSelectBoard}
|
||||||
{(ref) => (
|
sx={{
|
||||||
<Tooltip label={tooltip} openDelay={1000} hasArrow>
|
w: 'full',
|
||||||
<Flex
|
h: 'full',
|
||||||
ref={ref}
|
position: 'relative',
|
||||||
onClick={handleSelectBoard}
|
justifyContent: 'center',
|
||||||
sx={{
|
alignItems: 'center',
|
||||||
w: 'full',
|
borderRadius: 'base',
|
||||||
h: 'full',
|
cursor: 'pointer',
|
||||||
position: 'relative',
|
bg: 'base.200',
|
||||||
justifyContent: 'center',
|
_dark: {
|
||||||
alignItems: 'center',
|
bg: 'base.800',
|
||||||
borderRadius: 'base',
|
},
|
||||||
cursor: 'pointer',
|
}}
|
||||||
bg: 'base.200',
|
>
|
||||||
_dark: {
|
{coverImage?.thumbnail_url ? (
|
||||||
bg: 'base.800',
|
<Image
|
||||||
},
|
src={coverImage?.thumbnail_url}
|
||||||
}}
|
draggable={false}
|
||||||
>
|
sx={{
|
||||||
{coverImage?.thumbnail_url ? (
|
objectFit: 'cover',
|
||||||
<Image
|
w: 'full',
|
||||||
src={coverImage?.thumbnail_url}
|
h: 'full',
|
||||||
draggable={false}
|
maxH: 'full',
|
||||||
|
borderRadius: 'base',
|
||||||
|
borderBottomRadius: 'lg',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
w: 'full',
|
||||||
|
h: 'full',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
boxSize={12}
|
||||||
|
as={FaUser}
|
||||||
sx={{
|
sx={{
|
||||||
objectFit: 'cover',
|
mt: -6,
|
||||||
w: 'full',
|
opacity: 0.7,
|
||||||
h: 'full',
|
color: 'base.500',
|
||||||
maxH: 'full',
|
_dark: {
|
||||||
borderRadius: 'base',
|
color: 'base.500',
|
||||||
borderBottomRadius: 'lg',
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
</Flex>
|
||||||
<Flex
|
)}
|
||||||
sx={{
|
{/* <Flex
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
boxSize={12}
|
|
||||||
as={FaUser}
|
|
||||||
sx={{
|
|
||||||
mt: -6,
|
|
||||||
opacity: 0.7,
|
|
||||||
color: 'base.500',
|
|
||||||
_dark: {
|
|
||||||
color: 'base.500',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
{/* <Flex
|
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
insetInlineEnd: 0,
|
insetInlineEnd: 0,
|
||||||
@ -231,80 +232,77 @@ const GalleryBoard = memo(
|
|||||||
{totalImages}/{totalAssets}
|
{totalImages}/{totalAssets}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Flex> */}
|
</Flex> */}
|
||||||
{isSelectedForAutoAdd && <AutoAddIcon />}
|
{isSelectedForAutoAdd && <AutoAddIcon />}
|
||||||
<SelectionOverlay
|
<SelectionOverlay
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
isHovered={isHovered}
|
isHovered={isHovered}
|
||||||
/>
|
/>
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
p: 1,
|
p: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
w: 'full',
|
w: 'full',
|
||||||
maxW: 'full',
|
maxW: 'full',
|
||||||
borderBottomRadius: 'base',
|
borderBottomRadius: 'base',
|
||||||
bg: isSelected ? 'accent.400' : 'base.500',
|
bg: isSelected ? 'accent.400' : 'base.500',
|
||||||
|
color: isSelected ? 'base.50' : 'base.100',
|
||||||
|
_dark: {
|
||||||
|
bg: isSelected ? 'accent.500' : 'base.600',
|
||||||
color: isSelected ? 'base.50' : 'base.100',
|
color: isSelected ? 'base.50' : 'base.100',
|
||||||
_dark: {
|
},
|
||||||
bg: isSelected ? 'accent.500' : 'base.600',
|
lineHeight: 'short',
|
||||||
color: isSelected ? 'base.50' : 'base.100',
|
fontSize: 'xs',
|
||||||
},
|
}}
|
||||||
lineHeight: 'short',
|
>
|
||||||
fontSize: 'xs',
|
<Editable
|
||||||
|
value={localBoardName}
|
||||||
|
isDisabled={isUpdateBoardLoading}
|
||||||
|
submitOnBlur={true}
|
||||||
|
onChange={handleChange}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
sx={{
|
||||||
|
w: 'full',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Editable
|
<EditablePreview
|
||||||
value={localBoardName}
|
|
||||||
isDisabled={isUpdateBoardLoading}
|
|
||||||
submitOnBlur={true}
|
|
||||||
onChange={handleChange}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
sx={{
|
sx={{
|
||||||
w: 'full',
|
p: 0,
|
||||||
|
fontWeight: isSelected ? 700 : 500,
|
||||||
|
textAlign: 'center',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
}}
|
}}
|
||||||
>
|
noOfLines={1}
|
||||||
<EditablePreview
|
/>
|
||||||
sx={{
|
<EditableInput
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
_focusVisible: {
|
||||||
p: 0,
|
p: 0,
|
||||||
fontWeight: isSelected ? 700 : 500,
|
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
overflow: 'hidden',
|
// get rid of the edit border
|
||||||
textOverflow: 'ellipsis',
|
boxShadow: 'none',
|
||||||
}}
|
},
|
||||||
noOfLines={1}
|
}}
|
||||||
/>
|
/>
|
||||||
<EditableInput
|
</Editable>
|
||||||
sx={{
|
|
||||||
p: 0,
|
|
||||||
_focusVisible: {
|
|
||||||
p: 0,
|
|
||||||
textAlign: 'center',
|
|
||||||
// get rid of the edit border
|
|
||||||
boxShadow: 'none',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Editable>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<IAIDroppable
|
|
||||||
data={droppableData}
|
|
||||||
dropLabel={<Text fontSize="md">Move</Text>}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</BoardContextMenu>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
GalleryBoard.displayName = 'HoverableBoard';
|
<IAIDroppable
|
||||||
|
data={droppableData}
|
||||||
|
dropLabel={<Text fontSize="md">Move</Text>}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</BoardContextMenu>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default GalleryBoard;
|
export default memo(GalleryBoard);
|
||||||
|
@ -3,7 +3,7 @@ import IAIDroppable from 'common/components/IAIDroppable';
|
|||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import { TypesafeDroppableData } from 'features/dnd/types';
|
import { TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { BoardId } from 'features/gallery/store/types';
|
import { BoardId } from 'features/gallery/store/types';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode, memo } from 'react';
|
||||||
import BoardContextMenu from '../BoardContextMenu';
|
import BoardContextMenu from '../BoardContextMenu';
|
||||||
|
|
||||||
type GenericBoardProps = {
|
type GenericBoardProps = {
|
||||||
@ -105,4 +105,4 @@ const GenericBoard = (props: GenericBoardProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GenericBoard;
|
export default memo(GenericBoard);
|
||||||
|
@ -156,4 +156,4 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
|
|||||||
|
|
||||||
NoBoardBoard.displayName = 'HoverableBoard';
|
NoBoardBoard.displayName = 'HoverableBoard';
|
||||||
|
|
||||||
export default NoBoardBoard;
|
export default memo(NoBoardBoard);
|
||||||
|
@ -26,7 +26,7 @@ import {
|
|||||||
setShouldShowImageDetails,
|
setShouldShowImageDetails,
|
||||||
setShouldShowProgressInViewer,
|
setShouldShowProgressInViewer,
|
||||||
} from 'features/ui/store/uiSlice';
|
} from 'features/ui/store/uiSlice';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -323,4 +323,4 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CurrentImageButtons;
|
export default memo(CurrentImageButtons);
|
||||||
|
@ -2,6 +2,7 @@ import { Flex } from '@chakra-ui/react';
|
|||||||
|
|
||||||
import CurrentImageButtons from './CurrentImageButtons';
|
import CurrentImageButtons from './CurrentImageButtons';
|
||||||
import CurrentImagePreview from './CurrentImagePreview';
|
import CurrentImagePreview from './CurrentImagePreview';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const CurrentImageDisplay = () => {
|
const CurrentImageDisplay = () => {
|
||||||
return (
|
return (
|
||||||
@ -22,4 +23,4 @@ const CurrentImageDisplay = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CurrentImageDisplay;
|
export default memo(CurrentImageDisplay);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
import { FaEyeSlash } from 'react-icons/fa';
|
import { FaEyeSlash } from 'react-icons/fa';
|
||||||
|
|
||||||
const CurrentImageHidden = () => {
|
const CurrentImageHidden = () => {
|
||||||
@ -18,4 +19,4 @@ const CurrentImageHidden = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CurrentImageHidden;
|
export default memo(CurrentImageHidden);
|
||||||
|
@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
|
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
|
||||||
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
||||||
|
|
||||||
@ -41,4 +42,4 @@ const GalleryPinButton = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GalleryPinButton;
|
export default memo(GalleryPinButton);
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
setGalleryImageMinimumWidth,
|
setGalleryImageMinimumWidth,
|
||||||
shouldAutoSwitchChanged,
|
shouldAutoSwitchChanged,
|
||||||
} from 'features/gallery/store/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { ChangeEvent, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaWrench } from 'react-icons/fa';
|
import { FaWrench } from 'react-icons/fa';
|
||||||
import BoardAutoAddSelect from './Boards/BoardAutoAddSelect';
|
import BoardAutoAddSelect from './Boards/BoardAutoAddSelect';
|
||||||
@ -101,4 +101,4 @@ const GallerySettingsPopover = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GallerySettingsPopover;
|
export default memo(GallerySettingsPopover);
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
isModalOpenChanged,
|
isModalOpenChanged,
|
||||||
} from 'features/changeBoardModal/store/slice';
|
} from 'features/changeBoardModal/store/slice';
|
||||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { FaFolder, FaTrash } from 'react-icons/fa';
|
import { FaFolder, FaTrash } from 'react-icons/fa';
|
||||||
import { MdStar, MdStarBorder } from 'react-icons/md';
|
import { MdStar, MdStarBorder } from 'react-icons/md';
|
||||||
import {
|
import {
|
||||||
@ -74,4 +74,4 @@ const MultipleSelectionMenuItems = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MultipleSelectionMenuItems;
|
export default memo(MultipleSelectionMenuItems);
|
||||||
|
@ -136,11 +136,15 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
}, [copyImageToClipboard, imageDTO.image_url]);
|
}, [copyImageToClipboard, imageDTO.image_url]);
|
||||||
|
|
||||||
const handleStarImage = useCallback(() => {
|
const handleStarImage = useCallback(() => {
|
||||||
if (imageDTO) starImages({ imageDTOs: [imageDTO] });
|
if (imageDTO) {
|
||||||
|
starImages({ imageDTOs: [imageDTO] });
|
||||||
|
}
|
||||||
}, [starImages, imageDTO]);
|
}, [starImages, imageDTO]);
|
||||||
|
|
||||||
const handleUnstarImage = useCallback(() => {
|
const handleUnstarImage = useCallback(() => {
|
||||||
if (imageDTO) unstarImages({ imageDTOs: [imageDTO] });
|
if (imageDTO) {
|
||||||
|
unstarImages({ imageDTOs: [imageDTO] });
|
||||||
|
}
|
||||||
}, [unstarImages, imageDTO]);
|
}, [unstarImages, imageDTO]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Flex, Spinner, SpinnerProps } from '@chakra-ui/react';
|
import { Flex, Spinner, SpinnerProps } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
type ImageFallbackSpinnerProps = SpinnerProps;
|
type ImageFallbackSpinnerProps = SpinnerProps;
|
||||||
|
|
||||||
@ -23,4 +24,4 @@ const ImageFallbackSpinner = (props: ImageFallbackSpinnerProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ImageFallbackSpinner;
|
export default memo(ImageFallbackSpinner);
|
||||||
|
@ -88,8 +88,12 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const starIcon = useMemo(() => {
|
const starIcon = useMemo(() => {
|
||||||
if (imageDTO?.starred) return <MdStar size="20" />;
|
if (imageDTO?.starred) {
|
||||||
if (!imageDTO?.starred && isHovered) return <MdStarBorder size="20" />;
|
return <MdStar size="20" />;
|
||||||
|
}
|
||||||
|
if (!imageDTO?.starred && isHovered) {
|
||||||
|
return <MdStarBorder size="20" />;
|
||||||
|
}
|
||||||
}, [imageDTO?.starred, isHovered]);
|
}, [imageDTO?.starred, isHovered]);
|
||||||
|
|
||||||
if (!imageDTO) {
|
if (!imageDTO) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, FlexProps, forwardRef } from '@chakra-ui/react';
|
import { Box, FlexProps, forwardRef } from '@chakra-ui/react';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren, memo } from 'react';
|
||||||
|
|
||||||
type ItemContainerProps = PropsWithChildren & FlexProps;
|
type ItemContainerProps = PropsWithChildren & FlexProps;
|
||||||
const ItemContainer = forwardRef((props: ItemContainerProps, ref) => (
|
const ItemContainer = forwardRef((props: ItemContainerProps, ref) => (
|
||||||
@ -8,4 +8,4 @@ const ItemContainer = forwardRef((props: ItemContainerProps, ref) => (
|
|||||||
</Box>
|
</Box>
|
||||||
));
|
));
|
||||||
|
|
||||||
export default ItemContainer;
|
export default memo(ItemContainer);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FlexProps, Grid, forwardRef } from '@chakra-ui/react';
|
import { FlexProps, Grid, forwardRef } from '@chakra-ui/react';
|
||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren, memo } from 'react';
|
||||||
|
|
||||||
type ListContainerProps = PropsWithChildren & FlexProps;
|
type ListContainerProps = PropsWithChildren & FlexProps;
|
||||||
const ListContainer = forwardRef((props: ListContainerProps, ref) => {
|
const ListContainer = forwardRef((props: ListContainerProps, ref) => {
|
||||||
@ -23,4 +23,4 @@ const ListContainer = forwardRef((props: ListContainerProps, ref) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ListContainer;
|
export default memo(ListContainer);
|
||||||
|
@ -1,34 +1,35 @@
|
|||||||
import { Box, Flex, IconButton, Tooltip } from '@chakra-ui/react';
|
import { Box, Flex, IconButton, Tooltip } from '@chakra-ui/react';
|
||||||
|
import { isString } from 'lodash-es';
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { FaCopy, FaSave } from 'react-icons/fa';
|
import { FaCopy, FaSave } from 'react-icons/fa';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label: string;
|
label: string;
|
||||||
jsonObject: object;
|
data: object | string;
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ImageMetadataJSON = (props: Props) => {
|
const DataViewer = (props: Props) => {
|
||||||
const { label, jsonObject, fileName } = props;
|
const { label, data, fileName } = props;
|
||||||
const jsonString = useMemo(
|
const dataString = useMemo(
|
||||||
() => JSON.stringify(jsonObject, null, 2),
|
() => (isString(data) ? data : JSON.stringify(data, null, 2)),
|
||||||
[jsonObject]
|
[data]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useCallback(() => {
|
||||||
navigator.clipboard.writeText(jsonString);
|
navigator.clipboard.writeText(dataString);
|
||||||
}, [jsonString]);
|
}, [dataString]);
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
const handleSave = useCallback(() => {
|
||||||
const blob = new Blob([jsonString]);
|
const blob = new Blob([dataString]);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = URL.createObjectURL(blob);
|
a.href = URL.createObjectURL(blob);
|
||||||
a.download = `${fileName || label}.json`;
|
a.download = `${fileName || label}.json`;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
}, [jsonString, label, fileName]);
|
}, [dataString, label, fileName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -65,7 +66,7 @@ const ImageMetadataJSON = (props: Props) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<pre>{jsonString}</pre>
|
<pre>{dataString}</pre>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
</Box>
|
</Box>
|
||||||
<Flex sx={{ position: 'absolute', top: 0, insetInlineEnd: 0, p: 2 }}>
|
<Flex sx={{ position: 'absolute', top: 0, insetInlineEnd: 0, p: 2 }}>
|
||||||
@ -92,4 +93,4 @@ const ImageMetadataJSON = (props: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ImageMetadataJSON;
|
export default memo(DataViewer);
|
@ -1,5 +1,5 @@
|
|||||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { UnsafeImageMetadata } from 'services/api/types';
|
import { UnsafeImageMetadata } from 'services/api/types';
|
||||||
import ImageMetadataItem from './ImageMetadataItem';
|
import ImageMetadataItem from './ImageMetadataItem';
|
||||||
|
|
||||||
@ -206,4 +206,4 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ImageMetadataActions;
|
export default memo(ImageMetadataActions);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||||
import { Flex, IconButton, Link, Text, Tooltip } from '@chakra-ui/react';
|
import { Flex, IconButton, Link, Text, Tooltip } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaCopy } from 'react-icons/fa';
|
import { FaCopy } from 'react-icons/fa';
|
||||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||||
@ -74,4 +75,4 @@ const ImageMetadataItem = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ImageMetadataItem;
|
export default memo(ImageMetadataItem);
|
||||||
|
@ -16,7 +16,7 @@ import { useGetImageMetadataQuery } from 'services/api/endpoints/images';
|
|||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO } from 'services/api/types';
|
||||||
import { useDebounce } from 'use-debounce';
|
import { useDebounce } from 'use-debounce';
|
||||||
import ImageMetadataActions from './ImageMetadataActions';
|
import ImageMetadataActions from './ImageMetadataActions';
|
||||||
import ImageMetadataJSON from './ImageMetadataJSON';
|
import DataViewer from './DataViewer';
|
||||||
|
|
||||||
type ImageMetadataViewerProps = {
|
type ImageMetadataViewerProps = {
|
||||||
image: ImageDTO;
|
image: ImageDTO;
|
||||||
@ -79,21 +79,21 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
|||||||
<TabPanels>
|
<TabPanels>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
{metadata ? (
|
{metadata ? (
|
||||||
<ImageMetadataJSON jsonObject={metadata} label="Core Metadata" />
|
<DataViewer data={metadata} label="Core Metadata" />
|
||||||
) : (
|
) : (
|
||||||
<IAINoContentFallback label="No core metadata found" />
|
<IAINoContentFallback label="No core metadata found" />
|
||||||
)}
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
{image ? (
|
{image ? (
|
||||||
<ImageMetadataJSON jsonObject={image} label="Image Details" />
|
<DataViewer data={image} label="Image Details" />
|
||||||
) : (
|
) : (
|
||||||
<IAINoContentFallback label="No image details found" />
|
<IAINoContentFallback label="No image details found" />
|
||||||
)}
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
{graph ? (
|
{graph ? (
|
||||||
<ImageMetadataJSON jsonObject={graph} label="Graph" />
|
<DataViewer data={graph} label="Graph" />
|
||||||
) : (
|
) : (
|
||||||
<IAINoContentFallback label="No graph found" />
|
<IAINoContentFallback label="No graph found" />
|
||||||
)}
|
)}
|
||||||
|
@ -5,6 +5,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { map } from 'lodash-es';
|
import { map } from 'lodash-es';
|
||||||
import ParamLora from './ParamLora';
|
import ParamLora from './ParamLora';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -29,4 +30,4 @@ const ParamLoraList = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ParamLoraList;
|
export default memo(ParamLoraList);
|
||||||
|
@ -9,7 +9,7 @@ import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectI
|
|||||||
import { loraAdded } from 'features/lora/store/loraSlice';
|
import { loraAdded } from 'features/lora/store/loraSlice';
|
||||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||||
import { forEach } from 'lodash-es';
|
import { forEach } from 'lodash-es';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useGetLoRAModelsQuery } from 'services/api/endpoints/models';
|
import { useGetLoRAModelsQuery } from 'services/api/endpoints/models';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
@ -102,4 +102,4 @@ const ParamLoRASelect = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ParamLoRASelect;
|
export default memo(ParamLoRASelect);
|
||||||
|
@ -1,140 +0,0 @@
|
|||||||
import { Flex, Text } from '@chakra-ui/react';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { useAppToaster } from 'app/components/Toaster';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|
||||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
|
||||||
import { map } from 'lodash-es';
|
|
||||||
import { forwardRef, useCallback } from 'react';
|
|
||||||
import 'reactflow/dist/style.css';
|
|
||||||
import { AnyInvocationType } from 'services/events/types';
|
|
||||||
import { useBuildNodeData } from '../hooks/useBuildNodeData';
|
|
||||||
import { nodeAdded } from '../store/nodesSlice';
|
|
||||||
|
|
||||||
type NodeTemplate = {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
description: string;
|
|
||||||
tags: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const selector = createSelector(
|
|
||||||
[stateSelector],
|
|
||||||
({ nodes }) => {
|
|
||||||
const data: NodeTemplate[] = map(nodes.nodeTemplates, (template) => {
|
|
||||||
return {
|
|
||||||
label: template.title,
|
|
||||||
value: template.type,
|
|
||||||
description: template.description,
|
|
||||||
tags: template.tags,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
data.push({
|
|
||||||
label: 'Progress Image',
|
|
||||||
value: 'current_image',
|
|
||||||
description: 'Displays the current image in the Node Editor',
|
|
||||||
tags: ['progress'],
|
|
||||||
});
|
|
||||||
|
|
||||||
data.push({
|
|
||||||
label: 'Notes',
|
|
||||||
value: 'notes',
|
|
||||||
description: 'Add notes about your workflow',
|
|
||||||
tags: ['notes'],
|
|
||||||
});
|
|
||||||
|
|
||||||
return { data };
|
|
||||||
},
|
|
||||||
defaultSelectorOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
const AddNodeMenu = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const { data } = useAppSelector(selector);
|
|
||||||
|
|
||||||
const buildInvocation = useBuildNodeData();
|
|
||||||
|
|
||||||
const toaster = useAppToaster();
|
|
||||||
|
|
||||||
const addNode = useCallback(
|
|
||||||
(nodeType: AnyInvocationType) => {
|
|
||||||
const invocation = buildInvocation(nodeType);
|
|
||||||
|
|
||||||
if (!invocation) {
|
|
||||||
toaster({
|
|
||||||
status: 'error',
|
|
||||||
title: `Unknown Invocation type ${nodeType}`,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(nodeAdded(invocation));
|
|
||||||
},
|
|
||||||
[dispatch, buildInvocation, toaster]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleChange = useCallback(
|
|
||||||
(v: string | null) => {
|
|
||||||
if (!v) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
addNode(v as AnyInvocationType);
|
|
||||||
},
|
|
||||||
[addNode]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex sx={{ gap: 2, alignItems: 'center' }}>
|
|
||||||
<IAIMantineSearchableSelect
|
|
||||||
selectOnBlur={false}
|
|
||||||
placeholder="Add Node"
|
|
||||||
value={null}
|
|
||||||
data={data}
|
|
||||||
maxDropdownHeight={400}
|
|
||||||
nothingFound="No matching nodes"
|
|
||||||
itemComponent={SelectItem}
|
|
||||||
filter={(value, item: NodeTemplate) =>
|
|
||||||
item.label.toLowerCase().includes(value.toLowerCase().trim()) ||
|
|
||||||
item.value.toLowerCase().includes(value.toLowerCase().trim()) ||
|
|
||||||
item.description.toLowerCase().includes(value.toLowerCase().trim()) ||
|
|
||||||
item.tags.includes(value.toLowerCase().trim())
|
|
||||||
}
|
|
||||||
onChange={handleChange}
|
|
||||||
sx={{
|
|
||||||
width: '24rem',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SelectItem = forwardRef<HTMLDivElement, ItemProps>(
|
|
||||||
({ label, description, ...others }: ItemProps, ref) => {
|
|
||||||
return (
|
|
||||||
<div ref={ref} {...others}>
|
|
||||||
<div>
|
|
||||||
<Text fontWeight={600}>{label}</Text>
|
|
||||||
<Text
|
|
||||||
size="xs"
|
|
||||||
sx={{ color: 'base.600', _dark: { color: 'base.500' } }}
|
|
||||||
>
|
|
||||||
{description}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
SelectItem.displayName = 'SelectItem';
|
|
||||||
|
|
||||||
export default AddNodeMenu;
|
|
@ -1,199 +0,0 @@
|
|||||||
import { Badge, Flex } from '@chakra-ui/react';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|
||||||
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
|
||||||
import { memo, useMemo } from 'react';
|
|
||||||
import {
|
|
||||||
BaseEdge,
|
|
||||||
EdgeLabelRenderer,
|
|
||||||
EdgeProps,
|
|
||||||
getBezierPath,
|
|
||||||
} from 'reactflow';
|
|
||||||
import { FIELDS, colorTokenToCssVar } from '../types/constants';
|
|
||||||
import { isInvocationNode } from '../types/types';
|
|
||||||
|
|
||||||
const makeEdgeSelector = (
|
|
||||||
source: string,
|
|
||||||
sourceHandleId: string | null | undefined,
|
|
||||||
target: string,
|
|
||||||
targetHandleId: string | null | undefined,
|
|
||||||
selected?: boolean
|
|
||||||
) =>
|
|
||||||
createSelector(
|
|
||||||
stateSelector,
|
|
||||||
({ nodes }) => {
|
|
||||||
const sourceNode = nodes.nodes.find((node) => node.id === source);
|
|
||||||
const targetNode = nodes.nodes.find((node) => node.id === target);
|
|
||||||
|
|
||||||
const isInvocationToInvocationEdge =
|
|
||||||
isInvocationNode(sourceNode) && isInvocationNode(targetNode);
|
|
||||||
|
|
||||||
const isSelected =
|
|
||||||
sourceNode?.selected || targetNode?.selected || selected;
|
|
||||||
const sourceType = isInvocationToInvocationEdge
|
|
||||||
? sourceNode?.data?.outputs[sourceHandleId || '']?.type
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const stroke =
|
|
||||||
sourceType && nodes.shouldColorEdges
|
|
||||||
? colorTokenToCssVar(FIELDS[sourceType].color)
|
|
||||||
: colorTokenToCssVar('base.500');
|
|
||||||
|
|
||||||
return {
|
|
||||||
isSelected,
|
|
||||||
shouldAnimate: nodes.shouldAnimateEdges && isSelected,
|
|
||||||
stroke,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
defaultSelectorOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
const CollapsedEdge = memo(
|
|
||||||
({
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
sourcePosition,
|
|
||||||
targetPosition,
|
|
||||||
markerEnd,
|
|
||||||
data,
|
|
||||||
selected,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
sourceHandleId,
|
|
||||||
targetHandleId,
|
|
||||||
}: EdgeProps<{ count: number }>) => {
|
|
||||||
const selector = useMemo(
|
|
||||||
() =>
|
|
||||||
makeEdgeSelector(
|
|
||||||
source,
|
|
||||||
sourceHandleId,
|
|
||||||
target,
|
|
||||||
targetHandleId,
|
|
||||||
selected
|
|
||||||
),
|
|
||||||
[selected, source, sourceHandleId, target, targetHandleId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { isSelected, shouldAnimate } = useAppSelector(selector);
|
|
||||||
|
|
||||||
const [edgePath, labelX, labelY] = getBezierPath({
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
sourcePosition,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
targetPosition,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { base500 } = useChakraThemeTokens();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BaseEdge
|
|
||||||
path={edgePath}
|
|
||||||
markerEnd={markerEnd}
|
|
||||||
style={{
|
|
||||||
strokeWidth: isSelected ? 3 : 2,
|
|
||||||
stroke: base500,
|
|
||||||
opacity: isSelected ? 0.8 : 0.5,
|
|
||||||
animation: shouldAnimate
|
|
||||||
? 'dashdraw 0.5s linear infinite'
|
|
||||||
: undefined,
|
|
||||||
strokeDasharray: shouldAnimate ? 5 : 'none',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{data?.count && data.count > 1 && (
|
|
||||||
<EdgeLabelRenderer>
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
||||||
}}
|
|
||||||
className="nodrag nopan"
|
|
||||||
>
|
|
||||||
<Badge
|
|
||||||
variant="solid"
|
|
||||||
sx={{
|
|
||||||
bg: 'base.500',
|
|
||||||
opacity: isSelected ? 0.8 : 0.5,
|
|
||||||
boxShadow: 'base',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{data.count}
|
|
||||||
</Badge>
|
|
||||||
</Flex>
|
|
||||||
</EdgeLabelRenderer>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
CollapsedEdge.displayName = 'CollapsedEdge';
|
|
||||||
|
|
||||||
const DefaultEdge = memo(
|
|
||||||
({
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
sourcePosition,
|
|
||||||
targetPosition,
|
|
||||||
markerEnd,
|
|
||||||
selected,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
sourceHandleId,
|
|
||||||
targetHandleId,
|
|
||||||
}: EdgeProps) => {
|
|
||||||
const selector = useMemo(
|
|
||||||
() =>
|
|
||||||
makeEdgeSelector(
|
|
||||||
source,
|
|
||||||
sourceHandleId,
|
|
||||||
target,
|
|
||||||
targetHandleId,
|
|
||||||
selected
|
|
||||||
),
|
|
||||||
[source, sourceHandleId, target, targetHandleId, selected]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { isSelected, shouldAnimate, stroke } = useAppSelector(selector);
|
|
||||||
|
|
||||||
const [edgePath] = getBezierPath({
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
sourcePosition,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
targetPosition,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseEdge
|
|
||||||
path={edgePath}
|
|
||||||
markerEnd={markerEnd}
|
|
||||||
style={{
|
|
||||||
strokeWidth: isSelected ? 3 : 2,
|
|
||||||
stroke,
|
|
||||||
opacity: isSelected ? 0.8 : 0.5,
|
|
||||||
animation: shouldAnimate
|
|
||||||
? 'dashdraw 0.5s linear infinite'
|
|
||||||
: undefined,
|
|
||||||
strokeDasharray: shouldAnimate ? 5 : 'none',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
DefaultEdge.displayName = 'DefaultEdge';
|
|
||||||
|
|
||||||
export const edgeTypes = {
|
|
||||||
collapsed: CollapsedEdge,
|
|
||||||
default: DefaultEdge,
|
|
||||||
};
|
|
@ -1,9 +0,0 @@
|
|||||||
import CurrentImageNode from './nodes/CurrentImageNode';
|
|
||||||
import InvocationNodeWrapper from './nodes/InvocationNodeWrapper';
|
|
||||||
import NotesNode from './nodes/NotesNode';
|
|
||||||
|
|
||||||
export const nodeTypes = {
|
|
||||||
invocation: InvocationNodeWrapper,
|
|
||||||
current_image: CurrentImageNode,
|
|
||||||
notes: NotesNode,
|
|
||||||
};
|
|
@ -1,69 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
|
||||||
import IAIPopover from 'common/components/IAIPopover';
|
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
|
||||||
import { fieldBooleanValueChanged } from 'features/nodes/store/nodesSlice';
|
|
||||||
import { InvocationNodeData } from 'features/nodes/types/types';
|
|
||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
|
||||||
import { FaBars } from 'react-icons/fa';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
data: InvocationNodeData;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NodeSettings = (props: Props) => {
|
|
||||||
const { data } = props;
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const handleChangeIsIntermediate = useCallback(
|
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
dispatch(
|
|
||||||
fieldBooleanValueChanged({
|
|
||||||
nodeId: data.id,
|
|
||||||
fieldName: 'is_intermediate',
|
|
||||||
value: e.target.checked,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[data.id, dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IAIPopover
|
|
||||||
isLazy={false}
|
|
||||||
triggerComponent={
|
|
||||||
<IAIIconButton
|
|
||||||
className="nopan"
|
|
||||||
aria-label="Node Settings"
|
|
||||||
variant="link"
|
|
||||||
sx={{
|
|
||||||
minW: 8,
|
|
||||||
color: 'base.500',
|
|
||||||
_dark: {
|
|
||||||
color: 'base.500',
|
|
||||||
},
|
|
||||||
_hover: {
|
|
||||||
color: 'base.700',
|
|
||||||
_dark: {
|
|
||||||
color: 'base.300',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
icon={<FaBars />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Flex sx={{ flexDir: 'column', gap: 4, w: 64 }}>
|
|
||||||
<IAISwitch
|
|
||||||
label="Intermediate"
|
|
||||||
isChecked={Boolean(data.inputs['is_intermediate']?.value)}
|
|
||||||
onChange={handleChangeIsIntermediate}
|
|
||||||
helperText="The outputs of intermediate nodes are considered temporary objects. Intermediate images are not added to the gallery."
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</IAIPopover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(NodeSettings);
|
|
@ -6,9 +6,10 @@ import { memo, useState } from 'react';
|
|||||||
import { MdDeviceHub } from 'react-icons/md';
|
import { MdDeviceHub } from 'react-icons/md';
|
||||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import NodeEditorPanelGroup from './panel/NodeEditorPanelGroup';
|
import NodeEditorPanelGroup from './sidePanel/NodeEditorPanelGroup';
|
||||||
import { Flow } from './Flow';
|
import { Flow } from './flow/Flow';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import AddNodePopover from './flow/AddNodePopover/AddNodePopover';
|
||||||
|
|
||||||
const NodeEditor = () => {
|
const NodeEditor = () => {
|
||||||
const [isPanelCollapsed, setIsPanelCollapsed] = useState(false);
|
const [isPanelCollapsed, setIsPanelCollapsed] = useState(false);
|
||||||
@ -33,7 +34,7 @@ const NodeEditor = () => {
|
|||||||
/>
|
/>
|
||||||
<Panel id="node-editor-content">
|
<Panel id="node-editor-content">
|
||||||
<Flex
|
<Flex
|
||||||
layerStyle={'first'}
|
layerStyle="first"
|
||||||
sx={{
|
sx={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: 'full',
|
width: 'full',
|
||||||
@ -57,9 +58,10 @@ const NodeEditor = () => {
|
|||||||
opacity: 0,
|
opacity: 0,
|
||||||
transition: { duration: 0.2 },
|
transition: { duration: 0.2 },
|
||||||
}}
|
}}
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ position: 'relative', width: '100%', height: '100%' }}
|
||||||
>
|
>
|
||||||
<Flow />
|
<Flow />
|
||||||
|
<AddNodePopover />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
@ -80,7 +82,7 @@ const NodeEditor = () => {
|
|||||||
style={{ position: 'absolute', width: '100%', height: '100%' }}
|
style={{ position: 'absolute', width: '100%', height: '100%' }}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
layerStyle={'first'}
|
layerStyle="first"
|
||||||
sx={{
|
sx={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: 'full',
|
width: 'full',
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import { RootState } from 'app/store/store';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import ImageMetadataJSON from 'features/gallery/components/ImageMetadataViewer/ImageMetadataJSON';
|
|
||||||
import { omit } from 'lodash-es';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useDebounce } from 'use-debounce';
|
|
||||||
import { buildNodesGraph } from '../util/graphBuilders/buildNodesGraph';
|
|
||||||
|
|
||||||
const useNodesGraph = () => {
|
|
||||||
const nodes = useAppSelector((state: RootState) => state.nodes);
|
|
||||||
const [debouncedNodes] = useDebounce(nodes, 300);
|
|
||||||
const graph = useMemo(
|
|
||||||
() => omit(buildNodesGraph(debouncedNodes), 'id'),
|
|
||||||
[debouncedNodes]
|
|
||||||
);
|
|
||||||
|
|
||||||
return graph;
|
|
||||||
};
|
|
||||||
|
|
||||||
const NodeGraph = () => {
|
|
||||||
const graph = useNodesGraph();
|
|
||||||
|
|
||||||
return <ImageMetadataJSON jsonObject={graph} label="Graph" />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NodeGraph;
|
|
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